/******************************************************
Data types

(c) 1996 Innobase Oy

Created 1/16/1996 Heikki Tuuri
*******************************************************/

#include "mach0data.h"

/**********************************************************************
Get the variable length bounds of the given character set.

NOTE: the prototype of this function is copied from ha_innodb.cc! If you change
this function, you MUST change also the prototype here! */
extern
void
innobase_get_cset_width(
/*====================*/
	ulint	cset,		/* in: MySQL charset-collation code */
	ulint*	mbminlen,	/* out: minimum length of a char (in bytes) */
	ulint*	mbmaxlen);	/* out: maximum length of a char (in bytes) */

/*************************************************************************
Gets the MySQL charset-collation code for MySQL string types. */
UNIV_INLINE
ulint
dtype_get_charset_coll(
/*===================*/
	ulint	prtype)	/* in: precise data type */
{
	return((prtype >> 16) & 0xFFUL);
}

/*************************************************************************
Gets the MySQL type code from a dtype. */
UNIV_INLINE
ulint
dtype_get_mysql_type(
/*=================*/
				/* out: MySQL type code; this is NOT an InnoDB
				type code! */
	dtype_t*	type)	/* in: type struct */
{
	return(type->prtype & 0xFFUL);
}

/*************************************************************************
Sets the mbminlen and mbmaxlen members of a data type structure. */
UNIV_INLINE
void
dtype_set_mblen(
/*============*/
	dtype_t*	type)	/* in/out: type struct */
{
	ut_ad(type);
	if (dtype_is_string_type(type->mtype)) {
		innobase_get_cset_width(dtype_get_charset_coll(type->prtype),
				&type->mbminlen, &type->mbmaxlen);
		ut_ad(type->mbminlen <= type->mbmaxlen);
	} else {
		type->mbminlen = type->mbmaxlen = 0;
	}
}

/*************************************************************************
Sets a data type structure. */
UNIV_INLINE
void
dtype_set(
/*======*/
	dtype_t*	type,	/* in: type struct to init */
	ulint		mtype,	/* in: main data type */
	ulint		prtype,	/* in: precise type */
	ulint		len,	/* in: length of type */
	ulint		prec)	/* in: precision of type */
{
	ut_ad(type);
	ut_ad(mtype <= DATA_MTYPE_MAX);
	
	type->mtype = mtype;
	type->prtype = prtype;
	type->len = len;
	type->prec = prec;

	dtype_set_mblen(type);
	ut_ad(dtype_validate(type));
}

/*************************************************************************
Copies a data type structure. */
UNIV_INLINE
void
dtype_copy(
/*=======*/
	dtype_t*	type1,	/* in: type struct to copy to */
	dtype_t*	type2)	/* in: type struct to copy from */
{
	*type1 = *type2;

	ut_ad(dtype_validate(type1));
}

/*************************************************************************
Gets the SQL main data type. */
UNIV_INLINE
ulint
dtype_get_mtype(
/*============*/
	dtype_t*	type)
{
	ut_ad(type);

	return(type->mtype);
}

/*************************************************************************
Gets the precise data type. */
UNIV_INLINE
ulint
dtype_get_prtype(
/*=============*/
	dtype_t*	type)
{
	ut_ad(type);

	return(type->prtype);
}

/*************************************************************************
Gets the type length. */
UNIV_INLINE
ulint
dtype_get_len(
/*==========*/
	dtype_t*	type)
{
	ut_ad(type);

	return(type->len);
}

/*************************************************************************
Gets the type precision. */
UNIV_INLINE
ulint
dtype_get_prec(
/*===========*/
	dtype_t*	type)
{
	ut_ad(type);

	return(type->prec);
}

/*************************************************************************
Gets the minimum length of a character, in bytes. */
UNIV_INLINE
ulint
dtype_get_mbminlen(
/*===============*/
				/* out: minimum length of a char, in bytes,
				or 0 if this is not a character type */
	const dtype_t*	type)	/* in: type */
{
	ut_ad(type);
	return(type->mbminlen);
}
/*************************************************************************
Gets the maximum length of a character, in bytes. */
UNIV_INLINE
ulint
dtype_get_mbmaxlen(
/*===============*/
				/* out: maximum length of a char, in bytes,
				or 0 if this is not a character type */
	const dtype_t*	type)	/* in: type */
{
	ut_ad(type);
	return(type->mbmaxlen);
}

/*************************************************************************
Gets the padding character code for the type. */
UNIV_INLINE
ulint
dtype_get_pad_char(
/*===============*/
				/* out: padding character code, or
				ULINT_UNDEFINED if no padding specified */
	const dtype_t*	type)	/* in: type */
{
	switch (type->mtype) {
	case DATA_FIXBINARY:
	case DATA_BINARY:
		if (UNIV_UNLIKELY(dtype_get_charset_coll(type->prtype)
		    == DATA_MYSQL_BINARY_CHARSET_COLL)) {
			/* Starting from 5.0.18, do not pad
			VARBINARY or BINARY columns. */
			return(ULINT_UNDEFINED);
		}
		/* Fall through */
	case DATA_CHAR:
	case DATA_VARCHAR:
	case DATA_MYSQL:
	case DATA_VARMYSQL:
		/* Space is the padding character for all char and binary
	        strings, and starting from 5.0.3, also for TEXT strings. */

		return(0x20);
	case DATA_BLOB:
		if ((type->prtype & DATA_BINARY_TYPE) == 0) {
			return(0x20);
		}
		/* Fall through */
	default:
		/* No padding specified */
		return(ULINT_UNDEFINED);
	}
}

/**************************************************************************
Stores for a type the information which determines its alphabetical ordering
and the storage size of an SQL NULL value. This is the >= 4.1.x storage
format. */
UNIV_INLINE
void
dtype_new_store_for_order_and_null_size(
/*====================================*/
	byte*		buf,	/* in: buffer for
				DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE
				bytes where we store the info */
	dtype_t*	type,	/* in: type struct */
	ulint		prefix_len)/* in: prefix length to
				replace type->len, or 0 */
{
#if 6 != DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE
#error "6 != DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE"
#endif
	ulint	len;

	buf[0] = (byte)(type->mtype & 0xFFUL);

	if (type->prtype & DATA_BINARY_TYPE) {
		buf[0] = buf[0] | 128;
	}

	/* In versions < 4.1.2 we had: 	if (type->prtype & DATA_NONLATIN1) {
						buf[0] = buf[0] | 64;
					}
	*/

	buf[1] = (byte)(type->prtype & 0xFFUL);

	len = prefix_len ? prefix_len : type->len;

	mach_write_to_2(buf + 2, len & 0xFFFFUL);

	ut_ad(dtype_get_charset_coll(type->prtype) < 256);
	mach_write_to_2(buf + 4, dtype_get_charset_coll(type->prtype));

	if (type->prtype & DATA_NOT_NULL) {
		buf[4] |= 128;
	}
}

/**************************************************************************
Reads to a type the stored information which determines its alphabetical
ordering and the storage size of an SQL NULL value. This is the < 4.1.x
storage format. */
UNIV_INLINE
void
dtype_read_for_order_and_null_size(
/*===============================*/
	dtype_t*	type,	/* in: type struct */
	byte*		buf)	/* in: buffer for stored type order info */
{
	ut_ad(4 == DATA_ORDER_NULL_TYPE_BUF_SIZE);
	
	type->mtype = buf[0] & 63;
	type->prtype = buf[1];

	if (buf[0] & 128) {
	        type->prtype = type->prtype | DATA_BINARY_TYPE;
	}

	type->len = mach_read_from_2(buf + 2);
		
	type->prtype = dtype_form_prtype(type->prtype,
					data_mysql_default_charset_coll);
	dtype_set_mblen(type);
}	

/**************************************************************************
Reads to a type the stored information which determines its alphabetical
ordering and the storage size of an SQL NULL value. This is the >= 4.1.x
storage format. */
UNIV_INLINE
void
dtype_new_read_for_order_and_null_size(
/*===================================*/
	dtype_t*	type,	/* in: type struct */
	byte*		buf)	/* in: buffer for stored type order info */
{
	ulint	charset_coll;

#if 6 != DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE
#error "6 != DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE"
#endif
	
	type->mtype = buf[0] & 63;
	type->prtype = buf[1];

	if (buf[0] & 128) {
		type->prtype |= DATA_BINARY_TYPE;
	}

	if (buf[4] & 128) {
		type->prtype |= DATA_NOT_NULL;
	}

	type->len = mach_read_from_2(buf + 2);

	mach_read_from_2(buf + 4);

	charset_coll = mach_read_from_2(buf + 4) & 0x7fff;

	if (dtype_is_string_type(type->mtype)) {
		ut_a(charset_coll < 256);

		if (charset_coll == 0) {
			/* This insert buffer record was inserted with MySQL
			version < 4.1.2, and the charset-collation code was not
			explicitly stored to dtype->prtype at that time. It
			must be the default charset-collation of this MySQL
			installation. */

			charset_coll = data_mysql_default_charset_coll;
		}
		
		type->prtype = dtype_form_prtype(type->prtype, charset_coll);
	}						
	dtype_set_mblen(type);
}

#ifndef UNIV_HOTBACKUP
/***************************************************************************
Returns the size of a fixed size data type, 0 if not a fixed size type. */
UNIV_INLINE
ulint
dtype_get_fixed_size(
/*=================*/
				/* out: fixed size, or 0 */
	dtype_t*	type)	/* in: type */
{
	ulint	mtype;

	mtype = dtype_get_mtype(type);

	switch (mtype) {
	case DATA_SYS:
#ifdef UNIV_DEBUG
			switch (type->prtype & DATA_MYSQL_TYPE_MASK) {
			default:
				ut_ad(0);
				return(0);
			case DATA_ROW_ID:
				ut_ad(type->len == DATA_ROW_ID_LEN);
				break;
			case DATA_TRX_ID:
				ut_ad(type->len == DATA_TRX_ID_LEN);
				break;
			case DATA_ROLL_PTR:
				ut_ad(type->len == DATA_ROLL_PTR_LEN);
				break;
			case DATA_MIX_ID:
				ut_ad(type->len == DATA_MIX_ID_LEN);
				break;
			}
#endif /* UNIV_DEBUG */
	case DATA_CHAR:
	case DATA_FIXBINARY:
	case DATA_INT:
	case DATA_FLOAT:
	case DATA_DOUBLE:
			return(dtype_get_len(type));
	case DATA_MYSQL:
			if (type->prtype & DATA_BINARY_TYPE) {
				return(dtype_get_len(type));
			} else {
				/* We play it safe here and ask MySQL for
				mbminlen and mbmaxlen.  Although
				type->mbminlen and type->mbmaxlen are
				initialized if and only if type->prtype
				is (in one of the 3 functions in this file),
				it could be that none of these functions
				has been called. */

				ulint	mbminlen, mbmaxlen;

				innobase_get_cset_width(
					dtype_get_charset_coll(type->prtype),
					&mbminlen, &mbmaxlen);

				if (UNIV_UNLIKELY(type->mbminlen != mbminlen)
				|| UNIV_UNLIKELY(type->mbmaxlen != mbmaxlen)) {

					ut_print_timestamp(stderr);
					fprintf(stderr, "  InnoDB: "
						"mbminlen=%lu, "
						"mbmaxlen=%lu, "
						"type->mbminlen=%lu, "
						"type->mbmaxlen=%lu\n",
						(ulong) mbminlen,
						(ulong) mbmaxlen,
						(ulong) type->mbminlen,
						(ulong) type->mbmaxlen);
				}
				if (mbminlen == mbmaxlen) {
					return(dtype_get_len(type));
				}
			}
			/* fall through for variable-length charsets */
	case DATA_VARCHAR:
	case DATA_BINARY:
	case DATA_DECIMAL:
	case DATA_VARMYSQL:
	case DATA_BLOB:
			return(0); 
	default:	ut_error;
	}

	return(0);
}

/***************************************************************************
Returns the minimum size of a data type. */
UNIV_INLINE
ulint
dtype_get_min_size(
/*===============*/
				/* out: minimum size */
	const dtype_t*	type)	/* in: type */
{
	switch (type->mtype) {
	case DATA_SYS:
#ifdef UNIV_DEBUG
			switch (type->prtype & DATA_MYSQL_TYPE_MASK) {
			default:
				ut_ad(0);
				return(0);
			case DATA_ROW_ID:
				ut_ad(type->len == DATA_ROW_ID_LEN);
				break;
			case DATA_TRX_ID:
				ut_ad(type->len == DATA_TRX_ID_LEN);
				break;
			case DATA_ROLL_PTR:
				ut_ad(type->len == DATA_ROLL_PTR_LEN);
				break;
			case DATA_MIX_ID:
				ut_ad(type->len == DATA_MIX_ID_LEN);
				break;
			}
#endif /* UNIV_DEBUG */
	case DATA_CHAR:
	case DATA_FIXBINARY:
	case DATA_INT:
	case DATA_FLOAT:
	case DATA_DOUBLE:
			return(type->len);
	case DATA_MYSQL:
			if ((type->prtype & DATA_BINARY_TYPE)
					|| type->mbminlen == type->mbmaxlen) {
				return(type->len);
			}
			/* this is a variable-length character set */
			ut_a(type->mbminlen > 0);
			ut_a(type->mbmaxlen > type->mbminlen);
			ut_a(type->len % type->mbmaxlen == 0);
			return(type->len * type->mbminlen / type->mbmaxlen);
	case DATA_VARCHAR:
	case DATA_BINARY:
	case DATA_DECIMAL:
	case DATA_VARMYSQL:
	case DATA_BLOB:
			return(0); 
	default:	ut_error;
	}

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

/***************************************************************************
Returns a stored SQL NULL size for a type. For fixed length types it is
the fixed length of the type, otherwise 0. */
UNIV_INLINE
ulint
dtype_get_sql_null_size(
/*====================*/
				/* out: SQL null storage size */
	dtype_t*	type)	/* in: type */
{
	return(dtype_get_fixed_size(type));
}

/***************************************************************************
Returns TRUE if a type is of a fixed size. */
UNIV_INLINE
ibool
dtype_is_fixed_size(
/*================*/
				/* out: TRUE if fixed size */
	dtype_t*	type)	/* in: type */
{
	ulint	size;

	size = dtype_get_fixed_size(type);

	if (size) {
		return(TRUE);
	}

	return(FALSE);
}
