diff options
-rw-r--r-- | mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test | 118 | ||||
-rw-r--r-- | mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result | 113 | ||||
-rw-r--r-- | sql/ha_ndbcluster.cc | 2 | ||||
-rw-r--r-- | sql/handler.cc | 2 | ||||
-rw-r--r-- | sql/handler.h | 29 | ||||
-rw-r--r-- | sql/log.cc | 7 | ||||
-rw-r--r-- | sql/set_var.cc | 4 | ||||
-rw-r--r-- | sql/sp_head.cc | 19 | ||||
-rw-r--r-- | sql/sql_class.cc | 2 | ||||
-rw-r--r-- | sql/sql_class.h | 6 | ||||
-rw-r--r-- | sql/sql_delete.cc | 31 | ||||
-rw-r--r-- | sql/sql_insert.cc | 34 | ||||
-rw-r--r-- | sql/sql_load.cc | 16 | ||||
-rw-r--r-- | sql/sql_parse.cc | 17 | ||||
-rw-r--r-- | sql/sql_table.cc | 1 | ||||
-rw-r--r-- | sql/sql_update.cc | 41 |
16 files changed, 357 insertions, 85 deletions
diff --git a/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test b/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test index 7141bd1abb9..831f13893a1 100644 --- a/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test +++ b/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test @@ -316,3 +316,121 @@ disconnect con3; connection con4; select get_lock("a",10); # wait for rollback to finish +# +# Bug #27417 thd->no_trans_update.stmt lost value inside of SF-exec-stack +# bug #28960 non-trans temp table changes with insert .. select +# not binlogged after rollback +# +# testing appearence of insert into temp_table in binlog. +# There are two branches of execution that require different setup. + +## send_eof() branch + +# prepare + +create temporary table tt (a int unique); +create table ti (a int) engine=innodb; +reset master; +show master status; + +# action + +begin; +insert into ti values (1); +insert into ti values (2) ; +insert into tt select * from ti; +rollback; + +# check + +select count(*) from tt /* 2 */; +show master status; +--replace_column 2 # 5 # +show binlog events from 98; +select count(*) from ti /* zero */; +insert into ti select * from tt; +select * from ti /* that is what slave would miss - a bug */; + + +## send_error() branch +delete from ti; +delete from tt where a=1; +reset master; +show master status; + +# action + +begin; +insert into ti values (1); +insert into ti values (2) /* to make the dup error in the following */; +--error ER_DUP_ENTRY +insert into tt select * from ti /* one affected and error */; +rollback; + +# check + +show master status; +--replace_column 2 # 5 # +show binlog events from 98; +select count(*) from ti /* zero */; +insert into ti select * from tt; +select * from tt /* that is what otherwise slave missed - the bug */; + +drop table ti; + + +# +# Bug #27417 thd->no_trans_update.stmt lost value inside of SF-exec-stack +# +# Testing asserts: if there is a side effect of modifying non-transactional +# table thd->no_trans_update.stmt must be TRUE; +# the assert is active with debug build +# + +--disable_warnings +drop function if exists bug27417; +drop table if exists t1,t2; +--enable_warnings +# side effect table +CREATE TABLE t1 (a int NOT NULL auto_increment primary key) ENGINE=MyISAM; +# target tables +CREATE TABLE t2 (a int NOT NULL auto_increment, PRIMARY KEY (a)); + +delimiter |; +create function bug27417(n int) +RETURNS int(11) +begin + insert into t1 values (null); + return n; +end| +delimiter ;| + +reset master; + +# execute + +insert into t2 values (bug27417(1)); +insert into t2 select bug27417(2); +reset master; + +--error ER_DUP_ENTRY +insert into t2 values (bug27417(2)); +show master status; /* only (!) with fixes for #23333 will show there is the query */; +select count(*) from t1 /* must be 3 */; + +reset master; +select count(*) from t2; +delete from t2 where a=bug27417(3); +select count(*) from t2 /* nothing got deleted */; +show master status; /* the query must be in regardless of #23333 */; +select count(*) from t1 /* must be 5 */; + +--enable_info +delete t2 from t2 where t2.a=bug27417(100) /* must not affect t2 */; +--disable_info +select count(*) from t1 /* must be 7 */; + +drop function bug27417; +drop table t1,t2; + +--echo end of tests diff --git a/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result b/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result index c74fb17d600..56dee7838cd 100644 --- a/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result +++ b/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result @@ -380,3 +380,116 @@ select @a like "%#%error_code=0%ROLLBACK/*!*/;%ROLLBACK /* added by mysqlbinlog */;%" @a not like "%#%error_code=%error_code=%" 1 1 drop table t1, t2; +create temporary table tt (a int unique); +create table ti (a int) engine=innodb; +reset master; +show master status; +File Position Binlog_Do_DB Binlog_Ignore_DB +master-bin.000001 98 +begin; +insert into ti values (1); +insert into ti values (2) ; +insert into tt select * from ti; +rollback; +Warnings: +Warning 1196 Some non-transactional changed tables couldn't be rolled back +select count(*) from tt /* 2 */; +count(*) +2 +show master status; +File Position Binlog_Do_DB Binlog_Ignore_DB +master-bin.000001 507 +show binlog events from 98; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query 1 # use `test`; BEGIN +master-bin.000001 # Query 1 # use `test`; insert into ti values (1) +master-bin.000001 # Query 1 # use `test`; insert into ti values (2) +master-bin.000001 # Query 1 # use `test`; insert into tt select * from ti +master-bin.000001 # Query 1 # use `test`; ROLLBACK +select count(*) from ti /* zero */; +count(*) +0 +insert into ti select * from tt; +select * from ti /* that is what slave would miss - a bug */; +a +1 +2 +delete from ti; +delete from tt where a=1; +reset master; +show master status; +File Position Binlog_Do_DB Binlog_Ignore_DB +master-bin.000001 98 +begin; +insert into ti values (1); +insert into ti values (2) /* to make the dup error in the following */; +insert into tt select * from ti /* one affected and error */; +ERROR 23000: Duplicate entry '2' for key 1 +rollback; +Warnings: +Warning 1196 Some non-transactional changed tables couldn't be rolled back +show master status; +File Position Binlog_Do_DB Binlog_Ignore_DB +master-bin.000001 581 +show binlog events from 98; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query 1 # use `test`; BEGIN +master-bin.000001 # Query 1 # use `test`; insert into ti values (1) +master-bin.000001 # Query 1 # use `test`; insert into ti values (2) /* to make the dup error in the following */ +master-bin.000001 # Query 1 # use `test`; insert into tt select * from ti /* one affected and error */ +master-bin.000001 # Query 1 # use `test`; ROLLBACK +select count(*) from ti /* zero */; +count(*) +0 +insert into ti select * from tt; +select * from tt /* that is what otherwise slave missed - the bug */; +a +1 +2 +drop table ti; +drop function if exists bug27417; +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, PRIMARY KEY (a)); +create function bug27417(n int) +RETURNS int(11) +begin +insert into t1 values (null); +return n; +end| +reset master; +insert into t2 values (bug27417(1)); +insert into t2 select bug27417(2); +reset master; +insert into t2 values (bug27417(2)); +ERROR 23000: Duplicate entry '2' for key 1 +show master status; +File Position Binlog_Do_DB Binlog_Ignore_DB +master-bin.000001 98 +/* only (!) with fixes for #23333 will show there is the query */; +select count(*) from t1 /* must be 3 */; +count(*) +3 +reset master; +select count(*) from t2; +count(*) +2 +delete from t2 where a=bug27417(3); +select count(*) from t2 /* nothing got deleted */; +count(*) +2 +show master status; +File Position Binlog_Do_DB Binlog_Ignore_DB +master-bin.000001 195 +/* the query must be in regardless of #23333 */; +select count(*) from t1 /* must be 5 */; +count(*) +5 +delete t2 from t2 where t2.a=bug27417(100) /* must not affect t2 */; +affected rows: 0 +select count(*) from t1 /* must be 7 */; +count(*) +7 +drop function bug27417; +drop table t1,t2; +end of tests diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index dd29eb17059..48712600e79 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -4366,7 +4366,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->no_trans_update.all= thd->no_trans_update.stmt= TRUE; + thd->transaction.all.modified_non_trans_table= thd->transaction.stmt.modified_non_trans_table= TRUE; } else if (!thd->transaction.on) m_transaction_on= FALSE; diff --git a/sql/handler.cc b/sql/handler.cc index 55e2e478027..3b03efa080a 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -860,7 +860,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->no_trans_update.all && + if (is_real_trans && thd->transaction.all.modified_non_trans_table && !thd->slave_thread && thd->killed != THD::KILL_CONNECTION) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARNING_NOT_COMPLETE_ROLLBACK, diff --git a/sql/handler.h b/sql/handler.h index f45b28c55f5..172f43b6b59 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -728,6 +728,35 @@ typedef struct st_thd_trans bool no_2pc; /* storage engines that registered themselves for this transaction */ handlerton *ht[MAX_HA]; + /* + The purpose of this flag is to keep track of non-transactional + tables that were modified in scope of: + - transaction, when the variable is a member of + THD::transaction.all + - top-level statement or sub-statement, when the variable is a + member of THD::transaction.stmt + This member has the following life cycle: + * stmt.modified_non_trans_table is used to keep track of + modified non-transactional tables of top-level statements. At + the end of the previous statement and at the beginning of the session, + it is reset to FALSE. If such functions + as mysql_insert, mysql_update, mysql_delete etc modify a + non-transactional table, they set this flag to TRUE. At the + end of the statement, the value of stmt.modified_non_trans_table + is merged with all.modified_non_trans_table and gets reset. + * all.modified_non_trans_table is reset at the end of transaction + + * Since we do not have a dedicated context for execution of a + sub-statement, to keep track of non-transactional changes in a + sub-statement, we re-use stmt.modified_non_trans_table. + At entrance into a sub-statement, a copy of the value of + stmt.modified_non_trans_table (containing the changes of the + outer statement) is saved on stack. Then + stmt.modified_non_trans_table is reset to FALSE and the + substatement is executed. Then the new value is merged with the + saved value. + */ + bool modified_non_trans_table; } THD_TRANS; enum enum_tx_isolation { ISO_READ_UNCOMMITTED, ISO_READ_COMMITTED, diff --git a/sql/log.cc b/sql/log.cc index 0bf77d68410..415f98fd406 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -1609,7 +1609,8 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all) table. Such cases should be rare (updating a non-transactional table inside a transaction...) */ - if (unlikely(thd->no_trans_update.all || (thd->options & OPTION_KEEP_LOG))) + if (unlikely(thd->transaction.all.modified_non_trans_table || + (thd->options & OPTION_KEEP_LOG))) { Query_log_event qev(thd, STRING_WITH_LEN("ROLLBACK"), TRUE, FALSE); qev.error_code= 0; // see comment in MYSQL_LOG::write(THD, IO_CACHE) @@ -1663,7 +1664,8 @@ static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv) non-transactional table. Otherwise, truncate the binlog cache starting from the SAVEPOINT command. */ - if (unlikely(thd->no_trans_update.all || (thd->options & OPTION_KEEP_LOG))) + if (unlikely(thd->transaction.all.modified_non_trans_table || + (thd->options & OPTION_KEEP_LOG))) { int error= thd->binlog_query(THD::STMT_QUERY_TYPE, @@ -4094,6 +4096,7 @@ bool MYSQL_BIN_LOG::write(THD *thd, IO_CACHE *cache, Log_event *commit_event) /* NULL would represent nothing to replicate after ROLLBACK */ DBUG_ASSERT(commit_event != NULL); + DBUG_ASSERT(is_open()); if (likely(is_open())) // Should always be true { /* diff --git a/sql/set_var.cc b/sql/set_var.cc index 7f3e808090d..aee3b33cc24 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -2564,14 +2564,14 @@ static bool set_option_autocommit(THD *thd, set_var *var) { /* We changed to auto_commit mode */ thd->options&= ~(ulonglong) (OPTION_BEGIN | OPTION_KEEP_LOG); - thd->no_trans_update.all= FALSE; + thd->transaction.all.modified_non_trans_table= FALSE; thd->server_status|= SERVER_STATUS_AUTOCOMMIT; if (ha_commit(thd)) return 1; } else { - thd->no_trans_update.all= FALSE; + thd->transaction.all.modified_non_trans_table= FALSE; thd->server_status&= ~SERVER_STATUS_AUTOCOMMIT; } } diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 74714a42591..a5ad5043985 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -349,13 +349,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_stmt= thd->no_trans_update.stmt; + bool save_stmt_modified_non_trans_table= thd->transaction.stmt.modified_non_trans_table; 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.stmt= FALSE; + thd->transaction.stmt.modified_non_trans_table= FALSE; /* Save the value in the field. Convert the value if needed. */ @@ -363,7 +363,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.stmt= save_no_trans_update_stmt; + thd->transaction.stmt.modified_non_trans_table= save_stmt_modified_non_trans_table; if (thd->net.report_error) { @@ -2547,6 +2547,13 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, int res= 0; DBUG_ENTER("reset_lex_and_exec_core"); + /* + The flag is saved at the entry to the following substatement. + It's reset further in the common code part. + It's merged with the saved parent's value at the exit of this func. + */ + bool parent_modified_non_trans_table= thd->transaction.stmt.modified_non_trans_table; + thd->transaction.stmt.modified_non_trans_table= FALSE; DBUG_ASSERT(!thd->derived_tables); DBUG_ASSERT(thd->change_list.is_empty()); /* @@ -2616,7 +2623,11 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, /* Update the state of the active arena. */ thd->stmt_arena->state= Query_arena::EXECUTED; - + /* + Merge here with the saved parent's values + what is needed from the substatement gained + */ + thd->transaction.stmt.modified_non_trans_table |= parent_modified_non_trans_table; /* Unlike for PS we should not call Item's destructors for newly created items after execution of each instruction in stored routine. This is diff --git a/sql/sql_class.cc b/sql/sql_class.cc index da975ee3103..b64e74d5d36 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -582,7 +582,7 @@ void THD::init(void) if (variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) server_status|= SERVER_STATUS_NO_BACKSLASH_ESCAPES; options= thd_startup_options; - no_trans_update.stmt= no_trans_update.all= FALSE; + transaction.all.modified_non_trans_table= transaction.stmt.modified_non_trans_table= FALSE; open_options=ha_open_options; update_lock_default= (variables.low_priority_updates ? TL_WRITE_LOW_PRIORITY : diff --git a/sql/sql_class.h b/sql/sql_class.h index 71c13e001ee..2f01d04358b 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1409,10 +1409,6 @@ public: 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 */ - 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() */ @@ -1699,7 +1695,7 @@ public: inline bool really_abort_on_warning() { return (abort_on_warning && - (!no_trans_update.stmt || + (!transaction.stmt.modified_non_trans_table || (variables.sql_mode & MODE_STRICT_ALL_TABLES))); } void set_status_var_init(); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 7c868092921..bef9ac2eed5 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -341,6 +341,9 @@ cleanup: delete select; transactional_table= table->file->has_transactions(); + if (!transactional_table && deleted > 0) + thd->transaction.stmt.modified_non_trans_table= TRUE; + /* See similar binlogging code in sql_update.cc, for comments */ if ((error < 0) || (deleted && !transactional_table)) { @@ -364,9 +367,10 @@ cleanup: error=1; } } - if (!transactional_table) - thd->no_trans_update.all= TRUE; + if (thd->transaction.stmt.modified_non_trans_table) + thd->transaction.all.modified_non_trans_table= TRUE; } + DBUG_ASSERT(transactional_table || !deleted || thd->transaction.stmt.modified_non_trans_table); free_underlaid_joins(thd, select_lex); if (transactional_table) { @@ -677,20 +681,22 @@ bool multi_delete::send_data(List<Item> &values) if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, TRG_ACTION_BEFORE, FALSE)) - DBUG_RETURN(1); + DBUG_RETURN(1); table->status|= STATUS_DELETED; if (!(error=table->file->ha_delete_row(table->record[0]))) { - deleted++; + deleted++; + if (!table->file->has_transactions()) + thd->transaction.stmt.modified_non_trans_table= TRUE; if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, TRG_ACTION_AFTER, FALSE)) - DBUG_RETURN(1); + DBUG_RETURN(1); } else { - table->file->print_error(error,MYF(0)); - DBUG_RETURN(1); + table->file->print_error(error,MYF(0)); + DBUG_RETURN(1); } } else @@ -740,6 +746,7 @@ void multi_delete::send_error(uint errcode,const char *err) error= 1; send_eof(); } + DBUG_ASSERT(!normal_tables || !deleted || thd->transaction.stmt.modified_non_trans_table); DBUG_VOID_RETURN; } @@ -768,6 +775,7 @@ int multi_delete::do_deletes() for (; table_being_deleted; table_being_deleted= table_being_deleted->next_local, counter++) { + ha_rows last_deleted= deleted; TABLE *table = table_being_deleted->table; if (tempfiles[counter]->get(table)) { @@ -814,6 +822,8 @@ int multi_delete::do_deletes() table->file->print_error(local_error,MYF(0)); } } + if (last_deleted != deleted && !table->file->has_transactions()) + thd->transaction.stmt.modified_non_trans_table= TRUE; end_read_record(&info); if (thd->killed && !local_error) local_error= 1; @@ -852,7 +862,6 @@ bool multi_delete::send_eof() { query_cache_invalidate3(thd, delete_tables, 1); } - if ((local_error == 0) || (deleted && normal_tables)) { if (mysql_bin_log.is_open()) @@ -867,9 +876,11 @@ bool multi_delete::send_eof() local_error=1; // Log write failed: roll back the SQL statement } } - if (!transactional_tables) - thd->no_trans_update.all= TRUE; + if (thd->transaction.stmt.modified_non_trans_table) + thd->transaction.all.modified_non_trans_table= TRUE; } + DBUG_ASSERT(!normal_tables || !deleted || thd->transaction.stmt.modified_non_trans_table); + /* Commit or rollback the current SQL statement */ if (transactional_tables) if (ha_autocommit_or_rollback(thd,local_error > 0)) diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 445cabbd9b8..d77661ad367 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -713,7 +713,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, if (lock_type != TL_WRITE_DELAYED && !thd->prelocked_mode) table->file->ha_start_bulk_insert(values_list.elements); - thd->no_trans_update.stmt= FALSE; thd->abort_on_warning= (!ignore && (thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES))); @@ -883,10 +882,12 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, error=1; } } - if (!transactional_table && changed) - thd->no_trans_update.all= TRUE; + if (thd->transaction.stmt.modified_non_trans_table) + thd->transaction.all.modified_non_trans_table= TRUE; } } + DBUG_ASSERT(transactional_table || !changed || + thd->transaction.stmt.modified_non_trans_table); if (transactional_table) error=ha_autocommit_or_rollback(thd,error); @@ -1296,7 +1297,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.stmt to TRUE if table which is updated didn't have + Sets thd->transaction.stmt.modified_non_trans_table to TRUE if table which is updated didn't have transactions. RETURN VALUE @@ -1516,7 +1517,7 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) goto err; info->deleted++; if (!table->file->has_transactions()) - thd->no_trans_update.stmt= TRUE; + thd->transaction.stmt.modified_non_trans_table= TRUE; if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, TRG_ACTION_AFTER, TRUE)) @@ -1557,7 +1558,7 @@ ok_or_after_trg_err: if (key) my_safe_afree(key,table->s->max_unique_length,MAX_KEY_LENGTH); if (!table->file->has_transactions()) - thd->no_trans_update.stmt= TRUE; + thd->transaction.stmt.modified_non_trans_table= TRUE; DBUG_RETURN(trg_error); err: @@ -2922,7 +2923,6 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u) table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE); if (info.handle_duplicates == DUP_UPDATE) table->file->extra(HA_EXTRA_INSERT_WITH_UPDATE); - thd->no_trans_update.stmt= FALSE; thd->abort_on_warning= (!info.ignore && (thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | @@ -3071,6 +3071,7 @@ bool select_insert::send_eof() int error; bool const trans_table= table->file->has_transactions(); ulonglong id; + bool changed; DBUG_ENTER("select_insert::send_eof"); DBUG_PRINT("enter", ("trans_table=%d, table_type='%s'", trans_table, table->file->table_type())); @@ -3079,24 +3080,18 @@ bool select_insert::send_eof() table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); - if (info.copied || info.deleted || info.updated) + if (changed= (info.copied || info.deleted || info.updated)) { /* We must invalidate the table in the query cache before binlog writing and ha_autocommit_or_rollback. */ query_cache_invalidate3(thd, table, 1); - /* - Mark that we have done permanent changes if all of the below is true - - Table doesn't support transactions - - It's a normal (not temporary) table. (Changes to temporary tables - are not logged in RBR) - - We are using statement based replication - */ - if (!trans_table && - (!table->s->tmp_table || !thd->current_stmt_binlog_row_based)) - thd->no_trans_update.all= TRUE; - } + if (thd->transaction.stmt.modified_non_trans_table) + thd->transaction.all.modified_non_trans_table= TRUE; + } + DBUG_ASSERT(trans_table || !changed || + thd->transaction.stmt.modified_non_trans_table); /* Write to binlog before commiting transaction. No statement will @@ -3517,7 +3512,6 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) table->file->extra(HA_EXTRA_INSERT_WITH_UPDATE); if (!thd->prelocked_mode) table->file->ha_start_bulk_insert((ha_rows) 0); - thd->no_trans_update.stmt= FALSE; thd->abort_on_warning= (!info.ignore && (thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 3ffbdf83815..8bbe1e413b3 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -376,7 +376,6 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, table->file->ha_start_bulk_insert((ha_rows) 0); table->copy_blobs=1; - thd->no_trans_update.stmt= FALSE; thd->abort_on_warning= (!ignore && (thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | @@ -410,7 +409,6 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, ha_autocommit_... */ query_cache_invalidate3(thd, table_list, 0); - if (error) { if (read_file_from_client) @@ -469,8 +467,8 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, sprintf(name, ER(ER_LOAD_INFO), (ulong) info.records, (ulong) info.deleted, (ulong) (info.records - info.copied), (ulong) thd->cuted_fields); - if (!transactional_table) - thd->no_trans_update.all= TRUE; + if (thd->transaction.stmt.modified_non_trans_table) + thd->transaction.all.modified_non_trans_table= TRUE; #ifndef EMBEDDED_LIBRARY if (mysql_bin_log.is_open()) { @@ -506,6 +504,8 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, /* ok to client sent only after binlog write and engine commit */ send_ok(thd, info.copied + info.deleted, 0L, name); err: + DBUG_ASSERT(transactional_table || !(info.copied || info.deleted) || + thd->transaction.stmt.modified_non_trans_table); table->file->ha_release_auto_increment(); if (thd->lock) { @@ -552,7 +552,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_stmt, err; + bool err; DBUG_ENTER("read_fixed_length"); id= 0; @@ -580,7 +580,6 @@ 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_stmt= !table->file->has_transactions(); restore_record(table, s->default_values); /* @@ -650,7 +649,6 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, table->auto_increment_field_not_null= FALSE; if (err) DBUG_RETURN(1); - thd->no_trans_update.stmt= no_trans_update_stmt; /* We don't need to reset auto-increment field since we are restoring @@ -685,12 +683,11 @@ 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_stmt, err; + bool err; DBUG_ENTER("read_sep_field"); enclosed_length=enclosed.length(); id= 0; - no_trans_update_stmt= !table->file->has_transactions(); for (;;it.rewind()) { @@ -827,7 +824,6 @@ 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.stmt= no_trans_update_stmt; if (read_info.next_line()) // Skip to next line break; if (read_info.line_cuted) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 63971609794..f9b857f56fd 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -120,7 +120,7 @@ bool end_active_trans(THD *thd) error=1; } thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->no_trans_update.all= FALSE; + thd->transaction.all.modified_non_trans_table= FALSE; DBUG_RETURN(error); } @@ -578,7 +578,7 @@ 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_KEEP_LOG); - thd->no_trans_update.all= FALSE; + thd->transaction.all.modified_non_trans_table= FALSE; break; case COMMIT_RELEASE: do_release= 1; /* fall through */ @@ -596,7 +596,7 @@ int end_trans(THD *thd, enum enum_mysql_completiontype completion) if (ha_rollback(thd)) res= -1; thd->options&= ~(ulong) (OPTION_BEGIN | OPTION_KEEP_LOG); - thd->no_trans_update.all= FALSE; + thd->transaction.all.modified_non_trans_table= FALSE; if (!res && (completion == ROLLBACK_AND_CHAIN)) res= begin_trans(thd); break; @@ -1806,6 +1806,8 @@ mysql_execute_command(THD *thd) #endif status_var_increment(thd->status_var.com_stat[lex->sql_command]); + DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table == FALSE); + switch (lex->sql_command) { case SQLCOM_SHOW_EVENTS: if ((res= check_access(thd, EVENT_ACL, thd->lex->select_lex.db, 0, 0, 0, @@ -3620,7 +3622,8 @@ end_with_restore_list: res= TRUE; // cannot happen else { - if (((thd->options & OPTION_KEEP_LOG) || thd->no_trans_update.all) && + if (((thd->options & OPTION_KEEP_LOG) || + thd->transaction.all.modified_non_trans_table) && !thd->slave_thread) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARNING_NOT_COMPLETE_ROLLBACK, @@ -4200,8 +4203,8 @@ create_sp_error: 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->transaction.all.modified_non_trans_table= FALSE; thd->options= ((thd->options & ~(OPTION_KEEP_LOG)) | OPTION_BEGIN); - thd->no_trans_update.all= FALSE; thd->server_status|= SERVER_STATUS_IN_TRANS; send_ok(thd); break; @@ -4295,7 +4298,7 @@ create_sp_error: break; } thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->no_trans_update.all= FALSE; + thd->transaction.all.modified_non_trans_table= FALSE; thd->server_status&= ~SERVER_STATUS_IN_TRANS; xid_cache_delete(&thd->transaction.xid_state); thd->transaction.xid_state.xa_state=XA_NOTR; @@ -4326,7 +4329,7 @@ create_sp_error: else send_ok(thd); thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->no_trans_update.all= FALSE; + thd->transaction.all.modified_non_trans_table= FALSE; thd->server_status&= ~SERVER_STATUS_IN_TRANS; xid_cache_delete(&thd->transaction.xid_state); thd->transaction.xid_state.xa_state=XA_NOTR; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 54b115af19f..a10a5b15288 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -6766,7 +6766,6 @@ 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.stmt= FALSE; thd->abort_on_warning= !ignore && test(thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES)); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 1016a23b5ae..a85b86bfca8 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -530,7 +530,6 @@ int mysql_update(THD *thd, thd->proc_info="Updating"; transactional_table= table->file->has_transactions(); - thd->no_trans_update.stmt= FALSE; thd->abort_on_warning= test(!ignore && (thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | @@ -641,7 +640,6 @@ int mysql_update(THD *thd, updated++; else error= 0; - thd->no_trans_update.stmt= !transactional_table; if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, @@ -717,6 +715,10 @@ int mysql_update(THD *thd, } dup_key_found= 0; + if (!transactional_table && updated > 0) + thd->transaction.stmt.modified_non_trans_table= TRUE; + + /* todo bug#27571: to avoid asynchronization of `error' and `error_code' of binlog event constructor @@ -809,9 +811,10 @@ int mysql_update(THD *thd, error=1; // Rollback update } } - if (!transactional_table) - thd->no_trans_update.all= TRUE; + if (thd->transaction.stmt.modified_non_trans_table) + thd->transaction.all.modified_non_trans_table= TRUE; } + DBUG_ASSERT(transactional_table || !updated || thd->transaction.stmt.modified_non_trans_table); free_underlaid_joins(thd, select_lex); if (transactional_table) { @@ -1176,7 +1179,6 @@ bool mysql_multi_update(THD *thd, handle_duplicates, ignore))) DBUG_RETURN(TRUE); - thd->no_trans_update.stmt= FALSE; thd->abort_on_warning= test(thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES)); @@ -1549,9 +1551,7 @@ multi_update::~multi_update() if (copy_field) delete [] copy_field; thd->count_cuted_fields= CHECK_FIELD_IGNORE; // Restore this setting - if (!trans_safe) // todo: remove since redundant - thd->no_trans_update.all= TRUE; - DBUG_ASSERT(trans_safe || thd->no_trans_update.all); + DBUG_ASSERT(trans_safe || thd->transaction.all.modified_non_trans_table); } @@ -1655,7 +1655,7 @@ bool multi_update::send_data(List<Item> ¬_used_values) else { trans_safe= 0; - thd->no_trans_update.stmt= TRUE; + thd->transaction.stmt.modified_non_trans_table= TRUE; } if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, @@ -1718,7 +1718,6 @@ void multi_update::send_error(uint errcode,const char *err) /* Something already updated so we have to invalidate cache */ query_cache_invalidate3(thd, update_tables, 1); - /* If all tables that has been updated are trans safe then just do rollback. If not attempt to do remaining updates. @@ -1731,7 +1730,7 @@ void multi_update::send_error(uint errcode,const char *err) } else { - DBUG_ASSERT(thd->no_trans_update.stmt); + DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table); if (do_update && table_count > 1) { /* Add warning here */ @@ -1742,7 +1741,7 @@ void multi_update::send_error(uint errcode,const char *err) VOID(do_updates(0)); } } - if (thd->no_trans_update.stmt) + if (thd->transaction.stmt.modified_non_trans_table) { /* The query has to binlog because there's a modified non-transactional table @@ -1755,9 +1754,9 @@ void multi_update::send_error(uint errcode,const char *err) transactional_tables, FALSE); } if (!trans_safe) - thd->no_trans_update.all= TRUE; + thd->transaction.all.modified_non_trans_table= TRUE; } - DBUG_ASSERT(trans_safe || !updated || thd->no_trans_update.stmt); + DBUG_ASSERT(trans_safe || !updated || thd->transaction.stmt.modified_non_trans_table); if (transactional_tables) { @@ -1900,7 +1899,7 @@ int multi_update::do_updates(bool from_send_error) else { trans_safe= 0; // Can't do safe rollback - thd->no_trans_update.stmt= TRUE; + thd->transaction.stmt.modified_non_trans_table= TRUE; } } (void) table->file->ha_rnd_end(); @@ -1934,7 +1933,7 @@ err2: else { trans_safe= 0; - thd->no_trans_update.stmt= TRUE; + thd->transaction.stmt.modified_non_trans_table= TRUE; } } DBUG_RETURN(1); @@ -1961,7 +1960,6 @@ bool multi_update::send_eof() { query_cache_invalidate3(thd, update_tables, 1); } - /* Write the SQL statement to the binlog if we updated rows and we succeeded or if we updated some non @@ -1971,8 +1969,9 @@ bool multi_update::send_eof() either from the query's list or via a stored routine: bug#13270,23333 */ - DBUG_ASSERT(trans_safe || !updated || thd->no_trans_update.stmt); - if (local_error == 0 || thd->no_trans_update.stmt) + DBUG_ASSERT(trans_safe || !updated || + thd->transaction.stmt.modified_non_trans_table); + if (local_error == 0 || thd->transaction.stmt.modified_non_trans_table) { if (mysql_bin_log.is_open()) { @@ -1988,8 +1987,8 @@ bool multi_update::send_eof() local_error= 1; // Rollback update } } - if (!trans_safe) - thd->no_trans_update.all= TRUE; + if (thd->transaction.stmt.modified_non_trans_table) + thd->transaction.all.modified_non_trans_table= TRUE; } if (transactional_tables) |