Commit af3180ab authored by unknown's avatar unknown

MDEV-9095: Executing triggers on slave in row-based replication

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