mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 19:06:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			784 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			784 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 */
 | |
| 
 | |
| #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 transaction 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,
 | |
|                                    const Lex_ident_savepoint name,
 | |
|                                    SAVEPOINT ** const list)
 | |
| {
 | |
|   SAVEPOINT **sv= list;
 | |
| 
 | |
|   while (*sv)
 | |
|   {
 | |
|     if (name.streq(Lex_cstring((*sv)->name, (*sv)->length)))
 | |
|       break;
 | |
|     sv= &(*sv)->prev;
 | |
|   }
 | |
| 
 | |
|   return sv;
 | |
| }
 | |
| 
 | |
| /* Find a named savepoint in the current transaction. */
 | |
| static SAVEPOINT **
 | |
| find_savepoint(THD *thd, Lex_ident_savepoint name)
 | |
| {
 | |
|   return find_savepoint_in_list(thd, name, &thd->transaction->savepoints);
 | |
| }
 | |
| 
 | |
| SAVEPOINT* savepoint_add(THD *thd, Lex_ident_savepoint 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, Lex_ident_savepoint(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, Lex_ident_savepoint(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, Lex_ident_savepoint(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));
 | |
| }
 | 
