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

Copyright (c) 1995, 2016, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2012, Facebook Inc.
Copyright (c) 2013, 2016, 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, Suite 500, Boston, MA 02110-1335 USA

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

/**************************************************//**
@file include/mtr0mtr.h
Mini-transaction buffer

Created 11/26/1995 Heikki Tuuri
*******************************************************/

#ifndef mtr0mtr_h
#define mtr0mtr_h

#include "univ.i"
#include "log0types.h"
#include "mtr0types.h"
#include "buf0types.h"
#include "trx0types.h"
#include "dyn0buf.h"

/** Start a mini-transaction. */
#define mtr_start(m)		(m)->start()
/** Start a mini-transaction. */
#define mtr_start_trx(m, t)		(m)->start((t))

/** Start a synchronous mini-transaction */
#define mtr_start_sync(m)	(m)->start(true)

/** Start an asynchronous read-only mini-transaction */
#define mtr_start_ro(m)	(m)->start(true, true)

/** Commit a mini-transaction. */
#define mtr_commit(m)		(m)->commit()

/** Set and return a savepoint in mtr.
@return	savepoint */
#define mtr_set_savepoint(m)	(m)->get_savepoint()

/** Release the (index tree) s-latch stored in an mtr memo after a
savepoint. */
#define mtr_release_s_latch_at_savepoint(m, s, l)			\
				(m)->release_s_latch_at_savepoint((s), (l))

/** Get the logging mode of a mini-transaction.
@return	logging mode: MTR_LOG_NONE, ... */
#define mtr_get_log_mode(m)	(m)->get_log_mode()

/** Change the logging mode of a mini-transaction.
@return	old mode */
#define mtr_set_log_mode(m, d)	(m)->set_log_mode((d))

/** Get the flush observer of a mini-transaction.
@return flush observer object */
#define mtr_get_flush_observer(m)	(m)->get_flush_observer()

/** Set the flush observer of a mini-transaction. */
#define mtr_set_flush_observer(m, d)	(m)->set_flush_observer((d))

/** Read 1 - 4 bytes from a file page buffered in the buffer pool.
@return	value read */
#define mtr_read_ulint(p, t, m)	(m)->read_ulint((p), (t))

/** Release an object in the memo stack.
@return true if released */
#define mtr_memo_release(m, o, t)					\
				(m)->memo_release((o), (t))

#ifdef UNIV_DEBUG

/** Check if memo contains the given item. */
#define mtr_is_block_fix(m, o, t, table) mtr_memo_contains(m, o, t)

/** Check if memo contains the given page. */
#define mtr_is_page_fix(m, p, t, table) mtr_memo_contains_page(m, p, t)

/** Check if memo contains the given item.
@return	TRUE if contains */
#define mtr_memo_contains(m, o, t)					\
				(m)->memo_contains((m)->get_memo(), (o), (t))

/** Check if memo contains the given page.
@return	TRUE if contains */
#define mtr_memo_contains_page(m, p, t)					\
	(m)->memo_contains_page_flagged((p), (t))
#endif /* UNIV_DEBUG */

/** Print info of an mtr handle. */
#define mtr_print(m)		(m)->print()

/** Return the log object of a mini-transaction buffer.
@return	log */
#define mtr_get_log(m)		(m)->get_log()

/** Push an object to an mtr memo stack. */
#define mtr_memo_push(m, o, t)	(m)->memo_push(o, t)

/** Lock an rw-lock in s-mode. */
#define mtr_s_lock(l, m)	(m)->s_lock((l), __FILE__, __LINE__)

/** Lock an rw-lock in x-mode. */
#define mtr_x_lock(l, m)	(m)->x_lock((l), __FILE__, __LINE__)

/** Lock a tablespace in x-mode. */
#define mtr_x_lock_space(s, m)	(m)->x_lock_space((s), __FILE__, __LINE__)

/** Lock an rw-lock in sx-mode. */
#define mtr_sx_lock(l, m)	(m)->sx_lock((l), __FILE__, __LINE__)

#define mtr_memo_contains_flagged(m, p, l)				\
				(m)->memo_contains_flagged((p), (l))

#define mtr_memo_contains_page_flagged(m, p, l)				\
				(m)->memo_contains_page_flagged((p), (l))

#define mtr_release_block_at_savepoint(m, s, b)				\
				(m)->release_block_at_savepoint((s), (b))

#define mtr_block_sx_latch_at_savepoint(m, s, b)			\
				(m)->sx_latch_at_savepoint((s), (b))

#define mtr_block_x_latch_at_savepoint(m, s, b)				\
				(m)->x_latch_at_savepoint((s), (b))

/** Check if a mini-transaction is dirtying a clean page.
@param b	block being x-fixed
@return true if the mtr is dirtying a clean page. */
#define mtr_block_dirtied(b)	mtr_t::is_block_dirtied((b))

/** Forward declaration of a tablespace object */
struct fil_space_t;

/** Append records to the system-wide redo log buffer.
@param[in]	log	redo log records */
void
mtr_write_log(
	const mtr_buf_t*	log);

/** Mini-transaction memo stack slot. */
struct mtr_memo_slot_t {
	/** pointer to the object */
	void*		object;

	/** type of the stored object (MTR_MEMO_S_LOCK, ...) */
	ulint		type;
};

/** Mini-transaction handle and buffer */
struct mtr_t {

	/** State variables of the mtr */
	struct Impl {

		/** memo stack for locks etc. */
		mtr_buf_t	m_memo;

		/** mini-transaction log */
		mtr_buf_t	m_log;

		/** true if mtr has made at least one buffer pool page dirty */
		bool		m_made_dirty;

		/** true if inside ibuf changes */
		bool		m_inside_ibuf;

		/** true if the mini-transaction modified buffer pool pages */
		bool		m_modifications;

		/** Count of how many page initial log records have been
		written to the mtr log */
		ib_uint32_t	m_n_log_recs;

		/** specifies which operations should be logged; default
		value MTR_LOG_ALL */
		mtr_log_t	m_log_mode;
#ifdef UNIV_DEBUG
		/** Persistent user tablespace associated with the
		mini-transaction, or 0 (TRX_SYS_SPACE) if none yet */
		ulint		m_user_space_id;
#endif /* UNIV_DEBUG */
		/** User tablespace that is being modified by the
		mini-transaction */
		fil_space_t*	m_user_space;
		/** Undo tablespace that is being modified by the
		mini-transaction */
		fil_space_t*	m_undo_space;
		/** System tablespace if it is being modified by the
		mini-transaction */
		fil_space_t*	m_sys_space;

		/** State of the transaction */
		mtr_state_t	m_state;

		/** Flush Observer */
		FlushObserver*	m_flush_observer;

#ifdef UNIV_DEBUG
		/** For checking corruption. */
		ulint		m_magic_n;
#endif /* UNIV_DEBUG */

		/** Owning mini-transaction */
		mtr_t*		m_mtr;

		/* Transaction handle */
		trx_t*			m_trx;
	};

	mtr_t()
	{
		m_impl.m_state = MTR_STATE_INIT;
	}

	~mtr_t() { }

	/** Release the free extents that was reserved using
	fsp_reserve_free_extents().  This is equivalent to calling
	fil_space_release_free_extents().  This is intended for use
	with index pages.
	@param[in]	n_reserved	number of reserved extents */
	void release_free_extents(ulint n_reserved);

	/** Start a mini-transaction.
	@param sync		true if it is a synchronous mini-transaction
	@param read_only	true if read only mini-transaction */
	void start(bool sync = true, bool read_only = false);

	/** Start a mini-transaction.
	@param sync		true if it is a synchronous mini-transaction
	@param read_only	true if read only mini-transaction */
	void start(trx_t* trx, bool sync = true, bool read_only = false);

	/** @return whether this is an asynchronous mini-transaction. */
	bool is_async() const
	{
		return(!m_sync);
	}

	/** Request a future commit to be synchronous. */
	void set_sync()
	{
		m_sync = true;
	}

	/** Commit the mini-transaction. */
	void commit();

	/** Commit a mini-transaction that did not modify any pages,
	but generated some redo log on a higher level, such as
	MLOG_FILE_NAME records and a MLOG_CHECKPOINT marker.
	The caller must invoke log_mutex_enter() and log_mutex_exit().
	This is to be used at log_checkpoint().
	@param[in]	checkpoint_lsn	the LSN of the log checkpoint  */
	void commit_checkpoint(lsn_t checkpoint_lsn);

	/** Return current size of the buffer.
	@return	savepoint */
	ulint get_savepoint() const
		MY_ATTRIBUTE((warn_unused_result))
	{
		ut_ad(is_active());
		ut_ad(m_impl.m_magic_n == MTR_MAGIC_N);

		return(m_impl.m_memo.size());
	}

	/** Release the (index tree) s-latch stored in an mtr memo after a
	savepoint.
	@param savepoint	value returned by @see set_savepoint.
	@param lock		latch to release */
	inline void release_s_latch_at_savepoint(
		ulint		savepoint,
		rw_lock_t*	lock);

	/** Release the block in an mtr memo after a savepoint. */
	inline void release_block_at_savepoint(
		ulint		savepoint,
		buf_block_t*	block);

	/** SX-latch a not yet latched block after a savepoint. */
	inline void sx_latch_at_savepoint(ulint savepoint, buf_block_t* block);

	/** X-latch a not yet latched block after a savepoint. */
	inline void x_latch_at_savepoint(ulint savepoint, buf_block_t*	block);

	/** Get the logging mode.
	@return	logging mode */
	inline mtr_log_t get_log_mode() const
		MY_ATTRIBUTE((warn_unused_result));

	/** Change the logging mode.
	@param mode	 logging mode
	@return	old mode */
	inline mtr_log_t set_log_mode(mtr_log_t mode);

	/** Note that the mini-transaction is modifying the system tablespace
	(for example, for the change buffer or for undo logs)
	@return the system tablespace */
	fil_space_t* set_sys_modified()
	{
		if (!m_impl.m_sys_space) {
			lookup_sys_space();
		}
		return(m_impl.m_sys_space);
	}

	/** Copy the tablespaces associated with the mini-transaction
	(needed for generating MLOG_FILE_NAME records)
	@param[in]	mtr	mini-transaction that may modify
	the same set of tablespaces as this one */
	void set_spaces(const mtr_t& mtr)
	{
		ut_ad(m_impl.m_user_space_id == TRX_SYS_SPACE);
		ut_ad(!m_impl.m_user_space);
		ut_ad(!m_impl.m_undo_space);
		ut_ad(!m_impl.m_sys_space);

		ut_d(m_impl.m_user_space_id = mtr.m_impl.m_user_space_id);
		m_impl.m_user_space = mtr.m_impl.m_user_space;
		m_impl.m_undo_space = mtr.m_impl.m_undo_space;
		m_impl.m_sys_space = mtr.m_impl.m_sys_space;
	}

	/** Set the tablespace associated with the mini-transaction
	(needed for generating a MLOG_FILE_NAME record)
	@param[in]	space_id	user or system tablespace ID
	@return	the tablespace */
	fil_space_t* set_named_space(ulint space_id)
	{
		ut_ad(m_impl.m_user_space_id == TRX_SYS_SPACE);
		ut_d(m_impl.m_user_space_id = space_id);
		if (space_id == TRX_SYS_SPACE) {
			return(set_sys_modified());
		} else {
			lookup_user_space(space_id);
			return(m_impl.m_user_space);
		}
	}

	/** Set the tablespace associated with the mini-transaction
	(needed for generating a MLOG_FILE_NAME record)
	@param[in]	space	user or system tablespace */
	void set_named_space(fil_space_t* space);

#ifdef UNIV_DEBUG
	/** Check the tablespace associated with the mini-transaction
	(needed for generating a MLOG_FILE_NAME record)
	@param[in]	space	tablespace
	@return whether the mini-transaction is associated with the space */
	bool is_named_space(ulint space) const;
#endif /* UNIV_DEBUG */

	/** Read 1 - 4 bytes from a file page buffered in the buffer pool.
	@param ptr	pointer from where to read
	@param type)	MLOG_1BYTE, MLOG_2BYTES, MLOG_4BYTES
	@return	value read */
	inline ulint read_ulint(const byte* ptr, mlog_id_t type) const
		MY_ATTRIBUTE((warn_unused_result));

	/** Locks a rw-latch in S mode.
	NOTE: use mtr_s_lock().
	@param lock	rw-lock
	@param file	file name from where called
	@param line	line number in file */
	inline void s_lock(rw_lock_t* lock, const char* file, ulint line);

	/** Locks a rw-latch in X mode.
	NOTE: use mtr_x_lock().
	@param lock	rw-lock
	@param file	file name from where called
	@param line	line number in file */
	inline void x_lock(rw_lock_t* lock, const char*	file, ulint line);

	/** Locks a rw-latch in X mode.
	NOTE: use mtr_sx_lock().
	@param lock	rw-lock
	@param file	file name from where called
	@param line	line number in file */
	inline void sx_lock(rw_lock_t* lock, const char* file, ulint line);

	/** Acquire a tablespace X-latch.
	NOTE: use mtr_x_lock_space().
	@param[in]	space_id	tablespace ID
	@param[in]	file		file name from where called
	@param[in]	line		line number in file
	@return the tablespace object (never NULL) */
	fil_space_t* x_lock_space(
		ulint		space_id,
		const char*	file,
		ulint		line);

	/** Release an object in the memo stack.
	@param object	object
	@param type	object type: MTR_MEMO_S_LOCK, ...
	@return bool if lock released */
	bool memo_release(const void* object, ulint type);
	/** Release a page latch.
	@param[in]	ptr	pointer to within a page frame
	@param[in]	type	object type: MTR_MEMO_PAGE_X_FIX, ... */
	void release_page(const void* ptr, mtr_memo_type_t type);

	/** Note that the mini-transaction has modified data. */
	void set_modified()
	{
		m_impl.m_modifications = true;
	}

	/** Set the state to not-modified. This will not log the
	changes.  This is only used during redo log apply, to avoid
	logging the changes. */
	void discard_modifications()
	{
		m_impl.m_modifications = false;
	}

	/** Get the LSN of commit().
	@return the commit LSN
	@retval 0 if the transaction only modified temporary tablespaces */
	lsn_t commit_lsn() const
	{
		ut_ad(has_committed());
		return(m_commit_lsn);
	}

	/** Note that we are inside the change buffer code. */
	void enter_ibuf()
	{
		m_impl.m_inside_ibuf = true;
	}

	/** Note that we have exited from the change buffer code. */
	void exit_ibuf()
	{
		m_impl.m_inside_ibuf = false;
	}

	/** @return true if we are inside the change buffer code */
	bool is_inside_ibuf() const
	{
		return(m_impl.m_inside_ibuf);
	}

	/*
	@return true if the mini-transaction is active */
	bool is_active() const
	{
		return(m_impl.m_state == MTR_STATE_ACTIVE);
	}

	/** Get flush observer
	@return flush observer */
	FlushObserver* get_flush_observer() const
	{
		return(m_impl.m_flush_observer);
	}

	/** Set flush observer
	@param[in]	observer	flush observer */
	void set_flush_observer(FlushObserver*	observer)
	{
		ut_ad(observer == NULL
		      || m_impl.m_log_mode == MTR_LOG_NO_REDO);

		m_impl.m_flush_observer = observer;
	}

#ifdef UNIV_DEBUG
	/** Check if memo contains the given item.
	@param memo	memo stack
	@param object,	object to search
	@param type	type of object
	@return	true if contains */
	static bool memo_contains(
		mtr_buf_t*	memo,
		const void*	object,
		ulint		type)
		MY_ATTRIBUTE((warn_unused_result));

	/** Check if memo contains the given item.
	@param object		object to search
	@param flags		specify types of object (can be ORred) of
				MTR_MEMO_PAGE_S_FIX ... values
	@return true if contains */
	bool memo_contains_flagged(const void* ptr, ulint flags) const;

	/** Check if memo contains the given page.
	@param[in]	ptr	pointer to within buffer frame
	@param[in]	flags	specify types of object with OR of
				MTR_MEMO_PAGE_S_FIX... values
	@return	the block
	@retval	NULL	if not found */
	buf_block_t* memo_contains_page_flagged(
		const byte*	ptr,
		ulint		flags) const;

	/** Mark the given latched page as modified.
	@param[in]	ptr	pointer to within buffer frame */
	void memo_modify_page(const byte* ptr);

	/** Print info of an mtr handle. */
	void print() const;

	/** @return true if the mini-transaction has committed */
	bool has_committed() const
	{
		return(m_impl.m_state == MTR_STATE_COMMITTED);
	}

	/** @return true if the mini-transaction is committing */
	bool is_committing() const
	{
		return(m_impl.m_state == MTR_STATE_COMMITTING);
	}

	/** @return true if mini-transaction contains modifications. */
	bool has_modifications() const
	{
		return(m_impl.m_modifications);
	}

	/** @return the memo stack */
	const mtr_buf_t* get_memo() const
	{
		return(&m_impl.m_memo);
	}

	/** @return the memo stack */
	mtr_buf_t* get_memo()
	{
		return(&m_impl.m_memo);
	}
#endif /* UNIV_DEBUG */

	/** @return true if a record was added to the mini-transaction */
	bool is_dirty() const
	{
		return(m_impl.m_made_dirty);
	}

	/** Note that a record has been added to the log */
	void added_rec()
	{
		++m_impl.m_n_log_recs;
	}

	/** Get the buffered redo log of this mini-transaction.
	@return	redo log */
	const mtr_buf_t* get_log() const
	{
		ut_ad(m_impl.m_magic_n == MTR_MAGIC_N);

		return(&m_impl.m_log);
	}

	/** Get the buffered redo log of this mini-transaction.
	@return	redo log */
	mtr_buf_t* get_log()
	{
		ut_ad(m_impl.m_magic_n == MTR_MAGIC_N);

		return(&m_impl.m_log);
	}

	/** Push an object to an mtr memo stack.
	@param object	object
	@param type	object type: MTR_MEMO_S_LOCK, ... */
	inline void memo_push(void* object, mtr_memo_type_t type);

	/** Check if this mini-transaction is dirtying a clean page.
	@param block	block being x-fixed
	@return true if the mtr is dirtying a clean page. */
	static bool is_block_dirtied(const buf_block_t* block)
		MY_ATTRIBUTE((warn_unused_result));

private:
	/** Look up the system tablespace. */
	void lookup_sys_space();
	/** Look up the user tablespace.
	@param[in]	space_id	tablespace ID  */
	void lookup_user_space(ulint space_id);

	class Command;

	friend class Command;

private:
	Impl			m_impl;

	/** LSN at commit time */
	volatile lsn_t		m_commit_lsn;

	/** true if it is synchronous mini-transaction */
	bool			m_sync;
};

#ifndef UNIV_NONINL
#include "mtr0mtr.ic"
#endif /* UNIV_NOINL */

#endif /* mtr0mtr_h */