/*****************************************************************************

Copyright (c) 1996, 2009, Innobase Oy. All Rights Reserved.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA

*****************************************************************************/

/**************************************************//**
@file include/trx0sys.ic
Transaction system

Created 3/26/1996 Heikki Tuuri
*******************************************************/

#include "trx0trx.h"
#include "data0type.h"
#ifndef UNIV_HOTBACKUP
# include "srv0srv.h"
# include "mtr0log.h"

/* The typedef for rseg slot in the file copy */
typedef byte	trx_sysf_rseg_t;

/* Rollback segment specification slot offsets */
/*-------------------------------------------------------------*/
#define	TRX_SYS_RSEG_SPACE	0	/* space where the segment
					header is placed; starting with
					MySQL/InnoDB 5.1.7, this is
					UNIV_UNDEFINED if the slot is unused */
#define	TRX_SYS_RSEG_PAGE_NO	4	/*  page number where the segment
					header is placed; this is FIL_NULL
					if the slot is unused */
/*-------------------------------------------------------------*/
/* Size of a rollback segment specification slot */
#define TRX_SYS_RSEG_SLOT_SIZE	8

/*****************************************************************//**
Writes the value of max_trx_id to the file based trx system header. */
UNIV_INTERN
void
trx_sys_flush_max_trx_id(void);
/*==========================*/

/***************************************************************//**
Checks if a page address is the trx sys header page.
@return	TRUE if trx sys header page */
UNIV_INLINE
ibool
trx_sys_hdr_page(
/*=============*/
	ulint	space,	/*!< in: space */
	ulint	page_no)/*!< in: page number */
{
	if ((space == TRX_SYS_SPACE) && (page_no == TRX_SYS_PAGE_NO)) {

		return(TRUE);
	}

	return(FALSE);
}

/***************************************************************//**
Checks if a space is the system tablespaces.
@return TRUE if system tablespace */
UNIV_INLINE
ibool
trx_sys_sys_space(
/*==============*/
	ulint	space)	/*!< in: space */
{
	if (srv_doublewrite_file) {
		/* several spaces are reserved */
		return((ibool)(space <= TRX_SYS_SPACE_MAX));
	} else {
		return((ibool)(space == TRX_SYS_SPACE));
	}
}

/***************************************************************//**
Checks if a space is the doublewrite tablespace.
@return TRUE if doublewrite tablespace */
UNIV_INLINE
ibool
trx_sys_doublewrite_space(
/*======================*/
	ulint	space)	/*!< in: space */
{
	if (srv_doublewrite_file) {
		/* doublewrite buffer is separated */
		return((ibool)(space == TRX_DOUBLEWRITE_SPACE));
	} else {
		return((ibool)(space == TRX_SYS_SPACE));
	}
}

/***************************************************************//**
Gets the pointer in the nth slot of the rseg array.
@return	pointer to rseg object, NULL if slot not in use */
UNIV_INLINE
trx_rseg_t*
trx_sys_get_nth_rseg(
/*=================*/
	trx_sys_t*	sys,	/*!< in: trx system */
	ulint		n)	/*!< in: index of slot */
{
	ut_ad(mutex_own(&(kernel_mutex)));
	ut_ad(n < TRX_SYS_N_RSEGS);

	return(sys->rseg_array[n]);
}

/***************************************************************//**
Sets the pointer in the nth slot of the rseg array. */
UNIV_INLINE
void
trx_sys_set_nth_rseg(
/*=================*/
	trx_sys_t*	sys,	/*!< in: trx system */
	ulint		n,	/*!< in: index of slot */
	trx_rseg_t*	rseg)	/*!< in: pointer to rseg object, NULL if slot
				not in use */
{
	ut_ad(n < TRX_SYS_N_RSEGS);

	sys->rseg_array[n] = rseg;
}

/**********************************************************************//**
Gets a pointer to the transaction system header and x-latches its page.
@return	pointer to system header, page x-latched. */
UNIV_INLINE
trx_sysf_t*
trx_sysf_get(
/*=========*/
	mtr_t*	mtr)	/*!< in: mtr */
{
	buf_block_t*	block;
	trx_sysf_t*	header;

	ut_ad(mtr);

	block = buf_page_get(TRX_SYS_SPACE, 0, TRX_SYS_PAGE_NO,
			     RW_X_LATCH, mtr);
	buf_block_dbg_add_level(block, SYNC_TRX_SYS_HEADER);

	header = TRX_SYS + buf_block_get_frame(block);

	return(header);
}

/*****************************************************************//**
Gets the space of the nth rollback segment slot in the trx system
file copy.
@return	space id */
UNIV_INLINE
ulint
trx_sysf_rseg_get_space(
/*====================*/
	trx_sysf_t*	sys_header,	/*!< in: trx sys header */
	ulint		i,		/*!< in: slot index == rseg id */
	mtr_t*		mtr)		/*!< in: mtr */
{
	ut_ad(mutex_own(&(kernel_mutex)));
	ut_ad(sys_header);
	ut_ad(i < TRX_SYS_N_RSEGS);

	return(mtr_read_ulint(sys_header + TRX_SYS_RSEGS
			      + i * TRX_SYS_RSEG_SLOT_SIZE
			      + TRX_SYS_RSEG_SPACE, MLOG_4BYTES, mtr));
}

/*****************************************************************//**
Gets the page number of the nth rollback segment slot in the trx system
header.
@return	page number, FIL_NULL if slot unused */
UNIV_INLINE
ulint
trx_sysf_rseg_get_page_no(
/*======================*/
	trx_sysf_t*	sys_header,	/*!< in: trx system header */
	ulint		i,		/*!< in: slot index == rseg id */
	mtr_t*		mtr)		/*!< in: mtr */
{
	ut_ad(sys_header);
	ut_ad(mutex_own(&(kernel_mutex)));
	ut_ad(i < TRX_SYS_N_RSEGS);

	return(mtr_read_ulint(sys_header + TRX_SYS_RSEGS
			      + i * TRX_SYS_RSEG_SLOT_SIZE
			      + TRX_SYS_RSEG_PAGE_NO, MLOG_4BYTES, mtr));
}

/*****************************************************************//**
Sets the space id of the nth rollback segment slot in the trx system
file copy. */
UNIV_INLINE
void
trx_sysf_rseg_set_space(
/*====================*/
	trx_sysf_t*	sys_header,	/*!< in: trx sys file copy */
	ulint		i,		/*!< in: slot index == rseg id */
	ulint		space,		/*!< in: space id */
	mtr_t*		mtr)		/*!< in: mtr */
{
	ut_ad(mutex_own(&(kernel_mutex)));
	ut_ad(sys_header);
	ut_ad(i < TRX_SYS_N_RSEGS);

	mlog_write_ulint(sys_header + TRX_SYS_RSEGS
			 + i * TRX_SYS_RSEG_SLOT_SIZE
			 + TRX_SYS_RSEG_SPACE,
			 space,
			 MLOG_4BYTES, mtr);
}

/*****************************************************************//**
Sets the page number of the nth rollback segment slot in the trx system
header. */
UNIV_INLINE
void
trx_sysf_rseg_set_page_no(
/*======================*/
	trx_sysf_t*	sys_header,	/*!< in: trx sys header */
	ulint		i,		/*!< in: slot index == rseg id */
	ulint		page_no,	/*!< in: page number, FIL_NULL if the
					slot is reset to unused */
	mtr_t*		mtr)		/*!< in: mtr */
{
	ut_ad(mutex_own(&(kernel_mutex)));
	ut_ad(sys_header);
	ut_ad(i < TRX_SYS_N_RSEGS);

	mlog_write_ulint(sys_header + TRX_SYS_RSEGS
			 + i * TRX_SYS_RSEG_SLOT_SIZE
			 + TRX_SYS_RSEG_PAGE_NO,
			 page_no,
			 MLOG_4BYTES, mtr);
}
#endif /* !UNIV_HOTBACKUP */

/*****************************************************************//**
Writes a trx id to an index page. In case that the id size changes in
some future version, this function should be used instead of
mach_write_... */
UNIV_INLINE
void
trx_write_trx_id(
/*=============*/
	byte*		ptr,	/*!< in: pointer to memory where written */
	trx_id_t	id)	/*!< in: id */
{
#if DATA_TRX_ID_LEN != 6
# error "DATA_TRX_ID_LEN != 6"
#endif
	mach_write_to_6(ptr, id);
}

#ifndef UNIV_HOTBACKUP
/*****************************************************************//**
Reads a trx id from an index page. In case that the id size changes in
some future version, this function should be used instead of
mach_read_...
@return	id */
UNIV_INLINE
trx_id_t
trx_read_trx_id(
/*============*/
	const byte*	ptr)	/*!< in: pointer to memory from where to read */
{
#if DATA_TRX_ID_LEN != 6
# error "DATA_TRX_ID_LEN != 6"
#endif
	return(mach_read_from_6(ptr));
}

/****************************************************************//**
Looks for the trx handle with the given id in trx_list.
@return	the trx handle or NULL if not found */
UNIV_INLINE
trx_t*
trx_get_on_id(
/*==========*/
	trx_id_t	trx_id)	/*!< in: trx id to search for */
{
	trx_t*	trx;

	ut_ad(mutex_own(&(kernel_mutex)));

	trx = UT_LIST_GET_FIRST(trx_sys->trx_list);

	while (trx != NULL) {
		if (0 == ut_dulint_cmp(trx_id, trx->id)) {

			return(trx);
		}

		trx = UT_LIST_GET_NEXT(trx_list, trx);
	}

	return(NULL);
}

/****************************************************************//**
Returns the minumum trx id in trx list. This is the smallest id for which
the trx can possibly be active. (But, you must look at the trx->conc_state to
find out if the minimum trx id transaction itself is active, or already
committed.)
@return	the minimum trx id, or trx_sys->max_trx_id if the trx list is empty */
UNIV_INLINE
trx_id_t
trx_list_get_min_trx_id(void)
/*=========================*/
{
	trx_t*	trx;

	ut_ad(mutex_own(&(kernel_mutex)));

	trx = UT_LIST_GET_LAST(trx_sys->trx_list);

	if (trx == NULL) {

		return(trx_sys->max_trx_id);
	}

	return(trx->id);
}

/****************************************************************//**
Checks if a transaction with the given id is active.
IMPORTANT ASSUMPTION:
	It is assumed that this function is only used for the purpose of
	determining if locks need to be created for the input transaction.
	So if innobase_release_locks_early global option is set, and if
	the transaction is already in prepared state, this returns FALSE,
	as locks are no longer needed for the transaction.
@return	TRUE if active */
UNIV_INLINE
ibool
trx_is_active(
/*==========*/
	trx_id_t	trx_id)	/*!< in: trx id of the transaction */
{
	trx_t*	trx;

	ut_ad(mutex_own(&(kernel_mutex)));

	if (ut_dulint_cmp(trx_id, trx_list_get_min_trx_id()) < 0) {

		return(FALSE);
	}

	if (ut_dulint_cmp(trx_id, trx_sys->max_trx_id) >= 0) {

		/* There must be corruption: we return TRUE because this
		function is only called by lock_clust_rec_some_has_impl()
		and row_vers_impl_x_locked_off_kernel() and they have
		diagnostic prints in this case */

		return(TRUE);
	}

	trx = trx_get_on_id(trx_id);
	if (trx && (trx->conc_state == TRX_ACTIVE
		    || (trx->conc_state == TRX_PREPARED &&
			!innobase_release_locks_early))) {

		return(TRUE);
	}

	return(FALSE);
}

/*****************************************************************//**
Allocates a new transaction id.
@return	new, allocated trx id */
UNIV_INLINE
trx_id_t
trx_sys_get_new_trx_id(void)
/*========================*/
{
	trx_id_t	id;

	ut_ad(mutex_own(&kernel_mutex));

	/* VERY important: after the database is started, max_trx_id value is
	divisible by TRX_SYS_TRX_ID_WRITE_MARGIN, and the following if
	will evaluate to TRUE when this function is first time called,
	and the value for trx id will be written to disk-based header!
	Thus trx id values will not overlap when the database is
	repeatedly started! */

	if (ut_dulint_get_low(trx_sys->max_trx_id)
	    % TRX_SYS_TRX_ID_WRITE_MARGIN == 0) {

		trx_sys_flush_max_trx_id();
	}

	id = trx_sys->max_trx_id;

	UT_DULINT_INC(trx_sys->max_trx_id);

	return(id);
}

/*****************************************************************//**
Allocates a new transaction number.
@return	new, allocated trx number */
UNIV_INLINE
trx_id_t
trx_sys_get_new_trx_no(void)
/*========================*/
{
	ut_ad(mutex_own(&kernel_mutex));

	return(trx_sys_get_new_trx_id());
}
#endif /* !UNIV_HOTBACKUP */