Manual merge of patch fixing several trigger related bugs with main tree.
sql/item.cc: Auto merged sql/item.h: Auto merged sql/mysql_priv.h: Auto merged sql/sql_base.cc: Auto merged sql/sql_delete.cc: Auto merged sql/sql_insert.cc: Manual merge sql/sql_update.cc: Manual merge
This commit is contained in:
commit
896786eadd
@ -140,6 +140,48 @@ drop trigger t1.trg1;
|
|||||||
drop trigger t1.trg2;
|
drop trigger t1.trg2;
|
||||||
drop trigger t1.trg3;
|
drop trigger t1.trg3;
|
||||||
drop table t1;
|
drop table t1;
|
||||||
|
create table t1 (id int not null primary key, data int);
|
||||||
|
create trigger t1_bi before insert on t1 for each row
|
||||||
|
set @log:= concat(@log, "(BEFORE_INSERT: new=(id=", new.id, ", data=", new.data,"))");
|
||||||
|
create trigger t1_ai after insert on t1 for each row
|
||||||
|
set @log:= concat(@log, "(AFTER_INSERT: new=(id=", new.id, ", data=", new.data,"))");
|
||||||
|
create trigger t1_bu before update on t1 for each row
|
||||||
|
set @log:= concat(@log, "(BEFORE_UPDATE: old=(id=", old.id, ", data=", old.data,
|
||||||
|
") new=(id=", new.id, ", data=", new.data,"))");
|
||||||
|
create trigger t1_au after update on t1 for each row
|
||||||
|
set @log:= concat(@log, "(AFTER_UPDATE: old=(id=", old.id, ", data=", old.data,
|
||||||
|
") new=(id=", new.id, ", data=", new.data,"))");
|
||||||
|
create trigger t1_bd before delete on t1 for each row
|
||||||
|
set @log:= concat(@log, "(BEFORE_DELETE: old=(id=", old.id, ", data=", old.data,"))");
|
||||||
|
create trigger t1_ad after delete on t1 for each row
|
||||||
|
set @log:= concat(@log, "(AFTER_DELETE: old=(id=", old.id, ", data=", old.data,"))");
|
||||||
|
set @log:= "";
|
||||||
|
insert into t1 values (1, 1);
|
||||||
|
select @log;
|
||||||
|
@log
|
||||||
|
(BEFORE_INSERT: new=(id=1, data=1))(AFTER_INSERT: new=(id=1, data=1))
|
||||||
|
set @log:= "";
|
||||||
|
insert ignore t1 values (1, 2);
|
||||||
|
select @log;
|
||||||
|
@log
|
||||||
|
(BEFORE_INSERT: new=(id=1, data=2))
|
||||||
|
set @log:= "";
|
||||||
|
replace t1 values (1, 3), (2, 2);
|
||||||
|
select @log;
|
||||||
|
@log
|
||||||
|
(BEFORE_INSERT: new=(id=1, data=3))(BEFORE_UPDATE: old=(id=1, data=1) new=(id=1, data=3))(AFTER_UPDATE: old=(id=1, data=1) new=(id=1, data=3))(BEFORE_INSERT: new=(id=2, data=2))(AFTER_INSERT: new=(id=2, data=2))
|
||||||
|
alter table t1 add ts timestamp default now();
|
||||||
|
set @log:= "";
|
||||||
|
replace t1 (id, data) values (1, 4);
|
||||||
|
select @log;
|
||||||
|
@log
|
||||||
|
(BEFORE_INSERT: new=(id=1, data=4))(BEFORE_DELETE: old=(id=1, data=3))(AFTER_DELETE: old=(id=1, data=3))(AFTER_INSERT: new=(id=1, data=4))
|
||||||
|
set @log:= "";
|
||||||
|
insert into t1 (id, data) values (1, 5), (3, 3) on duplicate key update data= data + 2;
|
||||||
|
select @log;
|
||||||
|
@log
|
||||||
|
(BEFORE_INSERT: new=(id=1, data=5))(BEFORE_UPDATE: old=(id=1, data=4) new=(id=1, data=6))(AFTER_UPDATE: old=(id=1, data=4) new=(id=1, data=6))(BEFORE_INSERT: new=(id=3, data=3))(AFTER_INSERT: new=(id=3, data=3))
|
||||||
|
drop table t1;
|
||||||
create table t1 (i int);
|
create table t1 (i int);
|
||||||
create trigger trg before insert on t1 for each row set @a:= old.i;
|
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
|
ERROR HY000: There is no OLD row in on INSERT trigger
|
||||||
@ -206,3 +248,70 @@ create table t1 (i int);
|
|||||||
create trigger trg1 before insert on t1 for each row set @a:= 1;
|
create trigger trg1 before insert on t1 for each row set @a:= 1;
|
||||||
drop database mysqltest;
|
drop database mysqltest;
|
||||||
use test;
|
use test;
|
||||||
|
create table t1 (i int, j int default 10, k int not null, key (k));
|
||||||
|
create table t2 (i int);
|
||||||
|
insert into t1 (i, k) values (1, 1);
|
||||||
|
insert into t2 values (1);
|
||||||
|
create trigger trg1 before update on t1 for each row set @a:= @a + new.j - old.j;
|
||||||
|
create trigger trg2 after update on t1 for each row set @b:= "Fired";
|
||||||
|
set @a:= 0, @b:= "";
|
||||||
|
update t1, t2 set j = j + 10 where t1.i = t2.i;
|
||||||
|
select @a, @b;
|
||||||
|
@a @b
|
||||||
|
10 Fired
|
||||||
|
insert into t1 values (2, 13, 2);
|
||||||
|
insert into t2 values (2);
|
||||||
|
set @a:= 0, @b:= "";
|
||||||
|
update t1, t2 set j = j + 15 where t1.i = t2.i and t1.k >= 2;
|
||||||
|
select @a, @b;
|
||||||
|
@a @b
|
||||||
|
15 Fired
|
||||||
|
create trigger trg3 before delete on t1 for each row set @c:= @c + old.j;
|
||||||
|
create trigger trg4 before delete on t2 for each row set @d:= @d + old.i;
|
||||||
|
create trigger trg5 after delete on t1 for each row set @e:= "After delete t1 fired";
|
||||||
|
create trigger trg6 after delete on t2 for each row set @f:= "After delete t2 fired";
|
||||||
|
set @c:= 0, @d:= 0, @e:= "", @f:= "";
|
||||||
|
delete t1, t2 from t1, t2 where t1.i = t2.i;
|
||||||
|
select @c, @d, @e, @f;
|
||||||
|
@c @d @e @f
|
||||||
|
48 3 After delete t1 fired After delete t2 fired
|
||||||
|
drop table t1, t2;
|
||||||
|
create table t1 (i int, j int default 10)|
|
||||||
|
create table t2 (i int)|
|
||||||
|
insert into t2 values (1), (2)|
|
||||||
|
create trigger trg1 before insert on t1 for each row
|
||||||
|
begin
|
||||||
|
if new.i = 1 then
|
||||||
|
set new.j := 1;
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
create trigger trg2 after insert on t1 for each row set @a:= 1|
|
||||||
|
set @a:= 0|
|
||||||
|
insert into t1 (i) select * from t2|
|
||||||
|
select * from t1|
|
||||||
|
i j
|
||||||
|
1 1
|
||||||
|
2 10
|
||||||
|
select @a|
|
||||||
|
@a
|
||||||
|
1
|
||||||
|
drop table t1, t2|
|
||||||
|
create table t1 (i int, j int, k int);
|
||||||
|
create trigger trg1 before insert on t1 for each row set new.k = new.i;
|
||||||
|
create trigger trg2 after insert on t1 for each row set @b:= "Fired";
|
||||||
|
set @b:="";
|
||||||
|
load data infile '../../std_data/rpl_loaddata.dat' into table t1 (@a, i);
|
||||||
|
select *, @b from t1;
|
||||||
|
i j k @b
|
||||||
|
10 NULL 10 Fired
|
||||||
|
15 NULL 15 Fired
|
||||||
|
set @b:="";
|
||||||
|
load data infile '../../std_data/loaddata5.dat' into table t1 fields terminated by '' enclosed by '' (i, j);
|
||||||
|
select *, @b from t1;
|
||||||
|
i j k @b
|
||||||
|
10 NULL 10 Fired
|
||||||
|
15 NULL 15 Fired
|
||||||
|
1 2 1 Fired
|
||||||
|
3 4 3 Fired
|
||||||
|
5 6 5 Fired
|
||||||
|
drop table t1;
|
||||||
|
@ -150,6 +150,55 @@ drop trigger t1.trg3;
|
|||||||
drop table t1;
|
drop table t1;
|
||||||
|
|
||||||
|
|
||||||
|
# Let us test how triggers work for special forms of INSERT such as
|
||||||
|
# REPLACE and INSERT ... ON DUPLICATE KEY UPDATE
|
||||||
|
create table t1 (id int not null primary key, data int);
|
||||||
|
create trigger t1_bi before insert on t1 for each row
|
||||||
|
set @log:= concat(@log, "(BEFORE_INSERT: new=(id=", new.id, ", data=", new.data,"))");
|
||||||
|
create trigger t1_ai after insert on t1 for each row
|
||||||
|
set @log:= concat(@log, "(AFTER_INSERT: new=(id=", new.id, ", data=", new.data,"))");
|
||||||
|
create trigger t1_bu before update on t1 for each row
|
||||||
|
set @log:= concat(@log, "(BEFORE_UPDATE: old=(id=", old.id, ", data=", old.data,
|
||||||
|
") new=(id=", new.id, ", data=", new.data,"))");
|
||||||
|
create trigger t1_au after update on t1 for each row
|
||||||
|
set @log:= concat(@log, "(AFTER_UPDATE: old=(id=", old.id, ", data=", old.data,
|
||||||
|
") new=(id=", new.id, ", data=", new.data,"))");
|
||||||
|
create trigger t1_bd before delete on t1 for each row
|
||||||
|
set @log:= concat(@log, "(BEFORE_DELETE: old=(id=", old.id, ", data=", old.data,"))");
|
||||||
|
create trigger t1_ad after delete on t1 for each row
|
||||||
|
set @log:= concat(@log, "(AFTER_DELETE: old=(id=", old.id, ", data=", old.data,"))");
|
||||||
|
# Simple INSERT - both triggers should be called
|
||||||
|
set @log:= "";
|
||||||
|
insert into t1 values (1, 1);
|
||||||
|
select @log;
|
||||||
|
# INSERT IGNORE for already existing key - only before trigger should fire
|
||||||
|
set @log:= "";
|
||||||
|
insert ignore t1 values (1, 2);
|
||||||
|
select @log;
|
||||||
|
# REPLACE: before insert trigger should be called for both records,
|
||||||
|
# but then for first one update will be executed (and both update
|
||||||
|
# triggers should fire). For second after insert trigger will be
|
||||||
|
# called as for usual insert
|
||||||
|
set @log:= "";
|
||||||
|
replace t1 values (1, 3), (2, 2);
|
||||||
|
select @log;
|
||||||
|
# Now let us change table in such way that REPLACE on won't be executed
|
||||||
|
# using update.
|
||||||
|
alter table t1 add ts timestamp default now();
|
||||||
|
set @log:= "";
|
||||||
|
# This REPLACE should be executed via DELETE and INSERT so proper
|
||||||
|
# triggers should be invoked.
|
||||||
|
replace t1 (id, data) values (1, 4);
|
||||||
|
select @log;
|
||||||
|
# Finally let us test INSERT ... ON DUPLICATE KEY UPDATE ...
|
||||||
|
set @log:= "";
|
||||||
|
insert into t1 (id, data) values (1, 5), (3, 3) on duplicate key update data= data + 2;
|
||||||
|
select @log;
|
||||||
|
|
||||||
|
# This also drops associated triggers
|
||||||
|
drop table t1;
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Test of wrong column specifiers in triggers
|
# Test of wrong column specifiers in triggers
|
||||||
#
|
#
|
||||||
@ -249,3 +298,72 @@ create trigger trg1 before insert on t1 for each row set @a:= 1;
|
|||||||
# This should succeed
|
# This should succeed
|
||||||
drop database mysqltest;
|
drop database mysqltest;
|
||||||
use test;
|
use test;
|
||||||
|
|
||||||
|
# Test for bug #5860 "Multi-table UPDATE does not activate update triggers"
|
||||||
|
# We will also test how delete triggers wor for multi-table DELETE.
|
||||||
|
create table t1 (i int, j int default 10, k int not null, key (k));
|
||||||
|
create table t2 (i int);
|
||||||
|
insert into t1 (i, k) values (1, 1);
|
||||||
|
insert into t2 values (1);
|
||||||
|
create trigger trg1 before update on t1 for each row set @a:= @a + new.j - old.j;
|
||||||
|
create trigger trg2 after update on t1 for each row set @b:= "Fired";
|
||||||
|
set @a:= 0, @b:= "";
|
||||||
|
# Check that trigger works in case of update on the fly
|
||||||
|
update t1, t2 set j = j + 10 where t1.i = t2.i;
|
||||||
|
select @a, @b;
|
||||||
|
insert into t1 values (2, 13, 2);
|
||||||
|
insert into t2 values (2);
|
||||||
|
set @a:= 0, @b:= "";
|
||||||
|
# And now let us check that triggers work in case of multi-update which
|
||||||
|
# is done through temporary tables...
|
||||||
|
update t1, t2 set j = j + 15 where t1.i = t2.i and t1.k >= 2;
|
||||||
|
select @a, @b;
|
||||||
|
# Let us test delete triggers for multi-delete now.
|
||||||
|
# We create triggers for both tables because we want test how they
|
||||||
|
# work in both on-the-fly and via-temp-tables cases.
|
||||||
|
create trigger trg3 before delete on t1 for each row set @c:= @c + old.j;
|
||||||
|
create trigger trg4 before delete on t2 for each row set @d:= @d + old.i;
|
||||||
|
create trigger trg5 after delete on t1 for each row set @e:= "After delete t1 fired";
|
||||||
|
create trigger trg6 after delete on t2 for each row set @f:= "After delete t2 fired";
|
||||||
|
set @c:= 0, @d:= 0, @e:= "", @f:= "";
|
||||||
|
delete t1, t2 from t1, t2 where t1.i = t2.i;
|
||||||
|
select @c, @d, @e, @f;
|
||||||
|
# This also will drop triggers
|
||||||
|
drop table t1, t2;
|
||||||
|
|
||||||
|
# Test for bug #6812 "Triggers are not activated for INSERT ... SELECT".
|
||||||
|
# (We also check the fact that trigger modifies some field does not affect
|
||||||
|
# value of next record inserted).
|
||||||
|
delimiter |;
|
||||||
|
create table t1 (i int, j int default 10)|
|
||||||
|
create table t2 (i int)|
|
||||||
|
insert into t2 values (1), (2)|
|
||||||
|
create trigger trg1 before insert on t1 for each row
|
||||||
|
begin
|
||||||
|
if new.i = 1 then
|
||||||
|
set new.j := 1;
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
create trigger trg2 after insert on t1 for each row set @a:= 1|
|
||||||
|
set @a:= 0|
|
||||||
|
insert into t1 (i) select * from t2|
|
||||||
|
select * from t1|
|
||||||
|
select @a|
|
||||||
|
# This also will drop triggers
|
||||||
|
drop table t1, t2|
|
||||||
|
delimiter ;|
|
||||||
|
|
||||||
|
# Test for bug #8755 "Trigger is not activated by LOAD DATA"
|
||||||
|
create table t1 (i int, j int, k int);
|
||||||
|
create trigger trg1 before insert on t1 for each row set new.k = new.i;
|
||||||
|
create trigger trg2 after insert on t1 for each row set @b:= "Fired";
|
||||||
|
set @b:="";
|
||||||
|
# Test triggers with file with separators
|
||||||
|
load data infile '../../std_data/rpl_loaddata.dat' into table t1 (@a, i);
|
||||||
|
select *, @b from t1;
|
||||||
|
set @b:="";
|
||||||
|
# Test triggers with fixed size row file
|
||||||
|
load data infile '../../std_data/loaddata5.dat' into table t1 fields terminated by '' enclosed by '' (i, j);
|
||||||
|
select *, @b from t1;
|
||||||
|
# This also will drop triggers
|
||||||
|
drop table t1;
|
||||||
|
43
sql/item.cc
43
sql/item.cc
@ -4554,40 +4554,40 @@ void Item_insert_value::print(String *str)
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Bind item representing field of row being changed in trigger
|
Find index of Field object which will be appropriate for item
|
||||||
to appropriate Field object.
|
representing field of row being changed in trigger.
|
||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
setup_field()
|
setup_field()
|
||||||
thd - current thread context
|
thd - current thread context
|
||||||
table - table of trigger (and where we looking for fields)
|
table - table of trigger (and where we looking for fields)
|
||||||
event - type of trigger event
|
|
||||||
|
|
||||||
NOTE
|
NOTE
|
||||||
This function does almost the same as fix_fields() for Item_field
|
This function does almost the same as fix_fields() for Item_field
|
||||||
but is invoked during trigger definition parsing and takes TABLE
|
but is invoked right after trigger definition parsing. Since at
|
||||||
object as its argument. If proper field was not found in table
|
this stage we can't say exactly what Field object (corresponding
|
||||||
error will be reported at fix_fields() time.
|
to TABLE::record[0] or TABLE::record[1]) should be bound to this
|
||||||
|
Item, we only find out index of the Field and then select concrete
|
||||||
|
Field object in fix_fields() (by that time Table_trigger_list::old_field/
|
||||||
|
new_field should point to proper array of Fields).
|
||||||
|
It also binds Item_trigger_field to Table_triggers_list object for
|
||||||
|
table of trigger which uses this item.
|
||||||
*/
|
*/
|
||||||
void Item_trigger_field::setup_field(THD *thd, TABLE *table,
|
|
||||||
enum trg_event_type event)
|
void Item_trigger_field::setup_field(THD *thd, TABLE *table)
|
||||||
{
|
{
|
||||||
uint field_idx= (uint)-1;
|
|
||||||
bool save_set_query_id= thd->set_query_id;
|
bool save_set_query_id= thd->set_query_id;
|
||||||
|
|
||||||
/* TODO: Think more about consequences of this step. */
|
/* TODO: Think more about consequences of this step. */
|
||||||
thd->set_query_id= 0;
|
thd->set_query_id= 0;
|
||||||
|
/*
|
||||||
if (find_field_in_real_table(thd, table, field_name,
|
Try to find field by its name and if it will be found
|
||||||
strlen(field_name), 0, 0,
|
set field_idx properly.
|
||||||
&field_idx))
|
*/
|
||||||
{
|
(void)find_field_in_real_table(thd, table, field_name, strlen(field_name),
|
||||||
field= (row_version == OLD_ROW && event == TRG_EVENT_UPDATE) ?
|
0, 0, &field_idx);
|
||||||
table->triggers->old_field[field_idx] :
|
|
||||||
table->field[field_idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
thd->set_query_id= save_set_query_id;
|
thd->set_query_id= save_set_query_id;
|
||||||
|
triggers= table->triggers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -4612,9 +4612,10 @@ bool Item_trigger_field::fix_fields(THD *thd,
|
|||||||
*/
|
*/
|
||||||
DBUG_ASSERT(fixed == 0);
|
DBUG_ASSERT(fixed == 0);
|
||||||
|
|
||||||
if (field)
|
if (field_idx != (uint)-1)
|
||||||
{
|
{
|
||||||
// QQ: May be this should be moved to setup_field?
|
field= (row_version == OLD_ROW) ? triggers->old_field[field_idx] :
|
||||||
|
triggers->new_field[field_idx];
|
||||||
set_field(field);
|
set_field(field);
|
||||||
fixed= 1;
|
fixed= 1;
|
||||||
return 0;
|
return 0;
|
||||||
|
19
sql/item.h
19
sql/item.h
@ -1632,13 +1632,18 @@ enum trg_event_type
|
|||||||
TRG_EVENT_INSERT= 0 , TRG_EVENT_UPDATE= 1, TRG_EVENT_DELETE= 2
|
TRG_EVENT_INSERT= 0 , TRG_EVENT_UPDATE= 1, TRG_EVENT_DELETE= 2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Table_triggers_list;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Represents NEW/OLD version of field of row which is
|
Represents NEW/OLD version of field of row which is
|
||||||
changed/read in trigger.
|
changed/read in trigger.
|
||||||
|
|
||||||
Note: For this item actual binding to Field object happens not during
|
Note: For this item main part of actual binding to Field object happens
|
||||||
fix_fields() (like for Item_field) but during parsing of trigger
|
not during fix_fields() call (like for Item_field) but right after
|
||||||
definition, when table is opened, with special setup_field() call.
|
parsing of trigger definition, when table is opened, with special
|
||||||
|
setup_field() call. On fix_fields() stage we simply choose one of
|
||||||
|
two Field instances representing either OLD or NEW version of this
|
||||||
|
field.
|
||||||
*/
|
*/
|
||||||
class Item_trigger_field : public Item_field
|
class Item_trigger_field : public Item_field
|
||||||
{
|
{
|
||||||
@ -1648,13 +1653,17 @@ public:
|
|||||||
row_version_type row_version;
|
row_version_type row_version;
|
||||||
/* Next in list of all Item_trigger_field's in trigger */
|
/* Next in list of all Item_trigger_field's in trigger */
|
||||||
Item_trigger_field *next_trg_field;
|
Item_trigger_field *next_trg_field;
|
||||||
|
/* Index of the field in the TABLE::field array */
|
||||||
|
uint field_idx;
|
||||||
|
/* Pointer to Table_trigger_list object for table of this trigger */
|
||||||
|
Table_triggers_list *triggers;
|
||||||
|
|
||||||
Item_trigger_field(row_version_type row_ver_par,
|
Item_trigger_field(row_version_type row_ver_par,
|
||||||
const char *field_name_par):
|
const char *field_name_par):
|
||||||
Item_field((const char *)NULL, (const char *)NULL, field_name_par),
|
Item_field((const char *)NULL, (const char *)NULL, field_name_par),
|
||||||
row_version(row_ver_par)
|
row_version(row_ver_par), field_idx((uint)-1)
|
||||||
{}
|
{}
|
||||||
void setup_field(THD *thd, TABLE *table, enum trg_event_type event);
|
void setup_field(THD *thd, TABLE *table);
|
||||||
enum Type type() const { return TRIGGER_FIELD_ITEM; }
|
enum Type type() const { return TRIGGER_FIELD_ITEM; }
|
||||||
bool eq(const Item *item, bool binary_cmp) const;
|
bool eq(const Item *item, bool binary_cmp) const;
|
||||||
bool fix_fields(THD *, struct st_table_list *, Item **);
|
bool fix_fields(THD *, struct st_table_list *, Item **);
|
||||||
|
@ -924,10 +924,18 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table,
|
|||||||
bool return_if_owned_by_thd);
|
bool return_if_owned_by_thd);
|
||||||
bool close_cached_tables(THD *thd, bool wait_for_refresh, TABLE_LIST *tables);
|
bool close_cached_tables(THD *thd, bool wait_for_refresh, TABLE_LIST *tables);
|
||||||
void copy_field_from_tmp_record(Field *field,int offset);
|
void copy_field_from_tmp_record(Field *field,int offset);
|
||||||
bool fill_record(THD *thd, List<Item> &fields, List<Item> &values,
|
|
||||||
bool ignore_errors);
|
|
||||||
bool fill_record(THD *thd, Field **field, List<Item> &values,
|
bool fill_record(THD *thd, Field **field, List<Item> &values,
|
||||||
bool ignore_errors);
|
bool ignore_errors);
|
||||||
|
bool fill_record_n_invoke_before_triggers(THD *thd, List<Item> &fields,
|
||||||
|
List<Item> &values,
|
||||||
|
bool ignore_errors,
|
||||||
|
Table_triggers_list *triggers,
|
||||||
|
enum trg_event_type event);
|
||||||
|
bool fill_record_n_invoke_before_triggers(THD *thd, Field **field,
|
||||||
|
List<Item> &values,
|
||||||
|
bool ignore_errors,
|
||||||
|
Table_triggers_list *triggers,
|
||||||
|
enum trg_event_type event);
|
||||||
OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *wild);
|
OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *wild);
|
||||||
|
|
||||||
inline TABLE_LIST *find_table_in_global_list(TABLE_LIST *table,
|
inline TABLE_LIST *find_table_in_global_list(TABLE_LIST *table,
|
||||||
|
@ -3813,7 +3813,7 @@ err_no_arena:
|
|||||||
TRUE error occured
|
TRUE error occured
|
||||||
*/
|
*/
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
fill_record(THD * thd, List<Item> &fields, List<Item> &values,
|
fill_record(THD * thd, List<Item> &fields, List<Item> &values,
|
||||||
bool ignore_errors)
|
bool ignore_errors)
|
||||||
{
|
{
|
||||||
@ -3839,6 +3839,41 @@ fill_record(THD * thd, List<Item> &fields, List<Item> &values,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Fill fields in list with values from the list of items and invoke
|
||||||
|
before triggers.
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
fill_record_n_invoke_before_triggers()
|
||||||
|
thd thread context
|
||||||
|
fields Item_fields list to be filled
|
||||||
|
values values to fill with
|
||||||
|
ignore_errors TRUE if we should ignore errors
|
||||||
|
triggers object holding list of triggers to be invoked
|
||||||
|
event event type for triggers to be invoked
|
||||||
|
|
||||||
|
NOTE
|
||||||
|
This function assumes that fields which values will be set and triggers
|
||||||
|
to be invoked belong to the same table, and that TABLE::record[0] and
|
||||||
|
record[1] buffers correspond to new and old versions of row respectively.
|
||||||
|
|
||||||
|
RETURN
|
||||||
|
FALSE OK
|
||||||
|
TRUE error occured
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool
|
||||||
|
fill_record_n_invoke_before_triggers(THD *thd, List<Item> &fields,
|
||||||
|
List<Item> &values, bool ignore_errors,
|
||||||
|
Table_triggers_list *triggers,
|
||||||
|
enum trg_event_type event)
|
||||||
|
{
|
||||||
|
return (fill_record(thd, fields, values, ignore_errors) ||
|
||||||
|
triggers && triggers->process_triggers(thd, event,
|
||||||
|
TRG_ACTION_BEFORE, TRUE));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Fill field buffer with values from Field list
|
Fill field buffer with values from Field list
|
||||||
|
|
||||||
@ -3875,6 +3910,41 @@ fill_record(THD *thd, Field **ptr, List<Item> &values, bool ignore_errors)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Fill fields in array with values from the list of items and invoke
|
||||||
|
before triggers.
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
fill_record_n_invoke_before_triggers()
|
||||||
|
thd thread context
|
||||||
|
ptr NULL-ended array of fields to be filled
|
||||||
|
values values to fill with
|
||||||
|
ignore_errors TRUE if we should ignore errors
|
||||||
|
triggers object holding list of triggers to be invoked
|
||||||
|
event event type for triggers to be invoked
|
||||||
|
|
||||||
|
NOTE
|
||||||
|
This function assumes that fields which values will be set and triggers
|
||||||
|
to be invoked belong to the same table, and that TABLE::record[0] and
|
||||||
|
record[1] buffers correspond to new and old versions of row respectively.
|
||||||
|
|
||||||
|
RETURN
|
||||||
|
FALSE OK
|
||||||
|
TRUE error occured
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool
|
||||||
|
fill_record_n_invoke_before_triggers(THD *thd, Field **ptr,
|
||||||
|
List<Item> &values, bool ignore_errors,
|
||||||
|
Table_triggers_list *triggers,
|
||||||
|
enum trg_event_type event)
|
||||||
|
{
|
||||||
|
return (fill_record(thd, ptr, values, ignore_errors) ||
|
||||||
|
triggers && triggers->process_triggers(thd, event,
|
||||||
|
TRG_ACTION_BEFORE, TRUE));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void mysql_rm_tmp_tables(void)
|
static void mysql_rm_tmp_tables(void)
|
||||||
{
|
{
|
||||||
uint i, idx;
|
uint i, idx;
|
||||||
|
@ -176,13 +176,24 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
|
|||||||
if (!(select && select->skip_record())&& !thd->net.report_error )
|
if (!(select && select->skip_record())&& !thd->net.report_error )
|
||||||
{
|
{
|
||||||
|
|
||||||
if (table->triggers)
|
if (table->triggers &&
|
||||||
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
|
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
|
||||||
TRG_ACTION_BEFORE);
|
TRG_ACTION_BEFORE, FALSE))
|
||||||
|
{
|
||||||
|
error= 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(error=table->file->delete_row(table->record[0])))
|
if (!(error=table->file->delete_row(table->record[0])))
|
||||||
{
|
{
|
||||||
deleted++;
|
deleted++;
|
||||||
|
if (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
|
||||||
|
TRG_ACTION_AFTER, FALSE))
|
||||||
|
{
|
||||||
|
error= 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (!--limit && using_limit)
|
if (!--limit && using_limit)
|
||||||
{
|
{
|
||||||
error= -1;
|
error= -1;
|
||||||
@ -203,10 +214,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
|
|||||||
error= 1;
|
error= 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (table->triggers)
|
|
||||||
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
|
|
||||||
TRG_ACTION_AFTER);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
table->file->unlock_row(); // Row failed selection, release lock on it
|
table->file->unlock_row(); // Row failed selection, release lock on it
|
||||||
@ -509,9 +516,19 @@ bool multi_delete::send_data(List<Item> &values)
|
|||||||
if (secure_counter < 0)
|
if (secure_counter < 0)
|
||||||
{
|
{
|
||||||
/* If this is the table we are scanning */
|
/* If this is the table we are scanning */
|
||||||
|
if (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
|
||||||
|
TRG_ACTION_BEFORE, FALSE))
|
||||||
|
DBUG_RETURN(1);
|
||||||
table->status|= STATUS_DELETED;
|
table->status|= STATUS_DELETED;
|
||||||
if (!(error=table->file->delete_row(table->record[0])))
|
if (!(error=table->file->delete_row(table->record[0])))
|
||||||
|
{
|
||||||
deleted++;
|
deleted++;
|
||||||
|
if (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
|
||||||
|
TRG_ACTION_AFTER, FALSE))
|
||||||
|
DBUG_RETURN(1);
|
||||||
|
}
|
||||||
else if (!table_being_deleted->next_local ||
|
else if (!table_being_deleted->next_local ||
|
||||||
table_being_deleted->table->file->has_transactions())
|
table_being_deleted->table->file->has_transactions())
|
||||||
{
|
{
|
||||||
@ -614,12 +631,26 @@ int multi_delete::do_deletes(bool from_send_error)
|
|||||||
info.ignore_not_found_rows= 1;
|
info.ignore_not_found_rows= 1;
|
||||||
while (!(local_error=info.read_record(&info)) && !thd->killed)
|
while (!(local_error=info.read_record(&info)) && !thd->killed)
|
||||||
{
|
{
|
||||||
|
if (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
|
||||||
|
TRG_ACTION_BEFORE, FALSE))
|
||||||
|
{
|
||||||
|
local_error= 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
if ((local_error=table->file->delete_row(table->record[0])))
|
if ((local_error=table->file->delete_row(table->record[0])))
|
||||||
{
|
{
|
||||||
table->file->print_error(local_error,MYF(0));
|
table->file->print_error(local_error,MYF(0));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
deleted++;
|
deleted++;
|
||||||
|
if (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
|
||||||
|
TRG_ACTION_AFTER, FALSE))
|
||||||
|
{
|
||||||
|
local_error= 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
end_read_record(&info);
|
end_read_record(&info);
|
||||||
if (thd->killed && !local_error)
|
if (thd->killed && !local_error)
|
||||||
|
@ -398,7 +398,9 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
|||||||
if (fields.elements || !value_count)
|
if (fields.elements || !value_count)
|
||||||
{
|
{
|
||||||
restore_record(table,s->default_values); // Get empty record
|
restore_record(table,s->default_values); // Get empty record
|
||||||
if (fill_record(thd, fields, *values, 0))
|
if (fill_record_n_invoke_before_triggers(thd, fields, *values, 0,
|
||||||
|
table->triggers,
|
||||||
|
TRG_EVENT_INSERT))
|
||||||
{
|
{
|
||||||
if (values_list.elements != 1 && !thd->net.report_error)
|
if (values_list.elements != 1 && !thd->net.report_error)
|
||||||
{
|
{
|
||||||
@ -419,8 +421,17 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
|||||||
if (thd->used_tables) // Column used in values()
|
if (thd->used_tables) // Column used in values()
|
||||||
restore_record(table,s->default_values); // Get empty record
|
restore_record(table,s->default_values); // Get empty record
|
||||||
else
|
else
|
||||||
table->record[0][0]= table->s->default_values[0]; // Fix delete marker
|
{
|
||||||
if (fill_record(thd, table->field, *values, 0))
|
/*
|
||||||
|
Fix delete marker. No need to restore rest of record since it will
|
||||||
|
be overwritten by fill_record() anyway (and fill_record() does not
|
||||||
|
use default values in this case).
|
||||||
|
*/
|
||||||
|
table->record[0][0]= table->s->default_values[0];
|
||||||
|
}
|
||||||
|
if (fill_record_n_invoke_before_triggers(thd, table->field, *values, 0,
|
||||||
|
table->triggers,
|
||||||
|
TRG_EVENT_INSERT))
|
||||||
{
|
{
|
||||||
if (values_list.elements != 1 && ! thd->net.report_error)
|
if (values_list.elements != 1 && ! thd->net.report_error)
|
||||||
{
|
{
|
||||||
@ -432,14 +443,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
FIXME: Actually we should do this before
|
|
||||||
check_that_all_fields_are_given_values Or even go into write_record ?
|
|
||||||
*/
|
|
||||||
if (table->triggers)
|
|
||||||
table->triggers->process_triggers(thd, TRG_EVENT_INSERT,
|
|
||||||
TRG_ACTION_BEFORE);
|
|
||||||
|
|
||||||
if ((res= table_list->view_check_option(thd,
|
if ((res= table_list->view_check_option(thd,
|
||||||
(values_list.elements == 1 ?
|
(values_list.elements == 1 ?
|
||||||
0 :
|
0 :
|
||||||
@ -473,9 +476,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
|||||||
if (error)
|
if (error)
|
||||||
break;
|
break;
|
||||||
thd->row_count++;
|
thd->row_count++;
|
||||||
|
|
||||||
if (table->triggers)
|
|
||||||
table->triggers->process_triggers(thd, TRG_EVENT_INSERT, TRG_ACTION_AFTER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -802,15 +802,35 @@ static int last_uniq_key(TABLE *table,uint keynr)
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Write a record to table with optional deleting of conflicting records
|
Write a record to table with optional deleting of conflicting records,
|
||||||
|
invoke proper triggers if needed.
|
||||||
|
|
||||||
Sets thd->no_trans_update if table which is updated didn't have transactions
|
SYNOPSIS
|
||||||
|
write_record()
|
||||||
|
thd - thread context
|
||||||
|
table - table to which record should be written
|
||||||
|
info - COPY_INFO structure describing handling of duplicates
|
||||||
|
and which is used for counting number of records inserted
|
||||||
|
and deleted.
|
||||||
|
|
||||||
|
NOTE
|
||||||
|
Once this record will be written to table after insert trigger will
|
||||||
|
be invoked. If instead of inserting new record we will update old one
|
||||||
|
then both on update triggers will work instead. Similarly both on
|
||||||
|
delete triggers will be invoked if we will delete conflicting records.
|
||||||
|
|
||||||
|
Sets thd->no_trans_update if table which is updated didn't have
|
||||||
|
transactions.
|
||||||
|
|
||||||
|
RETURN VALUE
|
||||||
|
0 - success
|
||||||
|
non-0 - error
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
||||||
{
|
{
|
||||||
int error;
|
int error, trg_error= 0;
|
||||||
char *key=0;
|
char *key=0;
|
||||||
DBUG_ENTER("write_record");
|
DBUG_ENTER("write_record");
|
||||||
|
|
||||||
@ -881,25 +901,33 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
|||||||
restore_record(table,record[1]);
|
restore_record(table,record[1]);
|
||||||
DBUG_ASSERT(info->update_fields->elements ==
|
DBUG_ASSERT(info->update_fields->elements ==
|
||||||
info->update_values->elements);
|
info->update_values->elements);
|
||||||
if (fill_record(thd, *info->update_fields, *info->update_values, 0))
|
if (fill_record_n_invoke_before_triggers(thd, *info->update_fields,
|
||||||
goto err;
|
*info->update_values, 0,
|
||||||
|
table->triggers,
|
||||||
|
TRG_EVENT_UPDATE))
|
||||||
|
goto before_trg_err;
|
||||||
|
|
||||||
/* CHECK OPTION for VIEW ... ON DUPLICATE KEY UPDATE ... */
|
/* CHECK OPTION for VIEW ... ON DUPLICATE KEY UPDATE ... */
|
||||||
if (info->view &&
|
if (info->view &&
|
||||||
(res= info->view->view_check_option(current_thd, info->ignore)) ==
|
(res= info->view->view_check_option(current_thd, info->ignore)) ==
|
||||||
VIEW_CHECK_SKIP)
|
VIEW_CHECK_SKIP)
|
||||||
break;
|
goto ok_or_after_trg_err;
|
||||||
if (res == VIEW_CHECK_ERROR)
|
if (res == VIEW_CHECK_ERROR)
|
||||||
goto err;
|
goto before_trg_err;
|
||||||
|
|
||||||
if ((error=table->file->update_row(table->record[1],table->record[0])))
|
if ((error=table->file->update_row(table->record[1],table->record[0])))
|
||||||
{
|
{
|
||||||
if ((error == HA_ERR_FOUND_DUPP_KEY) && info->ignore)
|
if ((error == HA_ERR_FOUND_DUPP_KEY) && info->ignore)
|
||||||
break;
|
goto ok_or_after_trg_err;
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
info->updated++;
|
info->updated++;
|
||||||
break;
|
|
||||||
|
trg_error= (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
|
||||||
|
TRG_ACTION_AFTER, TRUE));
|
||||||
|
info->copied++;
|
||||||
|
goto ok_or_after_trg_err;
|
||||||
}
|
}
|
||||||
else /* DUP_REPLACE */
|
else /* DUP_REPLACE */
|
||||||
{
|
{
|
||||||
@ -916,20 +944,48 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
|||||||
(table->timestamp_field_type == TIMESTAMP_NO_AUTO_SET ||
|
(table->timestamp_field_type == TIMESTAMP_NO_AUTO_SET ||
|
||||||
table->timestamp_field_type == TIMESTAMP_AUTO_SET_ON_BOTH))
|
table->timestamp_field_type == TIMESTAMP_AUTO_SET_ON_BOTH))
|
||||||
{
|
{
|
||||||
|
if (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
|
||||||
|
TRG_ACTION_BEFORE, TRUE))
|
||||||
|
goto before_trg_err;
|
||||||
if ((error=table->file->update_row(table->record[1],
|
if ((error=table->file->update_row(table->record[1],
|
||||||
table->record[0])))
|
table->record[0])))
|
||||||
goto err;
|
goto err;
|
||||||
info->deleted++;
|
info->deleted++;
|
||||||
break; /* Update logfile and count */
|
trg_error= (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
|
||||||
|
TRG_ACTION_AFTER,
|
||||||
|
TRUE));
|
||||||
|
/* Update logfile and count */
|
||||||
|
info->copied++;
|
||||||
|
goto ok_or_after_trg_err;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
|
||||||
|
TRG_ACTION_BEFORE, TRUE))
|
||||||
|
goto before_trg_err;
|
||||||
|
if ((error=table->file->delete_row(table->record[1])))
|
||||||
|
goto err;
|
||||||
|
info->deleted++;
|
||||||
|
if (!table->file->has_transactions())
|
||||||
|
thd->no_trans_update= 1;
|
||||||
|
if (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
|
||||||
|
TRG_ACTION_AFTER, TRUE))
|
||||||
|
{
|
||||||
|
trg_error= 1;
|
||||||
|
goto ok_or_after_trg_err;
|
||||||
|
}
|
||||||
|
/* Let us attempt do write_row() once more */
|
||||||
}
|
}
|
||||||
else if ((error=table->file->delete_row(table->record[1])))
|
|
||||||
goto err;
|
|
||||||
info->deleted++;
|
|
||||||
if (!table->file->has_transactions())
|
|
||||||
thd->no_trans_update= 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info->copied++;
|
info->copied++;
|
||||||
|
trg_error= (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_INSERT,
|
||||||
|
TRG_ACTION_AFTER, TRUE));
|
||||||
}
|
}
|
||||||
else if ((error=table->file->write_row(table->record[0])))
|
else if ((error=table->file->write_row(table->record[0])))
|
||||||
{
|
{
|
||||||
@ -939,18 +995,27 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
|
|||||||
table->file->restore_auto_increment();
|
table->file->restore_auto_increment();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
info->copied++;
|
info->copied++;
|
||||||
|
trg_error= (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_INSERT,
|
||||||
|
TRG_ACTION_AFTER, TRUE));
|
||||||
|
}
|
||||||
|
|
||||||
|
ok_or_after_trg_err:
|
||||||
if (key)
|
if (key)
|
||||||
my_safe_afree(key,table->s->max_unique_length,MAX_KEY_LENGTH);
|
my_safe_afree(key,table->s->max_unique_length,MAX_KEY_LENGTH);
|
||||||
if (!table->file->has_transactions())
|
if (!table->file->has_transactions())
|
||||||
thd->no_trans_update= 1;
|
thd->no_trans_update= 1;
|
||||||
DBUG_RETURN(0);
|
DBUG_RETURN(trg_error);
|
||||||
|
|
||||||
err:
|
err:
|
||||||
if (key)
|
|
||||||
my_safe_afree(key,table->s->max_unique_length,MAX_KEY_LENGTH);
|
|
||||||
info->last_errno= error;
|
info->last_errno= error;
|
||||||
table->file->print_error(error,MYF(0));
|
table->file->print_error(error,MYF(0));
|
||||||
|
|
||||||
|
before_trg_err:
|
||||||
|
if (key)
|
||||||
|
my_safe_afree(key, table->s->max_unique_length, MAX_KEY_LENGTH);
|
||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2014,12 +2079,27 @@ bool select_insert::send_data(List<Item> &values)
|
|||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!(error= write_record(thd, table,&info)) && table->next_number_field)
|
if (!(error= write_record(thd, table, &info)))
|
||||||
{
|
{
|
||||||
/* Clear for next record */
|
if (table->triggers)
|
||||||
table->next_number_field->reset();
|
{
|
||||||
if (! last_insert_id && thd->insert_id_used)
|
/*
|
||||||
last_insert_id=thd->insert_id();
|
If triggers exist then whey can modify some fields which were not
|
||||||
|
originally touched by INSERT ... SELECT, so we have to restore
|
||||||
|
their original values for the next row.
|
||||||
|
*/
|
||||||
|
restore_record(table, s->default_values);
|
||||||
|
}
|
||||||
|
if (table->next_number_field)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Clear auto-increment field for the next record, if triggers are used
|
||||||
|
we will clear it twice, but this should be cheap.
|
||||||
|
*/
|
||||||
|
table->next_number_field->reset();
|
||||||
|
if (!last_insert_id && thd->insert_id_used)
|
||||||
|
last_insert_id= thd->insert_id();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DBUG_RETURN(error);
|
DBUG_RETURN(error);
|
||||||
}
|
}
|
||||||
@ -2028,9 +2108,11 @@ bool select_insert::send_data(List<Item> &values)
|
|||||||
void select_insert::store_values(List<Item> &values)
|
void select_insert::store_values(List<Item> &values)
|
||||||
{
|
{
|
||||||
if (fields->elements)
|
if (fields->elements)
|
||||||
fill_record(thd, *fields, values, 1);
|
fill_record_n_invoke_before_triggers(thd, *fields, values, 1,
|
||||||
|
table->triggers, TRG_EVENT_INSERT);
|
||||||
else
|
else
|
||||||
fill_record(thd, table->field, values, 1);
|
fill_record_n_invoke_before_triggers(thd, table->field, values, 1,
|
||||||
|
table->triggers, TRG_EVENT_INSERT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void select_insert::send_error(uint errcode,const char *err)
|
void select_insert::send_error(uint errcode,const char *err)
|
||||||
@ -2173,7 +2255,8 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
|
|||||||
|
|
||||||
void select_create::store_values(List<Item> &values)
|
void select_create::store_values(List<Item> &values)
|
||||||
{
|
{
|
||||||
fill_record(thd, field, values, 1);
|
fill_record_n_invoke_before_triggers(thd, field, values, 1,
|
||||||
|
table->triggers, TRG_EVENT_INSERT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
#include <my_dir.h>
|
#include <my_dir.h>
|
||||||
#include <m_ctype.h>
|
#include <m_ctype.h>
|
||||||
#include "sql_repl.h"
|
#include "sql_repl.h"
|
||||||
|
#include "sp_head.h"
|
||||||
|
#include "sql_trigger.h"
|
||||||
|
|
||||||
class READ_INFO {
|
class READ_INFO {
|
||||||
File file;
|
File file;
|
||||||
@ -568,7 +570,11 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
|
|||||||
ER(ER_WARN_TOO_MANY_RECORDS), thd->row_count);
|
ER(ER_WARN_TOO_MANY_RECORDS), thd->row_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fill_record(thd, set_fields, set_values, ignore_check_option_errors))
|
if (thd->killed ||
|
||||||
|
fill_record_n_invoke_before_triggers(thd, set_fields, set_values,
|
||||||
|
ignore_check_option_errors,
|
||||||
|
table->triggers,
|
||||||
|
TRG_EVENT_INSERT))
|
||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
|
|
||||||
switch (table_list->view_check_option(thd,
|
switch (table_list->view_check_option(thd,
|
||||||
@ -580,7 +586,7 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
|
|||||||
DBUG_RETURN(-1);
|
DBUG_RETURN(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thd->killed || write_record(thd,table,&info))
|
if (write_record(thd, table, &info))
|
||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
thd->no_trans_update= no_trans_update;
|
thd->no_trans_update= no_trans_update;
|
||||||
|
|
||||||
@ -592,8 +598,10 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
|
|||||||
*/
|
*/
|
||||||
if (!id && thd->insert_id_used)
|
if (!id && thd->insert_id_used)
|
||||||
id= thd->last_insert_id;
|
id= thd->last_insert_id;
|
||||||
if (table->next_number_field)
|
/*
|
||||||
table->next_number_field->reset(); // Clear for next record
|
We don't need to reset auto-increment field since we are restoring
|
||||||
|
its default value at the beginning of each loop iteration.
|
||||||
|
*/
|
||||||
if (read_info.next_line()) // Skip to next line
|
if (read_info.next_line()) // Skip to next line
|
||||||
break;
|
break;
|
||||||
if (read_info.line_cuted)
|
if (read_info.line_cuted)
|
||||||
@ -725,7 +733,11 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fill_record(thd, set_fields, set_values, ignore_check_option_errors))
|
if (thd->killed ||
|
||||||
|
fill_record_n_invoke_before_triggers(thd, set_fields, set_values,
|
||||||
|
ignore_check_option_errors,
|
||||||
|
table->triggers,
|
||||||
|
TRG_EVENT_INSERT))
|
||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
|
|
||||||
switch (table_list->view_check_option(thd,
|
switch (table_list->view_check_option(thd,
|
||||||
@ -738,7 +750,7 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (thd->killed || write_record(thd, table, &info))
|
if (write_record(thd, table, &info))
|
||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
/*
|
/*
|
||||||
If auto_increment values are used, save the first one
|
If auto_increment values are used, save the first one
|
||||||
@ -748,8 +760,10 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
|
|||||||
*/
|
*/
|
||||||
if (!id && thd->insert_id_used)
|
if (!id && thd->insert_id_used)
|
||||||
id= thd->last_insert_id;
|
id= thd->last_insert_id;
|
||||||
if (table->next_number_field)
|
/*
|
||||||
table->next_number_field->reset(); // Clear for next record
|
We don't need to reset auto-increment field since we are restoring
|
||||||
|
its default value at the beginning of each loop iteration.
|
||||||
|
*/
|
||||||
thd->no_trans_update= no_trans_update;
|
thd->no_trans_update= no_trans_update;
|
||||||
if (read_info.next_line()) // Skip to next line
|
if (read_info.next_line()) // Skip to next line
|
||||||
break;
|
break;
|
||||||
|
@ -85,7 +85,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
|
|||||||
DBUG_RETURN(TRUE);
|
DBUG_RETURN(TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(table->triggers= new (&table->mem_root) Table_triggers_list()))
|
if (!(table->triggers= new (&table->mem_root) Table_triggers_list(table)))
|
||||||
DBUG_RETURN(TRUE);
|
DBUG_RETURN(TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,17 +190,16 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
|
|||||||
to other tables from trigger we won't be able to catch changes in other
|
to other tables from trigger we won't be able to catch changes in other
|
||||||
tables...
|
tables...
|
||||||
|
|
||||||
To simplify code a bit we have to create Fields for accessing to old row
|
Since we don't plan to access to contents of the fields it does not
|
||||||
values if we have ON UPDATE trigger.
|
matter that we choose for both OLD and NEW values the same versions
|
||||||
|
of Field objects here.
|
||||||
*/
|
*/
|
||||||
if (!old_field && lex->trg_chistics.event == TRG_EVENT_UPDATE &&
|
old_field= new_field= table->field;
|
||||||
prepare_old_row_accessors(table))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
for (trg_field= (Item_trigger_field *)(lex->trg_table_fields.first);
|
for (trg_field= (Item_trigger_field *)(lex->trg_table_fields.first);
|
||||||
trg_field; trg_field= trg_field->next_trg_field)
|
trg_field; trg_field= trg_field->next_trg_field)
|
||||||
{
|
{
|
||||||
trg_field->setup_field(thd, table, lex->trg_chistics.event);
|
trg_field->setup_field(thd, table);
|
||||||
if (!trg_field->fixed &&
|
if (!trg_field->fixed &&
|
||||||
trg_field->fix_fields(thd, (TABLE_LIST *)0, (Item **)0))
|
trg_field->fix_fields(thd, (TABLE_LIST *)0, (Item **)0))
|
||||||
return 1;
|
return 1;
|
||||||
@ -318,34 +317,35 @@ Table_triggers_list::~Table_triggers_list()
|
|||||||
for (int j= 0; j < 2; j++)
|
for (int j= 0; j < 2; j++)
|
||||||
delete bodies[i][j];
|
delete bodies[i][j];
|
||||||
|
|
||||||
if (old_field)
|
if (record1_field)
|
||||||
for (Field **fld_ptr= old_field; *fld_ptr; fld_ptr++)
|
for (Field **fld_ptr= record1_field; *fld_ptr; fld_ptr++)
|
||||||
delete *fld_ptr;
|
delete *fld_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Prepare array of Field objects which will represent OLD.* row values in
|
Prepare array of Field objects referencing to TABLE::record[1] instead
|
||||||
ON UPDATE trigger (by referencing to record[1] instead of record[0]).
|
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).
|
||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
prepare_old_row_accessors()
|
prepare_record1_accessors()
|
||||||
table - pointer to TABLE object for which we are creating fields.
|
table - pointer to TABLE object for which we are creating fields.
|
||||||
|
|
||||||
RETURN VALUE
|
RETURN VALUE
|
||||||
False - success
|
False - success
|
||||||
True - error
|
True - error
|
||||||
*/
|
*/
|
||||||
bool Table_triggers_list::prepare_old_row_accessors(TABLE *table)
|
bool Table_triggers_list::prepare_record1_accessors(TABLE *table)
|
||||||
{
|
{
|
||||||
Field **fld, **old_fld;
|
Field **fld, **old_fld;
|
||||||
|
|
||||||
if (!(old_field= (Field **)alloc_root(&table->mem_root,
|
if (!(record1_field= (Field **)alloc_root(&table->mem_root,
|
||||||
(table->s->fields + 1) *
|
(table->s->fields + 1) *
|
||||||
sizeof(Field*))))
|
sizeof(Field*))))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
for (fld= table->field, old_fld= old_field; *fld; fld++, old_fld++)
|
for (fld= table->field, old_fld= record1_field; *fld; fld++, old_fld++)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
QQ: it is supposed that it is ok to use this function for field
|
QQ: it is supposed that it is ok to use this function for field
|
||||||
@ -406,7 +406,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
|
|||||||
parser->type()->length))
|
parser->type()->length))
|
||||||
{
|
{
|
||||||
Table_triggers_list *triggers=
|
Table_triggers_list *triggers=
|
||||||
new (&table->mem_root) Table_triggers_list();
|
new (&table->mem_root) Table_triggers_list(table);
|
||||||
|
|
||||||
if (!triggers)
|
if (!triggers)
|
||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
@ -417,8 +417,11 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
|
|||||||
|
|
||||||
table->triggers= triggers;
|
table->triggers= triggers;
|
||||||
|
|
||||||
/* TODO: This could be avoided if there is no ON UPDATE trigger. */
|
/*
|
||||||
if (triggers->prepare_old_row_accessors(table))
|
TODO: This could be avoided if there is no triggers
|
||||||
|
for UPDATE and DELETE.
|
||||||
|
*/
|
||||||
|
if (triggers->prepare_record1_accessors(table))
|
||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
|
|
||||||
List_iterator_fast<LEX_STRING> it(triggers->definitions_list);
|
List_iterator_fast<LEX_STRING> it(triggers->definitions_list);
|
||||||
@ -478,7 +481,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
|
|||||||
(Item_trigger_field *)(lex.trg_table_fields.first);
|
(Item_trigger_field *)(lex.trg_table_fields.first);
|
||||||
trg_field;
|
trg_field;
|
||||||
trg_field= trg_field->next_trg_field)
|
trg_field= trg_field->next_trg_field)
|
||||||
trg_field->setup_field(thd, table, lex.trg_chistics.event);
|
trg_field->setup_field(thd, table);
|
||||||
|
|
||||||
lex_end(&lex);
|
lex_end(&lex);
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,20 @@ class Table_triggers_list: public Sql_alloc
|
|||||||
/* Triggers as SPs grouped by event, action_time */
|
/* Triggers as SPs grouped by event, action_time */
|
||||||
sp_head *bodies[3][2];
|
sp_head *bodies[3][2];
|
||||||
/*
|
/*
|
||||||
Copy of TABLE::Field array with field pointers set to old version
|
Copy of TABLE::Field array with field pointers set to TABLE::record[1]
|
||||||
of record, used for OLD values in trigger on UPDATE.
|
buffer instead of TABLE::record[0] (used for OLD values in on UPDATE
|
||||||
|
trigger and DELETE trigger when it is called for REPLACE).
|
||||||
*/
|
*/
|
||||||
|
Field **record1_field;
|
||||||
|
/*
|
||||||
|
During execution of trigger new_field and old_field should point to the
|
||||||
|
array of fields representing new or old version of row correspondingly
|
||||||
|
(so it can point to TABLE::field or to Tale_triggers_list::record1_field)
|
||||||
|
*/
|
||||||
|
Field **new_field;
|
||||||
Field **old_field;
|
Field **old_field;
|
||||||
|
/* TABLE instance for which this triggers list object was created */
|
||||||
|
TABLE *table;
|
||||||
/*
|
/*
|
||||||
Names of triggers.
|
Names of triggers.
|
||||||
Should correspond to order of triggers on definitions_list,
|
Should correspond to order of triggers on definitions_list,
|
||||||
@ -26,8 +36,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
List<LEX_STRING> definitions_list;
|
List<LEX_STRING> definitions_list;
|
||||||
|
|
||||||
Table_triggers_list():
|
Table_triggers_list(TABLE *table_arg):
|
||||||
old_field(0)
|
record1_field(0), table(table_arg)
|
||||||
{
|
{
|
||||||
bzero((char *)bodies, sizeof(bodies));
|
bzero((char *)bodies, sizeof(bodies));
|
||||||
}
|
}
|
||||||
@ -36,7 +46,8 @@ public:
|
|||||||
bool create_trigger(THD *thd, TABLE_LIST *table);
|
bool create_trigger(THD *thd, TABLE_LIST *table);
|
||||||
bool drop_trigger(THD *thd, TABLE_LIST *table);
|
bool drop_trigger(THD *thd, TABLE_LIST *table);
|
||||||
bool process_triggers(THD *thd, trg_event_type event,
|
bool process_triggers(THD *thd, trg_event_type event,
|
||||||
trg_action_time_type time_type)
|
trg_action_time_type time_type,
|
||||||
|
bool old_row_is_record1)
|
||||||
{
|
{
|
||||||
int res= 0;
|
int res= 0;
|
||||||
|
|
||||||
@ -48,6 +59,17 @@ public:
|
|||||||
thd->net.no_send_ok= TRUE;
|
thd->net.no_send_ok= TRUE;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (old_row_is_record1)
|
||||||
|
{
|
||||||
|
old_field= record1_field;
|
||||||
|
new_field= table->field;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
new_field= record1_field;
|
||||||
|
old_field= table->field;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
FIXME: We should juggle with security context here (because trigger
|
FIXME: We should juggle with security context here (because trigger
|
||||||
should be invoked with creator rights).
|
should be invoked with creator rights).
|
||||||
@ -79,8 +101,13 @@ public:
|
|||||||
bodies[TRG_EVENT_DELETE][TRG_ACTION_AFTER]);
|
bodies[TRG_EVENT_DELETE][TRG_ACTION_AFTER]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool has_before_update_triggers()
|
||||||
|
{
|
||||||
|
return test(bodies[TRG_EVENT_UPDATE][TRG_ACTION_BEFORE]);
|
||||||
|
}
|
||||||
|
|
||||||
friend class Item_trigger_field;
|
friend class Item_trigger_field;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool prepare_old_row_accessors(TABLE *table);
|
bool prepare_record1_accessors(TABLE *table);
|
||||||
};
|
};
|
||||||
|
@ -398,14 +398,13 @@ int mysql_update(THD *thd,
|
|||||||
if (!(select && select->skip_record()))
|
if (!(select && select->skip_record()))
|
||||||
{
|
{
|
||||||
store_record(table,record[1]);
|
store_record(table,record[1]);
|
||||||
if (fill_record(thd, fields, values, 0))
|
if (fill_record_n_invoke_before_triggers(thd, fields, values, 0,
|
||||||
|
table->triggers,
|
||||||
|
TRG_EVENT_UPDATE))
|
||||||
break; /* purecov: inspected */
|
break; /* purecov: inspected */
|
||||||
|
|
||||||
found++;
|
found++;
|
||||||
|
|
||||||
if (table->triggers)
|
|
||||||
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_BEFORE);
|
|
||||||
|
|
||||||
if (compare_record(table, query_id))
|
if (compare_record(table, query_id))
|
||||||
{
|
{
|
||||||
if ((res= table_list->view_check_option(thd, ignore)) !=
|
if ((res= table_list->view_check_option(thd, ignore)) !=
|
||||||
@ -425,6 +424,14 @@ int mysql_update(THD *thd,
|
|||||||
{
|
{
|
||||||
updated++;
|
updated++;
|
||||||
thd->no_trans_update= !transactional_table;
|
thd->no_trans_update= !transactional_table;
|
||||||
|
|
||||||
|
if (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
|
||||||
|
TRG_ACTION_AFTER, TRUE))
|
||||||
|
{
|
||||||
|
error= 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!ignore || error != HA_ERR_FOUND_DUPP_KEY)
|
else if (!ignore || error != HA_ERR_FOUND_DUPP_KEY)
|
||||||
{
|
{
|
||||||
@ -435,9 +442,6 @@ int mysql_update(THD *thd,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (table->triggers)
|
|
||||||
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_AFTER);
|
|
||||||
|
|
||||||
if (!--limit && using_limit)
|
if (!--limit && using_limit)
|
||||||
{
|
{
|
||||||
error= -1; // Simulate end of file
|
error= -1; // Simulate end of file
|
||||||
@ -1073,8 +1077,8 @@ multi_update::initialize_tables(JOIN *join)
|
|||||||
|
|
||||||
NOTES
|
NOTES
|
||||||
We can update the first table in join on the fly if we know that
|
We can update the first table in join on the fly if we know that
|
||||||
a row in this tabel will never be read twice. This is true under
|
a row in this table will never be read twice. This is true under
|
||||||
the folloing conditions:
|
the following conditions:
|
||||||
|
|
||||||
- We are doing a table scan and the data is in a separate file (MyISAM) or
|
- We are doing a table scan and the data is in a separate file (MyISAM) or
|
||||||
if we don't update a clustered key.
|
if we don't update a clustered key.
|
||||||
@ -1082,6 +1086,10 @@ multi_update::initialize_tables(JOIN *join)
|
|||||||
- We are doing a range scan and we don't update the scan key or
|
- We are doing a range scan and we don't update the scan key or
|
||||||
the primary key for a clustered table handler.
|
the primary key for a clustered table handler.
|
||||||
|
|
||||||
|
When checking for above cases we also should take into account that
|
||||||
|
BEFORE UPDATE trigger potentially may change value of any field in row
|
||||||
|
being updated.
|
||||||
|
|
||||||
WARNING
|
WARNING
|
||||||
This code is a bit dependent of how make_join_readinfo() works.
|
This code is a bit dependent of how make_join_readinfo() works.
|
||||||
|
|
||||||
@ -1100,15 +1108,21 @@ static bool safe_update_on_fly(JOIN_TAB *join_tab, List<Item> *fields)
|
|||||||
return TRUE; // At most one matching row
|
return TRUE; // At most one matching row
|
||||||
case JT_REF:
|
case JT_REF:
|
||||||
case JT_REF_OR_NULL:
|
case JT_REF_OR_NULL:
|
||||||
return !check_if_key_used(table, join_tab->ref.key, *fields);
|
return !check_if_key_used(table, join_tab->ref.key, *fields) &&
|
||||||
|
!(table->triggers &&
|
||||||
|
table->triggers->has_before_update_triggers());
|
||||||
case JT_ALL:
|
case JT_ALL:
|
||||||
/* If range search on index */
|
/* If range search on index */
|
||||||
if (join_tab->quick)
|
if (join_tab->quick)
|
||||||
return !join_tab->quick->check_if_keys_used(fields);
|
return !join_tab->quick->check_if_keys_used(fields) &&
|
||||||
|
!(table->triggers &&
|
||||||
|
table->triggers->has_before_update_triggers());
|
||||||
/* If scanning in clustered key */
|
/* If scanning in clustered key */
|
||||||
if ((table->file->table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) &&
|
if ((table->file->table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) &&
|
||||||
table->s->primary_key < MAX_KEY)
|
table->s->primary_key < MAX_KEY)
|
||||||
return !check_if_key_used(table, table->s->primary_key, *fields);
|
return !check_if_key_used(table, table->s->primary_key, *fields) &&
|
||||||
|
!(table->triggers &&
|
||||||
|
table->triggers->has_before_update_triggers());
|
||||||
return TRUE;
|
return TRUE;
|
||||||
default:
|
default:
|
||||||
break; // Avoid compler warning
|
break; // Avoid compler warning
|
||||||
@ -1171,8 +1185,10 @@ bool multi_update::send_data(List<Item> ¬_used_values)
|
|||||||
{
|
{
|
||||||
table->status|= STATUS_UPDATED;
|
table->status|= STATUS_UPDATED;
|
||||||
store_record(table,record[1]);
|
store_record(table,record[1]);
|
||||||
if (fill_record(thd, *fields_for_table[offset],
|
if (fill_record_n_invoke_before_triggers(thd, *fields_for_table[offset],
|
||||||
*values_for_table[offset], 0))
|
*values_for_table[offset], 0,
|
||||||
|
table->triggers,
|
||||||
|
TRG_EVENT_UPDATE))
|
||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
|
|
||||||
found++;
|
found++;
|
||||||
@ -1208,8 +1224,15 @@ bool multi_update::send_data(List<Item> ¬_used_values)
|
|||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!table->file->has_transactions())
|
else
|
||||||
thd->no_trans_update= 1;
|
{
|
||||||
|
if (!table->file->has_transactions())
|
||||||
|
thd->no_trans_update= 1;
|
||||||
|
if (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
|
||||||
|
TRG_ACTION_AFTER, TRUE))
|
||||||
|
DBUG_RETURN(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1330,6 +1353,11 @@ int multi_update::do_updates(bool from_send_error)
|
|||||||
copy_field_ptr++)
|
copy_field_ptr++)
|
||||||
(*copy_field_ptr->do_copy)(copy_field_ptr);
|
(*copy_field_ptr->do_copy)(copy_field_ptr);
|
||||||
|
|
||||||
|
if (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
|
||||||
|
TRG_ACTION_BEFORE, TRUE))
|
||||||
|
goto err2;
|
||||||
|
|
||||||
if (compare_record(table, thd->query_id))
|
if (compare_record(table, thd->query_id))
|
||||||
{
|
{
|
||||||
if ((local_error=table->file->update_row(table->record[1],
|
if ((local_error=table->file->update_row(table->record[1],
|
||||||
@ -1339,6 +1367,11 @@ int multi_update::do_updates(bool from_send_error)
|
|||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
updated++;
|
updated++;
|
||||||
|
|
||||||
|
if (table->triggers &&
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
|
||||||
|
TRG_ACTION_AFTER, TRUE))
|
||||||
|
goto err2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1361,6 +1394,7 @@ err:
|
|||||||
table->file->print_error(local_error,MYF(0));
|
table->file->print_error(local_error,MYF(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err2:
|
||||||
(void) table->file->ha_rnd_end();
|
(void) table->file->ha_rnd_end();
|
||||||
(void) tmp_table->file->ha_rnd_end();
|
(void) tmp_table->file->ha_rnd_end();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user