/* 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 .
*/
if (opt_readonly)
{
if (!(thd->security_ctx->master_access & PRIV_IGNORE_READ_ONLY) ||
opt_readonly == READONLY_NO_LOCK_NO_ADMIN)
{
mariadb_error_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_ps_unsafe());
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 );
/*
Implicit rollback should reset OPTION_BEGIN flag to avoid starting a
new transaction implicitly in next statement . It makes the behaviour
uniform with direct commit and rollback .
*/
thd->variables.option_bits&= ~(OPTION_BEGIN | 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_ps_unsafe());
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_ps_unsafe());
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 );
if (unlikely(thd->in_sub_stmt & SUB_STMT_TRIGGER) &&
opt_binlog_engine_hton)
{
/*
ToDo : Probably using savepoints in triggers should be disallowed
unconditionally . For now , we disallow it in the new binlog
implementation , which is not enabled by default , so that backwards
compatibility is of smaller concern .
*/
my_error(ER_NOT_AVAILABLE_WITH_ENGINE_BINLOG, MYF(0 ),
"Using savepoints in triggers" );
DBUG_RETURN(1 );
}
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;
DBUG_ENTER("trans_rollback_to_savepoint" );
if (unlikely(thd->in_sub_stmt & SUB_STMT_TRIGGER) &&
opt_binlog_engine_hton)
{
/*
ToDo : Probably using savepoints in triggers should be disallowed
unconditionally . For now , we disallow it in the new binlog
implementation , which is not enabled by default , so that backwards
compatibility is of smaller concern .
*/
my_error(ER_NOT_AVAILABLE_WITH_ENGINE_BINLOG, MYF(0 ),
"Using savepoints in triggers" );
DBUG_RETURN(1 );
}
if ((sv= *find_savepoint(thd, Lex_ident_savepoint(name))) == 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 that releasing a savepoint also releases
any savepoints set following the specified savepoint .
@ 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 );
}
SAVEPOINT *p= thd->transaction->savepoints;
while (p)
{
if (ha_release_savepoint(thd, p))
res= TRUE ;
if (p == sv)
{
thd->transaction->savepoints= sv->prev;
DBUG_RETURN(MY_TEST(res));
}
p= p->prev;
}
DBUG_ASSERT(0 /* Should not get here, would imply that the list of savepoints
changed since find_savepoint ( ) called at the start of this
function. */ );
thd->transaction->savepoints= NULL;;
DBUG_RETURN(MY_TEST(res));
}
#ifdef WITH_WSREP
/* check if a named savepoint exists for the current transaction */
bool trans_savepoint_exists(THD *thd, LEX_CSTRING name)
{
SAVEPOINT **sv = find_savepoint(thd, Lex_ident_savepoint(name));
return (*sv != NULL);
}
#endif /* WITH_WSREP */
Messung V0.5 in Prozent C=93 H=98 G=95
¤ Dauer der Verarbeitung: 0.18 Sekunden
(vorverarbeitet am 2026-10-08)
¤
*© Formatika GbR, Deutschland