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

Copyright (c) 2007, 2014, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2018, 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

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

/**************************************************//**
@file include/lock0priv.ic
Lock module internal inline methods.

Created July 16, 2007 Vasil Dimov
*******************************************************/

/* This file contains only methods which are used in
lock/lock0* files, other than lock/lock0lock.cc.
I.e. lock/lock0lock.cc contains more internal inline
methods but they are used only in that file. */

#ifndef LOCK_MODULE_IMPLEMENTATION
#error Do not include lock0priv.ic outside of the lock/ module
#endif

#include "row0row.h"

/*********************************************************************//**
Gets the type of a lock.
@return LOCK_TABLE or LOCK_REC */
UNIV_INLINE
ulint
lock_get_type_low(
/*==============*/
	const lock_t*	lock)	/*!< in: lock */
{
	ut_ad(lock);

	return(lock->type_mode & LOCK_TYPE_MASK);
}

/*********************************************************************//**
Checks if some transaction has an implicit x-lock on a record in a clustered
index.
@return transaction id of the transaction which has the x-lock, or 0 */
UNIV_INLINE
trx_id_t
lock_clust_rec_some_has_impl(
/*=========================*/
	const rec_t*		rec,	/*!< in: user record */
	const dict_index_t*	index,	/*!< in: clustered index */
	const rec_offs*		offsets)/*!< in: rec_get_offsets(rec, index) */
{
	ut_ad(dict_index_is_clust(index));
	ut_ad(page_rec_is_user_rec(rec));

	return(row_get_rec_trx_id(rec, index, offsets));
}

/*********************************************************************//**
Gets the number of bits in a record lock bitmap.
@return	number of bits */
UNIV_INLINE
ulint
lock_rec_get_n_bits(
/*================*/
	const lock_t*	lock)	/*!< in: record lock */
{
	return(lock->un_member.rec_lock.n_bits);
}

/**********************************************************************//**
Sets the nth bit of a record lock to TRUE. */
UNIV_INLINE
void
lock_rec_set_nth_bit(
/*=================*/
	lock_t*	lock,	/*!< in: record lock */
	ulint	i)	/*!< in: index of the bit */
{
	ulint	byte_index;
	ulint	bit_index;

	ut_ad(lock);
	ut_ad(lock_get_type_low(lock) == LOCK_REC);
	ut_ad(i < lock->un_member.rec_lock.n_bits);

	byte_index = i / 8;
	bit_index = i % 8;

	((byte*) &lock[1])[byte_index] |= 1 << bit_index;

	++lock->trx->lock.n_rec_locks;
}

/*********************************************************************//**
Gets the first or next record lock on a page.
@return	next lock, NULL if none exists */
UNIV_INLINE
lock_t*
lock_rec_get_next_on_page(
/*======================*/
	lock_t*	lock)	/*!< in: a record lock */
{
	return((lock_t*) lock_rec_get_next_on_page_const(lock));
}

/*********************************************************************//**
Gets the first record lock on a page, where the page is identified by its
file address.
@return	first lock, NULL if none exists */
UNIV_INLINE
lock_t*
lock_rec_get_first_on_page_addr(
/*============================*/
	hash_table_t*	lock_hash,	/* Lock hash table */
	ulint		space,		/*!< in: space */
	ulint		page_no)	/*!< in: page number */
{
	ut_ad(lock_mutex_own());

	for (lock_t* lock = static_cast<lock_t*>(
			HASH_GET_FIRST(lock_hash,
				       lock_rec_hash(space, page_no)));
	     lock != NULL;
	     lock = static_cast<lock_t*>(HASH_GET_NEXT(hash, lock))) {

		if (lock->un_member.rec_lock.space == space
		    && lock->un_member.rec_lock.page_no == page_no) {

			return(lock);
		}
	}

	return(NULL);
}

/*********************************************************************//**
Gets the first record lock on a page, where the page is identified by a
pointer to it.
@return	first lock, NULL if none exists */
UNIV_INLINE
lock_t*
lock_rec_get_first_on_page(
/*=======================*/
	hash_table_t*		lock_hash,	/*!< in: lock hash table */
	const buf_block_t*	block)		/*!< in: buffer block */
{
	ut_ad(lock_mutex_own());

	ulint	space	= block->page.id.space();
	ulint	page_no	= block->page.id.page_no();
	ulint	hash = buf_block_get_lock_hash_val(block);

	for (lock_t* lock = static_cast<lock_t*>(
			HASH_GET_FIRST(lock_hash, hash));
	     lock != NULL;
	     lock = static_cast<lock_t*>(HASH_GET_NEXT(hash, lock))) {

		if (lock->un_member.rec_lock.space == space
		    && lock->un_member.rec_lock.page_no == page_no) {

			return(lock);
		}
	}

	return(NULL);
}

/*********************************************************************//**
Gets the next explicit lock request on a record.
@return	next lock, NULL if none exists or if heap_no == ULINT_UNDEFINED */
UNIV_INLINE
lock_t*
lock_rec_get_next(
/*==============*/
	ulint	heap_no,/*!< in: heap number of the record */
	lock_t*	lock)	/*!< in: lock */
{
	ut_ad(lock_mutex_own());

	do {
		ut_ad(lock_get_type_low(lock) == LOCK_REC);
		lock = lock_rec_get_next_on_page(lock);
	} while (lock && !lock_rec_get_nth_bit(lock, heap_no));

	return(lock);
}

/*********************************************************************//**
Gets the next explicit lock request on a record.
@return	next lock, NULL if none exists or if heap_no == ULINT_UNDEFINED */
UNIV_INLINE
const lock_t*
lock_rec_get_next_const(
/*====================*/
	ulint		heap_no,/*!< in: heap number of the record */
	const lock_t*	lock)	/*!< in: lock */
{
	return(lock_rec_get_next(heap_no, (lock_t*) lock));
}

/*********************************************************************//**
Gets the first explicit lock request on a record.
@return	first lock, NULL if none exists */
UNIV_INLINE
lock_t*
lock_rec_get_first(
/*===============*/
	hash_table_t*		hash,	/*!< in: hash chain the lock on */
	const buf_block_t*	block,	/*!< in: block containing the record */
	ulint			heap_no)/*!< in: heap number of the record */
{
	ut_ad(lock_mutex_own());

	for (lock_t* lock = lock_rec_get_first_on_page(hash, block); lock;
	     lock = lock_rec_get_next_on_page(lock)) {
		if (lock_rec_get_nth_bit(lock, heap_no)) {
			return(lock);
		}
	}

	return(NULL);
}

/*********************************************************************//**
Gets the nth bit of a record lock.
@return TRUE if bit set also if i == ULINT_UNDEFINED return FALSE*/
UNIV_INLINE
ibool
lock_rec_get_nth_bit(
/*=================*/
	const lock_t*	lock,	/*!< in: record lock */
	ulint		i)	/*!< in: index of the bit */
{
	const byte*     b;

	ut_ad(lock);
	ut_ad(lock_get_type_low(lock) == LOCK_REC);

	if (i >= lock->un_member.rec_lock.n_bits) {

		return(FALSE);
	}

	b = ((const byte*) &lock[1]) + (i / 8);

	return(1 & *b >> (i % 8));
}

/*********************************************************************//**
Gets the first or next record lock on a page.
@return next lock, NULL if none exists */
UNIV_INLINE
const lock_t*
lock_rec_get_next_on_page_const(
/*============================*/
	const lock_t*	lock)	/*!< in: a record lock */
{
	ut_ad(lock_mutex_own());
	ut_ad(lock_get_type_low(lock) == LOCK_REC);

	ulint	space = lock->un_member.rec_lock.space;
	ulint	page_no = lock->un_member.rec_lock.page_no;

	while ((lock = static_cast<const lock_t*>(HASH_GET_NEXT(hash, lock)))
	       != NULL) {

		if (lock->un_member.rec_lock.space == space
		    && lock->un_member.rec_lock.page_no == page_no) {

			return(lock);
		}
	}

	return(NULL);
}

/*********************************************************************//**
Gets the mode of a lock.
@return mode */
UNIV_INLINE
enum lock_mode
lock_get_mode(
/*==========*/
	const lock_t*	lock)   /*!< in: lock */
{
	ut_ad(lock);

	return(static_cast<enum lock_mode>(lock->type_mode & LOCK_MODE_MASK));
}

/*********************************************************************//**
Calculates if lock mode 1 is compatible with lock mode 2.
@return nonzero if mode1 compatible with mode2 */
UNIV_INLINE
ulint
lock_mode_compatible(
/*=================*/
	enum lock_mode	mode1,	/*!< in: lock mode */
	enum lock_mode	mode2)	/*!< in: lock mode */
{
	ut_ad((ulint) mode1 < lock_types);
	ut_ad((ulint) mode2 < lock_types);

	return(lock_compatibility_matrix[mode1][mode2]);
}

/*********************************************************************//**
Calculates if lock mode 1 is stronger or equal to lock mode 2.
@return nonzero if mode1 stronger or equal to mode2 */
UNIV_INLINE
ulint
lock_mode_stronger_or_eq(
/*=====================*/
	enum lock_mode	mode1,	/*!< in: lock mode */
	enum lock_mode	mode2)	/*!< in: lock mode */
{
	ut_ad((ulint) mode1 < lock_types);
	ut_ad((ulint) mode2 < lock_types);

	return(lock_strength_matrix[mode1][mode2]);
}

/*********************************************************************//**
Gets the wait flag of a lock.
@return LOCK_WAIT if waiting, 0 if not */
UNIV_INLINE
ulint
lock_get_wait(
/*==========*/
	const lock_t*	lock)	/*!< in: lock */
{
	ut_ad(lock);

	return(lock->type_mode & LOCK_WAIT);
}

/*********************************************************************//**
Looks for a suitable type record lock struct by the same trx on the same page.
This can be used to save space when a new record lock should be set on a page:
no new struct is needed, if a suitable old is found.
@return lock or NULL */
UNIV_INLINE
lock_t*
lock_rec_find_similar_on_page(
/*==========================*/
	ulint           type_mode,      /*!< in: lock type_mode field */
	ulint           heap_no,        /*!< in: heap number of the record */
	lock_t*         lock,           /*!< in: lock_rec_get_first_on_page() */
	const trx_t*    trx)            /*!< in: transaction */
{
	ut_ad(lock_mutex_own());

	for (/* No op */;
	     lock != NULL;
	     lock = lock_rec_get_next_on_page(lock)) {

		if (lock->trx == trx
		    && lock->type_mode == type_mode
		    && lock_rec_get_n_bits(lock) > heap_no) {

			return(lock);
		}
	}

	return(NULL);
}

/*********************************************************************//**
Checks if a transaction has the specified table lock, or stronger. This
function should only be called by the thread that owns the transaction.
@return lock or NULL */
UNIV_INLINE
const lock_t*
lock_table_has(
/*===========*/
	const trx_t*		trx,	/*!< in: transaction */
	const dict_table_t*	table,	/*!< in: table */
	lock_mode		in_mode)/*!< in: lock mode */
{
	/* Look for stronger locks the same trx already has on the table */

	for (lock_list::const_iterator it = trx->lock.table_locks.begin(),
             end = trx->lock.table_locks.end(); it != end; ++it) {

		const lock_t*	lock = *it;

		if (lock == NULL) {
			continue;
		}

		lock_mode	mode = lock_get_mode(lock);

		ut_ad(trx == lock->trx);
		ut_ad(lock_get_type_low(lock) & LOCK_TABLE);
		ut_ad(lock->un_member.tab_lock.table != NULL);

		if (table == lock->un_member.tab_lock.table
		    && lock_mode_stronger_or_eq(mode, in_mode)) {

			ut_ad(!lock_get_wait(lock));

			return(lock);
		}
	}

	return(NULL);
}

/* vim: set filetype=c: */
