Fix for bugs:
#5860 "Multi-table UPDATE does not activate update triggers" #6812 "Triggers are not activated for INSERT ... SELECT" #8755 "Trigger is not activated by LOAD DATA". This patch also implements proper handling of triggers for special forms of insert like REPLACE or INSERT ... ON DUPLICATE KEY UPDATE. Also now we don't call after trigger in case when we have failed to inserted/update or delete row. Trigger failure should stop statement execution. I have not properly tested handling of errors which happen inside of triggers in this patch, since it is simplier to do this once we will be able to access tables from triggers. mysql-test/r/trigger.result: Added tests for triggers behavior for various non-standard forms of INSERT such as REPLACE and INSERT ... ON DUPLICATE KEY UPDATE. Also added tests for bugs #5860 "Multi-table UPDATE does not activate update triggers", #6812 "Triggers are not activated for INSERT ... SELECT" and #8755 "Trigger is not activated by LOAD DATA". mysql-test/t/trigger.test: Added tests for triggers behavior for various non-standard forms of INSERT such as REPLACE and INSERT ... ON DUPLICATE KEY UPDATE. Also added tests for bugs #5860 "Multi-table UPDATE does not activate update triggers", #6812 "Triggers are not activated for INSERT ... SELECT" and #8755 "Trigger is not activated by LOAD DATA". sql/item.cc: Since it turned out that at trigger loading time we can't say in which buffer TABLE::record[0] or record[1] old version of row will be stored we have to change our approach to binding of Item_trigger_field to Field instances. Now after trigger parsing (in Item_trigger_field::setup_table()) we only find index of proper Field in the TABLE::field array. Then before trigger is invoked we set Table_triggers_list::old_field/new_field so they point to arrays holding Field instances bound to buffers with proper row versions. And as last step in Item_trigger_field::fix_fields() we get pointer to Field from those arrays using saved field index. Item_trigger_field::setup_field()/fix_fields() were changed to implement this approach. sql/item.h: Since it turned out that at trigger loading time we can't say in which buffer TABLE::record[0] or record[1] old version of row will be stored we have to change our approach to binding of Item_trigger_field to Field instances. Now after trigger parsing (in Item_trigger_field::setup_table()) we only find index of proper Field in the TABLE::field array. Then before trigger is invoked we set Table_triggers_list::old_field/new_field so they point to arrays holding Field instances bound to buffers with proper row versions. And as last step in Item_trigger_field::fix_fields() we get pointer to Field from those arrays using saved field index. Item_trigger_field: - Added field_idx member to store index of Field object corresponding to this Item in TABLE::field array. - Added triggers member to be able to access to parent Table_trigger_list object from fix_fields() method. - setup_field() no longer needs to know for which type of event this trigger is, since it does not make decision Field for which buffer (record[0] or record[1] is appropriate for this Item_trigger_field) sql/mysql_priv.h: Added fill_record_n_invoke_before_triggers() methods. They are simple wrappers around fill_record() which invoke proper before trigger right after filling record with values. sql/sql_base.cc: Added fill_record_n_invoke_before_triggers() methods. They are simple wrappers around fill_record() which invoke proper before trigger right after filling record with values. sql/sql_delete.cc: mysql_delete(): Now we stop statement execution if one of triggers failed, we also don't execute after delete trigger if we failed to delete row from the table (We also pass information about which buffer contains old version of row to process_triggers()). multi_delete::send_data()/do_deletes(): Now we also invoke triggers in case of multi-delete. sql/sql_insert.cc: mysql_insert(): Moved invocation of before triggers to fill_record_n_invoke_before_triggers() method. After triggers are now executed as part of write_record(). (as nice side effect now we also stop statement execution if one of triggers fail). write_record(): Invoke after insert trigger after performing insert. Also invoke proper triggers if insert is converted to update or conflicting row is deleted. Cleaned up error handling a bit - no sense to report error via handler::print_error if it was not generated by handler method and was reported before. Also now we will execute after trigger only if we really have written row to the table. select_insert::send_data()/store_values(): We should also execute INSERT triggers for INSERT ... SELECT statement. sql/sql_load.cc: read_fixed_length()/read_sep_field(): We should execute INSERT triggers when processing LOAD DATA statement. Small cleanup in auto-increment related code. Also moved check for thd->killed which is used to abort LOAD DATA in case of problems in 'traditional' mode to better place.. sql/sql_trigger.cc: Since it turned out that at trigger loading time we can't say in which buffer TABLE::record[0] or record[1] old version of row will be stored we have to change our approach to binding of Item_trigger_field to Field instances. Now after trigger parsing (in Item_trigger_field::setup_table()) we only find index of proper Field in the TABLE::field array. Then before trigger is invoked we set Table_triggers_list::old_field/new_field so they point to arrays holding Field instances bound to buffers with proper row versions. And as last step in Item_trigger_field::fix_fields() we get pointer to Field from those arrays using saved field index. Table_triggers_list methods were changed to implement this approach (see also comments for sql_trigger.h). sql/sql_trigger.h: Since it turned out that at trigger loading time we can't say in which buffer TABLE::record[0] or record[1] old version of row will be stored we have to change our approach to binding of Item_trigger_field to Field instances. Now after trigger parsing (in Item_trigger_field::setup_table()) we only find index of proper Field in the TABLE::field array. Then before trigger is invoked we set Table_triggers_list::old_field/new_field so they point to arrays holding Field instances bound to buffers with proper row versions. And as last step in Item_trigger_field::fix_fields() we get pointer to Field from those arrays using saved field index. Changed Table_triggers_list to implement this new approach: - Added record1_field member to store array of Field objects bound to TABLE::record[1] buffer (instead of existing old_field member) - Added new_field member and changed meaning of old_field member. During trigger execution they should point to arrays of Field objects bound to buffers holding new and old versions of row respectively. - Added 'table' member to be able to get access to TABLE instance (for which this trigger list object was created) from process_triggers() method. - Now process_triggers() method sets old_field and new_field members properly before executing triggers body (basing on new old_row_is_record1 parameter value). - Renamed prepare_old_row_accessors_method() to prepare_record1_accessors() Also added has_before_update_triggers() method which allows to check whenever any before update triggers exist for table. sql/sql_update.cc: mysql_update(): Now we invoke before triggers in fill_record_n_invoke_before_triggers() method. Also now we abort statement execution when one of triggers fail. safe_update_on_fly(): When we are trying to understand if we can update first table in multi update on the fly we should take into account that BEFORE UPDATE trigger can change field values. multi_update::send_data()/do_updates() We should execute proper triggers when doing multi-update (in both cases when we do it on the fly and using temporary tables).
This commit is contained in:
parent
4acfc0b611
commit
1fa7c69d31
@ -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
@ -4546,40 +4546,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -4604,9 +4604,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
@ -1601,13 +1601,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
|
||||||
{
|
{
|
||||||
@ -1617,13 +1622,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_afree(key);
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2013,12 +2078,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);
|
||||||
}
|
}
|
||||||
@ -2027,9 +2107,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)
|
||||||
@ -2172,7 +2254,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.
|
||||||
|
|
||||||
@ -1099,15 +1107,21 @@ static bool safe_update_on_fly(JOIN_TAB *join_tab, List<Item> *fields)
|
|||||||
case JT_EQ_REF:
|
case JT_EQ_REF:
|
||||||
return TRUE; // At most one matching row
|
return TRUE; // At most one matching row
|
||||||
case JT_REF:
|
case JT_REF:
|
||||||
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
|
||||||
@ -1170,8 +1184,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++;
|
||||||
@ -1207,8 +1223,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
|
||||||
@ -1329,6 +1352,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],
|
||||||
@ -1338,6 +1366,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1360,6 +1393,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