diff options
-rw-r--r-- | mysql-test/include/have_rbr_triggers.inc | 5 | ||||
-rw-r--r-- | mysql-test/r/mysqld--help.result | 1 | ||||
-rw-r--r-- | mysql-test/suite/rpl/r/rpl_row_triggers.result | 240 | ||||
-rw-r--r-- | mysql-test/suite/rpl/r/rpl_row_triggers_sbr.result | 14 | ||||
-rw-r--r-- | mysql-test/suite/rpl/t/rpl_row_triggers.test | 280 | ||||
-rw-r--r-- | mysql-test/suite/rpl/t/rpl_row_triggers_sbr.test | 43 | ||||
-rw-r--r-- | sql/log_event.cc | 269 | ||||
-rw-r--r-- | sql/log_event.h | 27 | ||||
-rw-r--r-- | sql/mysqld.cc | 1 | ||||
-rw-r--r-- | sql/mysqld.h | 1 | ||||
-rw-r--r-- | sql/rpl_utility.h | 1 | ||||
-rw-r--r-- | sql/sql_class.h | 3 | ||||
-rw-r--r-- | sql/sql_delete.cc | 22 | ||||
-rw-r--r-- | sql/sql_insert.cc | 50 | ||||
-rw-r--r-- | sql/sql_insert.h | 1 | ||||
-rw-r--r-- | sql/sql_load.cc | 4 | ||||
-rw-r--r-- | sql/sql_update.cc | 22 | ||||
-rw-r--r-- | sql/sys_vars.cc | 15 | ||||
-rw-r--r-- | sql/table.cc | 79 | ||||
-rw-r--r-- | sql/table.h | 8 |
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> ¬_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(); }; |