MDEV-9095: Executing triggers on slave in row-based replication
This commit is contained in:
parent
f1ca1f37c9
commit
af3180ab6f
5
mysql-test/include/have_rbr_triggers.inc
Normal file
5
mysql-test/include/have_rbr_triggers.inc
Normal file
@ -0,0 +1,5 @@
|
||||
if (`select count(*) = 0 from information_schema.session_variables where variable_name = 'slave_run_triggers_for_rbr'`)
|
||||
{
|
||||
skip RBR triggers are not available;
|
||||
}
|
||||
|
@ -1301,6 +1301,7 @@ slave-max-allowed-packet 1073741824
|
||||
slave-net-timeout 3600
|
||||
slave-parallel-max-queued 131072
|
||||
slave-parallel-threads 0
|
||||
slave-run-triggers-for-rbr NO
|
||||
slave-skip-errors (No default value)
|
||||
slave-sql-verify-checksum TRUE
|
||||
slave-transaction-retries 10
|
||||
|
240
mysql-test/suite/rpl/r/rpl_row_triggers.result
Normal file
240
mysql-test/suite/rpl/r/rpl_row_triggers.result
Normal file
@ -0,0 +1,240 @@
|
||||
include/master-slave.inc
|
||||
[connection master]
|
||||
# Test of row replication with triggers on the slave side
|
||||
CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1)) engine=innodb;
|
||||
SELECT * FROM t1;
|
||||
C1 C2
|
||||
SET @old_slave_exec_mode= @@global.slave_exec_mode;
|
||||
SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
|
||||
SET @@global.slave_exec_mode= IDEMPOTENT;
|
||||
SET @@global.slave_run_triggers_for_rbr= YES;
|
||||
SELECT * FROM t1;
|
||||
C1 C2
|
||||
create table t2 (id char(2) primary key, cnt int, o char(1), n char(1));
|
||||
insert into t2 values
|
||||
('u0', 0, ' ', ' '),('u1', 0, ' ', ' '),
|
||||
('d0', 0, ' ', ' '),('d1', 0, ' ', ' '),
|
||||
('i0', 0, ' ', ' '),('i1', 0, ' ', ' ');
|
||||
create trigger t1_cnt_b before update on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u0';
|
||||
create trigger t1_cnt_db before delete on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=' ' where id = 'd0';
|
||||
create trigger t1_cnt_ib before insert on t1 for each row
|
||||
update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i0';
|
||||
create trigger t1_cnt_a after update on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u1';
|
||||
create trigger t1_cnt_da after delete on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=' ' where id = 'd1';
|
||||
create trigger t1_cnt_ia after insert on t1 for each row
|
||||
update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i1';
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 0
|
||||
d1 0
|
||||
i0 0
|
||||
i1 0
|
||||
u0 0
|
||||
u1 0
|
||||
# INSERT triggers test
|
||||
insert into t1 values ('a','b');
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 0
|
||||
d1 0
|
||||
i0 1 a
|
||||
i1 1 a
|
||||
u0 0
|
||||
u1 0
|
||||
# UPDATE triggers test
|
||||
update t1 set C1= 'd';
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 0
|
||||
d1 0
|
||||
i0 1 a
|
||||
i1 1 a
|
||||
u0 1 a d
|
||||
u1 1 a d
|
||||
# DELETE triggers test
|
||||
delete from t1 where C1='d';
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 1 d
|
||||
d1 1 d
|
||||
i0 1 a
|
||||
i1 1 a
|
||||
u0 1 a d
|
||||
u1 1 a d
|
||||
# INSERT triggers which cause also UPDATE test (insert duplicate row)
|
||||
insert into t1 values ('0','1');
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 1 d
|
||||
d1 1 d
|
||||
i0 2 0
|
||||
i1 2 0
|
||||
u0 1 a d
|
||||
u1 1 a d
|
||||
insert into t1 values ('0','1');
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 1 d
|
||||
d1 1 d
|
||||
i0 3 0
|
||||
i1 3 0
|
||||
u0 2 0 0
|
||||
u1 2 0 0
|
||||
# INSERT triggers which cause also DELETE test
|
||||
# (insert duplicate row in table referenced by foreign key)
|
||||
insert into t1 values ('1','1');
|
||||
CREATE TABLE t3 (C1 CHAR(1) primary key, FOREIGN KEY (C1) REFERENCES t1(C1) ) engine=innodb;
|
||||
insert into t1 values ('1','1');
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 2 1
|
||||
d1 2 1
|
||||
i0 5 1
|
||||
i1 5 1
|
||||
u0 2 0 0
|
||||
u1 2 0 0
|
||||
drop table t3,t1;
|
||||
SET @@global.slave_exec_mode= @old_slave_exec_mode;
|
||||
SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
|
||||
drop table t2;
|
||||
CREATE TABLE t1 (i INT) ENGINE=InnoDB;
|
||||
CREATE TABLE t2 (i INT) ENGINE=InnoDB;
|
||||
SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
|
||||
SET GLOBAL slave_run_triggers_for_rbr=YES;
|
||||
CREATE TRIGGER tr AFTER INSERT ON t1 FOR EACH ROW
|
||||
INSERT INTO t2 VALUES (new.i);
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES (1);
|
||||
INSERT INTO t1 VALUES (2);
|
||||
COMMIT;
|
||||
select * from t2;
|
||||
i
|
||||
1
|
||||
2
|
||||
SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
|
||||
drop tables t2,t1;
|
||||
# Triggers on slave do not work if master has some
|
||||
CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1)) engine=innodb;
|
||||
SELECT * FROM t1;
|
||||
C1 C2
|
||||
create trigger t1_dummy before delete on t1 for each row
|
||||
set @dummy= 1;
|
||||
SET @old_slave_exec_mode= @@global.slave_exec_mode;
|
||||
SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
|
||||
SET @@global.slave_exec_mode= IDEMPOTENT;
|
||||
SET @@global.slave_run_triggers_for_rbr= YES;
|
||||
SELECT * FROM t1;
|
||||
C1 C2
|
||||
create table t2 (id char(2) primary key, cnt int, o char(1), n char(1));
|
||||
insert into t2 values
|
||||
('u0', 0, ' ', ' '),('u1', 0, ' ', ' '),
|
||||
('d0', 0, ' ', ' '),('d1', 0, ' ', ' '),
|
||||
('i0', 0, ' ', ' '),('i1', 0, ' ', ' ');
|
||||
create trigger t1_cnt_b before update on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u0';
|
||||
create trigger t1_cnt_ib before insert on t1 for each row
|
||||
update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i0';
|
||||
create trigger t1_cnt_a after update on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u1';
|
||||
create trigger t1_cnt_da after delete on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=' ' where id = 'd1';
|
||||
create trigger t1_cnt_ia after insert on t1 for each row
|
||||
update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i1';
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 0
|
||||
d1 0
|
||||
i0 0
|
||||
i1 0
|
||||
u0 0
|
||||
u1 0
|
||||
# INSERT triggers test
|
||||
insert into t1 values ('a','b');
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 0
|
||||
d1 0
|
||||
i0 0
|
||||
i1 0
|
||||
u0 0
|
||||
u1 0
|
||||
# UPDATE triggers test
|
||||
update t1 set C1= 'd';
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 0
|
||||
d1 0
|
||||
i0 0
|
||||
i1 0
|
||||
u0 0
|
||||
u1 0
|
||||
# DELETE triggers test
|
||||
delete from t1 where C1='d';
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 0
|
||||
d1 0
|
||||
i0 0
|
||||
i1 0
|
||||
u0 0
|
||||
u1 0
|
||||
# INSERT triggers which cause also UPDATE test (insert duplicate row)
|
||||
insert into t1 values ('0','1');
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 0
|
||||
d1 0
|
||||
i0 1 0
|
||||
i1 1 0
|
||||
u0 0
|
||||
u1 0
|
||||
insert into t1 values ('0','1');
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 0
|
||||
d1 0
|
||||
i0 1 0
|
||||
i1 1 0
|
||||
u0 0
|
||||
u1 0
|
||||
# INSERT triggers which cause also DELETE test
|
||||
# (insert duplicate row in table referenced by foreign key)
|
||||
insert into t1 values ('1','1');
|
||||
CREATE TABLE t3 (C1 CHAR(1) primary key, FOREIGN KEY (C1) REFERENCES t1(C1) ) engine=innodb;
|
||||
insert into t1 values ('1','1');
|
||||
SELECT * FROM t2 order by id;
|
||||
id cnt o n
|
||||
d0 0
|
||||
d1 0
|
||||
i0 2 1
|
||||
i1 2 1
|
||||
u0 0
|
||||
u1 0
|
||||
drop table t3,t1;
|
||||
SET @@global.slave_exec_mode= @old_slave_exec_mode;
|
||||
SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
|
||||
drop table t2;
|
||||
#
|
||||
# MDEV-5513: Trigger is applied to the rows after first one
|
||||
#
|
||||
create table t1 (a int, b int);
|
||||
create table tlog (a int);
|
||||
set sql_log_bin=0;
|
||||
create trigger tr1 after insert on t1 for each row insert into tlog values (1);
|
||||
set sql_log_bin=1;
|
||||
set @slave_run_triggers_for_rbr.saved = @@slave_run_triggers_for_rbr;
|
||||
set global slave_run_triggers_for_rbr=1;
|
||||
create trigger tr2 before insert on t1 for each row set new.b = new.a;
|
||||
insert into t1 values (1,10),(2,20),(3,30);
|
||||
select * from t1;
|
||||
a b
|
||||
1 10
|
||||
2 20
|
||||
3 30
|
||||
set global slave_run_triggers_for_rbr = @slave_run_triggers_for_rbr.saved;
|
||||
drop table t1, tlog;
|
||||
include/rpl_end.inc
|
14
mysql-test/suite/rpl/r/rpl_row_triggers_sbr.result
Normal file
14
mysql-test/suite/rpl/r/rpl_row_triggers_sbr.result
Normal file
@ -0,0 +1,14 @@
|
||||
include/master-slave.inc
|
||||
[connection master]
|
||||
set binlog_format = row;
|
||||
create table t1 (i int);
|
||||
create table t2 (i int);
|
||||
SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
|
||||
set global slave_run_triggers_for_rbr=YES;
|
||||
create trigger tr_before before insert on t1 for each row
|
||||
insert into t2 values (1);
|
||||
insert into t1 values (1);
|
||||
include/wait_for_slave_sql_error_and_skip.inc [errno=1666]
|
||||
drop tables t1,t2;
|
||||
SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
|
||||
include/rpl_end.inc
|
280
mysql-test/suite/rpl/t/rpl_row_triggers.test
Normal file
280
mysql-test/suite/rpl/t/rpl_row_triggers.test
Normal file
@ -0,0 +1,280 @@
|
||||
-- source include/have_binlog_format_row.inc
|
||||
-- source include/have_rbr_triggers.inc
|
||||
-- source include/have_innodb.inc
|
||||
-- source include/master-slave.inc
|
||||
|
||||
-- echo # Test of row replication with triggers on the slave side
|
||||
|
||||
connection master;
|
||||
CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1)) engine=innodb;
|
||||
SELECT * FROM t1;
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SET @old_slave_exec_mode= @@global.slave_exec_mode;
|
||||
SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
|
||||
SET @@global.slave_exec_mode= IDEMPOTENT;
|
||||
SET @@global.slave_run_triggers_for_rbr= YES;
|
||||
SELECT * FROM t1;
|
||||
create table t2 (id char(2) primary key, cnt int, o char(1), n char(1));
|
||||
insert into t2 values
|
||||
('u0', 0, ' ', ' '),('u1', 0, ' ', ' '),
|
||||
('d0', 0, ' ', ' '),('d1', 0, ' ', ' '),
|
||||
('i0', 0, ' ', ' '),('i1', 0, ' ', ' ');
|
||||
create trigger t1_cnt_b before update on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u0';
|
||||
create trigger t1_cnt_db before delete on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=' ' where id = 'd0';
|
||||
create trigger t1_cnt_ib before insert on t1 for each row
|
||||
update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i0';
|
||||
create trigger t1_cnt_a after update on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u1';
|
||||
create trigger t1_cnt_da after delete on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=' ' where id = 'd1';
|
||||
create trigger t1_cnt_ia after insert on t1 for each row
|
||||
update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i1';
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
connection master;
|
||||
--echo # INSERT triggers test
|
||||
insert into t1 values ('a','b');
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
connection master;
|
||||
|
||||
--echo # UPDATE triggers test
|
||||
update t1 set C1= 'd';
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
connection master;
|
||||
--echo # DELETE triggers test
|
||||
delete from t1 where C1='d';
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
--echo # INSERT triggers which cause also UPDATE test (insert duplicate row)
|
||||
insert into t1 values ('0','1');
|
||||
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
connection master;
|
||||
|
||||
insert into t1 values ('0','1');
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
|
||||
--echo # INSERT triggers which cause also DELETE test
|
||||
--echo # (insert duplicate row in table referenced by foreign key)
|
||||
insert into t1 values ('1','1');
|
||||
|
||||
connection master;
|
||||
|
||||
CREATE TABLE t3 (C1 CHAR(1) primary key, FOREIGN KEY (C1) REFERENCES t1(C1) ) engine=innodb;
|
||||
|
||||
insert into t1 values ('1','1');
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
connection master;
|
||||
|
||||
drop table t3,t1;
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SET @@global.slave_exec_mode= @old_slave_exec_mode;
|
||||
SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
|
||||
drop table t2;
|
||||
|
||||
--connection master
|
||||
|
||||
CREATE TABLE t1 (i INT) ENGINE=InnoDB;
|
||||
CREATE TABLE t2 (i INT) ENGINE=InnoDB;
|
||||
|
||||
--sync_slave_with_master
|
||||
|
||||
SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
|
||||
SET GLOBAL slave_run_triggers_for_rbr=YES;
|
||||
|
||||
CREATE TRIGGER tr AFTER INSERT ON t1 FOR EACH ROW
|
||||
INSERT INTO t2 VALUES (new.i);
|
||||
|
||||
--connection master
|
||||
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES (1);
|
||||
INSERT INTO t1 VALUES (2);
|
||||
COMMIT;
|
||||
|
||||
--sync_slave_with_master
|
||||
select * from t2;
|
||||
SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
|
||||
|
||||
--connection master
|
||||
drop tables t2,t1;
|
||||
|
||||
--sync_slave_with_master
|
||||
|
||||
-- echo # Triggers on slave do not work if master has some
|
||||
|
||||
connection master;
|
||||
CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1)) engine=innodb;
|
||||
SELECT * FROM t1;
|
||||
|
||||
create trigger t1_dummy before delete on t1 for each row
|
||||
set @dummy= 1;
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SET @old_slave_exec_mode= @@global.slave_exec_mode;
|
||||
SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
|
||||
SET @@global.slave_exec_mode= IDEMPOTENT;
|
||||
SET @@global.slave_run_triggers_for_rbr= YES;
|
||||
SELECT * FROM t1;
|
||||
create table t2 (id char(2) primary key, cnt int, o char(1), n char(1));
|
||||
insert into t2 values
|
||||
('u0', 0, ' ', ' '),('u1', 0, ' ', ' '),
|
||||
('d0', 0, ' ', ' '),('d1', 0, ' ', ' '),
|
||||
('i0', 0, ' ', ' '),('i1', 0, ' ', ' ');
|
||||
create trigger t1_cnt_b before update on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u0';
|
||||
create trigger t1_cnt_ib before insert on t1 for each row
|
||||
update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i0';
|
||||
create trigger t1_cnt_a after update on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=new.C1 where id = 'u1';
|
||||
create trigger t1_cnt_da after delete on t1 for each row
|
||||
update t2 set cnt=cnt+1, o=old.C1, n=' ' where id = 'd1';
|
||||
create trigger t1_cnt_ia after insert on t1 for each row
|
||||
update t2 set cnt=cnt+1, n=new.C1, o=' ' where id = 'i1';
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
connection master;
|
||||
--echo # INSERT triggers test
|
||||
insert into t1 values ('a','b');
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
connection master;
|
||||
|
||||
--echo # UPDATE triggers test
|
||||
update t1 set C1= 'd';
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
connection master;
|
||||
--echo # DELETE triggers test
|
||||
delete from t1 where C1='d';
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
--echo # INSERT triggers which cause also UPDATE test (insert duplicate row)
|
||||
insert into t1 values ('0','1');
|
||||
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
connection master;
|
||||
|
||||
insert into t1 values ('0','1');
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
|
||||
--echo # INSERT triggers which cause also DELETE test
|
||||
--echo # (insert duplicate row in table referenced by foreign key)
|
||||
insert into t1 values ('1','1');
|
||||
|
||||
connection master;
|
||||
|
||||
CREATE TABLE t3 (C1 CHAR(1) primary key, FOREIGN KEY (C1) REFERENCES t1(C1) ) engine=innodb;
|
||||
|
||||
insert into t1 values ('1','1');
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SELECT * FROM t2 order by id;
|
||||
|
||||
connection master;
|
||||
|
||||
drop table t3,t1;
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
connection slave;
|
||||
SET @@global.slave_exec_mode= @old_slave_exec_mode;
|
||||
SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
|
||||
drop table t2;
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-5513: Trigger is applied to the rows after first one
|
||||
--echo #
|
||||
|
||||
--connection master
|
||||
|
||||
create table t1 (a int, b int);
|
||||
create table tlog (a int);
|
||||
|
||||
set sql_log_bin=0;
|
||||
create trigger tr1 after insert on t1 for each row insert into tlog values (1);
|
||||
set sql_log_bin=1;
|
||||
|
||||
sync_slave_with_master;
|
||||
--connection slave
|
||||
|
||||
set @slave_run_triggers_for_rbr.saved = @@slave_run_triggers_for_rbr;
|
||||
set global slave_run_triggers_for_rbr=1;
|
||||
create trigger tr2 before insert on t1 for each row set new.b = new.a;
|
||||
|
||||
--connection master
|
||||
|
||||
insert into t1 values (1,10),(2,20),(3,30);
|
||||
|
||||
--sync_slave_with_master
|
||||
|
||||
select * from t1;
|
||||
|
||||
# Cleanup
|
||||
|
||||
set global slave_run_triggers_for_rbr = @slave_run_triggers_for_rbr.saved;
|
||||
|
||||
--connection master
|
||||
|
||||
drop table t1, tlog;
|
||||
|
||||
sync_slave_with_master;
|
||||
|
||||
|
||||
|
||||
--source include/rpl_end.inc
|
43
mysql-test/suite/rpl/t/rpl_row_triggers_sbr.test
Normal file
43
mysql-test/suite/rpl/t/rpl_row_triggers_sbr.test
Normal file
@ -0,0 +1,43 @@
|
||||
--source include/have_binlog_format_statement.inc
|
||||
--source include/have_rbr_triggers.inc
|
||||
--source include/master-slave.inc
|
||||
|
||||
--disable_query_log
|
||||
CALL mtr.add_suppression("Cannot execute statement: impossible to write to binary log since statement is in row format and BINLOG_FORMAT = STATEMENT");
|
||||
--enable_query_log
|
||||
|
||||
set binlog_format = row;
|
||||
|
||||
create table t1 (i int);
|
||||
create table t2 (i int);
|
||||
|
||||
--sync_slave_with_master
|
||||
--disable_query_log
|
||||
CALL mtr.add_suppression("impossible to write to binary log since statement is in row format and BINLOG_FORMAT = STATEMENT");
|
||||
--enable_query_log
|
||||
|
||||
SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr;
|
||||
set global slave_run_triggers_for_rbr=YES;
|
||||
|
||||
create trigger tr_before before insert on t1 for each row
|
||||
insert into t2 values (1);
|
||||
|
||||
--connection master
|
||||
|
||||
insert into t1 values (1);
|
||||
|
||||
--connection slave
|
||||
|
||||
--let $slave_sql_errno= 1666
|
||||
--source include/wait_for_slave_sql_error_and_skip.inc
|
||||
|
||||
--connection master
|
||||
|
||||
drop tables t1,t2;
|
||||
|
||||
--sync_slave_with_master
|
||||
SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr;
|
||||
|
||||
--connection master
|
||||
|
||||
--source include/rpl_end.inc
|
269
sql/log_event.cc
269
sql/log_event.cc
@ -9169,7 +9169,8 @@ Rows_log_event::Rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid,
|
||||
m_type(event_type), m_extra_row_data(0)
|
||||
#ifdef HAVE_REPLICATION
|
||||
, m_curr_row(NULL), m_curr_row_end(NULL),
|
||||
m_key(NULL), m_key_info(NULL), m_key_nr(0)
|
||||
m_key(NULL), m_key_info(NULL), m_key_nr(0),
|
||||
master_had_triggers(0)
|
||||
#endif
|
||||
{
|
||||
/*
|
||||
@ -9218,7 +9219,8 @@ Rows_log_event::Rows_log_event(const char *buf, uint event_len,
|
||||
m_extra_row_data(0)
|
||||
#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
|
||||
, m_curr_row(NULL), m_curr_row_end(NULL),
|
||||
m_key(NULL), m_key_info(NULL), m_key_nr(0)
|
||||
m_key(NULL), m_key_info(NULL), m_key_nr(0),
|
||||
master_had_triggers(0)
|
||||
#endif
|
||||
{
|
||||
DBUG_ENTER("Rows_log_event::Rows_log_event(const char*,...)");
|
||||
@ -9473,10 +9475,28 @@ int Rows_log_event::do_add_row_data(uchar *row_data, size_t length)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
|
||||
#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
|
||||
|
||||
/**
|
||||
Restores empty table list as it was before trigger processing.
|
||||
|
||||
@note We have a lot of ASSERTS that check the lists when we close tables.
|
||||
There was the same problem with MERGE MYISAM tables and so here we try to
|
||||
go the same way.
|
||||
*/
|
||||
static void restore_empty_query_table_list(LEX *lex)
|
||||
{
|
||||
if (lex->first_not_own_table())
|
||||
(*lex->first_not_own_table()->prev_global)= NULL;
|
||||
lex->query_tables= NULL;
|
||||
lex->query_tables_last= &lex->query_tables;
|
||||
}
|
||||
|
||||
|
||||
int Rows_log_event::do_apply_event(rpl_group_info *rgi)
|
||||
{
|
||||
Relay_log_info const *rli= rgi->rli;
|
||||
TABLE* table;
|
||||
DBUG_ENTER("Rows_log_event::do_apply_event(Relay_log_info*)");
|
||||
int error= 0;
|
||||
/*
|
||||
@ -9556,6 +9576,28 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
|
||||
/* A small test to verify that objects have consistent types */
|
||||
DBUG_ASSERT(sizeof(thd->variables.option_bits) == sizeof(OPTION_RELAXED_UNIQUE_CHECKS));
|
||||
|
||||
if (slave_run_triggers_for_rbr)
|
||||
{
|
||||
LEX *lex= thd->lex;
|
||||
uint8 new_trg_event_map= get_trg_event_map();
|
||||
|
||||
/*
|
||||
Trigger's procedures work with global table list. So we have to add
|
||||
rgi->tables_to_lock content there to get trigger's in the list.
|
||||
|
||||
Then restore_empty_query_table_list() restore the list as it was
|
||||
*/
|
||||
DBUG_ASSERT(lex->query_tables == NULL);
|
||||
if ((lex->query_tables= rgi->tables_to_lock))
|
||||
rgi->tables_to_lock->prev_global= &lex->query_tables;
|
||||
|
||||
for (TABLE_LIST *tables= rgi->tables_to_lock; tables;
|
||||
tables= tables->next_global)
|
||||
{
|
||||
tables->trg_event_map= new_trg_event_map;
|
||||
lex->query_tables_last= &tables->next_global;
|
||||
}
|
||||
}
|
||||
if (open_and_lock_tables(thd, rgi->tables_to_lock, FALSE, 0))
|
||||
{
|
||||
uint actual_error= thd->get_stmt_da()->sql_errno();
|
||||
@ -9573,8 +9615,9 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
|
||||
"unexpected success or fatal error"));
|
||||
thd->is_slave_error= 1;
|
||||
}
|
||||
rgi->slave_close_thread_tables(thd);
|
||||
DBUG_RETURN(actual_error);
|
||||
/* remove trigger's tables */
|
||||
error= actual_error;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -9618,8 +9661,9 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
|
||||
having severe errors which should not be skiped.
|
||||
*/
|
||||
thd->is_slave_error= 1;
|
||||
rgi->slave_close_thread_tables(thd);
|
||||
DBUG_RETURN(ERR_BAD_TABLE_DEF);
|
||||
/* remove trigger's tables */
|
||||
error= ERR_BAD_TABLE_DEF;
|
||||
goto err;
|
||||
}
|
||||
DBUG_PRINT("debug", ("Table: %s.%s is compatible with master"
|
||||
" - conv_table: %p",
|
||||
@ -9645,21 +9689,35 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
|
||||
*/
|
||||
TABLE_LIST *ptr= rgi->tables_to_lock;
|
||||
for (uint i=0 ; ptr && (i < rgi->tables_to_lock_count); ptr= ptr->next_global, i++)
|
||||
{
|
||||
rgi->m_table_map.set_table(ptr->table_id, ptr->table);
|
||||
/*
|
||||
Following is passing flag about triggers on the server. The problem was
|
||||
to pass it between table map event and row event. I do it via extended
|
||||
TABLE_LIST (RPL_TABLE_LIST) but row event uses only TABLE so I need to
|
||||
find somehow the corresponding TABLE_LIST.
|
||||
*/
|
||||
if (m_table_id == ptr->table_id)
|
||||
{
|
||||
ptr->table->master_had_triggers=
|
||||
((RPL_TABLE_LIST*)ptr)->master_had_triggers;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_QUERY_CACHE
|
||||
query_cache.invalidate_locked_for_write(thd, rgi->tables_to_lock);
|
||||
#endif
|
||||
}
|
||||
|
||||
TABLE*
|
||||
table=
|
||||
m_table= rgi->m_table_map.get_table(m_table_id);
|
||||
|
||||
DBUG_PRINT("debug", ("m_table: 0x%lx, m_table_id: %lu", (ulong) m_table, m_table_id));
|
||||
table= m_table= rgi->m_table_map.get_table(m_table_id);
|
||||
|
||||
DBUG_PRINT("debug", ("m_table: 0x%lx, m_table_id: %lu%s",
|
||||
(ulong) m_table, m_table_id,
|
||||
table && master_had_triggers ?
|
||||
" (master had triggers)" : ""));
|
||||
if (table)
|
||||
{
|
||||
master_had_triggers= table->master_had_triggers;
|
||||
bool transactional_table= table->file->has_transactions();
|
||||
/*
|
||||
table == NULL means that this table should not be replicated
|
||||
@ -9823,9 +9881,13 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
|
||||
*/
|
||||
thd->reset_current_stmt_binlog_format_row();
|
||||
thd->is_slave_error= 1;
|
||||
DBUG_RETURN(error);
|
||||
/* remove trigger's tables */
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* remove trigger's tables */
|
||||
if (slave_run_triggers_for_rbr)
|
||||
restore_empty_query_table_list(thd->lex);
|
||||
if (get_flags(STMT_END_F) && (error= rows_event_stmt_cleanup(rgi, thd)))
|
||||
slave_rows_error_report(ERROR_LEVEL,
|
||||
thd->is_error() ? 0 : error,
|
||||
@ -9833,6 +9895,12 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
|
||||
get_type_str(),
|
||||
RPL_LOG_NAME, (ulong) log_pos);
|
||||
DBUG_RETURN(error);
|
||||
|
||||
err:
|
||||
if (slave_run_triggers_for_rbr)
|
||||
restore_empty_query_table_list(thd->lex);
|
||||
rgi->slave_close_thread_tables(thd);
|
||||
DBUG_RETURN(error);
|
||||
}
|
||||
|
||||
Log_event::enum_skip_reason
|
||||
@ -10306,6 +10374,7 @@ Table_map_log_event::Table_map_log_event(THD *thd, TABLE *tbl, ulong tid,
|
||||
{
|
||||
uchar cbuf[MAX_INT_WIDTH];
|
||||
uchar *cbuf_end;
|
||||
DBUG_ENTER("Table_map_log_event::Table_map_log_event(TABLE)");
|
||||
DBUG_ASSERT(m_table_id != ~0UL);
|
||||
/*
|
||||
In TABLE_SHARE, "db" and "table_name" are 0-terminated (see this comment in
|
||||
@ -10326,6 +10395,9 @@ Table_map_log_event::Table_map_log_event(THD *thd, TABLE *tbl, ulong tid,
|
||||
DBUG_ASSERT(static_cast<size_t>(cbuf_end - cbuf) <= sizeof(cbuf));
|
||||
m_data_size+= (cbuf_end - cbuf) + m_colcnt; // COLCNT and column types
|
||||
|
||||
if (tbl->triggers)
|
||||
m_flags|= TM_BIT_HAS_TRIGGERS_F;
|
||||
|
||||
/* If malloc fails, caught in is_valid() */
|
||||
if ((m_memory= (uchar*) my_malloc(m_colcnt, MYF(MY_WME))))
|
||||
{
|
||||
@ -10370,6 +10442,7 @@ Table_map_log_event::Table_map_log_event(THD *thd, TABLE *tbl, ulong tid,
|
||||
if (m_table->field[i]->maybe_null())
|
||||
m_null_bits[(i / 8)]+= 1 << (i % 8);
|
||||
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
#endif /* !defined(MYSQL_CLIENT) */
|
||||
|
||||
@ -10732,7 +10805,11 @@ int Table_map_log_event::do_apply_event(rpl_group_info *rgi)
|
||||
table_list->table_id= DBUG_EVALUATE_IF("inject_tblmap_same_id_maps_diff_table", 0, m_table_id);
|
||||
table_list->updating= 1;
|
||||
table_list->required_type= FRMTYPE_TABLE;
|
||||
DBUG_PRINT("debug", ("table: %s is mapped to %u", table_list->table_name, table_list->table_id));
|
||||
table_list->master_had_triggers= ((m_flags & TM_BIT_HAS_TRIGGERS_F) ? 1 : 0);
|
||||
DBUG_PRINT("debug", ("table: %s is mapped to %u%s",
|
||||
table_list->table_name, table_list->table_id,
|
||||
(table_list->master_had_triggers ?
|
||||
" (master had triggers)" : "")));
|
||||
enum_tbl_map_status tblmap_status= check_table_map(rgi, table_list);
|
||||
if (tblmap_status == OK_TO_PROCESS)
|
||||
{
|
||||
@ -10904,8 +10981,10 @@ void Table_map_log_event::print(FILE *, PRINT_EVENT_INFO *print_event_info)
|
||||
{
|
||||
print_header(&print_event_info->head_cache, print_event_info, TRUE);
|
||||
my_b_printf(&print_event_info->head_cache,
|
||||
"\tTable_map: %`s.%`s mapped to number %lu\n",
|
||||
m_dbnam, m_tblnam, (ulong) m_table_id);
|
||||
"\tTable_map: %`s.%`s mapped to number %lu%s\n",
|
||||
m_dbnam, m_tblnam, m_table_id,
|
||||
((m_flags & TM_BIT_HAS_TRIGGERS_F) ?
|
||||
" (has triggers)" : ""));
|
||||
print_base64(&print_event_info->body_cache, print_event_info, TRUE);
|
||||
}
|
||||
}
|
||||
@ -10981,6 +11060,8 @@ Write_rows_log_event::do_before_row_operations(const Slave_reporting_capability
|
||||
/*
|
||||
NDB specific: update from ndb master wrapped as Write_rows
|
||||
so that the event should be applied to replace slave's row
|
||||
|
||||
Also following is needed in case if we have AFTER DELETE triggers.
|
||||
*/
|
||||
m_table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE);
|
||||
/*
|
||||
@ -10995,6 +11076,8 @@ Write_rows_log_event::do_before_row_operations(const Slave_reporting_capability
|
||||
from the start.
|
||||
*/
|
||||
}
|
||||
if (slave_run_triggers_for_rbr && !master_had_triggers && m_table->triggers )
|
||||
m_table->prepare_triggers_for_insert_stmt_or_event();
|
||||
|
||||
/* Honor next number column if present */
|
||||
m_table->next_number_field= m_table->found_next_number_field;
|
||||
@ -11040,6 +11123,25 @@ Write_rows_log_event::do_after_row_operations(const Slave_reporting_capability *
|
||||
|
||||
#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
|
||||
|
||||
bool Rows_log_event::process_triggers(trg_event_type event,
|
||||
trg_action_time_type time_type,
|
||||
bool old_row_is_record1)
|
||||
{
|
||||
bool result;
|
||||
DBUG_ENTER("Rows_log_event::process_triggers");
|
||||
if (slave_run_triggers_for_rbr == SLAVE_RUN_TRIGGERS_FOR_RBR_YES)
|
||||
{
|
||||
tmp_disable_binlog(thd); /* Do not replicate the low-level changes. */
|
||||
result= m_table->triggers->process_triggers(thd, event,
|
||||
time_type, old_row_is_record1);
|
||||
reenable_binlog(thd);
|
||||
}
|
||||
else
|
||||
result= m_table->triggers->process_triggers(thd, event,
|
||||
time_type, old_row_is_record1);
|
||||
|
||||
DBUG_RETURN(result);
|
||||
}
|
||||
/*
|
||||
Check if there are more UNIQUE keys after the given key.
|
||||
*/
|
||||
@ -11122,6 +11224,8 @@ Rows_log_event::write_row(rpl_group_info *rgi,
|
||||
TABLE *table= m_table; // pointer to event's table
|
||||
int error;
|
||||
int UNINIT_VAR(keynum);
|
||||
const bool invoke_triggers=
|
||||
slave_run_triggers_for_rbr && !master_had_triggers && table->triggers;
|
||||
auto_afree_ptr<char> key(NULL);
|
||||
|
||||
prepare_record(table, m_width,
|
||||
@ -11131,13 +11235,17 @@ Rows_log_event::write_row(rpl_group_info *rgi,
|
||||
if ((error= unpack_current_row(rgi)))
|
||||
DBUG_RETURN(error);
|
||||
|
||||
if (m_curr_row == m_rows_buf)
|
||||
if (m_curr_row == m_rows_buf && !invoke_triggers)
|
||||
{
|
||||
/* this is the first row to be inserted, we estimate the rows with
|
||||
/*
|
||||
This table has no triggers so we can do bulk insert.
|
||||
|
||||
This is the first row to be inserted, we estimate the rows with
|
||||
the size of the first row and use that value to initialize
|
||||
storage engine for bulk insertion */
|
||||
storage engine for bulk insertion.
|
||||
*/
|
||||
ulong estimated_rows= (m_rows_end - m_curr_row) / (m_curr_row_end - m_curr_row);
|
||||
m_table->file->ha_start_bulk_insert(estimated_rows);
|
||||
table->file->ha_start_bulk_insert(estimated_rows);
|
||||
}
|
||||
|
||||
|
||||
@ -11147,6 +11255,12 @@ Rows_log_event::write_row(rpl_group_info *rgi,
|
||||
DBUG_PRINT_BITSET("debug", "read_set = %s", table->read_set);
|
||||
#endif
|
||||
|
||||
if (invoke_triggers &&
|
||||
process_triggers(TRG_EVENT_INSERT, TRG_ACTION_BEFORE, TRUE))
|
||||
{
|
||||
DBUG_RETURN(HA_ERR_GENERIC); // in case if error is not set yet
|
||||
}
|
||||
|
||||
/*
|
||||
Try to write record. If a corresponding record already exists in the table,
|
||||
we try to change it using ha_update_row() if possible. Otherwise we delete
|
||||
@ -11273,38 +11387,61 @@ Rows_log_event::write_row(rpl_group_info *rgi,
|
||||
!table->file->referenced_by_foreign_key())
|
||||
{
|
||||
DBUG_PRINT("info",("Updating row using ha_update_row()"));
|
||||
error=table->file->ha_update_row(table->record[1],
|
||||
table->record[0]);
|
||||
switch (error) {
|
||||
|
||||
case HA_ERR_RECORD_IS_THE_SAME:
|
||||
DBUG_PRINT("info",("ignoring HA_ERR_RECORD_IS_THE_SAME error from"
|
||||
" ha_update_row()"));
|
||||
error= 0;
|
||||
|
||||
case 0:
|
||||
break;
|
||||
|
||||
default:
|
||||
DBUG_PRINT("info",("ha_update_row() returns error %d",error));
|
||||
table->file->print_error(error, MYF(0));
|
||||
if (invoke_triggers &&
|
||||
process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_BEFORE, FALSE))
|
||||
error= HA_ERR_GENERIC; // in case if error is not set yet
|
||||
else
|
||||
{
|
||||
error= table->file->ha_update_row(table->record[1],
|
||||
table->record[0]);
|
||||
switch (error) {
|
||||
|
||||
case HA_ERR_RECORD_IS_THE_SAME:
|
||||
DBUG_PRINT("info",("ignoring HA_ERR_RECORD_IS_THE_SAME error from"
|
||||
" ha_update_row()"));
|
||||
error= 0;
|
||||
|
||||
case 0:
|
||||
break;
|
||||
|
||||
default:
|
||||
DBUG_PRINT("info",("ha_update_row() returns error %d",error));
|
||||
table->file->print_error(error, MYF(0));
|
||||
}
|
||||
if (invoke_triggers && !error &&
|
||||
(process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_AFTER, TRUE) ||
|
||||
process_triggers(TRG_EVENT_INSERT, TRG_ACTION_AFTER, TRUE)))
|
||||
error= HA_ERR_GENERIC; // in case if error is not set yet
|
||||
}
|
||||
|
||||
|
||||
DBUG_RETURN(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
DBUG_PRINT("info",("Deleting offending row and trying to write new one again"));
|
||||
if ((error= table->file->ha_delete_row(table->record[1])))
|
||||
if (invoke_triggers &&
|
||||
process_triggers(TRG_EVENT_DELETE, TRG_ACTION_BEFORE, TRUE))
|
||||
error= HA_ERR_GENERIC; // in case if error is not set yet
|
||||
else
|
||||
{
|
||||
DBUG_PRINT("info",("ha_delete_row() returns error %d",error));
|
||||
table->file->print_error(error, MYF(0));
|
||||
DBUG_RETURN(error);
|
||||
if ((error= table->file->ha_delete_row(table->record[1])))
|
||||
{
|
||||
DBUG_PRINT("info",("ha_delete_row() returns error %d",error));
|
||||
table->file->print_error(error, MYF(0));
|
||||
DBUG_RETURN(error);
|
||||
}
|
||||
if (invoke_triggers &&
|
||||
process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, TRUE))
|
||||
DBUG_RETURN(HA_ERR_GENERIC); // in case if error is not set yet
|
||||
}
|
||||
/* Will retry ha_write_row() with the offending row removed. */
|
||||
}
|
||||
}
|
||||
|
||||
if (invoke_triggers &&
|
||||
process_triggers(TRG_EVENT_INSERT, TRG_ACTION_AFTER, TRUE))
|
||||
error= HA_ERR_GENERIC; // in case if error is not set yet
|
||||
|
||||
DBUG_RETURN(error);
|
||||
}
|
||||
|
||||
@ -11336,6 +11473,16 @@ void Write_rows_log_event::print(FILE *file, PRINT_EVENT_INFO* print_event_info)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
|
||||
uint8 Write_rows_log_event::get_trg_event_map()
|
||||
{
|
||||
return (static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_INSERT)) |
|
||||
static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_UPDATE)) |
|
||||
static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_DELETE)));
|
||||
}
|
||||
#endif
|
||||
|
||||
/**************************************************************************
|
||||
Delete_rows_log_event member functions
|
||||
**************************************************************************/
|
||||
@ -11960,6 +12107,8 @@ Delete_rows_log_event::do_before_row_operations(const Slave_reporting_capability
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
if (slave_run_triggers_for_rbr && !master_had_triggers)
|
||||
m_table->prepare_triggers_for_delete_stmt_or_event();
|
||||
|
||||
return find_key();
|
||||
}
|
||||
@ -11980,6 +12129,8 @@ Delete_rows_log_event::do_after_row_operations(const Slave_reporting_capability
|
||||
int Delete_rows_log_event::do_exec_row(rpl_group_info *rgi)
|
||||
{
|
||||
int error;
|
||||
const bool invoke_triggers=
|
||||
slave_run_triggers_for_rbr && !master_had_triggers && m_table->triggers;
|
||||
DBUG_ASSERT(m_table != NULL);
|
||||
|
||||
if (!(error= find_row(rgi)))
|
||||
@ -11987,7 +12138,14 @@ int Delete_rows_log_event::do_exec_row(rpl_group_info *rgi)
|
||||
/*
|
||||
Delete the record found, located in record[0]
|
||||
*/
|
||||
error= m_table->file->ha_delete_row(m_table->record[0]);
|
||||
if (invoke_triggers &&
|
||||
process_triggers(TRG_EVENT_DELETE, TRG_ACTION_BEFORE, FALSE))
|
||||
error= HA_ERR_GENERIC; // in case if error is not set yet
|
||||
if (!error)
|
||||
error= m_table->file->ha_delete_row(m_table->record[0]);
|
||||
if (invoke_triggers && !error &&
|
||||
process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, FALSE))
|
||||
error= HA_ERR_GENERIC; // in case if error is not set yet
|
||||
m_table->file->ha_index_or_rnd_end();
|
||||
}
|
||||
return error;
|
||||
@ -12004,6 +12162,13 @@ void Delete_rows_log_event::print(FILE *file,
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
|
||||
uint8 Delete_rows_log_event::get_trg_event_map()
|
||||
{
|
||||
return static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_DELETE));
|
||||
}
|
||||
#endif
|
||||
|
||||
/**************************************************************************
|
||||
Update_rows_log_event member functions
|
||||
**************************************************************************/
|
||||
@ -12086,6 +12251,9 @@ Update_rows_log_event::do_before_row_operations(const Slave_reporting_capability
|
||||
if ((err= find_key()))
|
||||
return err;
|
||||
|
||||
if (slave_run_triggers_for_rbr && !master_had_triggers)
|
||||
m_table->prepare_triggers_for_update_stmt_or_event();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -12105,6 +12273,8 @@ Update_rows_log_event::do_after_row_operations(const Slave_reporting_capability
|
||||
int
|
||||
Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
|
||||
{
|
||||
const bool invoke_triggers=
|
||||
slave_run_triggers_for_rbr && !master_had_triggers && m_table->triggers;
|
||||
DBUG_ASSERT(m_table != NULL);
|
||||
|
||||
int error= find_row(rgi);
|
||||
@ -12151,10 +12321,21 @@ Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
|
||||
DBUG_DUMP("new values", m_table->record[0], m_table->s->reclength);
|
||||
#endif
|
||||
|
||||
if (invoke_triggers &&
|
||||
process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_BEFORE, TRUE))
|
||||
{
|
||||
error= HA_ERR_GENERIC; // in case if error is not set yet
|
||||
goto err;
|
||||
}
|
||||
|
||||
error= m_table->file->ha_update_row(m_table->record[1], m_table->record[0]);
|
||||
if (error == HA_ERR_RECORD_IS_THE_SAME)
|
||||
error= 0;
|
||||
|
||||
if (invoke_triggers && !error &&
|
||||
process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_AFTER, TRUE))
|
||||
error= HA_ERR_GENERIC; // in case if error is not set yet
|
||||
|
||||
err:
|
||||
m_table->file->ha_index_or_rnd_end();
|
||||
return error;
|
||||
@ -12170,6 +12351,12 @@ void Update_rows_log_event::print(FILE *file,
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
|
||||
uint8 Update_rows_log_event::get_trg_event_map()
|
||||
{
|
||||
return static_cast<uint8> (1 << static_cast<int>(TRG_EVENT_UPDATE));
|
||||
}
|
||||
#endif
|
||||
|
||||
Incident_log_event::Incident_log_event(const char *buf, uint event_len,
|
||||
const Format_description_log_event *descr_event)
|
||||
|
@ -4049,7 +4049,9 @@ public:
|
||||
enum
|
||||
{
|
||||
TM_NO_FLAGS = 0U,
|
||||
TM_BIT_LEN_EXACT_F = (1U << 0)
|
||||
TM_BIT_LEN_EXACT_F = (1U << 0),
|
||||
// MariaDB flags (we starts from the other end)
|
||||
TM_BIT_HAS_TRIGGERS_F= (1U << 14)
|
||||
};
|
||||
|
||||
flag_set get_flags(flag_set flag) const { return m_flags & flag; }
|
||||
@ -4254,6 +4256,10 @@ public:
|
||||
|
||||
const uchar* get_extra_row_data() const { return m_extra_row_data; }
|
||||
|
||||
#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
|
||||
virtual uint8 get_trg_event_map()= 0;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/*
|
||||
The constructors are protected since you're supposed to inherit
|
||||
@ -4307,6 +4313,7 @@ protected:
|
||||
uchar *m_extra_row_data; /* Pointer to extra row data if any */
|
||||
/* If non null, first byte is length */
|
||||
|
||||
|
||||
/* helper functions */
|
||||
|
||||
#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
|
||||
@ -4315,6 +4322,7 @@ protected:
|
||||
uchar *m_key; /* Buffer to keep key value during searches */
|
||||
KEY *m_key_info; /* Pointer to KEY info for m_key_nr */
|
||||
uint m_key_nr; /* Key number */
|
||||
bool master_had_triggers; /* set after tables opening */
|
||||
|
||||
int find_key(); // Find a best key to use in find_row()
|
||||
int find_row(rpl_group_info *);
|
||||
@ -4329,6 +4337,9 @@ protected:
|
||||
return ::unpack_row(rgi, m_table, m_width, m_curr_row, &m_cols,
|
||||
&m_curr_row_end, &m_master_reclength, m_rows_end);
|
||||
}
|
||||
bool process_triggers(trg_event_type event,
|
||||
trg_action_time_type time_type,
|
||||
bool old_row_is_record1);
|
||||
#endif
|
||||
|
||||
private:
|
||||
@ -4433,6 +4444,10 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
|
||||
uint8 get_trg_event_map();
|
||||
#endif
|
||||
|
||||
private:
|
||||
virtual Log_event_type get_general_type_code() { return (Log_event_type)TYPE_CODE; }
|
||||
|
||||
@ -4507,6 +4522,10 @@ public:
|
||||
return Rows_log_event::is_valid() && m_cols_ai.bitmap;
|
||||
}
|
||||
|
||||
#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
|
||||
uint8 get_trg_event_map();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual Log_event_type get_general_type_code() { return (Log_event_type)TYPE_CODE; }
|
||||
|
||||
@ -4571,7 +4590,11 @@ public:
|
||||
cols, fields, before_record);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
|
||||
uint8 get_trg_event_map();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual Log_event_type get_general_type_code() { return (Log_event_type)TYPE_CODE; }
|
||||
|
||||
|
@ -479,6 +479,7 @@ ulong open_files_limit, max_binlog_size;
|
||||
ulong slave_trans_retries;
|
||||
uint slave_net_timeout;
|
||||
ulong slave_exec_mode_options;
|
||||
ulong slave_run_triggers_for_rbr= 0;
|
||||
ulong slave_ddl_exec_mode_options= SLAVE_EXEC_MODE_IDEMPOTENT;
|
||||
ulonglong slave_type_conversions_options;
|
||||
ulong thread_cache_size=0;
|
||||
|
@ -98,6 +98,7 @@ extern my_bool opt_safe_show_db, opt_local_infile, opt_myisam_use_mmap;
|
||||
extern my_bool opt_slave_compressed_protocol, use_temp_pool;
|
||||
extern ulong slave_exec_mode_options, slave_ddl_exec_mode_options;
|
||||
extern ulong slave_retried_transactions;
|
||||
extern ulong slave_run_triggers_for_rbr;
|
||||
extern ulonglong slave_type_conversions_options;
|
||||
extern my_bool read_only, opt_readonly;
|
||||
extern my_bool lower_case_file_system;
|
||||
|
@ -238,6 +238,7 @@ struct RPL_TABLE_LIST
|
||||
bool m_tabledef_valid;
|
||||
table_def m_tabledef;
|
||||
TABLE *m_conv_table;
|
||||
bool master_had_triggers;
|
||||
};
|
||||
|
||||
|
||||
|
@ -80,6 +80,9 @@ enum enum_delay_key_write { DELAY_KEY_WRITE_NONE, DELAY_KEY_WRITE_ON,
|
||||
enum enum_slave_exec_mode { SLAVE_EXEC_MODE_STRICT,
|
||||
SLAVE_EXEC_MODE_IDEMPOTENT,
|
||||
SLAVE_EXEC_MODE_LAST_BIT };
|
||||
enum enum_slave_run_triggers_for_rbr { SLAVE_RUN_TRIGGERS_FOR_RBR_NO,
|
||||
SLAVE_RUN_TRIGGERS_FOR_RBR_YES,
|
||||
SLAVE_RUN_TRIGGERS_FOR_RBR_LOGGING};
|
||||
enum enum_slave_type_conversions { SLAVE_TYPE_CONVERSIONS_ALL_LOSSY,
|
||||
SLAVE_TYPE_CONVERSIONS_ALL_NON_LOSSY};
|
||||
enum enum_mark_columns
|
||||
|
@ -521,16 +521,8 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
|
||||
init_ftfuncs(thd, select_lex, 1);
|
||||
THD_STAGE_INFO(thd, stage_updating);
|
||||
|
||||
if (table->triggers &&
|
||||
table->triggers->has_triggers(TRG_EVENT_DELETE,
|
||||
TRG_ACTION_AFTER))
|
||||
if (table->prepare_triggers_for_delete_stmt_or_event())
|
||||
{
|
||||
/*
|
||||
The table has AFTER DELETE triggers that might access to subject table
|
||||
and therefore might need delete to be done immediately. So we turn-off
|
||||
the batching.
|
||||
*/
|
||||
(void) table->file->extra(HA_EXTRA_DELETE_CANNOT_BATCH);
|
||||
will_batch= FALSE;
|
||||
}
|
||||
else
|
||||
@ -938,17 +930,7 @@ multi_delete::initialize_tables(JOIN *join)
|
||||
transactional_tables= 1;
|
||||
else
|
||||
normal_tables= 1;
|
||||
if (tbl->triggers &&
|
||||
tbl->triggers->has_triggers(TRG_EVENT_DELETE,
|
||||
TRG_ACTION_AFTER))
|
||||
{
|
||||
/*
|
||||
The table has AFTER DELETE triggers that might access to subject
|
||||
table and therefore might need delete to be done immediately.
|
||||
So we turn-off the batching.
|
||||
*/
|
||||
(void) tbl->file->extra(HA_EXTRA_DELETE_CANNOT_BATCH);
|
||||
}
|
||||
tbl->prepare_triggers_for_delete_stmt_or_event();
|
||||
tbl->prepare_for_position();
|
||||
tbl->mark_columns_needed_for_delete();
|
||||
}
|
||||
|
@ -373,48 +373,6 @@ static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Prepare triggers for INSERT-like statement.
|
||||
|
||||
SYNOPSIS
|
||||
prepare_triggers_for_insert_stmt()
|
||||
table Table to which insert will happen
|
||||
|
||||
NOTE
|
||||
Prepare triggers for INSERT-like statement by marking fields
|
||||
used by triggers and inform handlers that batching of UPDATE/DELETE
|
||||
cannot be done if there are BEFORE UPDATE/DELETE triggers.
|
||||
*/
|
||||
|
||||
void prepare_triggers_for_insert_stmt(TABLE *table)
|
||||
{
|
||||
if (table->triggers)
|
||||
{
|
||||
if (table->triggers->has_triggers(TRG_EVENT_DELETE,
|
||||
TRG_ACTION_AFTER))
|
||||
{
|
||||
/*
|
||||
The table has AFTER DELETE triggers that might access to
|
||||
subject table and therefore might need delete to be done
|
||||
immediately. So we turn-off the batching.
|
||||
*/
|
||||
(void) table->file->extra(HA_EXTRA_DELETE_CANNOT_BATCH);
|
||||
}
|
||||
if (table->triggers->has_triggers(TRG_EVENT_UPDATE,
|
||||
TRG_ACTION_AFTER))
|
||||
{
|
||||
/*
|
||||
The table has AFTER UPDATE triggers that might access to subject
|
||||
table and therefore might need update to be done immediately.
|
||||
So we turn-off the batching.
|
||||
*/
|
||||
(void) table->file->extra(HA_EXTRA_UPDATE_CANNOT_BATCH);
|
||||
}
|
||||
}
|
||||
table->mark_columns_needed_for_insert();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Upgrade table-level lock of INSERT statement to TL_WRITE if
|
||||
a more concurrent lock is infeasible for some reason. This is
|
||||
@ -902,7 +860,8 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
|
||||
thd->abort_on_warning= !ignore && thd->is_strict_mode();
|
||||
|
||||
prepare_triggers_for_insert_stmt(table);
|
||||
table->prepare_triggers_for_insert_stmt_or_event();
|
||||
table->mark_columns_needed_for_insert();
|
||||
|
||||
|
||||
if (table_list->prepare_where(thd, 0, TRUE) ||
|
||||
@ -3537,7 +3496,10 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
|
||||
table_list->prepare_check_option(thd));
|
||||
|
||||
if (!res)
|
||||
prepare_triggers_for_insert_stmt(table);
|
||||
{
|
||||
table->prepare_triggers_for_insert_stmt_or_event();
|
||||
table->mark_columns_needed_for_insert();
|
||||
}
|
||||
|
||||
DBUG_RETURN(res);
|
||||
}
|
||||
|
@ -38,7 +38,6 @@ void upgrade_lock_type_for_insert(THD *thd, thr_lock_type *lock_type,
|
||||
bool is_multi_insert);
|
||||
int check_that_all_fields_are_given_values(THD *thd, TABLE *entry,
|
||||
TABLE_LIST *table_list);
|
||||
void prepare_triggers_for_insert_stmt(TABLE *table);
|
||||
int write_record(THD *thd, TABLE *table, COPY_INFO *info);
|
||||
void kill_delayed_threads(void);
|
||||
|
||||
|
@ -28,7 +28,6 @@
|
||||
#include <my_dir.h>
|
||||
#include "sql_view.h" // check_key_in_view
|
||||
#include "sql_insert.h" // check_that_all_fields_are_given_values,
|
||||
// prepare_triggers_for_insert_stmt,
|
||||
// write_record
|
||||
#include "sql_acl.h" // INSERT_ACL, UPDATE_ACL
|
||||
#include "log_event.h" // Delete_file_log_event,
|
||||
@ -298,7 +297,8 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
|
||||
prepare_triggers_for_insert_stmt(table);
|
||||
table->prepare_triggers_for_insert_stmt_or_event();
|
||||
table->mark_columns_needed_for_insert();
|
||||
|
||||
uint tot_length=0;
|
||||
bool use_blobs= 0, use_vars= 0;
|
||||
|
@ -703,16 +703,8 @@ int mysql_update(THD *thd,
|
||||
|
||||
transactional_table= table->file->has_transactions();
|
||||
thd->abort_on_warning= !ignore && thd->is_strict_mode();
|
||||
if (table->triggers &&
|
||||
table->triggers->has_triggers(TRG_EVENT_UPDATE,
|
||||
TRG_ACTION_AFTER))
|
||||
if (table->prepare_triggers_for_update_stmt_or_event())
|
||||
{
|
||||
/*
|
||||
The table has AFTER UPDATE triggers that might access to subject
|
||||
table and therefore might need update to be done immediately.
|
||||
So we turn-off the batching.
|
||||
*/
|
||||
(void) table->file->extra(HA_EXTRA_UPDATE_CANNOT_BATCH);
|
||||
will_batch= FALSE;
|
||||
}
|
||||
else
|
||||
@ -1610,17 +1602,7 @@ int multi_update::prepare(List<Item> ¬_used_values,
|
||||
table->no_keyread=1;
|
||||
table->covering_keys.clear_all();
|
||||
table->pos_in_table_list= tl;
|
||||
if (table->triggers &&
|
||||
table->triggers->has_triggers(TRG_EVENT_UPDATE,
|
||||
TRG_ACTION_AFTER))
|
||||
{
|
||||
/*
|
||||
The table has AFTER UPDATE triggers that might access to subject
|
||||
table and therefore might need update to be done immediately.
|
||||
So we turn-off the batching.
|
||||
*/
|
||||
(void) table->file->extra(HA_EXTRA_UPDATE_CANNOT_BATCH);
|
||||
}
|
||||
table->prepare_triggers_for_update_stmt_or_event();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2737,6 +2737,21 @@ static Sys_var_enum Slave_ddl_exec_mode(
|
||||
GLOBAL_VAR(slave_ddl_exec_mode_options), CMD_LINE(REQUIRED_ARG),
|
||||
slave_exec_mode_names, DEFAULT(SLAVE_EXEC_MODE_IDEMPOTENT));
|
||||
|
||||
static const char *slave_run_triggers_for_rbr_names[]=
|
||||
{"NO", "YES", "LOGGING", 0};
|
||||
static Sys_var_enum Slave_run_triggers_for_rbr(
|
||||
"slave_run_triggers_for_rbr",
|
||||
"Modes for how triggers in row-base replication on slave side will be "
|
||||
"executed. Legal values are NO (default), YES and LOGGING. NO means "
|
||||
"that trigger for RBR will not be running on slave. YES and LOGGING "
|
||||
"means that triggers will be running on slave, if there was not "
|
||||
"triggers running on the master for the statement. LOGGING also means "
|
||||
"results of that the executed triggers work will be written to "
|
||||
"the binlog.",
|
||||
GLOBAL_VAR(slave_run_triggers_for_rbr), CMD_LINE(REQUIRED_ARG),
|
||||
slave_run_triggers_for_rbr_names,
|
||||
DEFAULT(SLAVE_RUN_TRIGGERS_FOR_RBR_NO));
|
||||
|
||||
static const char *slave_type_conversions_name[]= {"ALL_LOSSY", "ALL_NON_LOSSY", 0};
|
||||
static Sys_var_set Slave_type_conversions(
|
||||
"slave_type_conversions",
|
||||
|
79
sql/table.cc
79
sql/table.cc
@ -3994,6 +3994,10 @@ void TABLE::init(THD *thd, TABLE_LIST *tl)
|
||||
created= TRUE;
|
||||
cond_selectivity= 1.0;
|
||||
cond_selectivity_sampling_explain= NULL;
|
||||
#ifdef HAVE_REPLICATION
|
||||
/* used in RBR Triggers */
|
||||
master_had_triggers= 0;
|
||||
#endif
|
||||
|
||||
/* Catch wrong handling of the auto_increment_field_not_null. */
|
||||
DBUG_ASSERT(!auto_increment_field_not_null);
|
||||
@ -6656,6 +6660,81 @@ int TABLE::update_default_fields()
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Prepare triggers for INSERT-like statement.
|
||||
|
||||
SYNOPSIS
|
||||
prepare_triggers_for_insert_stmt_or_event()
|
||||
|
||||
NOTE
|
||||
Prepare triggers for INSERT-like statement by marking fields
|
||||
used by triggers and inform handlers that batching of UPDATE/DELETE
|
||||
cannot be done if there are BEFORE UPDATE/DELETE triggers.
|
||||
*/
|
||||
|
||||
void TABLE::prepare_triggers_for_insert_stmt_or_event()
|
||||
{
|
||||
if (triggers)
|
||||
{
|
||||
if (triggers->has_triggers(TRG_EVENT_DELETE,
|
||||
TRG_ACTION_AFTER))
|
||||
{
|
||||
/*
|
||||
The table has AFTER DELETE triggers that might access to
|
||||
subject table and therefore might need delete to be done
|
||||
immediately. So we turn-off the batching.
|
||||
*/
|
||||
(void) file->extra(HA_EXTRA_DELETE_CANNOT_BATCH);
|
||||
}
|
||||
if (triggers->has_triggers(TRG_EVENT_UPDATE,
|
||||
TRG_ACTION_AFTER))
|
||||
{
|
||||
/*
|
||||
The table has AFTER UPDATE triggers that might access to subject
|
||||
table and therefore might need update to be done immediately.
|
||||
So we turn-off the batching.
|
||||
*/
|
||||
(void) file->extra(HA_EXTRA_UPDATE_CANNOT_BATCH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool TABLE::prepare_triggers_for_delete_stmt_or_event()
|
||||
{
|
||||
if (triggers &&
|
||||
triggers->has_triggers(TRG_EVENT_DELETE,
|
||||
TRG_ACTION_AFTER))
|
||||
{
|
||||
/*
|
||||
The table has AFTER DELETE triggers that might access to subject table
|
||||
and therefore might need delete to be done immediately. So we turn-off
|
||||
the batching.
|
||||
*/
|
||||
(void) file->extra(HA_EXTRA_DELETE_CANNOT_BATCH);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
bool TABLE::prepare_triggers_for_update_stmt_or_event()
|
||||
{
|
||||
if (triggers &&
|
||||
triggers->has_triggers(TRG_EVENT_UPDATE,
|
||||
TRG_ACTION_AFTER))
|
||||
{
|
||||
/*
|
||||
The table has AFTER UPDATE triggers that might access to subject
|
||||
table and therefore might need update to be done immediately.
|
||||
So we turn-off the batching.
|
||||
*/
|
||||
(void) file->extra(HA_EXTRA_UPDATE_CANNOT_BATCH);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
@brief Reset const_table flag
|
||||
|
||||
|
@ -1234,6 +1234,10 @@ public:
|
||||
bool get_fields_in_item_tree; /* Signal to fix_field */
|
||||
bool m_needs_reopen;
|
||||
bool created; /* For tmp tables. TRUE <=> tmp table was actually created.*/
|
||||
#ifdef HAVE_REPLICATION
|
||||
/* used in RBR Triggers */
|
||||
bool master_had_triggers;
|
||||
#endif
|
||||
|
||||
REGINFO reginfo; /* field connections */
|
||||
MEM_ROOT mem_root;
|
||||
@ -1362,6 +1366,10 @@ public:
|
||||
ulong actual_key_flags(KEY *keyinfo);
|
||||
int update_default_fields();
|
||||
inline ha_rows stat_records() { return used_stat_records; }
|
||||
|
||||
void prepare_triggers_for_insert_stmt_or_event();
|
||||
bool prepare_triggers_for_delete_stmt_or_event();
|
||||
bool prepare_triggers_for_update_stmt_or_event();
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user