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

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

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

/******************************************************
Transaction system

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

#include "srv0srv.h"
#include "trx0trx.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 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 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. */
UNIV_INLINE
ibool
trx_sys_hdr_page(
/*=============*/
			/* out: TRUE if trx sys header 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);
}

/*******************************************************************
Gets the pointer in the nth slot of the rseg array. */
UNIV_INLINE
trx_rseg_t*
trx_sys_get_nth_rseg(
/*=================*/
				/* out: pointer to rseg object, NULL if slot
				not in use */
	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. */
UNIV_INLINE
trx_sysf_t*
trx_sysf_get(
/*=========*/
			/* out: pointer to system header, page x-latched. */
	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. */
UNIV_INLINE
ulint
trx_sysf_rseg_get_space(
/*====================*/
					/* out: space id */
	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. */
UNIV_INLINE
ulint
trx_sysf_rseg_get_page_no(
/*======================*/
					/* out: page number, FIL_NULL
					if slot unused */
	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);
}

/*********************************************************************
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 */
	dulint	id)	/* in: id */
{
#if DATA_TRX_ID_LEN != 6
# error "DATA_TRX_ID_LEN != 6"
#endif
	mach_write_to_6(ptr, id);
}

/*********************************************************************
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_... */
UNIV_INLINE
dulint
trx_read_trx_id(
/*============*/
				/* out: 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. */
UNIV_INLINE
trx_t*
trx_get_on_id(
/*==========*/
			/* out: the trx handle or NULL if not found */
	dulint	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.) */
UNIV_INLINE
dulint
trx_list_get_min_trx_id(void)
/*=========================*/
			/* out: the minimum trx id, or trx_sys->max_trx_id
			if the trx list is empty */
{
	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. */
UNIV_INLINE
ibool
trx_is_active(
/*==========*/
			/* out: TRUE if active */
	dulint	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)) {

		return(TRUE);
	}

	return(FALSE);
}

/*********************************************************************
Allocates a new transaction id. */
UNIV_INLINE
dulint
trx_sys_get_new_trx_id(void)
/*========================*/
			/* out: new, allocated trx id */
{
	dulint	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. */
UNIV_INLINE
dulint
trx_sys_get_new_trx_no(void)
/*========================*/
			/* out: new, allocated trx number */
{
	ut_ad(mutex_own(&kernel_mutex));

	return(trx_sys_get_new_trx_id());
}