Backport of:

------------------------------------------------------------
revno: 2630.22.3
committer: Davi Arnaut <Davi.Arnaut@Sun.COM>
branch nick: 4284-6.0
timestamp: Thu 2008-08-07 22:33:43 -0300
message:
WL#4284: Transactional DDL locking

Make transaction management more modular through a new interface.

The overall objective of this change is to provide groundwork
for the design of transactional DDL locking by cleaning up the
transaction high level API to better distinguish operations implicit
and explicit, and single statement transaction from operations on
the normal transaction.

Having a a high-level interface for transaction management provides
a better base for implementing transactional concepts that are not
always tied to storage engines and also makes it easier to interect
with other higher level modules of the server.
This commit is contained in:
Konstantin Osipov 2009-12-03 21:37:38 +03:00
parent 3a8a750919
commit c43f894c51
24 changed files with 819 additions and 581 deletions

View File

@ -107,7 +107,8 @@ sql_src=log_event.h mysql_priv.h rpl_constants.h \
rpl_utility.h rpl_tblmap.h rpl_tblmap.cc \
log_event.cc my_decimal.h my_decimal.cc \
log_event_old.h log_event_old.cc \
rpl_record_old.h rpl_record_old.cc
rpl_record_old.h rpl_record_old.cc \
transaction.h
strings_src=decimal.c
link_sources:

View File

@ -133,7 +133,7 @@ SET(LIBMYSQLD_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
../sql/partition_info.cc ../sql/sql_connect.cc
../sql/scheduler.cc ../sql/event_parse_data.cc
../sql/sql_signal.cc ../sql/rpl_handler.cc
../sql/mdl.cc
../sql/mdl.cc ../sql/transaction.cc
${GEN_SOURCES}
${LIB_SOURCES})

View File

@ -75,8 +75,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \
sp_head.cc sp_pcontext.cc sp.cc sp_cache.cc sp_rcontext.cc \
parse_file.cc sql_view.cc sql_trigger.cc my_decimal.cc \
rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \
debug_sync.cc \
sql_tablespace.cc \
debug_sync.cc sql_tablespace.cc transaction.cc \
rpl_injector.cc my_user.c partition_info.cc \
sql_servers.cc event_parse_data.cc sql_signal.cc \
rpl_handler.cc mdl.cc

View File

@ -77,6 +77,7 @@ SET (SQL_SOURCE
sql_connect.cc scheduler.cc
sql_profile.cc event_parse_data.cc
sql_signal.cc rpl_handler.cc mdl.cc
transaction.cc
${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc
${PROJECT_SOURCE_DIR}/sql/sql_yacc.h
${PROJECT_SOURCE_DIR}/include/mysqld_error.h

View File

@ -113,7 +113,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
sql_partition.h partition_info.h partition_element.h \
contributors.h sql_servers.h sql_signal.h records.h \
sql_prepare.h rpl_handler.h replication.h mdl.h \
sql_plist.h
sql_plist.h transaction.h
mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
item.cc item_sum.cc item_buff.cc item_func.cc \
@ -159,7 +159,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
sql_plugin.cc sql_binlog.cc \
sql_builtin.cc sql_tablespace.cc partition_info.cc \
sql_servers.cc event_parse_data.cc sql_signal.cc \
rpl_handler.cc mdl.cc
rpl_handler.cc mdl.cc transaction.cc
nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c

View File

@ -27,6 +27,7 @@
#include "rpl_handler.h"
#include "rpl_filter.h"
#include <myisampack.h>
#include "transaction.h"
#include <errno.h>
#include "probes_mysql.h"
@ -888,16 +889,16 @@ void ha_close_connection(THD* thd)
a transaction in a given engine is read-write and will not
involve the two-phase commit protocol!
At the end of a statement, server call
ha_autocommit_or_rollback() is invoked. This call in turn
invokes handlerton::prepare() for every involved engine.
Prepare is followed by a call to handlerton::commit_one_phase()
If a one-phase commit will suffice, handlerton::prepare() is not
invoked and the server only calls handlerton::commit_one_phase().
At statement commit, the statement-related read-write engine
flag is propagated to the corresponding flag in the normal
transaction. When the commit is complete, the list of registered
engines is cleared.
At the end of a statement, server call trans_commit_stmt is
invoked. This call in turn invokes handlerton::prepare()
for every involved engine. Prepare is followed by a call
to handlerton::commit_one_phase() If a one-phase commit
will suffice, handlerton::prepare() is not invoked and
the server only calls handlerton::commit_one_phase().
At statement commit, the statement-related read-write
engine flag is propagated to the corresponding flag in the
normal transaction. When the commit is complete, the list
of registered engines is cleared.
Rollback is handled in a similar fashion.
@ -908,7 +909,7 @@ void ha_close_connection(THD* thd)
do not "register" in thd->transaction lists, and thus do not
modify the transaction state. Besides, each DDL in
MySQL is prefixed with an implicit normal transaction commit
(a call to end_active_trans()), and thus leaves nothing
(a call to trans_commit_implicit()), and thus leaves nothing
to modify.
However, as it has been pointed out with CREATE TABLE .. SELECT,
some DDL statements can start a *new* transaction.
@ -1370,47 +1371,6 @@ int ha_rollback_trans(THD *thd, bool all)
DBUG_RETURN(error);
}
/**
This is used to commit or rollback a single statement depending on
the value of error.
@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.
*/
int ha_autocommit_or_rollback(THD *thd, int error)
{
DBUG_ENTER("ha_autocommit_or_rollback");
if (thd->transaction.stmt.ha_list)
{
if (!error)
{
if (ha_commit_trans(thd, 0))
error=1;
}
else
{
(void) ha_rollback_trans(thd, 0);
if (thd->transaction_rollback_request && !thd->in_sub_stmt)
(void) ha_rollback(thd);
}
thd->variables.tx_isolation=thd->session_tx_isolation;
}
else
{
if (!error)
RUN_HOOK(transaction, after_commit, (thd, FALSE));
else
RUN_HOOK(transaction, after_rollback, (thd, FALSE));
}
DBUG_RETURN(error);
}
struct xahton_st {
XID *xid;
@ -3496,7 +3456,7 @@ int ha_enable_transaction(THD *thd, bool on)
So, let's commit an open transaction (if any) now.
*/
if (!(error= ha_commit_trans(thd, 0)))
error= end_trans(thd, COMMIT);
error= trans_commit_implicit(thd);
}
DBUG_RETURN(error);
}

View File

@ -1945,10 +1945,6 @@ extern TYPELIB tx_isolation_typelib;
extern TYPELIB myisam_stats_method_typelib;
extern ulong total_ha, total_ha_2pc;
/* Wrapper functions */
#define ha_commit(thd) (ha_commit_trans((thd), TRUE))
#define ha_rollback(thd) (ha_rollback_trans((thd), TRUE))
/* lookups */
handlerton *ha_default_handlerton(THD *thd);
plugin_ref ha_resolve_by_name(THD *thd, const LEX_STRING *name);
@ -2025,13 +2021,12 @@ int ha_release_temporary_latches(THD *thd);
int ha_start_consistent_snapshot(THD *thd);
int ha_commit_or_rollback_by_xid(XID *xid, bool commit);
int ha_commit_one_phase(THD *thd, bool all);
int ha_commit_trans(THD *thd, bool all);
int ha_rollback_trans(THD *thd, bool all);
int ha_prepare(THD *thd);
int ha_recover(HASH *commit_list);
/* transactions: these functions never call handlerton functions directly */
int ha_commit_trans(THD *thd, bool all);
int ha_autocommit_or_rollback(THD *thd, int error);
int ha_enable_transaction(THD *thd, bool on);
/* savepoints */

View File

@ -31,6 +31,7 @@
#include "rpl_filter.h"
#include "rpl_utility.h"
#include "rpl_record.h"
#include "transaction.h"
#include <my_dir.h>
#endif /* MYSQL_CLIENT */
@ -3231,7 +3232,7 @@ Default database: '%s'. Query: '%s'",
them back here.
*/
if (expected_error && expected_error == actual_error)
ha_autocommit_or_rollback(thd, TRUE);
trans_rollback_stmt(thd);
}
/*
If we expected a non-zero error code and get nothing and, it is a concurrency
@ -3240,7 +3241,8 @@ Default database: '%s'. Query: '%s'",
else if (expected_error && !actual_error &&
(concurrency_error_code(expected_error) ||
ignored_error_code(expected_error)))
ha_autocommit_or_rollback(thd, TRUE);
trans_rollback_stmt(thd);
/*
Other cases: mostly we expected no error and get one.
*/
@ -5315,7 +5317,7 @@ int Xid_log_event::do_apply_event(Relay_log_info const *rli)
/* For a slave Xid_log_event is COMMIT */
general_log_print(thd, COM_QUERY,
"COMMIT /* implicit, from Xid_log_event */");
return end_trans(thd, COMMIT);
return trans_commit(thd);
}
Log_event::enum_skip_reason
@ -7607,7 +7609,7 @@ static int rows_event_stmt_cleanup(Relay_log_info const *rli, THD * thd)
are involved, commit the transaction and flush the pending event to the
binlog.
*/
error= ha_autocommit_or_rollback(thd, 0);
error= trans_commit_stmt(thd);
/*
Now what if this is not a transactional engine? we still need to

View File

@ -6,6 +6,7 @@
#endif
#include "log_event_old.h"
#include "rpl_record_old.h"
#include "transaction.h"
#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
@ -1795,7 +1796,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
are involved, commit the transaction and flush the pending event to the
binlog.
*/
if ((error= ha_autocommit_or_rollback(thd, 0)))
if ((error= trans_commit_stmt(thd)))
rli->report(ERROR_LEVEL, error,
"Error in %s event: commit of row events failed, "
"table `%s`.`%s`",

View File

@ -883,15 +883,6 @@ bool parse_sql(THD *thd,
Parser_state *parser_state,
Object_creation_ctx *creation_ctx);
enum enum_mysql_completiontype {
ROLLBACK_RELEASE=-2, ROLLBACK=1, ROLLBACK_AND_CHAIN=7,
COMMIT_RELEASE=-1, COMMIT=0, COMMIT_AND_CHAIN=6
};
bool begin_trans(THD *thd);
bool end_active_trans(THD *thd);
int end_trans(THD *thd, enum enum_mysql_completiontype completion);
Item *negate_expression(THD *thd, Item *expr);
/* log.cc */

View File

@ -15,6 +15,7 @@
#include "mysql_priv.h"
#include "rpl_injector.h"
#include "transaction.h"
/*
injector::transaction - member definitions
@ -35,7 +36,7 @@ injector::transaction::transaction(MYSQL_BIN_LOG *log, THD *thd)
m_start_pos.m_file_name= my_strdup(log_info.log_file_name, MYF(0));
m_start_pos.m_file_pos= log_info.pos;
begin_trans(m_thd);
trans_begin(m_thd);
thd->set_current_stmt_binlog_row_based();
}
@ -81,8 +82,8 @@ int injector::transaction::commit()
is committed by committing the statement transaction
explicitly.
*/
ha_autocommit_or_rollback(m_thd, 0);
end_trans(m_thd, COMMIT);
trans_commit_stmt(m_thd);
trans_commit(m_thd);
DBUG_RETURN(0);
}

View File

@ -20,6 +20,7 @@
#include <my_dir.h> // For MY_STAT
#include "sql_repl.h" // For check_binlog_magic
#include "rpl_utility.h"
#include "transaction.h"
static int count_relay_log_space(Relay_log_info* rli);
@ -1183,8 +1184,8 @@ void Relay_log_info::cleanup_context(THD *thd, bool error)
*/
if (error)
{
ha_autocommit_or_rollback(thd, 1); // if a "statement transaction"
end_trans(thd, ROLLBACK); // if a "real transaction"
trans_rollback_stmt(thd); // if a "statement transaction"
trans_rollback(thd); // if a "real transaction"
}
m_table_map.clear_tables();
slave_close_thread_tables(thd);

View File

@ -61,6 +61,7 @@
#include <my_dir.h>
#include "events.h"
#include "transaction.h"
/* WITH_NDBCLUSTER_STORAGE_ENGINE */
#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE
@ -3190,7 +3191,7 @@ static bool set_option_autocommit(THD *thd, set_var *var)
*/
if (var->save_result.ulong_value != 0 &&
(thd->options & OPTION_NOT_AUTOCOMMIT) &&
ha_commit(thd))
trans_commit(thd))
return 1;
if (var->save_result.ulong_value != 0)

View File

@ -34,6 +34,7 @@
#include "sql_repl.h"
#include "rpl_filter.h"
#include "repl_failsafe.h"
#include "transaction.h"
#include <thr_alarm.h>
#include <my_dir.h>
#include <sql_common.h>
@ -2430,7 +2431,7 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli)
else
{
exec_res= 0;
end_trans(thd, ROLLBACK);
trans_rollback(thd);
/* chance for concurrent connection to get more locks */
safe_sleep(thd, min(rli->trans_retries, MAX_SLAVE_RETRY_PAUSE),
(CHECK_KILLED_FUNC)sql_slave_killed, (void*)rli);

View File

@ -22,6 +22,7 @@
#include "sp_head.h"
#include "sp.h"
#include "sql_trigger.h"
#include "transaction.h"
#include "sql_prepare.h"
#include <m_ctype.h>
#include <my_dir.h>
@ -1402,7 +1403,7 @@ void close_thread_tables(THD *thd,
if (!(thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
{
thd->stmt_da->can_overwrite_status= TRUE;
ha_autocommit_or_rollback(thd, thd->is_error());
thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
thd->stmt_da->can_overwrite_status= FALSE;
/*

View File

@ -42,6 +42,7 @@
#include "sp_rcontext.h"
#include "sp_cache.h"
#include "transaction.h"
#include "debug_sync.h"
/*
@ -987,7 +988,8 @@ void THD::cleanup(void)
}
#endif
{
ha_rollback(this);
transaction.xid_state.xa_state= XA_NOTR;
trans_rollback(this);
xid_cache_delete(&transaction.xid_state);
}
locked_tables_list.unlock_locked_tables(this);

View File

@ -23,6 +23,7 @@
#include "sql_select.h"
#include "sp_head.h"
#include "sql_trigger.h"
#include "transaction.h"
/**
Implement DELETE SQL word.
@ -1071,8 +1072,8 @@ static bool mysql_truncate_by_delete(THD *thd, TABLE_LIST *table_list)
if (error)
{
DBUG_ASSERT(thd->stmt_da->is_error());
ha_autocommit_or_rollback(thd, TRUE);
end_active_trans(thd);
trans_rollback_stmt(thd);
trans_rollback(thd);
}
thd->current_stmt_binlog_row_based= save_binlog_row_based;
DBUG_RETURN(error);

View File

@ -17,6 +17,7 @@
/* Execute DO statement */
#include "mysql_priv.h"
#include "transaction.h"
bool mysql_do(THD *thd, List<Item> &values)
{
@ -36,7 +37,7 @@ bool mysql_do(THD *thd, List<Item> &values)
will clear the error and the rollback in the end of
dispatch_command() won't work.
*/
ha_autocommit_or_rollback(thd, thd->is_error());
trans_rollback_stmt(thd);
thd->clear_error(); // DO always is OK
}
my_ok(thd);

View File

@ -61,6 +61,7 @@
#include "sql_show.h"
#include "slave.h"
#include "rpl_mi.h"
#include "transaction.h"
#ifndef EMBEDDED_LIBRARY
static bool delayed_get_table(THD *thd, TABLE_LIST *table_list);
@ -2539,7 +2540,7 @@ pthread_handler_t handle_delayed_insert(void *arg)
*/
di->table->file->ha_release_auto_increment();
mysql_unlock_tables(thd, lock);
ha_autocommit_or_rollback(thd, 0);
trans_commit_stmt(thd);
di->group_count=0;
pthread_mutex_lock(&di->mutex);
}
@ -2559,7 +2560,7 @@ pthread_handler_t handle_delayed_insert(void *arg)
first call to ha_*_row() instead. Remove code that are used to
cover for the case outlined above.
*/
ha_autocommit_or_rollback(thd, 1);
trans_rollback_stmt(thd);
DBUG_LEAVE;
}
@ -3863,8 +3864,8 @@ bool select_create::send_eof()
*/
if (!table->s->tmp_table)
{
ha_autocommit_or_rollback(thd, 0);
end_active_trans(thd);
trans_commit_stmt(thd);
trans_commit_implicit(thd);
}
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);

View File

@ -28,6 +28,7 @@
#include "sp_cache.h"
#include "events.h"
#include "sql_trigger.h"
#include "transaction.h"
#include "sql_prepare.h"
#include "probes_mysql.h"
@ -88,113 +89,6 @@ const char *xa_state_names[]={
"NON-EXISTING", "ACTIVE", "IDLE", "PREPARED", "ROLLBACK ONLY"
};
/**
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.
*/
static bool xa_trans_rolled_back(XID_STATE *xid_state)
{
if (xid_state->rm_error)
{
switch (xid_state->rm_error) {
case ER_LOCK_WAIT_TIMEOUT:
my_error(ER_XA_RBTIMEOUT, MYF(0));
break;
case ER_LOCK_DEADLOCK:
my_error(ER_XA_RBDEADLOCK, MYF(0));
break;
default:
my_error(ER_XA_RBROLLBACK, MYF(0));
}
xid_state->xa_state= XA_ROLLBACK_ONLY;
}
return (xid_state->xa_state == XA_ROLLBACK_ONLY);
}
/**
Rollback work done on behalf of at ransaction branch.
*/
static bool xa_trans_rollback(THD *thd)
{
/*
Resource Manager error is meaningless at this point, as we perform
explicit rollback request by user. We must reset rm_error before
calling ha_rollback(), so thd->transaction.xid structure gets reset
by ha_rollback()/THD::transaction::cleanup().
*/
thd->transaction.xid_state.rm_error= 0;
bool status= test(ha_rollback(thd));
thd->options&= ~(ulong) OPTION_BEGIN;
thd->transaction.all.modified_non_trans_table= FALSE;
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
xid_cache_delete(&thd->transaction.xid_state);
thd->transaction.xid_state.xa_state= XA_NOTR;
return status;
}
bool end_active_trans(THD *thd)
{
int error=0;
DBUG_ENTER("end_active_trans");
if (unlikely(thd->in_sub_stmt))
{
my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
DBUG_RETURN(1);
}
if (thd->transaction.xid_state.xa_state != XA_NOTR)
{
my_error(ER_XAER_RMFAIL, MYF(0),
xa_state_names[thd->transaction.xid_state.xa_state]);
DBUG_RETURN(1);
}
if (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN |
OPTION_TABLE_LOCK))
{
DBUG_PRINT("info",("options: 0x%llx", thd->options));
/* Safety if one did "drop table" on locked tables */
if (!thd->locked_tables_mode)
thd->options&= ~OPTION_TABLE_LOCK;
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
if (ha_commit(thd))
error=1;
}
thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
thd->transaction.all.modified_non_trans_table= FALSE;
DBUG_RETURN(error);
}
bool begin_trans(THD *thd)
{
int error=0;
if (unlikely(thd->in_sub_stmt))
{
my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
return 1;
}
thd->locked_tables_list.unlock_locked_tables(thd);
if (end_active_trans(thd))
error= -1;
else
{
thd->options|= OPTION_BEGIN;
thd->server_status|= SERVER_STATUS_IN_TRANS;
}
return error;
}
#ifdef HAVE_REPLICATION
/**
@ -256,9 +150,9 @@ static bool opt_implicit_commit(THD *thd, uint mask)
if (!skip)
{
/* Commit or rollback the statement transaction. */
ha_autocommit_or_rollback(thd, thd->is_error());
thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
/* Commit the normal transaction if one is active. */
res= end_active_trans(thd);
res= trans_commit_implicit(thd);
}
DBUG_RETURN(res);
@ -691,80 +585,6 @@ void cleanup_items(Item *item)
DBUG_VOID_RETURN;
}
/**
Ends the current transaction and (maybe) begin the next.
@param thd Current thread
@param completion Completion type
@retval
0 OK
*/
int end_trans(THD *thd, enum enum_mysql_completiontype completion)
{
bool do_release= 0;
int res= 0;
DBUG_ENTER("end_trans");
if (unlikely(thd->in_sub_stmt))
{
my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
DBUG_RETURN(1);
}
if (thd->transaction.xid_state.xa_state != XA_NOTR)
{
my_error(ER_XAER_RMFAIL, MYF(0),
xa_state_names[thd->transaction.xid_state.xa_state]);
DBUG_RETURN(1);
}
switch (completion) {
case COMMIT:
/*
We don't use end_active_trans() here to ensure that this works
even if there is a problem with the OPTION_AUTO_COMMIT flag
(Which of course should never happen...)
*/
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
res= ha_commit(thd);
thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
thd->transaction.all.modified_non_trans_table= FALSE;
break;
case COMMIT_RELEASE:
do_release= 1; /* fall through */
case COMMIT_AND_CHAIN:
res= end_active_trans(thd);
if (!res && completion == COMMIT_AND_CHAIN)
res= begin_trans(thd);
break;
case ROLLBACK_RELEASE:
do_release= 1; /* fall through */
case ROLLBACK:
case ROLLBACK_AND_CHAIN:
{
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
if (ha_rollback(thd))
res= -1;
thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
thd->transaction.all.modified_non_trans_table= FALSE;
if (!res && (completion == ROLLBACK_AND_CHAIN))
res= begin_trans(thd);
break;
}
default:
res= -1;
my_error(ER_UNKNOWN_COM_ERROR, MYF(0));
DBUG_RETURN(-1);
}
if (res < 0)
my_error(thd->killed_errno(), MYF(0));
else if ((res == 0) && do_release)
thd->killed= THD::KILL_CONNECTION;
DBUG_RETURN(res);
}
#ifndef EMBEDDED_LIBRARY
/**
@ -1346,7 +1166,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
bool not_used;
status_var_increment(thd->status_var.com_stat[SQLCOM_FLUSH]);
ulong options= (ulong) (uchar) packet[0];
if (end_active_trans(thd))
if (trans_commit_implicit(thd))
break;
if (check_global_access(thd,RELOAD_ACL))
break;
@ -1374,7 +1194,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
#endif
if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, &not_used))
break;
if (end_active_trans(thd))
if (trans_commit_implicit(thd))
break;
my_ok(thd);
break;
@ -1532,7 +1352,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
/* If commit fails, we should be able to reset the OK status. */
thd->stmt_da->can_overwrite_status= TRUE;
ha_autocommit_or_rollback(thd, thd->is_error());
thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
thd->stmt_da->can_overwrite_status= FALSE;
thd->transaction.stmt.reset();
@ -3485,7 +3305,7 @@ end_with_restore_list:
thd->locked_tables_list.unlock_locked_tables(thd);
if (thd->options & OPTION_TABLE_LOCK)
{
end_active_trans(thd);
trans_commit_implicit(thd);
thd->options&= ~(OPTION_TABLE_LOCK);
}
if (thd->global_read_lock)
@ -3495,7 +3315,7 @@ end_with_restore_list:
case SQLCOM_LOCK_TABLES:
thd->locked_tables_list.unlock_locked_tables(thd);
/* we must end the trasaction first, regardless of anything */
if (end_active_trans(thd))
if (trans_commit_implicit(thd))
goto error;
if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables,
FALSE, UINT_MAX, FALSE))
@ -3524,8 +3344,8 @@ end_with_restore_list:
can free its locks if LOCK TABLES locked some tables before finding
that it can't lock a table in its list
*/
ha_autocommit_or_rollback(thd, 1);
end_active_trans(thd);
trans_rollback_stmt(thd);
trans_commit_implicit(thd);
thd->options&= ~(OPTION_TABLE_LOCK);
}
else
@ -4007,132 +3827,50 @@ end_with_restore_list:
break;
case SQLCOM_BEGIN:
if (thd->transaction.xid_state.xa_state != XA_NOTR)
{
my_error(ER_XAER_RMFAIL, MYF(0),
xa_state_names[thd->transaction.xid_state.xa_state]);
break;
}
if (begin_trans(thd))
if (trans_begin(thd, lex->start_transaction_opt))
goto error;
if (lex->start_transaction_opt & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT)
{
if (ha_start_consistent_snapshot(thd))
goto error;
}
my_ok(thd);
break;
case SQLCOM_COMMIT:
DBUG_ASSERT(thd->lock == NULL ||
thd->locked_tables_mode == LTM_LOCK_TABLES);
if (end_trans(thd, lex->tx_release ? COMMIT_RELEASE :
lex->tx_chain ? COMMIT_AND_CHAIN : COMMIT))
if (trans_commit(thd))
goto error;
/* Begin transaction with the same isolation level. */
if (lex->tx_chain && trans_begin(thd))
goto error;
/* Disconnect the current client connection. */
if (lex->tx_release)
thd->killed= THD::KILL_CONNECTION;
my_ok(thd);
break;
case SQLCOM_ROLLBACK:
DBUG_ASSERT(thd->lock == NULL ||
thd->locked_tables_mode == LTM_LOCK_TABLES);
if (end_trans(thd, lex->tx_release ? ROLLBACK_RELEASE :
lex->tx_chain ? ROLLBACK_AND_CHAIN : ROLLBACK))
if (trans_rollback(thd))
goto error;
/* Begin transaction with the same isolation level. */
if (lex->tx_chain && trans_begin(thd))
goto error;
/* Disconnect the current client connection. */
if (lex->tx_release)
thd->killed= THD::KILL_CONNECTION;
my_ok(thd);
break;
case SQLCOM_RELEASE_SAVEPOINT:
{
SAVEPOINT *sv;
for (sv=thd->transaction.savepoints; sv; sv=sv->prev)
{
if (my_strnncoll(system_charset_info,
(uchar *)lex->ident.str, lex->ident.length,
(uchar *)sv->name, sv->length) == 0)
break;
}
if (sv)
{
if (ha_release_savepoint(thd, sv))
res= TRUE; // cannot happen
else
my_ok(thd);
thd->transaction.savepoints=sv->prev;
}
else
my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", lex->ident.str);
if (trans_release_savepoint(thd, lex->ident))
goto error;
my_ok(thd);
break;
}
case SQLCOM_ROLLBACK_TO_SAVEPOINT:
{
SAVEPOINT *sv;
for (sv=thd->transaction.savepoints; sv; sv=sv->prev)
{
if (my_strnncoll(system_charset_info,
(uchar *)lex->ident.str, lex->ident.length,
(uchar *)sv->name, sv->length) == 0)
break;
}
if (sv)
{
if (ha_rollback_to_savepoint(thd, sv))
res= TRUE; // cannot happen
else
{
if (((thd->options & OPTION_KEEP_LOG) ||
thd->transaction.all.modified_non_trans_table) &&
!thd->slave_thread)
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_WARNING_NOT_COMPLETE_ROLLBACK,
ER(ER_WARNING_NOT_COMPLETE_ROLLBACK));
my_ok(thd);
}
thd->transaction.savepoints=sv;
}
else
my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", lex->ident.str);
if (trans_rollback_to_savepoint(thd, lex->ident))
goto error;
my_ok(thd);
break;
}
case SQLCOM_SAVEPOINT:
if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) ||
thd->in_sub_stmt) || !opt_using_transactions)
my_ok(thd);
else
{
SAVEPOINT **sv, *newsv;
for (sv=&thd->transaction.savepoints; *sv; sv=&(*sv)->prev)
{
if (my_strnncoll(system_charset_info,
(uchar *)lex->ident.str, lex->ident.length,
(uchar *)(*sv)->name, (*sv)->length) == 0)
break;
}
if (*sv) /* old savepoint of the same name exists */
{
newsv=*sv;
ha_release_savepoint(thd, *sv); // it cannot fail
*sv=(*sv)->prev;
}
else if ((newsv=(SAVEPOINT *) alloc_root(&thd->transaction.mem_root,
savepoint_alloc_size)) == 0)
{
my_error(ER_OUT_OF_RESOURCES, MYF(0));
break;
}
newsv->name=strmake_root(&thd->transaction.mem_root,
lex->ident.str, lex->ident.length);
newsv->length=lex->ident.length;
/*
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 (ha_savepoint(thd, newsv))
res= TRUE;
else
{
newsv->prev=thd->transaction.savepoints;
thd->transaction.savepoints=newsv;
my_ok(thd);
}
}
if (trans_savepoint(thd, lex->ident))
goto error;
my_ok(thd);
break;
case SQLCOM_CREATE_PROCEDURE:
case SQLCOM_CREATE_SPFUNCTION:
@ -4456,7 +4194,7 @@ create_sp_error:
lex->sql_command == SQLCOM_DROP_PROCEDURE, 0))
goto error;
if (end_active_trans(thd))
if (trans_commit_implicit(thd))
goto error;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (sp_automatic_privileges && !opt_noacl &&
@ -4620,185 +4358,29 @@ create_sp_error:
break;
}
case SQLCOM_XA_START:
if (thd->transaction.xid_state.xa_state == XA_IDLE &&
thd->lex->xa_opt == XA_RESUME)
{
if (! thd->transaction.xid_state.xid.eq(thd->lex->xid))
{
my_error(ER_XAER_NOTA, MYF(0));
break;
}
thd->transaction.xid_state.xa_state=XA_ACTIVE;
my_ok(thd);
break;
}
if (thd->lex->xa_opt != XA_NONE)
{ // JOIN is not supported yet. TODO
my_error(ER_XAER_INVAL, MYF(0));
break;
}
if (thd->transaction.xid_state.xa_state != XA_NOTR)
{
my_error(ER_XAER_RMFAIL, MYF(0),
xa_state_names[thd->transaction.xid_state.xa_state]);
break;
}
if (thd->locked_tables_mode || thd->active_transaction())
{
my_error(ER_XAER_OUTSIDE, MYF(0));
break;
}
if (xid_cache_search(thd->lex->xid))
{
my_error(ER_XAER_DUPID, MYF(0));
break;
}
DBUG_ASSERT(thd->transaction.xid_state.xid.is_null());
thd->transaction.xid_state.xa_state=XA_ACTIVE;
thd->transaction.xid_state.rm_error= 0;
thd->transaction.xid_state.xid.set(thd->lex->xid);
xid_cache_insert(&thd->transaction.xid_state);
thd->transaction.all.modified_non_trans_table= FALSE;
thd->options= ((thd->options & ~(OPTION_KEEP_LOG)) | OPTION_BEGIN);
thd->server_status|= SERVER_STATUS_IN_TRANS;
if (trans_xa_start(thd))
goto error;
my_ok(thd);
break;
case SQLCOM_XA_END:
/* fake it */
if (thd->lex->xa_opt != XA_NONE)
{ // SUSPEND and FOR MIGRATE are not supported yet. TODO
my_error(ER_XAER_INVAL, MYF(0));
break;
}
if (thd->transaction.xid_state.xa_state != XA_ACTIVE)
{
my_error(ER_XAER_RMFAIL, MYF(0),
xa_state_names[thd->transaction.xid_state.xa_state]);
break;
}
if (!thd->transaction.xid_state.xid.eq(thd->lex->xid))
{
my_error(ER_XAER_NOTA, MYF(0));
break;
}
if (xa_trans_rolled_back(&thd->transaction.xid_state))
break;
thd->transaction.xid_state.xa_state=XA_IDLE;
if (trans_xa_end(thd))
goto error;
my_ok(thd);
break;
case SQLCOM_XA_PREPARE:
if (thd->transaction.xid_state.xa_state != XA_IDLE)
{
my_error(ER_XAER_RMFAIL, MYF(0),
xa_state_names[thd->transaction.xid_state.xa_state]);
break;
}
if (!thd->transaction.xid_state.xid.eq(thd->lex->xid))
{
my_error(ER_XAER_NOTA, MYF(0));
break;
}
if (ha_prepare(thd))
{
my_error(ER_XA_RBROLLBACK, MYF(0));
xid_cache_delete(&thd->transaction.xid_state);
thd->transaction.xid_state.xa_state=XA_NOTR;
break;
}
thd->transaction.xid_state.xa_state=XA_PREPARED;
if (trans_xa_prepare(thd))
goto error;
my_ok(thd);
break;
case SQLCOM_XA_COMMIT:
if (!thd->transaction.xid_state.xid.eq(thd->lex->xid))
{
XID_STATE *xs=xid_cache_search(thd->lex->xid);
if (!xs || xs->in_thd)
my_error(ER_XAER_NOTA, MYF(0));
else if (xa_trans_rolled_back(xs))
{
ha_commit_or_rollback_by_xid(thd->lex->xid, 0);
xid_cache_delete(xs);
break;
}
else
{
ha_commit_or_rollback_by_xid(thd->lex->xid, 1);
xid_cache_delete(xs);
my_ok(thd);
}
break;
}
if (xa_trans_rolled_back(&thd->transaction.xid_state))
{
xa_trans_rollback(thd);
break;
}
if (thd->transaction.xid_state.xa_state == XA_IDLE &&
thd->lex->xa_opt == XA_ONE_PHASE)
{
int r;
if ((r= ha_commit(thd)))
my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0));
else
my_ok(thd);
}
else if (thd->transaction.xid_state.xa_state == XA_PREPARED &&
thd->lex->xa_opt == XA_NONE)
{
if (wait_if_global_read_lock(thd, 0, 0))
{
ha_rollback(thd);
my_error(ER_XAER_RMERR, MYF(0));
}
else
{
if (ha_commit_one_phase(thd, 1))
my_error(ER_XAER_RMERR, MYF(0));
else
my_ok(thd);
start_waiting_global_read_lock(thd);
}
}
else
{
my_error(ER_XAER_RMFAIL, MYF(0),
xa_state_names[thd->transaction.xid_state.xa_state]);
break;
}
thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
thd->transaction.all.modified_non_trans_table= FALSE;
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
xid_cache_delete(&thd->transaction.xid_state);
thd->transaction.xid_state.xa_state=XA_NOTR;
if (trans_xa_commit(thd))
goto error;
my_ok(thd);
break;
case SQLCOM_XA_ROLLBACK:
if (!thd->transaction.xid_state.xid.eq(thd->lex->xid))
{
XID_STATE *xs=xid_cache_search(thd->lex->xid);
if (!xs || xs->in_thd)
my_error(ER_XAER_NOTA, MYF(0));
else
{
bool ok= !xa_trans_rolled_back(xs);
ha_commit_or_rollback_by_xid(thd->lex->xid, 0);
xid_cache_delete(xs);
if (ok)
my_ok(thd);
}
break;
}
if (thd->transaction.xid_state.xa_state != XA_IDLE &&
thd->transaction.xid_state.xa_state != XA_PREPARED &&
thd->transaction.xid_state.xa_state != XA_ROLLBACK_ONLY)
{
my_error(ER_XAER_RMFAIL, MYF(0),
xa_state_names[thd->transaction.xid_state.xa_state]);
break;
}
if (xa_trans_rollback(thd))
my_error(ER_XAER_RMERR, MYF(0));
else
my_ok(thd);
if (trans_xa_rollback(thd))
goto error;
my_ok(thd);
break;
case SQLCOM_XA_RECOVER:
res= mysql_xa_recover(thd);

View File

@ -50,6 +50,7 @@
#include <errno.h>
#include <m_ctype.h>
#include "my_md5.h"
#include "transaction.h"
#ifdef WITH_PARTITION_STORAGE_ENGINE
#include "ha_partition.h"
@ -4327,8 +4328,8 @@ static int fast_end_partition(THD *thd, ulonglong copied,
if (!is_empty)
query_cache_invalidate3(thd, table_list, 0);
error= ha_autocommit_or_rollback(thd, 0);
if (end_active_trans(thd))
error= trans_commit_stmt(thd);
if (trans_commit_implicit(thd))
error= 1;
if (error)

View File

@ -22,6 +22,7 @@
#include "sp_head.h"
#include "sql_trigger.h"
#include "sql_show.h"
#include "transaction.h"
#ifdef __WIN__
#include <io.h>
@ -4668,8 +4669,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
DBUG_PRINT("admin", ("calling prepare_func"));
switch ((*prepare_func)(thd, table, check_opt)) {
case 1: // error, message written to net
ha_autocommit_or_rollback(thd, 1);
end_trans(thd, ROLLBACK);
trans_rollback_stmt(thd);
trans_rollback(thd);
close_thread_tables(thd);
DBUG_PRINT("admin", ("simple error, admin next table"));
continue;
@ -4740,8 +4741,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
length= my_snprintf(buff, sizeof(buff), ER(ER_OPEN_AS_READONLY),
table_name);
protocol->store(buff, length, system_charset_info);
ha_autocommit_or_rollback(thd, 0);
end_trans(thd, COMMIT);
trans_commit_stmt(thd);
trans_commit(thd);
close_thread_tables(thd);
lex->reset_query_tables_list(FALSE);
table->table=0; // For query cache
@ -4790,7 +4791,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
HA_ADMIN_NEEDS_ALTER))
{
DBUG_PRINT("admin", ("recreating table"));
ha_autocommit_or_rollback(thd, 1);
trans_rollback_stmt(thd);
close_thread_tables(thd);
tmp_disable_binlog(thd); // binlogging is done by caller if wanted
result_code= mysql_recreate_table(thd, table);
@ -4910,7 +4911,7 @@ send_result_message:
system_charset_info);
if (protocol->write())
goto err;
ha_autocommit_or_rollback(thd, 0);
trans_commit_stmt(thd);
close_thread_tables(thd);
DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze..."));
TABLE_LIST *save_next_local= table->next_local,
@ -4927,7 +4928,7 @@ send_result_message:
*/
if (thd->stmt_da->is_ok())
thd->stmt_da->reset_diagnostics_area();
ha_autocommit_or_rollback(thd, 0);
trans_commit_stmt(thd);
close_thread_tables(thd);
if (!result_code) // recreation went ok
{
@ -5022,8 +5023,8 @@ send_result_message:
query_cache_invalidate3(thd, table->table, 0);
}
}
ha_autocommit_or_rollback(thd, 0);
end_trans(thd, COMMIT);
trans_commit_stmt(thd);
trans_commit_implicit(thd);
close_thread_tables(thd);
table->table=0; // For query cache
if (protocol->write())
@ -5034,8 +5035,8 @@ send_result_message:
DBUG_RETURN(FALSE);
err:
ha_autocommit_or_rollback(thd, 1);
end_trans(thd, ROLLBACK);
trans_rollback_stmt(thd);
trans_rollback(thd);
close_thread_tables(thd); // Shouldn't be needed
if (table)
table->table=0;
@ -5550,15 +5551,15 @@ mysql_discard_or_import_tablespace(THD *thd,
query_cache_invalidate3(thd, table_list, 0);
/* The ALTER TABLE is always in its own transaction */
error = ha_autocommit_or_rollback(thd, 0);
if (end_active_trans(thd))
error= trans_commit_stmt(thd);
if (trans_commit_implicit(thd))
error=1;
if (error)
goto err;
write_bin_log(thd, FALSE, thd->query(), thd->query_length());
err:
ha_autocommit_or_rollback(thd, error);
trans_rollback_stmt(thd);
thd->tablespace_op=FALSE;
if (error == 0)
@ -7208,8 +7209,8 @@ view_err:
thd_proc_info(thd, "manage keys");
alter_table_manage_keys(table, table->file->indexes_are_disabled(),
alter_info->keys_onoff);
error= ha_autocommit_or_rollback(thd, 0);
if (end_active_trans(thd))
error= trans_commit_stmt(thd);
if (trans_commit_implicit(thd))
error= 1;
}
thd->count_cuted_fields= CHECK_FIELD_IGNORE;
@ -7297,7 +7298,7 @@ view_err:
/* Need to commit before a table is unlocked (NDB requirement). */
DBUG_PRINT("info", ("Committing before unlocking table"));
if (ha_autocommit_or_rollback(thd, 0) || end_active_trans(thd))
if (trans_commit_stmt(thd) || trans_commit_implicit(thd))
goto err_new_table_cleanup;
committed= 1;
}
@ -7803,9 +7804,9 @@ copy_data_between_tables(TABLE *from,TABLE *to,
Ensure that the new table is saved properly to disk so that we
can do a rename
*/
if (ha_autocommit_or_rollback(thd, 0))
if (trans_commit_stmt(thd))
error=1;
if (end_active_trans(thd))
if (trans_commit_implicit(thd))
error=1;
err:

648
sql/transaction.cc Normal file
View File

@ -0,0 +1,648 @@
/* Copyright (C) 2008 Sun/MySQL
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifdef USE_PRAGMA_IMPLEMENTATION
#pragma implementation // gcc: Class implementation
#endif
#include "mysql_priv.h"
#include "transaction.h"
#include "rpl_handler.h"
/* Conditions under which the transaction state must not change. */
static bool trans_check(THD *thd)
{
enum xa_states xa_state= thd->transaction.xid_state.xa_state;
DBUG_ENTER("trans_check");
if (unlikely(thd->in_sub_stmt))
my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
if (xa_state != XA_NOTR)
my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
else
DBUG_RETURN(FALSE);
DBUG_RETURN(TRUE);
}
/**
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.
*/
static bool xa_trans_rolled_back(XID_STATE *xid_state)
{
if (xid_state->rm_error)
{
switch (xid_state->rm_error) {
case ER_LOCK_WAIT_TIMEOUT:
my_error(ER_XA_RBTIMEOUT, MYF(0));
break;
case ER_LOCK_DEADLOCK:
my_error(ER_XA_RBDEADLOCK, MYF(0));
break;
default:
my_error(ER_XA_RBROLLBACK, MYF(0));
}
xid_state->xa_state= XA_ROLLBACK_ONLY;
}
return (xid_state->xa_state == XA_ROLLBACK_ONLY);
}
/**
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);
thd->locked_tables_list.unlock_locked_tables(thd);
DBUG_ASSERT(!thd->locked_tables_mode);
if (trans_commit_implicit(thd))
DBUG_RETURN(TRUE);
thd->options|= OPTION_BEGIN;
thd->server_status|= SERVER_STATUS_IN_TRANS;
if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT)
res= ha_start_consistent_snapshot(thd);
DBUG_RETURN(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;
DBUG_ENTER("trans_commit");
if (trans_check(thd))
DBUG_RETURN(TRUE);
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
res= ha_commit_trans(thd, TRUE);
if (res)
/*
if res is non-zero, then ha_commit_trans has rolled back the
transaction, so the hooks for rollback will be called.
*/
RUN_HOOK(transaction, after_rollback, (thd, FALSE));
else
RUN_HOOK(transaction, after_commit, (thd, FALSE));
thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
thd->transaction.all.modified_non_trans_table= FALSE;
thd->lex->start_transaction_opt= 0;
DBUG_RETURN(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->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN |
OPTION_TABLE_LOCK))
{
/* Safety if one did "drop table" on locked tables */
if (!thd->locked_tables_mode)
thd->options&= ~OPTION_TABLE_LOCK;
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
res= test(ha_commit_trans(thd, TRUE));
}
thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
thd->transaction.all.modified_non_trans_table= FALSE;
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;
DBUG_ENTER("trans_rollback");
if (trans_check(thd))
DBUG_RETURN(TRUE);
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
res= ha_rollback_trans(thd, TRUE);
RUN_HOOK(transaction, after_rollback, (thd, FALSE));
thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
thd->transaction.all.modified_non_trans_table= FALSE;
thd->lex->start_transaction_opt= 0;
DBUG_RETURN(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;
if (thd->transaction.stmt.ha_list)
res= ha_commit_trans(thd, FALSE);
if (res)
/*
if res is non-zero, then ha_commit_trans has rolled back the
transaction, so the hooks for rollback will be called.
*/
RUN_HOOK(transaction, after_rollback, (thd, FALSE));
else
RUN_HOOK(transaction, after_commit, (thd, FALSE));
DBUG_RETURN(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");
if (thd->transaction.stmt.ha_list)
{
ha_rollback_trans(thd, FALSE);
if (thd->transaction_rollback_request && !thd->in_sub_stmt)
ha_rollback_trans(thd, TRUE);
}
RUN_HOOK(transaction, after_rollback, (thd, FALSE));
DBUG_RETURN(FALSE);
}
/* Find a named savepoint in the current transaction. */
static SAVEPOINT **
find_savepoint(THD *thd, LEX_STRING name)
{
SAVEPOINT **sv= &thd->transaction.savepoints;
while (*sv)
{
if (my_strnncoll(system_charset_info, (uchar *) name.str, name.length,
(uchar *) (*sv)->name, (*sv)->length) == 0)
break;
sv= &(*sv)->prev;
}
return sv;
}
/**
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_STRING name)
{
SAVEPOINT **sv, *newsv;
DBUG_ENTER("trans_savepoint");
if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) ||
thd->in_sub_stmt) || !opt_using_transactions)
DBUG_RETURN(FALSE);
sv= find_savepoint(thd, name);
if (*sv) /* old savepoint of the same name exists */
{
newsv= *sv;
ha_release_savepoint(thd, *sv);
*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(TRUE);
}
newsv->name= strmake_root(&thd->transaction.mem_root, name.str, name.length);
newsv->length= name.length;
/*
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 (ha_savepoint(thd, newsv))
DBUG_RETURN(TRUE);
newsv->prev= thd->transaction.savepoints;
thd->transaction.savepoints= newsv;
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_STRING 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 (ha_rollback_to_savepoint(thd, sv))
res= TRUE;
else if (((thd->options & OPTION_KEEP_LOG) ||
thd->transaction.all.modified_non_trans_table) &&
!thd->slave_thread)
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_WARNING_NOT_COMPLETE_ROLLBACK,
ER(ER_WARNING_NOT_COMPLETE_ROLLBACK));
thd->transaction.savepoints= sv;
DBUG_RETURN(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_STRING 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(test(res));
}
/**
Starts an XA transaction with the given xid value.
@param thd Current thread
@retval FALSE Success
@retval TRUE Failure
*/
bool trans_xa_start(THD *thd)
{
enum xa_states xa_state= thd->transaction.xid_state.xa_state;
DBUG_ENTER("trans_xa_start");
if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_RESUME)
{
bool not_equal= !thd->transaction.xid_state.xid.eq(thd->lex->xid);
if (not_equal)
my_error(ER_XAER_NOTA, MYF(0));
else
thd->transaction.xid_state.xa_state= XA_ACTIVE;
DBUG_RETURN(not_equal);
}
/* TODO: JOIN is not supported yet. */
if (thd->lex->xa_opt != XA_NONE)
my_error(ER_XAER_INVAL, MYF(0));
else if (xa_state != XA_NOTR)
my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
else if (thd->locked_tables_mode || thd->active_transaction())
my_error(ER_XAER_OUTSIDE, MYF(0));
else if (xid_cache_search(thd->lex->xid))
my_error(ER_XAER_DUPID, MYF(0));
else if (!trans_begin(thd))
{
DBUG_ASSERT(thd->transaction.xid_state.xid.is_null());
thd->transaction.xid_state.xa_state= XA_ACTIVE;
thd->transaction.xid_state.rm_error= 0;
thd->transaction.xid_state.xid.set(thd->lex->xid);
xid_cache_insert(&thd->transaction.xid_state);
DBUG_RETURN(FALSE);
}
DBUG_RETURN(TRUE);
}
/**
Put a XA transaction in the IDLE state.
@param thd Current thread
@retval FALSE Success
@retval TRUE Failure
*/
bool trans_xa_end(THD *thd)
{
DBUG_ENTER("trans_xa_end");
/* TODO: SUSPEND and FOR MIGRATE are not supported yet. */
if (thd->lex->xa_opt != XA_NONE)
my_error(ER_XAER_INVAL, MYF(0));
else if (thd->transaction.xid_state.xa_state != XA_ACTIVE)
my_error(ER_XAER_RMFAIL, MYF(0),
xa_state_names[thd->transaction.xid_state.xa_state]);
else if (!thd->transaction.xid_state.xid.eq(thd->lex->xid))
my_error(ER_XAER_NOTA, MYF(0));
else if (!xa_trans_rolled_back(&thd->transaction.xid_state))
thd->transaction.xid_state.xa_state= XA_IDLE;
DBUG_RETURN(thd->transaction.xid_state.xa_state != XA_IDLE);
}
/**
Put a XA transaction in the PREPARED state.
@param thd Current thread
@retval FALSE Success
@retval TRUE Failure
*/
bool trans_xa_prepare(THD *thd)
{
DBUG_ENTER("trans_xa_prepare");
if (thd->transaction.xid_state.xa_state != XA_IDLE)
my_error(ER_XAER_RMFAIL, MYF(0),
xa_state_names[thd->transaction.xid_state.xa_state]);
else if (!thd->transaction.xid_state.xid.eq(thd->lex->xid))
my_error(ER_XAER_NOTA, MYF(0));
else if (ha_prepare(thd))
{
xid_cache_delete(&thd->transaction.xid_state);
thd->transaction.xid_state.xa_state= XA_NOTR;
my_error(ER_XA_RBROLLBACK, MYF(0));
}
else
thd->transaction.xid_state.xa_state= XA_PREPARED;
DBUG_RETURN(thd->transaction.xid_state.xa_state != XA_PREPARED);
}
/**
Commit and terminate the a XA transaction.
@param thd Current thread
@retval FALSE Success
@retval TRUE Failure
*/
bool trans_xa_commit(THD *thd)
{
bool res= TRUE;
enum xa_states xa_state= thd->transaction.xid_state.xa_state;
DBUG_ENTER("trans_xa_commit");
if (!thd->transaction.xid_state.xid.eq(thd->lex->xid))
{
XID_STATE *xs= xid_cache_search(thd->lex->xid);
res= !xs || xs->in_thd;
if (res)
my_error(ER_XAER_NOTA, MYF(0));
else
{
res= xa_trans_rolled_back(xs);
ha_commit_or_rollback_by_xid(thd->lex->xid, !res);
xid_cache_delete(xs);
}
DBUG_RETURN(res);
}
if (xa_trans_rolled_back(&thd->transaction.xid_state))
{
if ((res= test(ha_rollback_trans(thd, TRUE))))
my_error(ER_XAER_RMERR, MYF(0));
}
else if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_ONE_PHASE)
{
int r= ha_commit_trans(thd, TRUE);
if ((res= test(r)))
my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0));
}
else if (xa_state == XA_PREPARED && thd->lex->xa_opt == XA_NONE)
{
if (wait_if_global_read_lock(thd, 0, 0))
{
ha_rollback_trans(thd, TRUE);
my_error(ER_XAER_RMERR, MYF(0));
}
else
{
res= test(ha_commit_one_phase(thd, 1));
if (res)
my_error(ER_XAER_RMERR, MYF(0));
start_waiting_global_read_lock(thd);
}
}
else
{
my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
DBUG_RETURN(TRUE);
}
thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
thd->transaction.all.modified_non_trans_table= FALSE;
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
xid_cache_delete(&thd->transaction.xid_state);
thd->transaction.xid_state.xa_state= XA_NOTR;
DBUG_RETURN(res);
}
/**
Roll back and terminate a XA transaction.
@param thd Current thread
@retval FALSE Success
@retval TRUE Failure
*/
bool trans_xa_rollback(THD *thd)
{
bool res= TRUE;
enum xa_states xa_state= thd->transaction.xid_state.xa_state;
DBUG_ENTER("trans_xa_rollback");
if (!thd->transaction.xid_state.xid.eq(thd->lex->xid))
{
XID_STATE *xs= xid_cache_search(thd->lex->xid);
if (!xs || xs->in_thd)
my_error(ER_XAER_NOTA, MYF(0));
else
{
xa_trans_rolled_back(xs);
ha_commit_or_rollback_by_xid(thd->lex->xid, 0);
xid_cache_delete(xs);
}
DBUG_RETURN(thd->stmt_da->is_error());
}
if (xa_state != XA_IDLE && xa_state != XA_PREPARED && xa_state != XA_ROLLBACK_ONLY)
{
my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]);
DBUG_RETURN(TRUE);
}
/*
Resource Manager error is meaningless at this point, as we perform
explicit rollback request by user. We must reset rm_error before
calling ha_rollback(), so thd->transaction.xid structure gets reset
by ha_rollback()/THD::transaction::cleanup().
*/
thd->transaction.xid_state.rm_error= 0;
if ((res= test(ha_rollback_trans(thd, TRUE))))
my_error(ER_XAER_RMERR, MYF(0));
thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG);
thd->transaction.all.modified_non_trans_table= FALSE;
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
xid_cache_delete(&thd->transaction.xid_state);
thd->transaction.xid_state.xa_state= XA_NOTR;
DBUG_RETURN(res);
}

46
sql/transaction.h Normal file
View File

@ -0,0 +1,46 @@
/* Copyright (C) 2008 Sun/MySQL
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifndef TRANSACTION_H
#define TRANSACTION_H
#ifdef USE_PRAGMA_INTERFACE
#pragma interface /* gcc class implementation */
#endif
#include <my_global.h>
#include <m_string.h>
class THD;
bool trans_begin(THD *thd, uint flags= 0);
bool trans_commit(THD *thd);
bool trans_commit_implicit(THD *thd);
bool trans_rollback(THD *thd);
bool trans_commit_stmt(THD *thd);
bool trans_rollback_stmt(THD *thd);
bool trans_savepoint(THD *thd, LEX_STRING name);
bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name);
bool trans_release_savepoint(THD *thd, LEX_STRING name);
bool trans_xa_start(THD *thd);
bool trans_xa_end(THD *thd);
bool trans_xa_prepare(THD *thd);
bool trans_xa_commit(THD *thd);
bool trans_xa_rollback(THD *thd);
#endif /* TRANSACTION_H */