mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
Merge bk-internal.mysql.com:/home/bk/mysql-5.0
into mysql.com:/home/alik/MySQL/devel/5.0-wl2818
This commit is contained in:
@ -32,15 +32,36 @@ const char * const triggers_file_ext= ".TRG";
|
||||
*/
|
||||
static File_option triggers_file_parameters[]=
|
||||
{
|
||||
{{(char*)"triggers", 8},
|
||||
{
|
||||
{ (char *) STRING_WITH_LEN("triggers") },
|
||||
offsetof(class Table_triggers_list, definitions_list),
|
||||
FILE_OPTIONS_STRLIST},
|
||||
{{(char*)"sql_modes", 13},
|
||||
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 },
|
||||
offsetof(class Table_triggers_list, definition_modes_list),
|
||||
FILE_OPTIONS_ULLLIST},
|
||||
{{0, 0}, 0, FILE_OPTIONS_STRING}
|
||||
FILE_OPTIONS_ULLLIST
|
||||
},
|
||||
{
|
||||
{ (char *) STRING_WITH_LEN("definers") },
|
||||
offsetof(class Table_triggers_list, definers_list),
|
||||
FILE_OPTIONS_STRLIST
|
||||
},
|
||||
{ { 0, 0 }, 0, FILE_OPTIONS_STRING }
|
||||
};
|
||||
|
||||
/*
|
||||
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;
|
||||
|
||||
/*
|
||||
Structure representing contents of .TRN file which are used to support
|
||||
@ -58,9 +79,16 @@ const char * const trigname_file_ext= ".TRN";
|
||||
|
||||
static File_option trigname_file_parameters[]=
|
||||
{
|
||||
{{(char*)"trigger_table", 15}, offsetof(struct st_trigname, trigger_table),
|
||||
FILE_OPTIONS_ESTRING},
|
||||
{{0, 0}, 0, FILE_OPTIONS_STRING}
|
||||
{
|
||||
/*
|
||||
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 }
|
||||
};
|
||||
|
||||
|
||||
@ -104,6 +132,9 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
|
||||
{
|
||||
TABLE *table;
|
||||
bool result= TRUE;
|
||||
LEX_STRING definer_user;
|
||||
LEX_STRING definer_host;
|
||||
|
||||
DBUG_ENTER("mysql_create_or_drop_trigger");
|
||||
|
||||
/*
|
||||
@ -187,7 +218,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
|
||||
}
|
||||
|
||||
result= (create ?
|
||||
table->triggers->create_trigger(thd, tables):
|
||||
table->triggers->create_trigger(thd, tables, &definer_user, &definer_host):
|
||||
table->triggers->drop_trigger(thd, tables));
|
||||
|
||||
end:
|
||||
@ -195,17 +226,30 @@ end:
|
||||
start_waiting_global_read_lock(thd);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
if (mysql_bin_log.is_open())
|
||||
{
|
||||
if (mysql_bin_log.is_open())
|
||||
thd->clear_error();
|
||||
|
||||
String log_query(thd->query, thd->query_length, system_charset_info);
|
||||
|
||||
if (create)
|
||||
{
|
||||
thd->clear_error();
|
||||
/* Such a statement can always go directly to binlog, no trans cache */
|
||||
Query_log_event qinfo(thd, thd->query, thd->query_length, 0, FALSE);
|
||||
mysql_bin_log.write(&qinfo);
|
||||
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);
|
||||
}
|
||||
send_ok(thd);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
send_ok(thd);
|
||||
}
|
||||
|
||||
DBUG_RETURN(result);
|
||||
}
|
||||
|
||||
@ -215,15 +259,26 @@ end:
|
||||
|
||||
SYNOPSIS
|
||||
create_trigger()
|
||||
thd - current thread context (including trigger definition in LEX)
|
||||
tables - table list containing one open table for which trigger is
|
||||
created.
|
||||
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.
|
||||
|
||||
RETURN VALUE
|
||||
False - success
|
||||
True - error
|
||||
*/
|
||||
bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
|
||||
bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
|
||||
LEX_STRING *definer_user,
|
||||
LEX_STRING *definer_host)
|
||||
{
|
||||
LEX *lex= thd->lex;
|
||||
TABLE *table= tables->table;
|
||||
@ -232,6 +287,8 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
|
||||
LEX_STRING dir, file, trigname_file;
|
||||
LEX_STRING *trg_def, *name;
|
||||
ulonglong *trg_sql_mode;
|
||||
char trg_definer_holder[HOSTNAME_LENGTH + USERNAME_LENGTH + 2];
|
||||
LEX_STRING *trg_definer;
|
||||
Item_trigger_field *trg_field;
|
||||
struct st_trigname trigname;
|
||||
|
||||
@ -252,6 +309,31 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Let us check if all references to fields in old/new versions of row in
|
||||
this trigger are ok.
|
||||
@ -321,15 +403,39 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
|
||||
definitions_list.push_back(trg_def, &table->mem_root) ||
|
||||
!(trg_sql_mode= (ulonglong*)alloc_root(&table->mem_root,
|
||||
sizeof(ulonglong))) ||
|
||||
definition_modes_list.push_back(trg_sql_mode, &table->mem_root))
|
||||
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))
|
||||
goto err_with_cleanup;
|
||||
|
||||
trg_def->str= thd->query;
|
||||
trg_def->length= thd->query_length;
|
||||
*trg_sql_mode= thd->variables.sql_mode;
|
||||
|
||||
#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;
|
||||
|
||||
if (!sql_create_definition_file(&dir, &file, &triggers_file_type,
|
||||
(gptr)this, triggers_file_parameters, 3))
|
||||
(gptr)this, triggers_file_parameters,
|
||||
TRG_MAX_VERSIONS))
|
||||
return 0;
|
||||
|
||||
err_with_cleanup:
|
||||
@ -406,12 +512,14 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables)
|
||||
List_iterator_fast<LEX_STRING> it_name(names_list);
|
||||
List_iterator<LEX_STRING> it_def(definitions_list);
|
||||
List_iterator<ulonglong> it_mod(definition_modes_list);
|
||||
List_iterator<LEX_STRING> it_definer(definers_list);
|
||||
char path[FN_REFLEN];
|
||||
|
||||
while ((name= it_name++))
|
||||
{
|
||||
it_def++;
|
||||
it_mod++;
|
||||
it_definer++;
|
||||
|
||||
if (my_strcasecmp(table_alias_charset, lex->spname->m_name.str,
|
||||
name->str) == 0)
|
||||
@ -422,6 +530,7 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables)
|
||||
*/
|
||||
it_def.remove();
|
||||
it_mod.remove();
|
||||
it_definer.remove();
|
||||
|
||||
if (definitions_list.is_empty())
|
||||
{
|
||||
@ -449,7 +558,7 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables)
|
||||
|
||||
if (sql_create_definition_file(&dir, &file, &triggers_file_type,
|
||||
(gptr)this, triggers_file_parameters,
|
||||
3))
|
||||
TRG_MAX_VERSIONS))
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -571,7 +680,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
|
||||
DBUG_RETURN(0);
|
||||
|
||||
/*
|
||||
File exists so we got to load triggers
|
||||
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 ?
|
||||
*/
|
||||
@ -587,13 +696,16 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
|
||||
DBUG_RETURN(1);
|
||||
|
||||
/*
|
||||
We don't have sql_modes in old versions of .TRG file, so we should
|
||||
initialize list for safety.
|
||||
We don't have the following attributes in old versions of .TRG file, so
|
||||
we should initialize the list for safety:
|
||||
- sql_modes;
|
||||
- definers;
|
||||
*/
|
||||
triggers->definition_modes_list.empty();
|
||||
triggers->definers_list.empty();
|
||||
|
||||
if (parser->parse((gptr)triggers, &table->mem_root,
|
||||
triggers_file_parameters, 2))
|
||||
triggers_file_parameters, TRG_NUM_REQUIRED_PARAMETERS))
|
||||
DBUG_RETURN(1);
|
||||
|
||||
List_iterator_fast<LEX_STRING> it(triggers->definitions_list);
|
||||
@ -615,7 +727,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
|
||||
DBUG_RETURN(1); // EOM
|
||||
}
|
||||
*trg_sql_mode= global_system_variables.sql_mode;
|
||||
while ((trg_create_str= it++))
|
||||
while (it++)
|
||||
{
|
||||
if (triggers->definition_modes_list.push_back(trg_sql_mode,
|
||||
&table->mem_root))
|
||||
@ -626,8 +738,43 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
|
||||
it.rewind();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
DBUG_ASSERT(triggers->definition_modes_list.elements ==
|
||||
triggers->definitions_list.elements);
|
||||
DBUG_ASSERT(triggers->definers_list.elements ==
|
||||
triggers->definitions_list.elements);
|
||||
|
||||
table->triggers= triggers;
|
||||
|
||||
/*
|
||||
@ -650,6 +797,8 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
|
||||
|
||||
char *trg_name_buff;
|
||||
List_iterator_fast<ulonglong> itm(triggers->definition_modes_list);
|
||||
List_iterator_fast<LEX_STRING> it_definer(triggers->
|
||||
definers_list);
|
||||
LEX *old_lex= thd->lex, lex;
|
||||
ulong save_sql_mode= thd->variables.sql_mode;
|
||||
|
||||
@ -662,22 +811,55 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
|
||||
while ((trg_create_str= it++))
|
||||
{
|
||||
trg_sql_mode= itm++;
|
||||
LEX_STRING *trg_definer= it_definer++;
|
||||
thd->variables.sql_mode= (ulong)*trg_sql_mode;
|
||||
lex_start(thd, (uchar*)trg_create_str->str, trg_create_str->length);
|
||||
|
||||
if (yyparse((void *)thd) || thd->is_fatal_error)
|
||||
{
|
||||
/*
|
||||
Free lex associated resources
|
||||
Free lex associated resources.
|
||||
QQ: Do we really need all this stuff here ?
|
||||
*/
|
||||
delete lex.sphead;
|
||||
goto err_with_lex_cleanup;
|
||||
}
|
||||
|
||||
lex.sphead->m_sql_mode= *trg_sql_mode;
|
||||
lex.sphead->set_info(0, 0, &lex.sp_chistics, *trg_sql_mode);
|
||||
|
||||
triggers->bodies[lex.trg_chistics.event]
|
||||
[lex.trg_chistics.action_time]= lex.sphead;
|
||||
|
||||
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);
|
||||
|
||||
if (triggers->names_list.push_back(&lex.sphead->m_name,
|
||||
&table->mem_root))
|
||||
goto err_with_lex_cleanup;
|
||||
@ -704,6 +886,10 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
|
||||
trg_field= trg_field->next_trg_field)
|
||||
trg_field->setup_field(thd, table);
|
||||
|
||||
triggers->m_spec_var_used[lex.trg_chistics.event]
|
||||
[lex.trg_chistics.action_time]=
|
||||
lex.trg_table_fields.first ? TRUE : FALSE;
|
||||
|
||||
lex_end(&lex);
|
||||
}
|
||||
thd->db= save_db.str;
|
||||
@ -747,6 +933,9 @@ err_with_lex_cleanup:
|
||||
name - returns name of trigger
|
||||
stmt - returns statement of trigger
|
||||
sql_mode - returns sql_mode of trigger
|
||||
definer_user - returns definer/creator of trigger. The caller is
|
||||
responsible to allocate enough space for storing definer
|
||||
information.
|
||||
|
||||
RETURN VALUE
|
||||
False - success
|
||||
@ -757,7 +946,8 @@ 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,
|
||||
ulong *sql_mode)
|
||||
ulong *sql_mode,
|
||||
LEX_STRING *definer)
|
||||
{
|
||||
sp_head *body;
|
||||
DBUG_ENTER("get_trigger_info");
|
||||
@ -766,6 +956,18 @@ bool Table_triggers_list::get_trigger_info(THD *thd, trg_event_type event,
|
||||
*trigger_name= body->m_name;
|
||||
*trigger_stmt= body->m_body;
|
||||
*sql_mode= body->m_sql_mode;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
DBUG_RETURN(1);
|
||||
@ -901,8 +1103,9 @@ bool Table_triggers_list::process_triggers(THD *thd, trg_event_type event,
|
||||
bool old_row_is_record1)
|
||||
{
|
||||
int res= 0;
|
||||
sp_head *sp_trigger= bodies[event][time_type];
|
||||
|
||||
if (bodies[event][time_type])
|
||||
if (sp_trigger)
|
||||
{
|
||||
Sub_statement_state statement_state;
|
||||
|
||||
@ -917,14 +1120,54 @@ bool Table_triggers_list::process_triggers(THD *thd, trg_event_type event,
|
||||
old_field= table->field;
|
||||
}
|
||||
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
Security_context *save_ctx;
|
||||
|
||||
if (sp_change_security_context(thd, sp_trigger, &save_ctx))
|
||||
return TRUE;
|
||||
|
||||
/*
|
||||
FIXME: We should juggle with security context here (because trigger
|
||||
should be invoked with creator rights).
|
||||
NOTE: TRIGGER_ACL should be used below.
|
||||
*/
|
||||
|
||||
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
|
||||
|
||||
thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER);
|
||||
res= bodies[event][time_type]->execute_function(thd, 0, 0, 0);
|
||||
res= sp_trigger->execute_function(thd, 0, 0, 0);
|
||||
thd->restore_sub_statement_state(&statement_state);
|
||||
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
sp_restore_security_context(thd, save_ctx);
|
||||
#endif // NO_EMBEDDED_ACCESS_CHECKS
|
||||
}
|
||||
|
||||
return res;
|
||||
|
Reference in New Issue
Block a user