From 7012e2538468409b765b32fdc929dd58be94f7ec Mon Sep 17 00:00:00 2001 From: Santosh Praneeth Banda Date: Fri, 8 Mar 2019 09:06:09 -0800 Subject: [PATCH] FB8-48: Option to run triggers on slave for row-based events (#946) (#946) Summary: JIRA: https://jira.percona.com/browse/FB8-48 Reference Patch: https://github.com/facebook/mysql-5.6/commit/8225c64 Reference Patch: https://github.com/facebook/mysql-5.6/commit/f5466d6 Reference Patch: https://github.com/facebook/mysql-5.6/commit/87e3650 Port the slave_run_triggers_for_rbr feature from mariadb 10.1.1. When using statement based replication slave executes the sql statments which runs the slave side triggers. Since in row based replication, slave applies the row events directly to the storage engine, triggers on the slave table are not executed. Add functionality to run triggers on slave side when executing row based events. The following triggers are invoked: * Update_row_event runs an UPDATE trigger * Delete_row_event runs a DELETE trigger * Write_row_event action depends on whether the operation will require foreign key checks: 1) when FK checks are not necessary, the operation will invoke a DELETE trigger if the record to be modified existed in the table. After that, an INSERT trigger will be invoked. 2) when FK checks are necessary, either an UPDATE or or a combination of DELETE and INSERT triggers will be invoked. slave_run_triggers_for_rbr option controls the feature. Default value is NO which don't invoke trigger for row-based events; Setting the option to YES will cause the SQL slave thread to invoke triggers for row based events; setting it to LOGGING will also cause the changes made by the triggers to be written into the binary log. There is a basic protection against triggers being invoked both on the master and slave. If the master modifies a table that has triggers, it will produce row-based binlog events with the "triggers were invoked for this event" flag. The slave will not invoke any triggers for flagged events. optionally disable binlogging while executing triggers Online schema change (OSC) creates triggers only on master side. Since RBR logs row changes made by triggers, sql_thread will hit errors due to missing tables on slaves. Add a session variable SQL_LOG_BIN_TRIGGERS to optionally disable binlogging for trigger statements so that RBR and OSC are fine with each other. disable_sql_log_bin_triggers flag is added in the TABLE_LIST struct to track tables that are opened during trigger execution. This flag is used to skip writing Table_map_log_events for such tables. With sql_log_bin_triggers enabled, the trigger changes which may be necessary for slave are not propagated. To avoid this, slave_run_triggers_for_rbr option must be enabled on slave. Pull Request resolved: https://github.com/facebook/mysql-5.6/pull/946 Reviewed By: lloyd Differential Revision: D13972562 Pulled By: lth --- mysql-test/include/have_rbr_triggers.inc | 4 + mysql-test/r/mysqld--help-notwin.result | 15 + .../r/binlog_persist_only_variables.result | 4 + .../r/binlog_persist_variables.result | 4 + .../t/binlog_persist_only_variables.test | 2 +- .../suite/rocksdb/r/rpl_row_triggers.result | 2 +- .../suite/rpl/r/rpl_row_triggers.result | 279 ++++++++++++++++++ .../suite/rpl/r/rpl_row_triggers_sbr.result | 21 ++ .../rpl/r/rpl_sql_log_bin_triggers.result | 49 +++ mysql-test/suite/rpl/t/rpl_row_triggers.test | 240 +++++++++++++++ .../suite/rpl/t/rpl_row_triggers_sbr.test | 34 +++ .../suite/rpl/t/rpl_sql_log_bin_triggers.test | 61 ++++ .../r/slave_run_triggers_for_rbr_basic.result | 27 ++ .../r/sql_log_bin_triggers_basic.result | 66 +++++ .../t/slave_run_triggers_for_rbr_basic.test | 21 ++ .../t/sql_log_bin_triggers_basic.test | 46 +++ sql/handler.cc | 4 +- sql/item_subselect.cc | 2 +- sql/log_event.cc | 220 ++++++++++++-- sql/log_event.h | 13 +- sql/mysqld.cc | 1 + sql/mysqld.h | 1 + sql/rpl_utility.h | 1 + sql/sp_head.cc | 3 +- sql/sp_instr.cc | 12 + sql/sql_delete.cc | 17 +- sql/sql_derived.cc | 6 +- sql/sql_handler.cc | 6 +- sql/sql_insert.cc | 44 +-- sql/sql_insert.h | 1 - sql/sql_load.cc | 3 +- sql/sql_select.cc | 2 +- sql/sql_show.cc | 2 +- sql/sql_tmp_table.cc | 2 +- sql/sql_trigger.cc | 2 +- sql/sql_update.cc | 12 +- sql/sys_vars.cc | 21 ++ sql/system_variables.h | 8 + sql/table.cc | 66 ++++- sql/table.h | 11 + sql/trigger.cc | 7 + unittest/gunit/fake_table.h | 2 +- 42 files changed, 1233 insertions(+), 111 deletions(-) create mode 100644 mysql-test/include/have_rbr_triggers.inc create mode 100644 mysql-test/suite/rpl/r/rpl_row_triggers.result create mode 100644 mysql-test/suite/rpl/r/rpl_row_triggers_sbr.result create mode 100644 mysql-test/suite/rpl/r/rpl_sql_log_bin_triggers.result create mode 100644 mysql-test/suite/rpl/t/rpl_row_triggers.test create mode 100644 mysql-test/suite/rpl/t/rpl_row_triggers_sbr.test create mode 100644 mysql-test/suite/rpl/t/rpl_sql_log_bin_triggers.test create mode 100644 mysql-test/suite/sys_vars/r/slave_run_triggers_for_rbr_basic.result create mode 100644 mysql-test/suite/sys_vars/r/sql_log_bin_triggers_basic.result create mode 100644 mysql-test/suite/sys_vars/t/slave_run_triggers_for_rbr_basic.test create mode 100644 mysql-test/suite/sys_vars/t/sql_log_bin_triggers_basic.test diff --git a/mysql-test/include/have_rbr_triggers.inc b/mysql-test/include/have_rbr_triggers.inc new file mode 100644 index 000000000000..3d111c38b380 --- /dev/null +++ b/mysql-test/include/have_rbr_triggers.inc @@ -0,0 +1,4 @@ +if (`select count(*) = 0 from performance_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-notwin.result b/mysql-test/r/mysqld--help-notwin.result index 0df12d4995ac..4149fa67b8da 100644 --- a/mysql-test/r/mysqld--help-notwin.result +++ b/mysql-test/r/mysqld--help-notwin.result @@ -2093,6 +2093,15 @@ The following options may be given as the first argument: HASH_SCAN. Any combination is allowed, and the applier picks the most efficient among them for any given scenario. (Default: INDEX_SCAN, HASH_SCAN). + --slave-run-triggers-for-rbr=name + 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. --slave-skip-errors=name This option is deprecated. Use replica_skip_errors instead. @@ -2133,6 +2142,10 @@ The following options may be given as the first argument: When set, if a table is created without a primary key then server generates invisible auto-increment column as a primary key for the table. + --sql-log-bin-triggers + The row changes generated by execution of triggers are + not logged inbinlog if this option is false. + (Defaults to on; use --skip-sql-log-bin-triggers to disable.) --sql-mode=name Syntax: sql-mode=mode[,mode[,mode...]]. See the manual for the complete list of valid sql modes --sql-require-primary-key @@ -2873,6 +2886,7 @@ slave-parallel-workers 4 slave-pending-jobs-size-max 134217728 slave-preserve-commit-order TRUE slave-rows-search-algorithms INDEX_SCAN,HASH_SCAN +slave-run-triggers-for-rbr NO slave-skip-errors (No default value) slave-sql-verify-checksum TRUE slave-transaction-retries 10 @@ -2883,6 +2897,7 @@ sort-buffer-size 262144 source-verify-checksum FALSE sporadic-binlog-dump-fail FALSE sql-generate-invisible-primary-key FALSE +sql-log-bin-triggers TRUE sql-mode ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION sql-require-primary-key FALSE stored-program-cache 256 diff --git a/mysql-test/suite/binlog_nogtid/r/binlog_persist_only_variables.result b/mysql-test/suite/binlog_nogtid/r/binlog_persist_only_variables.result index 74b8ac0c595b..57c782819354 100644 --- a/mysql-test/suite/binlog_nogtid/r/binlog_persist_only_variables.result +++ b/mysql-test/suite/binlog_nogtid/r/binlog_persist_only_variables.result @@ -255,6 +255,7 @@ SET PERSIST_ONLY slave_rows_search_algorithms = @@GLOBAL.slave_rows_search_algor Warnings: Warning 1287 '@@slave_rows_search_algorithms' is deprecated and will be removed in a future release. Warning 1287 '@@slave_rows_search_algorithms' is deprecated and will be removed in a future release. +SET PERSIST_ONLY slave_run_triggers_for_rbr = @@GLOBAL.slave_run_triggers_for_rbr; SET PERSIST_ONLY slave_skip_errors = @@GLOBAL.slave_skip_errors; Warnings: Warning 1287 '@@slave_skip_errors' is deprecated and will be removed in a future release. Please use replica_skip_errors instead. @@ -272,6 +273,7 @@ Warnings: Warning 1287 '@@slave_type_conversions' is deprecated and will be removed in a future release. Please use replica_type_conversions instead. Warning 1287 '@@slave_type_conversions' is deprecated and will be removed in a future release. Please use replica_type_conversions instead. SET PERSIST_ONLY source_verify_checksum = @@GLOBAL.source_verify_checksum; +SET PERSIST_ONLY sql_log_bin_triggers = @@GLOBAL.sql_log_bin_triggers; SET PERSIST_ONLY sql_replica_skip_counter = @@GLOBAL.sql_replica_skip_counter; SET PERSIST_ONLY sql_slave_skip_counter = @@GLOBAL.sql_slave_skip_counter; Warnings: @@ -403,7 +405,9 @@ RESET PERSIST session_track_gtids; RESET PERSIST skip_replica_start; RESET PERSIST slave_compression_lib; RESET PERSIST slave_rows_search_algorithms; +RESET PERSIST slave_run_triggers_for_rbr; RESET PERSIST source_verify_checksum; +RESET PERSIST sql_log_bin_triggers; RESET PERSIST sql_replica_skip_counter; RESET PERSIST sync_binlog; RESET PERSIST sync_relay_log; diff --git a/mysql-test/suite/binlog_nogtid/r/binlog_persist_variables.result b/mysql-test/suite/binlog_nogtid/r/binlog_persist_variables.result index 936c702228aa..6f20919f1a59 100644 --- a/mysql-test/suite/binlog_nogtid/r/binlog_persist_variables.result +++ b/mysql-test/suite/binlog_nogtid/r/binlog_persist_variables.result @@ -234,6 +234,7 @@ SET PERSIST slave_rows_search_algorithms = @@GLOBAL.slave_rows_search_algorithms Warnings: Warning 1287 '@@slave_rows_search_algorithms' is deprecated and will be removed in a future release. Warning 1287 '@@slave_rows_search_algorithms' is deprecated and will be removed in a future release. +SET PERSIST slave_run_triggers_for_rbr = @@GLOBAL.slave_run_triggers_for_rbr; SET PERSIST slave_skip_errors = @@GLOBAL.slave_skip_errors; ERROR HY000: Variable 'slave_skip_errors' is a read only variable SET PERSIST slave_sql_verify_checksum = @@GLOBAL.slave_sql_verify_checksum; @@ -249,6 +250,7 @@ Warnings: Warning 1287 '@@slave_type_conversions' is deprecated and will be removed in a future release. Please use replica_type_conversions instead. Warning 1287 '@@slave_type_conversions' is deprecated and will be removed in a future release. Please use replica_type_conversions instead. SET PERSIST source_verify_checksum = @@GLOBAL.source_verify_checksum; +SET PERSIST sql_log_bin_triggers = @@GLOBAL.sql_log_bin_triggers; SET PERSIST sql_replica_skip_counter = @@GLOBAL.sql_replica_skip_counter; SET PERSIST sql_slave_skip_counter = @@GLOBAL.sql_slave_skip_counter; Warnings: @@ -460,6 +462,7 @@ RESET PERSIST IF EXISTS slave_preserve_commit_order; Warnings: Warning 3615 Variable slave_preserve_commit_order does not exist in persisted config file RESET PERSIST IF EXISTS slave_rows_search_algorithms; +RESET PERSIST IF EXISTS slave_run_triggers_for_rbr; RESET PERSIST IF EXISTS slave_skip_errors; Warnings: Warning 3615 Variable slave_skip_errors does not exist in persisted config file @@ -475,6 +478,7 @@ Warning 3615 Variable slave_type_conversions does not exist in persisted config RESET PERSIST IF EXISTS source_verify_checksum; Warnings: Warning 3615 Variable source_verify_checksum does not exist in persisted config file +RESET PERSIST IF EXISTS sql_log_bin_triggers; RESET PERSIST IF EXISTS sql_replica_skip_counter; RESET PERSIST IF EXISTS sql_slave_skip_counter; Warnings: diff --git a/mysql-test/suite/binlog_nogtid/t/binlog_persist_only_variables.test b/mysql-test/suite/binlog_nogtid/t/binlog_persist_only_variables.test index cdb1c0750a43..8b65ac7e6dd6 100644 --- a/mysql-test/suite/binlog_nogtid/t/binlog_persist_only_variables.test +++ b/mysql-test/suite/binlog_nogtid/t/binlog_persist_only_variables.test @@ -108,7 +108,7 @@ while ( $varid <= $countvars ) --eval SET PERSIST_ONLY $varname = @@GLOBAL.$varname # TODO: Remove/update this once Bug#27322592 is FIXED. - if (`SELECT '$varname' IN ('binlog_trx_meta_data', 'binlog_direct_non_transactional_updates', 'binlog_order_commits', 'binlog_rows_query_log_events', 'log_bin_trust_function_creators', 'log_slow_replica_statements', 'log_statements_unsafe_for_binlog', 'source_verify_checksum', 'replica_allow_batching', 'replica_compressed_protocol', 'replica_preserve_commit_order', 'replica_sql_verify_checksum', 'relay_log_purge', 'rpl_semi_sync_source_enabled', 'rpl_semi_sync_source_wait_no_replica', 'rpl_semi_sync_replica_enabled', 'binlog_gtid_simple_recovery', 'log_replica_updates', 'relay_log_recovery', 'binlog_rotate_encryption_master_key_at_startup', 'binlog_transaction_compression')`) + if (`SELECT '$varname' IN ('sql_log_bin_triggers', 'binlog_trx_meta_data', 'binlog_direct_non_transactional_updates', 'binlog_order_commits', 'binlog_rows_query_log_events', 'log_bin_trust_function_creators', 'log_slow_replica_statements', 'log_statements_unsafe_for_binlog', 'source_verify_checksum', 'replica_allow_batching', 'replica_compressed_protocol', 'replica_preserve_commit_order', 'replica_sql_verify_checksum', 'relay_log_purge', 'rpl_semi_sync_source_enabled', 'rpl_semi_sync_source_wait_no_replica', 'rpl_semi_sync_replica_enabled', 'binlog_gtid_simple_recovery', 'log_replica_updates', 'relay_log_recovery', 'binlog_rotate_encryption_master_key_at_startup', 'binlog_transaction_compression')`) { --disable_query_log --eval SELECT varvalue INTO @varvalue FROM rplvars WHERE id=$varid diff --git a/mysql-test/suite/rocksdb/r/rpl_row_triggers.result b/mysql-test/suite/rocksdb/r/rpl_row_triggers.result index e88ae97ac0e9..2950a3f5cd21 100644 --- a/mysql-test/suite/rocksdb/r/rpl_row_triggers.result +++ b/mysql-test/suite/rocksdb/r/rpl_row_triggers.result @@ -1,7 +1,7 @@ include/master-slave.inc Warnings: Note #### Sending passwords in plain text without SSL/TLS is extremely insecure. -Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information. +Note #### Storing MySQL user name or password information in the connection metadata repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START REPLICA; see the 'START REPLICA Syntax' in the MySQL Manual for more information. [connection master] # Test of row replication with triggers on the slave side CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1)); 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 000000000000..ca272aef2758 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_row_triggers.result @@ -0,0 +1,279 @@ +include/master-slave.inc +Warnings: +Note #### Sending passwords in plain text without SSL/TLS is extremely insecure. +Note #### Storing MySQL user name or password information in the connection metadata repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START REPLICA; see the 'START REPLICA Syntax' in the MySQL Manual for more information. +[connection master] +# Test of row replication with triggers on the slave side +CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1)); +SELECT * FROM t1; +C1 C2 +include/sync_slave_sql_with_master.inc +SET @old_replica_exec_mode= @@global.replica_exec_mode; +SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr; +SET @@global.replica_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'); +include/sync_slave_sql_with_master.inc +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'; +include/sync_slave_sql_with_master.inc +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'; +include/sync_slave_sql_with_master.inc +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'); +include/sync_slave_sql_with_master.inc +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) ); +insert into t1 values ('1','1'); +include/sync_slave_sql_with_master.inc +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; +include/sync_slave_sql_with_master.inc +SET @@global.replica_exec_mode= @old_replica_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; +include/sync_slave_sql_with_master.inc +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; +include/sync_slave_sql_with_master.inc +select * from t2; +i +1 +2 +SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr; +drop tables t2,t1; +include/sync_slave_sql_with_master.inc +# Triggers on slave do not work if master has some +CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1)); +SELECT * FROM t1; +C1 C2 +create trigger t1_dummy before delete on t1 for each row +set @dummy= 1; +include/sync_slave_sql_with_master.inc +SET @old_replica_exec_mode= @@global.replica_exec_mode; +SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr; +SET @@global.replica_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'); +include/sync_slave_sql_with_master.inc +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'; +include/sync_slave_sql_with_master.inc +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'; +include/sync_slave_sql_with_master.inc +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'); +include/sync_slave_sql_with_master.inc +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) ); +insert into t1 values ('1','1'); +include/sync_slave_sql_with_master.inc +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; +include/sync_slave_sql_with_master.inc +SET @@global.replica_exec_mode= @old_replica_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; +include/sync_slave_sql_with_master.inc +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); +include/sync_slave_sql_with_master.inc +select * from t1; +a b +1 10 +2 20 +3 30 +# +# Verify slave skips running triggers if master ran and logged the row events for triggers +# +create table t4(a int, b int); +delete from tlog; +create trigger tr4 before insert on t4 for each row insert into tlog values (1); +insert into t4 values (1, 10),(2, 20); +include/sync_slave_sql_with_master.inc +select * from t4; +a b +1 10 +2 20 +select * from tlog; +a +1 +1 +set global slave_run_triggers_for_rbr = @slave_run_triggers_for_rbr.saved; +drop table t1, tlog, t4; +include/sync_slave_sql_with_master.inc +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 000000000000..913e991c9c2c --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_row_triggers_sbr.result @@ -0,0 +1,21 @@ +include/master-slave.inc +Warnings: +Note #### Sending passwords in plain text without SSL/TLS is extremely insecure. +Note #### Storing MySQL user name or password information in the connection metadata repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START REPLICA; see the 'START REPLICA Syntax' in the MySQL Manual for more information. +[connection master] +set binlog_format = row; +Warnings: +Warning 1287 '@@binlog_format' is deprecated and will be removed in a future release. +create table t1 (i int); +create table t2 (i int); +include/sync_slave_sql_with_master.inc +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; +include/sync_slave_sql_with_master.inc +SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_sql_log_bin_triggers.result b/mysql-test/suite/rpl/r/rpl_sql_log_bin_triggers.result new file mode 100644 index 000000000000..2b02b9bf8fae --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_sql_log_bin_triggers.result @@ -0,0 +1,49 @@ +include/master-slave.inc +Warnings: +Note #### Sending passwords in plain text without SSL/TLS is extremely insecure. +Note #### Storing MySQL user name or password information in the connection metadata repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START REPLICA; see the 'START REPLICA Syntax' in the MySQL Manual for more information. +[connection master] +set @@session.sql_log_bin_triggers = OFF; +create table t1 (c1 char(1) primary key, c2 char(1)); +set @@session.sql_log_bin=0; +create table t2 (id char(2) primary key, cnt int, o char(1), n char(1)); +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'; +insert into t2 values +('u0', 0, ' ', ' '),('u1', 0, ' ', ' '), +('d0', 0, ' ', ' '),('d1', 0, ' ', ' '), +('i0', 0, ' ', ' '),('i1', 0, ' ', ' '); +set @@session.sql_log_bin=1; +include/sync_slave_sql_with_master.inc +# INSERT triggers test +insert into t1 values ('a','b'); +include/sync_slave_sql_with_master.inc +select * from t1; +c1 c2 +a b +# UPDATE triggers test +update t1 set C1= 'd'; +include/sync_slave_sql_with_master.inc +select * from t1; +c1 c2 +d b +# DELETE triggers test +delete from t1 where C1='d'; +include/sync_slave_sql_with_master.inc +select * from t1; +c1 c2 +set @@session.sql_log_bin=0; +drop table t2; +set @@session.sql_log_bin=1; +drop table t1; +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 000000000000..ebc9710b3982 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_triggers.test @@ -0,0 +1,240 @@ +-- source include/have_binlog_format_row.inc +-- source include/have_rbr_triggers.inc +-- source include/master-slave.inc + +-- echo # Test of row replication with triggers on the slave side +CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1)); +SELECT * FROM t1; + +--source include/sync_slave_sql_with_master.inc + +SET @old_replica_exec_mode= @@global.replica_exec_mode; +SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr; +SET @@global.replica_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'); + +--source include/sync_slave_sql_with_master.inc + +SELECT * FROM t2 order by id; + +connection master; +--echo # UPDATE triggers test +update t1 set C1= 'd'; +--source include/sync_slave_sql_with_master.inc + +SELECT * FROM t2 order by id; + +connection master; +--echo # DELETE triggers test +delete from t1 where C1='d'; + +--source include/sync_slave_sql_with_master.inc + +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'); + +--source include/sync_slave_sql_with_master.inc + +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) ); +insert into t1 values ('1','1'); + +--source include/sync_slave_sql_with_master.inc + +SELECT * FROM t2 order by id; + +connection master; +drop table t3,t1; + +--source include/sync_slave_sql_with_master.inc + +SET @@global.replica_exec_mode= @old_replica_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; + +--source include/sync_slave_sql_with_master.inc +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; + +--source include/sync_slave_sql_with_master.inc +select * from t2; +SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr; +--connection master +drop tables t2,t1; + +--source include/sync_slave_sql_with_master.inc + +-- echo # Triggers on slave do not work if master has some + +connection master; +CREATE TABLE t1 (C1 CHAR(1) primary key, C2 CHAR(1)); +SELECT * FROM t1; +create trigger t1_dummy before delete on t1 for each row + set @dummy= 1; + +--source include/sync_slave_sql_with_master.inc + +SET @old_replica_exec_mode= @@global.replica_exec_mode; +SET @old_slave_run_triggers_for_rbr= @@global.slave_run_triggers_for_rbr; +SET @@global.replica_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'); + +--source include/sync_slave_sql_with_master.inc + +SELECT * FROM t2 order by id; +connection master; +--echo # UPDATE triggers test +update t1 set C1= 'd'; + +--source include/sync_slave_sql_with_master.inc + +SELECT * FROM t2 order by id; + +connection master; +--echo # DELETE triggers test +delete from t1 where C1='d'; + +--source include/sync_slave_sql_with_master.inc + +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'); + +--source include/sync_slave_sql_with_master.inc + +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) ); +insert into t1 values ('1','1'); + +--source include/sync_slave_sql_with_master.inc + +SELECT * FROM t2 order by id; + +connection master; +drop table t3,t1; + +--source include/sync_slave_sql_with_master.inc + +SET @@global.replica_exec_mode= @old_replica_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; + +--source include/sync_slave_sql_with_master.inc + +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); + +--source include/sync_slave_sql_with_master.inc +select * from t1; + +--echo # +--echo # Verify slave skips running triggers if master ran and logged the row events for triggers +--echo # +--connection master +create table t4(a int, b int); +delete from tlog; +create trigger tr4 before insert on t4 for each row insert into tlog values (1); +insert into t4 values (1, 10),(2, 20); + +--source include/sync_slave_sql_with_master.inc +select * from t4; +select * from tlog; + +# Cleanup +set global slave_run_triggers_for_rbr = @slave_run_triggers_for_rbr.saved; +--connection master +drop table t1, tlog, t4; +--source include/sync_slave_sql_with_master.inc + +--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 000000000000..4ffddb7236f0 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_triggers_sbr.test @@ -0,0 +1,34 @@ +--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("impossible to write to binary log since statement is in row format and BINLOG_FORMAT = STATEMENT"); +call mtr.add_suppression("The replica coordinator and worker threads are stopped, possibly leaving data in inconsistent state"); +--enable_query_log + +set binlog_format = row; +create table t1 (i int); +create table t2 (i int); + +--source include/sync_slave_sql_with_master.inc + +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; +--source include/sync_slave_sql_with_master.inc + +SET @@global.slave_run_triggers_for_rbr= @old_slave_run_triggers_for_rbr; +--connection master +--source include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_sql_log_bin_triggers.test b/mysql-test/suite/rpl/t/rpl_sql_log_bin_triggers.test new file mode 100644 index 000000000000..415ec9cad09d --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_sql_log_bin_triggers.test @@ -0,0 +1,61 @@ +# This test verifies the functionality of sql_log_bin_triggers configuration +# option using the following steps: +# 1. Create master side triggers and table, don't propogate them to slave by turning off sql_log_bin +# 2. Execute SQL statements on master which run the triggers +# 3. Sync slave with master and verify slave is not broken + +source include/have_binlog_format_row.inc; +source include/master-slave.inc; + +connection master; +set @@session.sql_log_bin_triggers = OFF; + +create table t1 (c1 char(1) primary key, c2 char(1)); +set @@session.sql_log_bin=0; +create table t2 (id char(2) primary key, cnt int, o char(1), n char(1)); +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'; +insert into t2 values + ('u0', 0, ' ', ' '),('u1', 0, ' ', ' '), + ('d0', 0, ' ', ' '),('d1', 0, ' ', ' '), + ('i0', 0, ' ', ' '),('i1', 0, ' ', ' '); + +set @@session.sql_log_bin=1; +--source include/sync_slave_sql_with_master.inc + +connection master; +--echo # INSERT triggers test +insert into t1 values ('a','b'); +--source include/sync_slave_sql_with_master.inc +select * from t1; + +connection master; +--echo # UPDATE triggers test +update t1 set C1= 'd'; +--source include/sync_slave_sql_with_master.inc +select * from t1; + +connection master; +--echo # DELETE triggers test +delete from t1 where C1='d'; +--source include/sync_slave_sql_with_master.inc +select * from t1; + +connection master; +set @@session.sql_log_bin=0; +drop table t2; +set @@session.sql_log_bin=1; + +drop table t1; + +source include/rpl_end.inc; diff --git a/mysql-test/suite/sys_vars/r/slave_run_triggers_for_rbr_basic.result b/mysql-test/suite/sys_vars/r/slave_run_triggers_for_rbr_basic.result new file mode 100644 index 000000000000..488196a3c8f0 --- /dev/null +++ b/mysql-test/suite/sys_vars/r/slave_run_triggers_for_rbr_basic.result @@ -0,0 +1,27 @@ +set @old_slave_run_triggers_for_rbr = @@global.slave_run_triggers_for_rbr; +select @@global.slave_run_triggers_for_rbr; +@@global.slave_run_triggers_for_rbr +NO +set global slave_run_triggers_for_rbr = 1; +select @@global.slave_run_triggers_for_rbr; +@@global.slave_run_triggers_for_rbr +YES +set global slave_run_triggers_for_rbr = LOGGING; +select @@global.slave_run_triggers_for_rbr; +@@global.slave_run_triggers_for_rbr +LOGGING +set global slave_run_triggers_for_rbr = YES; +select @@global.slave_run_triggers_for_rbr; +@@global.slave_run_triggers_for_rbr +YES +set global slave_run_triggers_for_rbr = NO; +select @@global.slave_run_triggers_for_rbr; +@@global.slave_run_triggers_for_rbr +NO +set global slave_run_triggers_for_rbr = 100; +ERROR 42000: Variable 'slave_run_triggers_for_rbr' can't be set to the value of '100' +set global slave_run_triggers_for_rbr = WRONG_VALUE; +ERROR 42000: Variable 'slave_run_triggers_for_rbr' can't be set to the value of 'WRONG_VALUE' +set session slave_run_triggers_for_rbr = 1; +ERROR HY000: Variable 'slave_run_triggers_for_rbr' is a GLOBAL variable and should be set with SET GLOBAL +set @@global.slave_run_triggers_for_rbr = @old_slave_run_triggers_for_rbr; diff --git a/mysql-test/suite/sys_vars/r/sql_log_bin_triggers_basic.result b/mysql-test/suite/sys_vars/r/sql_log_bin_triggers_basic.result new file mode 100644 index 000000000000..8454966d2e69 --- /dev/null +++ b/mysql-test/suite/sys_vars/r/sql_log_bin_triggers_basic.result @@ -0,0 +1,66 @@ +SET @start_sql_log_bin_triggers = @@global.sql_log_bin_triggers; +SELECT @start_sql_log_bin_triggers; +@start_sql_log_bin_triggers +1 +SET @@global.sql_log_bin_triggers = false; +SET @@global.sql_log_bin_triggers = DEFAULT; +SELECT @@global.sql_log_bin_triggers; +@@global.sql_log_bin_triggers +1 +SET @@global.sql_log_bin_triggers = @start_sql_log_bin_triggers; +SELECT @@global.sql_log_bin_triggers = true; +@@global.sql_log_bin_triggers = true +1 +SET @@global.sql_log_bin_triggers = false; +SELECT @@global.sql_log_bin_triggers; +@@global.sql_log_bin_triggers +0 +SET @@global.sql_log_bin_triggers = true; +SELECT @@global.sql_log_bin_triggers; +@@global.sql_log_bin_triggers +1 +SET @@global.sql_log_bin_triggers = 1; +SELECT @@global.sql_log_bin_triggers; +@@global.sql_log_bin_triggers +1 +SET @@global.sql_log_bin_triggers = 0; +SELECT @@global.sql_log_bin_triggers; +@@global.sql_log_bin_triggers +0 +SET @@global.sql_log_bin_triggers = -1; +ERROR 42000: Variable 'sql_log_bin_triggers' can't be set to the value of '-1' +SELECT @@global.sql_log_bin_triggers; +@@global.sql_log_bin_triggers +0 +SET @@global.sql_log_bin_triggers = 100; +ERROR 42000: Variable 'sql_log_bin_triggers' can't be set to the value of '100' +SELECT @@global.sql_log_bin_triggers; +@@global.sql_log_bin_triggers +0 +SET @@global.sql_log_bin_triggers = 1000.01; +ERROR 42000: Incorrect argument type to variable 'sql_log_bin_triggers' +SELECT @@global.sql_log_bin_triggers; +@@global.sql_log_bin_triggers +0 +SET @@session.sql_log_bin_triggers = FALSE; +SELECT @@session.sql_log_bin_triggers; +@@session.sql_log_bin_triggers +0 +SELECT @@global.sql_log_bin_triggers = VARIABLE_VALUE +FROM performance_schema.global_variables +WHERE VARIABLE_NAME='sql_log_bin_triggers'; +@@global.sql_log_bin_triggers = VARIABLE_VALUE +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'OFF' +SELECT @@sql_log_bin_triggers = VARIABLE_VALUE +FROM performance_schema.session_variables +WHERE VARIABLE_NAME='sql_log_bin_triggers'; +@@sql_log_bin_triggers = VARIABLE_VALUE +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'OFF' +SET @@global.sql_log_bin_triggers = @start_sql_log_bin_triggers; +SELECT @@global.sql_log_bin_triggers; +@@global.sql_log_bin_triggers +1 diff --git a/mysql-test/suite/sys_vars/t/slave_run_triggers_for_rbr_basic.test b/mysql-test/suite/sys_vars/t/slave_run_triggers_for_rbr_basic.test new file mode 100644 index 000000000000..2a66750a6cb8 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/slave_run_triggers_for_rbr_basic.test @@ -0,0 +1,21 @@ +set @old_slave_run_triggers_for_rbr = @@global.slave_run_triggers_for_rbr; + +select @@global.slave_run_triggers_for_rbr; +set global slave_run_triggers_for_rbr = 1; +select @@global.slave_run_triggers_for_rbr; +set global slave_run_triggers_for_rbr = LOGGING; +select @@global.slave_run_triggers_for_rbr; +set global slave_run_triggers_for_rbr = YES; +select @@global.slave_run_triggers_for_rbr; +set global slave_run_triggers_for_rbr = NO; +select @@global.slave_run_triggers_for_rbr; + +--error ER_WRONG_VALUE_FOR_VAR +set global slave_run_triggers_for_rbr = 100; +--error ER_WRONG_VALUE_FOR_VAR +set global slave_run_triggers_for_rbr = WRONG_VALUE; + +--error ER_GLOBAL_VARIABLE +set session slave_run_triggers_for_rbr = 1; + +set @@global.slave_run_triggers_for_rbr = @old_slave_run_triggers_for_rbr; diff --git a/mysql-test/suite/sys_vars/t/sql_log_bin_triggers_basic.test b/mysql-test/suite/sys_vars/t/sql_log_bin_triggers_basic.test new file mode 100644 index 000000000000..1cac92fbbed8 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/sql_log_bin_triggers_basic.test @@ -0,0 +1,46 @@ +source include/load_sysvars.inc; + +SET @start_sql_log_bin_triggers = @@global.sql_log_bin_triggers; +SELECT @start_sql_log_bin_triggers; + +SET @@global.sql_log_bin_triggers = false; +SET @@global.sql_log_bin_triggers = DEFAULT; +SELECT @@global.sql_log_bin_triggers; + +SET @@global.sql_log_bin_triggers = @start_sql_log_bin_triggers; +SELECT @@global.sql_log_bin_triggers = true; + +SET @@global.sql_log_bin_triggers = false; +SELECT @@global.sql_log_bin_triggers; +SET @@global.sql_log_bin_triggers = true; +SELECT @@global.sql_log_bin_triggers; + +SET @@global.sql_log_bin_triggers = 1; +SELECT @@global.sql_log_bin_triggers; +SET @@global.sql_log_bin_triggers = 0; +SELECT @@global.sql_log_bin_triggers; + +--Error ER_WRONG_VALUE_FOR_VAR +SET @@global.sql_log_bin_triggers = -1; +SELECT @@global.sql_log_bin_triggers; +--Error ER_WRONG_VALUE_FOR_VAR +SET @@global.sql_log_bin_triggers = 100; +SELECT @@global.sql_log_bin_triggers; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@global.sql_log_bin_triggers = 1000.01; +SELECT @@global.sql_log_bin_triggers; + +SET @@session.sql_log_bin_triggers = FALSE; +SELECT @@session.sql_log_bin_triggers; + +SELECT @@global.sql_log_bin_triggers = VARIABLE_VALUE +FROM performance_schema.global_variables +WHERE VARIABLE_NAME='sql_log_bin_triggers'; + +SELECT @@sql_log_bin_triggers = VARIABLE_VALUE +FROM performance_schema.session_variables +WHERE VARIABLE_NAME='sql_log_bin_triggers'; + + +SET @@global.sql_log_bin_triggers = @start_sql_log_bin_triggers; +SELECT @@global.sql_log_bin_triggers; diff --git a/sql/handler.cc b/sql/handler.cc index f5c25d61e2b7..d9546f2b57f4 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -7758,6 +7758,8 @@ bool ha_show_status(THD *thd, handlerton *db_type, enum ha_stat_type stat) { - The binary log is open - The database the table resides in shall be binlogged (binlog_*_db rules) - table is not mysql.event + - This is not a table opened when executing triggers or sql_log_bin_triggers + is TRUE. */ static bool check_table_binlog_row_based(THD *thd, TABLE *table) { @@ -7774,7 +7776,7 @@ static bool check_table_binlog_row_based(THD *thd, TABLE *table) { return (thd->is_current_stmt_binlog_format_row() && table->s->cached_row_logging_check && (thd->variables.option_bits & OPTION_BIN_LOG) && - mysql_bin_log.is_open()); + mysql_bin_log.is_open() && !table->disable_sql_log_bin_triggers); } /** @brief diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index e45a69a157e4..63cc87c96db5 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -3342,7 +3342,7 @@ bool subselect_hash_sj_engine::setup( if (tmp_table_ref == nullptr) return true; // Assign Table_ref pointer temporarily, while creatung fields: - tmp_table->pos_in_table_list = tmp_table_ref; + tmp_table->set_pos_in_table_list(tmp_table_ref); tmp_table_ref->query_block = unit->first_query_block(); KEY_PART_INFO *key_parts = tmp_key->key_part; diff --git a/sql/log_event.cc b/sql/log_event.cc index 5a504cae49fa..054c6142fc82 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -160,6 +160,8 @@ #include "sql/sql_show.h" // append_identifier #include "sql/sql_tablespace.h" // Sql_cmd_tablespace #include "sql/table.h" +#include "sql/table_trigger_dispatcher.h" +#include "sql/thd_raii.h" // Disable_binlog_guard #include "sql/transaction.h" // trans_rollback_stmt #include "sql/transaction_info.h" #include "sql/tztime.h" // Time_zone @@ -7817,7 +7819,8 @@ Rows_log_event::Rows_log_event(THD *thd_arg, TABLE *tbl_arg, m_key(nullptr), m_key_info(nullptr), m_distinct_keys(Key_compare(&m_key_info)), - m_distinct_key_spare_buf(nullptr) { + m_distinct_key_spare_buf(nullptr), + master_had_triggers(false) { DBUG_TRACE; common_header->type_code = event_type; m_row_count = 0; @@ -7908,7 +7911,8 @@ Rows_log_event::Rows_log_event( m_key(nullptr), m_key_info(nullptr), m_distinct_keys(Key_compare(&m_key_info)), - m_distinct_key_spare_buf(nullptr) + m_distinct_key_spare_buf(nullptr), + master_had_triggers(false) #endif { DBUG_TRACE; @@ -8033,6 +8037,21 @@ Rows_log_event::~Rows_log_event() { } #ifdef MYSQL_SERVER +bool Rows_log_event::process_triggers(enum_trigger_event_type event, + enum_trigger_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) { + Disable_binlog_guard binlog_guard(thd); + result = m_table->triggers->process_triggers(thd, event, time_type, + old_row_is_record1); + } else + result = m_table->triggers->process_triggers(thd, event, time_type, + old_row_is_record1); + DBUG_RETURN(result); +} + int Rows_log_event::unpack_current_row(const Relay_log_info *const rli, MY_BITMAP const *cols, bool is_after_image, bool only_seek) { @@ -9713,6 +9732,20 @@ void Rows_log_event::set_writeset_from_col_names(TABLE *table, } } +/** + 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) = nullptr; + lex->query_tables = nullptr; + lex->query_tables_last = &lex->query_tables; +} + int Rows_log_event::do_apply_event(Relay_log_info const *rli) { DBUG_TRACE; TABLE *table = nullptr; @@ -9799,6 +9832,26 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { "now SIGNAL before_open_table WAIT_FOR go_ahead_sql"; assert(!debug_sync_set_action(thd, STRING_WITH_LEN(action))); };); + + if (slave_run_triggers_for_rbr) { + LEX *lex = thd->lex; + const auto new_trg_event_map = get_trg_event_map(); + + /* + Trigger's procedures work with global table list. So we have to add + rli->tables_to_lock content there to get trigger's in the list + Then restore_empty_query_table_list() restore the list as it was + */ + assert(lex->query_tables == nullptr); + if ((lex->query_tables = rli->tables_to_lock)) + rli->tables_to_lock->prev_global = &lex->query_tables; + for (Table_ref *tables = rli->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, rli->tables_to_lock, 0)) { if (thd->is_error()) { uint actual_error = thd->get_stmt_da()->mysql_errno(); @@ -9816,6 +9869,9 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { "Error executing row event: '%s'", thd->get_stmt_da()->message_text()); thd->is_slave_error = true; + error = actual_error; + /* remove trigger's tables */ + goto err; } } return 1; @@ -9881,8 +9937,9 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { ("Table: %s.%s is not compatible with source", ptr->table->s->db.str, ptr->table->s->table_name.str)); if (thd->is_slave_error) { - const_cast(rli)->slave_close_thread_tables(thd); - return ERR_BAD_TABLE_DEF; + error = ERR_BAD_TABLE_DEF; + /* remove trigger's tables */ + goto err; } else { thd->get_stmt_da()->reset_condition_info(thd); clear_all_errors(thd, const_cast(rli)); @@ -9912,6 +9969,14 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { if (ptr->parent_l) continue; const_cast(rli)->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_ref (RPL_Table_ref) but row event uses only TABLE so I need to + find somehow the corresponding Table_ref. + */ + ptr->table->master_had_triggers = + ((RPL_Table_ref *)ptr)->master_had_triggers; } /* @@ -9939,7 +10004,6 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { rli->report(ERROR_LEVEL, applier_error, ER_THD_NONCONST(thd, applier_error), buf); thd->is_slave_error = true; - const_cast(rli)->slave_close_thread_tables(thd); } else { /* For the cases in which a 'BINLOG' statement is set to @@ -9948,7 +10012,9 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { my_printf_error(applier_error, ER_THD_NONCONST(thd, applier_error), MYF(0), buf); } - return applier_error; + /* remove trigger's tables */ + error = applier_error; + goto err; } } @@ -9956,8 +10022,8 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { const_cast(rli)->m_table_map.get_table(m_table_id); DBUG_PRINT("debug", - ("m_table: %p, m_table_id: %llu", m_table, m_table_id.id())); - + ("m_table: %p, m_table_id: %llu%s", m_table, m_table_id.id(), + (table && master_had_triggers ? " (master had triggers)" : ""))); /* A row event comprising of a P_S table - should not be replicated (i.e executed) by the slave SQL thread. @@ -10032,6 +10098,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { } bool no_columns_to_update = false; + master_had_triggers = table->master_had_triggers; // set the database LEX_CSTRING thd_db; LEX_CSTRING current_db_name_saved = thd->db(); @@ -10341,10 +10408,14 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { */ thd->reset_current_stmt_binlog_format_row(); thd->is_slave_error = true; - return error; + /* remove trigger's tables */ + goto err; } end: + /* remove trigger's tables */ + if (slave_run_triggers_for_rbr) restore_empty_query_table_list(thd->lex); + if (get_flags(STMT_END_F)) { if ((error = rows_event_stmt_cleanup(rli, thd))) { if (table) @@ -10380,6 +10451,11 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) { mysql_mutex_unlock(&thd->LOCK_thd_query); } return error; + +err: + if (slave_run_triggers_for_rbr) restore_empty_query_table_list(thd->lex); + const_cast(rli)->slave_close_thread_tables(thd); + return error; } Log_event::enum_skip_reason Rows_log_event::do_shall_skip(Relay_log_info *rli) { @@ -10786,6 +10862,11 @@ Table_map_log_event::Table_map_log_event(THD *thd_arg, TABLE *tbl, (tbl->s->db.str[tbl->s->db.length] == 0)); assert(tbl->s->table_name.str[tbl->s->table_name.length] == 0); + // Trigger changes are not logged if sql_log_bin_triggers is FALSE. + // In this case, slaves should execute triggers while applying row events. + if (tbl->triggers && thd->variables.sql_log_bin_triggers) + m_flags |= TM_BIT_HAS_TRIGGERS_F; + m_data_size = Binary_log_event::TABLE_MAP_HEADER_LEN; DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_source", m_data_size = 6;); @@ -11083,8 +11164,12 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) { "inject_tblmap_same_id_maps_diff_table", 0, m_table_id.id()); table_list->updating = true; table_list->required_type = dd::enum_table_type::BASE_TABLE; - DBUG_PRINT("debug", ("table: %s is mapped to %llu", table_list->table_name, - table_list->table_id.id())); + table_list->master_had_triggers = m_flags & TM_BIT_HAS_TRIGGERS_F; + DBUG_PRINT( + "debug", + ("table: %s is mapped to %llu%s", table_list->table_name, + table_list->table_id.id(), + (table_list->master_had_triggers ? " (master had triggers)" : ""))); enum_tbl_map_status tblmap_status = check_table_map(rli, table_list); if (tblmap_status == OK_TO_PROCESS) { @@ -12298,6 +12383,8 @@ int Write_rows_log_event::do_before_row_operations( /* 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->ha_extra(HA_EXTRA_WRITE_CAN_REPLACE); /* @@ -12313,6 +12400,9 @@ int Write_rows_log_event::do_before_row_operations( */ } + 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; /* @@ -12443,6 +12533,9 @@ int Write_rows_log_event::write_row(const Relay_log_info *const rli, int keynum = 0; char *key = nullptr; + const bool invoke_triggers = + slave_run_triggers_for_rbr && !master_had_triggers && table->triggers; + prepare_record(table, &this->m_local_cols, table->file->ht->db_type != DB_TYPE_NDBCLUSTER); @@ -12468,7 +12561,8 @@ int Write_rows_log_event::write_row(const Relay_log_info *const rli, if (invoke_table_check_constraints(thd, table)) return ER_CHECK_CONSTRAINT_VIOLATED; - if (m_curr_row == m_rows_buf) { + if (m_curr_row == m_rows_buf && !invoke_triggers) { + // 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 */ @@ -12495,6 +12589,10 @@ int Write_rows_log_event::write_row(const Relay_log_info *const rli, DBUG_PRINT_BITSET("debug", "read_set = %s", table->read_set); #endif + if (invoke_triggers && + process_triggers(TRG_EVENT_INSERT, TRG_ACTION_BEFORE, true)) + 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 @@ -12635,34 +12733,57 @@ int Write_rows_log_event::write_row(const Relay_log_info *const rli, if (last_uniq_key(table, keynum) && !table->s->is_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; + 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; + case 0: + break; - default: - DBUG_PRINT("info", ("ha_update_row() returns error %d", error)); - table->file->print_error(error, MYF(0)); + 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 } - goto 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]))) { - DBUG_PRINT("info", ("ha_delete_row() returns error %d", error)); - table->file->print_error(error, MYF(0)); + if (invoke_triggers && + process_triggers(TRG_EVENT_DELETE, TRG_ACTION_BEFORE, true)) { + error = HA_ERR_GENERIC; // in case if error is not set yet goto error; + } else { + 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)); + goto error; + } + if (invoke_triggers && + process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, true)) { + error = HA_ERR_GENERIC; // in case if error is not set yet + goto error; + } } /* 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 + error: m_table->default_column_bitmaps(); return error; @@ -12680,6 +12801,12 @@ int Write_rows_log_event::do_exec_row(const Relay_log_info *const rli) { return error; } +uint8 Write_rows_log_event::get_trg_event_map() const noexcept { + return (static_cast(1 << static_cast(TRG_EVENT_INSERT)) | + static_cast(1 << static_cast(TRG_EVENT_UPDATE)) | + static_cast(1 << static_cast(TRG_EVENT_DELETE))); +} + #endif /* defined(MYSQL_SERVER) */ #ifndef MYSQL_SERVER @@ -12757,6 +12884,9 @@ int Delete_rows_log_event::do_before_row_operations( */ if (get_flags(STMT_END_F)) thd->status_var.com_stat[SQLCOM_DELETE]++; + if (slave_run_triggers_for_rbr && !master_had_triggers) + m_table->prepare_triggers_for_delete_stmt_or_event(); + /* Let storage engines treat this event as a DELETE command. @@ -12779,15 +12909,28 @@ int Delete_rows_log_event::do_after_row_operations(const Relay_log_info *const, } int Delete_rows_log_event::do_exec_row(const Relay_log_info *const) { - int error; + int error = 0; assert(m_table != nullptr); + const bool invoke_triggers = + slave_run_triggers_for_rbr && !master_had_triggers && m_table->triggers; + /* m_table->record[0] contains the BI */ m_table->mark_columns_per_binlog_row_image(thd); - 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->default_column_bitmaps(); return error; } +uint8 Delete_rows_log_event::get_trg_event_map() const noexcept { + return static_cast(1 << static_cast(TRG_EVENT_DELETE)); +} + #endif /* defined(MYSQL_SERVER) */ #ifndef MYSQL_SERVER @@ -12892,6 +13035,9 @@ int Update_rows_log_event::do_before_row_operations( */ if (get_flags(STMT_END_F)) thd->status_var.com_stat[SQLCOM_UPDATE]++; + if (slave_run_triggers_for_rbr && !master_had_triggers) + m_table->prepare_triggers_for_update_stmt_or_event(); + /* Let storage engines treat this event as an UPDATE command. @@ -12916,6 +13062,8 @@ int Update_rows_log_event::do_after_row_operations(const Relay_log_info *const, int Update_rows_log_event::do_exec_row(const Relay_log_info *const rli) { assert(m_table != nullptr); int error = 0; + const bool invoke_triggers = + slave_run_triggers_for_rbr && !master_had_triggers && m_table->triggers; /* This is the situation after locating BI: @@ -12948,13 +13096,27 @@ int Update_rows_log_event::do_exec_row(const Relay_log_info *const rli) { DBUG_DUMP("new values", m_table->record[0], m_table->s->reclength); m_table->mark_columns_per_binlog_row_image(thd); + 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->default_column_bitmaps(); return error; } +uint8 Update_rows_log_event::get_trg_event_map() const noexcept { + return static_cast(1 << static_cast(TRG_EVENT_UPDATE)); +} + #endif /* defined(MYSQL_SERVER) */ #ifndef MYSQL_SERVER diff --git a/sql/log_event.h b/sql/log_event.h index 71ac829efad5..106c6416e870 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -85,6 +85,7 @@ class Basic_ostream; #include "sql/key.h" #include "sql/rpl_filter.h" // rpl_filter #include "sql/table.h" +#include "sql/trigger_def.h" // enum_trigger_event_type #include "sql/xa.h" #endif @@ -2380,7 +2381,9 @@ class Table_map_log_event : public binary_log::Table_map_event, TM_GENERATED_INVISIBLE_PK_F = (1U << 2), /* Picked 10 because MySQL flags flow in ascending order and MariaDB flags flow in descending order */ - TM_METADATA_NOT_FB_FORMAT_F = (1U << 10) + TM_METADATA_NOT_FB_FORMAT_F = (1U << 10), + // 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; } @@ -2765,6 +2768,10 @@ class Rows_log_event : public virtual binary_log::Rows_event, public Log_event { bool write_data_header(Basic_ostream *ostream) override; bool write_data_body(Basic_ostream *ostream) override; const char *get_db() override { return m_table->s->db.str; } + virtual uint8 get_trg_event_map() const noexcept = 0; + bool process_triggers(enum_trigger_event_type event, + enum_trigger_action_time_type time_type, + bool old_row_is_record1); #endif uint m_row_count; /* The number of rows added to the event */ @@ -2880,6 +2887,7 @@ class Rows_log_event : public virtual binary_log::Rows_event, public Log_event { for doing an index scan with HASH_SCAN search algorithm. */ uchar *m_distinct_key_spare_buf; + bool master_had_triggers; /** Unpack the current row image from the event into m_table->record[0]. @@ -3259,6 +3267,7 @@ class Write_rows_log_event : public Rows_log_event, int do_before_row_operations(const Relay_log_info *const) override; int do_after_row_operations(const Relay_log_info *const, int) override; int do_exec_row(const Relay_log_info *const) override; + uint8 get_trg_event_map() const noexcept override; #endif }; @@ -3354,6 +3363,7 @@ class Update_rows_log_event : public Rows_log_event, int do_before_row_operations(const Relay_log_info *const) override; int do_after_row_operations(const Relay_log_info *const, int) override; int do_exec_row(const Relay_log_info *const) override; + uint8 get_trg_event_map() const noexcept override; int skip_after_image_for_update_event(const Relay_log_info *rli, const uchar *curr_bi_start) override; @@ -3460,6 +3470,7 @@ class Delete_rows_log_event : public Rows_log_event, int do_before_row_operations(const Relay_log_info *const) override; int do_after_row_operations(const Relay_log_info *const, int) override; int do_exec_row(const Relay_log_info *const) override; + uint8 get_trg_event_map() const noexcept override; #endif }; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 0ab94ac91ff5..d70f52264335 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -1386,6 +1386,7 @@ uint replica_net_timeout; ulong replica_exec_mode_options; ulonglong replica_type_conversions_options; ulong opt_mts_replica_parallel_workers; +ulong slave_run_triggers_for_rbr = 0; ulonglong opt_mts_pending_jobs_size_max; ulonglong slave_rows_search_algorithms_options; bool opt_replica_preserve_commit_order; diff --git a/sql/mysqld.h b/sql/mysqld.h index 3f8e32050867..733261c7744d 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -213,6 +213,7 @@ enum enum_replica_type_conversions { REPLICA_TYPE_CONVERSIONS_ALL_UNSIGNED, REPLICA_TYPE_CONVERSIONS_ALL_SIGNED }; +extern ulong slave_run_triggers_for_rbr; extern ulonglong replica_type_conversions_options; extern bool read_only, opt_readonly; diff --git a/sql/rpl_utility.h b/sql/rpl_utility.h index ccdfc60587e3..7bd42f2f8c66 100644 --- a/sql/rpl_utility.h +++ b/sql/rpl_utility.h @@ -536,6 +536,7 @@ struct RPL_Table_ref : public Table_ref { bool m_tabledef_valid; table_def m_tabledef; TABLE *m_conv_table; + bool master_had_triggers; }; class Deferred_log_events { diff --git a/sql/sp_head.cc b/sql/sp_head.cc index ebce498ee88e..212091047341 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3358,7 +3358,8 @@ void sp_head::add_used_tables_to_table_list(THD *thd, table->prelocking_placeholder = true; table->belong_to_view = belong_to_view; table->trg_event_map = stab->trg_event_map; - + table->disable_sql_log_bin_triggers = + !thd->variables.sql_log_bin_triggers; /* Everything else should be zeroed */ **query_tables_last_ptr = table; diff --git a/sql/sp_instr.cc b/sql/sp_instr.cc index 6541d4ff415c..8af5bd4b7692 100644 --- a/sql/sp_instr.cc +++ b/sql/sp_instr.cc @@ -450,6 +450,18 @@ bool sp_lex_instr::reset_lex_and_exec_core(THD *thd, uint *nextp, SP_instr_error_handler sp_instr_error_handler; thd->push_internal_handler(&sp_instr_error_handler); + /* + Set disable_sql_log_bin_triggers flag for query_tables if binlog is + turned OFF for trigger statements. This is necessary to avoid writing down + Table_map_log_events. + */ + if (!thd->variables.sql_log_bin_triggers) { + for (Table_ref *tables = m_lex->query_tables; tables; + tables = tables->next_global) { + tables->disable_sql_log_bin_triggers = true; + } + } + /* Open tables if needed. */ if (!error) { diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index f84d4cfa45d9..2b69966490f4 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -560,13 +560,7 @@ bool Sql_cmd_delete::delete_from_single_table(THD *thd) { THD_STAGE_INFO(thd, stage_updating); - if (has_after_triggers) { - /* - 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->ha_extra(HA_EXTRA_DELETE_CANNOT_BATCH); + if (table->prepare_triggers_for_delete_stmt_or_event()) { will_batch = false; } else { // No after delete triggers, attempt to start bulk delete @@ -1010,14 +1004,7 @@ bool DeleteRowsIterator::Init() { if (!IsBitSet(tableno, m_tables_to_delete_from)) continue; // We are going to delete from this table - if (IsBitSet(tableno, m_tables_with_after_triggers)) { - /* - The table has AFTER DELETE triggers that might access the subject - table and therefore might need delete to be done immediately. - So we turn-off the batching. - */ - (void)table->file->ha_extra(HA_EXTRA_DELETE_CANNOT_BATCH); - } + table->prepare_triggers_for_delete_stmt_or_event(); if (thd()->lex->is_ignore()) { table->file->ha_extra(HA_EXTRA_IGNORE_DUP_KEY); } diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index 8f60b360ee64..bbdd0aa60f68 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -211,7 +211,7 @@ TABLE *Common_table_expr::clone_tmp_table(THD *thd, Table_ref *tl) { t->copy_blobs = true; tl->table = t; - t->pos_in_table_list = tl; + t->set_pos_in_table_list(tl); // If initial CTE table has a hash key, set up a hash key for // all clones too. @@ -869,7 +869,7 @@ bool Table_ref::setup_materialized_derived_tmp_table(THD *thd) if (rc) return true; /* purecov: inspected */ table = derived_result->table; - table->pos_in_table_list = this; + table->set_pos_in_table_list(this); if (m_common_table_expr && m_common_table_expr->tmp_tables.push_back(this)) return true; /* purecov: inspected */ } @@ -959,7 +959,7 @@ bool Table_ref::setup_table_function(THD *thd) { if (table_function->create_result_table(thd, 0LL, alias)) return true; /* purecov: inspected */ table = table_function->table; - table->pos_in_table_list = this; + table->set_pos_in_table_list(this); table->s->tmp_table = NON_TRANSACTIONAL_TMP_TABLE; diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 40d2f041e664..f940a5de839e 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -550,7 +550,7 @@ bool Sql_cmd_handler_read::execute(THD *thd) { if (!lock) goto err1; // mysql_lock_tables() printed error message already tables->table = hash_tables->table; - tables->table->pos_in_table_list = tables; + tables->table->set_pos_in_table_list(tables); if (cond) { /* @@ -767,7 +767,7 @@ bool Sql_cmd_handler_read::execute(THD *thd) { so that the engine doesn't have to count locks. */ trans_commit_stmt(thd); - hash_tables->table->pos_in_table_list = hash_tables; + hash_tables->table->set_pos_in_table_list(hash_tables); mysql_unlock_tables(thd, lock); thd->mdl_context.rollback_to_savepoint(mdl_savepoint); table->cleanup_value_generator_items(); @@ -783,7 +783,7 @@ bool Sql_cmd_handler_read::execute(THD *thd) { thd->mdl_context.rollback_to_savepoint(mdl_savepoint); err0: if (hash_tables != nullptr && hash_tables->table != nullptr) - hash_tables->table->pos_in_table_list = hash_tables; + hash_tables->table->set_pos_in_table_list(hash_tables); DBUG_PRINT("exit", ("ERROR")); return true; } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 946f3b5b118e..fa960b2af9d8 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -318,40 +318,6 @@ bool validate_default_values_of_unset_fields(THD *thd, TABLE *table) { return false; } -/** - Prepare triggers for INSERT-like statement. - - @param thd Thread handler - @param 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(THD *thd, 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->ha_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->ha_extra(HA_EXTRA_UPDATE_CANNOT_BATCH); - } - } - table->mark_columns_needed_for_insert(thd); -} - /** Setup data for field BLOB/GEOMETRY field types for execution of "INSERT...UPDATE" statement. For a expression in 'UPDATE' clause @@ -559,8 +525,8 @@ bool Sql_cmd_insert_values::execute_inner(THD *thd) { if (thd->locked_tables_mode <= LTM_LOCK_TABLES) insert_table->file->ha_start_bulk_insert(insert_many_values.size()); - prepare_triggers_for_insert_stmt(thd, insert_table); - + insert_table->prepare_triggers_for_insert_stmt_or_event(); + insert_table->mark_columns_needed_for_insert(thd); /* Count warnings for all inserts. For single row insert, generate an error if trying to set a NOT NULL field to NULL. @@ -2304,7 +2270,8 @@ bool Query_result_insert::start_execution(THD *thd) { if (duplicate_handling == DUP_UPDATE) table->file->ha_extra(HA_EXTRA_INSERT_WITH_UPDATE); - prepare_triggers_for_insert_stmt(thd, table); + table->prepare_triggers_for_insert_stmt_or_event(); + table->mark_columns_needed_for_insert(thd); for (Field **next_field = table->field; *next_field; ++next_field) { (*next_field)->reset_warnings(); @@ -2356,7 +2323,8 @@ bool Query_result_insert::send_data(THD *thd, currently it is called for each row inserted by INSERT SELECT. Which looks like unnessary and results in resource wastage. */ - prepare_triggers_for_insert_stmt(thd, table); + table->prepare_triggers_for_insert_stmt_or_event(); + table->mark_columns_needed_for_insert(thd); if (table_list) // Not CREATE ... SELECT { diff --git a/sql/sql_insert.h b/sql/sql_insert.h index 1deedfacb0f4..664c134cecd3 100644 --- a/sql/sql_insert.h +++ b/sql/sql_insert.h @@ -51,7 +51,6 @@ struct MYSQL_LOCK; bool check_that_all_fields_are_given_values(THD *thd, TABLE *entry, Table_ref *table_list); -void prepare_triggers_for_insert_stmt(THD *thd, TABLE *table); bool write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update); bool validate_default_values_of_unset_fields(THD *thd, TABLE *table); diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 5eff9d64ab21..4f428b36220a 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -432,7 +432,8 @@ bool Sql_cmd_load_table::execute_inner(THD *thd, if (table->triggers->mark_fields(TRG_EVENT_INSERT)) return true; } - prepare_triggers_for_insert_stmt(thd, table); + table->prepare_triggers_for_insert_stmt_or_event(); + table->mark_columns_needed_for_insert(thd); size_t tot_length = 0; bool use_blobs = false, use_vars = false; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index f9b0fb943e4f..a7f53abec64b 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -3198,7 +3198,7 @@ bool JOIN::setup_semijoin_materialized_table(JOIN_TAB *tab, uint tableno, tl->set_tableno(tableno); - table->pos_in_table_list = tl; + table->set_pos_in_table_list(tl); table->pos_in_table_list->query_block = query_block; if (!(sjm_opt->mat_fields = (Item_field **)thd->mem_root->Alloc( diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 34a607b1832b..1e6e613fd462 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -4895,7 +4895,7 @@ bool mysql_schema_table(THD *thd, LEX *lex, Table_ref *table_list) { table->alias_name_used = my_strcasecmp( table_alias_charset, table_list->table_name, table_list->alias); table_list->table = table; - table->pos_in_table_list = table_list; + table->set_pos_in_table_list(table_list); if (table_list->query_block->first_execution) table_list->query_block->add_base_options(OPTION_SCHEMA_TABLE); lex->safe_to_cache_query = false; diff --git a/sql/sql_tmp_table.cc b/sql/sql_tmp_table.cc index caa9fecadb61..3f6988c37bd5 100644 --- a/sql/sql_tmp_table.cc +++ b/sql/sql_tmp_table.cc @@ -2670,7 +2670,7 @@ bool create_ondisk_from_heap(THD *thd, TABLE *wtable, int error, new_table.hash_field = table->hash_field; new_table.group = table->group; new_table.alias = table->alias; - new_table.pos_in_table_list = table->pos_in_table_list; + new_table.set_pos_in_table_list(table->pos_in_table_list); new_table.reginfo = table->reginfo; new_table.read_set = table->read_set; new_table.write_set = table->write_set; diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index ef24513bb2a6..7557e6086cbd 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -275,7 +275,7 @@ TABLE *Sql_cmd_ddl_trigger_common::open_and_lock_subj_table( } TABLE *table = tables->table; - table->pos_in_table_list = tables; + table->set_pos_in_table_list(tables); /* Later on we will need it to downgrade the lock */ *mdl_ticket = table->mdl_ticket; diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 6d5f48fac856..68ff964e3855 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -844,13 +844,7 @@ bool Sql_cmd_update::update_single_table(THD *thd) { /// read_removal is only used by NDB storage engine bool read_removal = false; - if (has_after_triggers) { - /* - 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->ha_extra(HA_EXTRA_UPDATE_CANNOT_BATCH); + if (table->prepare_triggers_for_update_stmt_or_event()) { will_batch = false; } else { // No after update triggers, attempt to start bulk update @@ -1878,7 +1872,7 @@ bool Query_result_update::prepare(THD *thd, const mem_root_deque &, TABLE *const table = tr->table; table->no_keyread = true; table->covering_keys.clear_all(); - table->pos_in_table_list = dup; + table->set_pos_in_table_list(dup); } } @@ -2195,7 +2189,7 @@ bool Query_result_update::optimize() { TABLE *const table = tr->table; update_table->table = table; - table->pos_in_table_list = update_table; + table->set_pos_in_table_list(update_table); table->covering_keys.clear_all(); if (table->triggers && diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 28e4a46f40e8..363d2446bdda 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -4206,6 +4206,27 @@ static Sys_var_enum Sys_replica_exec_mode( static Sys_var_deprecated_alias Sys_slave_exec_mode("slave_exec_mode", Sys_replica_exec_mode); +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 Sys_var_bool sql_log_bin_triggers( + "sql_log_bin_triggers", + "The row changes generated by execution of triggers are not logged in" + "binlog if this option is false.", + SESSION_VAR(sql_log_bin_triggers), CMD_LINE(OPT_ARG), DEFAULT(true)); + const char *replica_type_conversions_name[] = { "ALL_LOSSY", "ALL_NON_LOSSY", "ALL_UNSIGNED", "ALL_SIGNED", nullptr}; static Sys_var_set Sys_replica_type_conversions( diff --git a/sql/system_variables.h b/sql/system_variables.h index 1730ebf9f114..7f8793006451 100644 --- a/sql/system_variables.h +++ b/sql/system_variables.h @@ -57,6 +57,12 @@ enum enum_rbr_exec_mode { RBR_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 +}; + // Values for binlog_row_image sysvar enum enum_binlog_row_image { /** PKE in the before image and changed columns in the after image */ @@ -459,6 +465,8 @@ struct System_variables { */ bool require_row_format; + bool sql_log_bin_triggers; + bool gap_lock_raise_error; bool gap_lock_write_log; diff --git a/sql/table.cc b/sql/table.cc index 9791efd990d1..10eb9929ab9c 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -4116,8 +4116,9 @@ void TABLE::init(THD *thd, Table_ref *tl) { covering_keys = s->keys_for_keyread; set_not_started(); + master_had_triggers = false; /* used in RBR Triggers */ - pos_in_table_list = tl; + set_pos_in_table_list(tl); tl->table = this; clear_column_bitmaps(); @@ -8014,4 +8015,67 @@ bool assert_invalid_stats_is_locked(const TABLE *table) { return true; } #endif + +/* + 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->ha_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->ha_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->ha_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->ha_extra(HA_EXTRA_UPDATE_CANNOT_BATCH); + return true; + } + return false; +} + +void TABLE::set_pos_in_table_list(Table_ref *table_list) noexcept { + pos_in_table_list = table_list; + if (table_list) + disable_sql_log_bin_triggers = table_list->disable_sql_log_bin_triggers; +} + ////////////////////////////////////////////////////////////////////////// diff --git a/sql/table.h b/sql/table.h index 3caab6128758..df4ad4481ac8 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1869,6 +1869,9 @@ struct TABLE { /* If true, all partitions have been pruned away */ bool all_partitions_pruned_away{false}; MDL_ticket *mdl_ticket{nullptr}; + /* used in RBR Triggers */ + bool master_had_triggers{false}; + bool disable_sql_log_bin_triggers{false}; private: /// Cost model object for operations on this table @@ -1890,6 +1893,7 @@ struct TABLE { bool fill_item_list(mem_root_deque *item_list) const; void clear_column_bitmaps(void); void prepare_for_position(void); + void set_pos_in_table_list(Table_ref *table_list) noexcept; void mark_column_used(Field *field, enum enum_mark_columns mark); void mark_columns_used_by_index_no_reset(uint index, MY_BITMAP *map, @@ -2413,6 +2417,10 @@ struct TABLE { set or not */ bool should_binlog_drop_if_temp(void) const; + + void prepare_triggers_for_insert_stmt_or_event(); + bool prepare_triggers_for_delete_stmt_or_event(); + bool prepare_triggers_for_update_stmt_or_event(); }; static inline void empty_record(TABLE *table) { @@ -3946,6 +3954,9 @@ class Table_ref { MY_BITMAP read_set_saved; MY_BITMAP write_set_saved; MY_BITMAP read_set_internal_saved; + + public: + bool disable_sql_log_bin_triggers{false}; }; /* diff --git a/sql/trigger.cc b/sql/trigger.cc index 7075fe990a13..59de7b036d24 100644 --- a/sql/trigger.cc +++ b/sql/trigger.cc @@ -377,6 +377,13 @@ bool Trigger::execute(THD *thd) { thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER); + const bool disable_binlog = !thd->variables.sql_log_bin_triggers; + if (disable_binlog) { + // option_bits restored back using statement_state in + // restore_sub_statement_state(). + thd->variables.option_bits &= ~OPTION_BIN_LOG; + } + /* Reset current_query_block before call execute_trigger() and restore it after return from one. This way error is set diff --git a/unittest/gunit/fake_table.h b/unittest/gunit/fake_table.h index 50c41c64fc0c..22893835566e 100644 --- a/unittest/gunit/fake_table.h +++ b/unittest/gunit/fake_table.h @@ -140,7 +140,7 @@ class Fake_TABLE : public TABLE { read_set = &read_set_struct; write_set = &write_set_struct; next_number_field = nullptr; // No autoinc column - pos_in_table_list = new (*THR_MALLOC) Fake_Table_ref(); + set_pos_in_table_list(new (*THR_MALLOC) Fake_Table_ref()); pos_in_table_list->table = this; pos_in_table_list->query_block = new (&mem_root) Query_block(&mem_root, nullptr, nullptr);