/* 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 #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(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(>rid_length); } const uchar *key() const { return reinterpret_cast(>rid_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