1
0
mirror of https://github.com/MariaDB/server.git synced 2026-01-06 05:22:24 +03:00

Fix for bugs #5892/6182/8751/8758/10994 (based on Antony's patch)

"Triggers have the wrong namespace"
  "Triggers: duplicate names allowed"
  "Triggers: CREATE TRIGGER does not accept fully qualified names"
  "SHOW TRIGGERS"
This commit is contained in:
dlenev@mysql.com
2005-07-19 20:06:49 +04:00
parent bff3507b1d
commit 8a3e723b74
25 changed files with 644 additions and 130 deletions

View File

@@ -38,6 +38,45 @@ static File_option triggers_file_parameters[]=
};
/*
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[]=
{
{{(char*)"trigger_table", 15}, offsetof(struct st_trigname, trigger_table),
FILE_OPTIONS_ESTRING},
{{0, 0}, 0, FILE_OPTIONS_STRING}
};
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);
/*
Create or drop trigger for table.
@@ -69,6 +108,10 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
But do we want this ?
*/
if (!create &&
!(tables= add_table_for_trigger(thd, thd->lex->spname)))
DBUG_RETURN(TRUE);
/* We should have only one table in table list. */
DBUG_ASSERT(tables->next_global == 0);
@@ -174,11 +217,22 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
{
LEX *lex= thd->lex;
TABLE *table= tables->table;
char dir_buff[FN_REFLEN], file_buff[FN_REFLEN];
LEX_STRING dir, file;
char dir_buff[FN_REFLEN], file_buff[FN_REFLEN], trigname_buff[FN_REFLEN],
trigname_path[FN_REFLEN];
LEX_STRING dir, file, trigname_file;
LEX_STRING *trg_def, *name;
Item_trigger_field *trg_field;
List_iterator_fast<LEX_STRING> it(names_list);
struct st_trigname trigname;
/* Trigger must be in the same schema as target table. */
if (my_strcasecmp(system_charset_info, table->s->db,
lex->spname->m_db.str ? lex->spname->m_db.str :
thd->db))
{
my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0));
return 1;
}
/* We don't allow creation of several triggers of the same type yet */
if (bodies[lex->trg_chistics.event][lex->trg_chistics.action_time])
@@ -187,17 +241,6 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
return 1;
}
/* Let us check if trigger with the same name exists */
while ((name= it++))
{
if (my_strcasecmp(system_charset_info, lex->ident.str,
name->str) == 0)
{
my_message(ER_TRG_ALREADY_EXISTS, ER(ER_TRG_ALREADY_EXISTS), MYF(0));
return 1;
}
}
/*
Let us check if all references to fields in old/new versions of row in
this trigger are ok.
@@ -234,6 +277,25 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
file.length= strxnmov(file_buff, FN_REFLEN, tables->table_name,
triggers_file_ext, NullS) - file_buff;
file.str= file_buff;
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;
/*
Soon we will invalidate table object and thus Table_triggers_list object
@@ -246,13 +308,66 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
if (!(trg_def= (LEX_STRING *)alloc_root(&table->mem_root,
sizeof(LEX_STRING))) ||
definitions_list.push_back(trg_def, &table->mem_root))
return 1;
goto err_with_cleanup;
trg_def->str= thd->query;
trg_def->length= thd->query_length;
return sql_create_definition_file(&dir, &file, &triggers_file_type,
(gptr)this, triggers_file_parameters, 3);
if (!sql_create_definition_file(&dir, &file, &triggers_file_type,
(gptr)this, triggers_file_parameters, 3))
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));
}
@@ -275,12 +390,13 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables)
LEX_STRING *name;
List_iterator_fast<LEX_STRING> it_name(names_list);
List_iterator<LEX_STRING> it_def(definitions_list);
char path[FN_REFLEN];
while ((name= it_name++))
{
it_def++;
if (my_strcasecmp(system_charset_info, lex->ident.str,
if (my_strcasecmp(system_charset_info, lex->spname->m_name.str,
name->str) == 0)
{
/*
@@ -291,18 +407,14 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables)
if (definitions_list.is_empty())
{
char path[FN_REFLEN];
/*
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).
*/
strxnmov(path, FN_REFLEN, mysql_data_home, "/", tables->db, "/",
tables->table_name, triggers_file_ext, NullS);
unpack_filename(path, path);
return my_delete(path, MYF(MY_WME));
if (rm_trigger_file(path, tables->db, tables->table_name))
return 1;
}
else
{
@@ -317,10 +429,15 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables)
triggers_file_ext, NullS) - file_buff;
file.str= file_buff;
return sql_create_definition_file(&dir, &file, &triggers_file_type,
(gptr)this,
triggers_file_parameters, 3);
if (sql_create_definition_file(&dir, &file, &triggers_file_type,
(gptr)this, triggers_file_parameters,
3))
return 1;
}
if (rm_trigname_file(path, tables->db, lex->spname->m_name.str))
return 1;
return 0;
}
}
@@ -331,8 +448,8 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables)
Table_triggers_list::~Table_triggers_list()
{
for (int i= 0; i < 3; i++)
for (int j= 0; j < 2; j++)
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)
@@ -389,13 +506,16 @@ bool Table_triggers_list::prepare_record1_accessors(TABLE *table)
db - table's database name
table_name - table's name
table - pointer to table object
names_only - stop after loading trigger names
RETURN VALUE
False - success
True - error
*/
bool Table_triggers_list::check_n_load(THD *thd, const char *db,
const char *table_name, TABLE *table)
const char *table_name, TABLE *table,
bool names_only)
{
char path_buff[FN_REFLEN];
LEX_STRING path;
@@ -451,7 +571,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
TODO: This could be avoided if there is no triggers
for UPDATE and DELETE.
*/
if (triggers->prepare_record1_accessors(table))
if (!names_only && triggers->prepare_record1_accessors(table))
DBUG_RETURN(1);
List_iterator_fast<LEX_STRING> it(triggers->definitions_list);
@@ -471,32 +591,20 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
Free lex associated resources
QQ: Do we really need all this stuff here ?
*/
if (lex.sphead)
{
delete lex.sphead;
lex.sphead= 0;
}
delete lex.sphead;
goto err_with_lex_cleanup;
}
triggers->bodies[lex.trg_chistics.event]
[lex.trg_chistics.action_time]= lex.sphead;
lex.sphead= 0;
if (triggers->names_list.push_back(&lex.sphead->m_name, &table->mem_root))
goto err_with_lex_cleanup;
if (!(trg_name_buff= alloc_root(&table->mem_root,
sizeof(LEX_STRING) +
lex.ident.length + 1)))
goto err_with_lex_cleanup;
trg_name_str= (LEX_STRING *)trg_name_buff;
trg_name_buff+= sizeof(LEX_STRING);
memcpy(trg_name_buff, lex.ident.str,
lex.ident.length + 1);
trg_name_str->str= trg_name_buff;
trg_name_str->length= lex.ident.length;
if (triggers->names_list.push_back(trg_name_str, &table->mem_root))
goto err_with_lex_cleanup;
if (names_only)
{
lex_end(&lex);
continue;
}
/*
Let us bind Item_trigger_field objects representing access to fields
@@ -537,3 +645,160 @@ err_with_lex_cleanup:
DBUG_RETURN(1);
}
/*
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
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,
LEX_STRING *trigger_stmt)
{
sp_head *body;
DBUG_ENTER("get_trigger_info");
if ((body= bodies[event][time_type]))
{
*trigger_name= body->m_name;
*trigger_stmt= body->m_body;
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);
if (strncmp(trigname_file_type.str, parser->type()->str,
parser->type()->length))
{
my_error(ER_WRONG_OBJECT, MYF(0), trig->m_name.str, trigname_file_ext,
"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);
}