mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-04 12:56:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			789 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			789 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 | 
						|
   Copyright (c) 2009, 2021, MariaDB Corporation.
 | 
						|
 | 
						|
   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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1335  USA */
 | 
						|
 | 
						|
 | 
						|
#ifdef USE_PRAGMA_IMPLEMENTATION
 | 
						|
#pragma implementation                         // gcc: Class implementation
 | 
						|
#endif
 | 
						|
 | 
						|
#include "mariadb.h"
 | 
						|
#include "sql_priv.h"
 | 
						|
#include "transaction.h"
 | 
						|
#include "debug_sync.h"         // DEBUG_SYNC
 | 
						|
#include "sql_acl.h"
 | 
						|
#include "semisync_master.h"
 | 
						|
#include <pfs_transaction_provider.h>
 | 
						|
#include <mysql/psi/mysql_transaction.h>
 | 
						|
#ifdef WITH_WSREP
 | 
						|
#include "wsrep_trans_observer.h"
 | 
						|
#endif /* WITH_WSREP */
 | 
						|
 | 
						|
/**
 | 
						|
  Helper: Tell tracker (if any) that transaction ended.
 | 
						|
*/
 | 
						|
void trans_track_end_trx(THD *thd)
 | 
						|
{
 | 
						|
#ifndef EMBEDDED_LIBRARY
 | 
						|
  if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
 | 
						|
    thd->session_tracker.transaction_info.end_trx(thd);
 | 
						|
#endif //EMBEDDED_LIBRARY
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
  Helper: transaction ended, SET TRANSACTION one-shot variables
 | 
						|
  revert to session values. Let the transaction state tracker know.
 | 
						|
*/
 | 
						|
void trans_reset_one_shot_chistics(THD *thd)
 | 
						|
{
 | 
						|
#ifndef EMBEDDED_LIBRARY
 | 
						|
  if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
 | 
						|
  {
 | 
						|
    thd->session_tracker.transaction_info.set_read_flags(thd, TX_READ_INHERIT);
 | 
						|
    thd->session_tracker.transaction_info.set_isol_level(thd, TX_ISOL_INHERIT);
 | 
						|
  }
 | 
						|
#endif //EMBEDDED_LIBRARY
 | 
						|
  thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
 | 
						|
  thd->tx_read_only= thd->variables.tx_read_only;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
  Conditions under which the transaction state must not change
 | 
						|
 | 
						|
  @result TRUE  Transaction can not commit
 | 
						|
  @result FALSE Transaction can commit
 | 
						|
*/
 | 
						|
 | 
						|
static bool trans_check(THD *thd)
 | 
						|
{
 | 
						|
  DBUG_ENTER("trans_check");
 | 
						|
 | 
						|
  /*
 | 
						|
    Always commit statement transaction before manipulating with
 | 
						|
    the normal one.
 | 
						|
  */
 | 
						|
  DBUG_ASSERT(thd->transaction->stmt.is_empty());
 | 
						|
 | 
						|
  if (unlikely(thd->in_sub_stmt))
 | 
						|
  {
 | 
						|
    my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
 | 
						|
    DBUG_RETURN(TRUE);
 | 
						|
  }
 | 
						|
  if (likely(!thd->transaction->xid_state.is_explicit_XA()))
 | 
						|
    DBUG_RETURN(FALSE);
 | 
						|
 | 
						|
  thd->transaction->xid_state.er_xaer_rmfail();
 | 
						|
  DBUG_RETURN(TRUE);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
  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);
 | 
						|
 | 
						|
  if (thd->locked_tables_list.unlock_locked_tables(thd))
 | 
						|
    DBUG_RETURN(true);
 | 
						|
 | 
						|
  DBUG_ASSERT(!thd->locked_tables_mode);
 | 
						|
 | 
						|
  if (thd->in_multi_stmt_transaction_mode() ||
 | 
						|
      (thd->variables.option_bits & OPTION_TABLE_LOCK))
 | 
						|
  {
 | 
						|
    bool was_in_trans= thd->server_status &
 | 
						|
      (SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
 | 
						|
    thd->variables.option_bits&= ~OPTION_TABLE_LOCK;
 | 
						|
    thd->server_status&=
 | 
						|
      ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
 | 
						|
    DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
 | 
						|
    res= MY_TEST(ha_commit_trans(thd, TRUE));
 | 
						|
    if (was_in_trans)
 | 
						|
      trans_reset_one_shot_chistics(thd);
 | 
						|
#ifdef WITH_WSREP
 | 
						|
    if (wsrep_thd_is_local(thd))
 | 
						|
    {
 | 
						|
      res= res || wsrep_after_statement(thd);
 | 
						|
    }
 | 
						|
#endif /* WITH_WSREP */
 | 
						|
  }
 | 
						|
 | 
						|
  thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_BINLOG_THIS_TRX);
 | 
						|
 | 
						|
  /*
 | 
						|
    The following set should not be needed as transaction state should
 | 
						|
    already be reset. We should at some point change this to an assert.
 | 
						|
  */
 | 
						|
  thd->transaction->all.reset();
 | 
						|
  thd->has_waiter= false;
 | 
						|
  thd->waiting_on_group_commit= false;
 | 
						|
  thd->transaction->start_time.reset(thd);
 | 
						|
 | 
						|
  if (res)
 | 
						|
    DBUG_RETURN(TRUE);
 | 
						|
 | 
						|
  /*
 | 
						|
    Release transactional metadata locks only after the
 | 
						|
    transaction has been committed.
 | 
						|
  */
 | 
						|
  thd->release_transactional_locks();
 | 
						|
 | 
						|
  // The RO/RW options are mutually exclusive.
 | 
						|
  DBUG_ASSERT(!((flags & MYSQL_START_TRANS_OPT_READ_ONLY) &&
 | 
						|
                (flags & MYSQL_START_TRANS_OPT_READ_WRITE)));
 | 
						|
  if (flags & MYSQL_START_TRANS_OPT_READ_ONLY)
 | 
						|
  {
 | 
						|
    thd->tx_read_only= true;
 | 
						|
#ifndef EMBEDDED_LIBRARY
 | 
						|
    if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
 | 
						|
      thd->session_tracker.transaction_info.set_read_flags(thd, TX_READ_ONLY);
 | 
						|
#endif //EMBEDDED_LIBRARY
 | 
						|
  }
 | 
						|
  else if (flags & MYSQL_START_TRANS_OPT_READ_WRITE)
 | 
						|
  {
 | 
						|
    /*
 | 
						|
      Explicitly starting a RW transaction when the server is in
 | 
						|
      read-only mode, is not allowed unless the user has SUPER priv.
 | 
						|
      Implicitly starting a RW transaction is allowed for backward
 | 
						|
      compatibility.
 | 
						|
    */
 | 
						|
    const bool user_is_super=
 | 
						|
      MY_TEST(thd->security_ctx->master_access & PRIV_IGNORE_READ_ONLY);
 | 
						|
    if (opt_readonly && !user_is_super)
 | 
						|
    {
 | 
						|
      my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
 | 
						|
      DBUG_RETURN(true);
 | 
						|
    }
 | 
						|
    thd->tx_read_only= false;
 | 
						|
    /*
 | 
						|
      This flags that transaction_read_only was set explicitly, rather than
 | 
						|
      just from the session's default.
 | 
						|
    */
 | 
						|
#ifndef EMBEDDED_LIBRARY
 | 
						|
    if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
 | 
						|
      thd->session_tracker.transaction_info.set_read_flags(thd, TX_READ_WRITE);
 | 
						|
#endif //EMBEDDED_LIBRARY
 | 
						|
  }
 | 
						|
 | 
						|
#ifdef WITH_WSREP
 | 
						|
  if (WSREP(thd) && wsrep_thd_is_local(thd))
 | 
						|
  {
 | 
						|
    if (wsrep_sync_wait(thd))
 | 
						|
      DBUG_RETURN(TRUE);
 | 
						|
    if (!thd->tx_read_only &&
 | 
						|
        wsrep_start_transaction(thd, thd->wsrep_next_trx_id()))
 | 
						|
      DBUG_RETURN(TRUE);
 | 
						|
  }
 | 
						|
#endif /* WITH_WSREP */
 | 
						|
 | 
						|
  thd->variables.option_bits|= OPTION_BEGIN;
 | 
						|
  thd->server_status|= SERVER_STATUS_IN_TRANS;
 | 
						|
  if (thd->tx_read_only)
 | 
						|
    thd->server_status|= SERVER_STATUS_IN_TRANS_READONLY;
 | 
						|
  DBUG_PRINT("info", ("setting SERVER_STATUS_IN_TRANS"));
 | 
						|
 | 
						|
#ifndef EMBEDDED_LIBRARY
 | 
						|
  if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
 | 
						|
    thd->session_tracker.transaction_info.add_trx_state(thd, TX_EXPLICIT);
 | 
						|
#endif //EMBEDDED_LIBRARY
 | 
						|
 | 
						|
  /* ha_start_consistent_snapshot() relies on OPTION_BEGIN flag set. */
 | 
						|
  if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT)
 | 
						|
  {
 | 
						|
#ifndef EMBEDDED_LIBRARY
 | 
						|
    if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
 | 
						|
      thd->session_tracker.transaction_info.add_trx_state(thd, TX_WITH_SNAPSHOT);
 | 
						|
#endif //EMBEDDED_LIBRARY
 | 
						|
    res= ha_start_consistent_snapshot(thd);
 | 
						|
  }
 | 
						|
  /*
 | 
						|
    Register transaction start in performance schema if not done already.
 | 
						|
    We handle explicitly started transactions here, implicitly started
 | 
						|
    transactions (and single-statement transactions in autocommit=1 mode)
 | 
						|
    are handled in trans_register_ha().
 | 
						|
    We can't handle explicit transactions in the same way as implicit
 | 
						|
    because we want to correctly attribute statements which follow
 | 
						|
    BEGIN but do not touch any transactional tables.
 | 
						|
  */
 | 
						|
  if (thd->m_transaction_psi == NULL)
 | 
						|
  {
 | 
						|
    thd->m_transaction_psi= MYSQL_START_TRANSACTION(&thd->m_transaction_state,
 | 
						|
                                                 NULL, 0, thd->tx_isolation,
 | 
						|
                                                 thd->tx_read_only, false);
 | 
						|
    DEBUG_SYNC(thd, "after_set_transaction_psi_before_set_transaction_gtid");
 | 
						|
    //gtid_set_performance_schema_values(thd);
 | 
						|
  }
 | 
						|
 | 
						|
  DBUG_RETURN(MY_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;
 | 
						|
  PSI_stage_info org_stage;
 | 
						|
  DBUG_ENTER("trans_commit");
 | 
						|
 | 
						|
  if (trans_check(thd))
 | 
						|
    DBUG_RETURN(TRUE);
 | 
						|
 | 
						|
  thd->backup_stage(&org_stage);
 | 
						|
  THD_STAGE_INFO(thd, stage_commit);
 | 
						|
 | 
						|
  thd->server_status&=
 | 
						|
    ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
 | 
						|
  DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
 | 
						|
  res= ha_commit_trans(thd, TRUE);
 | 
						|
 | 
						|
  mysql_mutex_assert_not_owner(&LOCK_prepare_ordered);
 | 
						|
  mysql_mutex_assert_not_owner(mysql_bin_log.get_log_lock());
 | 
						|
  mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync);
 | 
						|
  mysql_mutex_assert_not_owner(&LOCK_commit_ordered);
 | 
						|
 | 
						|
  /*
 | 
						|
    if res is non-zero, then ha_commit_trans has rolled back the
 | 
						|
    transaction, so the hooks for rollback will be called.
 | 
						|
  */
 | 
						|
#ifdef HAVE_REPLICATION
 | 
						|
  if (res)
 | 
						|
    repl_semisync_master.wait_after_rollback(thd, FALSE);
 | 
						|
  else
 | 
						|
    repl_semisync_master.wait_after_commit(thd, FALSE);
 | 
						|
#endif
 | 
						|
  thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_BINLOG_THIS_TRX);
 | 
						|
  thd->transaction->all.reset();
 | 
						|
  thd->lex->start_transaction_opt= 0;
 | 
						|
 | 
						|
  /* The transaction should be marked as complete in P_S. */
 | 
						|
  DBUG_ASSERT(thd->m_transaction_psi == NULL);
 | 
						|
  trans_track_end_trx(thd);
 | 
						|
 | 
						|
  THD_STAGE_INFO(thd, org_stage);
 | 
						|
  DBUG_RETURN(MY_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->variables.option_bits & OPTION_GTID_BEGIN)
 | 
						|
  {
 | 
						|
    DBUG_PRINT("error", ("OPTION_GTID_BEGIN is set. "
 | 
						|
                         "Master and slave will have different GTID values"));
 | 
						|
  }
 | 
						|
 | 
						|
  if (thd->in_multi_stmt_transaction_mode() ||
 | 
						|
      (thd->variables.option_bits & OPTION_TABLE_LOCK))
 | 
						|
  {
 | 
						|
    PSI_stage_info org_stage;
 | 
						|
    thd->backup_stage(&org_stage);
 | 
						|
    THD_STAGE_INFO(thd, stage_commit_implicit);
 | 
						|
 | 
						|
    /* Safety if one did "drop table" on locked tables */
 | 
						|
    if (!thd->locked_tables_mode)
 | 
						|
      thd->variables.option_bits&= ~OPTION_TABLE_LOCK;
 | 
						|
    thd->server_status&=
 | 
						|
      ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
 | 
						|
    DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
 | 
						|
    res= MY_TEST(ha_commit_trans(thd, TRUE));
 | 
						|
 | 
						|
    THD_STAGE_INFO(thd, org_stage);
 | 
						|
  }
 | 
						|
 | 
						|
  thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_BINLOG_THIS_TRX);
 | 
						|
  thd->transaction->all.reset();
 | 
						|
 | 
						|
  /* The transaction should be marked as complete in P_S. */
 | 
						|
  DBUG_ASSERT(thd->m_transaction_psi == NULL);
 | 
						|
 | 
						|
  /*
 | 
						|
    Upon implicit commit, reset the current transaction
 | 
						|
    isolation level and access mode. We do not care about
 | 
						|
    @@session.completion_type since it's documented
 | 
						|
    to not have any effect on implicit commit.
 | 
						|
  */
 | 
						|
  trans_reset_one_shot_chistics(thd);
 | 
						|
 | 
						|
  trans_track_end_trx(thd);
 | 
						|
 | 
						|
  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;
 | 
						|
  PSI_stage_info org_stage;
 | 
						|
  DBUG_ENTER("trans_rollback");
 | 
						|
 | 
						|
  if (trans_check(thd))
 | 
						|
    DBUG_RETURN(TRUE);
 | 
						|
 | 
						|
  thd->backup_stage(&org_stage);
 | 
						|
  THD_STAGE_INFO(thd, stage_rollback);
 | 
						|
 | 
						|
  thd->server_status&=
 | 
						|
    ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
 | 
						|
  DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
 | 
						|
  res= ha_rollback_trans(thd, TRUE);
 | 
						|
#ifdef HAVE_REPLICATION
 | 
						|
  repl_semisync_master.wait_after_rollback(thd, FALSE);
 | 
						|
#endif
 | 
						|
  /* Reset the binlog transaction marker */
 | 
						|
  thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_BINLOG_THIS_TRX |
 | 
						|
                                 OPTION_GTID_BEGIN);
 | 
						|
  thd->transaction->all.reset();
 | 
						|
  thd->lex->start_transaction_opt= 0;
 | 
						|
 | 
						|
  /* The transaction should be marked as complete in P_S. */
 | 
						|
  DBUG_ASSERT(thd->m_transaction_psi == NULL);
 | 
						|
 | 
						|
  trans_track_end_trx(thd);
 | 
						|
 | 
						|
  THD_STAGE_INFO(thd, org_stage);
 | 
						|
  DBUG_RETURN(MY_TEST(res));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
  Implicitly rollback the current transaction, typically
 | 
						|
  after deadlock was discovered.
 | 
						|
 | 
						|
  @param thd     Current thread
 | 
						|
 | 
						|
  @retval False Success
 | 
						|
  @retval True  Failure
 | 
						|
 | 
						|
  @note ha_rollback_low() which is indirectly called by this
 | 
						|
        function will mark XA transaction for rollback by
 | 
						|
        setting appropriate RM error status if there was
 | 
						|
        transaction rollback request.
 | 
						|
*/
 | 
						|
 | 
						|
bool trans_rollback_implicit(THD *thd)
 | 
						|
{
 | 
						|
  int res;
 | 
						|
  PSI_stage_info org_stage;
 | 
						|
  DBUG_ENTER("trans_rollback_implict");
 | 
						|
 | 
						|
  thd->backup_stage(&org_stage);
 | 
						|
  THD_STAGE_INFO(thd, stage_rollback_implicit);
 | 
						|
 | 
						|
  /*
 | 
						|
    Always commit/rollback statement transaction before manipulating
 | 
						|
    with the normal one.
 | 
						|
    Don't perform rollback in the middle of sub-statement, wait till
 | 
						|
    its end.
 | 
						|
  */
 | 
						|
  DBUG_ASSERT(thd->transaction->stmt.is_empty() && !thd->in_sub_stmt);
 | 
						|
 | 
						|
  thd->server_status&= ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY);
 | 
						|
  DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS"));
 | 
						|
  res= ha_rollback_trans(thd, true);
 | 
						|
  /*
 | 
						|
    We don't reset OPTION_BEGIN flag below to simulate implicit start
 | 
						|
    of new transacton in @@autocommit=1 mode. This is necessary to
 | 
						|
    preserve backward compatibility.
 | 
						|
  */
 | 
						|
  thd->variables.option_bits&= ~(OPTION_BINLOG_THIS_TRX);
 | 
						|
  thd->transaction->all.reset();
 | 
						|
 | 
						|
  /* Rollback should clear transaction_rollback_request flag. */
 | 
						|
  DBUG_ASSERT(!thd->transaction_rollback_request);
 | 
						|
  /* The transaction should be marked as complete in P_S. */
 | 
						|
  DBUG_ASSERT(thd->m_transaction_psi == NULL);
 | 
						|
 | 
						|
  trans_track_end_trx(thd);
 | 
						|
 | 
						|
  THD_STAGE_INFO(thd, org_stage);
 | 
						|
  DBUG_RETURN(MY_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;
 | 
						|
  /*
 | 
						|
    We currently don't invoke commit/rollback at end of
 | 
						|
    a sub-statement.  In future, we perhaps should take
 | 
						|
    a savepoint for each nested statement, and release the
 | 
						|
    savepoint when statement has succeeded.
 | 
						|
  */
 | 
						|
  DBUG_ASSERT(!(thd->in_sub_stmt));
 | 
						|
 | 
						|
  thd->merge_unsafe_rollback_flags();
 | 
						|
 | 
						|
  if (thd->transaction->stmt.ha_list)
 | 
						|
  {
 | 
						|
    PSI_stage_info org_stage;
 | 
						|
    thd->backup_stage(&org_stage);
 | 
						|
    THD_STAGE_INFO(thd, stage_commit);
 | 
						|
 | 
						|
    res= ha_commit_trans(thd, FALSE);
 | 
						|
    if (! thd->in_active_multi_stmt_transaction())
 | 
						|
    {
 | 
						|
      trans_reset_one_shot_chistics(thd);
 | 
						|
    }
 | 
						|
 | 
						|
    THD_STAGE_INFO(thd, org_stage);
 | 
						|
  }
 | 
						|
 | 
						|
  mysql_mutex_assert_not_owner(&LOCK_prepare_ordered);
 | 
						|
  mysql_mutex_assert_not_owner(mysql_bin_log.get_log_lock());
 | 
						|
  mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync);
 | 
						|
  mysql_mutex_assert_not_owner(&LOCK_commit_ordered);
 | 
						|
 | 
						|
    /*
 | 
						|
      if res is non-zero, then ha_commit_trans has rolled back the
 | 
						|
      transaction, so the hooks for rollback will be called.
 | 
						|
    */
 | 
						|
  if (res)
 | 
						|
  {
 | 
						|
#ifdef HAVE_REPLICATION
 | 
						|
    repl_semisync_master.wait_after_rollback(thd, FALSE);
 | 
						|
#endif
 | 
						|
  }
 | 
						|
  else
 | 
						|
  {
 | 
						|
#ifdef HAVE_REPLICATION
 | 
						|
    repl_semisync_master.wait_after_commit(thd, FALSE);
 | 
						|
#endif
 | 
						|
  }
 | 
						|
 | 
						|
  /* In autocommit=1 mode the transaction should be marked as complete in P_S */
 | 
						|
  DBUG_ASSERT(thd->in_active_multi_stmt_transaction() ||
 | 
						|
              thd->m_transaction_psi == NULL);
 | 
						|
 | 
						|
  thd->transaction->stmt.reset();
 | 
						|
 | 
						|
  DBUG_RETURN(MY_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");
 | 
						|
 | 
						|
  /*
 | 
						|
    We currently don't invoke commit/rollback at end of
 | 
						|
    a sub-statement.  In future, we perhaps should take
 | 
						|
    a savepoint for each nested statement, and release the
 | 
						|
    savepoint when statement has succeeded.
 | 
						|
  */
 | 
						|
  DBUG_ASSERT(! thd->in_sub_stmt);
 | 
						|
 | 
						|
  thd->merge_unsafe_rollback_flags();
 | 
						|
 | 
						|
  if (thd->transaction->stmt.ha_list)
 | 
						|
  {
 | 
						|
    PSI_stage_info org_stage;
 | 
						|
    thd->backup_stage(&org_stage);
 | 
						|
    THD_STAGE_INFO(thd, stage_rollback);
 | 
						|
 | 
						|
    ha_rollback_trans(thd, FALSE);
 | 
						|
    if (! thd->in_active_multi_stmt_transaction())
 | 
						|
      trans_reset_one_shot_chistics(thd);
 | 
						|
 | 
						|
    THD_STAGE_INFO(thd, org_stage);
 | 
						|
  }
 | 
						|
 | 
						|
#ifdef HAVE_REPLICATION
 | 
						|
  repl_semisync_master.wait_after_rollback(thd, FALSE);
 | 
						|
#endif
 | 
						|
 | 
						|
  /* In autocommit=1 mode the transaction should be marked as complete in P_S */
 | 
						|
  DBUG_ASSERT(thd->in_active_multi_stmt_transaction() ||
 | 
						|
              thd->m_transaction_psi == NULL);
 | 
						|
 | 
						|
  thd->transaction->stmt.reset();
 | 
						|
 | 
						|
  DBUG_RETURN(FALSE);
 | 
						|
}
 | 
						|
 | 
						|
/** Find a savepoint by name in a savepoint list */
 | 
						|
SAVEPOINT** find_savepoint_in_list(THD *thd, LEX_CSTRING name,
 | 
						|
                                   SAVEPOINT ** const list)
 | 
						|
{
 | 
						|
  SAVEPOINT **sv= list;
 | 
						|
 | 
						|
  while (*sv)
 | 
						|
  {
 | 
						|
    if (system_charset_info->strnncoll(
 | 
						|
            (uchar *) name.str, name.length,
 | 
						|
            (uchar *) (*sv)->name, (*sv)->length) == 0)
 | 
						|
      break;
 | 
						|
    sv= &(*sv)->prev;
 | 
						|
  }
 | 
						|
 | 
						|
  return sv;
 | 
						|
}
 | 
						|
 | 
						|
/* Find a named savepoint in the current transaction. */
 | 
						|
static SAVEPOINT **
 | 
						|
find_savepoint(THD *thd, LEX_CSTRING name)
 | 
						|
{
 | 
						|
  return find_savepoint_in_list(thd, name, &thd->transaction->savepoints);
 | 
						|
}
 | 
						|
 | 
						|
SAVEPOINT* savepoint_add(THD *thd, LEX_CSTRING name, SAVEPOINT **list,
 | 
						|
                         int (*release_old)(THD*, SAVEPOINT*))
 | 
						|
{
 | 
						|
  DBUG_ENTER("savepoint_add");
 | 
						|
 | 
						|
  SAVEPOINT **sv= find_savepoint_in_list(thd, name, list);
 | 
						|
 | 
						|
  SAVEPOINT *newsv;
 | 
						|
 | 
						|
  if (*sv) /* old savepoint of the same name exists */
 | 
						|
  {
 | 
						|
    newsv= *sv;
 | 
						|
    if (release_old){
 | 
						|
      int error= release_old(thd, *sv);
 | 
						|
      if (error)
 | 
						|
        DBUG_RETURN(NULL);
 | 
						|
    }
 | 
						|
    *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(NULL);
 | 
						|
  }
 | 
						|
 | 
						|
  newsv->name= strmake_root(&thd->transaction->mem_root, name.str, name.length);
 | 
						|
  newsv->length= (uint)name.length;
 | 
						|
  DBUG_RETURN(newsv);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  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_CSTRING name)
 | 
						|
{
 | 
						|
  DBUG_ENTER("trans_savepoint");
 | 
						|
 | 
						|
  if (!(thd->in_multi_stmt_transaction_mode() || thd->in_sub_stmt) ||
 | 
						|
      !opt_using_transactions)
 | 
						|
    DBUG_RETURN(FALSE);
 | 
						|
 | 
						|
  if (thd->transaction->xid_state.check_has_uncommitted_xa())
 | 
						|
    DBUG_RETURN(TRUE);
 | 
						|
 | 
						|
  SAVEPOINT *newsv= savepoint_add(thd, name, &thd->transaction->savepoints,
 | 
						|
                                  ha_release_savepoint);
 | 
						|
 | 
						|
  if (newsv == NULL)
 | 
						|
    DBUG_RETURN(TRUE);
 | 
						|
 | 
						|
  /*
 | 
						|
    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 (unlikely(ha_savepoint(thd, newsv)))
 | 
						|
    DBUG_RETURN(TRUE);
 | 
						|
 | 
						|
  newsv->prev= thd->transaction->savepoints;
 | 
						|
  thd->transaction->savepoints= newsv;
 | 
						|
 | 
						|
  /*
 | 
						|
    Remember locks acquired before the savepoint was set.
 | 
						|
    They are used as a marker to only release locks acquired after
 | 
						|
    the setting of this savepoint.
 | 
						|
    Note: this works just fine if we're under LOCK TABLES,
 | 
						|
    since mdl_savepoint() is guaranteed to be beyond
 | 
						|
    the last locked table. This allows to release some
 | 
						|
    locks acquired during LOCK TABLES.
 | 
						|
  */
 | 
						|
  newsv->mdl_savepoint= thd->mdl_context.mdl_savepoint();
 | 
						|
 | 
						|
  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_CSTRING 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 (thd->transaction->xid_state.check_has_uncommitted_xa())
 | 
						|
    DBUG_RETURN(TRUE);
 | 
						|
 | 
						|
  if (ha_rollback_to_savepoint(thd, sv))
 | 
						|
    res= TRUE;
 | 
						|
  else if (((thd->variables.option_bits & OPTION_BINLOG_THIS_TRX) ||
 | 
						|
            thd->transaction->all.modified_non_trans_table) &&
 | 
						|
           !thd->slave_thread)
 | 
						|
    push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
 | 
						|
                 ER_WARNING_NOT_COMPLETE_ROLLBACK,
 | 
						|
                 ER_THD(thd, ER_WARNING_NOT_COMPLETE_ROLLBACK));
 | 
						|
 | 
						|
  thd->transaction->savepoints= sv;
 | 
						|
 | 
						|
  if (res)
 | 
						|
    /* An error occurred during rollback; we cannot release any MDL */;
 | 
						|
  else if (thd->variables.sql_log_bin &&
 | 
						|
           (WSREP_EMULATE_BINLOG_NNULL(thd) || mysql_bin_log.is_open()))
 | 
						|
    /* In some cases (such as with non-transactional tables) we may
 | 
						|
    choose to preserve events that were added after the SAVEPOINT,
 | 
						|
    delimiting them by SAVEPOINT and ROLLBACK TO SAVEPOINT statements.
 | 
						|
    Prematurely releasing MDL on such objects would break replication. */;
 | 
						|
  else if (ha_rollback_to_savepoint_can_release_mdl(thd))
 | 
						|
    thd->mdl_context.rollback_to_savepoint(sv->mdl_savepoint);
 | 
						|
 | 
						|
  DBUG_RETURN(MY_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_CSTRING 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(MY_TEST(res));
 | 
						|
}
 |