GIF89a;
Direktori : /usr/share/mysql-test/suite/binlog/t/ |
Current File : //usr/share/mysql-test/suite/binlog/t/binlog_unsafe_stmt_capable_engine.test |
# ==== Background ==== # # Some statements may execute differently on master and slave when # logged in statement format. Such statements are called unsafe. # Unsafe statements include: # # - statements using @@variables (with a small number of exceptions; # see below); # - statements using certain functions, e.g., UUID(); # - statements using LIMIT; # - insert into two autoinc columns; # - statements using UDF's. # - statements reading from log tables in the mysql database. # - INSERT ... SELECT ... ON DUPLICATE KEY UPDATE # - REPLACE ... SELECT # - CREATE TABLE [IGNORE/REPLACE] SELECT # - INSERT IGNORE...SELECT # - UPDATE IGNORE # - INSERT... ON DUPLICATE KEY UPDATE on a table with two UNIQUE KEYS # # Note that statements that use stored functions, stored procedures, # triggers, views, or prepared statements that invoke unsafe # statements shall also be unsafe. # # Unsafeness of a statement shall have the following consequences: # # 1. If the binlogging is on, for any unsafe statement, # - If binlog_format=STATEMENT, the statement shall give a warning. # - If binlog_format=MIXED or binlog_format=ROW, the statement shall # be logged in row format if the engine is capable of logging in row format. # # 2. If the binlogging is on, for any unsafe statement, # - If binlog_format=STATEMENT, the statement shall give a warning. # - If binlog_format=MIXED or binlog_format=ROW, the statement shall # error out if the engine is only statement capable. # # 3. If binlogging is off or the statement is not logged (e.g. SELECT # UUID()), no warning shall be issued and the statement shall not # be logged. # # Moreover, when a sub-statement of a recursive construct (i.e., # stored function, stored procedure, trigger, view, or prepared # statement) is unsafe and binlog_format=STATEMENT, then a warning # shall be issued for every recursive construct. In effect, this # creates a stack trace from the top-level statement to the unsafe # statement. # # ==== Purpose ==== # # Most of this test is copied from the test binlog_unsafe, # but here we test the logging of unsafe statements when # storage engine is only capable of logging in statement format. # # This test verifies that warning/error is generated when it should, # according to the above rules when storage engine is only statement capable. # # All @@variables should be unsafe, with some exceptions. Therefore, # this test also verifies that the exceptions do *not* generate a # warning. # # ==== Method ==== # # 1. Each type of statements listed above is executed. # # 2. Each unsafe statement is wrapped in each type of recursive # construct (stored function, stored procedure, trigger, view, or # prepared statement). # # 3. Each unsafe statement is wrapped in two levels of recursive # constructs (function invoking trigger invoking UUID(), etc). # # We try to insert the variables that should not be unsafe into a # table, and verify that *no* warning is issued. # # Execute a unsafe statement calling a trigger or stored function # or neither when SQL_LOG_BIN is turned ON, a warning/error should be issued # Execute a unsafe statement calling a trigger or stored function # or neither when @@SQL_LOG_BIN is turned OFF, # no warning/error is issued # # ==== References ==== # # https://dev.mysql.com/doc/refman/5.6/en/binary-log-mixed.html # # ==== Related bugs ==== # # Bug#28429993: IF THE STORAGE ENGINE SUPPORTS RBR, UNSAFE SQL STATEMENTES END UP IN BINLOG --source include/have_udf.inc --source include/have_debug.inc --source include/have_log_bin.inc --source include/have_binlog_format_statement.inc --source include/not_gtid_enabled.inc --disable_query_log call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT"); --enable_query_log --echo #### Setup tables #### CREATE TABLE t0 (a CHAR(100)); CREATE TABLE t1 (a CHAR(100)); CREATE TABLE t2 (a CHAR(100)); CREATE TABLE ta0 (a CHAR(100)); CREATE TABLE ta1 (a CHAR(100)); CREATE TABLE ta2 (a CHAR(100)); CREATE TABLE autoinc_table (a INT PRIMARY KEY AUTO_INCREMENT); CREATE TABLE data_table (a CHAR(100)); INSERT INTO data_table VALUES ('foo'); CREATE TABLE trigger_table_1 (a INT); CREATE TABLE trigger_table_2 (a INT); CREATE TABLE trigger_table_3 (a INT); CREATE TABLE double_autoinc_table (a INT PRIMARY KEY AUTO_INCREMENT); # Make the storage engine log only in statement format. SET @old_debug= @@global.debug; SET GLOBAL debug='+d,make_stmt_only_engines'; --DELIMITER | CREATE TRIGGER double_autoinc_trig BEFORE INSERT ON double_autoinc_table FOR EACH ROW BEGIN INSERT INTO autoinc_table VALUES (NULL); END| CREATE FUNCTION multi_unsafe_func() RETURNS INT BEGIN INSERT INTO t0 VALUES(CONCAT(@@hostname, @@hostname)); INSERT INTO t0 VALUES(0); INSERT INTO t0 VALUES(CONCAT(UUID(), @@hostname)); RETURN 1; END| --DELIMITER ; --replace_result $UDF_EXAMPLE_LIB UDF_EXAMPLE_LIB --eval CREATE FUNCTION myfunc_int RETURNS INTEGER SONAME "$UDF_EXAMPLE_LIB" # In each iteration of this loop, we select one method to make the # statement unsafe. --let $unsafe_type= 0 while ($unsafe_type < 7) { --echo if ($unsafe_type == 0) { --echo ==== Testing UUID() unsafeness ==== --let $desc_0= unsafe UUID() function --let $stmt_sidef_0= INSERT INTO t0 VALUES (UUID()) --let $value_0= UUID() --let $sel_sidef_0= --let $sel_retval_0= SELECT UUID() --let $CRC_ARG_expected_number_of_warnings= 1 --let $CRC_ARG_expected_number_of_deprecation_warnings= 0 } if ($unsafe_type == 1) { --echo ==== Testing @@hostname unsafeness ==== --let $desc_0= unsafe @@hostname variable --let $stmt_sidef_0= INSERT INTO t0 VALUES (@@hostname) --let $value_0= @@hostname --let $sel_sidef_0= # $sel_retval is going to be used in views. Views cannot execute # statements that refer to @@variables. Hence, we set $set_retval # to empty instead of SELECT @@hostname. --let $sel_retval_0= --let $CRC_ARG_expected_number_of_warnings= 1 --let $CRC_ARG_expected_number_of_deprecation_warnings= 0 } if ($unsafe_type == 2) { --echo ==== Testing SELECT...LIMIT unsafeness ==== --let $desc_0= unsafe SELECT...LIMIT statement --let $stmt_sidef_0= INSERT INTO t0 SELECT * FROM data_table LIMIT 1 --let $value_0= --let $sel_sidef_0= --let $sel_retval_0= SELECT * FROM data_table LIMIT 1 --let $CRC_ARG_expected_number_of_warnings= 1 --let $CRC_ARG_expected_number_of_deprecation_warnings= 0 } if ($unsafe_type == 3) { --echo ==== Testing unsafeness of insert of two autoinc values ==== --let $desc_0= unsafe update of two autoinc columns --let $stmt_sidef_0= INSERT INTO double_autoinc_table VALUES (NULL) --let $value_0= --let $sel_sidef_0= --let $sel_retval_0= --let $CRC_ARG_expected_number_of_warnings= 1 --let $CRC_ARG_expected_number_of_deprecation_warnings= 0 } if ($unsafe_type == 4) { --echo ==== Testing unsafeness of UDF's ==== --let $desc_0= unsafe UDF --let $stmt_sidef_0= INSERT INTO t0 VALUES (myfunc_int(10)) --let $value_0= myfunc_int(10) --let $sel_sidef_0= SELECT myfunc_int(10) --let $sel_retval_0= --let $CRC_ARG_expected_number_of_warnings= 1 --let $CRC_ARG_expected_number_of_deprecation_warnings= 0 } if ($unsafe_type == 5) { --echo ==== Testing unsafeness of access to mysql.general_log ==== --let $desc_0= unsafe use of mysql.general_log --let $stmt_sidef_0= INSERT INTO t0 SELECT COUNT(*) FROM mysql.general_log --let $value_0= --let $sel_sidef_0= --let $sel_retval_0= SELECT COUNT(*) FROM mysql.general_log --let $CRC_ARG_expected_number_of_warnings= 1 --let $CRC_ARG_expected_number_of_deprecation_warnings= 0 } if ($unsafe_type == 6) { --echo ==== Testing a statement that is unsafe several times ==== --let $desc_0= statement that is unsafe several times --let $stmt_sidef_0= INSERT INTO ta0 VALUES (multi_unsafe_func()) --let $value_0= --let $sel_sidef_0= SELECT multi_unsafe_func() --let $sel_retval_0= --let $CRC_ARG_expected_number_of_warnings= 2 --let $CRC_ARG_expected_number_of_deprecation_warnings= 0 } # In each iteration of the following loop, we select one way to # enclose the unsafe statement as a sub-statement of a recursive # construct (i.e., a function, procedure, trigger, view, or prepared # statement). # # In the last iteration, $call_type_1=7, we don't create a recursive # construct. Instead, we just invoke the unsafe statement directly. --let $call_type_1= 0 while ($call_type_1 < 8) { #--echo debug: level 1, types $call_type_1 -> $unsafe_type --let $CRC_ARG_level= 1 --let $CRC_ARG_type= $call_type_1 --let $CRC_ARG_stmt_sidef= $stmt_sidef_0 --let $CRC_ARG_value= $value_0 --let $CRC_ARG_sel_sidef= $sel_sidef_0 --let $CRC_ARG_sel_retval= $sel_retval_0 --let $CRC_ARG_desc= $desc_0 --source extra/rpl_tests/create_recursive_construct_stmt_capable_engine.inc --let $stmt_sidef_1= $CRC_RET_stmt_sidef --let $value_1= $CRC_RET_value --let $sel_sidef_1= $CRC_RET_sel_sidef --let $sel_retval_1= $CRC_RET_sel_retval --let $is_toplevel_1= $CRC_RET_is_toplevel --let $drop_1= $CRC_RET_drop --let $desc_1= $CRC_RET_desc # Some statements must be top-level statements, i.e., cannot be # called as a sub-statement of any recursive construct. (One # example is 'EXECUTE prepared_stmt'). When # create_recursive_construct_stmt_capable_engine.inc creates a top-level statement, it # sets $CRC_RET_is_toplevel=1. if (!$is_toplevel_1) { # In each iteration of this loop, we select one way to enclose # the previous recursive construct in another recursive # construct. --let $call_type_2= 0 while ($call_type_2 < 7) { #--echo debug: level 2, types $call_type_2 -> $call_type_1 -> $unsafe_type --let $CRC_ARG_level= 2 --let $CRC_ARG_type= $call_type_2 --let $CRC_ARG_stmt_sidef= $stmt_sidef_1 --let $CRC_ARG_value= $value_1 --let $CRC_ARG_sel_sidef= $sel_sidef_1 --let $CRC_ARG_sel_retval= $sel_retval_1 --let $CRC_ARG_desc= $desc_1 --source extra/rpl_tests/create_recursive_construct_stmt_capable_engine.inc --let $stmt_sidef_2= $CRC_RET_stmt_sidef --let $value_2= $CRC_RET_value --let $sel_sidef_2= $CRC_RET_sel_sidef --let $sel_retval_2= $CRC_RET_sel_retval --let $is_toplevel_2= $CRC_RET_is_toplevel --let $drop_2= $CRC_RET_drop --let $desc_2= $CRC_RET_desc # Drop created object. if ($drop_2) { --eval $drop_2 } --inc $call_type_2 } # while (call_type_2) } # if (!is_toplevel_1) # Drop created object. if ($drop_1) { --eval $drop_1 } --inc $call_type_1 } # while (call_type_1) --inc $unsafe_type } # while (unsafe_type) DROP TRIGGER double_autoinc_trig; DROP TABLE t0, t1, t2, ta0, ta1, ta2, autoinc_table, double_autoinc_table, data_table, trigger_table_1, trigger_table_2, trigger_table_3; DROP FUNCTION myfunc_int; DROP FUNCTION multi_unsafe_func; --echo ==== Special system variables that should *not* be unsafe ==== CREATE TABLE t1 (a VARCHAR(1000)); CREATE TABLE autoinc_table (a INT PRIMARY KEY AUTO_INCREMENT); INSERT INTO t1 VALUES (@@session.auto_increment_increment); INSERT INTO t1 VALUES (@@session.auto_increment_offset); INSERT INTO t1 VALUES (@@session.character_set_client); INSERT INTO t1 VALUES (@@session.character_set_connection); INSERT INTO t1 VALUES (@@session.character_set_database); INSERT INTO t1 VALUES (@@session.character_set_server); INSERT INTO t1 VALUES (@@session.collation_connection); INSERT INTO t1 VALUES (@@session.collation_database); INSERT INTO t1 VALUES (@@session.collation_server); INSERT INTO t1 VALUES (@@session.foreign_key_checks); INSERT INTO t1 VALUES (@@session.identity); INSERT INTO t1 VALUES (@@session.last_insert_id); INSERT INTO t1 VALUES (@@session.lc_time_names); INSERT INTO t1 VALUES (@@session.pseudo_thread_id); INSERT INTO t1 VALUES (@@session.sql_auto_is_null); INSERT INTO t1 VALUES (@@session.timestamp); INSERT INTO t1 VALUES (@@session.time_zone); INSERT INTO t1 VALUES (@@session.unique_checks); SET @my_var= 4711; INSERT INTO t1 VALUES (@my_var); # using insert_id implicitly should be ok. SET insert_id= 12; INSERT INTO autoinc_table VALUES (NULL); # See set_var.cc for explanation. --echo The following variables *should* give a warning, despite they are replicated. INSERT INTO t1 VALUES (@@session.sql_mode); INSERT INTO t1 VALUES (@@session.insert_id); DROP TABLE t1, autoinc_table; # Clean up SET GLOBAL debug= @old_debug; --echo "End of tests"