mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 12:02:42 +01:00
649 lines
16 KiB
C++
649 lines
16 KiB
C++
|
/* 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);
|
||
|
}
|