Bug #27395 OPTION_STATUS_NO_TRANS_UPDATE is not preserved at the end of SF()

thd->options' OPTION_STATUS_NO_TRANS_UPDATE bit was not restored at the end of SF() invocation, where
SF() modified non-ta table.
As the result of this artifact it was not possible to detect whether there were any side-effects when
top-level query ends. 
If the top level query table was not modified and the bit is lost there would be no binlogging.

Fixed with preserving the bit inside of thd->no_trans_update struct. The struct agregates two bool flags
telling whether the current query and the current transaction modified any non-ta table.
The flags stmt, all are dropped at the end of the query and the transaction.
parent 0114c0a7
......@@ -530,3 +530,35 @@ count(*)
drop table t3, t4|
drop procedure bug14210|
set @@session.max_heap_table_size=default|
drop function if exists bug23333|
drop table if exists t1,t2|
CREATE TABLE t1 (a int NOT NULL auto_increment primary key) ENGINE=MyISAM|
CREATE TABLE t2 (a int NOT NULL auto_increment, b int, PRIMARY KEY (a)) ENGINE=InnoDB|
reset master|
insert into t2 values (1,1)|
create function bug23333()
RETURNS int(11)
DETERMINISTIC
begin
insert into t1 values (null);
select count(*) from t1 into @a;
return @a;
end|
insert into t2 values (bug23333(),1)|
ERROR 23000: Duplicate entry '1' for key 1
show binlog events /* must show the insert */|
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 4 Format_desc 1 98 Server ver: 5.0.40-debug-log, Binlog ver: 4
master-bin.000001 98 Query 1 90 use `test`; insert into t2 values (1,1)
master-bin.000001 188 Xid 1 215 COMMIT /* xid=1165 */
master-bin.000001 215 Query 1 446 use `test`; CREATE DEFINER=`root`@`localhost` function bug23333()
RETURNS int(11)
DETERMINISTIC
begin
insert into t1 values (null);
select count(*) from t1 into @a;
return @a;
end
select count(*),@a from t1 /* must be 1,1 */|
count(*) @a
1 1
......@@ -553,6 +553,36 @@ drop procedure bug14210|
set @@session.max_heap_table_size=default|
#
# Bug #13270 INSERT,UPDATE,etc that calls func with side-effect does not binlog
# Bug #23333 stored function + non-transac table + transac table =
# breaks stmt-based binlog
# Bug #27395 OPTION_STATUS_NO_TRANS_UPDATE is not preserved at the end of SF()
#
--disable_warnings
drop function if exists bug23333|
drop table if exists t1,t2|
--enable_warnings
CREATE TABLE t1 (a int NOT NULL auto_increment primary key) ENGINE=MyISAM|
CREATE TABLE t2 (a int NOT NULL auto_increment, b int, PRIMARY KEY (a)) ENGINE=InnoDB|
reset master|
insert into t2 values (1,1)|
create function bug23333()
RETURNS int(11)
DETERMINISTIC
begin
insert into t1 values (null);
select count(*) from t1 into @a;
return @a;
end|
--error ER_DUP_ENTRY
insert into t2 values (bug23333(),1)|
show binlog events /* must show the insert */|
select count(*),@a from t1 /* must be 1,1 */|
#
# BUG#NNNN: New bug synopsis
#
......
......@@ -3636,8 +3636,7 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type)
{
m_transaction_on= FALSE;
/* Would be simpler if has_transactions() didn't always say "yes" */
thd->options|= OPTION_STATUS_NO_TRANS_UPDATE;
thd->no_trans_update= TRUE;
thd->no_trans_update= {TRUE, TRUE};
}
else if (!thd->transaction.on)
m_transaction_on= FALSE;
......
......@@ -830,7 +830,7 @@ int ha_rollback_trans(THD *thd, bool all)
the error log; but we don't want users to wonder why they have this
message in the error log, so we don't send it.
*/
if (is_real_trans && (thd->options & OPTION_STATUS_NO_TRANS_UPDATE) &&
if (is_real_trans && thd->no_trans_update.all &&
!thd->slave_thread)
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_WARNING_NOT_COMPLETE_ROLLBACK,
......
......@@ -162,7 +162,7 @@ static int binlog_rollback(THD *thd, bool all)
table. Such cases should be rare (updating a
non-transactional table inside a transaction...)
*/
if (unlikely(thd->options & OPTION_STATUS_NO_TRANS_UPDATE))
if (unlikely(thd->no_trans_update.all))
{
Query_log_event qev(thd, STRING_WITH_LEN("ROLLBACK"), TRUE, FALSE);
qev.error_code= 0; // see comment in MYSQL_LOG::write(THD, IO_CACHE)
......@@ -217,7 +217,7 @@ static int binlog_savepoint_rollback(THD *thd, void *sv)
non-transactional table. Otherwise, truncate the binlog cache starting
from the SAVEPOINT command.
*/
if (unlikely(thd->options & OPTION_STATUS_NO_TRANS_UPDATE))
if (unlikely(thd->no_trans_update.all))
{
Query_log_event qinfo(thd, thd->query, thd->query_length, TRUE, FALSE);
DBUG_RETURN(mysql_bin_log.write(&qinfo));
......
......@@ -2873,14 +2873,15 @@ static bool set_option_autocommit(THD *thd, set_var *var)
if ((org_options & OPTION_NOT_AUTOCOMMIT))
{
/* We changed to auto_commit mode */
thd->options&= ~(ulong) (OPTION_BEGIN | OPTION_STATUS_NO_TRANS_UPDATE);
thd->options&= ~(ulong) OPTION_BEGIN;
thd->no_trans_update.all= FALSE;
thd->server_status|= SERVER_STATUS_AUTOCOMMIT;
if (ha_commit(thd))
return 1;
}
else
{
thd->options&= ~(ulong) (OPTION_STATUS_NO_TRANS_UPDATE);
thd->no_trans_update.all= FALSE;
thd->server_status&= ~SERVER_STATUS_AUTOCOMMIT;
}
}
......
......@@ -337,13 +337,13 @@ sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr)
enum_check_fields save_count_cuted_fields= thd->count_cuted_fields;
bool save_abort_on_warning= thd->abort_on_warning;
bool save_no_trans_update= thd->no_trans_update;
bool save_no_trans_update_stmt= thd->no_trans_update.stmt;
thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL;
thd->abort_on_warning=
thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES);
thd->no_trans_update= 0;
thd->no_trans_update.stmt= FALSE;
/* Save the value in the field. Convert the value if needed. */
......@@ -351,7 +351,7 @@ sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr)
thd->count_cuted_fields= save_count_cuted_fields;
thd->abort_on_warning= save_abort_on_warning;
thd->no_trans_update= save_no_trans_update;
thd->no_trans_update.stmt= save_no_trans_update_stmt;
if (thd->net.report_error)
{
......
......@@ -1440,7 +1440,11 @@ class THD :public Statement,
bool charset_is_system_charset, charset_is_collation_connection;
bool charset_is_character_set_filesystem;
bool enable_slow_log; /* enable slow log for current statement */
bool no_trans_update, abort_on_warning;
struct {
bool all:1;
bool stmt:1;
} no_trans_update;
bool abort_on_warning;
bool got_warning; /* Set on call to push_warning() */
bool no_warnings_for_error; /* no warnings on call to my_error() */
/* set during loop of derived table processing */
......@@ -1664,7 +1668,7 @@ class THD :public Statement,
inline bool really_abort_on_warning()
{
return (abort_on_warning &&
(!no_trans_update ||
(!no_trans_update.stmt ||
(variables.sql_mode & MODE_STRICT_ALL_TABLES)));
}
void set_status_var_init();
......
......@@ -311,7 +311,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
error=1;
}
if (!transactional_table)
thd->options|=OPTION_STATUS_NO_TRANS_UPDATE;
thd->no_trans_update.all= TRUE;
}
free_underlaid_joins(thd, select_lex);
if (transactional_table)
......@@ -791,7 +791,7 @@ bool multi_delete::send_eof()
local_error=1; // Log write failed: roll back the SQL statement
}
if (!transactional_tables)
thd->options|=OPTION_STATUS_NO_TRANS_UPDATE;
thd->no_trans_update.all= TRUE;
}
/* Commit or rollback the current SQL statement */
if (transactional_tables)
......
......@@ -584,7 +584,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
if (lock_type != TL_WRITE_DELAYED && !thd->prelocked_mode)
table->file->start_bulk_insert(values_list.elements);
thd->no_trans_update= 0;
thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= (!ignore && (thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
MODE_STRICT_ALL_TABLES)));
......@@ -731,7 +731,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
error=1;
}
if (!transactional_table)
thd->options|=OPTION_STATUS_NO_TRANS_UPDATE;
thd->no_trans_update.all= TRUE;
}
}
if (transactional_table)
......@@ -1129,7 +1129,7 @@ static int last_uniq_key(TABLE *table,uint keynr)
then both on update triggers will work instead. Similarly both on
delete triggers will be invoked if we will delete conflicting records.
Sets thd->no_trans_update if table which is updated didn't have
Sets thd->no_trans_update.stmt to TRUE if table which is updated didn't have
transactions.
RETURN VALUE
......@@ -1295,7 +1295,7 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
goto err;
info->deleted++;
if (!table->file->has_transactions())
thd->no_trans_update= 1;
thd->no_trans_update.stmt= TRUE;
if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_AFTER, TRUE))
......@@ -1327,7 +1327,7 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
if (key)
my_safe_afree(key,table->s->max_unique_length,MAX_KEY_LENGTH);
if (!table->file->has_transactions())
thd->no_trans_update= 1;
thd->no_trans_update.stmt= TRUE;
DBUG_RETURN(trg_error);
err:
......@@ -2518,7 +2518,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE);
table->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS);
}
thd->no_trans_update= 0;
thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= (!info.ignore &&
(thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
......@@ -2676,7 +2676,7 @@ void select_insert::send_error(uint errcode,const char *err)
mysql_bin_log.write(&qinfo);
}
if (!table->s->tmp_table)
thd->options|=OPTION_STATUS_NO_TRANS_UPDATE;
thd->no_trans_update.all= TRUE;
}
if (info.copied || info.deleted || info.updated)
{
......@@ -2705,7 +2705,7 @@ bool select_insert::send_eof()
{
query_cache_invalidate3(thd, table, 1);
if (!(table->file->has_transactions() || table->s->tmp_table))
thd->options|=OPTION_STATUS_NO_TRANS_UPDATE;
thd->no_trans_update.all= TRUE;
}
if (last_insert_id)
......@@ -2931,7 +2931,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
}
if (!thd->prelocked_mode)
table->file->start_bulk_insert((ha_rows) 0);
thd->no_trans_update= 0;
thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= (!info.ignore &&
(thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
......
......@@ -377,7 +377,7 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
table->file->start_bulk_insert((ha_rows) 0);
table->copy_blobs=1;
thd->no_trans_update= 0;
thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= (!ignore &&
(thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
......@@ -467,7 +467,7 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
(ulong) (info.records - info.copied), (ulong) thd->cuted_fields);
if (!transactional_table)
thd->options|=OPTION_STATUS_NO_TRANS_UPDATE;
thd->no_trans_update.all= TRUE;
#ifndef EMBEDDED_LIBRARY
if (mysql_bin_log.is_open())
{
......@@ -531,7 +531,7 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
Item_field *sql_field;
TABLE *table= table_list->table;
ulonglong id;
bool no_trans_update;
bool no_trans_update_stmt;
DBUG_ENTER("read_fixed_length");
id= 0;
......@@ -559,7 +559,7 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
#ifdef HAVE_purify
read_info.row_end[0]=0;
#endif
no_trans_update= !table->file->has_transactions();
no_trans_update_stmt= !table->file->has_transactions();
restore_record(table, s->default_values);
/*
......@@ -627,7 +627,7 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
if (write_record(thd, table, &info))
DBUG_RETURN(1);
thd->no_trans_update= no_trans_update;
thd->no_trans_update.stmt= no_trans_update_stmt;
/*
If auto_increment values are used, save the first one for
......@@ -670,12 +670,12 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
TABLE *table= table_list->table;
uint enclosed_length;
ulonglong id;
bool no_trans_update;
bool no_trans_update_stmt;
DBUG_ENTER("read_sep_field");
enclosed_length=enclosed.length();
id= 0;
no_trans_update= !table->file->has_transactions();
no_trans_update_stmt= !table->file->has_transactions();
for (;;it.rewind())
{
......@@ -817,7 +817,7 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
We don't need to reset auto-increment field since we are restoring
its default value at the beginning of each loop iteration.
*/
thd->no_trans_update= no_trans_update;
thd->no_trans_update.stmt= no_trans_update_stmt;
if (read_info.next_line()) // Skip to next line
break;
if (read_info.line_cuted)
......
......@@ -147,7 +147,8 @@ static bool end_active_trans(THD *thd)
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
if (ha_commit(thd))
error=1;
thd->options&= ~(ulong) (OPTION_BEGIN | OPTION_STATUS_NO_TRANS_UPDATE);
thd->options&= ~(ulong) OPTION_BEGIN;
thd->no_trans_update.all= FALSE;
}
DBUG_RETURN(error);
}
......@@ -171,8 +172,8 @@ static bool begin_trans(THD *thd)
else
{
LEX *lex= thd->lex;
thd->options= ((thd->options & (ulong) ~(OPTION_STATUS_NO_TRANS_UPDATE)) |
OPTION_BEGIN);
thd->no_trans_update.all= FALSE;
thd->options|= (ulong) OPTION_BEGIN;
thd->server_status|= SERVER_STATUS_IN_TRANS;
if (lex->start_transaction_opt & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT)
error= ha_start_consistent_snapshot(thd);
......@@ -1463,7 +1464,8 @@ int end_trans(THD *thd, enum enum_mysql_completiontype completion)
*/
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
res= ha_commit(thd);
thd->options&= ~(ulong) (OPTION_BEGIN | OPTION_STATUS_NO_TRANS_UPDATE);
thd->options&= ~(ulong) OPTION_BEGIN;
thd->no_trans_update.all= FALSE;
break;
case COMMIT_RELEASE:
do_release= 1; /* fall through */
......@@ -1480,7 +1482,8 @@ int end_trans(THD *thd, enum enum_mysql_completiontype completion)
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
if (ha_rollback(thd))
res= -1;
thd->options&= ~(ulong) (OPTION_BEGIN | OPTION_STATUS_NO_TRANS_UPDATE);
thd->options&= ~(ulong) OPTION_BEGIN;
thd->no_trans_update.all= FALSE;
if (!res && (completion == ROLLBACK_AND_CHAIN))
res= begin_trans(thd);
break;
......@@ -2933,7 +2936,7 @@ mysql_execute_command(THD *thd)
else
{
/* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */
thd->options|= OPTION_STATUS_NO_TRANS_UPDATE;
thd->no_trans_update.all= TRUE;
}
DBUG_ASSERT(first_table == all_tables && first_table != 0);
bool link_to_local;
......@@ -3710,7 +3713,7 @@ mysql_execute_command(THD *thd)
lex->drop_if_exists= 1;
/* So that DROP TEMPORARY TABLE gets to binlog at commit/rollback */
thd->options|= OPTION_STATUS_NO_TRANS_UPDATE;
thd->no_trans_update.all= TRUE;
}
/* DDL and binlog write order protected by LOCK_open */
res= mysql_rm_table(thd, first_table, lex->drop_if_exists,
......@@ -4296,7 +4299,7 @@ mysql_execute_command(THD *thd)
res= TRUE; // cannot happen
else
{
if ((thd->options & OPTION_STATUS_NO_TRANS_UPDATE) &&
if (thd->no_trans_update.all &&
!thd->slave_thread)
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_WARNING_NOT_COMPLETE_ROLLBACK,
......@@ -4939,8 +4942,8 @@ mysql_execute_command(THD *thd)
thd->transaction.xid_state.xa_state=XA_ACTIVE;
thd->transaction.xid_state.xid.set(thd->lex->xid);
xid_cache_insert(&thd->transaction.xid_state);
thd->options= ((thd->options & (ulong) ~(OPTION_STATUS_NO_TRANS_UPDATE)) |
OPTION_BEGIN);
thd->no_trans_update.all= FALSE;
thd->options|= (ulong) OPTION_BEGIN;
thd->server_status|= SERVER_STATUS_IN_TRANS;
send_ok(thd);
break;
......@@ -5033,7 +5036,8 @@ mysql_execute_command(THD *thd)
xa_state_names[thd->transaction.xid_state.xa_state]);
break;
}
thd->options&= ~(ulong) (OPTION_BEGIN | OPTION_STATUS_NO_TRANS_UPDATE);
thd->options&= ~(ulong) OPTION_BEGIN;
thd->no_trans_update.all= FALSE;
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
xid_cache_delete(&thd->transaction.xid_state);
thd->transaction.xid_state.xa_state=XA_NOTR;
......@@ -5063,7 +5067,8 @@ mysql_execute_command(THD *thd)
my_error(ER_XAER_RMERR, MYF(0));
else
send_ok(thd);
thd->options&= ~(ulong) (OPTION_BEGIN | OPTION_STATUS_NO_TRANS_UPDATE);
thd->options&= ~(ulong) OPTION_BEGIN;
thd->no_trans_update.all= FALSE;
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
xid_cache_delete(&thd->transaction.xid_state);
thd->transaction.xid_state.xa_state=XA_NOTR;
......
......@@ -3968,7 +3968,7 @@ copy_data_between_tables(TABLE *from,TABLE *to,
alter_table_manage_keys(to, from->file->indexes_are_disabled(), keys_onoff);
/* We can abort alter table for any table type */
thd->no_trans_update= 0;
thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= !ignore && test(thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
MODE_STRICT_ALL_TABLES));
......
......@@ -429,7 +429,7 @@ int mysql_update(THD *thd,
query_id=thd->query_id;
transactional_table= table->file->has_transactions();
thd->no_trans_update= 0;
thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= test(!ignore &&
(thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
......@@ -474,7 +474,7 @@ int mysql_update(THD *thd,
(byte*) table->record[0])))
{
updated++;
thd->no_trans_update= !transactional_table;
thd->no_trans_update.stmt= !transactional_table;
if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
......@@ -546,7 +546,7 @@ int mysql_update(THD *thd,
error=1; // Rollback update
}
if (!transactional_table)
thd->options|=OPTION_STATUS_NO_TRANS_UPDATE;
thd->no_trans_update.all= TRUE;
}
free_underlaid_joins(thd, select_lex);
if (transactional_table)
......@@ -910,7 +910,7 @@ bool mysql_multi_update(THD *thd,
handle_duplicates, ignore)))
DBUG_RETURN(TRUE);
thd->no_trans_update= 0;
thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= test(thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
MODE_STRICT_ALL_TABLES));
......@@ -1224,7 +1224,7 @@ multi_update::~multi_update()
delete [] copy_field;
thd->count_cuted_fields= CHECK_FIELD_IGNORE; // Restore this setting
if (!trans_safe)
thd->options|=OPTION_STATUS_NO_TRANS_UPDATE;
thd->no_trans_update.all= TRUE;
}
......@@ -1311,7 +1311,7 @@ bool multi_update::send_data(List<Item> &not_used_values)
else
{
if (!table->file->has_transactions())
thd->no_trans_update= 1;
thd->no_trans_update.stmt= TRUE;
if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
TRG_ACTION_AFTER, TRUE))
......@@ -1544,7 +1544,7 @@ bool multi_update::send_eof()
local_error= 1; // Rollback update
}
if (!transactional_tables)
thd->options|=OPTION_STATUS_NO_TRANS_UPDATE;
thd->no_trans_update.all= TRUE;
}
if (transactional_tables)
......
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