2005-07-09 19:51:59 +02:00
|
|
|
/* Copyright (C) 2004-2005 MySQL AB
|
|
|
|
|
|
|
|
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; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
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, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
|
|
|
|
|
|
|
|
2004-09-07 14:29:46 +02:00
|
|
|
#include "mysql_priv.h"
|
|
|
|
#include "sp_head.h"
|
|
|
|
#include "sql_trigger.h"
|
|
|
|
#include "parse_file.h"
|
|
|
|
|
|
|
|
static const LEX_STRING triggers_file_type= {(char *)"TRIGGERS", 8};
|
2005-03-27 14:15:21 +02:00
|
|
|
|
|
|
|
const char * const triggers_file_ext= ".TRG";
|
2004-09-07 14:29:46 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
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[]=
|
|
|
|
{
|
2005-11-10 20:25:03 +01:00
|
|
|
{
|
|
|
|
{ (char *) STRING_WITH_LEN("triggers") },
|
2005-07-28 21:39:11 +02:00
|
|
|
offsetof(class Table_triggers_list, definitions_list),
|
2005-11-10 20:25:03 +01:00
|
|
|
FILE_OPTIONS_STRLIST
|
|
|
|
},
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
FIXME: Length specified for "sql_modes" key is erroneous, problem caused
|
|
|
|
by this are reported as BUG#14090 and should be fixed ASAP.
|
|
|
|
*/
|
|
|
|
{ (char *) "sql_modes", 13 },
|
2005-07-28 21:39:11 +02:00
|
|
|
offsetof(class Table_triggers_list, definition_modes_list),
|
2005-11-10 20:25:03 +01:00
|
|
|
FILE_OPTIONS_ULLLIST
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{ (char *) STRING_WITH_LEN("definers") },
|
|
|
|
offsetof(class Table_triggers_list, definers_list),
|
|
|
|
FILE_OPTIONS_STRLIST
|
|
|
|
},
|
|
|
|
{ { 0, 0 }, 0, FILE_OPTIONS_STRING }
|
2004-09-07 14:29:46 +02:00
|
|
|
};
|
|
|
|
|
2005-11-10 20:25:03 +01:00
|
|
|
/*
|
|
|
|
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= 4;
|
|
|
|
static const int TRG_MAX_VERSIONS= 3;
|
2004-09-07 14:29:46 +02:00
|
|
|
|
2005-07-19 18:06:49 +02:00
|
|
|
/*
|
|
|
|
Structure representing contents of .TRN file which are used to support
|
|
|
|
database wide trigger namespace.
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct st_trigname
|
|
|
|
{
|
|
|
|
LEX_STRING trigger_table;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const LEX_STRING trigname_file_type= {(char *)"TRIGGERNAME", 11};
|
|
|
|
|
|
|
|
const char * const trigname_file_ext= ".TRN";
|
|
|
|
|
|
|
|
static File_option trigname_file_parameters[]=
|
|
|
|
{
|
2005-11-10 20:25:03 +01:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
FIXME: Length specified for "trigger_table" key is erroneous, problem
|
|
|
|
caused by this are reported as BUG#14090 and should be fixed ASAP.
|
|
|
|
*/
|
|
|
|
{ (char *) "trigger_table", 15 },
|
|
|
|
offsetof(struct st_trigname, trigger_table),
|
|
|
|
FILE_OPTIONS_ESTRING
|
|
|
|
},
|
|
|
|
{ { 0, 0 }, 0, FILE_OPTIONS_STRING }
|
2005-07-19 18:06:49 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const LEX_STRING trg_action_time_type_names[]=
|
|
|
|
{
|
|
|
|
{ (char *) STRING_WITH_LEN("BEFORE") },
|
|
|
|
{ (char *) STRING_WITH_LEN("AFTER") }
|
|
|
|
};
|
|
|
|
|
|
|
|
const LEX_STRING trg_event_type_names[]=
|
|
|
|
{
|
|
|
|
{ (char *) STRING_WITH_LEN("INSERT") },
|
|
|
|
{ (char *) STRING_WITH_LEN("UPDATE") },
|
|
|
|
{ (char *) STRING_WITH_LEN("DELETE") }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static TABLE_LIST *add_table_for_trigger(THD *thd, sp_name *trig);
|
|
|
|
|
|
|
|
|
2004-09-07 14:29:46 +02:00
|
|
|
/*
|
|
|
|
Create or drop trigger for table.
|
|
|
|
|
|
|
|
SYNOPSIS
|
|
|
|
mysql_create_or_drop_trigger()
|
|
|
|
thd - current thread context (including trigger definition in LEX)
|
|
|
|
tables - table list containing one table for which trigger is created.
|
2005-05-06 10:39:30 +02:00
|
|
|
create - whenever we create (TRUE) or drop (FALSE) trigger
|
2004-09-07 14:29:46 +02:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
RETURN VALUE
|
2004-10-20 03:04:37 +02:00
|
|
|
FALSE Success
|
|
|
|
TRUE error
|
2004-09-07 14:29:46 +02:00
|
|
|
*/
|
2004-10-20 03:04:37 +02:00
|
|
|
bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
|
2004-09-07 14:29:46 +02:00
|
|
|
{
|
|
|
|
TABLE *table;
|
2005-10-17 20:37:24 +02:00
|
|
|
bool result= TRUE;
|
2005-11-10 20:25:03 +01:00
|
|
|
LEX_STRING definer_user;
|
|
|
|
LEX_STRING definer_host;
|
|
|
|
|
2004-09-07 14:29:46 +02:00
|
|
|
DBUG_ENTER("mysql_create_or_drop_trigger");
|
|
|
|
|
|
|
|
/*
|
|
|
|
QQ: This function could be merged in mysql_alter_table() function
|
|
|
|
But do we want this ?
|
|
|
|
*/
|
|
|
|
|
2005-07-19 18:06:49 +02:00
|
|
|
if (!create &&
|
|
|
|
!(tables= add_table_for_trigger(thd, thd->lex->spname)))
|
|
|
|
DBUG_RETURN(TRUE);
|
|
|
|
|
2005-07-13 21:51:09 +02:00
|
|
|
/* We should have only one table in table list. */
|
|
|
|
DBUG_ASSERT(tables->next_global == 0);
|
|
|
|
|
2004-09-24 15:55:43 +02:00
|
|
|
/*
|
|
|
|
TODO: We should check if user has TRIGGER privilege for table here.
|
|
|
|
Now we just require SUPER privilege for creating/dropping because
|
|
|
|
we don't have proper privilege checking for triggers in place yet.
|
|
|
|
*/
|
|
|
|
if (check_global_access(thd, SUPER_ACL))
|
2004-10-20 03:04:37 +02:00
|
|
|
DBUG_RETURN(TRUE);
|
2004-09-07 14:29:46 +02:00
|
|
|
|
|
|
|
/*
|
2005-10-17 20:37:24 +02:00
|
|
|
There is no DETERMINISTIC clause for triggers, so can't check it.
|
|
|
|
But a trigger can in theory be used to do nasty things (if it supported
|
|
|
|
DROP for example) so we do the check for privileges. For now there is
|
|
|
|
already a stronger test right above; but when this stronger test will
|
2005-11-10 17:50:51 +01:00
|
|
|
be removed, the test below will hold. Because triggers have the same
|
|
|
|
nature as functions regarding binlogging: their body is implicitely
|
|
|
|
binlogged, so they share the same danger, so trust_function_creators
|
|
|
|
applies to them too.
|
2004-09-07 14:29:46 +02:00
|
|
|
*/
|
2005-11-10 17:50:51 +01:00
|
|
|
if (!trust_function_creators && mysql_bin_log.is_open() &&
|
2005-10-17 20:37:24 +02:00
|
|
|
!(thd->security_ctx->master_access & SUPER_ACL))
|
2004-09-07 14:29:46 +02:00
|
|
|
{
|
2005-10-17 20:37:24 +02:00
|
|
|
my_error(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER, MYF(0));
|
2004-10-20 03:04:37 +02:00
|
|
|
DBUG_RETURN(TRUE);
|
2004-09-07 14:29:46 +02:00
|
|
|
}
|
|
|
|
|
2005-10-17 20:37:24 +02:00
|
|
|
/* We do not allow creation of triggers on temporary tables. */
|
|
|
|
if (create && find_temporary_table(thd, tables->db, tables->table_name))
|
2004-09-07 14:29:46 +02:00
|
|
|
{
|
2005-10-17 20:37:24 +02:00
|
|
|
my_error(ER_TRG_ON_VIEW_OR_TEMP_TABLE, MYF(0), tables->alias);
|
|
|
|
DBUG_RETURN(TRUE);
|
2004-09-07 14:29:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
We don't want perform our operations while global read lock is held
|
|
|
|
so we have to wait until its end and then prevent it from occuring
|
|
|
|
again until we are done. (Acquiring LOCK_open is not enough because
|
|
|
|
global read lock is held without helding LOCK_open).
|
|
|
|
*/
|
2005-10-17 20:37:24 +02:00
|
|
|
if (wait_if_global_read_lock(thd, 0, 1))
|
2004-10-20 03:04:37 +02:00
|
|
|
DBUG_RETURN(TRUE);
|
2004-09-07 14:29:46 +02:00
|
|
|
|
2005-10-17 20:37:24 +02:00
|
|
|
VOID(pthread_mutex_lock(&LOCK_open));
|
|
|
|
|
|
|
|
if (lock_table_names(thd, tables))
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
/* We also don't allow creation of triggers on views. */
|
|
|
|
tables->required_type= FRMTYPE_TABLE;
|
|
|
|
|
|
|
|
if (reopen_name_locked_table(thd, tables))
|
2005-05-06 18:52:19 +02:00
|
|
|
{
|
2005-10-17 20:37:24 +02:00
|
|
|
unlock_table_name(thd, tables);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
table= tables->table;
|
|
|
|
|
|
|
|
if (!table->triggers)
|
|
|
|
{
|
|
|
|
if (!create)
|
|
|
|
{
|
|
|
|
my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(table->triggers= new (&table->mem_root) Table_triggers_list(table)))
|
|
|
|
goto end;
|
2005-05-06 18:52:19 +02:00
|
|
|
}
|
|
|
|
|
2004-10-20 03:04:37 +02:00
|
|
|
result= (create ?
|
2005-11-10 20:25:03 +01:00
|
|
|
table->triggers->create_trigger(thd, tables, &definer_user, &definer_host):
|
2004-10-20 03:04:37 +02:00
|
|
|
table->triggers->drop_trigger(thd, tables));
|
2004-09-07 14:29:46 +02:00
|
|
|
|
2005-10-17 20:37:24 +02:00
|
|
|
end:
|
2004-09-07 14:29:46 +02:00
|
|
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
|
|
start_waiting_global_read_lock(thd);
|
|
|
|
|
|
|
|
if (!result)
|
2005-11-10 20:25:03 +01:00
|
|
|
{
|
|
|
|
if (mysql_bin_log.is_open())
|
2005-05-06 18:52:19 +02:00
|
|
|
{
|
2005-11-10 20:25:03 +01:00
|
|
|
thd->clear_error();
|
|
|
|
|
|
|
|
String log_query(thd->query, thd->query_length, system_charset_info);
|
|
|
|
|
|
|
|
if (create)
|
2005-05-06 18:52:19 +02:00
|
|
|
{
|
2005-11-10 20:25:03 +01:00
|
|
|
log_query.set((char *) 0, 0, system_charset_info); /* reset log_query */
|
|
|
|
|
|
|
|
log_query.append("CREATE ");
|
|
|
|
append_definer(thd, &log_query, &definer_user, &definer_host);
|
|
|
|
log_query.append(thd->lex->trigger_definition_begin);
|
2005-05-06 18:52:19 +02:00
|
|
|
}
|
2005-11-10 20:25:03 +01:00
|
|
|
|
|
|
|
/* Such a statement can always go directly to binlog, no trans cache. */
|
|
|
|
Query_log_event qinfo(thd, log_query.ptr(), log_query.length(), 0, FALSE);
|
|
|
|
mysql_bin_log.write(&qinfo);
|
2005-05-06 18:52:19 +02:00
|
|
|
}
|
2004-09-07 14:29:46 +02:00
|
|
|
|
2005-11-10 20:25:03 +01:00
|
|
|
send_ok(thd);
|
|
|
|
}
|
|
|
|
|
2004-09-07 14:29:46 +02:00
|
|
|
DBUG_RETURN(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Create trigger for table.
|
|
|
|
|
|
|
|
SYNOPSIS
|
|
|
|
create_trigger()
|
2005-11-10 20:25:03 +01:00
|
|
|
thd - current thread context (including trigger definition in
|
|
|
|
LEX)
|
|
|
|
tables - table list containing one open table for which the
|
|
|
|
trigger is created.
|
|
|
|
definer_user - [out] after a call it points to 0-terminated string,
|
|
|
|
which contains user name part of the actual trigger
|
|
|
|
definer. The caller is responsible to provide memory for
|
|
|
|
storing LEX_STRING object.
|
|
|
|
definer_host - [out] after a call it points to 0-terminated string,
|
|
|
|
which contains host name part of the actual trigger
|
|
|
|
definer. The caller is responsible to provide memory for
|
|
|
|
storing LEX_STRING object.
|
2004-09-07 14:29:46 +02:00
|
|
|
|
|
|
|
RETURN VALUE
|
|
|
|
False - success
|
|
|
|
True - error
|
|
|
|
*/
|
2005-11-10 20:25:03 +01:00
|
|
|
bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
|
|
|
|
LEX_STRING *definer_user,
|
|
|
|
LEX_STRING *definer_host)
|
2004-09-07 14:29:46 +02:00
|
|
|
{
|
|
|
|
LEX *lex= thd->lex;
|
|
|
|
TABLE *table= tables->table;
|
2005-07-19 18:06:49 +02:00
|
|
|
char dir_buff[FN_REFLEN], file_buff[FN_REFLEN], trigname_buff[FN_REFLEN],
|
|
|
|
trigname_path[FN_REFLEN];
|
|
|
|
LEX_STRING dir, file, trigname_file;
|
2004-09-07 14:29:46 +02:00
|
|
|
LEX_STRING *trg_def, *name;
|
2005-07-28 21:39:11 +02:00
|
|
|
ulonglong *trg_sql_mode;
|
2005-11-10 20:25:03 +01:00
|
|
|
char trg_definer_holder[HOSTNAME_LENGTH + USERNAME_LENGTH + 2];
|
|
|
|
LEX_STRING *trg_definer;
|
2004-11-24 10:24:02 +01:00
|
|
|
Item_trigger_field *trg_field;
|
2005-07-19 18:06:49 +02:00
|
|
|
struct st_trigname trigname;
|
2004-09-07 14:29:46 +02:00
|
|
|
|
2005-07-19 18:06:49 +02:00
|
|
|
|
|
|
|
/* Trigger must be in the same schema as target table. */
|
2005-07-31 11:49:55 +02:00
|
|
|
if (my_strcasecmp(table_alias_charset, table->s->db,
|
2005-07-19 18:06:49 +02:00
|
|
|
lex->spname->m_db.str ? lex->spname->m_db.str :
|
|
|
|
thd->db))
|
2004-09-07 14:29:46 +02:00
|
|
|
{
|
2005-07-19 18:06:49 +02:00
|
|
|
my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0));
|
2004-09-07 14:29:46 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2005-07-19 18:06:49 +02:00
|
|
|
/* We don't allow creation of several triggers of the same type yet */
|
|
|
|
if (bodies[lex->trg_chistics.event][lex->trg_chistics.action_time])
|
2004-09-07 14:29:46 +02:00
|
|
|
{
|
2005-07-19 18:06:49 +02:00
|
|
|
my_message(ER_TRG_ALREADY_EXISTS, ER(ER_TRG_ALREADY_EXISTS), MYF(0));
|
|
|
|
return 1;
|
2004-09-07 14:29:46 +02:00
|
|
|
}
|
|
|
|
|
2005-11-10 20:25:03 +01:00
|
|
|
/*
|
|
|
|
Definer attribute of the Lex instance is always set in sql_yacc.yy when
|
|
|
|
trigger is created.
|
|
|
|
*/
|
|
|
|
|
|
|
|
DBUG_ASSERT(lex->definer);
|
|
|
|
|
|
|
|
/*
|
|
|
|
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 (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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-11-24 10:24:02 +01:00
|
|
|
/*
|
|
|
|
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...
|
|
|
|
|
2005-05-24 20:19:33 +02:00
|
|
|
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.
|
2004-11-24 10:24:02 +01:00
|
|
|
*/
|
2005-05-24 20:19:33 +02:00
|
|
|
old_field= new_field= table->field;
|
2004-11-24 10:24:02 +01:00
|
|
|
|
|
|
|
for (trg_field= (Item_trigger_field *)(lex->trg_table_fields.first);
|
|
|
|
trg_field; trg_field= trg_field->next_trg_field)
|
|
|
|
{
|
2005-05-24 20:19:33 +02:00
|
|
|
trg_field->setup_field(thd, table);
|
2005-02-08 23:50:45 +01:00
|
|
|
if (!trg_field->fixed &&
|
2005-07-01 06:05:42 +02:00
|
|
|
trg_field->fix_fields(thd, (Item **)0))
|
2004-11-24 10:24:02 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2004-09-07 14:29:46 +02:00
|
|
|
/*
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
strxnmov(dir_buff, FN_REFLEN, mysql_data_home, "/", tables->db, "/", NullS);
|
|
|
|
dir.length= unpack_filename(dir_buff, dir_buff);
|
|
|
|
dir.str= dir_buff;
|
2005-01-06 12:00:13 +01:00
|
|
|
file.length= strxnmov(file_buff, FN_REFLEN, tables->table_name,
|
2004-09-07 14:29:46 +02:00
|
|
|
triggers_file_ext, NullS) - file_buff;
|
|
|
|
file.str= file_buff;
|
2005-07-19 18:06:49 +02:00
|
|
|
trigname_file.length= strxnmov(trigname_buff, FN_REFLEN,
|
|
|
|
lex->spname->m_name.str,
|
|
|
|
trigname_file_ext, NullS) - trigname_buff;
|
|
|
|
trigname_file.str= trigname_buff;
|
|
|
|
strxnmov(trigname_path, FN_REFLEN, dir_buff, trigname_buff, NullS);
|
|
|
|
|
|
|
|
/* Use the filesystem to enforce trigger namespace constraints. */
|
|
|
|
if (!access(trigname_path, F_OK))
|
|
|
|
{
|
|
|
|
my_error(ER_TRG_ALREADY_EXISTS, MYF(0));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
trigname.trigger_table.str= tables->table_name;
|
|
|
|
trigname.trigger_table.length= tables->table_name_length;
|
|
|
|
|
|
|
|
if (sql_create_definition_file(&dir, &trigname_file, &trigname_file_type,
|
|
|
|
(gptr)&trigname, trigname_file_parameters, 0))
|
|
|
|
return 1;
|
2004-09-07 14:29:46 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
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= (LEX_STRING *)alloc_root(&table->mem_root,
|
|
|
|
sizeof(LEX_STRING))) ||
|
2005-07-28 21:39:11 +02:00
|
|
|
definitions_list.push_back(trg_def, &table->mem_root) ||
|
|
|
|
!(trg_sql_mode= (ulonglong*)alloc_root(&table->mem_root,
|
|
|
|
sizeof(ulonglong))) ||
|
2005-11-10 20:25:03 +01:00
|
|
|
definition_modes_list.push_back(trg_sql_mode, &table->mem_root) ||
|
|
|
|
!(trg_definer= (LEX_STRING*) alloc_root(&table->mem_root,
|
|
|
|
sizeof(LEX_STRING))) ||
|
|
|
|
definers_list.push_back(trg_definer, &table->mem_root))
|
2005-07-19 18:06:49 +02:00
|
|
|
goto err_with_cleanup;
|
2004-09-07 14:29:46 +02:00
|
|
|
|
|
|
|
trg_def->str= thd->query;
|
|
|
|
trg_def->length= thd->query_length;
|
2005-07-28 21:39:11 +02:00
|
|
|
*trg_sql_mode= thd->variables.sql_mode;
|
2004-09-07 14:29:46 +02:00
|
|
|
|
2005-11-10 20:25:03 +01:00
|
|
|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
|
|
|
if (!is_acl_user(lex->definer->host.str,
|
|
|
|
lex->definer->user.str))
|
|
|
|
{
|
|
|
|
push_warning_printf(thd,
|
|
|
|
MYSQL_ERROR::WARN_LEVEL_NOTE,
|
|
|
|
ER_NO_SUCH_USER,
|
|
|
|
ER(ER_NO_SUCH_USER),
|
|
|
|
lex->definer->user.str,
|
|
|
|
lex->definer->host.str);
|
|
|
|
}
|
|
|
|
#endif /* NO_EMBEDDED_ACCESS_CHECKS */
|
|
|
|
|
|
|
|
*definer_user= lex->definer->user;
|
|
|
|
*definer_host= lex->definer->host;
|
|
|
|
|
|
|
|
trg_definer->str= trg_definer_holder;
|
|
|
|
trg_definer->length= strxmov(trg_definer->str, definer_user->str, "@",
|
|
|
|
definer_host->str, NullS) - trg_definer->str;
|
|
|
|
|
2005-07-19 18:06:49 +02:00
|
|
|
if (!sql_create_definition_file(&dir, &file, &triggers_file_type,
|
2005-11-10 20:25:03 +01:00
|
|
|
(gptr)this, triggers_file_parameters,
|
|
|
|
TRG_MAX_VERSIONS))
|
2005-07-19 18:06:49 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_with_cleanup:
|
|
|
|
my_delete(trigname_path, MYF(MY_WME));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Deletes the .TRG file for a table
|
|
|
|
|
|
|
|
SYNOPSIS
|
|
|
|
rm_trigger_file()
|
|
|
|
path - char buffer of size FN_REFLEN to be used
|
|
|
|
for constructing path to .TRG file.
|
|
|
|
db - table's database name
|
|
|
|
table_name - table's name
|
|
|
|
|
|
|
|
RETURN VALUE
|
|
|
|
False - success
|
|
|
|
True - error
|
|
|
|
*/
|
|
|
|
|
|
|
|
static bool rm_trigger_file(char *path, char *db, char *table_name)
|
|
|
|
{
|
|
|
|
strxnmov(path, FN_REFLEN, mysql_data_home, "/", db, "/", table_name,
|
|
|
|
triggers_file_ext, NullS);
|
|
|
|
unpack_filename(path, path);
|
|
|
|
return my_delete(path, MYF(MY_WME));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Deletes the .TRN file for a trigger
|
|
|
|
|
|
|
|
SYNOPSIS
|
|
|
|
rm_trigname_file()
|
|
|
|
path - char buffer of size FN_REFLEN to be used
|
|
|
|
for constructing path to .TRN file.
|
|
|
|
db - trigger's database name
|
|
|
|
table_name - trigger's name
|
|
|
|
|
|
|
|
RETURN VALUE
|
|
|
|
False - success
|
|
|
|
True - error
|
|
|
|
*/
|
|
|
|
|
|
|
|
static bool rm_trigname_file(char *path, char *db, char *trigger_name)
|
|
|
|
{
|
|
|
|
strxnmov(path, FN_REFLEN, mysql_data_home, "/", db, "/", trigger_name,
|
|
|
|
trigname_file_ext, NullS);
|
|
|
|
unpack_filename(path, path);
|
|
|
|
return my_delete(path, MYF(MY_WME));
|
2004-09-07 14:29:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Drop trigger for table.
|
|
|
|
|
|
|
|
SYNOPSIS
|
|
|
|
drop_trigger()
|
|
|
|
thd - current thread context (including trigger definition in LEX)
|
|
|
|
tables - table list containing one open table for which trigger is
|
|
|
|
dropped.
|
|
|
|
|
|
|
|
RETURN VALUE
|
|
|
|
False - success
|
|
|
|
True - error
|
|
|
|
*/
|
|
|
|
bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables)
|
|
|
|
{
|
|
|
|
LEX *lex= thd->lex;
|
|
|
|
LEX_STRING *name;
|
|
|
|
List_iterator_fast<LEX_STRING> it_name(names_list);
|
|
|
|
List_iterator<LEX_STRING> it_def(definitions_list);
|
2005-07-28 21:39:11 +02:00
|
|
|
List_iterator<ulonglong> it_mod(definition_modes_list);
|
2005-11-10 20:25:03 +01:00
|
|
|
List_iterator<LEX_STRING> it_definer(definers_list);
|
2005-07-19 18:06:49 +02:00
|
|
|
char path[FN_REFLEN];
|
2004-09-07 14:29:46 +02:00
|
|
|
|
|
|
|
while ((name= it_name++))
|
|
|
|
{
|
|
|
|
it_def++;
|
2005-07-28 21:39:11 +02:00
|
|
|
it_mod++;
|
2005-11-10 20:25:03 +01:00
|
|
|
it_definer++;
|
2004-09-07 14:29:46 +02:00
|
|
|
|
2005-07-31 11:49:55 +02:00
|
|
|
if (my_strcasecmp(table_alias_charset, lex->spname->m_name.str,
|
2004-09-07 14:29:46 +02:00
|
|
|
name->str) == 0)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
Again we don't care much about other things required for
|
|
|
|
clean trigger removing since table will be reopened anyway.
|
|
|
|
*/
|
|
|
|
it_def.remove();
|
2005-07-28 21:39:11 +02:00
|
|
|
it_mod.remove();
|
2005-11-10 20:25:03 +01:00
|
|
|
it_definer.remove();
|
2004-09-07 14:29:46 +02:00
|
|
|
|
|
|
|
if (definitions_list.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).
|
|
|
|
*/
|
2005-07-19 18:06:49 +02:00
|
|
|
if (rm_trigger_file(path, tables->db, tables->table_name))
|
|
|
|
return 1;
|
2004-09-07 14:29:46 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char dir_buff[FN_REFLEN], file_buff[FN_REFLEN];
|
|
|
|
LEX_STRING dir, file;
|
|
|
|
|
|
|
|
strxnmov(dir_buff, FN_REFLEN, mysql_data_home, "/", tables->db,
|
|
|
|
"/", NullS);
|
|
|
|
dir.length= unpack_filename(dir_buff, dir_buff);
|
|
|
|
dir.str= dir_buff;
|
2005-01-06 12:00:13 +01:00
|
|
|
file.length= strxnmov(file_buff, FN_REFLEN, tables->table_name,
|
2004-09-07 14:29:46 +02:00
|
|
|
triggers_file_ext, NullS) - file_buff;
|
|
|
|
file.str= file_buff;
|
|
|
|
|
2005-07-19 18:06:49 +02:00
|
|
|
if (sql_create_definition_file(&dir, &file, &triggers_file_type,
|
|
|
|
(gptr)this, triggers_file_parameters,
|
2005-11-10 20:25:03 +01:00
|
|
|
TRG_MAX_VERSIONS))
|
2005-07-19 18:06:49 +02:00
|
|
|
return 1;
|
2004-09-07 14:29:46 +02:00
|
|
|
}
|
2005-07-19 18:06:49 +02:00
|
|
|
|
|
|
|
if (rm_trigname_file(path, tables->db, lex->spname->m_name.str))
|
|
|
|
return 1;
|
|
|
|
return 0;
|
2004-09-07 14:29:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-11-12 13:34:00 +01:00
|
|
|
my_message(ER_TRG_DOES_NOT_EXIST, ER(ER_TRG_DOES_NOT_EXIST), MYF(0));
|
2004-09-07 14:29:46 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Table_triggers_list::~Table_triggers_list()
|
|
|
|
{
|
2005-07-19 18:06:49 +02:00
|
|
|
for (int i= 0; i < (int)TRG_EVENT_MAX; i++)
|
|
|
|
for (int j= 0; j < (int)TRG_ACTION_MAX; j++)
|
2004-09-07 14:29:46 +02:00
|
|
|
delete bodies[i][j];
|
|
|
|
|
2005-05-24 20:19:33 +02:00
|
|
|
if (record1_field)
|
|
|
|
for (Field **fld_ptr= record1_field; *fld_ptr; fld_ptr++)
|
2004-09-07 14:29:46 +02:00
|
|
|
delete *fld_ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-11-24 10:24:02 +01:00
|
|
|
/*
|
2005-05-24 20:19:33 +02:00
|
|
|
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).
|
2004-11-24 10:24:02 +01:00
|
|
|
|
|
|
|
SYNOPSIS
|
2005-05-24 20:19:33 +02:00
|
|
|
prepare_record1_accessors()
|
2004-11-24 10:24:02 +01:00
|
|
|
table - pointer to TABLE object for which we are creating fields.
|
|
|
|
|
|
|
|
RETURN VALUE
|
|
|
|
False - success
|
|
|
|
True - error
|
|
|
|
*/
|
2005-05-24 20:19:33 +02:00
|
|
|
bool Table_triggers_list::prepare_record1_accessors(TABLE *table)
|
2004-11-24 10:24:02 +01:00
|
|
|
{
|
|
|
|
Field **fld, **old_fld;
|
|
|
|
|
2005-05-24 20:19:33 +02:00
|
|
|
if (!(record1_field= (Field **)alloc_root(&table->mem_root,
|
|
|
|
(table->s->fields + 1) *
|
|
|
|
sizeof(Field*))))
|
2004-11-24 10:24:02 +01:00
|
|
|
return 1;
|
|
|
|
|
2005-05-24 20:19:33 +02:00
|
|
|
for (fld= table->field, old_fld= record1_field; *fld; fld++, old_fld++)
|
2004-11-24 10:24:02 +01:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
QQ: it is supposed that it is ok to use this function for field
|
|
|
|
cloning...
|
|
|
|
*/
|
|
|
|
if (!(*old_fld= (*fld)->new_field(&table->mem_root, table)))
|
|
|
|
return 1;
|
|
|
|
(*old_fld)->move_field((my_ptrdiff_t)(table->record[1] -
|
|
|
|
table->record[0]));
|
|
|
|
}
|
|
|
|
*old_fld= 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-09-15 01:56:09 +02:00
|
|
|
/*
|
|
|
|
Adjust Table_triggers_list with new TABLE pointer.
|
|
|
|
|
|
|
|
SYNOPSIS
|
|
|
|
set_table()
|
|
|
|
new_table - new pointer to TABLE instance
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Table_triggers_list::set_table(TABLE *new_table)
|
|
|
|
{
|
|
|
|
table= new_table;
|
|
|
|
for (Field **field= table->triggers->record1_field ; *field ; field++)
|
|
|
|
{
|
|
|
|
(*field)->table= (*field)->orig_table= new_table;
|
|
|
|
(*field)->table_name= &new_table->alias;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-09-07 14:29:46 +02:00
|
|
|
/*
|
|
|
|
Check whenever .TRG file for table exist and load all triggers it contains.
|
|
|
|
|
|
|
|
SYNOPSIS
|
|
|
|
check_n_load()
|
|
|
|
thd - current thread context
|
|
|
|
db - table's database name
|
|
|
|
table_name - table's name
|
|
|
|
table - pointer to table object
|
2005-07-19 18:06:49 +02:00
|
|
|
names_only - stop after loading trigger names
|
2004-09-07 14:29:46 +02:00
|
|
|
|
|
|
|
RETURN VALUE
|
|
|
|
False - success
|
|
|
|
True - error
|
|
|
|
*/
|
2005-07-19 18:06:49 +02:00
|
|
|
|
2004-09-07 14:29:46 +02:00
|
|
|
bool Table_triggers_list::check_n_load(THD *thd, const char *db,
|
2005-07-19 18:06:49 +02:00
|
|
|
const char *table_name, TABLE *table,
|
|
|
|
bool names_only)
|
2004-09-07 14:29:46 +02:00
|
|
|
{
|
|
|
|
char path_buff[FN_REFLEN];
|
|
|
|
LEX_STRING path;
|
|
|
|
File_parser *parser;
|
2005-07-27 13:17:05 +02:00
|
|
|
LEX_STRING save_db;
|
2004-09-07 14:29:46 +02:00
|
|
|
|
|
|
|
DBUG_ENTER("Table_triggers_list::check_n_load");
|
|
|
|
|
|
|
|
strxnmov(path_buff, FN_REFLEN, mysql_data_home, "/", db, "/", table_name,
|
|
|
|
triggers_file_ext, NullS);
|
|
|
|
path.length= unpack_filename(path_buff, path_buff);
|
|
|
|
path.str= path_buff;
|
|
|
|
|
|
|
|
// QQ: should we analyze errno somehow ?
|
|
|
|
if (access(path_buff, F_OK))
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
|
|
|
|
/*
|
2005-11-10 20:25:03 +01:00
|
|
|
File exists so we got to load triggers.
|
2004-09-07 14:29:46 +02:00
|
|
|
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)))
|
|
|
|
{
|
2005-07-31 11:49:55 +02:00
|
|
|
if (is_equal(&triggers_file_type, parser->type()))
|
2004-09-07 14:29:46 +02:00
|
|
|
{
|
2004-09-08 22:46:01 +02:00
|
|
|
Table_triggers_list *triggers=
|
2005-05-24 20:19:33 +02:00
|
|
|
new (&table->mem_root) Table_triggers_list(table);
|
2004-09-07 14:29:46 +02:00
|
|
|
|
2004-09-08 22:46:01 +02:00
|
|
|
if (!triggers)
|
2004-09-07 14:29:46 +02:00
|
|
|
DBUG_RETURN(1);
|
|
|
|
|
2005-07-28 21:39:11 +02:00
|
|
|
/*
|
2005-11-10 20:25:03 +01:00
|
|
|
We don't have the following attributes in old versions of .TRG file, so
|
|
|
|
we should initialize the list for safety:
|
|
|
|
- sql_modes;
|
|
|
|
- definers;
|
2005-07-28 21:39:11 +02:00
|
|
|
*/
|
|
|
|
triggers->definition_modes_list.empty();
|
2005-11-10 20:25:03 +01:00
|
|
|
triggers->definers_list.empty();
|
2005-07-28 21:39:11 +02:00
|
|
|
|
2004-09-08 22:46:01 +02:00
|
|
|
if (parser->parse((gptr)triggers, &table->mem_root,
|
2005-11-10 20:25:03 +01:00
|
|
|
triggers_file_parameters, TRG_NUM_REQUIRED_PARAMETERS))
|
2004-09-07 14:29:46 +02:00
|
|
|
DBUG_RETURN(1);
|
|
|
|
|
2005-07-28 21:39:11 +02:00
|
|
|
List_iterator_fast<LEX_STRING> it(triggers->definitions_list);
|
|
|
|
LEX_STRING *trg_create_str, *trg_name_str;
|
|
|
|
ulonglong *trg_sql_mode;
|
|
|
|
|
|
|
|
if (triggers->definition_modes_list.is_empty() &&
|
|
|
|
!triggers->definitions_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.
|
|
|
|
*/
|
|
|
|
if (!(trg_sql_mode= (ulonglong*)alloc_root(&table->mem_root,
|
|
|
|
sizeof(ulonglong))))
|
|
|
|
{
|
|
|
|
DBUG_RETURN(1); // EOM
|
|
|
|
}
|
|
|
|
*trg_sql_mode= global_system_variables.sql_mode;
|
2005-11-10 20:25:03 +01:00
|
|
|
while (it++)
|
2005-07-28 21:39:11 +02:00
|
|
|
{
|
|
|
|
if (triggers->definition_modes_list.push_back(trg_sql_mode,
|
|
|
|
&table->mem_root))
|
|
|
|
{
|
|
|
|
DBUG_RETURN(1); // EOM
|
|
|
|
}
|
|
|
|
}
|
|
|
|
it.rewind();
|
|
|
|
}
|
|
|
|
|
2005-11-10 20:25:03 +01:00
|
|
|
if (triggers->definers_list.is_empty() &&
|
|
|
|
!triggers->definitions_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= (LEX_STRING*)alloc_root(&table->mem_root,
|
|
|
|
sizeof(LEX_STRING))))
|
|
|
|
DBUG_RETURN(1); // EOM
|
|
|
|
|
|
|
|
trg_definer->str= "";
|
|
|
|
trg_definer->length= 0;
|
|
|
|
|
|
|
|
while (it++)
|
|
|
|
{
|
|
|
|
if (triggers->definers_list.push_back(trg_definer,
|
|
|
|
&table->mem_root))
|
|
|
|
{
|
|
|
|
DBUG_RETURN(1); // EOM
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
it.rewind();
|
|
|
|
}
|
|
|
|
|
2005-07-28 21:39:11 +02:00
|
|
|
DBUG_ASSERT(triggers->definition_modes_list.elements ==
|
|
|
|
triggers->definitions_list.elements);
|
2005-11-10 20:25:03 +01:00
|
|
|
DBUG_ASSERT(triggers->definers_list.elements ==
|
|
|
|
triggers->definitions_list.elements);
|
|
|
|
|
2004-09-08 22:46:01 +02:00
|
|
|
table->triggers= triggers;
|
2004-09-07 14:29:46 +02:00
|
|
|
|
2005-07-09 19:51:59 +02:00
|
|
|
/*
|
|
|
|
Construct key that will represent triggers for this table in the set
|
|
|
|
of routines used by statement.
|
|
|
|
*/
|
|
|
|
triggers->sroutines_key.length= 1+strlen(db)+1+strlen(table_name)+1;
|
|
|
|
if (!(triggers->sroutines_key.str=
|
|
|
|
alloc_root(&table->mem_root, triggers->sroutines_key.length)))
|
|
|
|
DBUG_RETURN(1);
|
|
|
|
triggers->sroutines_key.str[0]= TYPE_ENUM_TRIGGER;
|
2005-07-28 15:10:14 +02:00
|
|
|
strxmov(triggers->sroutines_key.str+1, db, ".", table_name, NullS);
|
2005-07-09 19:51:59 +02:00
|
|
|
|
2005-05-24 20:19:33 +02:00
|
|
|
/*
|
|
|
|
TODO: This could be avoided if there is no triggers
|
|
|
|
for UPDATE and DELETE.
|
|
|
|
*/
|
2005-07-19 18:06:49 +02:00
|
|
|
if (!names_only && triggers->prepare_record1_accessors(table))
|
2004-09-07 14:29:46 +02:00
|
|
|
DBUG_RETURN(1);
|
|
|
|
|
|
|
|
char *trg_name_buff;
|
2005-07-28 21:39:11 +02:00
|
|
|
List_iterator_fast<ulonglong> itm(triggers->definition_modes_list);
|
2005-11-10 20:25:03 +01:00
|
|
|
List_iterator_fast<LEX_STRING> it_definer(triggers->
|
|
|
|
definers_list);
|
2004-09-07 14:29:46 +02:00
|
|
|
LEX *old_lex= thd->lex, lex;
|
2005-07-28 21:39:11 +02:00
|
|
|
ulong save_sql_mode= thd->variables.sql_mode;
|
2004-09-07 14:29:46 +02:00
|
|
|
|
|
|
|
thd->lex= &lex;
|
|
|
|
|
2005-07-27 13:17:05 +02:00
|
|
|
save_db.str= thd->db;
|
|
|
|
save_db.length= thd->db_length;
|
|
|
|
thd->db_length= strlen(db);
|
|
|
|
thd->db= (char *) db;
|
2004-09-07 14:29:46 +02:00
|
|
|
while ((trg_create_str= it++))
|
|
|
|
{
|
2005-07-28 21:39:11 +02:00
|
|
|
trg_sql_mode= itm++;
|
2005-11-10 20:25:03 +01:00
|
|
|
LEX_STRING *trg_definer= it_definer++;
|
2005-07-28 21:39:11 +02:00
|
|
|
thd->variables.sql_mode= (ulong)*trg_sql_mode;
|
2004-11-03 11:39:38 +01:00
|
|
|
lex_start(thd, (uchar*)trg_create_str->str, trg_create_str->length);
|
2004-11-24 10:24:02 +01:00
|
|
|
|
2004-09-07 14:29:46 +02:00
|
|
|
if (yyparse((void *)thd) || thd->is_fatal_error)
|
|
|
|
{
|
|
|
|
/*
|
2005-11-10 20:25:03 +01:00
|
|
|
Free lex associated resources.
|
2004-09-07 14:29:46 +02:00
|
|
|
QQ: Do we really need all this stuff here ?
|
|
|
|
*/
|
2005-07-19 18:06:49 +02:00
|
|
|
delete lex.sphead;
|
2004-09-07 14:29:46 +02:00
|
|
|
goto err_with_lex_cleanup;
|
|
|
|
}
|
|
|
|
|
2005-11-10 20:25:03 +01:00
|
|
|
lex.sphead->set_info(0, 0, &lex.sp_chistics, *trg_sql_mode);
|
|
|
|
|
2004-09-08 22:46:01 +02:00
|
|
|
triggers->bodies[lex.trg_chistics.event]
|
2004-09-07 14:29:46 +02:00
|
|
|
[lex.trg_chistics.action_time]= lex.sphead;
|
2005-11-10 20:25:03 +01:00
|
|
|
|
|
|
|
if (!trg_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, MYSQL_ERROR::WARN_LEVEL_WARN,
|
|
|
|
ER_TRG_NO_DEFINER, ER(ER_TRG_NO_DEFINER),
|
|
|
|
(const char*) db,
|
|
|
|
(const char*) lex.sphead->m_name.str);
|
|
|
|
|
|
|
|
/*
|
|
|
|
Set definer to the '' to correct displaying in the information
|
|
|
|
schema.
|
|
|
|
*/
|
|
|
|
|
|
|
|
lex.sphead->set_definer("", 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
Triggers without definer information are executed under the
|
|
|
|
authorization of the invoker.
|
|
|
|
*/
|
|
|
|
|
|
|
|
lex.sphead->m_chistics->suid= SP_IS_NOT_SUID;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
lex.sphead->set_definer(trg_definer->str, trg_definer->length);
|
|
|
|
|
2005-07-28 21:39:11 +02:00
|
|
|
if (triggers->names_list.push_back(&lex.sphead->m_name,
|
|
|
|
&table->mem_root))
|
2005-07-19 18:06:49 +02:00
|
|
|
goto err_with_lex_cleanup;
|
2004-09-07 14:29:46 +02:00
|
|
|
|
2005-07-19 18:06:49 +02:00
|
|
|
if (names_only)
|
|
|
|
{
|
|
|
|
lex_end(&lex);
|
|
|
|
continue;
|
|
|
|
}
|
2004-09-07 14:29:46 +02:00
|
|
|
|
2004-11-24 10:24:02 +01:00
|
|
|
/*
|
|
|
|
Let us bind Item_trigger_field objects representing access to fields
|
|
|
|
in old/new versions of row in trigger to Field objects in table being
|
|
|
|
opened.
|
|
|
|
|
2005-07-28 21:39:11 +02:00
|
|
|
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)...
|
2004-11-24 10:24:02 +01:00
|
|
|
Anyway some things can be checked only during trigger execution.
|
|
|
|
*/
|
|
|
|
for (Item_trigger_field *trg_field=
|
|
|
|
(Item_trigger_field *)(lex.trg_table_fields.first);
|
|
|
|
trg_field;
|
|
|
|
trg_field= trg_field->next_trg_field)
|
2005-05-24 20:19:33 +02:00
|
|
|
trg_field->setup_field(thd, table);
|
2004-11-24 10:24:02 +01:00
|
|
|
|
2005-11-10 20:25:03 +01:00
|
|
|
triggers->m_spec_var_used[lex.trg_chistics.event]
|
|
|
|
[lex.trg_chistics.action_time]=
|
|
|
|
lex.trg_table_fields.first ? TRUE : FALSE;
|
|
|
|
|
2004-09-07 14:29:46 +02:00
|
|
|
lex_end(&lex);
|
|
|
|
}
|
2005-07-27 13:17:05 +02:00
|
|
|
thd->db= save_db.str;
|
|
|
|
thd->db_length= save_db.length;
|
2004-09-07 14:29:46 +02:00
|
|
|
thd->lex= old_lex;
|
2005-07-28 21:39:11 +02:00
|
|
|
thd->variables.sql_mode= save_sql_mode;
|
2004-09-07 14:29:46 +02:00
|
|
|
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
|
|
|
|
err_with_lex_cleanup:
|
|
|
|
// QQ: anything else ?
|
|
|
|
lex_end(&lex);
|
|
|
|
thd->lex= old_lex;
|
2005-07-28 21:39:11 +02:00
|
|
|
thd->variables.sql_mode= save_sql_mode;
|
2005-07-27 13:17:05 +02:00
|
|
|
thd->db= save_db.str;
|
|
|
|
thd->db_length= save_db.length;
|
2004-09-07 14:29:46 +02:00
|
|
|
DBUG_RETURN(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
We don't care about this error message much because .TRG files will
|
|
|
|
be merged into .FRM anyway.
|
|
|
|
*/
|
now my_printf_error is not better then my_error, but my_error call is shorter
used only one implementation of format parser of (printf)
fixed multistatement
include/mysqld_error.h:
newerror messages
mysql-test/t/key.test:
unknown error replaced with real error
mysys/my_error.c:
my_error & my_printf_error use my_vsprintf
sql/field_conv.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/ha_innodb.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/handler.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/item.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/item_cmpfunc.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/item_func.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/item_strfunc.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/lock.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/log.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/parse_file.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/procedure.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/protocol.cc:
no need reset thd->lex->found_colon to break multiline sequance now, send_error called too late
sql/repl_failsafe.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/set_var.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/share/czech/errmsg.txt:
new errors converted from unknown error
sql/share/danish/errmsg.txt:
new errors converted from unknown error
sql/share/dutch/errmsg.txt:
new errors converted from unknown error
sql/share/english/errmsg.txt:
new errors converted from unknown error
sql/share/estonian/errmsg.txt:
new errors converted from unknown error
sql/share/french/errmsg.txt:
new errors converted from unknown error
sql/share/german/errmsg.txt:
new errors converted from unknown error
sql/share/greek/errmsg.txt:
new errors converted from unknown error
sql/share/hungarian/errmsg.txt:
new errors converted from unknown error
sql/share/italian/errmsg.txt:
new errors converted from unknown error
sql/share/japanese/errmsg.txt:
new errors converted from unknown error
sql/share/korean/errmsg.txt:
new errors converted from unknown error
sql/share/norwegian-ny/errmsg.txt:
new errors converted from unknown error
sql/share/norwegian/errmsg.txt:
new errors converted from unknown error
sql/share/polish/errmsg.txt:
new errors converted from unknown error
sql/share/portuguese/errmsg.txt:
new errors converted from unknown error
sql/share/romanian/errmsg.txt:
new errors converted from unknown error
sql/share/russian/errmsg.txt:
new errors converted from unknown error
sql/share/serbian/errmsg.txt:
new errors converted from unknown error
sql/share/slovak/errmsg.txt:
new errors converted from unknown error
sql/share/spanish/errmsg.txt:
new errors converted from unknown error
sql/share/swedish/errmsg.txt:
new errors converted from unknown error
sql/share/ukrainian/errmsg.txt:
new errors converted from unknown error
sql/slave.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sp.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sp_head.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_acl.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_analyse.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_base.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_class.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_db.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_delete.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_handler.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_insert.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_load.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_map.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_parse.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
multi-row command fixed
sql/sql_prepare.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
remover send_error ingected from 4.1
sql/sql_rename.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_repl.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_select.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_show.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_table.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_trigger.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_udf.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_update.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_view.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/sql_yacc.yy:
now my_printf_error is not better then my_error, but my_error call is shorter
sql/table.cc:
now my_printf_error is not better then my_error, but my_error call is shorter
strings/my_vsnprintf.c:
* format support added to my_vsprint
2004-11-13 18:35:51 +01:00
|
|
|
my_error(ER_WRONG_OBJECT, MYF(0),
|
2005-07-31 11:49:55 +02:00
|
|
|
table_name, triggers_file_ext+1, "TRIGGER");
|
2004-09-07 14:29:46 +02:00
|
|
|
DBUG_RETURN(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
DBUG_RETURN(1);
|
|
|
|
}
|
2005-07-19 18:06:49 +02:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Obtains and returns trigger metadata
|
|
|
|
|
|
|
|
SYNOPSIS
|
|
|
|
get_trigger_info()
|
|
|
|
thd - current thread context
|
|
|
|
event - trigger event type
|
|
|
|
time_type - trigger action time
|
|
|
|
name - returns name of trigger
|
|
|
|
stmt - returns statement of trigger
|
2005-07-28 21:39:11 +02:00
|
|
|
sql_mode - returns sql_mode of trigger
|
2005-11-10 20:25:03 +01:00
|
|
|
definer_user - returns definer/creator of trigger. The caller is
|
|
|
|
responsible to allocate enough space for storing definer
|
|
|
|
information.
|
2005-07-19 18:06:49 +02:00
|
|
|
|
|
|
|
RETURN VALUE
|
|
|
|
False - success
|
|
|
|
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,
|
2005-07-28 21:39:11 +02:00
|
|
|
LEX_STRING *trigger_stmt,
|
2005-11-10 20:25:03 +01:00
|
|
|
ulong *sql_mode,
|
|
|
|
LEX_STRING *definer)
|
2005-07-19 18:06:49 +02:00
|
|
|
{
|
|
|
|
sp_head *body;
|
|
|
|
DBUG_ENTER("get_trigger_info");
|
|
|
|
if ((body= bodies[event][time_type]))
|
|
|
|
{
|
|
|
|
*trigger_name= body->m_name;
|
|
|
|
*trigger_stmt= body->m_body;
|
2005-07-28 21:39:11 +02:00
|
|
|
*sql_mode= body->m_sql_mode;
|
2005-11-10 20:25:03 +01:00
|
|
|
|
|
|
|
if (body->m_chistics->suid == SP_IS_NOT_SUID)
|
|
|
|
{
|
|
|
|
definer->str[0]= 0;
|
|
|
|
definer->length= 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
definer->length= strxmov(definer->str, body->m_definer_user.str, "@",
|
|
|
|
body->m_definer_host.str, NullS) - definer->str;
|
|
|
|
}
|
|
|
|
|
2005-07-19 18:06:49 +02:00
|
|
|
DBUG_RETURN(0);
|
|
|
|
}
|
|
|
|
DBUG_RETURN(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Find trigger's table from trigger identifier and add it to
|
|
|
|
the statement table list.
|
|
|
|
|
|
|
|
SYNOPSIS
|
|
|
|
mysql_table_for_trigger()
|
|
|
|
thd - current thread context
|
|
|
|
trig - identifier for trigger
|
|
|
|
|
|
|
|
RETURN VALUE
|
|
|
|
0 - error
|
|
|
|
# - pointer to TABLE_LIST object for the table
|
|
|
|
*/
|
|
|
|
|
|
|
|
static TABLE_LIST *add_table_for_trigger(THD *thd, sp_name *trig)
|
|
|
|
{
|
|
|
|
const char *db= !trig->m_db.str ? thd->db : trig->m_db.str;
|
|
|
|
LEX *lex= thd->lex;
|
|
|
|
char path_buff[FN_REFLEN];
|
|
|
|
LEX_STRING path;
|
|
|
|
File_parser *parser;
|
|
|
|
struct st_trigname trigname;
|
|
|
|
DBUG_ENTER("add_table_for_trigger");
|
|
|
|
|
|
|
|
strxnmov(path_buff, FN_REFLEN, mysql_data_home, "/", db, "/",
|
|
|
|
trig->m_name.str, trigname_file_ext, NullS);
|
|
|
|
path.length= unpack_filename(path_buff, path_buff);
|
|
|
|
path.str= path_buff;
|
|
|
|
|
|
|
|
if (access(path_buff, F_OK))
|
|
|
|
{
|
|
|
|
my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(parser= sql_parse_prepare(&path, thd->mem_root, 1)))
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
|
2005-07-31 11:49:55 +02:00
|
|
|
if (!is_equal(&trigname_file_type, parser->type()))
|
2005-07-19 18:06:49 +02:00
|
|
|
{
|
2005-07-31 11:49:55 +02:00
|
|
|
my_error(ER_WRONG_OBJECT, MYF(0), trig->m_name.str, trigname_file_ext+1,
|
2005-07-19 18:06:49 +02:00
|
|
|
"TRIGGERNAME");
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parser->parse((gptr)&trigname, thd->mem_root,
|
|
|
|
trigname_file_parameters, 1))
|
|
|
|
DBUG_RETURN(0);
|
|
|
|
|
|
|
|
/* We need to reset statement table list to be PS/SP friendly. */
|
|
|
|
lex->query_tables= 0;
|
|
|
|
lex->query_tables_last= &lex->query_tables;
|
|
|
|
DBUG_RETURN(sp_add_to_query_tables(thd, lex, db,
|
|
|
|
trigname.trigger_table.str, TL_WRITE));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Drop all triggers for table.
|
|
|
|
|
|
|
|
SYNOPSIS
|
|
|
|
drop_all_triggers()
|
|
|
|
thd - current thread context
|
|
|
|
db - schema for table
|
|
|
|
name - name for table
|
|
|
|
|
|
|
|
NOTE
|
|
|
|
The calling thread should hold the LOCK_open mutex;
|
|
|
|
|
|
|
|
RETURN VALUE
|
|
|
|
False - success
|
|
|
|
True - error
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool Table_triggers_list::drop_all_triggers(THD *thd, char *db, char *name)
|
|
|
|
{
|
|
|
|
TABLE table;
|
|
|
|
char path[FN_REFLEN];
|
|
|
|
bool result= 0;
|
|
|
|
DBUG_ENTER("drop_all_triggers");
|
|
|
|
|
|
|
|
bzero(&table, sizeof(table));
|
|
|
|
init_alloc_root(&table.mem_root, 8192, 0);
|
|
|
|
|
|
|
|
safe_mutex_assert_owner(&LOCK_open);
|
|
|
|
|
|
|
|
if (Table_triggers_list::check_n_load(thd, db, name, &table, 1))
|
|
|
|
{
|
|
|
|
result= 1;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
if (table.triggers)
|
|
|
|
{
|
|
|
|
LEX_STRING *trigger;
|
|
|
|
List_iterator_fast<LEX_STRING> it_name(table.triggers->names_list);
|
|
|
|
|
|
|
|
while ((trigger= it_name++))
|
|
|
|
{
|
|
|
|
if (rm_trigname_file(path, db, trigger->str))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
Instead of immediately bailing out with error if we were unable
|
|
|
|
to remove .TRN file we will try to drop other files.
|
|
|
|
*/
|
|
|
|
result= 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rm_trigger_file(path, db, name))
|
|
|
|
{
|
|
|
|
result= 1;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end:
|
|
|
|
if (table.triggers)
|
|
|
|
delete table.triggers;
|
|
|
|
free_root(&table.mem_root, MYF(0));
|
|
|
|
DBUG_RETURN(result);
|
|
|
|
}
|
2005-08-15 17:15:12 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool Table_triggers_list::process_triggers(THD *thd, trg_event_type event,
|
|
|
|
trg_action_time_type time_type,
|
|
|
|
bool old_row_is_record1)
|
|
|
|
{
|
|
|
|
int res= 0;
|
2005-11-10 20:25:03 +01:00
|
|
|
sp_head *sp_trigger= bodies[event][time_type];
|
2005-08-15 17:15:12 +02:00
|
|
|
|
2005-11-10 20:25:03 +01:00
|
|
|
if (sp_trigger)
|
2005-08-15 17:15:12 +02:00
|
|
|
{
|
|
|
|
Sub_statement_state statement_state;
|
|
|
|
|
|
|
|
if (old_row_is_record1)
|
|
|
|
{
|
|
|
|
old_field= record1_field;
|
|
|
|
new_field= table->field;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
new_field= record1_field;
|
|
|
|
old_field= table->field;
|
|
|
|
}
|
|
|
|
|
2005-11-10 20:25:03 +01:00
|
|
|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
|
|
|
Security_context *save_ctx;
|
|
|
|
|
|
|
|
if (sp_change_security_context(thd, sp_trigger, &save_ctx))
|
|
|
|
return TRUE;
|
|
|
|
|
2005-08-15 17:15:12 +02:00
|
|
|
/*
|
2005-11-10 20:25:03 +01:00
|
|
|
NOTE: TRIGGER_ACL should be used below.
|
2005-08-15 17:15:12 +02:00
|
|
|
*/
|
|
|
|
|
2005-11-10 20:25:03 +01:00
|
|
|
if (check_global_access(thd, SUPER_ACL))
|
|
|
|
{
|
|
|
|
sp_restore_security_context(thd, save_ctx);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
If the trigger uses special variables (NEW/OLD), check that we have
|
|
|
|
SELECT and UPDATE privileges on the subject table.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (is_special_var_used(event, time_type))
|
|
|
|
{
|
|
|
|
TABLE_LIST table_list;
|
|
|
|
bzero((char *) &table_list, sizeof (table_list));
|
|
|
|
table_list.db= (char *) table->s->db;
|
|
|
|
table_list.db_length= strlen(table_list.db);
|
|
|
|
table_list.table_name= (char *) table->s->table_name;
|
|
|
|
table_list.table_name_length= strlen(table_list.table_name);
|
|
|
|
table_list.alias= (char *) table->alias;
|
|
|
|
table_list.table= table;
|
|
|
|
|
|
|
|
if (check_table_access(thd, SELECT_ACL | UPDATE_ACL, &table_list, 0))
|
|
|
|
{
|
|
|
|
sp_restore_security_context(thd, save_ctx);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // NO_EMBEDDED_ACCESS_CHECKS
|
|
|
|
|
2005-08-15 17:15:12 +02:00
|
|
|
thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER);
|
2005-11-10 20:25:03 +01:00
|
|
|
res= sp_trigger->execute_function(thd, 0, 0, 0);
|
2005-08-15 17:15:12 +02:00
|
|
|
thd->restore_sub_statement_state(&statement_state);
|
2005-11-10 20:25:03 +01:00
|
|
|
|
|
|
|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
|
|
|
sp_restore_security_context(thd, save_ctx);
|
|
|
|
#endif // NO_EMBEDDED_ACCESS_CHECKS
|
2005-08-15 17:15:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|