summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mysql-test/include/have_rbr_triggers.inc5
-rw-r--r--mysql-test/r/mysqld--help.result1
-rw-r--r--mysql-test/suite/rpl/r/rpl_row_triggers.result240
-rw-r--r--mysql-test/suite/rpl/r/rpl_row_triggers_sbr.result14
-rw-r--r--mysql-test/suite/rpl/t/rpl_row_triggers.test280
-rw-r--r--mysql-test/suite/rpl/t/rpl_row_triggers_sbr.test43
-rw-r--r--sql/log_event.cc269
-rw-r--r--sql/log_event.h27
-rw-r--r--sql/mysqld.cc1
-rw-r--r--sql/mysqld.h1
-rw-r--r--sql/rpl_utility.h1
-rw-r--r--sql/sql_class.h3
-rw-r--r--sql/sql_delete.cc22
-rw-r--r--sql/sql_insert.cc50
-rw-r--r--sql/sql_insert.h1
-rw-r--r--sql/sql_load.cc4
-rw-r--r--sql/sql_update.cc22
-rw-r--r--sql/sys_vars.cc15
-rw-r--r--sql/table.cc79
-rw-r--r--sql/table.h8
20 files changed, 956 insertions, 130 deletions
diff --git a/mysql-test/include/have_rbr_triggers.inc b/mysql-test/include/have_rbr_triggers.inc
new file mode 100644
index 00000000000..9ccfc18bfde
--- /dev/null
+++ b/mysql-test/include/have_rbr_triggers.inc
@@ -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;
+}
+
diff --git a/mysql-test/r/mysqld--help.result b/mysql-test/r/mysqld--help.result
index 5e37fde47a7..c123d305bc7 100644
--- a/mysql-test/r/mysqld--help.result
+++ b/mysql-test/r/mysqld--help.result
@@ -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
diff --git a/mysql-test/suite/rpl/r/rpl_row_triggers.result b/mysql-test/suite/rpl/r/rpl_row_triggers.result
new file mode 100644
index 00000000000..82dbc727993
--- /dev/null
+++ b/mysql-test/suite/rpl/r/rpl_row_triggers.result
@@ -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
diff --git a/mysql-test/suite/rpl/r/rpl_row_triggers_sbr.result b/mysql-test/suite/rpl/r/rpl_row_triggers_sbr.result
new file mode 100644
index 00000000000..96197393de9
--- /dev/null
+++ b/mysql-test/suite/rpl/r/rpl_row_triggers_sbr.result
@@ -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
diff --git a/mysql-test/suite/rpl/t/rpl_row_triggers.test b/mysql-test/suite/rpl/t/rpl_row_triggers.test
new file mode 100644
index 00000000000..3a884727325
--- /dev/null
+++ b/mysql-test/suite/rpl/t/rpl_row_triggers.test
@@ -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
diff --git a/mysql-test/suite/rpl/t/rpl_row_triggers_sbr.test b/mysql-test/suite/rpl/t/rpl_row_triggers_sbr.test
new file mode 100644
index 00000000000..a801363b931
--- /dev/null
+++ b/mysql-test/suite/rpl/t/rpl_row_triggers_sbr.test
@@ -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
diff --git a/sql/log_event.cc b/sql/log_event.cc
index c769c5b9209..f1389c36f5c 100644
--- a/sql/log_event.cc
+++ b/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)
diff --git a/sql/log_event.h b/sql/log_event.h
index a9d1b08171f..dc6afb10a6d 100644
--- a/sql/log_event.h
+++ b/sql/log_event.h
@@ -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; }
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index 774f9531893..de461be524d 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -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;
diff --git a/sql/mysqld.h b/sql/mysqld.h
index fa79dd2d11d..aaf8837868e 100644
--- a/sql/mysqld.h
+++ b/sql/mysqld.h
@@ -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;
diff --git a/sql/rpl_utility.h b/sql/rpl_utility.h
index 1a00a58d453..7568a2d786c 100644
--- a/sql/rpl_utility.h
+++ b/sql/rpl_utility.h
@@ -238,6 +238,7 @@ struct RPL_TABLE_LIST
bool m_tabledef_valid;
table_def m_tabledef;
TABLE *m_conv_table;
+ bool master_had_triggers;
};
diff --git a/sql/sql_class.h b/sql/sql_class.h
index 6d8b23f0f8f..16e02e6951c 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -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
diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc
index b92a4cd23f6..a8f4018a53b 100644
--- a/sql/sql_delete.cc
+++ b/sql/sql_delete.cc
@@ -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();
}
diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
index 6a1d33df9a6..92be2296f6f 100644
--- a/sql/sql_insert.cc
+++ b/sql/sql_insert.cc
@@ -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);
}
diff --git a/sql/sql_insert.h b/sql/sql_insert.h
index 8564f4d103e..cbfc1ea9dcd 100644
--- a/sql/sql_insert.h
+++ b/sql/sql_insert.h
@@ -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);
diff --git a/sql/sql_load.cc b/sql/sql_load.cc
index d112b3e689a..bdf26ec0292 100644
--- a/sql/sql_load.cc
+++ b/sql/sql_load.cc
@@ -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;
diff --git a/sql/sql_update.cc b/sql/sql_update.cc
index 290f6c6329c..153fae1b895 100644
--- a/sql/sql_update.cc
+++ b/sql/sql_update.cc
@@ -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> &not_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();
}
}
diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc
index 15ff75078f0..474362b5628 100644
--- a/sql/sys_vars.cc
+++ b/sql/sys_vars.cc
@@ -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",
diff --git a/sql/table.cc b/sql/table.cc
index f703a302864..96f51b1c745 100644
--- a/sql/table.cc
+++ b/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);
@@ -6657,6 +6661,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
@detail
diff --git a/sql/table.h b/sql/table.h
index e1e66898a6d..043760dd887 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -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();
};