Commit 88b32056 authored by Alfranio Correia's avatar Alfranio Correia

BUG#50312 Warnings for unsafe sub-statement not returned to client

                        
After BUG#36649, warnings for sub-statements are cleared when a 
new sub-statement is started. This is problematic since it suppresses
warnings for unsafe statements in some cases. It is important that we
always give a warning to the client, because the user needs to know
when there is a risk that the slave goes out of sync.
                        
We fixed the problem by generating warning messages for unsafe statements
while returning from a stored procedure, function, trigger or while
executing a top level statement.
                        
We also started checking unsafeness when both performance and log tables are
used. This is necessary after the performance schema which does a distinction
between performance and log tables.

mysql-test/extra/rpl_tests/create_recursive_construct.inc:
  Changed the order of the calls in the procedure because the code
  that checks if a warning message is printed out expects that the
  first statement gives an warning what is not the case for INSERT
  INTO ta$CRC_ARG_level VALUES (47);
mysql-test/suite/binlog/r/binlog_stm_unsafe_warning.result:
  Updated the result file.
mysql-test/suite/binlog/r/binlog_unsafe.result:
  There are several changes here:
          
  (1) - Changed the CREATE PROCEDURE $CRC.
                                  
  (2) - The procedure $CRC was failing and the content of the binlog
        was being printed out, after fix (1) the failure disappeared.
                                  
  (3) - The warning message for unsafeness due to auto-increment collumns was
        changed.
                                  
  (4) - The warning message for unsafeness due to VERSION(), RAND() was changed.
mysql-test/suite/binlog/t/binlog_stm_unsafe_warning.test:
  Tested filters.
mysql-test/suite/binlog/t/binlog_unsafe.test:
  Reenabled the test case binlog_unsafe.
mysql-test/suite/binlog/t/disabled.def:
  Reenabled the test case binlog_unsafe.
mysql-test/suite/rpl/r/rpl_begin_commit_rollback.result:
  Updated the result file.
mysql-test/suite/rpl/r/rpl_non_direct_stm_mixing_engines.result:
  Updated the result file.
mysql-test/suite/rpl/r/rpl_stm_auto_increment_bug33029.result:
  Updated the result file.
sql/sql_class.cc:
  Moved the stmt_accessed_table_flag variable and related information to the
  LEX as we need the variable reset after each statement even inside a stored
  procedure, what did not happen if the information was in the THD.
          
  Changed the routine in the THD::binlog_query that prints the warning
  messages to avoid trying to print them when inside a stored procedure,
  function or trigger.
                          
  Checked for unsafeness when both performance and log tables where used.
  After the introduction of the performance schema, we need to check both.
parent 8685b842
......@@ -200,7 +200,7 @@ if (`SELECT $CRC_ARG_type = 1`) {
if (`SELECT $CRC_ARG_type = 2`) {
# It will be unsafe to call this procedure.
--let $CRC_name= proc_$CRC_ARG_level
--let $CRC_create= CREATE PROCEDURE $CRC_name() BEGIN INSERT INTO ta$CRC_ARG_level VALUES (47); $CRC_ARG_stmt_sidef; END
--let $CRC_create= CREATE PROCEDURE $CRC_name() BEGIN $CRC_ARG_stmt_sidef; INSERT INTO ta$CRC_ARG_level VALUES (47); END
--let $CRC_RET_stmt_sidef= CALL $CRC_name()
--let $CRC_RET_value=
--let $CRC_RET_sel_retval=
......@@ -344,25 +344,8 @@ if (`SELECT '$CRC_RET_stmt_sidef' != ''`) {
if (`SELECT '$event_type' != 'Table_map'`) {
--enable_query_log
--echo ******** Failure! Event number 3 was a '$event_type', not a 'Table_map'. ********
# Currently, there is a bug causing some statements to be logged
# partially in statement format. Hence, we don't fail here, we
# just print the events (masking out nondeterministic components
# of the output) and continue. When binloggging works perfectly,
# we should instead execute:
#--enable_query_log
#SHOW BINLOG EVENTS;
#--die Wrong events in binlog.
# Here, we should really source
# include/show_binlog_events.inc. But due to BUG#41913, that
# doesn't work, and we have to inline the entire file here. Sigh
# :-(
--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR 107 <binlog_start>
--replace_column 2 # 4 # 5 #
--replace_regex /\/\* xid=.* \*\//\/* XID *\// /table_id: [0-9]+/table_id: #/ /file_id=[0-9]+/file_id=#/ /block_len=[0-9]+/block_len=#/
--eval SHOW BINLOG EVENTS FROM 107
--disable_query_log
SHOW BINLOG EVENTS;
--die Wrong events in binlog.
}
SET binlog_format = STATEMENT;
......
......@@ -28,6 +28,25 @@ INSERT INTO t1 VALUES (1,2), (2,3);
UPDATE t1 SET b='4' WHERE a=1 LIMIT 1;
UPDATE t1 SET b='5' WHERE a=2 ORDER BY a LIMIT 1;
DROP TABLE t1;
CREATE TABLE t1 (a VARCHAR(1000));
INSERT INTO t1 VALUES (CURRENT_USER());
INSERT INTO t1 VALUES (FOUND_ROWS());
INSERT INTO t1 VALUES (GET_LOCK('tmp', 1));
INSERT INTO t1 VALUES (IS_FREE_LOCK('tmp'));
INSERT INTO t1 VALUES (IS_USED_LOCK('tmp'));
INSERT INTO t1 VALUES (LOAD_FILE('../../std_data/words2.dat'));
INSERT INTO t1 VALUES (MASTER_POS_WAIT('dummy arg', 4711, 1));
INSERT INTO t1 VALUES (RELEASE_LOCK('tmp'));
INSERT INTO t1 VALUES (ROW_COUNT());
INSERT INTO t1 VALUES (SESSION_USER());
INSERT INTO t1 VALUES (SLEEP(1));
INSERT INTO t1 VALUES (SYSDATE());
INSERT INTO t1 VALUES (SYSTEM_USER());
INSERT INTO t1 VALUES (USER());
INSERT INTO t1 VALUES (UUID());
INSERT INTO t1 VALUES (UUID_SHORT());
INSERT INTO t1 VALUES (VERSION());
INSERT INTO t1 VALUES (RAND());
DROP DATABASE b42851;
USE test;
#
......@@ -67,8 +86,11 @@ Note 1592 Unsafe statement written to the binary log using statement format sinc
SELECT sf_bug50192();
sf_bug50192()
1
Warnings:
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statement is unsafe because it invokes a trigger or a stored function that inserts into an AUTO_INCREMENT column. Inserted values cannot be logged correctly.
SHOW WARNINGS;
Level Code Message
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statement is unsafe because it invokes a trigger or a stored function that inserts into an AUTO_INCREMENT column. Inserted values cannot be logged correctly.
DROP FUNCTION sf_bug50192;
DROP TRIGGER tr_bug50192;
DROP TABLE t1, t2;
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -72,6 +72,26 @@ UPDATE t1 SET b='4' WHERE a=1 LIMIT 1;
UPDATE t1 SET b='5' WHERE a=2 ORDER BY a LIMIT 1;
DROP TABLE t1;
CREATE TABLE t1 (a VARCHAR(1000));
INSERT INTO t1 VALUES (CURRENT_USER()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (FOUND_ROWS()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (GET_LOCK('tmp', 1)); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (IS_FREE_LOCK('tmp')); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (IS_USED_LOCK('tmp')); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (LOAD_FILE('../../std_data/words2.dat')); #marked unsafe in BUG#39701
INSERT INTO t1 VALUES (MASTER_POS_WAIT('dummy arg', 4711, 1));
INSERT INTO t1 VALUES (RELEASE_LOCK('tmp')); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (ROW_COUNT()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (SESSION_USER()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (SLEEP(1)); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (SYSDATE()); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (SYSTEM_USER()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (USER()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (UUID()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (UUID_SHORT()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (VERSION()); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (RAND()); #marked unsafe in BUG#49222
# clean up
DROP DATABASE b42851;
......
......@@ -468,6 +468,7 @@ BEGIN
END|
DELIMITER ;|
--echo "One unsafe warning should be issued in the following statement"
--error ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN
SELECT fun_check_log_bin();
--echo "SQL_LOG_BIN should be ON still"
SHOW VARIABLES LIKE "SQL_LOG_BIN";
......
......@@ -11,5 +11,4 @@
##############################################################################
binlog_truncate_innodb : BUG#42643 2009-02-06 mats Changes to InnoDB requires to complete fix for BUG#36763
binlog_unsafe : BUG#50312 2010-01-13 lsoares Warnings for unsafe sub-statement not returned to client
binlog_spurious_ddl_errors : BUG#54195 2010-06-03 alik binlog_spurious_ddl_errors.test fails, thus disabled
......@@ -72,6 +72,8 @@ before call db1.p1()
INSERT INTO db1.t2 VALUES ('before call db1.p2()');
BEGIN;
CALL db1.p2();
Warnings:
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statement is unsafe because it accesses a non-transactional table after accessing a transactional table within the same transaction.
ROLLBACK;
INSERT INTO db1.t2 VALUES ('after call db1.p2()');
SELECT * FROM db1.t1;
......
......@@ -39,6 +39,8 @@ INSERT INTO t2 VALUES (NULL);
RETURN i;
END//
CALL p1();
Warnings:
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statement is unsafe because it invokes a trigger or a stored function that inserts into an AUTO_INCREMENT column. Inserted values cannot be logged correctly.
show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query # # BEGIN
......@@ -123,6 +125,8 @@ SELECT * FROM t2;
id
DROP TRIGGER tr1;
CALL p2();
Warnings:
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statement is unsafe because it invokes a trigger or a stored function that inserts into an AUTO_INCREMENT column. Inserted values cannot be logged correctly.
show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query # # BEGIN
......
......@@ -4704,7 +4704,7 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info)
file= cache_mngr->get_binlog_cache_log(is_trans_cache);
cache_data= cache_mngr->get_binlog_cache_data(is_trans_cache);
if (thd->stmt_accessed_non_trans_temp_table())
if (thd->lex->stmt_accessed_non_trans_temp_table())
cache_data->set_changes_to_non_trans_temp_table();
thd->binlog_start_trans_and_stmt();
......
......@@ -679,7 +679,7 @@ Log_event::Log_event(THD* thd_arg, uint16 flags_arg, bool using_trans)
server_id= thd->server_id;
when= thd->start_time;
cache_type= ((using_trans || stmt_has_updated_trans_table(thd) ||
(thd->stmt_accessed_temp_table() &&
(thd->lex->stmt_accessed_temp_table() &&
trans_has_updated_trans_table(thd)))
? Log_event::EVENT_TRANSACTIONAL_CACHE :
Log_event::EVENT_STMT_CACHE);
......@@ -2573,7 +2573,7 @@ Query_log_event::Query_log_event(THD* thd_arg, const char* query_arg,
else
{
cache_type= ((using_trans || stmt_has_updated_trans_table(thd) || trx_cache ||
(thd->stmt_accessed_temp_table() &&
(thd->lex->stmt_accessed_temp_table() &&
trans_has_updated_trans_table(thd)))
? Log_event::EVENT_TRANSACTIONAL_CACHE :
Log_event::EVENT_STMT_CACHE);
......
......@@ -1950,6 +1950,14 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount,
free_root(&call_mem_root, MYF(0));
thd->spcont= octx;
/*
If not insided a procedure and a function printing warning
messsages.
*/
if (need_binlog_call &&
thd->spcont == NULL && !thd->binlog_evt_union.do_union)
thd->issue_unsafe_warnings();
DBUG_RETURN(err_status);
}
......@@ -2195,6 +2203,17 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
thd->spcont= save_spcont;
thd->utime_after_lock= utime_before_sp_exec;
/*
If not insided a procedure and a function printing warning
messsages.
*/
bool need_binlog_call= mysql_bin_log.is_open() &&
(thd->variables.option_bits & OPTION_BIN_LOG) &&
!thd->is_current_stmt_binlog_format_row();
if (need_binlog_call && thd->spcont == NULL &&
!thd->binlog_evt_union.do_union)
thd->issue_unsafe_warnings();
DBUG_RETURN(err_status);
}
......
This diff is collapsed.
......@@ -1566,125 +1566,6 @@ class THD :public Statement,
return current_stmt_binlog_format == BINLOG_FORMAT_ROW;
}
enum enum_stmt_accessed_table
{
/*
If a transactional table is about to be read. Note that
a write implies a read.
*/
STMT_READS_TRANS_TABLE= 0,
/*
If a transactional table is about to be updated.
*/
STMT_WRITES_TRANS_TABLE,
/*
If a non-transactional table is about to be read. Note that
a write implies a read.
*/
STMT_READS_NON_TRANS_TABLE,
/*
If a non-transactional table is about to be updated.
*/
STMT_WRITES_NON_TRANS_TABLE,
/*
If a temporary transactional table is about to be read. Note
that a write implies a read.
*/
STMT_READS_TEMP_TRANS_TABLE,
/*
If a temporary transactional table is about to be updated.
*/
STMT_WRITES_TEMP_TRANS_TABLE,
/*
If a temporary non-transactional table is about to be read. Note
that a write implies a read.
*/
STMT_READS_TEMP_NON_TRANS_TABLE,
/*
If a temporary non-transactional table is about to be updated.
*/
STMT_WRITES_TEMP_NON_TRANS_TABLE,
/*
The last element of the enumeration. Please, if necessary add
anything before this.
*/
STMT_ACCESS_TABLE_COUNT
};
/**
Sets the type of table that is about to be accessed while executing a
statement.
@param accessed_table Enumeration type that defines the type of table,
e.g. temporary, transactional, non-transactional.
*/
inline void set_stmt_accessed_table(enum_stmt_accessed_table accessed_table)
{
DBUG_ENTER("THD::set_stmt_accessed_table");
DBUG_ASSERT(accessed_table >= 0 && accessed_table < STMT_ACCESS_TABLE_COUNT);
stmt_accessed_table_flag |= (1U << accessed_table);
DBUG_VOID_RETURN;
}
/**
Checks if a type of table is about to be accessed while executing a
statement.
@param accessed_table Enumeration type that defines the type of table,
e.g. temporary, transactional, non-transactional.
@return
@retval TRUE if the type of the table is about to be accessed
@retval FALSE otherwise
*/
inline bool stmt_accessed_table(enum_stmt_accessed_table accessed_table)
{
DBUG_ENTER("THD::stmt_accessed_table");
DBUG_ASSERT(accessed_table >= 0 && accessed_table < STMT_ACCESS_TABLE_COUNT);
DBUG_RETURN((stmt_accessed_table_flag & (1U << accessed_table)) != 0);
}
/**
Checks if a temporary table is about to be accessed while executing a
statement.
@return
@retval TRUE if a temporary table is about to be accessed
@retval FALSE otherwise
*/
inline bool stmt_accessed_temp_table()
{
DBUG_ENTER("THD::stmt_accessed_temp_table");
DBUG_RETURN((stmt_accessed_table_flag &
((1U << STMT_READS_TEMP_TRANS_TABLE) |
(1U << STMT_WRITES_TEMP_TRANS_TABLE) |
(1U << STMT_READS_TEMP_NON_TRANS_TABLE) |
(1U << STMT_WRITES_TEMP_NON_TRANS_TABLE))) != 0);
}
/**
Checks if a temporary non-transactional table is about to be accessed
while executing a statement.
@return
@retval TRUE if a temporary non-transactional table is about to be
accessed
@retval FALSE otherwise
*/
inline bool stmt_accessed_non_trans_temp_table()
{
DBUG_ENTER("THD::stmt_accessed_non_trans_temp_table");
DBUG_RETURN((stmt_accessed_table_flag &
((1U << STMT_READS_TEMP_NON_TRANS_TABLE) |
(1U << STMT_WRITES_TEMP_NON_TRANS_TABLE))) != 0);
}
private:
/**
Indicates the format in which the current statement will be
......@@ -1695,24 +1576,8 @@ class THD :public Statement,
/**
Bit field for the state of binlog warnings.
There are two groups of bits:
- The first Lex::BINLOG_STMT_UNSAFE_COUNT bits list all types of
unsafeness that the current statement has.
- The following Lex::BINLOG_STMT_UNSAFE_COUNT bits list all types
of unsafeness that the current statement has issued warnings
for.
Hence, this variable must be big enough to hold
2*Lex::BINLOG_STMT_UNSAFE_COUNT bits. This is asserted in @c
issue_unsafe_warnings().
The first and second groups of bits are set by @c
decide_logging_format() when it detects that a warning should be
issued. The third group of bits is set from @c binlog_query()
when a warning is issued. All bits are cleared at the end of the
top-level statement.
The first Lex::BINLOG_STMT_UNSAFE_COUNT bits list all types of
unsafeness that the current statement has.
This must be a member of THD and not of LEX, because warnings are
detected and issued in different places (@c
......@@ -1722,20 +1587,14 @@ class THD :public Statement,
*/
uint32 binlog_unsafe_warning_flags;
/**
Bit field that determines the type of tables that are about to be
be accessed while executing a statement.
*/
uint32 stmt_accessed_table_flag;
void issue_unsafe_warnings();
/*
Number of outstanding table maps, i.e., table maps in the
transaction cache.
*/
uint binlog_table_maps;
public:
void issue_unsafe_warnings();
uint get_binlog_table_maps() const {
return binlog_table_maps;
}
......
......@@ -2300,6 +2300,7 @@ void Query_tables_list::reset_query_tables_list(bool init)
sroutines_list_own_last= sroutines_list.next;
sroutines_list_own_elements= 0;
binlog_stmt_flags= 0;
stmt_accessed_table_flag= 0;
}
......
......@@ -1276,6 +1276,125 @@ class Query_tables_list
DBUG_VOID_RETURN;
}
enum enum_stmt_accessed_table
{
/*
If a transactional table is about to be read. Note that
a write implies a read.
*/
STMT_READS_TRANS_TABLE= 0,
/*
If a transactional table is about to be updated.
*/
STMT_WRITES_TRANS_TABLE,
/*
If a non-transactional table is about to be read. Note that
a write implies a read.
*/
STMT_READS_NON_TRANS_TABLE,
/*
If a non-transactional table is about to be updated.
*/
STMT_WRITES_NON_TRANS_TABLE,
/*
If a temporary transactional table is about to be read. Note
that a write implies a read.
*/
STMT_READS_TEMP_TRANS_TABLE,
/*
If a temporary transactional table is about to be updated.
*/
STMT_WRITES_TEMP_TRANS_TABLE,
/*
If a temporary non-transactional table is about to be read. Note
that a write implies a read.
*/
STMT_READS_TEMP_NON_TRANS_TABLE,
/*
If a temporary non-transactional table is about to be updated.
*/
STMT_WRITES_TEMP_NON_TRANS_TABLE,
/*
The last element of the enumeration. Please, if necessary add
anything before this.
*/
STMT_ACCESS_TABLE_COUNT
};
/**
Sets the type of table that is about to be accessed while executing a
statement.
@param accessed_table Enumeration type that defines the type of table,
e.g. temporary, transactional, non-transactional.
*/
inline void set_stmt_accessed_table(enum_stmt_accessed_table accessed_table)
{
DBUG_ENTER("THD::set_stmt_accessed_table");
DBUG_ASSERT(accessed_table >= 0 && accessed_table < STMT_ACCESS_TABLE_COUNT);
stmt_accessed_table_flag |= (1U << accessed_table);
DBUG_VOID_RETURN;
}
/**
Checks if a type of table is about to be accessed while executing a
statement.
@param accessed_table Enumeration type that defines the type of table,
e.g. temporary, transactional, non-transactional.
@return
@retval TRUE if the type of the table is about to be accessed
@retval FALSE otherwise
*/
inline bool stmt_accessed_table(enum_stmt_accessed_table accessed_table)
{
DBUG_ENTER("THD::stmt_accessed_table");
DBUG_ASSERT(accessed_table >= 0 && accessed_table < STMT_ACCESS_TABLE_COUNT);
DBUG_RETURN((stmt_accessed_table_flag & (1U << accessed_table)) != 0);
}
/**
Checks if a temporary table is about to be accessed while executing a
statement.
@return
@retval TRUE if a temporary table is about to be accessed
@retval FALSE otherwise
*/
inline bool stmt_accessed_temp_table()
{
DBUG_ENTER("THD::stmt_accessed_temp_table");
DBUG_RETURN((stmt_accessed_table_flag &
((1U << STMT_READS_TEMP_TRANS_TABLE) |
(1U << STMT_WRITES_TEMP_TRANS_TABLE) |
(1U << STMT_READS_TEMP_NON_TRANS_TABLE) |
(1U << STMT_WRITES_TEMP_NON_TRANS_TABLE))) != 0);
}
/**
Checks if a temporary non-transactional table is about to be accessed
while executing a statement.
@return
@retval TRUE if a temporary non-transactional table is about to be
accessed
@retval FALSE otherwise
*/
inline bool stmt_accessed_non_trans_temp_table()
{
DBUG_ENTER("THD::stmt_accessed_non_trans_temp_table");
DBUG_RETURN((stmt_accessed_table_flag &
((1U << STMT_READS_TEMP_NON_TRANS_TABLE) |
(1U << STMT_WRITES_TEMP_NON_TRANS_TABLE))) != 0);
}
/**
true if the parsed tree contains references to stored procedures
or functions, false otherwise
......@@ -1317,6 +1436,12 @@ class Query_tables_list
stored procedure has its own LEX object (but no own THD object).
*/
uint32 binlog_stmt_flags;
/**
Bit field that determines the type of tables that are about to be
be accessed while executing a statement.
*/
uint32 stmt_accessed_table_flag;
};
......
......@@ -5640,7 +5640,6 @@ void THD::reset_for_next_command()
thd->reset_current_stmt_binlog_format_row();
thd->binlog_unsafe_warning_flags= 0;
thd->stmt_accessed_table_flag= 0;
DBUG_PRINT("debug",
("is_current_stmt_binlog_format_row(): %d",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment