WL#1218 "Triggers". Some very preliminary version of patch.

Mostly needed for Monty for him getting notion what needed for triggers 
from new .FRM format. 

Things to be done:
- Right placement of trigger's invocations
- Right handling of errors in triggers (including transaction rollback)
- Support for priviliges
- Right handling of DROP/RENAME table (hope that it will be handled automatically
  with merging of .TRG into .FRM file)
- Saving/restoring some information critical for trigger creation and replication
  with their definitions (e.g. sql_mode, creator, ...)
- Replication

Already has some known bugs so probably not for general review.
parent b93aa71d
......@@ -370,4 +370,9 @@
#define ER_VIEW_WRONG_LIST 1351
#define ER_WARN_VIEW_MERGE 1352
#define ER_WARN_VIEW_WITHOUT_KEY 1353
#define ER_ERROR_MESSAGES 354
#define ER_TRG_ALREADY_EXISTS 1354
#define ER_TRG_DOES_NOT_EXIST 1355
#define ER_TRG_ON_VIEW_OR_TEMP_TABLE 1356
#define ER_TRG_CANT_CHANGE_ROW 1357
#define ER_TRG_NO_SUCH_ROW_IN_TRG 1358
#define ER_ERROR_MESSAGES 359
drop table if exists t1, t2;
drop view if exists v1;
create table t1 (i int);
create trigger trg before insert on t1 for each row set @a:=1;
set @a:=0;
select @a;
@a
0
insert into t1 values (1);
select @a;
@a
1
drop trigger t1.trg;
create trigger trg before insert on t1 for each row set @a:=new.i;
insert into t1 values (123);
select @a;
@a
123
drop trigger t1.trg;
drop table t1;
create table t1 (i int not null, j int);
create trigger trg before insert on t1 for each row
begin
if isnull(new.j) then
set new.j:= new.i * 10;
end if;
end|
insert into t1 (i) values (1)|
insert into t1 (i,j) values (2, 3)|
select * from t1|
i j
1 10
2 3
drop trigger t1.trg|
drop table t1|
create table t1 (i int not null primary key);
create trigger trg after insert on t1 for each row
set @a:= if(@a,concat(@a, ":", new.i), new.i);
set @a:="";
insert into t1 values (2),(3),(4),(5);
select @a;
@a
2:3:4:5
drop trigger t1.trg;
drop table t1;
create table t1 (aid int not null primary key, balance int not null default 0);
insert into t1 values (1, 1000), (2,3000);
create trigger trg before update on t1 for each row
begin
declare loc_err varchar(255);
if abs(new.balance - old.balance) > 1000 then
set new.balance:= old.balance;
set loc_err := concat("Too big change for aid = ", new.aid);
set @update_failed:= if(@update_failed, concat(@a, ":", loc_err), loc_err);
end if;
end|
set @update_failed:=""|
update t1 set balance=1500|
select @update_failed;
select * from t1|
@update_failed
Too big change for aid = 2
aid balance
1 1500
2 3000
drop trigger t1.trg|
drop table t1|
create table t1 (i int);
insert into t1 values (1),(2),(3),(4);
create trigger trg after update on t1 for each row
set @total_change:=@total_change + new.i - old.i;
set @total_change:=0;
update t1 set i=3;
select @total_change;
@total_change
2
drop trigger t1.trg;
drop table t1;
create table t1 (i int);
insert into t1 values (1),(2),(3),(4);
create trigger trg before delete on t1 for each row
set @del_sum:= @del_sum + old.i;
set @del_sum:= 0;
delete from t1 where i <= 3;
select @del_sum;
@del_sum
6
drop trigger t1.trg;
drop table t1;
create table t1 (i int);
insert into t1 values (1),(2),(3),(4);
create trigger trg after delete on t1 for each row set @del:= 1;
set @del:= 0;
delete from t1 where i <> 0;
select @del;
@del
1
drop trigger t1.trg;
drop table t1;
create table t1 (i int, j int);
create trigger trg1 before insert on t1 for each row
begin
if new.j > 10 then
set new.j := 10;
end if;
end|
create trigger trg2 before update on t1 for each row
begin
if old.i % 2 = 0 then
set new.j := -1;
end if;
end|
create trigger trg3 after update on t1 for each row
begin
if new.j = -1 then
set @fired:= "Yes";
end if;
end|
set @fired:="";
insert into t1 values (1,2),(2,3),(3,14);
select @fired;
@fired
select * from t1;
i j
1 2
2 3
3 10
update t1 set j= 20;
select @fired;
@fired
Yes
select * from t1;
i j
1 20
2 -1
3 20
drop trigger t1.trg1;
drop trigger t1.trg2;
drop trigger t1.trg3;
drop table t1;
create table t1 (i int);
create trigger trg before insert on t1 for each row set @a:= old.i;
ERROR HY000: There is no OLD row in on INSERT trigger
create trigger trg before delete on t1 for each row set @a:= new.i;
ERROR HY000: There is no NEW row in on DELETE trigger
create trigger trg before update on t1 for each row set old.i:=1;
ERROR HY000: Updating of OLD row is not allowed in trigger
create trigger trg before delete on t1 for each row set new.i:=1;
ERROR HY000: There is no NEW row in on DELETE trigger
create trigger trg after update on t1 for each row set new.i:=1;
ERROR HY000: Updating of NEW row is not allowed in after trigger
create trigger trg before insert on t2 for each row set @a:=1;
ERROR 42S02: Table 'test.t2' doesn't exist
create trigger trg before insert on t1 for each row set @a:=1;
create trigger trg after insert on t1 for each row set @a:=1;
ERROR HY000: Trigger already exists
create trigger trg2 before insert on t1 for each row set @a:=1;
ERROR HY000: Trigger already exists
drop trigger t1.trg;
drop trigger t1.trg;
ERROR HY000: Trigger does not exist
create view v1 as select * from t1;
create trigger trg before insert on v1 for each row set @a:=1;
ERROR HY000: Trigger's 'v1' is view or temporary table
drop view v1;
drop table t1;
create temporary table t1 (i int);
create trigger trg before insert on t1 for each row set @a:=1;
ERROR HY000: Trigger's 't1' is view or temporary table
drop table t1;
#
# Basic triggers test
#
--disable_warnings
drop table if exists t1, t2;
drop view if exists v1;
--enable_warnings
create table t1 (i int);
# let us test some very simple trigger
create trigger trg before insert on t1 for each row set @a:=1;
set @a:=0;
select @a;
insert into t1 values (1);
select @a;
drop trigger t1.trg;
# let us test simple trigger reading some values
create trigger trg before insert on t1 for each row set @a:=new.i;
insert into t1 values (123);
select @a;
drop trigger t1.trg;
drop table t1;
# Let us test before insert trigger
# Such triggers can be used for setting complex default values
create table t1 (i int not null, j int);
delimiter |;
create trigger trg before insert on t1 for each row
begin
if isnull(new.j) then
set new.j:= new.i * 10;
end if;
end|
insert into t1 (i) values (1)|
insert into t1 (i,j) values (2, 3)|
select * from t1|
drop trigger t1.trg|
drop table t1|
delimiter ;|
# After insert trigger
# Useful for aggregating data
create table t1 (i int not null primary key);
create trigger trg after insert on t1 for each row
set @a:= if(@a,concat(@a, ":", new.i), new.i);
set @a:="";
insert into t1 values (2),(3),(4),(5);
select @a;
drop trigger t1.trg;
drop table t1;
# Before update trigger
# (In future we will achieve this via proper error handling in triggers)
create table t1 (aid int not null primary key, balance int not null default 0);
insert into t1 values (1, 1000), (2,3000);
delimiter |;
create trigger trg before update on t1 for each row
begin
declare loc_err varchar(255);
if abs(new.balance - old.balance) > 1000 then
set new.balance:= old.balance;
set loc_err := concat("Too big change for aid = ", new.aid);
set @update_failed:= if(@update_failed, concat(@a, ":", loc_err), loc_err);
end if;
end|
set @update_failed:=""|
update t1 set balance=1500|
select @update_failed;
select * from t1|
drop trigger t1.trg|
drop table t1|
delimiter ;|
# After update trigger
create table t1 (i int);
insert into t1 values (1),(2),(3),(4);
create trigger trg after update on t1 for each row
set @total_change:=@total_change + new.i - old.i;
set @total_change:=0;
update t1 set i=3;
select @total_change;
drop trigger t1.trg;
drop table t1;
# Before delete trigger
# This can be used for aggregation too :)
create table t1 (i int);
insert into t1 values (1),(2),(3),(4);
create trigger trg before delete on t1 for each row
set @del_sum:= @del_sum + old.i;
set @del_sum:= 0;
delete from t1 where i <= 3;
select @del_sum;
drop trigger t1.trg;
drop table t1;
# After delete trigger.
# Just run out of imagination.
create table t1 (i int);
insert into t1 values (1),(2),(3),(4);
create trigger trg after delete on t1 for each row set @del:= 1;
set @del:= 0;
delete from t1 where i <> 0;
select @del;
drop trigger t1.trg;
drop table t1;
# Several triggers on one table
create table t1 (i int, j int);
delimiter |;
create trigger trg1 before insert on t1 for each row
begin
if new.j > 10 then
set new.j := 10;
end if;
end|
create trigger trg2 before update on t1 for each row
begin
if old.i % 2 = 0 then
set new.j := -1;
end if;
end|
create trigger trg3 after update on t1 for each row
begin
if new.j = -1 then
set @fired:= "Yes";
end if;
end|
delimiter ;|
set @fired:="";
insert into t1 values (1,2),(2,3),(3,14);
select @fired;
select * from t1;
update t1 set j= 20;
select @fired;
select * from t1;
drop trigger t1.trg1;
drop trigger t1.trg2;
drop trigger t1.trg3;
drop table t1;
#
# Test of wrong column specifiers in triggers
#
create table t1 (i int);
--error 1358
create trigger trg before insert on t1 for each row set @a:= old.i;
--error 1358
create trigger trg before delete on t1 for each row set @a:= new.i;
--error 1357
create trigger trg before update on t1 for each row set old.i:=1;
--error 1358
create trigger trg before delete on t1 for each row set new.i:=1;
--error 1357
create trigger trg after update on t1 for each row set new.i:=1;
# TODO: We should also test wrong field names here, we don't do it now
# because proper error handling is not in place yet.
#
# Let us test various trigger creation errors
#
#
--error 1146
create trigger trg before insert on t2 for each row set @a:=1;
create trigger trg before insert on t1 for each row set @a:=1;
--error 1354
create trigger trg after insert on t1 for each row set @a:=1;
--error 1354
create trigger trg2 before insert on t1 for each row set @a:=1;
drop trigger t1.trg;
--error 1355
drop trigger t1.trg;
create view v1 as select * from t1;
--error 1356
create trigger trg before insert on v1 for each row set @a:=1;
drop view v1;
drop table t1;
create temporary table t1 (i int);
--error 1356
create trigger trg before insert on t1 for each row set @a:=1;
drop table t1;
......@@ -61,7 +61,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
spatial.h gstream.h client_settings.h tzfile.h \
tztime.h examples/ha_example.h examples/ha_archive.h \
sp_head.h sp_pcontext.h sp_rcontext.h sp.h sp_cache.h \
parse_file.h sql_view.h
parse_file.h sql_view.h sql_trigger.h
mysqld_SOURCES = sql_lex.cc sql_handler.cc \
item.cc item_sum.cc item_buff.cc item_func.cc \
item_cmpfunc.cc item_strfunc.cc item_timefunc.cc \
......@@ -95,7 +95,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc \
tztime.cc my_time.c \
examples/ha_example.cc examples/ha_archive.cc \
sp_head.cc sp_pcontext.cc sp_rcontext.cc sp.cc \
sp_cache.cc parse_file.cc
sp_cache.cc parse_file.cc sql_trigger.cc
gen_lex_hash_SOURCES = gen_lex_hash.cc
gen_lex_hash_LDADD = $(LDADD) $(CXXLDFLAGS)
mysql_tzinfo_to_sql_SOURCES = mysql_tzinfo_to_sql.cc
......
......@@ -24,6 +24,8 @@
#include "my_dir.h"
#include "sp_rcontext.h"
#include "sql_acl.h"
#include "sp_head.h"
#include "sql_trigger.h"
static void mark_as_dependent(THD *thd,
SELECT_LEX *last, SELECT_LEX *current,
......@@ -2251,6 +2253,97 @@ void Item_insert_value::print(String *str)
str->append(')');
}
/*
Bind item representing field of row being changed in trigger
to appropriate Field object.
SYNOPSIS
setup_field()
thd - current thread context
table - table of trigger (and where we looking for fields)
event - type of trigger event
NOTE
This function does almost the same as fix_fields() for Item_field
but is invoked during trigger definition parsing and takes TABLE
object as its argument.
RETURN VALUES
0 ok
1 field was not found.
*/
bool Item_trigger_field::setup_field(THD *thd, TABLE *table,
enum trg_event_type event)
{
bool result= 1;
uint field_idx= (uint)-1;
bool save_set_query_id= thd->set_query_id;
/* TODO: Think more about consequences of this step. */
thd->set_query_id= 0;
if (find_field_in_real_table(thd, table, field_name,
strlen(field_name), 0, 0,
&field_idx))
{
field= (row_version == OLD_ROW && event == TRG_EVENT_UPDATE) ?
table->triggers->old_field[field_idx] :
table->field[field_idx];
result= 0;
}
thd->set_query_id= save_set_query_id;
return result;
}
bool Item_trigger_field::eq(const Item *item, bool binary_cmp) const
{
return item->type() == TRIGGER_FIELD_ITEM &&
row_version == ((Item_trigger_field *)item)->row_version &&
!my_strcasecmp(system_charset_info, field_name,
((Item_trigger_field *)item)->field_name);
}
bool Item_trigger_field::fix_fields(THD *thd,
TABLE_LIST *table_list,
Item **items)
{
/*
Since trigger is object tightly associated with TABLE object most
of its set up can be performed during trigger loading i.e. trigger
parsing! So we have little to do in fix_fields. :)
FIXME may be we still should bother about permissions here.
*/
DBUG_ASSERT(fixed == 0);
// QQ: May be this should be moved to setup_field?
set_field(field);
fixed= 1;
return 0;
}
void Item_trigger_field::print(String *str)
{
str->append((row_version == NEW_ROW) ? "NEW" : "OLD", 3);
str->append('.');
str->append(field_name);
}
void Item_trigger_field::cleanup()
{
/*
Since special nature of Item_trigger_field we should not do most of
things from Item_field::cleanup() or Item_ident::cleanup() here.
*/
Item::cleanup();
}
/*
If item is a const function, calculate it and return a const item
The original item is freed if not returned
......
......@@ -99,7 +99,7 @@ public:
PROC_ITEM,COND_ITEM, REF_ITEM, FIELD_STD_ITEM,
FIELD_VARIANCE_ITEM, INSERT_VALUE_ITEM,
SUBSELECT_ITEM, ROW_ITEM, CACHE_ITEM, TYPE_HOLDER,
PARAM_ITEM};
PARAM_ITEM, TRIGGER_FIELD_ITEM};
enum cond_result { COND_UNDEF,COND_OK,COND_TRUE,COND_FALSE };
......@@ -439,6 +439,7 @@ public:
class Item_field :public Item_ident
{
protected:
void set_field(Field *field);
public:
Field *field,*result_field;
......@@ -1152,6 +1153,57 @@ public:
}
};
/*
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
};
/*
Event on which trigger is invoked.
*/
enum trg_event_type
{
TRG_EVENT_INSERT= 0 , TRG_EVENT_UPDATE= 1, TRG_EVENT_DELETE= 2
};
/*
Represents NEW/OLD version of field of row which is
changed/read in trigger.
Note: For this item actual binding to Field object happens not during
fix_fields() (like for Item_field) but during parsing of trigger
definition, when table is opened, with special setup_field() call.
*/
class Item_trigger_field : public Item_field
{
public:
/* Is this item represents row from NEW or OLD row ? */
enum row_version_type {OLD_ROW, NEW_ROW};
row_version_type row_version;
Item_trigger_field(row_version_type row_ver_par,
const char *field_name_par):
Item_field((const char *)NULL, (const char *)NULL, field_name_par),
row_version(row_ver_par)
{}
bool setup_field(THD *thd, TABLE *table, enum trg_event_type event);
enum Type type() const { return TRIGGER_FIELD_ITEM; }
bool eq(const Item *item, bool binary_cmp) const;
bool fix_fields(THD *, struct st_table_list *, Item **);
void print(String *str);
table_map used_tables() const { return (table_map)0L; }
void cleanup();
};
class Item_cache: public Item
{
protected:
......
......@@ -2563,6 +2563,16 @@ void Item_func_set_user_var::print(String *str)
}
void Item_func_set_user_var::print_as_stmt(String *str)
{
str->append("set @", 5);
str->append(name.str, name.length);
str->append(":=", 2);
args[0]->print(str);
str->append(')');
}
String *
Item_func_get_user_var::val_str(String *str)
{
......@@ -3296,6 +3306,11 @@ Item_func_sp::execute(Item **itp)
sp_change_security_context(thd, m_sp, &save_ctx);
#endif
/*
We don't need to surpress senfing of ok packet here (by setting
thd->net.no_send_ok to true), because we are not allowing statements
in functions now.
*/
res= m_sp->execute_function(thd, args, arg_count, itp);
#ifndef NO_EMBEDDED_ACCESS_CHECKS
......
......@@ -945,6 +945,7 @@ public:
bool fix_fields(THD *thd, struct st_table_list *tables, Item **ref);
void fix_length_and_dec();
void print(String *str);
void print_as_stmt(String *str);
const char *func_name() const { return "set_user_var"; }
};
......
......@@ -167,6 +167,7 @@ static SYMBOL symbols[] = {
{ "DUMPFILE", SYM(DUMPFILE)},
{ "DUPLICATE", SYM(DUPLICATE_SYM)},
{ "DYNAMIC", SYM(DYNAMIC_SYM)},
{ "EACH", SYM(EACH_SYM)},
{ "ELSE", SYM(ELSE)},
{ "ELSEIF", SYM(ELSEIF_SYM)},
{ "ENABLE", SYM(ENABLE_SYM)},
......@@ -468,6 +469,7 @@ static SYMBOL symbols[] = {
{ "TO", SYM(TO_SYM)},
{ "TRAILING", SYM(TRAILING)},
{ "TRANSACTION", SYM(TRANSACTION_SYM)},
{ "TRIGGER", SYM(TRIGGER_SYM)},
{ "TRUE", SYM(TRUE_SYM)},
{ "TRUNCATE", SYM(TRUNCATE_SYM)},
{ "TYPE", SYM(TYPE_SYM)},
......
......@@ -471,6 +471,7 @@ int mysql_rm_table_part2_with_lock(THD *thd, TABLE_LIST *tables,
bool log_query);
int quick_rm_table(enum db_type base,const char *db,
const char *table_name);
void close_cached_table(THD *thd, TABLE *table);
bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list);
bool mysql_change_db(THD *thd,const char *name);
void mysql_parse(THD *thd,char *inBuf,uint length);
......@@ -611,6 +612,7 @@ int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds);
int mysql_delete(THD *thd, TABLE_LIST *table, COND *conds, SQL_LIST *order,
ha_rows rows, ulong options);
int mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok=0);
int mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create);
TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update);
TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem,
bool *refresh);
......@@ -637,6 +639,10 @@ Field *find_field_in_table(THD *thd, TABLE_LIST *tables, const char *name,
bool check_grant_table, bool check_grant_view,
bool allow_rowid,
uint *cached_field_index_ptr);
Field *find_field_in_real_table(THD *thd, TABLE *table,
const char *name, uint length,
bool check_grants, bool allow_rowid,
uint *cached_field_index_ptr);
#ifdef HAVE_OPENSSL
#include <openssl/des.h>
struct st_des_keyblock
......
......@@ -46,7 +46,7 @@ write_escaped_string(IO_CACHE *file, LEX_STRING *val_s)
{
/*
Should be in sync with read_escaped_string() and
parse_quated_escaped_string()
parse_quoted_escaped_string()
*/
switch(*ptr) {
case '\\': // escape character
......@@ -154,11 +154,10 @@ write_parameter(IO_CACHE *file, gptr base, File_option *parameter,
LEX_STRING *str;
while ((str= it++))
{
num.set((ulonglong)str->length, &my_charset_bin);
// ',' after string to detect list continuation
// We need ' ' after string to detect list continuation
if ((!first && my_b_append(file, (const byte *)" ", 1)) ||
my_b_append(file, (const byte *)"\'", 1) ||
my_b_append(file, (const byte *)str->str, str->length) ||
write_escaped_string(file, str) ||
my_b_append(file, (const byte *)"\'", 1))
{
DBUG_RETURN(TRUE);
......@@ -486,7 +485,7 @@ read_escaped_string(char *ptr, char *eol, LEX_STRING *str)
return TRUE;
/*
Should be in sync with write_escaped_string() and
parse_quated_escaped_string()
parse_quoted_escaped_string()
*/
switch(*ptr) {
case '\\':
......@@ -562,7 +561,7 @@ parse_escaped_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str)
*/
static char *
parse_quated_escaped_string(char *ptr, char *end,
parse_quoted_escaped_string(char *ptr, char *end,
MEM_ROOT *mem_root, LEX_STRING *str)
{
char *eol;
......@@ -684,7 +683,6 @@ File_parser::parse(gptr base, MEM_ROOT *mem_root,
my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0),
parameter->name.str, line);
DBUG_RETURN(TRUE);
DBUG_RETURN(TRUE);
}
break;
}
......@@ -724,6 +722,7 @@ File_parser::parse(gptr base, MEM_ROOT *mem_root,
/*
TODO: remove play with mem_root, when List will be able
to store MEM_ROOT* pointer for list elements allocation
FIXME: we can't handle empty lists
*/
sql_mem= my_pthread_getspecific_ptr(MEM_ROOT*, THR_MALLOC);
list= (List<LEX_STRING>*)(base + parameter->offset);
......@@ -741,7 +740,7 @@ File_parser::parse(gptr base, MEM_ROOT *mem_root,
sizeof(LEX_STRING))) ||
list->push_back(str))
goto list_err;
if(!(ptr= parse_quated_escaped_string(ptr, end, mem_root, str)))
if(!(ptr= parse_quoted_escaped_string(ptr, end, mem_root, str)))
goto list_err_w_message;
switch (*ptr) {
case '\n':
......
......@@ -27,7 +27,8 @@ enum file_opt_type {
FILE_OPTIONS_REV, /* Revision version number (ulonglong) */
FILE_OPTIONS_TIMESTAMP, /* timestamp (LEX_STRING have to be
allocated with length 20 (19+1) */
FILE_OPTIONS_STRLIST /* list of strings (List<char*>) */
FILE_OPTIONS_STRLIST /* list of escaped strings
(List<LEX_STRING>) */
};
struct File_option
......
......@@ -366,3 +366,8 @@ character-set=latin2
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -360,3 +360,8 @@ character-set=latin1
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -368,3 +368,8 @@ character-set=latin1
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -357,3 +357,8 @@ character-set=latin1
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -362,3 +362,8 @@ character-set=latin7
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -357,3 +357,8 @@ character-set=latin1
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -369,3 +369,8 @@ character-set=latin1
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -357,3 +357,8 @@ character-set=greek
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -359,3 +359,8 @@ character-set=latin2
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -357,3 +357,8 @@ character-set=latin1
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -359,3 +359,8 @@ character-set=ujis
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -357,3 +357,8 @@ character-set=euckr
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -359,3 +359,8 @@ character-set=latin1
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -359,3 +359,8 @@ character-set=latin1
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -361,3 +361,8 @@ character-set=latin2
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -358,3 +358,8 @@ character-set=latin1
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -361,3 +361,8 @@ character-set=latin2
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -359,3 +359,8 @@ character-set=koi8r
"View SELECT view "
" view ( )"
" view ()"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -363,3 +363,8 @@ character-set=cp1250
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -365,3 +365,8 @@ character-set=latin2
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -359,3 +359,8 @@ character-set=latin1
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -357,3 +357,8 @@ character-set=latin1
"View's SELECT and view's field list have different column counts"
"View merge algorithm can't be used here for now (assumed undefined algorithm)"
"View being update does not have complete key of underlying table in it"
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -362,3 +362,8 @@ character-set=koi8u
"View SELECT ̦ æ view Ҧ ˦˦ æ"
" view ( )"
"View, , ͦ æ(), Ҧ "
"Trigger already exists"
"Trigger does not exist"
"Trigger's '%-.64s' is view or temporary table"
"Updating of %s row is not allowed in %strigger"
"There is no %s row in %s trigger"
......@@ -242,10 +242,13 @@ sp_head::init_strings(THD *thd, LEX *lex, sp_name *name)
/* During parsing, we must use thd->mem_root */
MEM_ROOT *root= &thd->mem_root;
/* We have to copy strings to get them into the right memroot */
if (name)
{
DBUG_PRINT("info", ("name: %*.s%*s",
name->m_db.length, name->m_db.str,
name->m_name.length, name->m_name.str));
/* We have to copy strings to get them into the right memroot */
if (name->m_db.length == 0)
{
m_db.length= (thd->db ? strlen(thd->db) : 0);
......@@ -263,10 +266,19 @@ sp_head::init_strings(THD *thd, LEX *lex, sp_name *name)
name->init_qname(thd);
m_qname.length= name->m_qname.length;
m_qname.str= strmake_root(root, name->m_qname.str, m_qname.length);
}
else
{
m_db.length= (thd->db ? strlen(thd->db) : 0);
m_db.str= strmake_root(root, (thd->db ? thd->db : ""), m_db.length);
}
m_params.length= m_param_end- m_param_begin;
if (m_param_begin && m_param_end)
{
m_params.length= m_param_end - m_param_begin;
m_params.str= strmake_root(root,
(char *)m_param_begin, m_params.length);
}
if (m_returns_begin && m_returns_end)
{
/* QQ KLUDGE: We can't seem to cut out just the type in the parser
......@@ -508,8 +520,10 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
thd->spcont= nctx;
ret= execute(thd);
if (ret == 0)
if (m_type == TYPE_ENUM_FUNCTION && ret == 0)
{
/* We need result only in function but not in trigger */
Item *it= nctx->get_result();
if (it)
......@@ -683,6 +697,9 @@ sp_head::reset_lex(THD *thd)
/* And keep the SP stuff too */
sublex->sphead= oldlex->sphead;
sublex->spcont= oldlex->spcont;
/* And trigger related stuff too */
sublex->trg_chistics= oldlex->trg_chistics;
sublex->trg_table= oldlex->trg_table;
mysql_init_query(thd, true); // Only init lex
sublex->sp_lex_in_use= FALSE;
DBUG_VOID_RETURN;
......@@ -1040,6 +1057,60 @@ sp_instr_set::print(String *str)
m_value->print(str);
}
//
// sp_instr_set_user_var
//
int
sp_instr_set_user_var::execute(THD *thd, uint *nextp)
{
int res= 0;
DBUG_ENTER("sp_instr_set_user_var::execute");
/*
It is ok to pass 0 as 3rd argument to fix_fields() since
Item_func_set_user_var::fix_fields() won't use it.
QQ: Still unsure what should we return in case of error 1 or -1 ?
*/
if (!m_set_var_item.fixed && m_set_var_item.fix_fields(thd, 0, 0) ||
m_set_var_item.check() || m_set_var_item.update())
res= -1;
*nextp= m_ip + 1;
DBUG_RETURN(res);
}
void
sp_instr_set_user_var::print(String *str)
{
m_set_var_item.print_as_stmt(str);
}
//
// sp_instr_set_trigger_field
//
int
sp_instr_set_trigger_field::execute(THD *thd, uint *nextp)
{
int res= 0;
DBUG_ENTER("sp_instr_set_trigger_field::execute");
/* QQ: Still unsure what should we return in case of error 1 or -1 ? */
if (!value->fixed && value->fix_fields(thd, 0, &value) ||
trigger_field.fix_fields(thd, 0, 0) ||
(value->save_in_field(trigger_field.field, 0) < 0))
res= -1;
*nextp= m_ip + 1;
DBUG_RETURN(res);
}
void
sp_instr_set_trigger_field::print(String *str)
{
str->append("set ", 4);
trigger_field.print(str);
str->append(":=", 2);
value->print(str);
}
//
// sp_instr_jump
//
......
......@@ -28,6 +28,7 @@
// in the CREATE TABLE command.
#define TYPE_ENUM_FUNCTION 1
#define TYPE_ENUM_PROCEDURE 2
#define TYPE_ENUM_TRIGGER 3
Item_result
sp_map_result_type(enum enum_field_types type);
......@@ -342,6 +343,71 @@ private:
}; // class sp_instr_set : public sp_instr
/*
Set user variable instruction.
Used in functions and triggers to set user variables because we don't
want use sp_instr_stmt + "SET @a:=..." statement in this case since
latter will close all tables and thus will ruin execution of statement
calling/invoking this function/trigger.
*/
class sp_instr_set_user_var : public sp_instr
{
sp_instr_set_user_var(const sp_instr_set_user_var &);
void operator=(sp_instr_set_user_var &);
public:
sp_instr_set_user_var(uint ip, LEX_STRING var, Item *val)
: sp_instr(ip), m_set_var_item(var, val)
{}
virtual ~sp_instr_set_user_var()
{}
virtual int execute(THD *thd, uint *nextp);
virtual void print(String *str);
private:
Item_func_set_user_var m_set_var_item;
}; // class sp_instr_set_user_var : public sp_instr
/*
Set NEW/OLD row field value instruction. Used in triggers.
*/
class sp_instr_set_trigger_field : public sp_instr
{
sp_instr_set_trigger_field(const sp_instr_set_trigger_field &);
void operator=(sp_instr_set_trigger_field &);
public:
sp_instr_set_trigger_field(uint ip, LEX_STRING field_name, Item *val)
: sp_instr(ip),
trigger_field(Item_trigger_field::NEW_ROW, field_name.str),
value(val)
{}
virtual ~sp_instr_set_trigger_field()
{}
virtual int execute(THD *thd, uint *nextp);
virtual void print(String *str);
bool setup_field(THD *thd, TABLE *table, enum trg_event_type event)
{
return trigger_field.setup_field(thd, table, event);
}
private:
Item_trigger_field trigger_field;
Item *value;
}; // class sp_instr_trigger_field : public sp_instr
class sp_instr_jump : public sp_instr
{
sp_instr_jump(const sp_instr_jump &); /* Prevent use of these */
......
......@@ -20,6 +20,8 @@
#include "mysql_priv.h"
#include "sql_acl.h"
#include "sql_select.h"
#include "sp_head.h"
#include "sql_trigger.h"
#include <m_ctype.h>
#include <my_dir.h>
#include <hash.h>
......@@ -41,10 +43,6 @@ static my_bool open_new_frm(const char *path, const char *alias,
uint db_stat, uint prgflag,
uint ha_open_flags, TABLE *outparam,
TABLE_LIST *table_desc, MEM_ROOT *mem_root);
static Field *find_field_in_real_table(THD *thd, TABLE *table,
const char *name, uint length,
bool check_grants, bool allow_rowid,
uint *cached_field_index_ptr);
extern "C" byte *table_cache_key(const byte *record,uint *length,
my_bool not_used __attribute__((unused)))
......@@ -209,6 +207,7 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *wild)
void intern_close_table(TABLE *table)
{ // Free all structures
free_io_cache(table);
delete table->triggers;
if (table->file)
VOID(closefrm(table)); // close file
}
......@@ -790,6 +789,7 @@ TABLE *reopen_name_locked_table(THD* thd, TABLE_LIST* table_list)
!(table->table_cache_key =memdup_root(&table->mem_root,(char*) key,
key_length)))
{
delete table->triggers;
closefrm(table);
pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(0);
......@@ -1053,6 +1053,7 @@ bool reopen_table(TABLE *table,bool locked)
if (!(tmp.table_cache_key= memdup_root(&tmp.mem_root,db,
table->key_length)))
{
delete tmp.triggers;
closefrm(&tmp); // End of memory
goto end;
}
......@@ -1081,6 +1082,7 @@ bool reopen_table(TABLE *table,bool locked)
tmp.next= table->next;
tmp.prev= table->prev;
delete table->triggers;
if (table->file)
VOID(closefrm(table)); // close file, free everything
......@@ -1469,6 +1471,9 @@ static int open_unireg_entry(THD *thd, TABLE *entry, const char *db,
if (error == 5)
DBUG_RETURN(0); // we have just opened VIEW
if (Table_triggers_list::check_n_load(thd, db, name, entry))
goto err;
/*
If we are here, there was no fatal error (but error may be still
unitialized).
......@@ -1497,6 +1502,7 @@ static int open_unireg_entry(THD *thd, TABLE *entry, const char *db,
*/
sql_print_error("Error: when opening HEAP table, could not allocate \
memory to write 'DELETE FROM `%s`.`%s`' to the binary log",db,name);
delete entry->triggers;
if (entry->file)
closefrm(entry);
goto err;
......@@ -1998,7 +2004,7 @@ Field *find_field_in_table(THD *thd, TABLE_LIST *table_list,
# pointer to field
*/
static Field *find_field_in_real_table(THD *thd, TABLE *table,
Field *find_field_in_real_table(THD *thd, TABLE *table,
const char *name, uint length,
bool check_grants, bool allow_rowid,
uint *cached_field_index_ptr)
......
......@@ -26,6 +26,8 @@
#include "mysql_priv.h"
#include "ha_innodb.h"
#include "sql_select.h"
#include "sp_head.h"
#include "sql_trigger.h"
int mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SQL_LIST *order,
ha_rows limit, ulong options)
......@@ -160,6 +162,11 @@ int mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SQL_LIST *order,
// thd->net.report_error is tested to disallow delete row on error
if (!(select && select->skip_record())&& !thd->net.report_error )
{
if (table->triggers)
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_BEFORE);
if (!(error=table->file->delete_row(table->record[0])))
{
deleted++;
......@@ -183,6 +190,10 @@ int mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SQL_LIST *order,
error= 1;
break;
}
if (table->triggers)
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_AFTER);
}
else
table->file->unlock_row(); // Row failed selection, release lock on it
......
......@@ -19,6 +19,8 @@
#include "mysql_priv.h"
#include "sql_acl.h"
#include "sp_head.h"
#include "sql_trigger.h"
static int check_null_fields(THD *thd,TABLE *entry);
#ifndef EMBEDDED_LIBRARY
......@@ -299,6 +301,12 @@ int mysql_insert(THD *thd,TABLE_LIST *table_list,
break;
}
}
// FIXME: Actually we should do this before check_null_fields.
// Or even go into write_record ?
if (table->triggers)
table->triggers->process_triggers(thd, TRG_EVENT_INSERT, TRG_ACTION_BEFORE);
#ifndef EMBEDDED_LIBRARY
if (lock_type == TL_WRITE_DELAYED)
{
......@@ -321,6 +329,9 @@ int mysql_insert(THD *thd,TABLE_LIST *table_list,
id= thd->last_insert_id;
}
thd->row_count++;
if (table->triggers)
table->triggers->process_triggers(thd, TRG_EVENT_INSERT, TRG_ACTION_AFTER);
}
/*
......
......@@ -24,6 +24,12 @@
#include "sp.h"
#include "sp_head.h"
/*
We are using pointer to this variable for distinguishing between assignment
to NEW row field (when parsing trigger definition) and structured variable.
*/
sys_var_long_ptr trg_new_row_fake_var(0, 0);
/* Macros to look like lex */
#define yyGet() *(lex->ptr++)
......@@ -130,6 +136,7 @@ LEX *lex_start(THD *thd, uchar *buf,uint length)
lex->duplicates= DUP_ERROR;
lex->sphead= NULL;
lex->spcont= NULL;
lex->trg_table= NULL;
extern byte *sp_lex_spfuns_key(const byte *ptr, uint *plen, my_bool first);
hash_free(&lex->spfuns);
......
......@@ -85,6 +85,7 @@ enum enum_sql_command {
SQLCOM_SHOW_STATUS_PROC, SQLCOM_SHOW_STATUS_FUNC,
SQLCOM_PREPARE, SQLCOM_EXECUTE, SQLCOM_DEALLOCATE_PREPARE,
SQLCOM_CREATE_VIEW, SQLCOM_DROP_VIEW,
SQLCOM_CREATE_TRIGGER, SQLCOM_DROP_TRIGGER,
/* This should be the last !!! */
SQLCOM_END
};
......@@ -606,6 +607,15 @@ struct st_sp_chistics
bool detistic;
};
struct st_trg_chistics
{
enum trg_action_time_type action_time;
enum trg_event_type event;
};
extern sys_var_long_ptr trg_new_row_fake_var;
/* The state of the lex parsing. This is saved in the THD struct */
typedef struct st_lex
......@@ -710,6 +720,14 @@ typedef struct st_lex
rexecuton
*/
bool empty_field_list_on_rset;
/* Characterstics of trigger being created */
st_trg_chistics trg_chistics;
/*
Points to table being opened when we are parsing trigger definition
while opening table. 0 if we are parsing user provided CREATE TRIGGER
or any other statement. Used for NEW/OLD row field lookup in trigger.
*/
TABLE *trg_table;
st_lex()
{
......
......@@ -3828,6 +3828,20 @@ purposes internal to the MySQL server", MYF(0));
res= mysql_drop_view(thd, first_table, thd->lex->drop_mode);
break;
}
case SQLCOM_CREATE_TRIGGER:
{
/* We don't care much about trigger body at that point */
delete lex->sphead;
lex->sphead= 0;
res= mysql_create_or_drop_trigger(thd, all_tables, 1);
break;
}
case SQLCOM_DROP_TRIGGER:
{
res= mysql_create_or_drop_trigger(thd, all_tables, 0);
break;
}
default: /* Impossible */
send_ok(thd);
break;
......
......@@ -1509,7 +1509,7 @@ static void wait_while_table_is_used(THD *thd,TABLE *table,
Win32 clients must also have a WRITE LOCK on the table !
*/
static bool close_cached_table(THD *thd, TABLE *table)
void close_cached_table(THD *thd, TABLE *table)
{
DBUG_ENTER("close_cached_table");
......@@ -1525,7 +1525,6 @@ static bool close_cached_table(THD *thd, TABLE *table)
/* When lock on LOCK_open is freed other threads can continue */
pthread_cond_broadcast(&COND_refresh);
DBUG_RETURN(0);
}
static int send_check_errmsg(THD *thd, TABLE_LIST* table,
......@@ -3130,12 +3129,7 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name,
close the original table at before doing the rename
*/
table_name=thd->strdup(table_name); // must be saved
if (close_cached_table(thd, table))
{ // Aborted
VOID(quick_rm_table(new_db_type,new_db,tmp_name));
VOID(pthread_mutex_unlock(&LOCK_open));
goto err;
}
close_cached_table(thd, table);
table=0; // Marker that table is closed
}
#if (!defined( __WIN__) && !defined( __EMX__) && !defined( OS2))
......
This diff is collapsed.
/*
This class holds all information about triggers of table.
QQ: Will it be merged into TABLE in future ?
*/
class Table_triggers_list: public Sql_alloc
{
/* Triggers as SPs grouped by event, action_time */
sp_head *bodies[3][2];
/*
Copy of TABLE::Field array with field pointers set to old version
of record, used for OLD values in trigger on UPDATE.
*/
Field **old_field;
/*
Names of triggers.
Should correspond to order of triggers on definitions_list,
used in CREATE/DROP TRIGGER for looking up trigger by name.
*/
List<LEX_STRING> names_list;
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;
Table_triggers_list():
old_field(0)
{
bzero((char *)bodies, sizeof(bodies));
}
~Table_triggers_list();
bool create_trigger(THD *thd, TABLE_LIST *table);
bool drop_trigger(THD *thd, TABLE_LIST *table);
bool process_triggers(THD *thd, trg_event_type event,
trg_action_time_type time_type)
{
int res= 0;
if (bodies[event][time_type])
{
/*
Similar to function invocation we don't need to surpress sending of
ok packets here because don't allow execute statements from trigger.
FIXME: We should juggle with security context here (because trigger
should be invoked with creator rights).
*/
res= bodies[event][time_type]->execute_function(thd, 0, 0, 0);
}
return res;
}
static bool check_n_load(THD *thd, const char *db, const char *table_name,
TABLE *table);
friend class Item_trigger_field;
};
......@@ -23,6 +23,8 @@
#include "mysql_priv.h"
#include "sql_acl.h"
#include "sql_select.h"
#include "sp_head.h"
#include "sql_trigger.h"
static bool safe_update_on_fly(JOIN_TAB *join_tab, List<Item> *fields);
......@@ -341,6 +343,10 @@ int mysql_update(THD *thd,
if (fill_record(fields,values, 0) || thd->net.report_error)
break; /* purecov: inspected */
found++;
if (table->triggers)
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_BEFORE);
if (compare_record(table, query_id))
{
if (!(error=table->file->update_row((byte*) table->record[1],
......@@ -356,6 +362,10 @@ int mysql_update(THD *thd,
break;
}
}
if (table->triggers)
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_AFTER);
if (!--limit && using_limit)
{
error= -1; // Simulate end of file
......
This diff is collapsed.
......@@ -63,6 +63,7 @@ typedef struct st_filesort_info
class Field_timestamp;
class Field_blob;
class Table_triggers_list;
struct st_table {
handler *file;
......@@ -144,6 +145,8 @@ struct st_table {
REGINFO reginfo; /* field connections */
MEM_ROOT mem_root;
GRANT_INFO grant;
/* Table's triggers, 0 if there are no of them */
Table_triggers_list *triggers;
char *table_cache_key;
char *table_name,*real_name,*path;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment