mirror of
https://github.com/MariaDB/server.git
synced 2025-05-29 21:42:28 +03:00
#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).
318 lines
9.5 KiB
Plaintext
318 lines
9.5 KiB
Plaintext
drop table if exists t1, t2;
|
|
drop view if exists v1;
|
|
drop database if exists mysqltest;
|
|
create table t1 (i int);
|
|
create trigger trg before insert on t1 for each row set @a:=1;
|
|
set @a:=0;
|
|
select @a;
|
|
@a
|
|
0
|
|
insert into t1 values (1);
|
|
select @a;
|
|
@a
|
|
1
|
|
drop trigger t1.trg;
|
|
create trigger trg before insert on t1 for each row set @a:=new.i;
|
|
insert into t1 values (123);
|
|
select @a;
|
|
@a
|
|
123
|
|
drop trigger t1.trg;
|
|
drop table t1;
|
|
create table t1 (i int not null, j int);
|
|
create trigger trg before insert on t1 for each row
|
|
begin
|
|
if isnull(new.j) then
|
|
set new.j:= new.i * 10;
|
|
end if;
|
|
end|
|
|
insert into t1 (i) values (1)|
|
|
insert into t1 (i,j) values (2, 3)|
|
|
select * from t1|
|
|
i j
|
|
1 10
|
|
2 3
|
|
drop trigger t1.trg|
|
|
drop table t1|
|
|
create table t1 (i int not null primary key);
|
|
create trigger trg after insert on t1 for each row
|
|
set @a:= if(@a,concat(@a, ":", new.i), new.i);
|
|
set @a:="";
|
|
insert into t1 values (2),(3),(4),(5);
|
|
select @a;
|
|
@a
|
|
2:3:4:5
|
|
drop trigger t1.trg;
|
|
drop table t1;
|
|
create table t1 (aid int not null primary key, balance int not null default 0);
|
|
insert into t1 values (1, 1000), (2,3000);
|
|
create trigger trg before update on t1 for each row
|
|
begin
|
|
declare loc_err varchar(255);
|
|
if abs(new.balance - old.balance) > 1000 then
|
|
set new.balance:= old.balance;
|
|
set loc_err := concat("Too big change for aid = ", new.aid);
|
|
set @update_failed:= if(@update_failed, concat(@a, ":", loc_err), loc_err);
|
|
end if;
|
|
end|
|
|
set @update_failed:=""|
|
|
update t1 set balance=1500|
|
|
select @update_failed;
|
|
select * from t1|
|
|
@update_failed
|
|
Too big change for aid = 2
|
|
aid balance
|
|
1 1500
|
|
2 3000
|
|
drop trigger t1.trg|
|
|
drop table t1|
|
|
create table t1 (i int);
|
|
insert into t1 values (1),(2),(3),(4);
|
|
create trigger trg after update on t1 for each row
|
|
set @total_change:=@total_change + new.i - old.i;
|
|
set @total_change:=0;
|
|
update t1 set i=3;
|
|
select @total_change;
|
|
@total_change
|
|
2
|
|
drop trigger t1.trg;
|
|
drop table t1;
|
|
create table t1 (i int);
|
|
insert into t1 values (1),(2),(3),(4);
|
|
create trigger trg before delete on t1 for each row
|
|
set @del_sum:= @del_sum + old.i;
|
|
set @del_sum:= 0;
|
|
delete from t1 where i <= 3;
|
|
select @del_sum;
|
|
@del_sum
|
|
6
|
|
drop trigger t1.trg;
|
|
drop table t1;
|
|
create table t1 (i int);
|
|
insert into t1 values (1),(2),(3),(4);
|
|
create trigger trg after delete on t1 for each row set @del:= 1;
|
|
set @del:= 0;
|
|
delete from t1 where i <> 0;
|
|
select @del;
|
|
@del
|
|
1
|
|
drop trigger t1.trg;
|
|
drop table t1;
|
|
create table t1 (i int, j int);
|
|
create trigger trg1 before insert on t1 for each row
|
|
begin
|
|
if new.j > 10 then
|
|
set new.j := 10;
|
|
end if;
|
|
end|
|
|
create trigger trg2 before update on t1 for each row
|
|
begin
|
|
if old.i % 2 = 0 then
|
|
set new.j := -1;
|
|
end if;
|
|
end|
|
|
create trigger trg3 after update on t1 for each row
|
|
begin
|
|
if new.j = -1 then
|
|
set @fired:= "Yes";
|
|
end if;
|
|
end|
|
|
set @fired:="";
|
|
insert into t1 values (1,2),(2,3),(3,14);
|
|
select @fired;
|
|
@fired
|
|
|
|
select * from t1;
|
|
i j
|
|
1 2
|
|
2 3
|
|
3 10
|
|
update t1 set j= 20;
|
|
select @fired;
|
|
@fired
|
|
Yes
|
|
select * from t1;
|
|
i j
|
|
1 20
|
|
2 -1
|
|
3 20
|
|
drop trigger t1.trg1;
|
|
drop trigger t1.trg2;
|
|
drop trigger t1.trg3;
|
|
drop table t1;
|
|
create table t1 (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 trigger trg before insert on t1 for each row set @a:= old.i;
|
|
ERROR HY000: There is no OLD row in on INSERT trigger
|
|
create trigger trg before delete on t1 for each row set @a:= new.i;
|
|
ERROR HY000: There is no NEW row in on DELETE trigger
|
|
create trigger trg before update on t1 for each row set old.i:=1;
|
|
ERROR HY000: Updating of OLD row is not allowed in trigger
|
|
create trigger trg before delete on t1 for each row set new.i:=1;
|
|
ERROR HY000: There is no NEW row in on DELETE trigger
|
|
create trigger trg after update on t1 for each row set new.i:=1;
|
|
ERROR HY000: Updating of NEW row is not allowed in after trigger
|
|
create trigger trg before update on t1 for each row set new.j:=1;
|
|
ERROR 42S22: Unknown column 'j' in 'NEW'
|
|
create trigger trg before update on t1 for each row set @a:=old.j;
|
|
ERROR 42S22: Unknown column 'j' in 'OLD'
|
|
create trigger trg before insert on t2 for each row set @a:=1;
|
|
ERROR 42S02: Table 'test.t2' doesn't exist
|
|
create trigger trg before insert on t1 for each row set @a:=1;
|
|
create trigger trg after insert on t1 for each row set @a:=1;
|
|
ERROR HY000: Trigger already exists
|
|
create trigger trg2 before insert on t1 for each row set @a:=1;
|
|
ERROR HY000: Trigger already exists
|
|
drop trigger t1.trg;
|
|
drop trigger t1.trg;
|
|
ERROR HY000: Trigger does not exist
|
|
create view v1 as select * from t1;
|
|
create trigger trg before insert on v1 for each row set @a:=1;
|
|
ERROR HY000: Trigger's 'v1' is view or temporary table
|
|
drop view v1;
|
|
drop table t1;
|
|
create temporary table t1 (i int);
|
|
create trigger trg before insert on t1 for each row set @a:=1;
|
|
ERROR HY000: Trigger's 't1' is view or temporary table
|
|
drop table t1;
|
|
create table t1 (x1col char);
|
|
create trigger tx1 before insert on t1 for each row set new.x1col = 'x';
|
|
insert into t1 values ('y');
|
|
drop trigger t1.tx1;
|
|
drop table t1;
|
|
create table t1 (i int) engine=myisam;
|
|
insert into t1 values (1), (2);
|
|
create trigger trg1 before delete on t1 for each row set @del_before:= @del_before + old.i;
|
|
create trigger trg2 after delete on t1 for each row set @del_after:= @del_after + old.i;
|
|
set @del_before:=0, @del_after:= 0;
|
|
delete from t1;
|
|
select @del_before, @del_after;
|
|
@del_before @del_after
|
|
3 3
|
|
drop trigger t1.trg1;
|
|
drop trigger t1.trg2;
|
|
drop table t1;
|
|
create table t1 (a int);
|
|
create trigger trg1 before insert on t1 for each row set new.a= 10;
|
|
drop table t1;
|
|
create table t1 (a int);
|
|
insert into t1 values ();
|
|
select * from t1;
|
|
a
|
|
NULL
|
|
drop table t1;
|
|
create database mysqltest;
|
|
use mysqltest;
|
|
create table t1 (i int);
|
|
create trigger trg1 before insert on t1 for each row set @a:= 1;
|
|
drop database mysqltest;
|
|
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;
|