749 lines
17 KiB
C++

/*
Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
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,
51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
#ifndef XA_H_INCLUDED
#define XA_H_INCLUDED
#include "my_global.h" // ulonglong
#include "mysql/plugin.h" // MYSQL_XIDDATASIZE
#include "mysqld.h" // server_id
#include "sql_cmd.h"
#include "sql_plugin_ref.h" // plugin_ref
#include <string.h>
#include "xa_aux.h"
class Protocol;
class THD;
struct xid_t;
enum xa_option_words {XA_NONE, XA_JOIN, XA_RESUME, XA_ONE_PHASE,
XA_SUSPEND, XA_FOR_MIGRATE};
static const int TC_HEURISTIC_NOT_USED= 0;
static const int TC_HEURISTIC_RECOVER_COMMIT= 1;
static const int TC_HEURISTIC_RECOVER_ROLLBACK= 2;
/**
This class represents SQL statement which starts an XA transaction
with the given xid value.
*/
class Sql_cmd_xa_start : public Sql_cmd
{
public:
Sql_cmd_xa_start(xid_t *xid_arg, enum xa_option_words xa_option)
: m_xid(xid_arg), m_xa_opt(xa_option)
{}
virtual enum_sql_command sql_command_code() const
{
return SQLCOM_XA_START;
}
virtual bool execute(THD *thd);
private:
bool trans_xa_start(THD *thd);
xid_t *m_xid;
enum xa_option_words m_xa_opt;
};
/**
This class represents SQL statement which puts in the IDLE state
an XA transaction with the given xid value.
*/
class Sql_cmd_xa_end : public Sql_cmd
{
public:
Sql_cmd_xa_end(xid_t *xid_arg, enum xa_option_words xa_option)
: m_xid(xid_arg), m_xa_opt(xa_option)
{}
virtual enum_sql_command sql_command_code() const
{
return SQLCOM_XA_END;
}
virtual bool execute(THD *thd);
private:
bool trans_xa_end(THD *thd);
xid_t *m_xid;
enum xa_option_words m_xa_opt;
};
/**
This class represents SQL statement which puts in the PREPARED state
an XA transaction with the given xid value.
*/
class Sql_cmd_xa_prepare : public Sql_cmd
{
public:
explicit Sql_cmd_xa_prepare(xid_t *xid_arg)
: m_xid(xid_arg)
{}
virtual enum_sql_command sql_command_code() const
{
return SQLCOM_XA_PREPARE;
}
virtual bool execute(THD *thd);
private:
bool trans_xa_prepare(THD *thd);
xid_t *m_xid;
};
/**
This class represents SQL statement which returns to a client
a list of XID's prepared to a XA commit/rollback.
*/
class Sql_cmd_xa_recover : public Sql_cmd
{
public:
explicit Sql_cmd_xa_recover(bool print_xid_as_hex)
: m_print_xid_as_hex(print_xid_as_hex)
{}
virtual enum_sql_command sql_command_code() const
{
return SQLCOM_XA_RECOVER;
}
virtual bool execute(THD *thd);
private:
bool trans_xa_recover(THD *thd);
bool m_print_xid_as_hex;
};
/**
This class represents SQL statement which commits
and terminates an XA transaction with the given xid value.
*/
class Sql_cmd_xa_commit : public Sql_cmd
{
public:
Sql_cmd_xa_commit(xid_t *xid_arg, enum xa_option_words xa_option)
: m_xid(xid_arg), m_xa_opt(xa_option)
{}
virtual enum_sql_command sql_command_code() const
{
return SQLCOM_XA_COMMIT;
}
virtual bool execute(THD *thd);
enum xa_option_words get_xa_opt() const
{
return m_xa_opt;
}
private:
bool trans_xa_commit(THD *thd);
xid_t *m_xid;
enum xa_option_words m_xa_opt;
};
/**
This class represents SQL statement which rollbacks and
terminates an XA transaction with the given xid value.
*/
class Sql_cmd_xa_rollback : public Sql_cmd
{
public:
explicit Sql_cmd_xa_rollback(xid_t *xid_arg)
: m_xid(xid_arg)
{}
virtual enum_sql_command sql_command_code() const
{
return SQLCOM_XA_ROLLBACK;
}
virtual bool execute(THD *thd);
private:
bool trans_xa_rollback(THD *thd);
xid_t *m_xid;
};
typedef ulonglong my_xid; // this line is the same as in log_event.h
#define MYSQL_XID_PREFIX "MySQLXid"
#define XIDDATASIZE MYSQL_XIDDATASIZE
class XID_STATE;
/**
struct xid_t is binary compatible with the XID structure as
in the X/Open CAE Specification, Distributed Transaction Processing:
The XA Specification, X/Open Company Ltd., 1991.
http://www.opengroup.org/bookstore/catalog/c193.htm
@see MYSQL_XID in mysql/plugin.h
*/
typedef struct xid_t
{
private:
static const uint MYSQL_XID_PREFIX_LEN= 8; // must be a multiple of 8
static const uint MYSQL_XID_OFFSET= MYSQL_XID_PREFIX_LEN + sizeof(server_id);
static const uint MYSQL_XID_GTRID_LEN= MYSQL_XID_OFFSET + sizeof(my_xid);
/**
-1 means that the XID is null
*/
long formatID;
/**
value from 1 through 64
*/
long gtrid_length;
/**
value from 1 through 64
*/
long bqual_length;
/**
distributed trx identifier. not \0-terminated.
*/
char data[XIDDATASIZE];
public:
xid_t()
: formatID(-1),
gtrid_length(0),
bqual_length(0)
{
memset(data, 0, XIDDATASIZE);
}
long get_format_id() const
{
return formatID;
}
void set_format_id(long v)
{
formatID= v;
}
long get_gtrid_length() const
{
return gtrid_length;
}
void set_gtrid_length(long v)
{
gtrid_length= v;
}
long get_bqual_length() const
{
return bqual_length;
}
void set_bqual_length(long v)
{
bqual_length= v;
}
const char* get_data() const
{
return data;
}
void set_data(const void* v, long l)
{
DBUG_ASSERT(l <= XIDDATASIZE);
memcpy(data, v, l);
}
void reset()
{
formatID= -1;
gtrid_length= 0;
bqual_length= 0;
memset(data, 0, XIDDATASIZE);
}
void set(long f, const char *g, long gl, const char *b, long bl)
{
formatID= f;
memcpy(data, g, gtrid_length= gl);
memcpy(data + gl, b, bqual_length= bl);
}
my_xid get_my_xid() const
{
if (gtrid_length == static_cast<long>(MYSQL_XID_GTRID_LEN) &&
bqual_length == 0 &&
!memcmp(data, MYSQL_XID_PREFIX, MYSQL_XID_PREFIX_LEN))
{
my_xid tmp;
memcpy(&tmp, data + MYSQL_XID_OFFSET, sizeof(tmp));
return tmp;
}
return 0;
}
uchar *key()
{
return reinterpret_cast<uchar *>(&gtrid_length);
}
const uchar *key() const
{
return reinterpret_cast<const uchar*>(&gtrid_length);
}
uint key_length() const
{
return sizeof(gtrid_length) + sizeof(bqual_length) +
gtrid_length + bqual_length;
}
/*
The size of the string containing serialized Xid representation
is computed as a sum of
eight as the number of formatting symbols (X'',X'',)
plus 2 x XIDDATASIZE (2 due to hex format),
plus space for decimal digits of XID::formatID,
plus one for 0x0.
*/
static const uint ser_buf_size=
8 + 2 * XIDDATASIZE + 4 * sizeof(long) + 1;
/**
The method fills XID in a buffer in format of GTRID,BQUAL,FORMATID
where GTRID, BQUAL are represented as hex strings.
@param buf a pointer to buffer
@return the value of the first argument
*/
char *serialize(char *buf) const
{
return serialize_xid(buf, formatID, gtrid_length, bqual_length, data);
}
#ifndef DBUG_OFF
/**
Get printable XID value.
@param buf pointer to the buffer where printable XID value has to be stored
@return pointer to the buffer passed in the first argument
*/
char* xid_to_str(char *buf) const;
#endif
bool eq(const xid_t *xid) const
{
return xid->formatID == formatID &&
xid->gtrid_length == gtrid_length &&
xid->bqual_length == bqual_length &&
!memcmp(xid->data, data, gtrid_length + bqual_length);
}
bool is_null() const
{
return formatID == -1;
}
private:
void set(const xid_t *xid)
{
memcpy(this, xid, sizeof(xid->formatID) + xid->key_length());
}
void set(my_xid xid)
{
formatID= 1;
memcpy(data, MYSQL_XID_PREFIX, MYSQL_XID_PREFIX_LEN);
memcpy(data + MYSQL_XID_PREFIX_LEN, &server_id, sizeof(server_id));
memcpy(data + MYSQL_XID_OFFSET, &xid, sizeof(xid));
gtrid_length= MYSQL_XID_GTRID_LEN;
bqual_length= 0;
}
void null()
{
formatID= -1;
}
friend class XID_STATE;
} XID;
class XID_STATE
{
public:
enum xa_states {XA_NOTR=0, XA_ACTIVE, XA_IDLE, XA_PREPARED, XA_ROLLBACK_ONLY};
/**
Transaction identifier.
For now, this is only used to catch duplicated external xids.
*/
private:
static const char *xa_state_names[];
XID m_xid;
/// Used by external XA only
xa_states xa_state;
bool in_recovery;
/// Error reported by the Resource Manager (RM) to the Transaction Manager.
uint rm_error;
/*
XA-prepare binary logging status. The flag serves as a facility
to conduct XA transaction two round binary logging.
It is set to @c false at XA-start.
It is set to @c true by binlogging routine of XA-prepare handler as well
as recovered to @c true at the server recovery upon restart.
Checked and reset at XA-commit/rollback.
*/
bool m_is_binlogged;
public:
XID_STATE()
: xa_state(XA_NOTR),
in_recovery(false),
rm_error(0),
m_is_binlogged(false)
{ m_xid.null(); }
void set_state(xa_states state)
{ xa_state= state; }
enum xa_states get_state()
{ return xa_state; }
bool has_state(xa_states state) const
{ return xa_state == state; }
const char* state_name() const
{ return xa_state_names[xa_state]; }
const XID *get_xid() const
{ return &m_xid; }
XID *get_xid()
{ return &m_xid; }
bool has_same_xid(const XID *xid) const
{ return m_xid.eq(xid); }
void set_query_id(query_id_t query_id)
{
if (m_xid.is_null())
m_xid.set(query_id);
}
void set_error(THD *thd);
void reset_error()
{ rm_error= 0; }
void cleanup()
{
/*
If rm_error is raised, it means that this piece of a distributed
transaction has failed and must be rolled back. But the user must
rollback it explicitly, so don't start a new distributed XA until
then.
*/
if (!rm_error)
m_xid.null();
}
void reset()
{
xa_state= XA_NOTR;
m_xid.null();
in_recovery= false;
m_is_binlogged= false;
}
void start_normal_xa(const XID *xid)
{
DBUG_ASSERT(m_xid.is_null());
xa_state= XA_ACTIVE;
m_xid.set(xid);
in_recovery= false;
rm_error= 0;
}
void start_recovery_xa(const XID *xid, bool binlogged_arg= false)
{
xa_state= XA_PREPARED;
m_xid.set(xid);
in_recovery= true;
rm_error= 0;
m_is_binlogged= binlogged_arg;
}
bool is_in_recovery() const
{ return in_recovery; }
bool is_binlogged() const
{ return m_is_binlogged; }
void set_binlogged()
{ m_is_binlogged= true; }
void unset_binlogged()
{ m_is_binlogged= false; }
void store_xid_info(Protocol *protocol, bool print_xid_as_hex) const;
/**
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.
*/
bool xa_trans_rolled_back();
/**
Check that XA transaction is in state IDLE or PREPARED.
@param report_error true if state IDLE or PREPARED has to be interpreted
as an error, else false
@return result of check
@retval false XA transaction is NOT in state IDLE or PREPARED
@retval true XA transaction is in state IDLE or PREPARED
*/
bool check_xa_idle_or_prepared(bool report_error) const;
/**
Check that XA transaction has an uncommitted work. Report an error
to a mysql user in case when there is an uncommitted work for XA transaction.
@return result of check
@retval false XA transaction is NOT in state IDLE, PREPARED
or ROLLBACK_ONLY.
@retval true XA transaction is in state IDLE or PREPARED
or ROLLBACK_ONLY.
*/
bool check_has_uncommitted_xa() const;
/**
Check if an XA transaction has been started.
@param report_error true if report an error in case when
XA transaction has been stared, else false.
@return result of check
@retval false XA transaction hasn't been started (XA_NOTR)
@retval true XA transaction has been started (!XA_NOTR)
*/
bool check_in_xa(bool report_error) const;
};
class Transaction_ctx;
/**
Initialize a cache to store Transaction_ctx and a mutex to protect access
to the cache
@return result of initialization
@retval false success
@retval true failure
*/
bool transaction_cache_init();
/**
Search information about XA transaction by a XID value.
@param xid Pointer to a XID structure that identifies a XA transaction.
@return pointer to a Transaction_ctx that describes the whole transaction
including XA-specific information (XID_STATE).
@retval NULL failure
@retval != NULL success
*/
Transaction_ctx *transaction_cache_search(XID *xid);
/**
Insert information about XA transaction into a cache indexed by XID.
@param xid Pointer to a XID structure that identifies a XA transaction.
@param transaction
Pointer to Transaction object that is inserted.
@return operation result
@retval false success or a cache already contains XID_STATE
for this XID value
@retval true failure
*/
bool transaction_cache_insert(XID *xid, Transaction_ctx *transaction);
/**
Transaction is marked in the cache as if it's recovered.
The method allows to sustain prepared transaction disconnection.
@param transaction
Pointer to Transaction object that is replaced.
@return operation result
@retval false success or a cache already contains XID_STATE
for this XID value
@retval true failure
*/
bool transaction_cache_detach(Transaction_ctx *transaction);
/**
Insert information about XA transaction being recovered into a cache
indexed by XID.
@param xid Pointer to a XID structure that identifies a XA transaction.
@return operation result
@retval false success or a cache already contains Transaction_ctx
for this XID value
@retval true failure
*/
bool transaction_cache_insert_recovery(XID *xid);
/**
Remove information about transaction from a cache.
@param transaction Pointer to a Transaction_ctx that has to be removed
from a cache.
*/
void transaction_cache_delete(Transaction_ctx *transaction);
/**
Release resources occupied by transaction cache.
*/
void transaction_cache_free();
/**
This is a specific to "slave" applier collection of standard cleanup
actions to reset XA transaction state at the end of XA prepare rather than
to do it at the transaction commit, see @c ha_commit_one_phase.
THD of the slave applier is dissociated from a transaction object in engine
that continues to exist there.
@param THD current thread
@return the value of is_error()
*/
bool applier_reset_xa_trans(THD *thd);
/* interface to randomly access plugin data */
struct st_plugin_int *plugin_find_by_type(const LEX_CSTRING &plugin, int type);
/**
The function detaches existing storage engines transaction
context from thd. Backup area to save it is provided to low level
storage engine function.
is invoked by plugin_foreach() after
trans_xa_start() for each storage engine.
@param[in,out] thd Thread context
@param plugin Reference to handlerton
@return FALSE on success, TRUE otherwise.
*/
my_bool detach_native_trx(THD *thd, plugin_ref plugin,
void *unused);
/**
The function reattaches existing storage engines transaction
context to thd. Backup area to save it is provided to low level
storage engine function.
is invoked by plugin_foreach() after
trans_xa_prepare() for each storage engine.
@param[in,out] thd Thread context
@param plugin Reference to handlerton
@return FALSE on success,
TRUE otherwise.
*/
my_bool reattach_native_trx(THD *thd, plugin_ref plugin, void *);
/**
Reset some transaction state information and delete corresponding
Transaction_ctx object from cache.
@param thd Current thread
*/
void cleanup_trans_state(THD *thd);
/**
Rollback the active XA transaction.
@note Resets rm_error before calling ha_rollback(), so
the thd->transaction.xid structure gets reset
by ha_rollback() / THD::transaction::cleanup().
@return true if the rollback failed, false otherwise.
*/
bool xa_trans_force_rollback(THD *thd);
#endif