summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Osipov <kostja@sun.com>2009-12-03 21:37:38 +0300
committerKonstantin Osipov <kostja@sun.com>2009-12-03 21:37:38 +0300
commitc52d4830bf167cfe0b536be28a0adca121ae5d72 (patch)
treee72864841720da65102a5acd0ba5cc908cc8dec8
parent6a5bbd4bdc5535d524b7e1f009fd81b2cc21544c (diff)
downloadmariadb-git-c52d4830bf167cfe0b536be28a0adca121ae5d72.tar.gz
Backport of:
------------------------------------------------------------ revno: 2630.22.3 committer: Davi Arnaut <Davi.Arnaut@Sun.COM> branch nick: 4284-6.0 timestamp: Thu 2008-08-07 22:33:43 -0300 message: WL#4284: Transactional DDL locking Make transaction management more modular through a new interface. The overall objective of this change is to provide groundwork for the design of transactional DDL locking by cleaning up the transaction high level API to better distinguish operations implicit and explicit, and single statement transaction from operations on the normal transaction. Having a a high-level interface for transaction management provides a better base for implementing transactional concepts that are not always tied to storage engines and also makes it easier to interect with other higher level modules of the server. client/Makefile.am: Add new file to the build. libmysqld/CMakeLists.txt: Add new file to the build. libmysqld/Makefile.am: Add new file to the build. sql/CMakeLists.txt: Add new file to the build. sql/Makefile.am: Add new file to the build. sql/handler.cc: Remove multiplexer commit or rollback function. Most callers already have enough information to decided whether to rollback or commit. Having plain and well named functions makes it easier to read and understand code. sql/handler.h: Remove wrapper function as the low level transaction functions shouldn't be called directly anymore. sql/log_event.cc: Rename transaction management functions to the new names. sql/log_event_old.cc: Rename transaction management functions to the new names. sql/mysql_priv.h: Remove obsolete functions for implicit and explicit commit. sql/rpl_injector.cc: Rename transaction management functions to the new names. sql/rpl_rli.cc: Rename transaction management functions to the new names. sql/set_var.cc: Rename transaction management functions to the new names. sql/slave.cc: Rename transaction management functions to the new names. sql/sql_base.cc: Rename transaction management functions to the new names. sql/sql_class.cc: Rename transaction management functions to the new names. sql/sql_delete.cc: Rename transaction management functions to the new names. sql/sql_do.cc: Rename transaction management functions to the new names. sql/sql_insert.cc: Rename transaction management functions to the new names. sql/sql_parse.cc: Rename transaction management functions to the new names. sql/sql_partition.cc: Rename transaction management functions to the new names. sql/sql_table.cc: Rename transaction management functions to the new names. sql/transaction.cc: Implement wrapper functions to differentiate operations on the single statement transaction from the ones operating on the normal transaction. sql/transaction.h: Export new functions for dealing with transaction commands.
-rw-r--r--client/Makefile.am3
-rw-r--r--libmysqld/CMakeLists.txt2
-rw-r--r--libmysqld/Makefile.am3
-rwxr-xr-xsql/CMakeLists.txt1
-rw-r--r--sql/Makefile.am4
-rw-r--r--sql/handler.cc66
-rw-r--r--sql/handler.h7
-rw-r--r--sql/log_event.cc10
-rw-r--r--sql/log_event_old.cc3
-rw-r--r--sql/mysql_priv.h9
-rw-r--r--sql/rpl_injector.cc7
-rw-r--r--sql/rpl_rli.cc5
-rw-r--r--sql/set_var.cc3
-rw-r--r--sql/slave.cc3
-rw-r--r--sql/sql_base.cc3
-rw-r--r--sql/sql_class.cc4
-rw-r--r--sql/sql_delete.cc5
-rw-r--r--sql/sql_do.cc3
-rw-r--r--sql/sql_insert.cc9
-rw-r--r--sql/sql_parse.cc512
-rw-r--r--sql/sql_partition.cc5
-rw-r--r--sql/sql_table.cc39
-rw-r--r--sql/transaction.cc648
-rw-r--r--sql/transaction.h46
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, &not_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 */