5376 Dmitry Shulga 2013-01-25
Separate trigger loading and parsing. Added DOXYGEN-style comments
for new methods.
added:
sql/table_trigger_dispatcher.cc
sql/table_trigger_dispatcher.h
modified:
sql/trigger.cc
sql/trigger.h
sql/trigger_loader.cc
sql/trigger_loader.h
5375 Dmitry Shulga 2013-01-23
Table_trigger_list was renamed to Table_trigger_dispatcher
removed:
sql/table_trigger_list.cc
sql/table_trigger_list.h
modified:
sql/CMakeLists.txt
sql/item.cc
sql/item.h
sql/ndb_local_schema.cc
sql/sp_head.h
sql/sp_instr.cc
sql/sql_base.cc
sql/sql_base.h
sql/sql_rename.cc
sql/sql_show.cc
sql/sql_table.cc
sql/sql_trigger.cc
sql/table.cc
sql/table.h
sql/trigger.cc
sql/trigger.h
sql/trigger_loader.cc
sql/trigger_loader.h
5374 Alexander Nozdrin 2013-01-23
Remove xxx.empty() as Trigger_loader does not have state.
modified:
sql/table_trigger_list.cc
sql/trigger_loader.cc
5373 Alexander Nozdrin 2013-01-23
Reformat Trigger_loader::parse_triggers() (II).
modified:
sql/trigger_loader.cc
5372 Alexander Nozdrin 2013-01-23
Reformat Trigger_loader::parse_triggers() (I).
modified:
sql/trigger_loader.cc
5371 Alexander Nozdrin 2013-01-23
Reformat Trigger_loader::load_triggers() (II).
modified:
sql/trigger_loader.cc
sql/trigger_loader.h
5370 Alexander Nozdrin 2013-01-23
Reformat Trigger_loader::load_triggers().
modified:
sql/trigger_loader.cc
5369 Alexander Nozdrin 2013-01-23
Join Trigger_loader::load_triggers() and Trigger_loader::parse_triggers().
modified:
sql/table_trigger_list.cc
sql/trigger_loader.cc
sql/trigger_loader.h
5368 Alexander Nozdrin 2013-01-23
A step to join Trigger_loader::load_triggers() and Trigger_loader::parse_triggers().
modified:
sql/table_trigger_list.cc
5367 Alexander Nozdrin 2013-01-23
Move parse_triggers() to Trigger_loader.
added:
sql/trigger_creation_ctx.cc
sql/trigger_creation_ctx.h
modified:
sql/CMakeLists.txt
sql/sql_trigger.cc
sql/sql_trigger.h
sql/table_trigger_list.cc
sql/table_trigger_list.h
sql/trigger.cc
sql/trigger_loader.cc
sql/trigger_loader.h
5366 Alexander Nozdrin 2013-01-23
Getting rid of names_only parameter (Final step).
modified:
sql/table_trigger_list.cc
sql/table_trigger_list.h
5365 Alexander Nozdrin 2013-01-22
Getting rid of names_only parameter (Step 2).
modified:
sql/table_trigger_list.cc
sql/trigger.cc
sql/trigger.h
5364 Alexander Nozdrin 2013-01-22
Getting rid of names_only parameter (Step 1); Polishing.
modified:
sql/table_trigger_list.cc
sql/table_trigger_list.h
5363 Alexander Nozdrin 2013-01-22
Make Table_loader::check_uniqueness() static.
modified:
sql/table_trigger_list.cc
sql/trigger_loader.cc
sql/trigger_loader.h
5362 Alexander Nozdrin 2013-01-22
Rename Table_triggers_list to Table_trigger_list.
modified:
sql/item.cc
sql/item.h
sql/ndb_local_schema.cc
sql/sp_head.h
sql/sp_instr.cc
sql/sql_base.cc
sql/sql_base.h
sql/sql_rename.cc
sql/sql_show.cc
sql/sql_table.cc
sql/sql_trigger.cc
sql/table.cc
sql/table.h
sql/table_trigger_list.cc
sql/table_trigger_list.h
sql/trigger.cc
sql/trigger.h
sql/trigger_loader.cc
5361 Alexander Nozdrin 2013-01-22
Extract Table_triggers_list into a separate file (table_trigger_list.h/.cc).
added:
sql/table_trigger_list.cc
sql/table_trigger_list.h
modified:
sql/CMakeLists.txt
sql/item.h
sql/sql_trigger.cc
sql/sql_trigger.h
5360 Alexander Nozdrin 2013-01-22
Get rid of Table_triggers_list::trigger_loader
modified:
sql/sql_trigger.cc
sql/sql_trigger.h
5359 Alexander Nozdrin 2013-01-22
Extract Trigger_loader into trigger_loader.h/.cc. Tests pass.
added:
sql/trigger_loader.cc
sql/trigger_loader.h
modified:
sql/CMakeLists.txt
sql/sql_show.cc
sql/sql_trigger.cc
sql/sql_trigger.h
sql/trigger.h
5358 Alexander Nozdrin 2013-01-22
Extract Trigger into trigger.h/.cc
added:
sql/trigger.cc
sql/trigger.h
modified:
sql/CMakeLists.txt
sql/sql_class.h
sql/sql_trigger.cc
sql/sql_trigger.h
sql/table.h
5357 Alexander Nozdrin 2013-01-22
Fix build failure
modified:
sql/sql_trigger.cc
5356 Dmitry Shulga 2013-01-22
Thhis is a refactoring of triggers support.
modified:
sql/sql_class.h
sql/sql_trigger.cc
sql/sql_trigger.h
=== modified file 'sql/CMakeLists.txt'
--- a/sql/CMakeLists.txt 2012-11-21 12:44:48 +0000
+++ b/sql/CMakeLists.txt 2013-01-23 15:11:25 +0000
@@ -160,10 +160,14 @@ SET(SQL_SHARED_SOURCES
sql_view.cc
strfunc.cc
sys_vars.cc
- table.cc
table_cache.cc
+ table.cc
+ table_trigger_dispatcher.cc
thr_malloc.cc
transaction.cc
+ trigger.cc
+ trigger_creation_ctx.cc
+ trigger_loader.cc
tztime.cc
uniques.cc
unireg.cc
=== modified file 'sql/item.cc'
--- a/sql/item.cc 2012-12-13 11:16:18 +0000
+++ b/sql/item.cc 2013-01-23 15:11:25 +0000
@@ -8132,9 +8132,10 @@ void Item_insert_value::print(String *st
this stage we can't say exactly what Field object (corresponding
to TABLE::record[0] or TABLE::record[1]) should be bound to this
Item, we only find out index of the Field and then select concrete
- Field object in fix_fields() (by that time Table_trigger_list::old_field/
- new_field should point to proper array of Fields).
- It also binds Item_trigger_field to Table_triggers_list object for
+ Field object in fix_fields() (by that time
+ Table_trigger_dispatcher::old_field/new_field should point to proper
+ array of Fields).
+ It also binds Item_trigger_field to Table_trigger_dispatcher object for
table of trigger which uses this item.
*/
@@ -8145,7 +8146,7 @@ void Item_trigger_field::setup_field(THD
It is too early to mark fields used here, because before execution
of statement that will invoke trigger other statements may use same
TABLE object, so all such mark-up will be wiped out.
- So instead we do it in Table_triggers_list::mark_fields_used()
+ So instead we do it in Table_trigger_dispatcher::mark_fields_used()
method which is called during execution of these statements.
*/
enum_mark_columns save_mark_used_columns= thd->mark_used_columns;
=== modified file 'sql/item.h'
--- a/sql/item.h 2012-12-11 11:58:31 +0000
+++ b/sql/item.h 2013-01-23 15:11:25 +0000
@@ -24,7 +24,7 @@
#include "thr_malloc.h" /* sql_calloc */
#include "field.h" /* Derivation */
#include "sql_array.h"
-#include "sql_trigger.h"
+#include "table_trigger_dispatcher.h"
class Protocol;
struct TABLE_LIST;
@@ -3990,8 +3990,6 @@ public:
};
-class Table_triggers_list;
-
/*
Represents NEW/OLD version of field of row which is
changed/read in trigger.
@@ -4014,8 +4012,8 @@ public:
Item_trigger_field *next_trg_field;
/* Index of the field in the TABLE::field array */
uint field_idx;
- /* Pointer to Table_trigger_list object for table of this trigger */
- Table_triggers_list *triggers;
+ /* Pointer to Table_trigger_dispatcher object for table of this trigger */
+ Table_trigger_dispatcher *triggers;
Item_trigger_field(Name_resolution_context *context_arg,
row_version_type row_ver_arg,
=== modified file 'sql/ndb_local_schema.cc'
--- a/sql/ndb_local_schema.cc 2012-11-23 10:59:29 +0000
+++ b/sql/ndb_local_schema.cc 2013-01-23 15:11:25 +0000
@@ -246,7 +246,7 @@ Ndb_local_schema::Table::remove_table(vo
strmov(db_name_buf, m_db);
strmov(table_name_buf, m_name);
- if (Table_triggers_list::drop_all_triggers(m_thd,
+ if (Table_trigger_dispatcher::drop_all_triggers(m_thd,
db_name_buf,
table_name_buf))
{
@@ -273,9 +273,9 @@ Ndb_local_schema::Table::rename_table(co
}
else
{
- if (Table_triggers_list::change_table_name(m_thd,
- m_db, m_name, m_name,
- new_db, new_name))
+ if (Table_trigger_dispatcher::change_table_name(m_thd,
+ m_db, m_name, m_name,
+ new_db, new_name))
{
log_warning("Failed to rename all triggers");
}
=== modified file 'sql/sp_head.h'
--- a/sql/sp_head.h 2012-11-06 14:16:49 +0000
+++ b/sql/sp_head.h 2013-01-23 15:11:25 +0000
@@ -578,8 +578,8 @@ public:
/// Trigger characteristics.
st_trg_chistics m_trg_chistics;
- /// The Table_triggers_list instance, where this trigger belongs to.
- class Table_triggers_list *m_trg_list;
+ /// The Table_trigger_dispatcher instance, where this trigger belongs to.
+ class Table_trigger_dispatcher *m_trg_list;
public:
static void *operator new(size_t size) throw ();
=== modified file 'sql/sp_instr.cc'
--- a/sql/sp_instr.cc 2012-11-09 09:18:49 +0000
+++ b/sql/sp_instr.cc 2013-01-23 15:11:25 +0000
@@ -515,7 +515,7 @@ LEX *sp_lex_instr::parse_expr(THD *thd,
execution.
*/
- Table_triggers_list *ttl= sp->m_trg_list;
+ Table_trigger_dispatcher *ttl= sp->m_trg_list;
int event= sp->m_trg_chistics.event;
int action_time= sp->m_trg_chistics.action_time;
GRANT_INFO *grant_table= &ttl->subject_table_grants[event][action_time];
=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc 2012-12-21 09:22:13 +0000
+++ b/sql/sql_base.cc 2013-01-23 15:11:25 +0000
@@ -3767,8 +3767,8 @@ err:
static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry)
{
- if (Table_triggers_list::check_n_load(thd, share->db.str,
- share->table_name.str, entry, 0))
+ if (Table_trigger_dispatcher::check_n_load(thd, share->db.str,
+ share->table_name.str, entry, 0))
return TRUE;
/*
@@ -8940,7 +8940,7 @@ inline bool command_invokes_insert_trigg
@retval true Error occurred
*/
inline bool call_before_insert_triggers(THD *thd,
- Table_triggers_list *triggers,
+ Table_trigger_dispatcher *triggers,
enum trg_event_type event,
MY_BITMAP *insert_into_fields_bitmap)
{
@@ -8983,7 +8983,7 @@ inline bool call_before_insert_triggers(
bool
fill_record_n_invoke_before_triggers(THD *thd, List<Item> &fields,
List<Item> &values, bool ignore_errors,
- Table_triggers_list *triggers,
+ Table_trigger_dispatcher *triggers,
enum trg_event_type event,
int num_fields)
{
@@ -9138,7 +9138,7 @@ err:
bool
fill_record_n_invoke_before_triggers(THD *thd, Field **ptr,
List<Item> &values, bool ignore_errors,
- Table_triggers_list *triggers,
+ Table_trigger_dispatcher *triggers,
enum trg_event_type event,
int num_fields)
{
=== modified file 'sql/sql_base.h'
--- a/sql/sql_base.h 2012-12-11 18:12:13 +0000
+++ b/sql/sql_base.h 2013-01-23 15:11:25 +0000
@@ -175,13 +175,13 @@ void close_thread_tables(THD *thd);
bool fill_record_n_invoke_before_triggers(THD *thd, List<Item> &fields,
List<Item> &values,
bool ignore_errors,
- Table_triggers_list *triggers,
+ Table_trigger_dispatcher *triggers,
enum trg_event_type event,
int num_fields);
bool fill_record_n_invoke_before_triggers(THD *thd, Field **field,
List<Item> &values,
bool ignore_errors,
- Table_triggers_list *triggers,
+ Table_trigger_dispatcher *triggers,
enum trg_event_type event,
int num_fields);
bool insert_fields(THD *thd, Name_resolution_context *context,
=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h 2013-01-22 09:24:23 +0000
+++ b/sql/sql_class.h 2013-01-22 10:39:43 +0000
@@ -5138,6 +5138,21 @@ inline bool add_group_to_list(THD *thd,
return thd->lex->current_select->add_group_to_list(thd, item, asc);
}
+/*************************************************************************/
+
+template <class T>
+inline T *alloc_type(MEM_ROOT *m)
+{
+ return (T *) alloc_root(m, sizeof (T));
+}
+
+inline LEX_STRING *alloc_lex_string(MEM_ROOT *m)
+{
+ return alloc_type<LEX_STRING>(m);
+}
+
+/*************************************************************************/
+
#endif /* MYSQL_SERVER */
#endif /* SQL_CLASS_INCLUDED */
=== modified file 'sql/sql_rename.cc'
--- a/sql/sql_rename.cc 2012-11-21 15:32:54 +0000
+++ b/sql/sql_rename.cc 2013-01-23 15:11:25 +0000
@@ -281,11 +281,13 @@ do_rename(THD *thd, TABLE_LIST *ren_tabl
if (!(rc= mysql_rename_table(hton, ren_table->db, old_alias,
new_db, new_alias, 0)))
{
- if ((rc= Table_triggers_list::change_table_name(thd, ren_table->db,
- old_alias,
- ren_table->table_name,
- new_db,
- new_alias)))
+ if ((rc= Table_trigger_dispatcher::change_table_name(
+ thd,
+ ren_table->db,
+ old_alias,
+ ren_table->table_name,
+ new_db,
+ new_alias)))
{
/*
We've succeeded in renaming table's .frm and in updating
=== modified file 'sql/sql_show.cc'
--- a/sql/sql_show.cc 2012-11-21 15:32:54 +0000
+++ b/sql/sql_show.cc 2013-01-23 15:11:25 +0000
@@ -94,6 +94,20 @@ enum enum_i_s_events_fields
ISE_DB_CL
};
+
+static const LEX_STRING trg_action_time_type_names[]=
+{
+ { C_STRING_WITH_LEN("BEFORE") },
+ { C_STRING_WITH_LEN("AFTER") }
+};
+
+static const LEX_STRING trg_event_type_names[]=
+{
+ { C_STRING_WITH_LEN("INSERT") },
+ { C_STRING_WITH_LEN("UPDATE") },
+ { C_STRING_WITH_LEN("DELETE") }
+};
+
#ifndef NO_EMBEDDED_ACCESS_CHECKS
static const char *grant_names[]={
"select","insert","update","delete","create","drop","reload","shutdown",
@@ -3716,8 +3730,8 @@ static int fill_schema_table_from_frm(TH
if (schema_table->i_s_requested_object & OPEN_TRIGGER_ONLY)
{
init_sql_alloc(&tbl.mem_root, TABLE_ALLOC_BLOCK_SIZE, 0);
- if (!Table_triggers_list::check_n_load(thd, db_name->str,
- table_name->str, &tbl, 1))
+ if (!Table_trigger_dispatcher::check_n_load(thd, db_name->str,
+ table_name->str, &tbl, 1))
{
table_list.table= &tbl;
res= schema_table->process_table(thd, &table_list, table,
@@ -5655,7 +5669,7 @@ static int get_schema_triggers_record(TH
}
if (!tables->view && tables->table->triggers)
{
- Table_triggers_list *triggers= tables->table->triggers;
+ Table_trigger_dispatcher *triggers= tables->table->triggers;
int event, timing;
if (check_table_access(thd, TRIGGER_ACL, tables, FALSE, 1, TRUE))
@@ -8140,7 +8154,7 @@ int finalize_schema_table(st_plugin_int
*/
static bool show_create_trigger_impl(THD *thd,
- Table_triggers_list *triggers,
+ Table_trigger_dispatcher *triggers,
int trigger_idx)
{
int ret_code;
@@ -8333,7 +8347,7 @@ bool show_create_trigger(THD *thd, const
{
TABLE_LIST *lst= get_trigger_table(thd, trg_name);
uint num_tables; /* NOTE: unused, only to pass to open_tables(). */
- Table_triggers_list *triggers;
+ Table_trigger_dispatcher *triggers;
int trigger_idx;
bool error= TRUE;
@@ -8353,7 +8367,7 @@ bool show_create_trigger(THD *thd, const
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
/*
- Open the table by name in order to load Table_triggers_list object.
+ Open the table by name in order to load Table_trigger_dispatcher object.
*/
if (open_tables(thd, &lst, &num_tables,
MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL))
=== modified file 'sql/sql_table.cc'
--- a/sql/sql_table.cc 2012-12-27 02:11:06 +0000
+++ b/sql/sql_table.cc 2013-01-23 15:11:25 +0000
@@ -2467,8 +2467,9 @@ int mysql_rm_table_no_locks(THD *thd, TA
if (!(new_error= mysql_file_delete(key_file_frm, path, MYF(MY_WME))))
{
non_tmp_table_deleted= TRUE;
- new_error= Table_triggers_list::drop_all_triggers(thd, db,
- table->table_name);
+ new_error=
+ Table_trigger_dispatcher::drop_all_triggers(thd, db,
+ table->table_name);
}
error|= new_error;
}
@@ -6524,12 +6525,12 @@ static bool mysql_inplace_alter_table(TH
*/
DBUG_RETURN(true);
}
- if (Table_triggers_list::change_table_name(thd,
- alter_ctx->db,
- alter_ctx->alias,
- alter_ctx->table_name,
- alter_ctx->new_db,
- alter_ctx->new_alias))
+ if (Table_trigger_dispatcher::change_table_name(thd,
+ alter_ctx->db,
+ alter_ctx->alias,
+ alter_ctx->table_name,
+ alter_ctx->new_db,
+ alter_ctx->new_alias))
{
/*
If the rename of trigger files fails, try to rename the table
@@ -7468,12 +7469,12 @@ simple_rename_or_index_change(THD *thd,
if (mysql_rename_table(old_db_type, alter_ctx->db, alter_ctx->table_name,
alter_ctx->new_db, alter_ctx->new_alias, 0))
error= -1;
- else if (Table_triggers_list::change_table_name(thd,
- alter_ctx->db,
- alter_ctx->alias,
- alter_ctx->table_name,
- alter_ctx->new_db,
- alter_ctx->new_alias))
+ else if (Table_trigger_dispatcher::change_table_name(thd,
+ alter_ctx->db,
+ alter_ctx->alias,
+ alter_ctx->table_name,
+ alter_ctx->new_db,
+ alter_ctx->new_alias))
{
(void) mysql_rename_table(old_db_type,
alter_ctx->new_db, alter_ctx->new_alias,
@@ -8358,12 +8359,12 @@ bool mysql_alter_table(THD *thd,char *ne
// Check if we renamed the table and if so update trigger files.
if (alter_ctx.is_table_renamed() &&
- Table_triggers_list::change_table_name(thd,
- alter_ctx.db,
- alter_ctx.alias,
- alter_ctx.table_name,
- alter_ctx.new_db,
- alter_ctx.new_alias))
+ Table_trigger_dispatcher::change_table_name(thd,
+ alter_ctx.db,
+ alter_ctx.alias,
+ alter_ctx.table_name,
+ alter_ctx.new_db,
+ alter_ctx.new_alias))
{
// Rename succeeded, delete the new table.
(void) quick_rm_table(thd, new_db_type,
=== modified file 'sql/sql_trigger.cc'
--- a/sql/sql_trigger.cc 2013-01-22 09:24:23 +0000
+++ b/sql/sql_trigger.cc 2013-01-23 15:11:25 +0000
@@ -15,279 +15,23 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
-#define MYSQL_LEX 1
-#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */
-#include "sql_priv.h"
-#include "unireg.h"
-#include "sp_head.h"
+#include "my_global.h" // NO_EMBEDDED_ACCESS_CHECKS
#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>
+#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 "sql_parse.h" // check_table_access()
+#include "trigger_loader.h" // Trigger_loader
-/*************************************************************************/
-
-template <class T>
-inline T *alloc_type(MEM_ROOT *m)
-{
- return (T *) alloc_root(m, sizeof (T));
-}
-
-/*
- NOTE: Since alloc_type() is declared as inline, alloc_root() calls should
- be inlined by the compiler. So, implementation of alloc_root() is not
- needed. However, let's put the implementation in object file just in case
- of stupid MS or other old compilers.
-*/
-
-template LEX_STRING *alloc_type<LEX_STRING>(MEM_ROOT *m);
-template ulonglong *alloc_type<ulonglong>(MEM_ROOT *m);
-
-inline LEX_STRING *alloc_lex_string(MEM_ROOT *m)
-{
- return alloc_type<LEX_STRING>(m);
-}
-
-/*************************************************************************/
-/**
- Trigger_creation_ctx -- creation context of triggers.
-*/
-
-class Trigger_creation_ctx : public Stored_program_creation_ctx,
- public Sql_alloc
-{
-public:
- static Trigger_creation_ctx *create(THD *thd,
- const char *db_name,
- const char *table_name,
- const LEX_STRING *client_cs_name,
- const LEX_STRING *connection_cl_name,
- const LEX_STRING *db_cl_name);
-
-public:
- virtual Stored_program_creation_ctx *clone(MEM_ROOT *mem_root)
- {
- return new (mem_root) Trigger_creation_ctx(m_client_cs,
- m_connection_cl,
- m_db_cl);
- }
-
-protected:
- virtual Object_creation_ctx *create_backup_ctx(THD *thd) const
- {
- return new Trigger_creation_ctx(thd);
- }
-
-private:
- Trigger_creation_ctx(THD *thd)
- :Stored_program_creation_ctx(thd)
- { }
-
- Trigger_creation_ctx(const CHARSET_INFO *client_cs,
- const CHARSET_INFO *connection_cl,
- const CHARSET_INFO *db_cl)
- :Stored_program_creation_ctx(client_cs, connection_cl, db_cl)
- { }
-};
-
-/**************************************************************************
- Trigger_creation_ctx implementation.
-**************************************************************************/
-
-Trigger_creation_ctx *
-Trigger_creation_ctx::create(THD *thd,
- const char *db_name,
- const char *table_name,
- const LEX_STRING *client_cs_name,
- const LEX_STRING *connection_cl_name,
- const LEX_STRING *db_cl_name)
-{
- const CHARSET_INFO *client_cs;
- const CHARSET_INFO *connection_cl;
- const CHARSET_INFO *db_cl;
-
- bool invalid_creation_ctx= FALSE;
-
- if (resolve_charset(client_cs_name->str,
- thd->variables.character_set_client,
- &client_cs))
- {
- sql_print_warning("Trigger for table '%s'.'%s': "
- "invalid character_set_client value (%s).",
- (const char *) db_name,
- (const char *) table_name,
- (const char *) client_cs_name->str);
-
- invalid_creation_ctx= TRUE;
- }
-
- if (resolve_collation(connection_cl_name->str,
- thd->variables.collation_connection,
- &connection_cl))
- {
- sql_print_warning("Trigger for table '%s'.'%s': "
- "invalid collation_connection value (%s).",
- (const char *) db_name,
- (const char *) table_name,
- (const char *) connection_cl_name->str);
-
- invalid_creation_ctx= TRUE;
- }
-
- if (resolve_collation(db_cl_name->str, NULL, &db_cl))
- {
- sql_print_warning("Trigger for table '%s'.'%s': "
- "invalid database_collation value (%s).",
- (const char *) db_name,
- (const char *) table_name,
- (const char *) db_cl_name->str);
-
- invalid_creation_ctx= TRUE;
- }
-
- if (invalid_creation_ctx)
- {
- push_warning_printf(thd,
- Sql_condition::SL_WARNING,
- ER_TRG_INVALID_CREATION_CTX,
- ER(ER_TRG_INVALID_CREATION_CTX),
- (const char *) db_name,
- (const char *) table_name);
- }
-
- /*
- If we failed to resolve the database collation, load the default one
- from the disk.
- */
-
- if (!db_cl)
- db_cl= get_default_db_collation(thd, db_name);
-
- return new Trigger_creation_ctx(client_cs, connection_cl, db_cl);
-}
-
-/*************************************************************************/
-
-const char * const TRG_EXT= ".TRG";
-
-File_option sql_modes_parameters=
-{
- { C_STRING_WITH_LEN("sql_modes") },
- my_offsetof(class Triggers_definition_loader, definition_modes_list),
- FILE_OPTIONS_ULLLIST
-};
-
-
-/*
- Structure representing contents of .TRN file which are used to support
- database wide trigger namespace.
-*/
-
-struct st_trigname
-{
- LEX_STRING trigger_table;
-};
+///////////////////////////////////////////////////////////////////////////
const char * const TRN_EXT= ".TRN";
+const char * const TRG_EXT= ".TRG";
-const LEX_STRING trg_action_time_type_names[]=
-{
- { C_STRING_WITH_LEN("BEFORE") },
- { C_STRING_WITH_LEN("AFTER") }
-};
-
-const LEX_STRING trg_event_type_names[]=
-{
- { C_STRING_WITH_LEN("INSERT") },
- { C_STRING_WITH_LEN("UPDATE") },
- { C_STRING_WITH_LEN("DELETE") }
-};
-
-
-class Handle_old_incorrect_sql_modes_hook: public Unknown_key_hook
-{
-private:
- char *path;
-public:
- Handle_old_incorrect_sql_modes_hook(char *file_path)
- :path(file_path)
- {};
- virtual bool process_unknown_string(const char *&unknown_key, uchar* base,
- MEM_ROOT *mem_root, const char *end);
-};
-
-class Handle_old_incorrect_trigger_table_hook: public Unknown_key_hook
-{
-public:
- Handle_old_incorrect_trigger_table_hook(char *file_path,
- LEX_STRING *trigger_table_arg)
- :path(file_path), trigger_table_value(trigger_table_arg)
- {};
- virtual bool process_unknown_string(const char *&unknown_key, uchar* base,
- MEM_ROOT *mem_root, const char *end);
-private:
- char *path;
- LEX_STRING *trigger_table_value;
-};
-
-
-/**
- An error handler that catches all non-OOM errors which can occur during
- parsing of trigger body. Such errors are ignored and corresponding error
- message is used to construct a more verbose error message which contains
- name of problematic trigger. This error message is later emitted when
- one tries to perform DML or some of DDL on this table.
- Also, if possible, grabs name of the trigger being parsed so it can be
- used to correctly drop problematic trigger.
-*/
-class Deprecated_trigger_syntax_handler : public Internal_error_handler
-{
-private:
-
- char m_message[MYSQL_ERRMSG_SIZE];
- LEX_STRING *m_trigger_name;
-
-public:
-
- Deprecated_trigger_syntax_handler() : m_trigger_name(NULL) {}
-
- virtual bool handle_condition(THD *thd,
- uint sql_errno,
- const char* sqlstate,
- Sql_condition::enum_severity_level level,
- const char* message,
- Sql_condition ** cond_hdl)
- {
- if (sql_errno != EE_OUTOFMEMORY &&
- sql_errno != ER_OUT_OF_RESOURCES)
- {
- if(thd->lex->spname)
- m_trigger_name= &thd->lex->spname->m_name;
- if (m_trigger_name)
- my_snprintf(m_message, sizeof(m_message),
- ER(ER_ERROR_IN_TRIGGER_BODY),
- m_trigger_name->str, message);
- else
- my_snprintf(m_message, sizeof(m_message),
- ER(ER_ERROR_IN_UNKNOWN_TRIGGER_BODY), message);
- return true;
- }
- return false;
- }
-
- LEX_STRING *get_trigger_name() { return m_trigger_name; }
- char *get_error_message() { return m_message; }
-};
-
+///////////////////////////////////////////////////////////////////////////
/**
Create or drop trigger for table.
@@ -299,8 +43,8 @@ public:
@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_triggers_list
- methods.
+ 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.
@@ -484,7 +228,8 @@ bool mysql_create_or_drop_trigger(THD *t
goto end;
}
- if (!(table->triggers= new (&table->mem_root) Table_triggers_list(table)))
+ if (!(table->triggers=
+ new (&table->mem_root) Table_trigger_dispatcher(table)))
goto end;
}
@@ -536,2108 +281,68 @@ end:
}
-bool Table_triggers_list::parse_trigger_definitions(THD *thd, TABLE *table,
- const char *db,
- const char *table_name,
- bool names_only)
-{
- DBUG_ENTER("Table_triggers_list::parse_trigger_definitions");
-
- LEX_STRING save_db;
- PSI_statement_locker *parent_locker= thd->m_statement_psi;
- List_iterator_fast<LEX_STRING> it(trigger_loader.definitions_list);
- List_iterator_fast<sql_mode_t> itm(trigger_loader.definition_modes_list);
- List_iterator_fast<LEX_STRING> it_definer(trigger_loader.definers_list);
- List_iterator_fast<LEX_STRING> it_client_cs_name(trigger_loader.client_cs_names);
- List_iterator_fast<LEX_STRING> it_connection_cl_name(trigger_loader.connection_cl_names);
- List_iterator_fast<LEX_STRING> it_db_cl_name(trigger_loader.db_cl_names);
- LEX *old_lex= thd->lex, lex;
- sp_rcontext *sp_runtime_ctx_saved= thd->sp_runtime_ctx;
- sql_mode_t save_sql_mode= thd->variables.sql_mode;
-
- /*
- TODO: This could be avoided if there is no triggers
- for UPDATE and DELETE.
- */
- if (!names_only && prepare_record1_accessors())
- DBUG_RETURN(true);
-
- thd->lex= &lex;
-
- save_db.str= thd->db;
- save_db.length= thd->db_length;
- thd->reset_db((char*) db, strlen(db));
-
- LEX_STRING *trg_create_str, *trg_definer;
- LEX_STRING *client_cs_name, *connection_cl_name, *db_cl_name;
- sql_mode_t *trg_sql_mode;
-
- LEX_STRING *db_str= alloc_lex_string(&table->mem_root);
- lex_string_set(db_str, db);
- LEX_STRING *table_name_str= alloc_lex_string(&table->mem_root);
- lex_string_set(table_name_str, table_name);
- while ((trg_create_str= it++))
- {
- trg_sql_mode= itm++;
- trg_definer= it_definer++;
-
- thd->variables.sql_mode= *trg_sql_mode;
-
- Parser_state parser_state;
- if (parser_state.init(thd, trg_create_str->str, trg_create_str->length))
- goto err_with_lex_cleanup;
-
- client_cs_name= it_client_cs_name++;
- connection_cl_name= it_connection_cl_name++;
- db_cl_name= it_db_cl_name++;
-
- Trigger_creation_ctx *creation_ctx=
- Trigger_creation_ctx::create(thd,
- db,
- table_name,
- client_cs_name,
- connection_cl_name,
- db_cl_name);
-
- lex_start(thd);
- thd->sp_runtime_ctx= NULL;
-
- Deprecated_trigger_syntax_handler error_handler;
- thd->push_internal_handler(&error_handler);
- thd->m_statement_psi= NULL;
- bool parse_error= parse_sql(thd, & parser_state, creation_ctx);
- thd->m_statement_psi= parent_locker;
- thd->pop_internal_handler();
-
- /*
- 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_STRING *trigger_name;
- if (parse_error)
- {
- if (!m_has_unparseable_trigger)
- set_parse_error_message(error_handler.get_error_message());
- /* Currently sphead is always set to NULL in case of a parse error */
- DBUG_ASSERT(lex.sphead == NULL);
- if (error_handler.get_trigger_name())
- {
- trigger_name= lex_string_dup(&table->mem_root,
- error_handler.get_trigger_name());
- if (!trigger_name)
- goto err_with_lex_cleanup;
-
- Trigger* new_trigger=
- new (&table->mem_root) Trigger(trigger_name, db_str,
- table_name_str, trg_create_str,
- *trg_sql_mode, trg_definer,
- client_cs_name, connection_cl_name,
- db_cl_name);
- new_trigger->set_has_parse_error();
-
- if (table_triggers.push_back(new_trigger,
- &table->mem_root))
- goto err_with_lex_cleanup;
- }
- lex_end(&lex);
- continue;
- }
-
- trigger_name= lex_string_dup(&table->mem_root, &lex.spname->m_name);
- if (!trigger_name)
- goto err_with_lex_cleanup;
-
- Trigger* new_trigger=
- new (&table->mem_root) Trigger(trigger_name, db_str,
- table_name_str, trg_create_str,
- *trg_sql_mode, trg_definer,
- client_cs_name, connection_cl_name,
- db_cl_name);
-
- if (new_trigger->init(thd, &lex, this, creation_ctx,
- db, table, names_only))
- goto err_with_lex_cleanup;
-
- if (table_triggers.push_back(new_trigger, &table->mem_root))
- goto err_with_lex_cleanup;
-
- bodies[new_trigger->trg_event][new_trigger->trg_action_time]= new_trigger;
-
-#ifndef DBUG_OFF
- /*
- Let us check that we correctly update trigger definitions when we
- rename tables with triggers.
-
- In special cases like "RENAME TABLE `#mysql50#somename` TO `somename`"
- or "ALTER DATABASE `#mysql50#somename` UPGRADE DATA DIRECTORY NAME"
- we might be given table or database name with "#mysql50#" prefix (and
- trigger's definiton contains un-prefixed version of the same name).
- To remove this prefix we use check_n_cut_mysql50_prefix().
- */
-
- char fname[NAME_LEN + 1];
- DBUG_ASSERT((!my_strcasecmp(table_alias_charset, lex.query_tables->db, db) ||
- (check_n_cut_mysql50_prefix(db, fname, sizeof(fname)) &&
- !my_strcasecmp(table_alias_charset, lex.query_tables->db, fname))));
- DBUG_ASSERT((!my_strcasecmp(table_alias_charset, lex.query_tables->table_name, table_name) ||
- (check_n_cut_mysql50_prefix(table_name, fname, sizeof(fname)) &&
- !my_strcasecmp(table_alias_charset, lex.query_tables->table_name, fname))));
-#endif
- lex_end(&lex);
- }
-
- thd->reset_db(save_db.str, save_db.length);
- thd->lex= old_lex;
- thd->sp_runtime_ctx= sp_runtime_ctx_saved;
- thd->variables.sql_mode= save_sql_mode;
-
- DBUG_RETURN(0);
-
-err_with_lex_cleanup:
-// QQ: anything else ?
- lex_end(&lex);
- thd->lex= old_lex;
- thd->sp_runtime_ctx= sp_runtime_ctx_saved;
- thd->variables.sql_mode= save_sql_mode;
- thd->reset_db(save_db.str, save_db.length);
- DBUG_RETURN(1);
-}
-
/**
- Create trigger for table.
-
- @param thd current thread context (including trigger definition in
- LEX)
- @param tables table list containing one open table for which the
- trigger is created.
- @param[out] stmt_query after successful return, this string contains
- well-formed statement for creation this trigger.
+ Find trigger's table from trigger identifier and add it to
+ the statement table list.
- @note
- - Assumes that trigger name is fully qualified.
- - NULL-string means the following LEX_STRING instance:
- { str = 0; length = 0 }.
- - In other words, definer_user and definer_host should contain
- simultaneously NULL-strings (non-SUID/old trigger) or valid strings
- (SUID/new trigger).
+ @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.
- @retval
- False success
- @retval
- True error
+ @return Operation status
+ @retval FALSE On success.
+ @retval TRUE Otherwise.
*/
-bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
- String *stmt_query)
+
+bool add_table_for_trigger(THD *thd,
+ const sp_name *trg_name,
+ bool if_exists,
+ TABLE_LIST **table)
{
LEX *lex= thd->lex;
- TABLE *table= tables->table;
- LEX_STRING *trg_def;
- LEX_STRING definer_user;
- LEX_STRING definer_host;
- sql_mode_t trg_sql_mode;
- LEX_STRING *trg_definer;
- LEX_STRING *trg_client_cs_name;
- LEX_STRING *trg_connection_cl_name;
- LEX_STRING *trg_db_cl_name;
-
- if (check_for_broken_triggers())
- return true;
-
- /* Trigger must be in the same schema as target table. */
- if (my_strcasecmp(table_alias_charset, table->s->db.str,
- lex->spname->m_db.str))
- {
- my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0));
- return true;
- }
+ char trn_path_buff[FN_REFLEN];
+ LEX_STRING trn_path= { trn_path_buff, 0 };
+ LEX_STRING tbl_name= { NULL, 0 };
- if (trigger_loader.check_for_uniqueness(tables->db,
- thd->lex->spname->m_name.str))
- return true;
-
- sp_head *trg= lex->sphead;
- int trg_event= trg->m_trg_chistics.event;
- int trg_action_time= trg->m_trg_chistics.action_time;
+ DBUG_ENTER("add_table_for_trigger");
- /* We don't allow creation of several triggers of the same type yet */
- if (bodies[trg_event][trg_action_time] != NULL)
- {
- my_error(ER_NOT_SUPPORTED_YET, MYF(0),
- "multiple triggers with the same action time"
- " and event for one table");
- return true;
- }
+ build_trn_path(thd, trg_name, &trn_path);
- if (!lex->definer)
+ if (check_trn_exists(&trn_path))
{
- /*
- DEFINER-clause is missing.
-
- If we are in slave thread, this means that we received CREATE TRIGGER
- from the master, that does not support definer in triggers. So, we
- should mark this trigger as non-SUID. Note that this does not happen
- when we parse triggers' definitions during opening .TRG file.
- LEX::definer is ignored in that case.
-
- Otherwise, we should use CURRENT_USER() as definer.
-
- NOTE: when CREATE TRIGGER statement is allowed to be executed in PS/SP,
- it will be required to create the definer below in persistent MEM_ROOT
- of PS/SP.
- */
-
- if (!thd->slave_thread)
+ if (if_exists)
{
- if (!(lex->definer= create_default_definer(thd)))
- return true;
- }
- }
+ push_warning_printf(thd,
+ Sql_condition::SL_NOTE,
+ ER_TRG_DOES_NOT_EXIST,
+ ER(ER_TRG_DOES_NOT_EXIST));
- /*
- If the specified definer differs from the current user, we should check
- that the current user has SUPER privilege (in order to create trigger
- under another user one must have SUPER privilege).
- */
+ *table= NULL;
- if (lex->definer &&
- (strcmp(lex->definer->user.str, thd->security_ctx->priv_user) ||
- my_strcasecmp(system_charset_info,
- lex->definer->host.str,
- thd->security_ctx->priv_host)))
- {
- if (check_global_access(thd, SUPER_ACL))
- {
- my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "SUPER");
- return true;
+ DBUG_RETURN(FALSE);
}
- }
-
- /*
- Let us check if all references to fields in old/new versions of row in
- this trigger are ok.
-
- NOTE: We do it here more from ease of use standpoint. We still have to
- do some checks on each execution. E.g. we can catch privilege changes
- only during execution. Also in near future, when we will allow access
- to other tables from trigger we won't be able to catch changes in other
- tables...
-
- Since we don't plan to access to contents of the fields it does not
- matter that we choose for both OLD and NEW values the same versions
- of Field objects here.
- */
- old_field= new_field= table->field;
-
- for (Item_trigger_field *trg_field= lex->sphead->m_trg_table_fields.first;
- trg_field; trg_field= trg_field->next_trg_field)
- {
- /*
- NOTE: now we do not check privileges at CREATE TRIGGER time. This will
- be changed in the future.
- */
- trg_field->setup_field(thd, table, NULL);
-
- if (!trg_field->fixed &&
- trg_field->fix_fields(thd, (Item **)0))
- return true;
- }
-
-
- /*
- Soon we will invalidate table object and thus Table_triggers_list object
- so don't care about place to which trg_def->ptr points and other
- invariants (e.g. we don't bother to update names_list)
-
- QQ: Hmm... probably we should not care about setting up active thread
- mem_root too.
- */
- if (!(trg_def= alloc_lex_string(&table->mem_root)) ||
- !(trg_definer= alloc_lex_string(&table->mem_root)) ||
- !(trg_client_cs_name= alloc_lex_string(&table->mem_root)) ||
- !(trg_connection_cl_name= alloc_lex_string(&table->mem_root)) ||
- !(trg_db_cl_name= alloc_lex_string(&table->mem_root)))
- {
- return true;
- }
-
- trg_sql_mode= thd->variables.sql_mode;
-
-#ifndef NO_EMBEDDED_ACCESS_CHECKS
- if (lex->definer && !is_acl_user(lex->definer->host.str,
- lex->definer->user.str))
- {
- push_warning_printf(thd,
- Sql_condition::SL_NOTE,
- ER_NO_SUCH_USER,
- ER(ER_NO_SUCH_USER),
- lex->definer->user.str,
- lex->definer->host.str);
- }
-#endif /* NO_EMBEDDED_ACCESS_CHECKS */
-
- if (lex->definer)
- {
- char trg_definer_buf[USER_HOST_BUFF_SIZE];
- /* SUID trigger. */
-
- definer_user= lex->definer->user;
- definer_host= lex->definer->host;
-
- size_t trg_definer_len= strxmov(trg_definer_buf, definer_user.str, "@",
- definer_host.str, NullS) - trg_definer_buf;
- lex_string_copy(&table->mem_root, trg_definer, trg_definer_buf,
- trg_definer_len);
- }
- else
- {
- /* non-SUID trigger. */
-
- definer_user.str= 0;
- definer_user.length= 0;
-
- definer_host.str= 0;
- definer_host.length= 0;
-
- trg_definer->str= (char*) "";
- trg_definer->length= 0;
- }
-
- /*
- Fill character set information:
- - client character set contains charset info only;
- - connection collation contains pair {character set, collation};
- - database collation contains pair {character set, collation};
- */
-
- lex_string_set(trg_client_cs_name, thd->charset()->csname);
-
- lex_string_set(trg_connection_cl_name,
- thd->variables.collation_connection->name);
-
- lex_string_set(trg_db_cl_name,
- get_default_db_collation(thd, tables->db)->name);
-
- /*
- Create well-formed trigger definition query. Original query is not
- appropriated, because definer-clause can be not truncated.
- */
-
- stmt_query->append(STRING_WITH_LEN("CREATE "));
-
- /*
- Append definer-clause if the trigger is SUID (a usual trigger in
- new MySQL versions).
- */
-
- append_definer(thd, stmt_query, &definer_user, &definer_host);
-
- LEX_STRING stmt_definition;
- stmt_definition.str= (char*) thd->lex->stmt_definition_begin;
- stmt_definition.length= thd->lex->stmt_definition_end
- - thd->lex->stmt_definition_begin;
- trim_whitespace(thd->charset(), & stmt_definition);
-
- stmt_query->append(stmt_definition.str, stmt_definition.length);
-
- lex_string_copy(&table->mem_root, trg_def, stmt_query->c_ptr(),
- stmt_query->length());
-
- Trigger *new_trigger=
- new (&table->mem_root) Trigger(&lex->spname->m_name,
- &trigger_table->s->db,
- &trigger_table->s->table_name,
- trg_def, trg_sql_mode,
- trg_definer, trg_client_cs_name,
- trg_connection_cl_name, trg_db_cl_name);
-
- return trigger_loader.store_trigger(tables, new_trigger,
- &table_triggers);
-}
-
-
-bool Trigger::init(THD *thd, LEX *lex, Table_triggers_list* object_owner,
- Stored_program_creation_ctx *trg_creation_ctx,
- const char *db, TABLE *table, bool names_only)
-{
- sp= lex->sphead;
- sp->set_info(0, 0, &lex->sp_chistics, definition_mode);
- sp->m_trg_list= object_owner;
-
- trg_event= sp->m_trg_chistics.event;
- trg_action_time= sp->m_trg_chistics.action_time;
-
- lex->sphead= NULL; /* Prevent double cleanup. */
-
- sp->set_creation_ctx(trg_creation_ctx);
-
- if (!definer->length)
- {
- /*
- This trigger was created/imported from the previous version of
- MySQL, which does not support triggers definers. We should emit
- warning here.
- */
-
- push_warning_printf(thd, Sql_condition::SL_WARNING,
- ER_TRG_NO_DEFINER, ER(ER_TRG_NO_DEFINER),
- (const char*) db,
- (const char*) sp->m_name.str);
-
- /*
- Set definer to the '' to correct displaying in the information
- schema.
- */
-
- sp->set_definer((char*) "", 0);
-
- /*
- Triggers without definer information are executed under the
- authorization of the invoker.
- */
-
- sp->m_chistics->suid= SP_IS_NOT_SUID;
- }
- else
- sp->set_definer(definer->str, definer->length);
-
- if (!(on_table_name= alloc_lex_string(&table->mem_root)))
- return true;
- on_table_name->str= (char*) lex->raw_trg_on_table_name_begin;
- on_table_name->length= lex->raw_trg_on_table_name_end -
- lex->raw_trg_on_table_name_begin;
-
- subject_table_grant= &object_owner->subject_table_grants[trg_event][trg_action_time];
-
- if (!names_only)
- {
- /*
- Also let us bind these objects to Field objects in table being
- opened.
-
- We ignore errors here, because if even something is wrong we still
- will be willing to open table to perform some operations (e.g.
- SELECT)...
- Anyway some things can be checked only during trigger execution.
- */
- for (Item_trigger_field *trg_field= sp->m_trg_table_fields.first;
- trg_field;
- trg_field= trg_field->next_trg_field)
- {
- trg_field->setup_field(thd, table, subject_table_grant);
- }
+ my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
+ DBUG_RETURN(TRUE);
}
- return false;
-}
-
-bool Trigger::execute(THD *thd)
-{
- if (has_parse_error)
- return true;
-
- bool err_status;
- Sub_statement_state statement_state;
- SELECT_LEX *save_current_select;
-
- thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER);
-
- /*
- Reset current_select before call execute_trigger() and
- restore it after return from one. This way error is set
- in case of failure during trigger execution.
- */
- save_current_select= thd->lex->current_select;
- thd->lex->current_select= NULL;
- err_status=
- sp->execute_trigger(thd,
- db_name,
- table_name,
- subject_table_grant);
- thd->lex->current_select= save_current_select;
-
- thd->restore_sub_statement_state(&statement_state);
-
- return err_status;
-}
-
+ if (load_table_name_for_trigger(thd, trg_name, &trn_path, &tbl_name))
+ DBUG_RETURN(TRUE);
-void Trigger::getInfo(LEX_STRING *trg_name,
- LEX_STRING *trigger_stmt,
- sql_mode_t *sql_mode,
- LEX_STRING *trg_definer,
- LEX_STRING *trg_definition,
- LEX_STRING *client_cs_nam,
- LEX_STRING *connection_cl_nam,
- LEX_STRING *db_cl_nam)
-{
- *trg_name= *trigger_name;
- if (trigger_stmt)
- *trigger_stmt= sp->m_body_utf8;
- *sql_mode= definition_mode;
-
- if (trg_definer)
- *trg_definer= *definer;
-
- if (trg_definition)
- *trg_definition= *definition;
-
- *client_cs_nam= *client_cs_name;
- *connection_cl_nam= *connection_cl_name;
- *db_cl_nam= *db_cl_name;
-}
-
-/**
- Table of .TRG file field descriptors.
- We have here only one field now because in nearest future .TRG
- files will be merged into .FRM files (so we don't need something
- like md5 or created fields).
-*/
-File_option Triggers_definition_loader::triggers_file_parameters[]=
-{
- {
- { C_STRING_WITH_LEN("triggers") },
- my_offsetof(class Triggers_definition_loader, definitions_list),
- FILE_OPTIONS_STRLIST
- },
- {
- { C_STRING_WITH_LEN("sql_modes") },
- my_offsetof(class Triggers_definition_loader, definition_modes_list),
- FILE_OPTIONS_ULLLIST
- },
- {
- { C_STRING_WITH_LEN("definers") },
- my_offsetof(class Triggers_definition_loader, definers_list),
- FILE_OPTIONS_STRLIST
- },
- {
- { C_STRING_WITH_LEN("client_cs_names") },
- my_offsetof(class Triggers_definition_loader, client_cs_names),
- FILE_OPTIONS_STRLIST
- },
- {
- { C_STRING_WITH_LEN("connection_cl_names") },
- my_offsetof(class Triggers_definition_loader, connection_cl_names),
- FILE_OPTIONS_STRLIST
- },
- {
- { C_STRING_WITH_LEN("db_cl_names") },
- my_offsetof(class Triggers_definition_loader, db_cl_names),
- FILE_OPTIONS_STRLIST
- },
- { { 0, 0 }, 0, FILE_OPTIONS_STRING }
-};
-
-File_option Triggers_definition_loader::trigname_file_parameters[]=
-{
- {
- { C_STRING_WITH_LEN("trigger_table")},
- offsetof(struct st_trigname, trigger_table),
- FILE_OPTIONS_ESTRING
- },
- { { 0, 0 }, 0, FILE_OPTIONS_STRING }
-};
-
-const LEX_STRING Triggers_definition_loader::triggers_file_type=
- { C_STRING_WITH_LEN("TRIGGERS") };
-
-const LEX_STRING Triggers_definition_loader::trigname_file_type=
- { C_STRING_WITH_LEN("TRIGGERNAME") };
-
-
-bool Triggers_definition_loader::load_triggers(THD *thd, const char *db,
- const char *table_name,
- TABLE *table,
- bool *triggers_not_found)
-{
- char path_buff[FN_REFLEN];
- LEX_STRING path;
- File_parser *parser;
-
- DBUG_ENTER("Triggers_defintion_loader::load_triggers");
-
- path.length= build_table_filename(path_buff, FN_REFLEN - 1,
- db, table_name, TRG_EXT, 0);
- path.str= path_buff;
-
- *triggers_not_found= false;
-
- if (access(path_buff, F_OK))
- {
- if (errno == ENOENT)
- *triggers_not_found= true;
- DBUG_RETURN(true);
- }
-
- /*
- File exists so we got to load triggers.
- FIXME: A lot of things to do here e.g. how about other funcs and being
- more paranoical ?
- */
-
- if ((parser= sql_parse_prepare(&path, &table->mem_root, 1)))
- {
- if (is_equal(&triggers_file_type, parser->type()))
- {
- Handle_old_incorrect_sql_modes_hook sql_modes_hook(path.str);
-
- /*
- We don't have the following attributes in old versions of .TRG file, so
- we should initialize the list for safety:
- - sql_modes;
- - definers;
- - character sets (client, connection, database);
- */
- definition_modes_list.empty();
- definers_list.empty();
- client_cs_names.empty();
- connection_cl_names.empty();
- db_cl_names.empty();
-
- if (parser->parse((uchar*)this, &table->mem_root,
- triggers_file_parameters,
- TRG_NUM_REQUIRED_PARAMETERS,
- &sql_modes_hook))
- DBUG_RETURN(true);
-
- List_iterator_fast<LEX_STRING> it(definitions_list);
-
- if (!definitions_list.is_empty())
- {
- if (definition_modes_list.is_empty())
- {
- /*
- It is old file format => we should fill list of sql_modes.
-
- We use one mode (current) for all triggers, because we have not
- information about mode in old format.
- */
- sql_mode_t *trg_sql_mode;
- if (!(trg_sql_mode= alloc_type<sql_mode_t>(&table->mem_root)))
- {
- DBUG_RETURN(true); // EOM
- }
- *trg_sql_mode= global_system_variables.sql_mode;
- while (it++)
- {
- if (definition_modes_list.push_back(trg_sql_mode,
- &table->mem_root))
- {
- DBUG_RETURN(true); // EOM
- }
- }
- it.rewind();
- }
-
- if (definers_list.is_empty())
- {
- /*
- It is old file format => we should fill list of definers.
-
- If there is no definer information, we should not switch context to
- definer when checking privileges. I.e. privileges for such triggers
- are checked for "invoker" rather than for "definer".
- */
-
- LEX_STRING *trg_definer;
-
- if (!(trg_definer= alloc_lex_string(&table->mem_root)))
- DBUG_RETURN(true); // EOM
-
- trg_definer->str= (char*) "";
- trg_definer->length= 0;
-
- while (it++)
- {
- if (definers_list.push_back(trg_definer,
- &table->mem_root))
- {
- DBUG_RETURN(true); // EOM
- }
- }
- it.rewind();
- }
-
- if (client_cs_names.is_empty() ||
- connection_cl_names.is_empty() ||
- db_cl_names.is_empty())
- {
- LEX_STRING *trg_client_cs_name;
- LEX_STRING *trg_connection_cl_name;
- LEX_STRING *trg_db_cl_name;
-
- if (!client_cs_names.is_empty() ||
- !connection_cl_names.is_empty() ||
- !db_cl_names.is_empty())
- {
- my_error(ER_TRG_CORRUPTED_FILE, MYF(0),
- (const char *) db,
- (const char *) table_name);
-
- DBUG_RETURN(true); // EOM
- }
-
- push_warning_printf(thd, Sql_condition::SL_WARNING,
- ER_TRG_NO_CREATION_CTX,
- ER(ER_TRG_NO_CREATION_CTX),
- (const char*) db,
- (const char*) table_name);
-
- if (!(trg_client_cs_name= alloc_lex_string(&table->mem_root)) ||
- !(trg_connection_cl_name= alloc_lex_string(&table->mem_root)) ||
- !(trg_db_cl_name= alloc_lex_string(&table->mem_root)))
- {
- DBUG_RETURN(true); // EOM
- }
-
- /*
- Backward compatibility: assume that the query is in the current
- character set.
- */
-
- lex_string_set(trg_client_cs_name,
- thd->variables.character_set_client->csname);
-
- lex_string_set(trg_connection_cl_name,
- thd->variables.collation_connection->name);
-
- lex_string_set(trg_db_cl_name,
- thd->variables.collation_database->name);
-
- while (it++)
- {
- if (client_cs_names.push_back(trg_client_cs_name,
- &table->mem_root) ||
- connection_cl_names.push_back(trg_connection_cl_name,
- &table->mem_root) ||
- db_cl_names.push_back(trg_db_cl_name,
- &table->mem_root))
- {
- DBUG_RETURN(true); // EOM
- }
- }
-
- it.rewind();
- }
- }
-
- DBUG_ASSERT(definition_modes_list.elements ==
- definitions_list.elements);
- DBUG_ASSERT(definers_list.elements ==
- definitions_list.elements);
- DBUG_ASSERT(client_cs_names.elements ==
- definitions_list.elements);
- DBUG_ASSERT(connection_cl_names.elements ==
- definitions_list.elements);
- DBUG_ASSERT(db_cl_names.elements ==
- definitions_list.elements);
-
- DBUG_RETURN(false);
- }
-
- /*
- We don't care about this error message much because .TRG files will
- be merged into .FRM anyway.
- */
- my_error(ER_WRONG_OBJECT, MYF(0),
- table_name, TRG_EXT + 1, "TRIGGER");
- DBUG_RETURN(true);
- }
- DBUG_RETURN(true);
-}
-
-
-bool Triggers_definition_loader::store_trigger(TABLE_LIST *tables,
- Trigger *new_trigger,
- List<Trigger> *table_triggers)
-{
- LEX_STRING trigname_file;
- TABLE *table= tables->table;
- char trigname_buff[FN_REFLEN];
- struct st_trigname trigname;
- bool was_truncated;
-
- if (table_triggers->push_back(new_trigger, &table->mem_root))
- return true;
-
- if (update_triggers_definition(tables->table, table_triggers, NULL, NULL))
- return true;
-
- /*
- Here we are creating file with triggers and save all triggers in it.
- sql_create_definition_file() files handles renaming and backup of older
- versions
- */
- trigname.trigger_table.str= tables->table_name;
- trigname.trigger_table.length= tables->table_name_length;
-
- /* Building .TRN trigger filenames */
- trigname_file.length= build_table_filename(trigname_buff, FN_REFLEN-1,
- tables->db,
- new_trigger->trigger_name->str,
- TRN_EXT, 0, &was_truncated);
-
- // Check if we hit FN_REFLEN bytes in path length
- if (was_truncated)
- {
- my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), sizeof(trigname_buff)-1,
- trigname_buff);
- return true;
- }
- trigname_file.str= trigname_buff;
-
- if (sql_create_definition_file(NULL, &trigname_file, &trigname_file_type,
- (uchar*)&trigname, trigname_file_parameters))
- return true;
-
- /* Create trigger definition file. */
- if (save_trigger_file(tables->db, tables->table_name))
- {
- mysql_file_delete(key_file_trn, trigname_buff, MYF(MY_WME));
- return true;
- }
-
- return false;
-}
-
-
-bool Triggers_definition_loader::drop_trigger(TABLE_LIST *tables,
- const char *trigger_name,
- List<Trigger> *table_triggers)
-{
- char path[FN_REFLEN];
-
- bool found= false;
- update_triggers_definition(tables->table, table_triggers,
- trigger_name, &found);
-
- if (found)
- {
- if (table_triggers->is_empty())
- {
- /*
- TODO: Probably instead of removing .TRG file we should move
- to archive directory but this should be done as part of
- parse_file.cc functionality (because we will need it
- elsewhere).
- */
- if (rm_trigger_file(path, tables->db, tables->table_name))
- return true;
- }
- else
- {
- if (save_trigger_file(tables->db, tables->table_name))
- return true;
- }
-
- if (rm_trigname_file(path, tables->db, trigger_name))
- return true;
- return false;
- }
-
- my_message(ER_TRG_DOES_NOT_EXIST, ER(ER_TRG_DOES_NOT_EXIST), MYF(0));
- return true;
-}
-
-
-/**
- This method saves .TRG file for the table specified by arguments.
-
- @param triggers Table_triggers_list object for which file should be saved
- @param db Name of database for subject table
- @param table_name Name of subject table
-
- @retval
- FALSE Success
- @retval
- TRUE Error
-*/
-
-bool Triggers_definition_loader::save_trigger_file(const char *db,
- const char *table_name)
-{
- char file_buff[FN_REFLEN];
- LEX_STRING file;
- bool was_truncated= false;
-
- file.length= build_table_filename(file_buff, FN_REFLEN - 1, db, table_name,
- TRG_EXT, 0, &was_truncated);
-
- if (was_truncated)
- {
- my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), sizeof(file_buff)-1,
- file_buff);
- return true;
- }
-
- file.str= file_buff;
- return sql_create_definition_file(NULL, &file, &triggers_file_type,
- (uchar*)this, triggers_file_parameters);
-}
-
-
-/**
- found has to be set to false before passing to this method.
- name != NULL if it needs to remove trigger definition by its name.
-
- @return Operation status
- @retval false On success.
- @retval true Otherwise.
-*/
-bool Triggers_definition_loader::update_triggers_definition(TABLE *table,
- List<Trigger> *trgs,
- const char *name,
- bool *found)
-{
- List_iterator<Trigger> it_table_triggers(*trgs);
-
- definitions_list.empty();
- definition_modes_list.empty();
- definers_list.empty();
- client_cs_names.empty();
- connection_cl_names.empty();
- db_cl_names.empty();
-
- Trigger *next_trigger;
- while ((next_trigger= it_table_triggers++))
- {
- if (name &&
- my_strcasecmp(table_alias_charset, next_trigger->trigger_name->str,
- name) == 0)
- {
- it_table_triggers.remove();
- *found= true;
- continue;
- }
- if (definitions_list.push_back(next_trigger->definition,
- &table->mem_root) ||
- definition_modes_list.push_back(&next_trigger->definition_mode,
- &table->mem_root) ||
- definers_list.push_back(next_trigger->definer,
- &table->mem_root) ||
- client_cs_names.push_back(next_trigger->client_cs_name,
- &table->mem_root) ||
- connection_cl_names.push_back(next_trigger->connection_cl_name,
- &table->mem_root) ||
- db_cl_names.push_back(next_trigger->db_cl_name, &table->mem_root))
- return true;
- }
-
- return false;
-}
-
-
-bool Triggers_definition_loader::check_for_uniqueness(const char *db_name,
- const char *trigger_name)
-{
- LEX_STRING trigname_file;
- char trigname_buff[FN_REFLEN];
- bool was_truncated;
-
- /* Building .TRN trigger filenames */
- build_table_filename(trigname_buff, FN_REFLEN-1,
- db_name,
- trigger_name,
- TRN_EXT, 0, &was_truncated);
- // Check if we hit FN_REFLEN bytes in path length
- if (was_truncated)
- {
- my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), sizeof(trigname_buff)-1,
- trigname_buff);
- return true;
- }
- trigname_file.str= trigname_buff;
-
- /* Use the filesystem to enforce trigger namespace constraints. */
- if (!access(trigname_buff, F_OK))
- {
- my_error(ER_TRG_ALREADY_EXISTS, MYF(0));
- return true;
- }
-
- return false;
-}
-
-
-bool Triggers_definition_loader::get_table_name_for_trigger(
- THD *thd,
- const sp_name *trg_name,
- const LEX_STRING *trn_path,
- LEX_STRING *tbl_name)
-{
- File_parser *parser;
- struct st_trigname trn_data;
-
- Handle_old_incorrect_trigger_table_hook trigger_table_hook(
- trn_path->str,
- &trn_data.trigger_table);
-
- DBUG_ENTER("load_table_name_for_trigger");
-
- /* Parse the TRN-file. */
-
- if (!(parser= sql_parse_prepare(trn_path, thd->mem_root, TRUE)))
- DBUG_RETURN(TRUE);
-
- if (!is_equal(&trigname_file_type, parser->type()))
- {
- my_error(ER_WRONG_OBJECT, MYF(0),
- trg_name->m_name.str,
- TRN_EXT + 1,
- "TRIGGERNAME");
-
- DBUG_RETURN(TRUE);
- }
-
- if (parser->parse((uchar*) &trn_data, thd->mem_root,
- trigname_file_parameters, 1,
- &trigger_table_hook))
- DBUG_RETURN(TRUE);
-
- /* Copy trigger table name. */
-
- *tbl_name= trn_data.trigger_table;
-
- /* That's all. */
-
- DBUG_RETURN(FALSE);
-}
-
-
-bool Triggers_definition_loader::drop_all_triggers(const char *db_name,
- const char *table_name,
- List<Trigger> &table_trgs)
-{
- Trigger *trigger;
- char path[FN_REFLEN];
- List_iterator_fast<Trigger> it_triggers(table_trgs);
- bool result= false;
-
- while ((trigger= it_triggers++))
- {
- if (rm_trigname_file(path, db_name, trigger->trigger_name->str))
- {
- /*
- Instead of immediately bailing out with error if we were unable
- to remove .TRN file we will try to drop other files.
- */
- result= true;
- continue;
- }
- }
-
- if (rm_trigger_file(path, db_name, table_name))
- result= true;
-
- return result;
-}
-
-
-/**
- Deletes the .TRN file for a trigger.
-
- @param path char buffer of size FN_REFLEN to be used
- for constructing path to .TRN file.
- @param db trigger's database name
- @param trigger_name trigger's name
-
- @retval
- False success
- @retval
- True error
-*/
-
-bool Triggers_definition_loader::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 mysql_file_delete(key_file_trn, path, MYF(MY_WME));
-}
-
-
-/**
- Deletes the .TRG file for a table.
-
- @param path char buffer of size FN_REFLEN to be used
- for constructing path to .TRG file.
- @param db table's database name
- @param table_name table's name
-
- @retval
- False success
- @retval
- True error
-*/
-
-bool Triggers_definition_loader::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 mysql_file_delete(key_file_trg, path, MYF(MY_WME));
-}
-
-
-/**
- Drop trigger for table.
-
- @param thd current thread context
- (including trigger definition in LEX)
- @param tables table list containing one open table for which trigger
- is dropped.
- @param[out] stmt_query after successful return, this string contains
- well-formed statement for creation this trigger.
-
- @todo
- Probably instead of removing .TRG file we should move
- to archive directory but this should be done as part of
- parse_file.cc functionality (because we will need it
- elsewhere).
-
- @retval
- False success
- @retval
- True error
-*/
-bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables,
- String *stmt_query)
-{
- const char *sp_name= thd->lex->spname->m_name.str; // alias
- stmt_query->append(thd->query(), thd->query_length());
-
- return trigger_loader.drop_trigger(tables, sp_name, &table_triggers);
-}
-
-
-Table_triggers_list::~Table_triggers_list()
-{
- for (int i= 0; i < (int)TRG_EVENT_MAX; i++)
- for (int j= 0; j < (int)TRG_ACTION_MAX; j++)
- delete bodies[i][j];
-
- if (record1_field)
- for (Field **fld_ptr= record1_field; *fld_ptr; fld_ptr++)
- delete *fld_ptr;
-}
-
-
-/**
- Prepare array of Field objects referencing to TABLE::record[1] instead
- of record[0] (they will represent OLD.* row values in ON UPDATE trigger
- and in ON DELETE trigger which will be called during REPLACE execution).
-
- @retval
- False success
- @retval
- True error
-*/
-bool Table_triggers_list::prepare_record1_accessors()
-{
- Field **fld, **old_fld;
-
- if (!(record1_field= (Field **)alloc_root(&trigger_table->mem_root,
- (trigger_table->s->fields + 1) *
- sizeof(Field*))))
- return true;
-
- for (fld= trigger_table->field, old_fld= record1_field; *fld; fld++, old_fld++)
- {
- /*
- QQ: it is supposed that it is ok to use this function for field
- cloning...
- */
- if (!(*old_fld= (*fld)->new_field(&trigger_table->mem_root, trigger_table,
- trigger_table == (*fld)->table)))
- return true;
- (*old_fld)->move_field_offset((my_ptrdiff_t)(trigger_table->record[1] -
- trigger_table->record[0]));
- }
- *old_fld= 0;
-
- return false;
-}
-
-
-/**
- Adjust Table_triggers_list with new TABLE pointer.
-
- @param new_table new pointer to TABLE instance
-*/
-
-void Table_triggers_list::set_table(TABLE *new_table)
-{
- trigger_table= new_table;
- for (Field **field= new_table->triggers->record1_field ; *field ; field++)
- {
- (*field)->table= (*field)->orig_table= new_table;
- (*field)->table_name= &new_table->alias;
- }
-}
-
-
-/**
- Check whenever .TRG file for table exist and load all triggers it contains.
-
- @param thd current thread context
- @param db table's database name
- @param table_name table's name
- @param table pointer to table object
- @param names_only stop after loading trigger names
-
- @todo
- A lot of things to do here e.g. how about other funcs and being
- more paranoical ?
-
- @todo
- This could be avoided if there is no triggers for UPDATE and DELETE.
-
- @retval
- False success
- @retval
- True error
-*/
-
-bool Table_triggers_list::check_n_load(THD *thd, const char *db,
- const char *table_name, TABLE *table,
- bool names_only)
-{
- DBUG_ENTER("Table_triggers_list::check_n_load");
-
- //if (Table_triggers_list::)
- Table_triggers_list *triggers=
- new (&table->mem_root) Table_triggers_list(table);
-
- if (!triggers)
- DBUG_RETURN(true);
-
- bool triggers_not_found;
- if (triggers->load_triggers(thd, db, table_name, &triggers_not_found))
- {
- delete triggers;
- if (triggers_not_found)
- DBUG_RETURN(false);
- DBUG_RETURN(true);
- }
-
- table->triggers= triggers;
-
- return triggers->parse_trigger_definitions(thd, table, db,
- table_name, names_only);
-}
-
-
-/**
- Obtains and returns trigger metadata.
-
- @param thd current thread context
- @param event trigger event type
- @param time_type trigger action time
- @param trigger_name returns name of trigger
- @param trigger_stmt returns statement of trigger
- @param sql_mode returns sql_mode of trigger
- @param definer returns definer/creator of trigger. The caller is
- responsible to allocate enough space for storing
- definer information.
-
- @retval
- False success
- @retval
- True error
-*/
-
-bool Table_triggers_list::get_trigger_info(THD *thd, trg_event_type event,
- trg_action_time_type time_type,
- LEX_STRING *trigger_name,
- LEX_STRING *trigger_stmt,
- sql_mode_t *sql_mode,
- LEX_STRING *definer,
- LEX_STRING *client_cs_name,
- LEX_STRING *connection_cl_name,
- LEX_STRING *db_cl_name)
-{
- Trigger *trigger;
- DBUG_ENTER("get_trigger_info");
-
- if ((trigger= bodies[event][time_type]))
- {
- trigger->getInfo(trigger_name, trigger_stmt, sql_mode, definer, NULL,
- client_cs_name, connection_cl_name, db_cl_name);
- DBUG_RETURN(false);
- }
-
- DBUG_RETURN(true);
-}
-
-
-void Table_triggers_list::get_trigger_info(THD *thd,
- int trigger_idx,
- LEX_STRING *trigger_name,
- sql_mode_t *sql_mode,
- LEX_STRING *sql_original_stmt,
- LEX_STRING *client_cs_name,
- LEX_STRING *connection_cl_name,
- LEX_STRING *db_cl_name)
-{
- List_iterator_fast<Trigger> it_table_triggers(table_triggers);
- for (int i = 0; i < trigger_idx; ++i)
- {
- it_table_triggers.next_fast();
- }
-
- Trigger *found_trigger= it_table_triggers++;
- found_trigger->getInfo(trigger_name, NULL, sql_mode, NULL,
- sql_original_stmt, client_cs_name,
- connection_cl_name, db_cl_name);
-}
-
-
-int Table_triggers_list::find_trigger_by_name(const LEX_STRING *trg_name)
-{
- List_iterator_fast<Trigger> it_table_triggers(table_triggers);
- for (int i = 0; ; ++i)
- {
- Trigger *trigger= it_table_triggers++;
-
- if (!trigger)
- return -1;
-
- if (strcmp(trigger->trigger_name->str, trg_name->str) == 0)
- return i;
- }
-}
-
-/**
- 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 sp_name *trg_name,
- bool if_exists,
- TABLE_LIST **table)
-{
- LEX *lex= thd->lex;
- char trn_path_buff[FN_REFLEN];
- LEX_STRING trn_path= { trn_path_buff, 0 };
- LEX_STRING tbl_name= { NULL, 0 };
-
- DBUG_ENTER("add_table_for_trigger");
-
- build_trn_path(thd, trg_name, &trn_path);
-
- if (check_trn_exists(&trn_path))
- {
- if (if_exists)
- {
- push_warning_printf(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 (load_table_name_for_trigger(thd, trg_name, &trn_path, &tbl_name))
- DBUG_RETURN(TRUE);
-
- *table= sp_add_to_query_tables(thd, lex, trg_name->m_db.str,
- tbl_name.str, TL_IGNORE,
- MDL_SHARED_NO_WRITE);
+ *table= sp_add_to_query_tables(thd, lex, trg_name->m_db.str,
+ tbl_name.str, TL_IGNORE,
+ MDL_SHARED_NO_WRITE);
DBUG_RETURN(*table ? FALSE : TRUE);
}
/**
- Drop all triggers for table.
-
- @param thd current thread context
- @param db schema for table
- @param name name for table
-
- @retval
- False success
- @retval
- True error
-*/
-
-bool Table_triggers_list::drop_all_triggers(THD *thd, char *db, char *name)
-{
- TABLE table;
- bool result= 0;
- DBUG_ENTER("drop_all_triggers");
-
- memset(&table, 0, sizeof(table));
- init_sql_alloc(&table.mem_root, 8192, 0);
-
- if (Table_triggers_list::check_n_load(thd, db, name, &table, 1))
- {
- result= 1;
- goto end;
- }
- if (table.triggers)
- {
- Triggers_definition_loader triggers_definition_loader;
- result= triggers_definition_loader.drop_all_triggers(
- db, name,
- table.triggers->table_triggers);
-
- }
-end:
- if (table.triggers)
- delete table.triggers;
- table.triggers= NULL;
- free_root(&table.mem_root, MYF(0));
- DBUG_RETURN(result);
-}
-
-
-/**
- Update .TRG file after renaming triggers' subject table
- (change name of table in triggers' definitions).
-
- @param thd Thread context
- @param old_db_name Old database of subject table
- @param new_db_name New database of subject table
- @param old_table_name Old subject table's name
- @param new_table_name New subject table's name
-
- @retval
- FALSE Success
- @retval
- TRUE Failure
-*/
-
-bool
-Table_triggers_list::change_table_name_in_triggers(THD *thd,
- const char *old_db_name,
- const char *new_db_name,
- LEX_STRING *old_table_name,
- LEX_STRING *new_table_name,
- bool upgrading50to51)
-{
- LEX_STRING *def, *on_table_name, new_def;
- sql_mode_t save_sql_mode= thd->variables.sql_mode;
- List_iterator_fast<Trigger> it_table_triggers(table_triggers);
- size_t on_q_table_name_len, before_on_len;
- String buff;
- Trigger *trigger;
-
- while ((trigger= it_table_triggers++))
- {
- def= trigger->definition;
- on_table_name= trigger->on_table_name;
- thd->variables.sql_mode= trigger->definition_mode;
-
- /* Construct CREATE TRIGGER statement with new table name. */
- buff.length(0);
-
- /* WARNING: 'on_table_name' is supposed to point inside 'def' */
- DBUG_ASSERT(on_table_name->str > def->str);
- DBUG_ASSERT(on_table_name->str < (def->str + def->length));
- before_on_len= on_table_name->str - def->str;
-
- buff.append(def->str, before_on_len);
- buff.append(STRING_WITH_LEN("ON "));
- append_identifier(thd, &buff, new_table_name->str, new_table_name->length);
- buff.append(STRING_WITH_LEN(" "));
- on_q_table_name_len= buff.length() - before_on_len;
- buff.append(on_table_name->str + on_table_name->length,
- def->length - (before_on_len + on_table_name->length));
- /*
- It is OK to allocate some memory on table's MEM_ROOT since this
- table instance will be thrown out at the end of rename anyway.
- */
- new_def.str= (char*) memdup_root(&trigger_table->mem_root, buff.ptr(),
- buff.length());
- new_def.length= buff.length();
- on_table_name->str= new_def.str + before_on_len;
- on_table_name->length= on_q_table_name_len;
- *def= new_def;
- }
-
- thd->variables.sql_mode= save_sql_mode;
-
- if (thd->is_fatal_error)
- return true; /* OOM */
-
- if (trigger_loader.rename_table_in_trigger(trigger_table,
- &table_triggers,
- old_db_name, old_table_name,
- new_db_name, new_table_name,
- upgrading50to51))
- return true;
-
- return false;
-}
-
-bool Triggers_definition_loader::rename_table_in_trigger(
- TABLE *trigger_table,
- List<Trigger> *table_triggers,
- const char *old_db_name,
- LEX_STRING *old_table_name,
- const char *new_db_name,
- LEX_STRING *new_table_name,
- bool upgrading50to51)
-{
- char path_buff[FN_REFLEN];
- LEX_STRING *err_trigname;
-
- if (update_triggers_definition(trigger_table, table_triggers,
- NULL, NULL))
- return true; /* OOM */
-
- if ((err_trigname= change_table_name_in_trignames(
- *table_triggers,
- upgrading50to51 ? old_db_name : NULL,
- new_db_name, new_table_name, 0)))
- {
- /*
- If we were unable to update one of .TRN files properly we will
- revert all changes that we have done and report about error.
- We assume that we will be able to undo our changes without errors
- (we can't do much if there will be an error anyway).
- */
- (void) change_table_name_in_trignames(
- *table_triggers,
- upgrading50to51 ? new_db_name : NULL, old_db_name,
- old_table_name, err_trigname);
- return true;
- }
-
- if (save_trigger_file(new_db_name, new_table_name->str))
- return true;
- if (rm_trigger_file(path_buff, old_db_name, old_table_name->str))
- {
- (void) rm_trigger_file(path_buff, new_db_name, new_table_name->str);
- return true;
- }
- return false;
-}
-
-/**
- Iterate though Table_triggers_list::names_list list and update
- .TRN files after renaming triggers' subject table.
-
- @param old_db_name Old database of subject table
- @param new_db_name New database of subject table
- @param new_table_name New subject table's name
- @param stopper Pointer to Table_triggers_list::names_list at
- which we should stop updating.
-
- @retval
- 0 Success
- @retval
- non-0 Failure, pointer to Table_triggers_list::names_list element
- for which update failed.
-*/
-
-LEX_STRING*
-Triggers_definition_loader::change_table_name_in_trignames(
- List<Trigger> &table_triggers,
- const char *old_db_name,
- const char *new_db_name,
- LEX_STRING *new_table_name,
- LEX_STRING *stopper)
-{
- char trigname_buff[FN_REFLEN];
- struct st_trigname trigname;
- LEX_STRING trigname_file;
- Trigger *trigger;
- List_iterator_fast<Trigger> it_table_triggers(table_triggers);
-
- while ((trigger= it_table_triggers++))
- {
- if (trigger->trigger_name == stopper)
- break;
- trigname_file.length= build_table_filename(trigname_buff, FN_REFLEN-1,
- new_db_name,
- trigger->trigger_name->str,
- TRN_EXT, 0);
- trigname_file.str= trigname_buff;
-
- trigname.trigger_table= *new_table_name;
-
- if (sql_create_definition_file(NULL, &trigname_file, &trigname_file_type,
- (uchar*)&trigname, trigname_file_parameters))
- return trigger->trigger_name;
-
- /* Remove stale .TRN file in case of database upgrade */
- if (old_db_name)
- {
- if (rm_trigname_file(trigname_buff, old_db_name,
- trigger->trigger_name->str))
- {
- (void) rm_trigname_file(trigname_buff, new_db_name,
- trigger->trigger_name->str);
- return trigger->trigger_name;
- }
- }
- }
-
- return NULL;
-}
-
-
-/**
- Update .TRG and .TRN files after renaming triggers' subject table.
-
- @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
-
- @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.
- Also this method assumes that subject table is not renamed to itself.
- 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)
-{
- TABLE table;
- bool result= false;
- bool upgrading50to51= false;
- DBUG_ENTER("change_table_name");
-
- memset(&table, 0, sizeof(table));
- init_sql_alloc(&table.mem_root, 8192, 0);
-
- /*
- This method interfaces the mysql server code protected by
- an exclusive metadata lock.
- */
- 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_alias, new_table));
-
- if (Table_triggers_list::check_n_load(thd, db, old_table, &table, TRUE))
- {
- result= true;
- goto end;
- }
- if (table.triggers)
- {
- if (table.triggers->check_for_broken_triggers())
- {
- result= true;
- goto end;
- }
- 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
- 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().
- */
- if (my_strcasecmp(table_alias_charset, db, new_db))
- {
- char dbname[NAME_LEN + 1];
- if (check_n_cut_mysql50_prefix(db, dbname, sizeof(dbname)) &&
- !my_strcasecmp(table_alias_charset, dbname, new_db))
- {
- upgrading50to51= true;
- }
- else
- {
- my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0));
- result= true;
- goto end;
- }
- }
-
- if (table.triggers->change_table_name_in_triggers(thd, db, new_db,
- &old_table_name,
- &new_table_name,
- upgrading50to51))
- {
- result= true;
- goto end;
- }
- }
-
-end:
- delete table.triggers;
- table.triggers= NULL;
- free_root(&table.mem_root, MYF(0));
- DBUG_RETURN(result);
-}
-
-
-/**
- Execute trigger for given (event, time) pair.
-
- The operation executes trigger for the specified event (insert, update,
- delete) and time (after, before) if it is set.
-
- @param thd
- @param event
- @param time_type
- @param old_row_is_record1
-
- @return Error status.
- @retval FALSE on success.
- @retval TRUE on error.
-*/
-
-bool Table_triggers_list::process_triggers(THD *thd,
- trg_event_type event,
- trg_action_time_type time_type,
- bool old_row_is_record1)
-{
- Trigger *trigger= bodies[event][time_type];
-
- if (check_for_broken_triggers())
- return true;
-
- if (trigger == NULL)
- return false;
-
- if (old_row_is_record1)
- {
- old_field= record1_field;
- new_field= trigger_table->field;
- }
- else
- {
- 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)));
-
- return trigger->execute(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 */
- Trigger *trigger= table_list->table->triggers->bodies[i][j];
-
- if (trigger)
- {
- MDL_key key(MDL_key::TRIGGER, trigger->sp->m_db.str, trigger->sp->m_name.str);
-
- if (sp_add_used_routine(prelocking_ctx, thd->stmt_arena,
- &key, table_list->belong_to_view))
- {
- trigger->sp->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->sp->m_sroutines,
- table_list->belong_to_view);
- trigger->sp->propagate_attributes(prelocking_ctx);
- }
- }
- }
- }
- }
- return FALSE;
-}
-
-
-/**
- Check if any of the marked fields are used in the trigger.
-
- @param used_fields Bitmap over fields to check
- @param event_type Type of event triggers for which we are going to inspect
- @param action_time Type of trigger action time we are going to inspect
-*/
-
-bool Table_triggers_list::is_fields_updated_in_trigger(MY_BITMAP *used_fields,
- trg_event_type event_type,
- trg_action_time_type action_time)
-{
- Item_trigger_field *trg_field;
- Trigger *trigger= bodies[event_type][action_time];
- DBUG_ASSERT(used_fields->n_bits == trigger_table->s->fields);
-
- for (trg_field= trigger->sp->m_trg_table_fields.first; trg_field;
- trg_field= trg_field->next_trg_field)
- {
- /* We cannot check fields which does not present in table. */
- if (trg_field->field_idx != (uint)-1)
- {
- if (bitmap_is_set(used_fields, trg_field->field_idx) &&
- trg_field->get_settable_routine_parameter())
- return true;
- }
- }
- return false;
-}
-
-
-/**
- Mark all trigger fields as "temporary nullable" and remember the current
- THD::count_cuted_fields value.
-
- @param thd Thread context.
-*/
-void Table_triggers_list::enable_fields_temporary_nullability(THD* thd)
-{
- for (Field** next_field= trigger_table->field; *next_field; ++next_field)
- {
- (*next_field)->set_tmp_nullable();
- (*next_field)->set_count_cuted_fields(thd->count_cuted_fields);
-
- /*
- For statement LOAD INFILE we set field values during parsing of data file
- and later run fill_record_n_invoke_before_triggers() to invoke table's
- triggers. fill_record_n_invoke_before_triggers() calls this method
- to enable temporary nullability before running trigger's instructions
- Since for the case of handling statement LOAD INFILE the null value of
- fields have been already set we don't have to reset these ones here.
- In case of handling statements INSERT/REPLACE/INSERT SELECT/
- REPLACE SELECT we set field's values inside method fill_record
- that is called from fill_record_n_invoke_before_triggers()
- after the method enable_fields_temporary_nullability has been executed.
- */
- if (thd->lex->sql_command != SQLCOM_LOAD)
- (*next_field)->reset_tmp_null();
- }
-}
-
-
-/**
- Reset "temporary nullable" flag from trigger fields.
-*/
-void Table_triggers_list::disable_fields_temporary_nullability()
-{
- for (Field** next_field= trigger_table->field; *next_field; ++next_field)
- (*next_field)->reset_tmp_nullable();
-}
-
-
-/**
- Mark fields of subject table which we read/set in its triggers
- as such.
-
- This method marks fields of subject table which are read/set in its
- triggers as such (by properly updating TABLE::read_set/write_set)
- and thus informs handler that values for these fields should be
- retrieved/stored during execution of statement.
-
- @param event Type of event triggers for which we are going to inspect
-*/
-
-void Table_triggers_list::mark_fields_used(trg_event_type event)
-{
- int action_time;
- Item_trigger_field *trg_field;
-
- for (action_time= 0; action_time < (int)TRG_ACTION_MAX; action_time++)
- {
- Trigger *trigger= bodies[event][action_time];
-
- if (!trigger)
- continue;
-
- for (trg_field= trigger->sp->m_trg_table_fields.first; trg_field;
- trg_field= trg_field->next_trg_field)
- {
- /* We cannot mark fields which does not present in table. */
- if (trg_field->field_idx != (uint)-1)
- {
- bitmap_set_bit(trigger_table->read_set, trg_field->field_idx);
- if (trg_field->get_settable_routine_parameter())
- bitmap_set_bit(trigger_table->write_set, trg_field->field_idx);
- }
- }
- }
- trigger_table->file->column_bitmaps_signal();
-}
-
-
-/**
- Signals to the Table_triggers_list that a parse error has occured when
- reading a trigger from file. This makes the Table_triggers_list enter an
- error state flagged by m_has_unparseable_trigger == true. The error message
- will be used whenever a statement invoking or manipulating triggers is
- issued against the Table_triggers_list's table.
-
- @param error_message The error message thrown by the parser.
- */
-void Table_triggers_list::set_parse_error_message(char *error_message)
-{
- m_has_unparseable_trigger= true;
- strcpy(m_parse_error_message, error_message);
-}
-
-
-/**
- Trigger BUG#14090 compatibility hook.
-
- @param[in,out] unknown_key reference on the line with unknown
- parameter and the parsing point
- @param[in] base base address for parameter writing
- (structure like TABLE)
- @param[in] mem_root MEM_ROOT for parameters allocation
- @param[in] end the end of the configuration
-
- @note
- NOTE: this hook process back compatibility for incorrectly written
- sql_modes parameter (see BUG#14090).
-
- @retval
- FALSE OK
- @retval
- TRUE Error
-*/
-
-#define INVALID_SQL_MODES_LENGTH 13
-
-bool
-Handle_old_incorrect_sql_modes_hook::
-process_unknown_string(const char *&unknown_key, uchar* base,
- MEM_ROOT *mem_root, const char *end)
-{
- DBUG_ENTER("Handle_old_incorrect_sql_modes_hook::process_unknown_string");
- DBUG_PRINT("info", ("unknown key: %60s", unknown_key));
-
- if (unknown_key + INVALID_SQL_MODES_LENGTH + 1 < end &&
- unknown_key[INVALID_SQL_MODES_LENGTH] == '=' &&
- !memcmp(unknown_key, STRING_WITH_LEN("sql_modes")))
- {
- const char *ptr= unknown_key + INVALID_SQL_MODES_LENGTH + 1;
-
- DBUG_PRINT("info", ("sql_modes affected by BUG#14090 detected"));
- push_warning_printf(current_thd,
- Sql_condition::SL_NOTE,
- ER_OLD_FILE_FORMAT,
- ER(ER_OLD_FILE_FORMAT),
- (char *)path, "TRIGGER");
- if (get_file_options_ulllist(ptr, end, unknown_key, base,
- &sql_modes_parameters, mem_root))
- {
- DBUG_RETURN(TRUE);
- }
- /*
- Set parsing pointer to the last symbol of string (\n)
- 1) to avoid problem with \0 in the junk after sql_modes
- 2) to speed up skipping this line by parser.
- */
- unknown_key= ptr-1;
- }
- DBUG_RETURN(FALSE);
-}
-
-#define INVALID_TRIGGER_TABLE_LENGTH 15
-
-/**
- Trigger BUG#15921 compatibility hook. For details see
- Handle_old_incorrect_sql_modes_hook::process_unknown_string().
-*/
-bool
-Handle_old_incorrect_trigger_table_hook::
-process_unknown_string(const char *&unknown_key, uchar* base,
- MEM_ROOT *mem_root, const char *end)
-{
- DBUG_ENTER("Handle_old_incorrect_trigger_table_hook::process_unknown_string");
- DBUG_PRINT("info", ("unknown key: %60s", unknown_key));
-
- if (unknown_key + INVALID_TRIGGER_TABLE_LENGTH + 1 < end &&
- unknown_key[INVALID_TRIGGER_TABLE_LENGTH] == '=' &&
- !memcmp(unknown_key, STRING_WITH_LEN("trigger_table")))
- {
- const char *ptr= unknown_key + INVALID_TRIGGER_TABLE_LENGTH + 1;
-
- DBUG_PRINT("info", ("trigger_table affected by BUG#15921 detected"));
- push_warning_printf(current_thd,
- Sql_condition::SL_NOTE,
- ER_OLD_FILE_FORMAT,
- ER(ER_OLD_FILE_FORMAT),
- (char *)path, "TRIGGER");
-
- if (!(ptr= parse_escaped_string(ptr, end, mem_root, trigger_table_value)))
- {
- my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0), "trigger_table",
- unknown_key);
- DBUG_RETURN(TRUE);
- }
-
- /* Set parsing pointer to the last symbol of string (\n). */
- unknown_key= ptr-1;
- }
- DBUG_RETURN(FALSE);
-}
-
-
-/**
Contruct path to TRN-file.
@param thd[in] Thread context.
@@ -2690,7 +395,7 @@ bool load_table_name_for_trigger(THD *th
const LEX_STRING *trn_path,
LEX_STRING *tbl_name)
{
- Triggers_definition_loader trigger_loader;
+ Trigger_loader trigger_loader;
return trigger_loader.get_table_name_for_trigger(thd, trg_name,
trn_path, tbl_name);
=== modified file 'sql/sql_trigger.h'
--- a/sql/sql_trigger.h 2013-01-22 09:24:23 +0000
+++ b/sql/sql_trigger.h 2013-01-22 20:38:40 +0000
@@ -17,396 +17,23 @@
along with this program; if not, write to the Free Software Foundation,
51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
-/* Forward declarations */
+///////////////////////////////////////////////////////////////////////////
+
+#include "m_string.h"
-class Item_trigger_field;
-class sp_head;
class sp_name;
-class Query_tables_list;
+class THD;
+
struct TABLE_LIST;
-class Query_tables_list;
-class Stored_program_creation_ctx;
-/** 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
-};
-
-#include "table.h" /* GRANT_INFO */
-
-/*
- We need this two enums here instead of sql_lex.h because
- at least one of them is used by Item_trigger_field interface.
-
- Time when trigger is invoked (i.e. before or after row actually
- inserted/updated/deleted).
-*/
-enum trg_action_time_type
-{
- TRG_ACTION_BEFORE= 0, TRG_ACTION_AFTER= 1, TRG_ACTION_MAX
-};
-
-
-class Table_triggers_list;
-class Trigger : public Sql_alloc
-{
-public:
- Trigger(LEX_STRING *trg_name, LEX_STRING *db_nam, LEX_STRING *table_nam,
- LEX_STRING *trg_create_str, sql_mode_t trg_sql_mode,
- LEX_STRING *trg_definer, LEX_STRING *client_cs_nam,
- LEX_STRING *connection_cl_nam, LEX_STRING *db_cl_nam)
- : has_parse_error(false),
- trigger_name(trg_name), db_name(db_nam), table_name(table_nam),
- on_table_name(NULL), definition(trg_create_str),
- definition_mode(trg_sql_mode), definer(trg_definer),
- client_cs_name(client_cs_nam), connection_cl_name(connection_cl_nam),
- db_cl_name(db_cl_nam), sp(NULL),
- trg_action_time(TRG_ACTION_MAX), trg_event(TRG_EVENT_MAX)
- {}
-
- void set_has_parse_error()
- {
- has_parse_error= true;
- }
-
- bool init(THD *thd, LEX *lex, Table_triggers_list* object_owner,
- Stored_program_creation_ctx *trg_creation_ctx,
- const char *db, TABLE *table, bool names_only);
-
- bool execute(THD *thd);
-
- void getInfo(LEX_STRING *trg_name,
- LEX_STRING *trigger_stmt,
- sql_mode_t *sql_mode,
- LEX_STRING *trg_definer,
- LEX_STRING *trg_definition,
- LEX_STRING *client_cs_nam,
- LEX_STRING *connection_cl_nam,
- LEX_STRING *db_cl_nam);
-
- bool has_parse_error;
-
- /**
- Names of trigger.
- */
- LEX_STRING *trigger_name;
-
- LEX_STRING *db_name;
- LEX_STRING *table_name;
- /**
- "ON table_name" part in trigger definition, used for
- updating trigger definition during RENAME TABLE.
- */
- LEX_STRING *on_table_name;
-
- /**
- Grant information for the trigger.
- */
- GRANT_INFO *subject_table_grant;
-
- /*
- Trigger definition to save it in file.
- */
- LEX_STRING *definition;
-
- /*
- sql mode for trigger
- */
- sql_mode_t definition_mode;
-
- LEX_STRING *definer;
-
- /*
- Character set context, used for parsing and executing trigger.
- */
- LEX_STRING *client_cs_name;
- LEX_STRING *connection_cl_name;
- LEX_STRING *db_cl_name;
- sp_head *sp;
-
- trg_action_time_type trg_action_time;
- trg_event_type trg_event;
-};
-
-
-class Triggers_definition_loader
-{
-public:
- bool load_triggers(THD *thd, const char *db,
- const char *table_name,
- TABLE *table, bool *trigger_not_found);
-
- bool store_trigger(TABLE_LIST *tables,
- Trigger *new_trigger,
- List<Trigger> *table_triggers);
-
- bool drop_trigger(TABLE_LIST *tables,
- const char *trigger_name,
- List<Trigger> *table_triggers);
-
- bool check_for_uniqueness(const char *db_name,
- const char *trigger_name);
-
- bool rename_table_in_trigger(TABLE *trigger_table,
- List<Trigger> *table_triggers,
- const char *old_db_name,
- LEX_STRING *old_table_name,
- const char *new_db_name,
- LEX_STRING *new_table_name,
- bool upgrading50to51);
-
- bool get_table_name_for_trigger(
- THD *thd,
- const sp_name *trg_name,
- const LEX_STRING *trn_path,
- LEX_STRING *tbl_name);
-
- bool drop_all_triggers(const char* db_name,
- const char* table_name,
- List<Trigger> &table_triggers);
-
-private:
- bool update_triggers_definition(TABLE *table,
- List<Trigger> *triggers,
- const char *name,
- bool *found);
-
- bool save_trigger_file(const char *db,
- const char *table_name);
-
- LEX_STRING* change_table_name_in_trignames(
- List<Trigger> &table_triggers,
- const char *old_db_name,
- const char *new_db_name,
- LEX_STRING *new_table_name,
- LEX_STRING *stopper);
-
- bool rm_trigname_file(char *path, const char *db,
- const char *trigger_name);
-
- bool rm_trigger_file(char *path, const char *db,
- const char *table_name);
-
- /**
- This must be kept up to date whenever a new option is added to the list
- above, as it specifies the number of required parameters of the trigger in
- .trg file.
- */
-
- static const int TRG_NUM_REQUIRED_PARAMETERS= 6;
-
- /**
- Table of .TRG file field descriptors.
- We have here only one field now because in nearest future .TRG
- files will be merged into .FRM files (so we don't need something
- like md5 or created fields).
- */
- static File_option triggers_file_parameters[];
-
- static File_option trigname_file_parameters[];
-
- static const LEX_STRING triggers_file_type;
-
- static const LEX_STRING trigname_file_type;
-
-public:
- /**
- Field responsible for storing triggers definitions in file.
- It have to be public because we are using it directly from parser.
- */
- List<LEX_STRING> definitions_list;
-
- /**
- List of sql modes for triggers
- */
- List<ulonglong> definition_modes_list;
-
- List<LEX_STRING> definers_list;
-
- /* Character set context, used for parsing and executing triggers. */
-
- List<LEX_STRING> client_cs_names;
- List<LEX_STRING> connection_cl_names;
- List<LEX_STRING> db_cl_names;
-};
-
-
-/**
- This class holds all information about triggers of table.
-
- QQ: Will it be merged into TABLE in the future ?
-*/
-
-class Table_triggers_list: public Sql_alloc
-{
- /** Triggers grouped by event, action_time */
- Trigger *bodies[TRG_EVENT_MAX][TRG_ACTION_MAX];
-
- /**
- Copy of TABLE::Field array with field pointers set to TABLE::record[1]
- buffer instead of TABLE::record[0] (used for OLD values in on UPDATE
- trigger and DELETE trigger when it is called for REPLACE).
- */
- Field **record1_field;
-
- /**
- During execution of trigger new_field and old_field should point to the
- array of fields representing new or old version of row correspondingly
- (so it can point to TABLE::field or to Tale_triggers_list::record1_field)
- */
- Field **new_field;
- Field **old_field;
-
-public:
- /** TABLE instance for which this triggers list object was created. */
- TABLE *trigger_table;
-
-private:
- /*
- List of triggers assigned to this table object.
- */
- List<Trigger> table_triggers;
-
-public:
- /**
- Grant information for each trigger (pair: subject table, trigger definer).
- */
- GRANT_INFO subject_table_grants[TRG_EVENT_MAX][TRG_ACTION_MAX];
-
-private:
- /**
- This flag indicates that one of the triggers was not parsed successfully,
- and as a precaution the object has entered a state where all trigger
- access results in errors until all such triggers are dropped. It is not
- safe to add triggers since we don't know if the broken trigger has the
- same name or event type. Nor is it safe to invoke any trigger for the
- aforementioned reasons. The only safe operations are drop_trigger and
- drop_all_triggers.
-
- @see Table_triggers_list::set_parse_error
- */
- bool m_has_unparseable_trigger;
-
- /**
- This error will be displayed when the user tries to manipulate or invoke
- triggers on a table that has broken triggers. It will get set only once
- per statement and thus will contain the first parse error encountered in
- the trigger file.
- */
- char m_parse_error_message[MYSQL_ERRMSG_SIZE];
-
- Triggers_definition_loader trigger_loader;
-
-public:
- Table_triggers_list(TABLE *table_arg)
- :record1_field(0), trigger_table(table_arg),
- m_has_unparseable_trigger(false)
- {
- memset(bodies, 0, sizeof(bodies));
- memset(&subject_table_grants, 0, sizeof(subject_table_grants));
- }
-
- ~Table_triggers_list();
-
- bool load_triggers(THD *thd, const char *db, const char *table_name,
- bool *triggers_not_found)
- {
- return trigger_loader.load_triggers(thd, db, table_name, trigger_table,
- triggers_not_found);
- }
-
- bool create_trigger(THD *thd, TABLE_LIST *table, String *stmt_query);
- bool drop_trigger(THD *thd, TABLE_LIST *table, String *stmt_query);
- bool process_triggers(THD *thd, trg_event_type event,
- trg_action_time_type time_type,
- bool old_row_is_record1);
-
- bool get_trigger_info(THD *thd, trg_event_type event,
- trg_action_time_type time_type,
- LEX_STRING *trigger_name, LEX_STRING *trigger_stmt,
- sql_mode_t *sql_mode,
- LEX_STRING *definer,
- LEX_STRING *client_cs_name,
- LEX_STRING *connection_cl_name,
- LEX_STRING *db_cl_name);
-
- void get_trigger_info(THD *thd,
- int trigger_idx,
- LEX_STRING *trigger_name,
- sql_mode_t *sql_mode,
- LEX_STRING *sql_original_stmt,
- LEX_STRING *client_cs_name,
- LEX_STRING *connection_cl_name,
- LEX_STRING *db_cl_name);
-
- int find_trigger_by_name(const LEX_STRING *trigger_name);
-
- static bool check_n_load(THD *thd, const char *db, const char *table_name,
- TABLE *table, bool names_only);
- static bool drop_all_triggers(THD *thd, char *db, char *table_name);
- static bool change_table_name(THD *thd, const char *db,
- const char *old_alias,
- const char *old_table,
- const char *new_db,
- const char *new_table);
- bool has_triggers(trg_event_type event_type,
- trg_action_time_type action_time)
- {
- return (bodies[event_type][action_time] != NULL);
- }
- bool has_delete_triggers()
- {
- return (bodies[TRG_EVENT_DELETE][TRG_ACTION_BEFORE] ||
- bodies[TRG_EVENT_DELETE][TRG_ACTION_AFTER]);
- }
-
- void set_table(TABLE *new_table);
-
- void mark_fields_used(trg_event_type event);
-
- void set_parse_error_message(char *error_message);
-
- friend class Item_trigger_field;
-
- bool add_tables_and_routines_for_triggers(THD *thd,
- Query_tables_list *prelocking_ctx,
- TABLE_LIST *table_list);
- bool is_fields_updated_in_trigger(MY_BITMAP *used_fields,
- trg_event_type event_type,
- trg_action_time_type action_time);
-
- void enable_fields_temporary_nullability(THD* thd);
- void disable_fields_temporary_nullability();
-
-private:
- bool parse_trigger_definitions(THD *thd, TABLE *table, const char *db,
- const char *table_name, bool names_only);
-
- bool prepare_record1_accessors();
- bool change_table_name_in_triggers(THD *thd,
- const char *old_db_name,
- const char *new_db_name,
- LEX_STRING *old_table_name,
- LEX_STRING *new_table_name,
- bool upgrading50to51);
-
- bool check_for_broken_triggers()
- {
- if (m_has_unparseable_trigger)
- {
- my_message(ER_PARSE_ERROR, m_parse_error_message, MYF(0));
- return true;
- }
- return false;
- }
-};
+///////////////////////////////////////////////////////////////////////////
+
+extern const char * const TRG_EXT;
+extern const char * const TRN_EXT;
+
+///////////////////////////////////////////////////////////////////////////
-extern const LEX_STRING trg_action_time_type_names[];
-extern const LEX_STRING trg_event_type_names[];
+bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create);
bool add_table_for_trigger(THD *thd,
const sp_name *trg_name,
@@ -421,9 +48,7 @@ bool load_table_name_for_trigger(THD *th
const sp_name *trg_name,
const LEX_STRING *trn_path,
LEX_STRING *tbl_name);
-bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create);
-extern const char * const TRG_EXT;
-extern const char * const TRN_EXT;
+///////////////////////////////////////////////////////////////////////////
#endif /* SQL_TRIGGER_INCLUDED */
=== modified file 'sql/table.cc'
--- a/sql/table.cc 2012-12-13 11:16:18 +0000
+++ b/sql/table.cc 2013-01-23 15:11:25 +0000
@@ -5288,7 +5288,7 @@ void TABLE::mark_columns_needed_for_dele
Unlike other similar methods, it doesn't mark fields used by triggers,
that is the responsibility of the caller to do, by using
- Table_triggers_list::mark_used_fields(TRG_EVENT_UPDATE)!
+ Table_trigger_dispatcher::mark_used_fields(TRG_EVENT_UPDATE)!
*/
void TABLE::mark_columns_needed_for_update()
=== modified file 'sql/table.h'
--- a/sql/table.h 2012-12-11 18:12:13 +0000
+++ b/sql/table.h 2013-01-23 15:11:25 +0000
@@ -261,7 +261,7 @@ typedef struct st_grant_internal_info GR
A GRANT_INFO also serves as a cache of the privilege hash tables. Relevant
members are grant_table and version.
*/
-typedef struct st_grant_info
+struct GRANT_INFO
{
/**
@brief A copy of the privilege information regarding the current host,
@@ -310,7 +310,7 @@ typedef struct st_grant_info
ulong orig_want_privilege;
/** The grant state for internal tables. */
GRANT_INTERNAL_INFO m_internal;
-} GRANT_INFO;
+};
enum tmp_table_type
{
@@ -367,7 +367,7 @@ public:
};
class Field_blob;
-class Table_triggers_list;
+class Table_trigger_dispatcher;
/**
Category of table found in the table share.
@@ -1038,7 +1038,7 @@ public:
Field *found_next_number_field; /* Set on open */
/* Table's triggers, 0 if there are no of them */
- Table_triggers_list *triggers;
+ Table_trigger_dispatcher *triggers;
TABLE_LIST *pos_in_table_list;/* Element referring to this table */
/* Position in thd->locked_table_list under LOCK TABLES */
TABLE_LIST *pos_in_locked_tables;
=== added file 'sql/table_trigger_dispatcher.cc'
--- a/sql/table_trigger_dispatcher.cc 1970-01-01 00:00:00 +0000
+++ b/sql/table_trigger_dispatcher.cc 2013-01-25 11:48:04 +0000
@@ -0,0 +1,1071 @@
+/*
+ Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#define MYSQL_LEX 1
+
+#include "my_global.h"
+#include "table_trigger_dispatcher.h"
+#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 "trigger_loader.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Create trigger for table.
+
+ @param thd current thread context (including trigger definition in
+ LEX)
+ @param tables table list containing one open table for which the
+ trigger is created.
+ @param[out] stmt_query after successful return, this string contains
+ well-formed statement for creation this trigger.
+
+ @note
+ - Assumes that trigger name is fully qualified.
+ - NULL-string means the following LEX_STRING instance:
+ { str = 0; length = 0 }.
+ - In other words, definer_user and definer_host should contain
+ simultaneously NULL-strings (non-SUID/old trigger) or valid strings
+ (SUID/new trigger).
+
+ @retval
+ false success
+ @retval
+ true error
+*/
+
+bool Table_trigger_dispatcher::create_trigger(THD *thd, TABLE_LIST *tables,
+ String *stmt_query)
+{
+ LEX *lex= thd->lex;
+ TABLE *table= tables->table;
+ LEX_STRING *trg_def;
+ LEX_STRING definer_user;
+ LEX_STRING definer_host;
+ sql_mode_t trg_sql_mode;
+ LEX_STRING *trg_definer;
+ LEX_STRING *trg_client_cs_name;
+ LEX_STRING *trg_connection_cl_name;
+ LEX_STRING *trg_db_cl_name;
+
+ if (check_for_broken_triggers())
+ return true;
+
+ /* Trigger must be in the same schema as target table. */
+ if (my_strcasecmp(table_alias_charset, table->s->db.str,
+ lex->spname->m_db.str))
+ {
+ my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0));
+ return true;
+ }
+
+ if (Trigger_loader::check_for_uniqueness(tables->db,
+ thd->lex->spname->m_name.str))
+ return true;
+
+ sp_head *trg= lex->sphead;
+ int trg_event= trg->m_trg_chistics.event;
+ int trg_action_time= trg->m_trg_chistics.action_time;
+
+ /* We don't allow creation of several triggers of the same type yet */
+ if (bodies[trg_event][trg_action_time] != NULL)
+ {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0),
+ "multiple triggers with the same action time"
+ " and event for one table");
+ return true;
+ }
+
+ if (!lex->definer)
+ {
+ /*
+ DEFINER-clause is missing.
+
+ If we are in slave thread, this means that we received CREATE TRIGGER
+ from the master, that does not support definer in triggers. So, we
+ should mark this trigger as non-SUID. Note that this does not happen
+ when we parse triggers' definitions during opening .TRG file.
+ LEX::definer is ignored in that case.
+
+ Otherwise, we should use CURRENT_USER() as definer.
+
+ NOTE: when CREATE TRIGGER statement is allowed to be executed in PS/SP,
+ it will be required to create the definer below in persistent MEM_ROOT
+ of PS/SP.
+ */
+
+ if (!thd->slave_thread)
+ {
+ if (!(lex->definer= create_default_definer(thd)))
+ return true;
+ }
+ }
+
+ /*
+ If the specified definer differs from the current user, we should check
+ that the current user has SUPER privilege (in order to create trigger
+ under another user one must have SUPER privilege).
+ */
+
+ if (lex->definer &&
+ (strcmp(lex->definer->user.str, thd->security_ctx->priv_user) ||
+ my_strcasecmp(system_charset_info,
+ lex->definer->host.str,
+ thd->security_ctx->priv_host)))
+ {
+ if (check_global_access(thd, SUPER_ACL))
+ {
+ my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "SUPER");
+ return true;
+ }
+ }
+
+ /*
+ Let us check if all references to fields in old/new versions of row in
+ this trigger are ok.
+
+ NOTE: We do it here more from ease of use standpoint. We still have to
+ do some checks on each execution. E.g. we can catch privilege changes
+ only during execution. Also in near future, when we will allow access
+ to other tables from trigger we won't be able to catch changes in other
+ tables...
+
+ Since we don't plan to access to contents of the fields it does not
+ matter that we choose for both OLD and NEW values the same versions
+ of Field objects here.
+ */
+ old_field= new_field= table->field;
+
+ for (Item_trigger_field *trg_field= lex->sphead->m_trg_table_fields.first;
+ trg_field; trg_field= trg_field->next_trg_field)
+ {
+ /*
+ NOTE: now we do not check privileges at CREATE TRIGGER time. This will
+ be changed in the future.
+ */
+ trg_field->setup_field(thd, table, NULL);
+
+ if (!trg_field->fixed &&
+ trg_field->fix_fields(thd, (Item **)0))
+ return true;
+ }
+
+
+ /*
+ Soon we will invalidate table object and thus Table_trigger_dispatcher
+ object so don't care about place to which trg_def->ptr points and other
+ invariants (e.g. we don't bother to update names_list)
+
+ QQ: Hmm... probably we should not care about setting up active thread
+ mem_root too.
+ */
+ if (!(trg_def= alloc_lex_string(&table->mem_root)) ||
+ !(trg_definer= alloc_lex_string(&table->mem_root)) ||
+ !(trg_client_cs_name= alloc_lex_string(&table->mem_root)) ||
+ !(trg_connection_cl_name= alloc_lex_string(&table->mem_root)) ||
+ !(trg_db_cl_name= alloc_lex_string(&table->mem_root)))
+ {
+ return true;
+ }
+
+ trg_sql_mode= thd->variables.sql_mode;
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (lex->definer && !is_acl_user(lex->definer->host.str,
+ lex->definer->user.str))
+ {
+ push_warning_printf(thd,
+ Sql_condition::SL_NOTE,
+ ER_NO_SUCH_USER,
+ ER(ER_NO_SUCH_USER),
+ lex->definer->user.str,
+ lex->definer->host.str);
+ }
+#endif /* NO_EMBEDDED_ACCESS_CHECKS */
+
+ if (lex->definer)
+ {
+ char trg_definer_buf[USER_HOST_BUFF_SIZE];
+ /* SUID trigger. */
+
+ definer_user= lex->definer->user;
+ definer_host= lex->definer->host;
+
+ size_t trg_definer_len= strxmov(trg_definer_buf, definer_user.str, "@",
+ definer_host.str, NullS) - trg_definer_buf;
+ lex_string_copy(&table->mem_root, trg_definer, trg_definer_buf,
+ trg_definer_len);
+ }
+ else
+ {
+ /* non-SUID trigger. */
+
+ definer_user.str= 0;
+ definer_user.length= 0;
+
+ definer_host.str= 0;
+ definer_host.length= 0;
+
+ trg_definer->str= (char*) "";
+ trg_definer->length= 0;
+ }
+
+ /*
+ Fill character set information:
+ - client character set contains charset info only;
+ - connection collation contains pair {character set, collation};
+ - database collation contains pair {character set, collation};
+ */
+
+ lex_string_set(trg_client_cs_name, thd->charset()->csname);
+
+ lex_string_set(trg_connection_cl_name,
+ thd->variables.collation_connection->name);
+
+ lex_string_set(trg_db_cl_name,
+ get_default_db_collation(thd, tables->db)->name);
+
+ /*
+ Create well-formed trigger definition query. Original query is not
+ appropriated, because definer-clause can be not truncated.
+ */
+
+ stmt_query->append(STRING_WITH_LEN("CREATE "));
+
+ /*
+ Append definer-clause if the trigger is SUID (a usual trigger in
+ new MySQL versions).
+ */
+
+ append_definer(thd, stmt_query, &definer_user, &definer_host);
+
+ LEX_STRING stmt_definition;
+ stmt_definition.str= (char*) thd->lex->stmt_definition_begin;
+ stmt_definition.length= thd->lex->stmt_definition_end
+ - thd->lex->stmt_definition_begin;
+ trim_whitespace(thd->charset(), & stmt_definition);
+
+ stmt_query->append(stmt_definition.str, stmt_definition.length);
+
+ lex_string_copy(&table->mem_root, trg_def, stmt_query->c_ptr(),
+ stmt_query->length());
+
+ Trigger *new_trigger=
+ new (&table->mem_root) Trigger(&trigger_table->s->db,
+ &trigger_table->s->table_name,
+ trg_def, trg_sql_mode,
+ trg_definer, trg_client_cs_name,
+ trg_connection_cl_name, trg_db_cl_name);
+
+ new_trigger->set_trigger_name(&lex->spname->m_name);
+
+ {
+ Trigger_loader trigger_loader;
+ return trigger_loader.store_trigger(tables, new_trigger,
+ &table_triggers);
+ }
+}
+
+
+/**
+ Drop trigger for table.
+
+ @param thd current thread context
+ (including trigger definition in LEX)
+ @param tables table list containing one open table for which trigger
+ is dropped.
+ @param[out] stmt_query after successful return, this string contains
+ well-formed statement for creation this trigger
+
+ @todo
+ Probably instead of removing .TRG file we should move
+ to archive directory but this should be done as part of
+ parse_file.cc functionality (because we will need it
+ elsewhere).
+
+ @retval
+ False success
+ @retval
+ True error
+*/
+bool Table_trigger_dispatcher::drop_trigger(THD *thd, TABLE_LIST *tables,
+ String *stmt_query)
+{
+ const char *sp_name= thd->lex->spname->m_name.str; // alias
+ stmt_query->append(thd->query(), thd->query_length());
+
+ Trigger_loader trigger_loader;
+ return trigger_loader.drop_trigger(tables, sp_name, &table_triggers);
+}
+
+
+Table_trigger_dispatcher::~Table_trigger_dispatcher()
+{
+ for (int i= 0; i < (int)TRG_EVENT_MAX; i++)
+ for (int j= 0; j < (int)TRG_ACTION_MAX; j++)
+ delete bodies[i][j];
+
+ if (record1_field)
+ for (Field **fld_ptr= record1_field; *fld_ptr; fld_ptr++)
+ delete *fld_ptr;
+}
+
+
+/**
+ Prepare array of Field objects referencing to TABLE::record[1] instead
+ of record[0] (they will represent OLD.* row values in ON UPDATE trigger
+ and in ON DELETE trigger which will be called during REPLACE execution).
+
+ @retval
+ False success
+ @retval
+ True error
+*/
+
+bool Table_trigger_dispatcher::prepare_record1_accessors()
+{
+ Field **fld, **old_fld;
+
+ if (!(record1_field= (Field **)alloc_root(&trigger_table->mem_root,
+ (trigger_table->s->fields + 1) *
+ sizeof(Field*))))
+ return true;
+
+ for (fld= trigger_table->field, old_fld= record1_field; *fld; fld++, old_fld++)
+ {
+ /*
+ QQ: it is supposed that it is ok to use this function for field
+ cloning...
+ */
+ if (!(*old_fld= (*fld)->new_field(&trigger_table->mem_root, trigger_table,
+ trigger_table == (*fld)->table)))
+ return true;
+ (*old_fld)->move_field_offset((my_ptrdiff_t)(trigger_table->record[1] -
+ trigger_table->record[0]));
+ }
+ *old_fld= 0;
+
+ return false;
+}
+
+
+/**
+ Adjust Table_trigger_dispatcher with new TABLE pointer.
+
+ @param new_table new pointer to TABLE instance
+*/
+
+void Table_trigger_dispatcher::set_table(TABLE *new_table)
+{
+ trigger_table= new_table;
+ for (Field **field= new_table->triggers->record1_field ; *field ; field++)
+ {
+ (*field)->table= (*field)->orig_table= new_table;
+ (*field)->table_name= &new_table->alias;
+ }
+}
+
+
+/**
+ Check whenever .TRG file for table exist and load all triggers it contains.
+
+ @param thd current thread context
+ @param db table's database name
+ @param table_name table's name
+ @param table pointer to table object
+ @param names_only stop after loading trigger names
+
+ @todo
+ A lot of things to do here e.g. how about other funcs and being
+ more paranoical ?
+
+ @todo
+ This could be avoided if there is no triggers for UPDATE and DELETE.
+
+ @retval
+ False success
+ @retval
+ True error
+*/
+
+bool Table_trigger_dispatcher::check_n_load(THD *thd, const char *db,
+ const char *table_name,
+ TABLE *table,
+ bool names_only)
+{
+ DBUG_ENTER("Table_trigger_dispatcher::check_n_load");
+
+ Table_trigger_dispatcher *table_trigger_dispatch=
+ new (&table->mem_root) Table_trigger_dispatcher(table);
+
+ if (!table_trigger_dispatch)
+ DBUG_RETURN(true);
+
+ bool triggers_not_found;
+
+ if (Trigger_loader::load_triggers(thd, db, table_name, table,
+ &table_trigger_dispatch->table_triggers,
+ &triggers_not_found))
+ {
+ delete table_trigger_dispatch;
+ DBUG_RETURN(!triggers_not_found);
+ }
+
+ parse_triggers(thd, table, table_trigger_dispatch,
+ table_trigger_dispatch->table_triggers);
+ table->triggers= table_trigger_dispatch;
+
+ /*
+ TODO: This could be avoided if there is no triggers
+ for UPDATE and DELETE.
+ */
+ if (!names_only && table_trigger_dispatch->prepare_record1_accessors())
+ {
+ delete table_trigger_dispatch;
+ DBUG_RETURN(true);
+ }
+
+ {
+ List_iterator_fast<Trigger> it(table_trigger_dispatch->table_triggers);
+ while (true)
+ {
+ Trigger *t= it++;
+
+ if (!t)
+ break;
+
+ table_trigger_dispatch->bodies[t->get_trg_event()]
+ [t->get_trg_action_time()]= t;
+
+ if (!names_only)
+ t->setup_fields(thd, table);
+ }
+ }
+
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Obtains and returns trigger metadata.
+
+ @param thd current thread context
+ @param event trigger event type
+ @param time_type trigger action time
+ @param trigger_name returns name of trigger
+ @param trigger_stmt returns statement of trigger
+ @param sql_mode returns sql_mode of trigger
+ @param definer returns definer/creator of trigger. The caller is
+ responsible to allocate enough space for storing
+ definer information.
+
+ @retval
+ false success
+ @retval
+ true error
+*/
+
+bool Table_trigger_dispatcher::get_trigger_info(THD *thd, trg_event_type event,
+ trg_action_time_type time_type,
+ LEX_STRING *trigger_name,
+ LEX_STRING *trigger_stmt,
+ sql_mode_t *sql_mode,
+ LEX_STRING *definer,
+ LEX_STRING *client_cs_name,
+ LEX_STRING *connection_cl_name,
+ LEX_STRING *db_cl_name)
+{
+ Trigger *trigger;
+ DBUG_ENTER("get_trigger_info");
+
+ if ((trigger= bodies[event][time_type]))
+ {
+ trigger->get_info(trigger_name, trigger_stmt, sql_mode, definer, NULL,
+ client_cs_name, connection_cl_name, db_cl_name);
+ DBUG_RETURN(false);
+ }
+
+ DBUG_RETURN(true);
+}
+
+
+/**
+ Get information about trigger by its id.
+
+ @param [in] thd thread handle
+ @param [in] trigger_idx ordinal number of trigger in the list
+ @param [out] trigger_name pointer to variable where to store
+ the trigger name
+ @param [out] sql_mode pointer to variable where to store
+ the sql mode
+ @param [out] sql_original_stmt pointer to variable where to store
+ the trigger definition
+ @param [out] client_cs_name client character set
+ @param [out] connection_cl_name connection collation
+ @param [out] db_cl_name database collation
+*/
+
+void Table_trigger_dispatcher::get_trigger_info(THD *thd,
+ int trigger_idx,
+ LEX_STRING *trigger_name,
+ sql_mode_t *sql_mode,
+ LEX_STRING *sql_original_stmt,
+ LEX_STRING *client_cs_name,
+ LEX_STRING *connection_cl_name,
+ LEX_STRING *db_cl_name)
+{
+ List_iterator_fast<Trigger> it_table_triggers(table_triggers);
+ for (int i = 0; i < trigger_idx; ++i)
+ {
+ it_table_triggers.next_fast();
+ }
+
+ Trigger *found_trigger= it_table_triggers++;
+ found_trigger->get_info(trigger_name, NULL, sql_mode, NULL,
+ sql_original_stmt, client_cs_name,
+ connection_cl_name, db_cl_name);
+}
+
+
+/**
+ Get trigger ordinal number by trigger name.
+
+ @param [in] trigger_name trigger name
+
+ @return ordinal number of trigger in the list
+ @retval -1 if trigger not found
+ @retval >= 0 trigger index
+*/
+
+int Table_trigger_dispatcher::find_trigger_by_name(const LEX_STRING *trg_name)
+{
+ List_iterator_fast<Trigger> it_table_triggers(table_triggers);
+ for (int i = 0; ; ++i)
+ {
+ Trigger *trigger= it_table_triggers++;
+
+ if (!trigger)
+ return -1;
+
+ if (strcmp(trigger->m_trigger_name->str, trg_name->str) == 0)
+ return i;
+ }
+}
+
+
+/**
+ Drop all triggers for table.
+
+ @param thd current thread context
+ @param db schema for table
+ @param name name for table
+
+ @retval
+ false success
+ @retval
+ true error
+*/
+
+bool Table_trigger_dispatcher::drop_all_triggers(THD *thd, char *db,
+ char *name)
+{
+ TABLE table;
+ bool result= 0;
+ DBUG_ENTER("drop_all_triggers");
+
+ memset(&table, 0, sizeof(table));
+ init_sql_alloc(&table.mem_root, 8192, 0);
+
+ if (Table_trigger_dispatcher::check_n_load(thd, db, name, &table, 1))
+ {
+ result= 1;
+ goto end;
+ }
+ if (table.triggers)
+ {
+ Trigger_loader trigger_loader;
+ result= trigger_loader.drop_all_triggers(
+ db, name,
+ table.triggers->table_triggers);
+
+ }
+end:
+ if (table.triggers)
+ delete table.triggers;
+ table.triggers= NULL;
+ free_root(&table.mem_root, MYF(0));
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Update .TRG file after renaming triggers' subject table
+ (change name of table in triggers' definitions).
+
+ @param thd Thread context
+ @param old_db_name Old database of subject table
+ @param new_db_name New database of subject table
+ @param old_table_name Old subject table's name
+ @param new_table_name New subject table's name
+
+ @retval
+ false Success
+ @retval
+ true Failure
+*/
+
+bool
+Table_trigger_dispatcher::change_table_name_in_triggers(
+ THD *thd,
+ const char *old_db_name,
+ const char *new_db_name,
+ LEX_STRING *old_table_name,
+ LEX_STRING *new_table_name,
+ bool upgrading50to51)
+{
+ LEX_STRING *def, *on_table_name, new_def;
+ sql_mode_t save_sql_mode= thd->variables.sql_mode;
+ List_iterator_fast<Trigger> it_table_triggers(table_triggers);
+ size_t on_q_table_name_len, before_on_len;
+ String buff;
+ Trigger *trigger;
+
+ while ((trigger= it_table_triggers++))
+ {
+ def= trigger->m_definition;
+ on_table_name= trigger->m_on_table_name;
+ thd->variables.sql_mode= trigger->m_definition_mode;
+
+ /* Construct CREATE TRIGGER statement with new table name. */
+ buff.length(0);
+
+ /* WARNING: 'on_table_name' is supposed to point inside 'def' */
+ DBUG_ASSERT(on_table_name->str > def->str);
+ DBUG_ASSERT(on_table_name->str < (def->str + def->length));
+ before_on_len= on_table_name->str - def->str;
+
+ buff.append(def->str, before_on_len);
+ buff.append(STRING_WITH_LEN("ON "));
+ append_identifier(thd, &buff, new_table_name->str, new_table_name->length);
+ buff.append(STRING_WITH_LEN(" "));
+ on_q_table_name_len= buff.length() - before_on_len;
+ buff.append(on_table_name->str + on_table_name->length,
+ def->length - (before_on_len + on_table_name->length));
+ /*
+ It is OK to allocate some memory on table's MEM_ROOT since this
+ table instance will be thrown out at the end of rename anyway.
+ */
+ new_def.str= (char*) memdup_root(&trigger_table->mem_root, buff.ptr(),
+ buff.length());
+ new_def.length= buff.length();
+ on_table_name->str= new_def.str + before_on_len;
+ on_table_name->length= on_q_table_name_len;
+ *def= new_def;
+ }
+
+ thd->variables.sql_mode= save_sql_mode;
+
+ if (thd->is_fatal_error)
+ return true; /* OOM */
+
+ Trigger_loader trigger_loader;
+ if (trigger_loader.rename_table_in_trigger(trigger_table,
+ &table_triggers,
+ old_db_name, old_table_name,
+ new_db_name, new_table_name,
+ upgrading50to51))
+ return true;
+
+ return false;
+}
+
+
+/**
+ Update .TRG and .TRN files after renaming triggers' subject table.
+
+ @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
+
+ @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.
+ Also this method assumes that subject table is not renamed to itself.
+ This method needs to be called under an exclusive table metadata lock.
+
+ @retval false Success
+ @retval true Error
+*/
+
+bool Table_trigger_dispatcher::change_table_name(THD *thd, const char *db,
+ const char *old_alias,
+ const char *old_table,
+ const char *new_db,
+ const char *new_table)
+{
+ TABLE table;
+ bool result= false;
+ bool upgrading50to51= false;
+ DBUG_ENTER("change_table_name");
+
+ memset(&table, 0, sizeof(table));
+ init_sql_alloc(&table.mem_root, 8192, 0);
+
+ /*
+ This method interfaces the mysql server code protected by
+ an exclusive metadata lock.
+ */
+ 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_alias, new_table));
+
+ if (Table_trigger_dispatcher::check_n_load(thd, db, old_table, &table, true))
+ {
+ result= true;
+ goto end;
+ }
+ if (table.triggers)
+ {
+ if (table.triggers->check_for_broken_triggers())
+ {
+ result= true;
+ goto end;
+ }
+ 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
+ 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().
+ */
+ if (my_strcasecmp(table_alias_charset, db, new_db))
+ {
+ char dbname[NAME_LEN + 1];
+ if (check_n_cut_mysql50_prefix(db, dbname, sizeof(dbname)) &&
+ !my_strcasecmp(table_alias_charset, dbname, new_db))
+ {
+ upgrading50to51= true;
+ }
+ else
+ {
+ my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0));
+ result= true;
+ goto end;
+ }
+ }
+
+ if (table.triggers->change_table_name_in_triggers(thd, db, new_db,
+ &old_table_name,
+ &new_table_name,
+ upgrading50to51))
+ {
+ result= true;
+ goto end;
+ }
+ }
+
+end:
+ delete table.triggers;
+ table.triggers= NULL;
+ free_root(&table.mem_root, MYF(0));
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Load table triggers from the data dictionary.
+
+ @param [in] thd thread handle
+ @param [in] table pointer to the trigger's table
+ @param [in] dsptch pointer to the Table_trigger_dispatcher
+ @param [in] table_trigger_lst reference to the list of triggers to parse
+
+ @return Operation status
+ @retval true Failure
+ @retval false Success
+*/
+
+bool Table_trigger_dispatcher::parse_triggers(THD *thd, TABLE *table,
+ Table_trigger_dispatcher *dsptch,
+ List<Trigger> &table_trigger_lst)
+{
+ List_iterator<Trigger> it(table_trigger_lst);
+ while (true)
+ {
+ Trigger *t= it++;
+
+ if (!t)
+ break;
+
+ bool result= t->parse_trigger_body(thd, table);
+ if (result || t->has_parse_error())
+ {
+ dsptch->set_parse_error_message(t->get_parse_error_message());
+ if (result)
+ {
+ /*
+ The method Trigger::parse_trigger_body() returns true when the
+ trigger definition is unparseable, e.g. instead of SQL-statement
+ 'CREATE TRIGGER trg1 ...' the trigger definition contains
+ the string 'bla-bla-bla'. In this case we remove the trigger object
+ from the list.
+ */
+ delete t;
+ it.remove();
+ }
+ continue;
+ }
+
+ GRANT_INFO *table_grant=
+ &dsptch->subject_table_grants[t->get_trg_event()]
+ [t->get_trg_action_time()];
+ t->set_subject_table_grant(table_grant);
+
+ if (t->m_sp)
+ t->m_sp->m_trg_list= dsptch;
+ }
+
+ return false;
+}
+
+
+/**
+ Execute trigger for given (event, time) pair.
+
+ The operation executes trigger for the specified event (insert, update,
+ delete) and time (after, before) if it is set.
+
+ @param thd
+ @param event
+ @param time_type
+ @param old_row_is_record1
+
+ @return Error status.
+ @retval false on success.
+ @retval true on error.
+*/
+
+bool Table_trigger_dispatcher::process_triggers(THD *thd,
+ trg_event_type event,
+ trg_action_time_type time_type,
+ bool old_row_is_record1)
+{
+ Trigger *trigger= bodies[event][time_type];
+
+ if (check_for_broken_triggers())
+ return true;
+
+ if (trigger == NULL)
+ return false;
+
+ if (old_row_is_record1)
+ {
+ old_field= record1_field;
+ new_field= trigger_table->field;
+ }
+ else
+ {
+ 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)));
+
+ return trigger->execute(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_trigger_dispatcher::
+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 */
+ Trigger *trigger= table_list->table->triggers->bodies[i][j];
+
+ if (trigger)
+ trigger->add_tables_and_routines(thd, prelocking_ctx, table_list);
+ }
+ }
+ }
+ return FALSE;
+}
+
+
+/**
+ Check if any of the marked fields are used in the trigger.
+
+ @param used_fields Bitmap over fields to check
+ @param event_type Type of event triggers for which we are going to inspect
+ @param action_time Type of trigger action time we are going to inspect
+*/
+
+bool Table_trigger_dispatcher::is_fields_updated_in_trigger(
+ MY_BITMAP *used_fields,
+ trg_event_type event_type,
+ trg_action_time_type action_time)
+{
+ Trigger *trigger= bodies[event_type][action_time];
+ DBUG_ASSERT(used_fields->n_bits == trigger_table->s->fields);
+
+ return trigger->is_fields_updated_in_trigger(used_fields);
+}
+
+
+/**
+ Mark all trigger fields as "temporary nullable" and remember the current
+ THD::count_cuted_fields value.
+
+ @param thd Thread context.
+*/
+void Table_trigger_dispatcher::enable_fields_temporary_nullability(THD* thd)
+{
+ for (Field** next_field= trigger_table->field; *next_field; ++next_field)
+ {
+ (*next_field)->set_tmp_nullable();
+ (*next_field)->set_count_cuted_fields(thd->count_cuted_fields);
+
+ /*
+ For statement LOAD INFILE we set field values during parsing of data file
+ and later run fill_record_n_invoke_before_triggers() to invoke table's
+ triggers. fill_record_n_invoke_before_triggers() calls this method
+ to enable temporary nullability before running trigger's instructions
+ Since for the case of handling statement LOAD INFILE the null value of
+ fields have been already set we don't have to reset these ones here.
+ In case of handling statements INSERT/REPLACE/INSERT SELECT/
+ REPLACE SELECT we set field's values inside method fill_record
+ that is called from fill_record_n_invoke_before_triggers()
+ after the method enable_fields_temporary_nullability has been executed.
+ */
+ if (thd->lex->sql_command != SQLCOM_LOAD)
+ (*next_field)->reset_tmp_null();
+ }
+}
+
+
+/**
+ Reset "temporary nullable" flag from trigger fields.
+*/
+
+void Table_trigger_dispatcher::disable_fields_temporary_nullability()
+{
+ for (Field** next_field= trigger_table->field; *next_field; ++next_field)
+ (*next_field)->reset_tmp_nullable();
+}
+
+
+/**
+ Mark fields of subject table which we read/set in its triggers
+ as such.
+
+ This method marks fields of subject table which are read/set in its
+ triggers as such (by properly updating TABLE::read_set/write_set)
+ and thus informs handler that values for these fields should be
+ retrieved/stored during execution of statement.
+
+ @param event Type of event triggers for which we are going to inspect
+*/
+
+void Table_trigger_dispatcher::mark_fields_used(trg_event_type event)
+{
+ int action_time;
+
+ for (action_time= 0; action_time < (int)TRG_ACTION_MAX; action_time++)
+ {
+ Trigger *trigger= bodies[event][action_time];
+
+ if (!trigger)
+ continue;
+
+ trigger->mark_field_used(trigger_table);
+ }
+ trigger_table->file->column_bitmaps_signal();
+}
+
+
+/**
+ Signals to the Table_trigger_dispatcher that a parse error has occurred when
+ reading a trigger from file. This makes the Table_trigger_dispatcher enter
+ an error state flagged by m_has_unparseable_trigger == true. The error
+ message will be used whenever a statement invoking or manipulating triggers
+ is issued against the Table_trigger_dispatcher's table.
+
+ @param error_message The error message thrown by the parser.
+*/
+
+void Table_trigger_dispatcher::set_parse_error_message(const char *error_message)
+{
+ if (!m_has_unparseable_trigger)
+ {
+ m_has_unparseable_trigger= true;
+ strlcpy(m_parse_error_message, error_message, sizeof(m_parse_error_message));
+ }
+}
+
=== added file 'sql/table_trigger_dispatcher.h'
--- a/sql/table_trigger_dispatcher.h 1970-01-01 00:00:00 +0000
+++ b/sql/table_trigger_dispatcher.h 2013-01-25 11:48:04 +0000
@@ -0,0 +1,199 @@
+/*
+ Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#ifndef TABLE_TRIGGER_DISPATCHER_H_INCLUDED
+#define TABLE_TRIGGER_DISPATCHER_H_INCLUDED
+
+///////////////////////////////////////////////////////////////////////////
+
+#include "table.h" /* GRANT_INFO */
+#include "trigger.h"
+
+#include <mysqld_error.h>
+
+///////////////////////////////////////////////////////////////////////////
+
+struct TABLE_LIST;
+
+class sp_name;
+class Query_tables_list;
+class Trigger;
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ This class holds all information about triggers of table.
+*/
+
+class Table_trigger_dispatcher: public Sql_alloc
+{
+ /** Triggers grouped by event, action_time */
+ Trigger *bodies[TRG_EVENT_MAX][TRG_ACTION_MAX];
+
+ /**
+ Copy of TABLE::Field array with field pointers set to TABLE::record[1]
+ buffer instead of TABLE::record[0] (used for OLD values in on UPDATE
+ trigger and DELETE trigger when it is called for REPLACE).
+ */
+ Field **record1_field;
+
+ /**
+ During execution of trigger new_field and old_field should point to the
+ array of fields representing new or old version of row correspondingly
+ (so it can point to TABLE::field or to Tale_triggers_list::record1_field)
+ */
+ Field **new_field;
+ Field **old_field;
+
+public:
+ /** TABLE instance for which this triggers list object was created. */
+ TABLE *trigger_table;
+
+private:
+ /*
+ List of triggers assigned to this table object.
+ */
+ List<Trigger> table_triggers;
+
+public:
+ /**
+ Grant information for each trigger (pair: subject table, trigger definer).
+ */
+ GRANT_INFO subject_table_grants[TRG_EVENT_MAX][TRG_ACTION_MAX];
+
+private:
+ /**
+ This flag indicates that one of the triggers was not parsed successfully,
+ and as a precaution the object has entered a state where all trigger
+ access results in errors until all such triggers are dropped. It is not
+ safe to add triggers since we don't know if the broken trigger has the
+ same name or event type. Nor is it safe to invoke any trigger for the
+ aforementioned reasons. The only safe operations are drop_trigger and
+ drop_all_triggers.
+
+ @see Table_trigger_dispatcher::set_parse_error
+ */
+ bool m_has_unparseable_trigger;
+
+ /**
+ This error will be displayed when the user tries to manipulate or invoke
+ triggers on a table that has broken triggers. It will get set only once
+ per statement and thus will contain the first parse error encountered in
+ the trigger file.
+ */
+ char m_parse_error_message[MYSQL_ERRMSG_SIZE];
+
+public:
+ Table_trigger_dispatcher(TABLE *table_arg)
+ :record1_field(0), trigger_table(table_arg),
+ m_has_unparseable_trigger(false)
+ {
+ memset(bodies, 0, sizeof(bodies));
+ memset(&subject_table_grants, 0, sizeof(subject_table_grants));
+ }
+
+ ~Table_trigger_dispatcher();
+
+ bool create_trigger(THD *thd, TABLE_LIST *table, String *stmt_query);
+ bool drop_trigger(THD *thd, TABLE_LIST *table, String *stmt_query);
+ bool process_triggers(THD *thd, trg_event_type event,
+ trg_action_time_type time_type,
+ bool old_row_is_record1);
+
+ bool get_trigger_info(THD *thd, trg_event_type event,
+ trg_action_time_type time_type,
+ LEX_STRING *trigger_name, LEX_STRING *trigger_stmt,
+ sql_mode_t *sql_mode,
+ LEX_STRING *definer,
+ LEX_STRING *client_cs_name,
+ LEX_STRING *connection_cl_name,
+ LEX_STRING *db_cl_name);
+
+ void get_trigger_info(THD *thd,
+ int trigger_idx,
+ LEX_STRING *trigger_name,
+ sql_mode_t *sql_mode,
+ LEX_STRING *sql_original_stmt,
+ LEX_STRING *client_cs_name,
+ LEX_STRING *connection_cl_name,
+ LEX_STRING *db_cl_name);
+
+ int find_trigger_by_name(const LEX_STRING *trigger_name);
+
+ static bool check_n_load(THD *thd, const char *db, const char *table_name,
+ TABLE *table, bool names_only);
+ static bool drop_all_triggers(THD *thd, char *db, char *table_name);
+ static bool change_table_name(THD *thd, const char *db,
+ const char *old_alias,
+ const char *old_table,
+ const char *new_db,
+ const char *new_table);
+ bool has_triggers(trg_event_type event_type,
+ trg_action_time_type action_time)
+ {
+ return (bodies[event_type][action_time] != NULL);
+ }
+ bool has_delete_triggers()
+ {
+ return (bodies[TRG_EVENT_DELETE][TRG_ACTION_BEFORE] ||
+ bodies[TRG_EVENT_DELETE][TRG_ACTION_AFTER]);
+ }
+
+ void set_table(TABLE *new_table);
+
+ void mark_fields_used(trg_event_type event);
+
+ void set_parse_error_message(const char *error_message);
+
+ friend class Item_trigger_field;
+
+ bool add_tables_and_routines_for_triggers(THD *thd,
+ Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list);
+ bool is_fields_updated_in_trigger(MY_BITMAP *used_fields,
+ trg_event_type event_type,
+ trg_action_time_type action_time);
+
+ void enable_fields_temporary_nullability(THD* thd);
+ void disable_fields_temporary_nullability();
+
+private:
+ bool prepare_record1_accessors();
+ bool change_table_name_in_triggers(THD *thd,
+ const char *old_db_name,
+ const char *new_db_name,
+ LEX_STRING *old_table_name,
+ LEX_STRING *new_table_name,
+ bool upgrading50to51);
+
+ static bool parse_triggers(THD *thd, TABLE *table,
+ Table_trigger_dispatcher *dsptch,
+ List<Trigger> &table_trigger_list);
+
+ bool check_for_broken_triggers()
+ {
+ if (m_has_unparseable_trigger)
+ {
+ my_message(ER_PARSE_ERROR, m_parse_error_message, MYF(0));
+ return true;
+ }
+ return false;
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+#endif // TABLE_TRIGGER_LIST_H_INCLUDED
=== added file 'sql/trigger.cc'
--- a/sql/trigger.cc 1970-01-01 00:00:00 +0000
+++ b/sql/trigger.cc 2013-01-25 11:48:04 +0000
@@ -0,0 +1,476 @@
+/*
+ Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#include "my_global.h"
+#include "sql_class.h"
+#include "trigger.h"
+#include "sp_head.h"
+#include "mysys_err.h" // EE_OUTOFMEMORY
+#include "trigger_creation_ctx.h"
+#include "sql_parse.h" // parse_sql()
+#include "sql_lex.h" //Query_tables_list
+#include "table.h" // TABLE_LIST
+#include "sp.h" // sp_update_stmt_used_routines, sp_add_used_routine
+
+
+/**
+ An error handler that catches all non-OOM errors which can occur during
+ parsing of trigger body. Such errors are ignored and corresponding error
+ message is used to construct a more verbose error message which contains
+ name of problematic trigger. This error message is later emitted when
+ one tries to perform DML or some of DDL on this table.
+ Also, if possible, grabs name of the trigger being parsed so it can be
+ used to correctly drop problematic trigger.
+*/
+class Deprecated_trigger_syntax_handler : public Internal_error_handler
+{
+private:
+
+ char m_message[MYSQL_ERRMSG_SIZE];
+ LEX_STRING *m_trigger_name;
+
+public:
+
+ Deprecated_trigger_syntax_handler() : m_trigger_name(NULL) {}
+
+ virtual bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_severity_level level,
+ const char* message,
+ Sql_condition ** cond_hdl)
+ {
+ if (sql_errno != EE_OUTOFMEMORY &&
+ sql_errno != ER_OUT_OF_RESOURCES)
+ {
+ if(thd->lex->spname)
+ m_trigger_name= &thd->lex->spname->m_name;
+ if (m_trigger_name)
+ my_snprintf(m_message, sizeof(m_message),
+ ER(ER_ERROR_IN_TRIGGER_BODY),
+ m_trigger_name->str, message);
+ else
+ my_snprintf(m_message, sizeof(m_message),
+ ER(ER_ERROR_IN_UNKNOWN_TRIGGER_BODY), message);
+ return true;
+ }
+ return false;
+ }
+
+ LEX_STRING *get_trigger_name() { return m_trigger_name; }
+ const char *get_error_message() { return m_message; }
+};
+
+
+/**
+ Initialize an instance of Trigger.
+
+ @param [in] thd thread handle
+ @param [in] lex LEX for parsing of trigger
+ @param [in] trigger_name trigger name
+ @param [in] trg_creation_ctx creation context of trigger
+ @param [in] db_name name of schema
+ @param [in] table pointer to the trigger's table
+
+ @return Operation status
+ @retval true Failure
+ @retval false Success
+*/
+
+bool Trigger::init(THD *thd, LEX *lex, LEX_STRING *trigger_name,
+ Stored_program_creation_ctx *trg_creation_ctx,
+ const LEX_STRING *db_name, TABLE *table)
+{
+ m_sp= lex->sphead;
+ m_sp->set_info(0, 0, &lex->sp_chistics, m_definition_mode);
+
+ m_trg_event= m_sp->m_trg_chistics.event;
+ m_trg_action_time= m_sp->m_trg_chistics.action_time;
+
+ lex->sphead= NULL; /* Prevent double cleanup. */
+
+ m_sp->set_creation_ctx(trg_creation_ctx);
+ set_trigger_name(trigger_name);
+
+ if (!m_definer->length)
+ {
+ /*
+ This trigger was created/imported from the previous version of
+ MySQL, which does not support triggers definers. We should emit
+ warning here.
+ */
+
+ push_warning_printf(thd, Sql_condition::SL_WARNING,
+ ER_TRG_NO_DEFINER, ER(ER_TRG_NO_DEFINER),
+ db_name->str,
+ m_sp->m_name.str);
+
+ /*
+ Set definer to the '' to correct displaying in the information
+ schema.
+ */
+
+ m_sp->set_definer((char*) "", 0);
+
+ /*
+ Triggers without definer information are executed under the
+ authorization of the invoker.
+ */
+
+ m_sp->m_chistics->suid= SP_IS_NOT_SUID;
+ }
+ else
+ m_sp->set_definer(m_definer->str, m_definer->length);
+
+ if (!(m_on_table_name= alloc_lex_string(&table->mem_root)))
+ return true;
+
+ m_on_table_name->str= (char*) lex->raw_trg_on_table_name_begin;
+ m_on_table_name->length= lex->raw_trg_on_table_name_end -
+ lex->raw_trg_on_table_name_begin;
+
+ return false;
+}
+
+
+/**
+ Execute trigger's body.
+
+ @param [in] thd Thread handle
+
+ @return Operation status
+ @retval true Trigger execution failed or trigger has compilation errors
+ @retval false Success
+*/
+
+bool Trigger::execute(THD *thd)
+{
+ if (m_has_parse_error)
+ return true;
+
+ bool err_status;
+ Sub_statement_state statement_state;
+ SELECT_LEX *save_current_select;
+
+ thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER);
+
+ /*
+ Reset current_select before call execute_trigger() and
+ restore it after return from one. This way error is set
+ in case of failure during trigger execution.
+ */
+ save_current_select= thd->lex->current_select;
+ thd->lex->current_select= NULL;
+ err_status=
+ m_sp->execute_trigger(thd,
+ m_db_name,
+ m_table_name,
+ m_subject_table_grant);
+ thd->lex->current_select= save_current_select;
+
+ thd->restore_sub_statement_state(&statement_state);
+
+ return err_status;
+}
+
+
+/**
+ Get information about trigger.
+
+ @param[out] trg_name trigger name
+ @param[out] trigger_stmt returns statement of trigger
+ @param[out] sql_mode returns sql_mode of trigger
+ @param[out] trg_definer returns definer/creator of trigger
+ @param[out] trg_definition returns definition of trigger
+ @param[out] client_cs_nam returns client character set
+ @param[out] connection_cl_nam returns connection collation
+ @param[out] db_cl_nam returns database collation
+*/
+
+void Trigger::get_info(LEX_STRING *trg_name,
+ LEX_STRING *trigger_stmt,
+ sql_mode_t *sql_mode,
+ LEX_STRING *trg_definer,
+ LEX_STRING *trg_definition,
+ LEX_STRING *client_cs_name,
+ LEX_STRING *connection_cl_name,
+ LEX_STRING *db_cl_name)
+{
+ *trg_name= *m_trigger_name;
+ if (trigger_stmt)
+ *trigger_stmt= m_sp->m_body_utf8;
+ *sql_mode= m_definition_mode;
+
+ if (trg_definer)
+ *trg_definer= *m_definer;
+
+ if (trg_definition)
+ *trg_definition= *m_definition;
+
+ *client_cs_name= *m_client_cs_name;
+ *connection_cl_name= *m_connection_cl_name;
+ *db_cl_name= *m_db_cl_name;
+}
+
+
+/**
+ Parse CREATE TRIGGER statement.
+
+ @param [in] thd thread handle
+ @param [in] table pointer to the trigger's table
+
+ @return Operation status
+ @retval true Failure
+ @retval false Success
+*/
+
+bool Trigger::parse_trigger_body(THD *thd, TABLE *table)
+{
+ LEX *old_lex= thd->lex, lex;
+ sp_rcontext *sp_runtime_ctx_saved= thd->sp_runtime_ctx;
+ sql_mode_t sql_mode_saved= thd->variables.sql_mode;
+ PSI_statement_locker *parent_locker= thd->m_statement_psi;
+ LEX_STRING *trigger_name= NULL;
+ bool result= false;
+
+ thd->variables.sql_mode= m_definition_mode;
+
+ Parser_state parser_state;
+ if (parser_state.init(thd, m_definition->str, m_definition->length))
+ {
+ thd->variables.sql_mode= sql_mode_saved;
+ return true;
+ }
+
+ thd->lex= &lex;
+
+ LEX_STRING current_db_name_saved= {thd->db, thd->db_length};
+ thd->reset_db(m_db_name->str, m_db_name->length);
+
+ Trigger_creation_ctx *creation_ctx=
+ Trigger_creation_ctx::create(thd,
+ m_db_name->str,
+ m_table_name->str,
+ m_client_cs_name,
+ m_connection_cl_name,
+ m_db_cl_name);
+
+ lex_start(thd);
+ thd->sp_runtime_ctx= NULL;
+
+ Deprecated_trigger_syntax_handler error_handler;
+ thd->push_internal_handler(&error_handler);
+ thd->m_statement_psi= NULL;
+ bool parse_error= parse_sql(thd, &parser_state, creation_ctx);
+ thd->m_statement_psi= parent_locker;
+ thd->pop_internal_handler();
+
+ /*
+ 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();
+
+ const LEX_STRING *trigger_name_ptr= NULL;
+
+ if (parse_error)
+ {
+ set_parse_error_message(error_handler.get_error_message());
+
+ /* Currently sphead is always set to NULL in case of a parse error */
+ DBUG_ASSERT(lex.sphead == NULL);
+
+ if (!error_handler.get_trigger_name())
+ {
+ result= true;
+ goto cleanup;
+ }
+
+ trigger_name_ptr= error_handler.get_trigger_name();
+ }
+ else
+ {
+ trigger_name_ptr= &lex.spname->m_name;
+ }
+
+ // Make a copy of trigger name.
+
+ trigger_name= lex_string_dup(&table->mem_root, trigger_name_ptr);
+
+ if (!trigger_name)
+ {
+ result= true;
+ goto cleanup;
+ }
+
+ if (!parse_error)
+ {
+ if (init(thd, &lex, trigger_name, creation_ctx, m_db_name, table))
+ {
+ result= true;
+ goto cleanup;
+ }
+ }
+ else
+ set_trigger_name(trigger_name);
+
+#ifndef DBUG_OFF
+ if (!parse_error)
+ {
+ /*
+ Let us check that we correctly update trigger definitions when we
+ rename tables with triggers.
+
+ In special cases like "RENAME TABLE `#mysql50#somename` TO `somename`"
+ or "ALTER DATABASE `#mysql50#somename` UPGRADE DATA DIRECTORY NAME"
+ we might be given table or database name with "#mysql50#" prefix (and
+ trigger's definiton contains un-prefixed version of the same name).
+ To remove this prefix we use check_n_cut_mysql50_prefix().
+ */
+
+ char fname[NAME_LEN + 1];
+ DBUG_ASSERT((!my_strcasecmp(table_alias_charset, lex.query_tables->db, m_db_name) ||
+ (check_n_cut_mysql50_prefix(m_db_name, fname, sizeof(fname)) &&
+ !my_strcasecmp(table_alias_charset, lex.query_tables->db, fname))));
+ DBUG_ASSERT((!my_strcasecmp(table_alias_charset, lex.query_tables->table_name, m_table_name) ||
+ (check_n_cut_mysql50_prefix(m_table_name, fname, sizeof(fname)) &&
+ !my_strcasecmp(table_alias_charset, lex.query_tables->table_name, fname))));
+ }
+#endif
+
+cleanup:
+ lex_end(&lex);
+ thd->reset_db(current_db_name_saved.str, current_db_name_saved.length);
+ thd->lex= old_lex;
+ thd->sp_runtime_ctx= sp_runtime_ctx_saved;
+ thd->variables.sql_mode= sql_mode_saved;
+
+ DBUG_RETURN(result);
+}
+
+
+/**
+ Setup table fields referenced from trigger.
+
+ @param [in] thd thread handle
+ @param [in] table pointer to the trigger's table
+*/
+
+void Trigger::setup_fields(THD *thd, TABLE *table)
+{
+ if (!m_sp)
+ return;
+
+ /*
+ Also let us bind these objects to Field objects in table being
+ opened.
+
+ We ignore errors here, because if even something is wrong we still
+ will be willing to open table to perform some operations (e.g.
+ SELECT)...
+ Anyway some things can be checked only during trigger execution.
+ */
+ for (Item_trigger_field *trg_field= m_sp->m_trg_table_fields.first;
+ trg_field;
+ trg_field= trg_field->next_trg_field)
+ {
+ trg_field->setup_field(thd, table, m_subject_table_grant);
+ }
+}
+
+
+/**
+ Add tables and routines used by trigger to the set of elements
+ used by statement.
+
+ @param [in] thd thread handle
+ @param [in out] prelocking_ctx prelocking context of the statement
+ @param [in] table_list TABLE_LIST for the table
+*/
+
+void Trigger::add_tables_and_routines(THD *thd,
+ Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list)
+{
+ MDL_key key(MDL_key::TRIGGER, m_sp->m_db.str, m_sp->m_name.str);
+
+ if (sp_add_used_routine(prelocking_ctx, thd->stmt_arena,
+ &key, table_list->belong_to_view))
+ {
+ m_sp->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,
+ &m_sp->m_sroutines,
+ table_list->belong_to_view);
+ m_sp->propagate_attributes(prelocking_ctx);
+ }
+}
+
+
+/**
+ Check whether any table's fields are used in trigger.
+
+ @param [in] used_fields bitmap of fields to check
+
+ @return Check result
+ @retval true Some table fields are used in trigger
+ @retval false None of table fields are used in trigger
+*/
+
+bool Trigger::is_fields_updated_in_trigger(const MY_BITMAP *used_fields) const
+{
+ Item_trigger_field *trg_field;
+ for (trg_field= m_sp->m_trg_table_fields.first; trg_field;
+ trg_field= trg_field->next_trg_field)
+ {
+ /* We cannot check fields which does not present in table. */
+ if (trg_field->field_idx != (uint)-1)
+ {
+ if (bitmap_is_set(used_fields, trg_field->field_idx) &&
+ trg_field->get_settable_routine_parameter())
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ Mark fields of subject table which we read/set in the trigger
+
+ @param [in] trigger_table pointer to the trigger's table
+*/
+
+void Trigger::mark_field_used(TABLE *trigger_table)
+{
+ Item_trigger_field *trg_field;
+
+ for (trg_field= m_sp->m_trg_table_fields.first; trg_field;
+ trg_field= trg_field->next_trg_field)
+ {
+ /* We cannot mark fields which does not present in table. */
+ if (trg_field->field_idx != (uint)-1)
+ {
+ bitmap_set_bit(trigger_table->read_set, trg_field->field_idx);
+ if (trg_field->get_settable_routine_parameter())
+ bitmap_set_bit(trigger_table->write_set, trg_field->field_idx);
+ }
+ }
+}
=== added file 'sql/trigger.h'
--- a/sql/trigger.h 1970-01-01 00:00:00 +0000
+++ b/sql/trigger.h 2013-01-25 11:48:04 +0000
@@ -0,0 +1,211 @@
+/*
+ Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#ifndef TRIGGER_H_INCLUDED
+#define TRIGGER_H_INCLUDED
+
+///////////////////////////////////////////////////////////////////////////
+
+struct GRANT_INFO;
+
+class sp_head;
+class Stored_program_creation_ctx;
+
+/** 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
+};
+
+/*
+ We need this two enums here instead of sql_lex.h because
+ at least one of them is used by Item_trigger_field interface.
+
+ Time when trigger is invoked (i.e. before or after row actually
+ inserted/updated/deleted).
+*/
+enum trg_action_time_type
+{
+ TRG_ACTION_BEFORE= 0,
+ TRG_ACTION_AFTER= 1,
+ TRG_ACTION_MAX
+};
+
+#include "my_global.h"
+#include "sql_alloc.h"
+
+class Query_tables_list;
+
+// FIXME: remove it.
+typedef ulonglong sql_mode_t;
+
+
+/**
+ This is a class that represents a trigger entity.
+ Trigger can be created, initialized, parsed and executed.
+*/
+class Trigger : public Sql_alloc
+{
+public:
+ Trigger(LEX_STRING *db_name, LEX_STRING *table_name,
+ LEX_STRING *trg_create_str, sql_mode_t trg_sql_mode,
+ LEX_STRING *trg_definer, LEX_STRING *client_cs_name,
+ LEX_STRING *connection_cl_name, LEX_STRING *db_cl_name)
+ : m_has_parse_error(false),
+ m_trigger_name(NULL), m_db_name(db_name), m_table_name(table_name),
+ m_on_table_name(NULL), m_subject_table_grant(NULL),
+ m_definition(trg_create_str), m_definition_mode(trg_sql_mode),
+ m_definer(trg_definer), m_client_cs_name(client_cs_name),
+ m_connection_cl_name(connection_cl_name), m_db_cl_name(db_cl_name),
+ m_sp(NULL), m_trg_action_time(TRG_ACTION_MAX), m_trg_event(TRG_EVENT_MAX)
+ {}
+
+ void set_has_parse_error()
+ {
+ m_has_parse_error= true;
+ }
+
+ bool init(THD *thd, LEX *lex, LEX_STRING *trg_name,
+ Stored_program_creation_ctx *trg_creation_ctx,
+ const LEX_STRING *db_name, TABLE *table);
+
+ bool execute(THD *thd);
+
+ void get_info(LEX_STRING *trg_name,
+ LEX_STRING *trigger_stmt,
+ sql_mode_t *sql_mode,
+ LEX_STRING *trg_definer,
+ LEX_STRING *trg_definition,
+ LEX_STRING *client_cs_nam,
+ LEX_STRING *connection_cl_nam,
+ LEX_STRING *db_cl_nam);
+
+ bool parse_trigger_body(THD *thd, TABLE *table);
+
+ void set_parse_error_message(const char *error_message)
+ {
+ m_has_parse_error= true;
+ strlcpy(m_parse_error_message, error_message,
+ sizeof(m_parse_error_message));
+ }
+
+ void set_trigger_name(LEX_STRING *trigger_name)
+ {
+ m_trigger_name= trigger_name;
+ }
+
+ bool has_parse_error() const
+ {
+ return m_has_parse_error;
+ }
+
+ const char* get_parse_error_message() const
+ {
+ return m_parse_error_message;
+ }
+
+ void setup_fields(THD *thd, TABLE *table);
+
+ void add_tables_and_routines(THD *thd,
+ Query_tables_list *prelocking_ctx,
+ TABLE_LIST *table_list);
+
+ bool is_fields_updated_in_trigger(const MY_BITMAP *used_fields) const;
+
+ void mark_field_used(TABLE *trigger_table);
+
+ void set_subject_table_grant(GRANT_INFO *table_grant)
+ {
+ m_subject_table_grant= table_grant;
+ }
+
+ trg_action_time_type get_trg_action_time() const
+ {
+ return m_trg_action_time;
+ }
+
+ trg_event_type get_trg_event() const
+ {
+ return m_trg_event;
+ }
+
+private:
+ bool m_has_parse_error;
+
+ /*
+ This error will be displayed when the user tries to manipulate or invoke
+ triggers on a table that has broken triggers. It will get set only once
+ per statement and thus will contain the first parse error encountered in
+ the trigger file.
+ */
+ char m_parse_error_message[MYSQL_ERRMSG_SIZE];
+
+public:
+ /*
+ Names of trigger.
+ */
+ LEX_STRING *m_trigger_name;
+
+private:
+ LEX_STRING *m_db_name;
+ LEX_STRING *m_table_name;
+
+public:
+ /**
+ "ON table_name" part in trigger definition, used for
+ updating trigger definition during RENAME TABLE.
+ */
+ LEX_STRING *m_on_table_name;
+
+private:
+ /**
+ Grant information for the trigger.
+ */
+ GRANT_INFO *m_subject_table_grant;
+
+public:
+ /*
+ Trigger definition to save it in file.
+ */
+ LEX_STRING *m_definition;
+
+ /*
+ sql mode for trigger
+ */
+ sql_mode_t m_definition_mode;
+
+ LEX_STRING *m_definer;
+
+ /*
+ Character set context, used for parsing and executing trigger.
+ */
+ LEX_STRING *m_client_cs_name;
+ LEX_STRING *m_connection_cl_name;
+ LEX_STRING *m_db_cl_name;
+ sp_head *m_sp;
+
+private:
+ trg_action_time_type m_trg_action_time;
+ trg_event_type m_trg_event;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+#endif // TRIGGER_H_INCLUDED
+
=== added file 'sql/trigger_creation_ctx.cc'
--- a/sql/trigger_creation_ctx.cc 1970-01-01 00:00:00 +0000
+++ b/sql/trigger_creation_ctx.cc 2013-01-22 20:38:40 +0000
@@ -0,0 +1,75 @@
+#include "my_global.h"
+#include "trigger_creation_ctx.h"
+#include "sql_db.h" // get_default_db_collation()
+
+Trigger_creation_ctx *
+Trigger_creation_ctx::create(THD *thd,
+ const char *db_name,
+ const char *table_name,
+ const LEX_STRING *client_cs_name,
+ const LEX_STRING *connection_cl_name,
+ const LEX_STRING *db_cl_name)
+{
+ const CHARSET_INFO *client_cs;
+ const CHARSET_INFO *connection_cl;
+ const CHARSET_INFO *db_cl;
+
+ bool invalid_creation_ctx= FALSE;
+
+ if (resolve_charset(client_cs_name->str,
+ thd->variables.character_set_client,
+ &client_cs))
+ {
+ sql_print_warning("Trigger for table '%s'.'%s': "
+ "invalid character_set_client value (%s).",
+ (const char *) db_name,
+ (const char *) table_name,
+ (const char *) client_cs_name->str);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (resolve_collation(connection_cl_name->str,
+ thd->variables.collation_connection,
+ &connection_cl))
+ {
+ sql_print_warning("Trigger for table '%s'.'%s': "
+ "invalid collation_connection value (%s).",
+ (const char *) db_name,
+ (const char *) table_name,
+ (const char *) connection_cl_name->str);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (resolve_collation(db_cl_name->str, NULL, &db_cl))
+ {
+ sql_print_warning("Trigger for table '%s'.'%s': "
+ "invalid database_collation value (%s).",
+ (const char *) db_name,
+ (const char *) table_name,
+ (const char *) db_cl_name->str);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (invalid_creation_ctx)
+ {
+ push_warning_printf(thd,
+ Sql_condition::SL_WARNING,
+ ER_TRG_INVALID_CREATION_CTX,
+ ER(ER_TRG_INVALID_CREATION_CTX),
+ (const char *) db_name,
+ (const char *) table_name);
+ }
+
+ /*
+ If we failed to resolve the database collation, load the default one
+ from the disk.
+ */
+
+ if (!db_cl)
+ db_cl= get_default_db_collation(thd, db_name);
+
+ return new Trigger_creation_ctx(client_cs, connection_cl, db_cl);
+}
=== added file 'sql/trigger_creation_ctx.h'
--- a/sql/trigger_creation_ctx.h 1970-01-01 00:00:00 +0000
+++ b/sql/trigger_creation_ctx.h 2013-01-22 20:38:40 +0000
@@ -0,0 +1,67 @@
+/*
+ Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#ifndef TRIGGER_CREATION_CTX_H_INCLUDED
+#define TRIGGER_CREATION_CTX_H_INCLUDED
+
+///////////////////////////////////////////////////////////////////////////
+
+#include "sp_head.h" // Stored_program_creation_ctx
+
+/**
+ Trigger_creation_ctx -- creation context of triggers.
+*/
+
+class Trigger_creation_ctx : public Stored_program_creation_ctx,
+ public Sql_alloc
+{
+public:
+ static Trigger_creation_ctx *create(THD *thd,
+ const char *db_name,
+ const char *table_name,
+ const LEX_STRING *client_cs_name,
+ const LEX_STRING *connection_cl_name,
+ const LEX_STRING *db_cl_name);
+
+public:
+ virtual Stored_program_creation_ctx *clone(MEM_ROOT *mem_root)
+ {
+ return new (mem_root) Trigger_creation_ctx(m_client_cs,
+ m_connection_cl,
+ m_db_cl);
+ }
+
+protected:
+ virtual Object_creation_ctx *create_backup_ctx(THD *thd) const
+ {
+ return new Trigger_creation_ctx(thd);
+ }
+
+private:
+ Trigger_creation_ctx(THD *thd)
+ :Stored_program_creation_ctx(thd)
+ { }
+
+ Trigger_creation_ctx(const CHARSET_INFO *client_cs,
+ const CHARSET_INFO *connection_cl,
+ const CHARSET_INFO *db_cl)
+ :Stored_program_creation_ctx(client_cs, connection_cl, db_cl)
+ { }
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+#endif // TRIGGER_CREATION_CTX_H_INCLUDED
=== added file 'sql/trigger_loader.cc'
--- a/sql/trigger_loader.cc 1970-01-01 00:00:00 +0000
+++ b/sql/trigger_loader.cc 2013-01-25 11:48:04 +0000
@@ -0,0 +1,1078 @@
+/*
+ Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#include "my_global.h"
+#include "trigger_loader.h"
+#include "sql_class.h"
+#include "sp_head.h" // sp_name
+#include "sql_base.h" // is_equal(LEX_STRING, LEX_STRING)
+#include "sql_table.h" // build_table_filename()
+#include <mysys_err.h> // EE_OUTOFMEMORY
+
+///////////////////////////////////////////////////////////////////////////
+
+/*
+ Structure representing contents of .TRN file which are used to support
+ database wide trigger namespace.
+*/
+
+struct Trigger_name
+{
+ LEX_STRING trigger_table;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Table of .TRG file field descriptors.
+ We have here only one field now because in nearest future .TRG
+ files will be merged into .FRM files (so we don't need something
+ like md5 or created fields).
+*/
+
+File_option Trigger_loader::trg_file_parameters[]=
+{
+ {
+ { C_STRING_WITH_LEN("triggers") },
+ my_offsetof(class Trigger_loader, definitions_list),
+ FILE_OPTIONS_STRLIST
+ },
+ {
+ { C_STRING_WITH_LEN("sql_modes") },
+ my_offsetof(class Trigger_loader, definition_modes_list),
+ FILE_OPTIONS_ULLLIST
+ },
+ {
+ { C_STRING_WITH_LEN("definers") },
+ my_offsetof(class Trigger_loader, definers_list),
+ FILE_OPTIONS_STRLIST
+ },
+ {
+ { C_STRING_WITH_LEN("client_cs_names") },
+ my_offsetof(class Trigger_loader, client_cs_names),
+ FILE_OPTIONS_STRLIST
+ },
+ {
+ { C_STRING_WITH_LEN("connection_cl_names") },
+ my_offsetof(class Trigger_loader, connection_cl_names),
+ FILE_OPTIONS_STRLIST
+ },
+ {
+ { C_STRING_WITH_LEN("db_cl_names") },
+ my_offsetof(class Trigger_loader, db_cl_names),
+ FILE_OPTIONS_STRLIST
+ },
+ { { 0, 0 }, 0, FILE_OPTIONS_STRING }
+};
+
+File_option Trigger_loader::trn_file_parameters[]=
+{
+ {
+ { C_STRING_WITH_LEN("trigger_table")},
+ offsetof(struct Trigger_name, trigger_table),
+ FILE_OPTIONS_ESTRING
+ },
+ { { 0, 0 }, 0, FILE_OPTIONS_STRING }
+};
+
+File_option sql_modes_parameters=
+{
+ { C_STRING_WITH_LEN("sql_modes") },
+ my_offsetof(class Trigger_loader, definition_modes_list),
+ FILE_OPTIONS_ULLLIST
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+const LEX_STRING Trigger_loader::trg_file_type=
+ { C_STRING_WITH_LEN("TRIGGERS") };
+
+const LEX_STRING Trigger_loader::trn_file_type=
+ { C_STRING_WITH_LEN("TRIGGERNAME") };
+
+///////////////////////////////////////////////////////////////////////////
+
+class Handle_old_incorrect_sql_modes_hook: public Unknown_key_hook
+{
+private:
+ char *path;
+public:
+ Handle_old_incorrect_sql_modes_hook(char *file_path)
+ :path(file_path)
+ {};
+ virtual bool process_unknown_string(const char *&unknown_key, uchar* base,
+ MEM_ROOT *mem_root, const char *end);
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+class Handle_old_incorrect_trigger_table_hook: public Unknown_key_hook
+{
+public:
+ Handle_old_incorrect_trigger_table_hook(char *file_path,
+ LEX_STRING *trigger_table_arg)
+ :path(file_path), trigger_table_value(trigger_table_arg)
+ {};
+ virtual bool process_unknown_string(const char *&unknown_key, uchar* base,
+ MEM_ROOT *mem_root, const char *end);
+private:
+ char *path;
+ LEX_STRING *trigger_table_value;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ Trigger BUG#14090 compatibility hook.
+
+ @param[in,out] unknown_key reference on the line with unknown
+ parameter and the parsing point
+ @param[in] base base address for parameter writing
+ (structure like TABLE)
+ @param[in] mem_root MEM_ROOT for parameters allocation
+ @param[in] end the end of the configuration
+
+ @note
+ NOTE: this hook process back compatibility for incorrectly written
+ sql_modes parameter (see BUG#14090).
+
+ @retval
+ false OK
+ @retval
+ true Error
+*/
+
+#define INVALID_SQL_MODES_LENGTH 13
+
+bool
+Handle_old_incorrect_sql_modes_hook::
+process_unknown_string(const char *&unknown_key, uchar* base,
+ MEM_ROOT *mem_root, const char *end)
+{
+ DBUG_ENTER("Handle_old_incorrect_sql_modes_hook::process_unknown_string");
+ DBUG_PRINT("info", ("unknown key: %60s", unknown_key));
+
+ if (unknown_key + INVALID_SQL_MODES_LENGTH + 1 < end &&
+ unknown_key[INVALID_SQL_MODES_LENGTH] == '=' &&
+ !memcmp(unknown_key, STRING_WITH_LEN("sql_modes")))
+ {
+ const char *ptr= unknown_key + INVALID_SQL_MODES_LENGTH + 1;
+
+ DBUG_PRINT("info", ("sql_modes affected by BUG#14090 detected"));
+ push_warning_printf(current_thd,
+ Sql_condition::SL_NOTE,
+ ER_OLD_FILE_FORMAT,
+ ER(ER_OLD_FILE_FORMAT),
+ (char *)path, "TRIGGER");
+ if (get_file_options_ulllist(ptr, end, unknown_key, base,
+ &sql_modes_parameters, mem_root))
+ {
+ DBUG_RETURN(true);
+ }
+ /*
+ Set parsing pointer to the last symbol of string (\n)
+ 1) to avoid problem with \0 in the junk after sql_modes
+ 2) to speed up skipping this line by parser.
+ */
+ unknown_key= ptr-1;
+ }
+ DBUG_RETURN(false);
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+#define INVALID_TRIGGER_TABLE_LENGTH 15
+
+/**
+ Trigger BUG#15921 compatibility hook. For details see
+ Handle_old_incorrect_sql_modes_hook::process_unknown_string().
+*/
+
+bool
+Handle_old_incorrect_trigger_table_hook::
+process_unknown_string(const char *&unknown_key, uchar* base,
+ MEM_ROOT *mem_root, const char *end)
+{
+ DBUG_ENTER("Handle_old_incorrect_trigger_table_hook::process_unknown_string");
+ DBUG_PRINT("info", ("unknown key: %60s", unknown_key));
+
+ if (unknown_key + INVALID_TRIGGER_TABLE_LENGTH + 1 < end &&
+ unknown_key[INVALID_TRIGGER_TABLE_LENGTH] == '=' &&
+ !memcmp(unknown_key, STRING_WITH_LEN("trigger_table")))
+ {
+ const char *ptr= unknown_key + INVALID_TRIGGER_TABLE_LENGTH + 1;
+
+ DBUG_PRINT("info", ("trigger_table affected by BUG#15921 detected"));
+ push_warning_printf(current_thd,
+ Sql_condition::SL_NOTE,
+ ER_OLD_FILE_FORMAT,
+ ER(ER_OLD_FILE_FORMAT),
+ (char *)path, "TRIGGER");
+
+ if (!(ptr= parse_escaped_string(ptr, end, mem_root, trigger_table_value)))
+ {
+ my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0), "trigger_table",
+ unknown_key);
+ DBUG_RETURN(true);
+ }
+
+ /* Set parsing pointer to the last symbol of string (\n). */
+ unknown_key= ptr-1;
+ }
+ DBUG_RETURN(false);
+}
+
+///////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ An error handler that catches all non-OOM errors which can occur during
+ parsing of trigger body. Such errors are ignored and corresponding error
+ message is used to construct a more verbose error message which contains
+ name of problematic trigger. This error message is later emitted when
+ one tries to perform DML or some of DDL on this table.
+ Also, if possible, grabs name of the trigger being parsed so it can be
+ used to correctly drop problematic trigger.
+*/
+
+class Deprecated_trigger_syntax_handler : public Internal_error_handler
+{
+private:
+
+ char m_message[MYSQL_ERRMSG_SIZE];
+ LEX_STRING *m_trigger_name;
+
+public:
+
+ Deprecated_trigger_syntax_handler() : m_trigger_name(NULL) {}
+
+ virtual bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ Sql_condition::enum_severity_level level,
+ const char* message,
+ Sql_condition ** cond_hdl)
+ {
+ if (sql_errno != EE_OUTOFMEMORY &&
+ sql_errno != ER_OUT_OF_RESOURCES)
+ {
+ if(thd->lex->spname)
+ m_trigger_name= &thd->lex->spname->m_name;
+ if (m_trigger_name)
+ my_snprintf(m_message, sizeof(m_message),
+ ER(ER_ERROR_IN_TRIGGER_BODY),
+ m_trigger_name->str, message);
+ else
+ my_snprintf(m_message, sizeof(m_message),
+ ER(ER_ERROR_IN_UNKNOWN_TRIGGER_BODY), message);
+ return true;
+ }
+ return false;
+ }
+
+ LEX_STRING *get_trigger_name() { return m_trigger_name; }
+ char *get_error_message() { return m_message; }
+};
+
+///////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////
+
+
+/**
+ Load table triggers from the data dictionary.
+
+ @param [in] thd thread handle
+ @param [in] db_name name of schema
+ @param [in] db_name name of table
+ @param [in] table pointer to the trigger's table
+ @param [out] table_trigger_list pointer to the list where new Trigger
+ objects will be inserted
+ @param [out] trigger_not_found true if trigger was not found by name,
+ else false
+
+ @return Operation status
+ @retval true Failure
+ @retval false Success
+*/
+
+bool Trigger_loader::load_triggers(THD *thd,
+ const char *db_name,
+ const char *table_name,
+ TABLE *table,
+ List<Trigger> *table_trigger_lst,
+ bool *trigger_not_found)
+{
+ Trigger_loader l;
+
+ return
+ l.load_triggers_impl(thd, db_name, table_name, table, trigger_not_found,
+ table_trigger_lst);
+}
+
+
+/**
+ Implementation for loading table triggers from the data dictionary.
+
+ @param [in] thd thread handle
+ @param [in] db_name name of schema
+ @param [in] table_name name of table
+ @param [in] table pointer to the trigger's table
+ @param [out] trigger_not_found true if trigger was not found by name,
+ else false
+ @param [out] table_trigger_list pointer to the list where new Trigger
+ objects will be inserted
+
+ @return Operation status
+ @retval true Failure
+ @retval false Success
+*/
+
+bool Trigger_loader::load_triggers_impl(THD *thd,
+ const char *db_name,
+ const char *table_name,
+ TABLE *table,
+ bool *triggers_not_found,
+ List<Trigger> *table_trigger_list)
+{
+ DBUG_ENTER("Trigger_loader::load_triggers_impl");
+
+ // Check that TRG-file exists.
+
+ char trg_file_path_buffer[FN_REFLEN];
+ LEX_STRING trg_file_path;
+
+ trg_file_path.length= build_table_filename(trg_file_path_buffer,
+ FN_REFLEN - 1,
+ db_name, table_name, TRG_EXT, 0);
+ trg_file_path.str= trg_file_path_buffer;
+
+ *triggers_not_found= false;
+
+ if (access(trg_file_path_buffer, F_OK))
+ {
+ if (errno == ENOENT)
+ *triggers_not_found= true;
+
+ DBUG_RETURN(true);
+ }
+
+ // The TRG-file exists so we got to load triggers.
+
+ File_parser *parser=
+ sql_parse_prepare(&trg_file_path, &table->mem_root, true);
+
+ if (!parser)
+ DBUG_RETURN(true);
+
+ if (!is_equal(&trg_file_type, parser->type()))
+ {
+ my_error(ER_WRONG_OBJECT, MYF(0), table_name, TRG_EXT + 1, "TRIGGER");
+ DBUG_RETURN(true);
+ }
+
+ Handle_old_incorrect_sql_modes_hook sql_modes_hook(trg_file_path.str);
+
+ if (parser->parse((uchar*)this, &table->mem_root,
+ trg_file_parameters,
+ TRG_NUM_REQUIRED_PARAMETERS,
+ &sql_modes_hook))
+ DBUG_RETURN(true);
+
+ if (definitions_list.is_empty())
+ {
+ DBUG_ASSERT(definition_modes_list.is_empty());
+ DBUG_ASSERT(definers_list.is_empty());
+ DBUG_ASSERT(client_cs_names.is_empty());
+ DBUG_ASSERT(connection_cl_names.is_empty());
+ DBUG_ASSERT(db_cl_names.is_empty());
+ DBUG_RETURN(false);
+ }
+
+ List_iterator_fast<LEX_STRING> it(definitions_list);
+
+ // Make sure sql_mode list is filled.
+
+ if (definition_modes_list.is_empty())
+ {
+ /*
+ It is old file format => we should fill list of sql_modes.
+
+ We use one mode (current) for all triggers, because we have not
+ information about mode in old format.
+ */
+ sql_mode_t *sql_mode= alloc_type<sql_mode_t>(&table->mem_root);
+
+ if (!sql_mode)
+ DBUG_RETURN(true); // EOM
+
+ *sql_mode= global_system_variables.sql_mode;
+
+ while (it++)
+ {
+ if (definition_modes_list.push_back(sql_mode, &table->mem_root))
+ DBUG_RETURN(true); // EOM
+ }
+
+ it.rewind();
+ }
+
+ // Make sure definer list is filled.
+
+ if (definers_list.is_empty())
+ {
+ /*
+ It is old file format => we should fill list of definers.
+
+ If there is no definer information, we should not switch context to
+ definer when checking privileges. I.e. privileges for such triggers
+ are checked for "invoker" rather than for "definer".
+ */
+
+ LEX_STRING *definer= alloc_lex_string(&table->mem_root);
+
+ if (!definer)
+ DBUG_RETURN(true); // EOM
+
+ definer->str= (char*) "";
+ definer->length= 0;
+
+ while (it++)
+ {
+ if (definers_list.push_back(definer, &table->mem_root))
+ DBUG_RETURN(true); // EOM
+ }
+
+ it.rewind();
+ }
+
+ // Make sure character set properties are filled.
+
+ if (client_cs_names.is_empty() ||
+ connection_cl_names.is_empty() ||
+ db_cl_names.is_empty())
+ {
+ if (!client_cs_names.is_empty() ||
+ !connection_cl_names.is_empty() ||
+ !db_cl_names.is_empty())
+ {
+ my_error(ER_TRG_CORRUPTED_FILE, MYF(0),
+ (const char *) db_name,
+ (const char *) table_name);
+
+ DBUG_RETURN(true);
+ }
+
+ push_warning_printf(thd, Sql_condition::SL_WARNING,
+ ER_TRG_NO_CREATION_CTX,
+ ER(ER_TRG_NO_CREATION_CTX),
+ (const char*) db_name,
+ (const char*) table_name);
+
+ LEX_STRING *trg_client_cs_name= alloc_lex_string(&table->mem_root);
+ LEX_STRING *trg_connection_cl_name= alloc_lex_string(&table->mem_root);
+ LEX_STRING *trg_db_cl_name= alloc_lex_string(&table->mem_root);
+
+ if (!trg_client_cs_name || !trg_connection_cl_name || !trg_db_cl_name)
+ DBUG_RETURN(true); // EOM
+
+ /*
+ Backward compatibility: assume that the query is in the current
+ character set.
+ */
+
+ lex_string_set(trg_client_cs_name,
+ thd->variables.character_set_client->csname);
+
+ lex_string_set(trg_connection_cl_name,
+ thd->variables.collation_connection->name);
+
+ lex_string_set(trg_db_cl_name,
+ thd->variables.collation_database->name);
+
+ while (it++)
+ {
+ if (client_cs_names.push_back(trg_client_cs_name, &table->mem_root) ||
+ connection_cl_names.push_back(trg_connection_cl_name, &table->mem_root) ||
+ db_cl_names.push_back(trg_db_cl_name, &table->mem_root))
+ {
+ DBUG_RETURN(true); // EOM
+ }
+ }
+
+ it.rewind();
+ }
+
+ DBUG_ASSERT(definition_modes_list.elements == definitions_list.elements);
+ DBUG_ASSERT(definers_list.elements == definitions_list.elements);
+ DBUG_ASSERT(client_cs_names.elements == definitions_list.elements);
+ DBUG_ASSERT(connection_cl_names.elements == definitions_list.elements);
+ DBUG_ASSERT(db_cl_names.elements == definitions_list.elements);
+
+ LEX_STRING *current_db_name= alloc_lex_string(&table->mem_root);
+ lex_string_set(current_db_name, db_name);
+
+ LEX_STRING *table_name_str= alloc_lex_string(&table->mem_root);
+ lex_string_set(table_name_str, table_name);
+
+ LEX_STRING *trg_create_str;
+
+ List_iterator_fast<sql_mode_t> itm(definition_modes_list);
+ List_iterator_fast<LEX_STRING> it_definer(definers_list);
+ List_iterator_fast<LEX_STRING> it_client_cs_name(client_cs_names);
+ List_iterator_fast<LEX_STRING> it_connection_cl_name(connection_cl_names);
+ List_iterator_fast<LEX_STRING> it_db_cl_name(db_cl_names);
+
+ while ((trg_create_str= it++))
+ {
+ sql_mode_t *sql_mode= itm++;
+ LEX_STRING *definer= it_definer++;
+ LEX_STRING *client_cs_name= it_client_cs_name++;
+ LEX_STRING *connection_cl_name= it_connection_cl_name++;
+ LEX_STRING *db_cl_name= it_db_cl_name++;
+
+ // Create an new trigger instance
+
+ Trigger* trigger=
+ new (&table->mem_root) Trigger(current_db_name,
+ table_name_str, trg_create_str,
+ *sql_mode, definer,
+ client_cs_name, connection_cl_name,
+ db_cl_name);
+
+ if (table_trigger_list->push_back(trigger, &table->mem_root))
+ {
+ delete trigger;
+ DBUG_RETURN(true);
+ }
+ }
+
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Store a table trigger into the data dictionary.
+
+ @param [in] tables pointer to trigger's table
+ @param [in] new_trigger trigger to save
+ @param [in] table_triggers pointer to the list where new trigger object
+ has to be added
+
+ @return Operation status
+ @retval true Failure
+ @retval false Success
+*/
+
+bool Trigger_loader::store_trigger(TABLE_LIST *tables,
+ Trigger *new_trigger,
+ List<Trigger> *table_triggers)
+{
+ LEX_STRING trn_file_name;
+ TABLE *table= tables->table;
+ char trigger_name_buffer[FN_REFLEN];
+ Trigger_name trigger_name;
+ bool was_truncated;
+
+ if (table_triggers->push_back(new_trigger, &table->mem_root))
+ return true;
+
+ if (update_triggers_definition(tables->table, table_triggers, NULL, NULL))
+ return true;
+
+ /*
+ Here we are creating file with triggers and save all triggers in it.
+ sql_create_definition_file() files handles renaming and backup of older
+ versions
+ */
+ trigger_name.trigger_table.str= tables->table_name;
+ trigger_name.trigger_table.length= tables->table_name_length;
+
+ /* Building .TRN trigger filenames */
+ trn_file_name.length= build_table_filename(trigger_name_buffer, FN_REFLEN-1,
+ tables->db,
+ new_trigger->m_trigger_name->str,
+ TRN_EXT, 0, &was_truncated);
+
+ // Check if we hit FN_REFLEN bytes in path length
+ if (was_truncated)
+ {
+ my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), sizeof(trigger_name_buffer)-1,
+ trigger_name_buffer);
+ return true;
+ }
+ trn_file_name.str= trigger_name_buffer;
+
+ if (sql_create_definition_file(NULL, &trn_file_name, &trn_file_type,
+ (uchar*)&trigger_name, trn_file_parameters))
+ return true;
+
+ /* Create trigger definition file. */
+ if (save_trigger_file(tables->db, tables->table_name))
+ {
+ mysql_file_delete(key_file_trn, trigger_name_buffer, MYF(MY_WME));
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ Store a table trigger into the data dictionary.
+
+ @param [in] tables pointer to trigger's table
+ @param [in] new_trigger trigger to save
+ @param [in] table_triggers pointer to the list where new trigger object
+ has to be added
+
+ @return Operation status
+ @retval true Failure
+ @retval false Success
+*/
+
+bool Trigger_loader::drop_trigger(TABLE_LIST *tables,
+ const char *trigger_name,
+ List<Trigger> *table_triggers)
+{
+ char path[FN_REFLEN];
+
+ bool found= false;
+ update_triggers_definition(tables->table, table_triggers,
+ trigger_name, &found);
+
+ if (found)
+ {
+ if (table_triggers->is_empty())
+ {
+ /*
+ TODO: Probably instead of removing .TRG file we should move
+ to archive directory but this should be done as part of
+ parse_file.cc functionality (because we will need it
+ elsewhere).
+ */
+ if (rm_trigger_file(path, tables->db, tables->table_name))
+ return true;
+ }
+ else
+ {
+ if (save_trigger_file(tables->db, tables->table_name))
+ return true;
+ }
+
+ if (rm_trigname_file(path, tables->db, trigger_name))
+ return true;
+ return false;
+ }
+
+ my_message(ER_TRG_DOES_NOT_EXIST, ER(ER_TRG_DOES_NOT_EXIST), MYF(0));
+ return true;
+}
+
+
+/**
+ This method saves .TRG file for the table specified by arguments.
+
+ @param db name of database for subject table
+ @param table_name name of subject table
+
+ @retval
+ false Success
+ @retval
+ true Error
+*/
+
+bool Trigger_loader::save_trigger_file(const char *db,
+ const char *table_name)
+{
+ char file_buff[FN_REFLEN];
+ LEX_STRING file;
+ bool was_truncated= false;
+
+ file.length= build_table_filename(file_buff, FN_REFLEN - 1, db, table_name,
+ TRG_EXT, 0, &was_truncated);
+
+ if (was_truncated)
+ {
+ my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), sizeof(file_buff)-1,
+ file_buff);
+ return true;
+ }
+
+ file.str= file_buff;
+ return sql_create_definition_file(NULL, &file, &trg_file_type,
+ (uchar*)this, trg_file_parameters);
+}
+
+
+/**
+ Update trigger informations in internal structures.
+ This internal structure is used later to save trigger definitions
+ in the data dictionary.
+
+ @param [in] table pointer to the TABLE that triggers belong
+ @param [in out] trgs list of triggers to prepare for storing
+ in the data dictionary. On return those triggers
+ that have unparseable SQL-definition will be
+ removed from the list
+ @param [in] name name of trigger whose definition has to be
+ removed
+ @param [out] found pointer to the variable where the result of
+ trigger removing will be stored. found has to be
+ set to the false before passing to this method
+
+
+ @return Operation status
+ @retval false On success.
+ @retval true Otherwise.
+*/
+
+bool Trigger_loader::update_triggers_definition(TABLE *table,
+ List<Trigger> *trgs,
+ const char *name,
+ bool *found)
+{
+ List_iterator<Trigger> it_table_triggers(*trgs);
+
+ Trigger *next_trigger;
+ while ((next_trigger= it_table_triggers++))
+ {
+ if (name &&
+ my_strcasecmp(table_alias_charset, next_trigger->m_trigger_name->str,
+ name) == 0)
+ {
+ it_table_triggers.remove();
+ *found= true;
+ continue;
+ }
+ if (definitions_list.push_back(next_trigger->m_definition,
+ &table->mem_root) ||
+ definition_modes_list.push_back(&next_trigger->m_definition_mode,
+ &table->mem_root) ||
+ definers_list.push_back(next_trigger->m_definer,
+ &table->mem_root) ||
+ client_cs_names.push_back(next_trigger->m_client_cs_name,
+ &table->mem_root) ||
+ connection_cl_names.push_back(next_trigger->m_connection_cl_name,
+ &table->mem_root) ||
+ db_cl_names.push_back(next_trigger->m_db_cl_name, &table->mem_root))
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ Check whether the trigger specified by schema and name does exist
+ in the data dictionary.
+
+ @param [in] db_name name of schema
+ @param [in] trigger_name name of trigger
+
+ @return Operation status
+ @retval true Trigger exists. Set error in Diagnostic_area as
+ a side effect
+ @retval false Trigger doesn't exist
+*/
+
+bool Trigger_loader::check_for_uniqueness(const char *db_name,
+ const char *trigger_name)
+{
+ char trigger_name_buffer[FN_REFLEN];
+ bool was_truncated;
+
+ /* Building .TRN trigger filenames */
+ build_table_filename(trigger_name_buffer, FN_REFLEN-1,
+ db_name,
+ trigger_name,
+ TRN_EXT, 0, &was_truncated);
+ // Check if we hit FN_REFLEN bytes in path length
+ if (was_truncated)
+ {
+ my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), sizeof(trigger_name_buffer)-1,
+ trigger_name_buffer);
+ return true;
+ }
+
+ /* Use the filesystem to enforce trigger namespace constraints. */
+ if (!access(trigger_name_buffer, F_OK))
+ {
+ my_error(ER_TRG_ALREADY_EXISTS, MYF(0));
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ Get name of table by trigger name.
+
+ @param [in] thd thread handle
+ @param [in] trg_name name of trigger
+ @param [in] trn_path path to the corresponding TRN-file
+ @param [out] tbl_name variable to store retrieved table name
+
+ @return Operation status
+ @retval true Failure.
+ @retval false Success.
+*/
+
+bool Trigger_loader::get_table_name_for_trigger(THD *thd,
+ const sp_name *trg_name,
+ const LEX_STRING *trn_path,
+ LEX_STRING *tbl_name)
+{
+ File_parser *parser;
+ struct Trigger_name trn_data;
+
+ Handle_old_incorrect_trigger_table_hook trigger_table_hook(
+ trn_path->str,
+ &trn_data.trigger_table);
+
+ DBUG_ENTER("Trigger_loader::get_table_name_for_trigger()");
+
+ /* Parse the TRN-file. */
+
+ if (!(parser= sql_parse_prepare(trn_path, thd->mem_root, true)))
+ DBUG_RETURN(true);
+
+ if (!is_equal(&trn_file_type, parser->type()))
+ {
+ my_error(ER_WRONG_OBJECT, MYF(0),
+ trg_name->m_name.str,
+ TRN_EXT + 1,
+ "TRIGGERNAME");
+
+ DBUG_RETURN(true);
+ }
+
+ if (parser->parse((uchar*) &trn_data, thd->mem_root,
+ trn_file_parameters, 1,
+ &trigger_table_hook))
+ DBUG_RETURN(true);
+
+ /* Copy trigger table name. */
+
+ *tbl_name= trn_data.trigger_table;
+
+ /* That's all. */
+
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Get name of table by trigger name.
+
+ @param [in] thd Thread handle
+ @param [in] trg_name Name of trigger
+ @param [in] trn_path Path to the corresponding TRN-file
+ @param [out] tbl_name Variable to store retrieved table name
+
+ @return Operation status
+ @retval true Failure.
+ @retval false Success.
+*/
+
+bool Trigger_loader::drop_all_triggers(const char *db_name,
+ const char *table_name,
+ List<Trigger> &table_trgs)
+{
+ Trigger *trigger;
+ char path[FN_REFLEN];
+ List_iterator_fast<Trigger> it_triggers(table_trgs);
+ bool result= false;
+
+ while ((trigger= it_triggers++))
+ {
+ if (rm_trigname_file(path, db_name, trigger->m_trigger_name->str))
+ {
+ /*
+ Instead of immediately bailing out with error if we were unable
+ to remove .TRN file we will try to drop other files.
+ */
+ result= true;
+ continue;
+ }
+ }
+
+ if (rm_trigger_file(path, db_name, table_name))
+ result= true;
+
+ return result;
+}
+
+
+/**
+ Deletes the .TRN file for a trigger.
+
+ @param [in] path char buffer of size FN_REFLEN to be used
+ for constructing path to .TRN file
+ @param [in] db trigger's database name
+ @param [in] trigger_name trigger's name
+
+ @return Operation status
+ @retval true Failure
+ @retval false Success
+*/
+
+bool Trigger_loader::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 mysql_file_delete(key_file_trn, path, MYF(MY_WME));
+}
+
+
+/**
+ Deletes the .TRG file for a table.
+
+ @param path char buffer of size FN_REFLEN to be used
+ for constructing path to .TRG file
+ @param db table's database name
+ @param table_name table's name
+
+ @return Operation status
+ @retval true Failure.
+ @retval false Success.
+*/
+
+bool Trigger_loader::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 mysql_file_delete(key_file_trg, path, MYF(MY_WME));
+}
+
+
+/**
+ Get name of table by trigger name.
+
+ @param [in] thd thread handle
+ @param [in] trg_name name of trigger
+ @param [in] trn_path path to the corresponding TRN-file
+ @param [out] tbl_name variable to store retrieved table name
+
+ @return Operation status
+ @retval true Failure.
+ @retval false Success.
+*/
+
+bool Trigger_loader::rename_table_in_trigger(TABLE *trigger_table,
+ List<Trigger> *table_triggers,
+ const char *old_db_name,
+ LEX_STRING *old_table_name,
+ const char *new_db_name,
+ LEX_STRING *new_table_name,
+ bool upgrading50to51)
+{
+ char path_buff[FN_REFLEN];
+ LEX_STRING *err_trigname;
+
+ if (update_triggers_definition(trigger_table, table_triggers,
+ NULL, NULL))
+ return true; /* OOM */
+
+ if ((err_trigname= change_table_name_in_trignames(
+ *table_triggers,
+ upgrading50to51 ? old_db_name : NULL,
+ new_db_name, new_table_name, 0)))
+ {
+ /*
+ If we were unable to update one of .TRN files properly we will
+ revert all changes that we have done and report about error.
+ We assume that we will be able to undo our changes without errors
+ (we can't do much if there will be an error anyway).
+ */
+ (void) change_table_name_in_trignames(
+ *table_triggers,
+ upgrading50to51 ? new_db_name : NULL, old_db_name,
+ old_table_name, err_trigname);
+ return true;
+ }
+
+ if (save_trigger_file(new_db_name, new_table_name->str))
+ return true;
+ if (rm_trigger_file(path_buff, old_db_name, old_table_name->str))
+ {
+ (void) rm_trigger_file(path_buff, new_db_name, new_table_name->str);
+ return true;
+ }
+ return false;
+}
+
+/**
+ Iterate though list of triggers and update
+ .TRN files after renaming triggers' subject table.
+
+ @param old_db_name Old database of subject table
+ @param new_db_name New database of subject table
+ @param new_table_name New subject table's name
+ @param stopper Pointer to a trigger_name for
+ which we should stop updating.
+
+ @retval
+ 0 Success
+ @retval
+ non-0 Failure, pointer to trigger_name
+ for which update failed.
+*/
+
+LEX_STRING*
+Trigger_loader::change_table_name_in_trignames(List<Trigger> &table_triggers,
+ const char *old_db_name,
+ const char *new_db_name,
+ LEX_STRING *new_table_name,
+ LEX_STRING *stopper)
+{
+ char trigger_name_buffer[FN_REFLEN];
+ Trigger_name trigger_name;
+ LEX_STRING trn_file_name;
+ Trigger *trigger;
+ List_iterator_fast<Trigger> it_table_triggers(table_triggers);
+
+ while ((trigger= it_table_triggers++))
+ {
+ if (trigger->m_trigger_name == stopper)
+ break;
+ trn_file_name.length= build_table_filename(trigger_name_buffer, FN_REFLEN-1,
+ new_db_name,
+ trigger->m_trigger_name->str,
+ TRN_EXT, 0);
+ trn_file_name.str= trigger_name_buffer;
+
+ trigger_name.trigger_table= *new_table_name;
+
+ if (sql_create_definition_file(NULL, &trn_file_name, &trn_file_type,
+ (uchar*)&trigger_name, trn_file_parameters))
+ return trigger->m_trigger_name;
+
+ /* Remove stale .TRN file in case of database upgrade */
+ if (old_db_name)
+ {
+ if (rm_trigname_file(trigger_name_buffer, old_db_name,
+ trigger->m_trigger_name->str))
+ {
+ (void) rm_trigname_file(trigger_name_buffer, new_db_name,
+ trigger->m_trigger_name->str);
+ return trigger->m_trigger_name;
+ }
+ }
+ }
+
+ return NULL;
+}
=== added file 'sql/trigger_loader.h'
--- a/sql/trigger_loader.h 1970-01-01 00:00:00 +0000
+++ b/sql/trigger_loader.h 2013-01-25 11:48:04 +0000
@@ -0,0 +1,147 @@
+/*
+ Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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,
+ 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
+
+#ifndef TRIGGER_LOADER_H_INCLUDED
+#define TRIGGER_LOADER_H_INCLUDED
+
+///////////////////////////////////////////////////////////////////////////
+
+#include "sql_list.h"
+
+#include "parse_file.h" // File_option FIXME: remove dependency.
+
+struct TABLE;
+struct TABLE_LIST;
+
+class THD;
+class Trigger;
+class sp_name;
+
+class Trigger_loader
+{
+public:
+ static bool load_triggers(THD *thd,
+ const char *db_name,
+ const char *table_name,
+ TABLE *table,
+ List<Trigger> *table_trigger_lst,
+ bool *trigger_not_found);
+
+private:
+ bool load_triggers_impl(THD *thd,
+ const char *db_name,
+ const char *table_name,
+ TABLE *table,
+ bool *trigger_not_found,
+ List<Trigger> *table_trigger_lst);
+
+public:
+ bool store_trigger(TABLE_LIST *tables,
+ Trigger *new_trigger,
+ List<Trigger> *table_triggers);
+
+ bool drop_trigger(TABLE_LIST *tables,
+ const char *trigger_name,
+ List<Trigger> *table_triggers);
+
+ static bool check_for_uniqueness(const char *db_name,
+ const char *trigger_name);
+
+ bool rename_table_in_trigger(TABLE *trigger_table,
+ List<Trigger> *table_triggers,
+ const char *old_db_name,
+ LEX_STRING *old_table_name,
+ const char *new_db_name,
+ LEX_STRING *new_table_name,
+ bool upgrading50to51);
+
+ bool get_table_name_for_trigger(THD *thd,
+ const sp_name *trg_name,
+ const LEX_STRING *trn_path,
+ LEX_STRING *tbl_name);
+
+ bool drop_all_triggers(const char* db_name,
+ const char* table_name,
+ List<Trigger> &table_triggers);
+
+private:
+ bool update_triggers_definition(TABLE *table,
+ List<Trigger> *triggers,
+ const char *name,
+ bool *found);
+
+ bool save_trigger_file(const char *db,
+ const char *table_name);
+
+ LEX_STRING* change_table_name_in_trignames(List<Trigger> &table_triggers,
+ const char *old_db_name,
+ const char *new_db_name,
+ LEX_STRING *new_table_name,
+ LEX_STRING *stopper);
+
+ bool rm_trigname_file(char *path, const char *db,
+ const char *trigger_name);
+
+ bool rm_trigger_file(char *path, const char *db,
+ const char *table_name);
+
+ /**
+ This must be kept up to date whenever a new option is added to the list
+ above, as it specifies the number of required parameters of the trigger in
+ .trg file.
+ */
+
+ static const int TRG_NUM_REQUIRED_PARAMETERS= 6;
+
+ /**
+ Table of .TRG file field descriptors.
+ We have here only one field now because in nearest future .TRG
+ files will be merged into .FRM files (so we don't need something
+ like md5 or created fields).
+ */
+ static File_option trg_file_parameters[];
+
+ static File_option trn_file_parameters[];
+
+ static const LEX_STRING trg_file_type;
+
+ static const LEX_STRING trn_file_type;
+
+ /**
+ Field responsible for storing triggers definitions in file.
+ */
+ List<LEX_STRING> definitions_list;
+
+public:
+ /**
+ List of sql modes for triggers.
+ It has to be public because we are using it directly from parser.
+ */
+ List<ulonglong> definition_modes_list;
+
+private:
+ List<LEX_STRING> definers_list;
+
+ /* Character set context, used for parsing and executing triggers. */
+
+ List<LEX_STRING> client_cs_names;
+ List<LEX_STRING> connection_cl_names;
+ List<LEX_STRING> db_cl_names;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+#endif // TRIGGER_LOADER_H_INCLUDED
No bundle (reason: useless for push emails).
| Thread |
|---|
| • bzr push into mysql-trunk branch (Dmitry.Shulga:5356 to 5376) | Dmitry Shulga | 11 Mar 2013 |