diff options
-rw-r--r-- | client/Makefile.am | 3 | ||||
-rw-r--r-- | libmysqld/CMakeLists.txt | 2 | ||||
-rw-r--r-- | libmysqld/Makefile.am | 3 | ||||
-rwxr-xr-x | sql/CMakeLists.txt | 1 | ||||
-rw-r--r-- | sql/Makefile.am | 4 | ||||
-rw-r--r-- | sql/handler.cc | 66 | ||||
-rw-r--r-- | sql/handler.h | 7 | ||||
-rw-r--r-- | sql/log_event.cc | 10 | ||||
-rw-r--r-- | sql/log_event_old.cc | 3 | ||||
-rw-r--r-- | sql/mysql_priv.h | 9 | ||||
-rw-r--r-- | sql/rpl_injector.cc | 7 | ||||
-rw-r--r-- | sql/rpl_rli.cc | 5 | ||||
-rw-r--r-- | sql/set_var.cc | 3 | ||||
-rw-r--r-- | sql/slave.cc | 3 | ||||
-rw-r--r-- | sql/sql_base.cc | 3 | ||||
-rw-r--r-- | sql/sql_class.cc | 4 | ||||
-rw-r--r-- | sql/sql_delete.cc | 5 | ||||
-rw-r--r-- | sql/sql_do.cc | 3 | ||||
-rw-r--r-- | sql/sql_insert.cc | 9 | ||||
-rw-r--r-- | sql/sql_parse.cc | 512 | ||||
-rw-r--r-- | sql/sql_partition.cc | 5 | ||||
-rw-r--r-- | sql/sql_table.cc | 39 | ||||
-rw-r--r-- | sql/transaction.cc | 648 | ||||
-rw-r--r-- | sql/transaction.h | 46 |
24 files changed, 819 insertions, 581 deletions
diff --git a/client/Makefile.am b/client/Makefile.am index ccd0d8aada0..cfc322c2531 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -107,7 +107,8 @@ sql_src=log_event.h mysql_priv.h rpl_constants.h \ rpl_utility.h rpl_tblmap.h rpl_tblmap.cc \ log_event.cc my_decimal.h my_decimal.cc \ log_event_old.h log_event_old.cc \ - rpl_record_old.h rpl_record_old.cc + rpl_record_old.h rpl_record_old.cc \ + transaction.h strings_src=decimal.c link_sources: diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 655082f0304..053f277ffbb 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -133,7 +133,7 @@ SET(LIBMYSQLD_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/partition_info.cc ../sql/sql_connect.cc ../sql/scheduler.cc ../sql/event_parse_data.cc ../sql/sql_signal.cc ../sql/rpl_handler.cc - ../sql/mdl.cc + ../sql/mdl.cc ../sql/transaction.cc ${GEN_SOURCES} ${LIB_SOURCES}) diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am index 3a7fa934778..824145a462a 100644 --- a/libmysqld/Makefile.am +++ b/libmysqld/Makefile.am @@ -75,8 +75,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \ sp_head.cc sp_pcontext.cc sp.cc sp_cache.cc sp_rcontext.cc \ parse_file.cc sql_view.cc sql_trigger.cc my_decimal.cc \ rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \ - debug_sync.cc \ - sql_tablespace.cc \ + debug_sync.cc sql_tablespace.cc transaction.cc \ rpl_injector.cc my_user.c partition_info.cc \ sql_servers.cc event_parse_data.cc sql_signal.cc \ rpl_handler.cc mdl.cc diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index a40cf04f37c..38c6adf755b 100755 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -77,6 +77,7 @@ SET (SQL_SOURCE sql_connect.cc scheduler.cc sql_profile.cc event_parse_data.cc sql_signal.cc rpl_handler.cc mdl.cc + transaction.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.h ${PROJECT_SOURCE_DIR}/include/mysqld_error.h diff --git a/sql/Makefile.am b/sql/Makefile.am index 95864dfbb1b..a7d897be3f0 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -113,7 +113,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ sql_partition.h partition_info.h partition_element.h \ contributors.h sql_servers.h sql_signal.h records.h \ sql_prepare.h rpl_handler.h replication.h mdl.h \ - sql_plist.h + sql_plist.h transaction.h mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ item.cc item_sum.cc item_buff.cc item_func.cc \ @@ -159,7 +159,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ sql_plugin.cc sql_binlog.cc \ sql_builtin.cc sql_tablespace.cc partition_info.cc \ sql_servers.cc event_parse_data.cc sql_signal.cc \ - rpl_handler.cc mdl.cc + rpl_handler.cc mdl.cc transaction.cc nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c diff --git a/sql/handler.cc b/sql/handler.cc index 2ccb87591dc..66b0450d8d6 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -27,6 +27,7 @@ #include "rpl_handler.h" #include "rpl_filter.h" #include <myisampack.h> +#include "transaction.h" #include <errno.h> #include "probes_mysql.h" @@ -888,16 +889,16 @@ void ha_close_connection(THD* thd) a transaction in a given engine is read-write and will not involve the two-phase commit protocol! - At the end of a statement, server call - ha_autocommit_or_rollback() is invoked. This call in turn - invokes handlerton::prepare() for every involved engine. - Prepare is followed by a call to handlerton::commit_one_phase() - If a one-phase commit will suffice, handlerton::prepare() is not - invoked and the server only calls handlerton::commit_one_phase(). - At statement commit, the statement-related read-write engine - flag is propagated to the corresponding flag in the normal - transaction. When the commit is complete, the list of registered - engines is cleared. + At the end of a statement, server call trans_commit_stmt is + invoked. This call in turn invokes handlerton::prepare() + for every involved engine. Prepare is followed by a call + to handlerton::commit_one_phase() If a one-phase commit + will suffice, handlerton::prepare() is not invoked and + the server only calls handlerton::commit_one_phase(). + At statement commit, the statement-related read-write + engine flag is propagated to the corresponding flag in the + normal transaction. When the commit is complete, the list + of registered engines is cleared. Rollback is handled in a similar fashion. @@ -908,7 +909,7 @@ void ha_close_connection(THD* thd) do not "register" in thd->transaction lists, and thus do not modify the transaction state. Besides, each DDL in MySQL is prefixed with an implicit normal transaction commit - (a call to end_active_trans()), and thus leaves nothing + (a call to trans_commit_implicit()), and thus leaves nothing to modify. However, as it has been pointed out with CREATE TABLE .. SELECT, some DDL statements can start a *new* transaction. @@ -1370,47 +1371,6 @@ int ha_rollback_trans(THD *thd, bool all) DBUG_RETURN(error); } -/** - This is used to commit or rollback a single statement depending on - the value of error. - - @note - Note that if the autocommit is on, then the following call inside - InnoDB will commit or rollback the whole transaction (= the statement). The - autocommit mechanism built into InnoDB is based on counting locks, but if - the user has used LOCK TABLES then that mechanism does not know to do the - commit. -*/ -int ha_autocommit_or_rollback(THD *thd, int error) -{ - DBUG_ENTER("ha_autocommit_or_rollback"); - - if (thd->transaction.stmt.ha_list) - { - if (!error) - { - if (ha_commit_trans(thd, 0)) - error=1; - } - else - { - (void) ha_rollback_trans(thd, 0); - if (thd->transaction_rollback_request && !thd->in_sub_stmt) - (void) ha_rollback(thd); - } - - thd->variables.tx_isolation=thd->session_tx_isolation; - } - else - { - if (!error) - RUN_HOOK(transaction, after_commit, (thd, FALSE)); - else - RUN_HOOK(transaction, after_rollback, (thd, FALSE)); - } - DBUG_RETURN(error); -} - struct xahton_st { XID *xid; @@ -3496,7 +3456,7 @@ int ha_enable_transaction(THD *thd, bool on) So, let's commit an open transaction (if any) now. */ if (!(error= ha_commit_trans(thd, 0))) - error= end_trans(thd, COMMIT); + error= trans_commit_implicit(thd); } DBUG_RETURN(error); } diff --git a/sql/handler.h b/sql/handler.h index 7e64a08700f..1bcf6a73b33 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -1945,10 +1945,6 @@ extern TYPELIB tx_isolation_typelib; extern TYPELIB myisam_stats_method_typelib; extern ulong total_ha, total_ha_2pc; - /* Wrapper functions */ -#define ha_commit(thd) (ha_commit_trans((thd), TRUE)) -#define ha_rollback(thd) (ha_rollback_trans((thd), TRUE)) - /* lookups */ handlerton *ha_default_handlerton(THD *thd); plugin_ref ha_resolve_by_name(THD *thd, const LEX_STRING *name); @@ -2025,13 +2021,12 @@ int ha_release_temporary_latches(THD *thd); int ha_start_consistent_snapshot(THD *thd); int ha_commit_or_rollback_by_xid(XID *xid, bool commit); int ha_commit_one_phase(THD *thd, bool all); +int ha_commit_trans(THD *thd, bool all); int ha_rollback_trans(THD *thd, bool all); int ha_prepare(THD *thd); int ha_recover(HASH *commit_list); /* transactions: these functions never call handlerton functions directly */ -int ha_commit_trans(THD *thd, bool all); -int ha_autocommit_or_rollback(THD *thd, int error); int ha_enable_transaction(THD *thd, bool on); /* savepoints */ diff --git a/sql/log_event.cc b/sql/log_event.cc index b3f6fd58f1a..9c3a2ce2131 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -31,6 +31,7 @@ #include "rpl_filter.h" #include "rpl_utility.h" #include "rpl_record.h" +#include "transaction.h" #include <my_dir.h> #endif /* MYSQL_CLIENT */ @@ -3231,7 +3232,7 @@ Default database: '%s'. Query: '%s'", them back here. */ if (expected_error && expected_error == actual_error) - ha_autocommit_or_rollback(thd, TRUE); + trans_rollback_stmt(thd); } /* If we expected a non-zero error code and get nothing and, it is a concurrency @@ -3240,7 +3241,8 @@ Default database: '%s'. Query: '%s'", else if (expected_error && !actual_error && (concurrency_error_code(expected_error) || ignored_error_code(expected_error))) - ha_autocommit_or_rollback(thd, TRUE); + trans_rollback_stmt(thd); + /* Other cases: mostly we expected no error and get one. */ @@ -5315,7 +5317,7 @@ int Xid_log_event::do_apply_event(Relay_log_info const *rli) /* For a slave Xid_log_event is COMMIT */ general_log_print(thd, COM_QUERY, "COMMIT /* implicit, from Xid_log_event */"); - return end_trans(thd, COMMIT); + return trans_commit(thd); } Log_event::enum_skip_reason @@ -7607,7 +7609,7 @@ static int rows_event_stmt_cleanup(Relay_log_info const *rli, THD * thd) are involved, commit the transaction and flush the pending event to the binlog. */ - error= ha_autocommit_or_rollback(thd, 0); + error= trans_commit_stmt(thd); /* Now what if this is not a transactional engine? we still need to diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index 3dcf19f6b32..90ea8b6cefe 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -6,6 +6,7 @@ #endif #include "log_event_old.h" #include "rpl_record_old.h" +#include "transaction.h" #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) @@ -1795,7 +1796,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) are involved, commit the transaction and flush the pending event to the binlog. */ - if ((error= ha_autocommit_or_rollback(thd, 0))) + if ((error= trans_commit_stmt(thd))) rli->report(ERROR_LEVEL, error, "Error in %s event: commit of row events failed, " "table `%s`.`%s`", diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 72bc20238fc..3f6ed2b1cb0 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -883,15 +883,6 @@ bool parse_sql(THD *thd, Parser_state *parser_state, Object_creation_ctx *creation_ctx); -enum enum_mysql_completiontype { - ROLLBACK_RELEASE=-2, ROLLBACK=1, ROLLBACK_AND_CHAIN=7, - COMMIT_RELEASE=-1, COMMIT=0, COMMIT_AND_CHAIN=6 -}; - -bool begin_trans(THD *thd); -bool end_active_trans(THD *thd); -int end_trans(THD *thd, enum enum_mysql_completiontype completion); - Item *negate_expression(THD *thd, Item *expr); /* log.cc */ diff --git a/sql/rpl_injector.cc b/sql/rpl_injector.cc index 684655d1c3b..738341cc034 100644 --- a/sql/rpl_injector.cc +++ b/sql/rpl_injector.cc @@ -15,6 +15,7 @@ #include "mysql_priv.h" #include "rpl_injector.h" +#include "transaction.h" /* injector::transaction - member definitions @@ -35,7 +36,7 @@ injector::transaction::transaction(MYSQL_BIN_LOG *log, THD *thd) m_start_pos.m_file_name= my_strdup(log_info.log_file_name, MYF(0)); m_start_pos.m_file_pos= log_info.pos; - begin_trans(m_thd); + trans_begin(m_thd); thd->set_current_stmt_binlog_row_based(); } @@ -81,8 +82,8 @@ int injector::transaction::commit() is committed by committing the statement transaction explicitly. */ - ha_autocommit_or_rollback(m_thd, 0); - end_trans(m_thd, COMMIT); + trans_commit_stmt(m_thd); + trans_commit(m_thd); DBUG_RETURN(0); } diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index 42cd12bd66c..0a703475aa7 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -20,6 +20,7 @@ #include <my_dir.h> // For MY_STAT #include "sql_repl.h" // For check_binlog_magic #include "rpl_utility.h" +#include "transaction.h" static int count_relay_log_space(Relay_log_info* rli); @@ -1183,8 +1184,8 @@ void Relay_log_info::cleanup_context(THD *thd, bool error) */ if (error) { - ha_autocommit_or_rollback(thd, 1); // if a "statement transaction" - end_trans(thd, ROLLBACK); // if a "real transaction" + trans_rollback_stmt(thd); // if a "statement transaction" + trans_rollback(thd); // if a "real transaction" } m_table_map.clear_tables(); slave_close_thread_tables(thd); diff --git a/sql/set_var.cc b/sql/set_var.cc index 82ecb6b71cf..266cdd9ad6d 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -61,6 +61,7 @@ #include <my_dir.h> #include "events.h" +#include "transaction.h" /* WITH_NDBCLUSTER_STORAGE_ENGINE */ #ifdef WITH_NDBCLUSTER_STORAGE_ENGINE @@ -3190,7 +3191,7 @@ static bool set_option_autocommit(THD *thd, set_var *var) */ if (var->save_result.ulong_value != 0 && (thd->options & OPTION_NOT_AUTOCOMMIT) && - ha_commit(thd)) + trans_commit(thd)) return 1; if (var->save_result.ulong_value != 0) diff --git a/sql/slave.cc b/sql/slave.cc index 99a7fc8d3eb..ed722305b29 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -34,6 +34,7 @@ #include "sql_repl.h" #include "rpl_filter.h" #include "repl_failsafe.h" +#include "transaction.h" #include <thr_alarm.h> #include <my_dir.h> #include <sql_common.h> @@ -2430,7 +2431,7 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) else { exec_res= 0; - end_trans(thd, ROLLBACK); + trans_rollback(thd); /* chance for concurrent connection to get more locks */ safe_sleep(thd, min(rli->trans_retries, MAX_SLAVE_RETRY_PAUSE), (CHECK_KILLED_FUNC)sql_slave_killed, (void*)rli); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index a84891c0ab7..0bd1acaa08e 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -22,6 +22,7 @@ #include "sp_head.h" #include "sp.h" #include "sql_trigger.h" +#include "transaction.h" #include "sql_prepare.h" #include <m_ctype.h> #include <my_dir.h> @@ -1402,7 +1403,7 @@ void close_thread_tables(THD *thd, if (!(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)) { thd->stmt_da->can_overwrite_status= TRUE; - ha_autocommit_or_rollback(thd, thd->is_error()); + thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); thd->stmt_da->can_overwrite_status= FALSE; /* diff --git a/sql/sql_class.cc b/sql/sql_class.cc index ff1b03102a6..de2ef98e993 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -42,6 +42,7 @@ #include "sp_rcontext.h" #include "sp_cache.h" +#include "transaction.h" #include "debug_sync.h" /* @@ -987,7 +988,8 @@ void THD::cleanup(void) } #endif { - ha_rollback(this); + transaction.xid_state.xa_state= XA_NOTR; + trans_rollback(this); xid_cache_delete(&transaction.xid_state); } locked_tables_list.unlock_locked_tables(this); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index c0162605e2f..13c331af95c 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -23,6 +23,7 @@ #include "sql_select.h" #include "sp_head.h" #include "sql_trigger.h" +#include "transaction.h" /** Implement DELETE SQL word. @@ -1071,8 +1072,8 @@ static bool mysql_truncate_by_delete(THD *thd, TABLE_LIST *table_list) if (error) { DBUG_ASSERT(thd->stmt_da->is_error()); - ha_autocommit_or_rollback(thd, TRUE); - end_active_trans(thd); + trans_rollback_stmt(thd); + trans_rollback(thd); } thd->current_stmt_binlog_row_based= save_binlog_row_based; DBUG_RETURN(error); diff --git a/sql/sql_do.cc b/sql/sql_do.cc index 8406a9eaf45..0f3a7e1ecef 100644 --- a/sql/sql_do.cc +++ b/sql/sql_do.cc @@ -17,6 +17,7 @@ /* Execute DO statement */ #include "mysql_priv.h" +#include "transaction.h" bool mysql_do(THD *thd, List<Item> &values) { @@ -36,7 +37,7 @@ bool mysql_do(THD *thd, List<Item> &values) will clear the error and the rollback in the end of dispatch_command() won't work. */ - ha_autocommit_or_rollback(thd, thd->is_error()); + trans_rollback_stmt(thd); thd->clear_error(); // DO always is OK } my_ok(thd); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index e0199fcdf20..16e9c1ded38 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -61,6 +61,7 @@ #include "sql_show.h" #include "slave.h" #include "rpl_mi.h" +#include "transaction.h" #ifndef EMBEDDED_LIBRARY static bool delayed_get_table(THD *thd, TABLE_LIST *table_list); @@ -2539,7 +2540,7 @@ pthread_handler_t handle_delayed_insert(void *arg) */ di->table->file->ha_release_auto_increment(); mysql_unlock_tables(thd, lock); - ha_autocommit_or_rollback(thd, 0); + trans_commit_stmt(thd); di->group_count=0; pthread_mutex_lock(&di->mutex); } @@ -2559,7 +2560,7 @@ pthread_handler_t handle_delayed_insert(void *arg) first call to ha_*_row() instead. Remove code that are used to cover for the case outlined above. */ - ha_autocommit_or_rollback(thd, 1); + trans_rollback_stmt(thd); DBUG_LEAVE; } @@ -3863,8 +3864,8 @@ bool select_create::send_eof() */ if (!table->s->tmp_table) { - ha_autocommit_or_rollback(thd, 0); - end_active_trans(thd); + trans_commit_stmt(thd); + trans_commit_implicit(thd); } table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 660241e8e2c..2df66a44fa6 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -28,6 +28,7 @@ #include "sp_cache.h" #include "events.h" #include "sql_trigger.h" +#include "transaction.h" #include "sql_prepare.h" #include "probes_mysql.h" @@ -88,113 +89,6 @@ const char *xa_state_names[]={ "NON-EXISTING", "ACTIVE", "IDLE", "PREPARED", "ROLLBACK ONLY" }; -/** - Mark a XA transaction as rollback-only if the RM unilaterally - rolled back the transaction branch. - - @note If a rollback was requested by the RM, this function sets - the appropriate rollback error code and transits the state - to XA_ROLLBACK_ONLY. - - @return TRUE if transaction was rolled back or if the transaction - state is XA_ROLLBACK_ONLY. FALSE otherwise. -*/ -static bool xa_trans_rolled_back(XID_STATE *xid_state) -{ - if (xid_state->rm_error) - { - switch (xid_state->rm_error) { - case ER_LOCK_WAIT_TIMEOUT: - my_error(ER_XA_RBTIMEOUT, MYF(0)); - break; - case ER_LOCK_DEADLOCK: - my_error(ER_XA_RBDEADLOCK, MYF(0)); - break; - default: - my_error(ER_XA_RBROLLBACK, MYF(0)); - } - xid_state->xa_state= XA_ROLLBACK_ONLY; - } - - return (xid_state->xa_state == XA_ROLLBACK_ONLY); -} - -/** - Rollback work done on behalf of at ransaction branch. -*/ -static bool xa_trans_rollback(THD *thd) -{ - /* - Resource Manager error is meaningless at this point, as we perform - explicit rollback request by user. We must reset rm_error before - calling ha_rollback(), so thd->transaction.xid structure gets reset - by ha_rollback()/THD::transaction::cleanup(). - */ - thd->transaction.xid_state.rm_error= 0; - - bool status= test(ha_rollback(thd)); - - thd->options&= ~(ulong) OPTION_BEGIN; - 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; - - return status; -} - -bool end_active_trans(THD *thd) -{ - int error=0; - DBUG_ENTER("end_active_trans"); - if (unlikely(thd->in_sub_stmt)) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - DBUG_RETURN(1); - } - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - DBUG_RETURN(1); - } - if (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN | - OPTION_TABLE_LOCK)) - { - DBUG_PRINT("info",("options: 0x%llx", thd->options)); - /* Safety if one did "drop table" on locked tables */ - if (!thd->locked_tables_mode) - thd->options&= ~OPTION_TABLE_LOCK; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - if (ha_commit(thd)) - error=1; - } - thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - DBUG_RETURN(error); -} - - -bool begin_trans(THD *thd) -{ - int error=0; - if (unlikely(thd->in_sub_stmt)) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - return 1; - } - - thd->locked_tables_list.unlock_locked_tables(thd); - - if (end_active_trans(thd)) - error= -1; - else - { - thd->options|= OPTION_BEGIN; - thd->server_status|= SERVER_STATUS_IN_TRANS; - } - return error; -} #ifdef HAVE_REPLICATION /** @@ -256,9 +150,9 @@ static bool opt_implicit_commit(THD *thd, uint mask) if (!skip) { /* Commit or rollback the statement transaction. */ - ha_autocommit_or_rollback(thd, thd->is_error()); + thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); /* Commit the normal transaction if one is active. */ - res= end_active_trans(thd); + res= trans_commit_implicit(thd); } DBUG_RETURN(res); @@ -691,80 +585,6 @@ void cleanup_items(Item *item) DBUG_VOID_RETURN; } -/** - Ends the current transaction and (maybe) begin the next. - - @param thd Current thread - @param completion Completion type - - @retval - 0 OK -*/ - -int end_trans(THD *thd, enum enum_mysql_completiontype completion) -{ - bool do_release= 0; - int res= 0; - DBUG_ENTER("end_trans"); - - if (unlikely(thd->in_sub_stmt)) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - DBUG_RETURN(1); - } - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - DBUG_RETURN(1); - } - switch (completion) { - case COMMIT: - /* - We don't use end_active_trans() here to ensure that this works - even if there is a problem with the OPTION_AUTO_COMMIT flag - (Which of course should never happen...) - */ - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - res= ha_commit(thd); - thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - break; - case COMMIT_RELEASE: - do_release= 1; /* fall through */ - case COMMIT_AND_CHAIN: - res= end_active_trans(thd); - if (!res && completion == COMMIT_AND_CHAIN) - res= begin_trans(thd); - break; - case ROLLBACK_RELEASE: - do_release= 1; /* fall through */ - case ROLLBACK: - case ROLLBACK_AND_CHAIN: - { - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - if (ha_rollback(thd)) - res= -1; - thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - if (!res && (completion == ROLLBACK_AND_CHAIN)) - res= begin_trans(thd); - break; - } - default: - res= -1; - my_error(ER_UNKNOWN_COM_ERROR, MYF(0)); - DBUG_RETURN(-1); - } - - if (res < 0) - my_error(thd->killed_errno(), MYF(0)); - else if ((res == 0) && do_release) - thd->killed= THD::KILL_CONNECTION; - - DBUG_RETURN(res); -} - #ifndef EMBEDDED_LIBRARY /** @@ -1346,7 +1166,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, bool not_used; status_var_increment(thd->status_var.com_stat[SQLCOM_FLUSH]); ulong options= (ulong) (uchar) packet[0]; - if (end_active_trans(thd)) + if (trans_commit_implicit(thd)) break; if (check_global_access(thd,RELOAD_ACL)) break; @@ -1374,7 +1194,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #endif if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, ¬_used)) break; - if (end_active_trans(thd)) + if (trans_commit_implicit(thd)) break; my_ok(thd); break; @@ -1532,7 +1352,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, /* If commit fails, we should be able to reset the OK status. */ thd->stmt_da->can_overwrite_status= TRUE; - ha_autocommit_or_rollback(thd, thd->is_error()); + thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); thd->stmt_da->can_overwrite_status= FALSE; thd->transaction.stmt.reset(); @@ -3485,7 +3305,7 @@ end_with_restore_list: thd->locked_tables_list.unlock_locked_tables(thd); if (thd->options & OPTION_TABLE_LOCK) { - end_active_trans(thd); + trans_commit_implicit(thd); thd->options&= ~(OPTION_TABLE_LOCK); } if (thd->global_read_lock) @@ -3495,7 +3315,7 @@ end_with_restore_list: case SQLCOM_LOCK_TABLES: thd->locked_tables_list.unlock_locked_tables(thd); /* we must end the trasaction first, regardless of anything */ - if (end_active_trans(thd)) + if (trans_commit_implicit(thd)) goto error; if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)) @@ -3524,8 +3344,8 @@ end_with_restore_list: can free its locks if LOCK TABLES locked some tables before finding that it can't lock a table in its list */ - ha_autocommit_or_rollback(thd, 1); - end_active_trans(thd); + trans_rollback_stmt(thd); + trans_commit_implicit(thd); thd->options&= ~(OPTION_TABLE_LOCK); } else @@ -4007,132 +3827,50 @@ end_with_restore_list: break; case SQLCOM_BEGIN: - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (begin_trans(thd)) + if (trans_begin(thd, lex->start_transaction_opt)) goto error; - if (lex->start_transaction_opt & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT) - { - if (ha_start_consistent_snapshot(thd)) - goto error; - } my_ok(thd); break; case SQLCOM_COMMIT: DBUG_ASSERT(thd->lock == NULL || thd->locked_tables_mode == LTM_LOCK_TABLES); - if (end_trans(thd, lex->tx_release ? COMMIT_RELEASE : - lex->tx_chain ? COMMIT_AND_CHAIN : COMMIT)) + if (trans_commit(thd)) + goto error; + /* Begin transaction with the same isolation level. */ + if (lex->tx_chain && trans_begin(thd)) goto error; + /* Disconnect the current client connection. */ + if (lex->tx_release) + thd->killed= THD::KILL_CONNECTION; my_ok(thd); break; case SQLCOM_ROLLBACK: DBUG_ASSERT(thd->lock == NULL || thd->locked_tables_mode == LTM_LOCK_TABLES); - if (end_trans(thd, lex->tx_release ? ROLLBACK_RELEASE : - lex->tx_chain ? ROLLBACK_AND_CHAIN : ROLLBACK)) + if (trans_rollback(thd)) + goto error; + /* Begin transaction with the same isolation level. */ + if (lex->tx_chain && trans_begin(thd)) goto error; + /* Disconnect the current client connection. */ + if (lex->tx_release) + thd->killed= THD::KILL_CONNECTION; my_ok(thd); break; case SQLCOM_RELEASE_SAVEPOINT: - { - SAVEPOINT *sv; - for (sv=thd->transaction.savepoints; sv; sv=sv->prev) - { - if (my_strnncoll(system_charset_info, - (uchar *)lex->ident.str, lex->ident.length, - (uchar *)sv->name, sv->length) == 0) - break; - } - if (sv) - { - if (ha_release_savepoint(thd, sv)) - res= TRUE; // cannot happen - else - my_ok(thd); - thd->transaction.savepoints=sv->prev; - } - else - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", lex->ident.str); + if (trans_release_savepoint(thd, lex->ident)) + goto error; + my_ok(thd); break; - } case SQLCOM_ROLLBACK_TO_SAVEPOINT: - { - SAVEPOINT *sv; - for (sv=thd->transaction.savepoints; sv; sv=sv->prev) - { - if (my_strnncoll(system_charset_info, - (uchar *)lex->ident.str, lex->ident.length, - (uchar *)sv->name, sv->length) == 0) - break; - } - if (sv) - { - if (ha_rollback_to_savepoint(thd, sv)) - res= TRUE; // cannot happen - else - { - 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, - ER(ER_WARNING_NOT_COMPLETE_ROLLBACK)); - my_ok(thd); - } - thd->transaction.savepoints=sv; - } - else - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", lex->ident.str); + if (trans_rollback_to_savepoint(thd, lex->ident)) + goto error; + my_ok(thd); break; - } case SQLCOM_SAVEPOINT: - if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) || - thd->in_sub_stmt) || !opt_using_transactions) - my_ok(thd); - else - { - SAVEPOINT **sv, *newsv; - for (sv=&thd->transaction.savepoints; *sv; sv=&(*sv)->prev) - { - if (my_strnncoll(system_charset_info, - (uchar *)lex->ident.str, lex->ident.length, - (uchar *)(*sv)->name, (*sv)->length) == 0) - break; - } - if (*sv) /* old savepoint of the same name exists */ - { - newsv=*sv; - ha_release_savepoint(thd, *sv); // it cannot fail - *sv=(*sv)->prev; - } - else if ((newsv=(SAVEPOINT *) alloc_root(&thd->transaction.mem_root, - savepoint_alloc_size)) == 0) - { - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - break; - } - newsv->name=strmake_root(&thd->transaction.mem_root, - lex->ident.str, lex->ident.length); - newsv->length=lex->ident.length; - /* - if we'll get an error here, don't add new savepoint to the list. - we'll lose a little bit of memory in transaction mem_root, but it'll - be free'd when transaction ends anyway - */ - if (ha_savepoint(thd, newsv)) - res= TRUE; - else - { - newsv->prev=thd->transaction.savepoints; - thd->transaction.savepoints=newsv; - my_ok(thd); - } - } + if (trans_savepoint(thd, lex->ident)) + goto error; + my_ok(thd); break; case SQLCOM_CREATE_PROCEDURE: case SQLCOM_CREATE_SPFUNCTION: @@ -4456,7 +4194,7 @@ create_sp_error: lex->sql_command == SQLCOM_DROP_PROCEDURE, 0)) goto error; - if (end_active_trans(thd)) + if (trans_commit_implicit(thd)) goto error; #ifndef NO_EMBEDDED_ACCESS_CHECKS if (sp_automatic_privileges && !opt_noacl && @@ -4620,185 +4358,29 @@ create_sp_error: break; } case SQLCOM_XA_START: - if (thd->transaction.xid_state.xa_state == XA_IDLE && - thd->lex->xa_opt == XA_RESUME) - { - if (! thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - my_error(ER_XAER_NOTA, MYF(0)); - break; - } - thd->transaction.xid_state.xa_state=XA_ACTIVE; - my_ok(thd); - break; - } - if (thd->lex->xa_opt != XA_NONE) - { // JOIN is not supported yet. TODO - my_error(ER_XAER_INVAL, MYF(0)); - break; - } - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (thd->locked_tables_mode || thd->active_transaction()) - { - my_error(ER_XAER_OUTSIDE, MYF(0)); - break; - } - if (xid_cache_search(thd->lex->xid)) - { - my_error(ER_XAER_DUPID, MYF(0)); - break; - } - DBUG_ASSERT(thd->transaction.xid_state.xid.is_null()); - thd->transaction.xid_state.xa_state=XA_ACTIVE; - thd->transaction.xid_state.rm_error= 0; - 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->server_status|= SERVER_STATUS_IN_TRANS; + if (trans_xa_start(thd)) + goto error; my_ok(thd); break; case SQLCOM_XA_END: - /* fake it */ - if (thd->lex->xa_opt != XA_NONE) - { // SUSPEND and FOR MIGRATE are not supported yet. TODO - my_error(ER_XAER_INVAL, MYF(0)); - break; - } - if (thd->transaction.xid_state.xa_state != XA_ACTIVE) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - my_error(ER_XAER_NOTA, MYF(0)); - break; - } - if (xa_trans_rolled_back(&thd->transaction.xid_state)) - break; - thd->transaction.xid_state.xa_state=XA_IDLE; + if (trans_xa_end(thd)) + goto error; my_ok(thd); break; case SQLCOM_XA_PREPARE: - if (thd->transaction.xid_state.xa_state != XA_IDLE) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - my_error(ER_XAER_NOTA, MYF(0)); - break; - } - if (ha_prepare(thd)) - { - my_error(ER_XA_RBROLLBACK, MYF(0)); - xid_cache_delete(&thd->transaction.xid_state); - thd->transaction.xid_state.xa_state=XA_NOTR; - break; - } - thd->transaction.xid_state.xa_state=XA_PREPARED; + if (trans_xa_prepare(thd)) + goto error; my_ok(thd); break; case SQLCOM_XA_COMMIT: - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - XID_STATE *xs=xid_cache_search(thd->lex->xid); - if (!xs || xs->in_thd) - my_error(ER_XAER_NOTA, MYF(0)); - else if (xa_trans_rolled_back(xs)) - { - ha_commit_or_rollback_by_xid(thd->lex->xid, 0); - xid_cache_delete(xs); - break; - } - else - { - ha_commit_or_rollback_by_xid(thd->lex->xid, 1); - xid_cache_delete(xs); - my_ok(thd); - } - break; - } - if (xa_trans_rolled_back(&thd->transaction.xid_state)) - { - xa_trans_rollback(thd); - break; - } - if (thd->transaction.xid_state.xa_state == XA_IDLE && - thd->lex->xa_opt == XA_ONE_PHASE) - { - int r; - if ((r= ha_commit(thd))) - my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0)); - else - my_ok(thd); - } - else if (thd->transaction.xid_state.xa_state == XA_PREPARED && - thd->lex->xa_opt == XA_NONE) - { - if (wait_if_global_read_lock(thd, 0, 0)) - { - ha_rollback(thd); - my_error(ER_XAER_RMERR, MYF(0)); - } - else - { - if (ha_commit_one_phase(thd, 1)) - my_error(ER_XAER_RMERR, MYF(0)); - else - my_ok(thd); - start_waiting_global_read_lock(thd); - } - } - else - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - 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; + if (trans_xa_commit(thd)) + goto error; + my_ok(thd); break; case SQLCOM_XA_ROLLBACK: - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - XID_STATE *xs=xid_cache_search(thd->lex->xid); - if (!xs || xs->in_thd) - my_error(ER_XAER_NOTA, MYF(0)); - else - { - bool ok= !xa_trans_rolled_back(xs); - ha_commit_or_rollback_by_xid(thd->lex->xid, 0); - xid_cache_delete(xs); - if (ok) - my_ok(thd); - } - break; - } - if (thd->transaction.xid_state.xa_state != XA_IDLE && - thd->transaction.xid_state.xa_state != XA_PREPARED && - thd->transaction.xid_state.xa_state != XA_ROLLBACK_ONLY) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (xa_trans_rollback(thd)) - my_error(ER_XAER_RMERR, MYF(0)); - else - my_ok(thd); + if (trans_xa_rollback(thd)) + goto error; + my_ok(thd); break; case SQLCOM_XA_RECOVER: res= mysql_xa_recover(thd); diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index f4525db7def..e0461533dde 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -50,6 +50,7 @@ #include <errno.h> #include <m_ctype.h> #include "my_md5.h" +#include "transaction.h" #ifdef WITH_PARTITION_STORAGE_ENGINE #include "ha_partition.h" @@ -4327,8 +4328,8 @@ static int fast_end_partition(THD *thd, ulonglong copied, if (!is_empty) query_cache_invalidate3(thd, table_list, 0); - error= ha_autocommit_or_rollback(thd, 0); - if (end_active_trans(thd)) + error= trans_commit_stmt(thd); + if (trans_commit_implicit(thd)) error= 1; if (error) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 9f486768043..d6a592c4799 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -22,6 +22,7 @@ #include "sp_head.h" #include "sql_trigger.h" #include "sql_show.h" +#include "transaction.h" #ifdef __WIN__ #include <io.h> @@ -4668,8 +4669,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, DBUG_PRINT("admin", ("calling prepare_func")); switch ((*prepare_func)(thd, table, check_opt)) { case 1: // error, message written to net - ha_autocommit_or_rollback(thd, 1); - end_trans(thd, ROLLBACK); + trans_rollback_stmt(thd); + trans_rollback(thd); close_thread_tables(thd); DBUG_PRINT("admin", ("simple error, admin next table")); continue; @@ -4740,8 +4741,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, length= my_snprintf(buff, sizeof(buff), ER(ER_OPEN_AS_READONLY), table_name); protocol->store(buff, length, system_charset_info); - ha_autocommit_or_rollback(thd, 0); - end_trans(thd, COMMIT); + trans_commit_stmt(thd); + trans_commit(thd); close_thread_tables(thd); lex->reset_query_tables_list(FALSE); table->table=0; // For query cache @@ -4790,7 +4791,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, HA_ADMIN_NEEDS_ALTER)) { DBUG_PRINT("admin", ("recreating table")); - ha_autocommit_or_rollback(thd, 1); + trans_rollback_stmt(thd); close_thread_tables(thd); tmp_disable_binlog(thd); // binlogging is done by caller if wanted result_code= mysql_recreate_table(thd, table); @@ -4910,7 +4911,7 @@ send_result_message: system_charset_info); if (protocol->write()) goto err; - ha_autocommit_or_rollback(thd, 0); + trans_commit_stmt(thd); close_thread_tables(thd); DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze...")); TABLE_LIST *save_next_local= table->next_local, @@ -4927,7 +4928,7 @@ send_result_message: */ if (thd->stmt_da->is_ok()) thd->stmt_da->reset_diagnostics_area(); - ha_autocommit_or_rollback(thd, 0); + trans_commit_stmt(thd); close_thread_tables(thd); if (!result_code) // recreation went ok { @@ -5022,8 +5023,8 @@ send_result_message: query_cache_invalidate3(thd, table->table, 0); } } - ha_autocommit_or_rollback(thd, 0); - end_trans(thd, COMMIT); + trans_commit_stmt(thd); + trans_commit_implicit(thd); close_thread_tables(thd); table->table=0; // For query cache if (protocol->write()) @@ -5034,8 +5035,8 @@ send_result_message: DBUG_RETURN(FALSE); err: - ha_autocommit_or_rollback(thd, 1); - end_trans(thd, ROLLBACK); + trans_rollback_stmt(thd); + trans_rollback(thd); close_thread_tables(thd); // Shouldn't be needed if (table) table->table=0; @@ -5550,15 +5551,15 @@ mysql_discard_or_import_tablespace(THD *thd, query_cache_invalidate3(thd, table_list, 0); /* The ALTER TABLE is always in its own transaction */ - error = ha_autocommit_or_rollback(thd, 0); - if (end_active_trans(thd)) + error= trans_commit_stmt(thd); + if (trans_commit_implicit(thd)) error=1; if (error) goto err; write_bin_log(thd, FALSE, thd->query(), thd->query_length()); err: - ha_autocommit_or_rollback(thd, error); + trans_rollback_stmt(thd); thd->tablespace_op=FALSE; if (error == 0) @@ -7208,8 +7209,8 @@ view_err: thd_proc_info(thd, "manage keys"); alter_table_manage_keys(table, table->file->indexes_are_disabled(), alter_info->keys_onoff); - error= ha_autocommit_or_rollback(thd, 0); - if (end_active_trans(thd)) + error= trans_commit_stmt(thd); + if (trans_commit_implicit(thd)) error= 1; } thd->count_cuted_fields= CHECK_FIELD_IGNORE; @@ -7297,7 +7298,7 @@ view_err: /* Need to commit before a table is unlocked (NDB requirement). */ DBUG_PRINT("info", ("Committing before unlocking table")); - if (ha_autocommit_or_rollback(thd, 0) || end_active_trans(thd)) + if (trans_commit_stmt(thd) || trans_commit_implicit(thd)) goto err_new_table_cleanup; committed= 1; } @@ -7803,9 +7804,9 @@ copy_data_between_tables(TABLE *from,TABLE *to, Ensure that the new table is saved properly to disk so that we can do a rename */ - if (ha_autocommit_or_rollback(thd, 0)) + if (trans_commit_stmt(thd)) error=1; - if (end_active_trans(thd)) + if (trans_commit_implicit(thd)) error=1; err: diff --git a/sql/transaction.cc b/sql/transaction.cc new file mode 100644 index 00000000000..f4a616ead6b --- /dev/null +++ b/sql/transaction.cc @@ -0,0 +1,648 @@ +/* Copyright (C) 2008 Sun/MySQL + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "mysql_priv.h" +#include "transaction.h" +#include "rpl_handler.h" + +/* Conditions under which the transaction state must not change. */ +static bool trans_check(THD *thd) +{ + enum xa_states xa_state= thd->transaction.xid_state.xa_state; + DBUG_ENTER("trans_check"); + + if (unlikely(thd->in_sub_stmt)) + my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); + if (xa_state != XA_NOTR) + my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); + else + DBUG_RETURN(FALSE); + + DBUG_RETURN(TRUE); +} + + +/** + Mark a XA transaction as rollback-only if the RM unilaterally + rolled back the transaction branch. + + @note If a rollback was requested by the RM, this function sets + the appropriate rollback error code and transits the state + to XA_ROLLBACK_ONLY. + + @return TRUE if transaction was rolled back or if the transaction + state is XA_ROLLBACK_ONLY. FALSE otherwise. +*/ +static bool xa_trans_rolled_back(XID_STATE *xid_state) +{ + if (xid_state->rm_error) + { + switch (xid_state->rm_error) { + case ER_LOCK_WAIT_TIMEOUT: + my_error(ER_XA_RBTIMEOUT, MYF(0)); + break; + case ER_LOCK_DEADLOCK: + my_error(ER_XA_RBDEADLOCK, MYF(0)); + break; + default: + my_error(ER_XA_RBROLLBACK, MYF(0)); + } + xid_state->xa_state= XA_ROLLBACK_ONLY; + } + + return (xid_state->xa_state == XA_ROLLBACK_ONLY); +} + + +/** + Begin a new transaction. + + @note Beginning a transaction implicitly commits any current + transaction and releases existing locks. + + @param thd Current thread + @param flags Transaction flags + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_begin(THD *thd, uint flags) +{ + int res= FALSE; + DBUG_ENTER("trans_begin"); + + if (trans_check(thd)) + DBUG_RETURN(TRUE); + + thd->locked_tables_list.unlock_locked_tables(thd); + + DBUG_ASSERT(!thd->locked_tables_mode); + + if (trans_commit_implicit(thd)) + DBUG_RETURN(TRUE); + + thd->options|= OPTION_BEGIN; + thd->server_status|= SERVER_STATUS_IN_TRANS; + + if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT) + res= ha_start_consistent_snapshot(thd); + + DBUG_RETURN(test(res)); +} + + +/** + Commit the current transaction, making its changes permanent. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_commit(THD *thd) +{ + int res; + DBUG_ENTER("trans_commit"); + + if (trans_check(thd)) + DBUG_RETURN(TRUE); + + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + res= ha_commit_trans(thd, TRUE); + if (res) + /* + if res is non-zero, then ha_commit_trans has rolled back the + transaction, so the hooks for rollback will be called. + */ + RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + else + RUN_HOOK(transaction, after_commit, (thd, FALSE)); + thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + thd->lex->start_transaction_opt= 0; + + DBUG_RETURN(test(res)); +} + + +/** + Implicitly commit the current transaction. + + @note A implicit commit does not releases existing table locks. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_commit_implicit(THD *thd) +{ + bool res= FALSE; + DBUG_ENTER("trans_commit_implicit"); + + if (trans_check(thd)) + DBUG_RETURN(TRUE); + + if (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN | + OPTION_TABLE_LOCK)) + { + /* Safety if one did "drop table" on locked tables */ + if (!thd->locked_tables_mode) + thd->options&= ~OPTION_TABLE_LOCK; + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + res= test(ha_commit_trans(thd, TRUE)); + } + + thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + + DBUG_RETURN(res); +} + + +/** + Rollback the current transaction, canceling its changes. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_rollback(THD *thd) +{ + int res; + DBUG_ENTER("trans_rollback"); + + if (trans_check(thd)) + DBUG_RETURN(TRUE); + + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + res= ha_rollback_trans(thd, TRUE); + RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + thd->lex->start_transaction_opt= 0; + + DBUG_RETURN(test(res)); +} + + +/** + Commit the single statement transaction. + + @note Note that if the autocommit is on, then the following call + inside InnoDB will commit or rollback the whole transaction + (= the statement). The autocommit mechanism built into InnoDB + is based on counting locks, but if the user has used LOCK + TABLES then that mechanism does not know to do the commit. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_commit_stmt(THD *thd) +{ + DBUG_ENTER("trans_commit_stmt"); + int res= FALSE; + if (thd->transaction.stmt.ha_list) + res= ha_commit_trans(thd, FALSE); + + if (res) + /* + if res is non-zero, then ha_commit_trans has rolled back the + transaction, so the hooks for rollback will be called. + */ + RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + else + RUN_HOOK(transaction, after_commit, (thd, FALSE)); + DBUG_RETURN(test(res)); +} + + +/** + Rollback the single statement transaction. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ +bool trans_rollback_stmt(THD *thd) +{ + DBUG_ENTER("trans_rollback_stmt"); + + if (thd->transaction.stmt.ha_list) + { + ha_rollback_trans(thd, FALSE); + if (thd->transaction_rollback_request && !thd->in_sub_stmt) + ha_rollback_trans(thd, TRUE); + } + + RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + + DBUG_RETURN(FALSE); +} + +/* Find a named savepoint in the current transaction. */ +static SAVEPOINT ** +find_savepoint(THD *thd, LEX_STRING name) +{ + SAVEPOINT **sv= &thd->transaction.savepoints; + + while (*sv) + { + if (my_strnncoll(system_charset_info, (uchar *) name.str, name.length, + (uchar *) (*sv)->name, (*sv)->length) == 0) + break; + sv= &(*sv)->prev; + } + + return sv; +} + + +/** + Set a named transaction savepoint. + + @param thd Current thread + @param name Savepoint name + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_savepoint(THD *thd, LEX_STRING name) +{ + SAVEPOINT **sv, *newsv; + DBUG_ENTER("trans_savepoint"); + + if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) || + thd->in_sub_stmt) || !opt_using_transactions) + DBUG_RETURN(FALSE); + + sv= find_savepoint(thd, name); + + if (*sv) /* old savepoint of the same name exists */ + { + newsv= *sv; + ha_release_savepoint(thd, *sv); + *sv= (*sv)->prev; + } + else if ((newsv= (SAVEPOINT *) alloc_root(&thd->transaction.mem_root, + savepoint_alloc_size)) == NULL) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + DBUG_RETURN(TRUE); + } + + newsv->name= strmake_root(&thd->transaction.mem_root, name.str, name.length); + newsv->length= name.length; + + /* + if we'll get an error here, don't add new savepoint to the list. + we'll lose a little bit of memory in transaction mem_root, but it'll + be free'd when transaction ends anyway + */ + if (ha_savepoint(thd, newsv)) + DBUG_RETURN(TRUE); + + newsv->prev= thd->transaction.savepoints; + thd->transaction.savepoints= newsv; + + DBUG_RETURN(FALSE); +} + + +/** + Rollback a transaction to the named savepoint. + + @note Modifications that the current transaction made to + rows after the savepoint was set are undone in the + rollback. + + @note Savepoints that were set at a later time than the + named savepoint are deleted. + + @param thd Current thread + @param name Savepoint name + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name) +{ + int res= FALSE; + SAVEPOINT *sv= *find_savepoint(thd, name); + DBUG_ENTER("trans_rollback_to_savepoint"); + + if (sv == NULL) + { + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", name.str); + DBUG_RETURN(TRUE); + } + + if (ha_rollback_to_savepoint(thd, sv)) + res= TRUE; + else 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, + ER(ER_WARNING_NOT_COMPLETE_ROLLBACK)); + + thd->transaction.savepoints= sv; + + DBUG_RETURN(test(res)); +} + + +/** + Remove the named savepoint from the set of savepoints of + the current transaction. + + @note No commit or rollback occurs. It is an error if the + savepoint does not exist. + + @param thd Current thread + @param name Savepoint name + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_release_savepoint(THD *thd, LEX_STRING name) +{ + int res= FALSE; + SAVEPOINT *sv= *find_savepoint(thd, name); + DBUG_ENTER("trans_release_savepoint"); + + if (sv == NULL) + { + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", name.str); + DBUG_RETURN(TRUE); + } + + if (ha_release_savepoint(thd, sv)) + res= TRUE; + + thd->transaction.savepoints= sv->prev; + + DBUG_RETURN(test(res)); +} + + +/** + Starts an XA transaction with the given xid value. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_start(THD *thd) +{ + enum xa_states xa_state= thd->transaction.xid_state.xa_state; + DBUG_ENTER("trans_xa_start"); + + if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_RESUME) + { + bool not_equal= !thd->transaction.xid_state.xid.eq(thd->lex->xid); + if (not_equal) + my_error(ER_XAER_NOTA, MYF(0)); + else + thd->transaction.xid_state.xa_state= XA_ACTIVE; + DBUG_RETURN(not_equal); + } + + /* TODO: JOIN is not supported yet. */ + if (thd->lex->xa_opt != XA_NONE) + my_error(ER_XAER_INVAL, MYF(0)); + else if (xa_state != XA_NOTR) + my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); + else if (thd->locked_tables_mode || thd->active_transaction()) + my_error(ER_XAER_OUTSIDE, MYF(0)); + else if (xid_cache_search(thd->lex->xid)) + my_error(ER_XAER_DUPID, MYF(0)); + else if (!trans_begin(thd)) + { + DBUG_ASSERT(thd->transaction.xid_state.xid.is_null()); + thd->transaction.xid_state.xa_state= XA_ACTIVE; + thd->transaction.xid_state.rm_error= 0; + thd->transaction.xid_state.xid.set(thd->lex->xid); + xid_cache_insert(&thd->transaction.xid_state); + DBUG_RETURN(FALSE); + } + + DBUG_RETURN(TRUE); +} + + +/** + Put a XA transaction in the IDLE state. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_end(THD *thd) +{ + DBUG_ENTER("trans_xa_end"); + + /* TODO: SUSPEND and FOR MIGRATE are not supported yet. */ + if (thd->lex->xa_opt != XA_NONE) + my_error(ER_XAER_INVAL, MYF(0)); + else if (thd->transaction.xid_state.xa_state != XA_ACTIVE) + my_error(ER_XAER_RMFAIL, MYF(0), + xa_state_names[thd->transaction.xid_state.xa_state]); + else if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) + my_error(ER_XAER_NOTA, MYF(0)); + else if (!xa_trans_rolled_back(&thd->transaction.xid_state)) + thd->transaction.xid_state.xa_state= XA_IDLE; + + DBUG_RETURN(thd->transaction.xid_state.xa_state != XA_IDLE); +} + + +/** + Put a XA transaction in the PREPARED state. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_prepare(THD *thd) +{ + DBUG_ENTER("trans_xa_prepare"); + + if (thd->transaction.xid_state.xa_state != XA_IDLE) + my_error(ER_XAER_RMFAIL, MYF(0), + xa_state_names[thd->transaction.xid_state.xa_state]); + else if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) + my_error(ER_XAER_NOTA, MYF(0)); + else if (ha_prepare(thd)) + { + xid_cache_delete(&thd->transaction.xid_state); + thd->transaction.xid_state.xa_state= XA_NOTR; + my_error(ER_XA_RBROLLBACK, MYF(0)); + } + else + thd->transaction.xid_state.xa_state= XA_PREPARED; + + DBUG_RETURN(thd->transaction.xid_state.xa_state != XA_PREPARED); +} + + +/** + Commit and terminate the a XA transaction. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_commit(THD *thd) +{ + bool res= TRUE; + enum xa_states xa_state= thd->transaction.xid_state.xa_state; + DBUG_ENTER("trans_xa_commit"); + + if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) + { + XID_STATE *xs= xid_cache_search(thd->lex->xid); + res= !xs || xs->in_thd; + if (res) + my_error(ER_XAER_NOTA, MYF(0)); + else + { + res= xa_trans_rolled_back(xs); + ha_commit_or_rollback_by_xid(thd->lex->xid, !res); + xid_cache_delete(xs); + } + DBUG_RETURN(res); + } + + if (xa_trans_rolled_back(&thd->transaction.xid_state)) + { + if ((res= test(ha_rollback_trans(thd, TRUE)))) + my_error(ER_XAER_RMERR, MYF(0)); + } + else if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_ONE_PHASE) + { + int r= ha_commit_trans(thd, TRUE); + if ((res= test(r))) + my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0)); + } + else if (xa_state == XA_PREPARED && thd->lex->xa_opt == XA_NONE) + { + if (wait_if_global_read_lock(thd, 0, 0)) + { + ha_rollback_trans(thd, TRUE); + my_error(ER_XAER_RMERR, MYF(0)); + } + else + { + res= test(ha_commit_one_phase(thd, 1)); + if (res) + my_error(ER_XAER_RMERR, MYF(0)); + start_waiting_global_read_lock(thd); + } + } + else + { + my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); + DBUG_RETURN(TRUE); + } + + thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + 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; + + DBUG_RETURN(res); +} + + +/** + Roll back and terminate a XA transaction. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_rollback(THD *thd) +{ + bool res= TRUE; + enum xa_states xa_state= thd->transaction.xid_state.xa_state; + DBUG_ENTER("trans_xa_rollback"); + + if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) + { + XID_STATE *xs= xid_cache_search(thd->lex->xid); + if (!xs || xs->in_thd) + my_error(ER_XAER_NOTA, MYF(0)); + else + { + xa_trans_rolled_back(xs); + ha_commit_or_rollback_by_xid(thd->lex->xid, 0); + xid_cache_delete(xs); + } + DBUG_RETURN(thd->stmt_da->is_error()); + } + + if (xa_state != XA_IDLE && xa_state != XA_PREPARED && xa_state != XA_ROLLBACK_ONLY) + { + my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); + DBUG_RETURN(TRUE); + } + + /* + Resource Manager error is meaningless at this point, as we perform + explicit rollback request by user. We must reset rm_error before + calling ha_rollback(), so thd->transaction.xid structure gets reset + by ha_rollback()/THD::transaction::cleanup(). + */ + thd->transaction.xid_state.rm_error= 0; + if ((res= test(ha_rollback_trans(thd, TRUE)))) + my_error(ER_XAER_RMERR, MYF(0)); + + thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + 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; + + DBUG_RETURN(res); +} diff --git a/sql/transaction.h b/sql/transaction.h new file mode 100644 index 00000000000..135e6cae73f --- /dev/null +++ b/sql/transaction.h @@ -0,0 +1,46 @@ +/* Copyright (C) 2008 Sun/MySQL + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef TRANSACTION_H +#define TRANSACTION_H + +#ifdef USE_PRAGMA_INTERFACE +#pragma interface /* gcc class implementation */ +#endif + +#include <my_global.h> +#include <m_string.h> + +class THD; + +bool trans_begin(THD *thd, uint flags= 0); +bool trans_commit(THD *thd); +bool trans_commit_implicit(THD *thd); +bool trans_rollback(THD *thd); + +bool trans_commit_stmt(THD *thd); +bool trans_rollback_stmt(THD *thd); + +bool trans_savepoint(THD *thd, LEX_STRING name); +bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name); +bool trans_release_savepoint(THD *thd, LEX_STRING name); + +bool trans_xa_start(THD *thd); +bool trans_xa_end(THD *thd); +bool trans_xa_prepare(THD *thd); +bool trans_xa_commit(THD *thd); +bool trans_xa_rollback(THD *thd); + +#endif /* TRANSACTION_H */ |