From 2bb6cefa22eb2d452a85a632c0b44b6d725ebc11 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= <marko.makela@oracle.com>
Date: Thu, 15 Nov 2012 20:30:36 +0200
Subject: [PATCH] Bug#15874001 CREATE INDEX ON A UTF8 CHAR COLUMN FAILS WITH
 ROW_FORMAT=REDUNDANT

CHAR(n) in ROW_FORMAT=REDUNDANT tables is always fixed-length
(n*mbmaxlen bytes), but in the temporary file it is variable-length
(n*mbminlen to n*mbmaxlen bytes) for variable-length character sets,
such as UTF-8.

The temporary file format used during index creation and online ALTER
TABLE is based on ROW_FORMAT=COMPACT. Thus, it should use the
variable-length encoding even if the base table is in
ROW_FORMAT=REDUNDNAT.

dtype_get_fixed_size_low(): Replace an assertion-like check with a
debug assertion.

rec_init_offsets_comp_ordinary(), rec_convert_dtuple_to_rec_comp():
Make this an inline function.  Replace 'ulint extra' with 'bool temp'.

rec_get_converted_size_comp_prefix_low(): Renamed from
rec_get_converted_size_comp_prefix(), and made inline. Add the
parameter 'bool temp'. If temp=true, do not add REC_N_NEW_EXTRA_BYTES.

rec_get_converted_size_comp_prefix(): Remove the comment about
dict_table_is_comp(). This function is only to be called for other
than ROW_FORMAT=REDUNDANT records.

rec_get_converted_size_temp(): New function for computing temporary
file record size. Omit REC_N_NEW_EXTRA_BYTES from the sizes.

rec_init_offsets_temp(), rec_convert_dtuple_to_temp(): New functions,
for operating on temporary file records.

rb:1559 approved by Jimmy Yang
---
 storage/innodb_plugin/ChangeLog            |   7 +
 storage/innodb_plugin/include/data0type.ic |  28 +--
 storage/innodb_plugin/include/rem0rec.h    |  80 +++----
 storage/innodb_plugin/rem/rem0rec.c        | 237 +++++++++++++++------
 storage/innodb_plugin/row/row0merge.c      |  44 ++--
 5 files changed, 251 insertions(+), 145 deletions(-)

diff --git a/storage/innodb_plugin/ChangeLog b/storage/innodb_plugin/ChangeLog
index f69f9e16904..5387555d24f 100644
--- a/storage/innodb_plugin/ChangeLog
+++ b/storage/innodb_plugin/ChangeLog
@@ -1,3 +1,10 @@
+2012-11-15	The InnoDB Team
+
+	* include/data0type.ic, include/rem0rec.h,
+	rem/rem0rec.c, row/row0merge.c:
+	Fix Bug#15874001 CREATE INDEX ON A UTF8 CHAR COLUMN FAILS WITH
+	ROW_FORMAT=REDUNDANT
+
 2012-10-18	The InnoDB Team
 
 	* row/row0sel.c:
diff --git a/storage/innodb_plugin/include/data0type.ic b/storage/innodb_plugin/include/data0type.ic
index 2bf67a941bd..8a838e32689 100644
--- a/storage/innodb_plugin/include/data0type.ic
+++ b/storage/innodb_plugin/include/data0type.ic
@@ -1,6 +1,6 @@
 /*****************************************************************************
 
-Copyright (c) 1996, 2010, Innobase Oy. All Rights Reserved.
+Copyright (c) 1996, 2012, Oracle and/or its affiliates. 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
@@ -439,34 +439,16 @@ dtype_get_fixed_size_low(
 		} else if (!comp) {
 			return(len);
 		} else {
-			/* We play it safe here and ask MySQL for
-			mbminlen and mbmaxlen.	Although
-			mbminlen and mbmaxlen are
-			initialized if and only if prtype
-			is (in one of the 3 functions in this file),
-			it could be that none of these functions
-			has been called. */
-
+#ifdef UNIV_DEBUG
 			ulint	i_mbminlen, i_mbmaxlen;
 
 			innobase_get_cset_width(
 				dtype_get_charset_coll(prtype),
 				&i_mbminlen, &i_mbmaxlen);
 
-			if (UNIV_UNLIKELY(mbminlen != i_mbminlen)
-			    || UNIV_UNLIKELY(mbmaxlen != i_mbmaxlen)) {
-
-				ut_print_timestamp(stderr);
-				fprintf(stderr, "  InnoDB: "
-					"mbminlen=%lu, "
-					"mbmaxlen=%lu, "
-					"type->mbminlen=%lu, "
-					"type->mbmaxlen=%lu\n",
-					(ulong) i_mbminlen,
-					(ulong) i_mbmaxlen,
-					(ulong) mbminlen,
-					(ulong) mbmaxlen);
-			}
+			ut_ad(mbminlen == i_mbminlen);
+			ut_ad(mbmaxlen == i_mbmaxlen);
+#endif /* UNIV_DEBUG */
 			if (mbminlen == mbmaxlen) {
 				return(len);
 			}
diff --git a/storage/innodb_plugin/include/rem0rec.h b/storage/innodb_plugin/include/rem0rec.h
index 06de23be757..0666c43a026 100644
--- a/storage/innodb_plugin/include/rem0rec.h
+++ b/storage/innodb_plugin/include/rem0rec.h
@@ -361,24 +361,6 @@ rec_get_offsets_func(
 #define rec_get_offsets(rec,index,offsets,n,heap)	\
 	rec_get_offsets_func(rec,index,offsets,n,heap,__FILE__,__LINE__)
 
-/******************************************************//**
-Determine the offset to each field in a leaf-page record
-in ROW_FORMAT=COMPACT.  This is a special case of
-rec_init_offsets() and rec_get_offsets_func(). */
-UNIV_INTERN
-void
-rec_init_offsets_comp_ordinary(
-/*===========================*/
-	const rec_t*		rec,	/*!< in: physical record in
-					ROW_FORMAT=COMPACT */
-	ulint			extra,	/*!< in: number of bytes to reserve
-					between the record header and
-					the data payload
-					(usually REC_N_NEW_EXTRA_BYTES) */
-	const dict_index_t*	index,	/*!< in: record descriptor */
-	ulint*			offsets);/*!< in/out: array of offsets;
-					in: n=rec_offs_n_fields(offsets) */
-
 /******************************************************//**
 The following function determines the offsets to each field
 in the record.  It can reuse a previously allocated array. */
@@ -639,8 +621,48 @@ rec_copy(
 /*=====*/
 	void*		buf,	/*!< in: buffer */
 	const rec_t*	rec,	/*!< in: physical record */
-	const ulint*	offsets);/*!< in: array returned by rec_get_offsets() */
+	const ulint*	offsets)/*!< in: array returned by rec_get_offsets() */
+	__attribute__((nonnull));
 #ifndef UNIV_HOTBACKUP
+/**********************************************************//**
+Determines the size of a data tuple prefix in a temporary file.
+@return	total size */
+UNIV_INTERN
+ulint
+rec_get_converted_size_temp(
+/*========================*/
+	const dict_index_t*	index,	/*!< in: record descriptor */
+	const dfield_t*		fields,	/*!< in: array of data fields */
+	ulint			n_fields,/*!< in: number of data fields */
+	ulint*			extra)	/*!< out: extra size */
+	__attribute__((warn_unused_result, nonnull));
+
+/******************************************************//**
+Determine the offset to each field in temporary file.
+@see rec_convert_dtuple_to_temp() */
+UNIV_INTERN
+void
+rec_init_offsets_temp(
+/*==================*/
+	const rec_t*		rec,	/*!< in: temporary file record */
+	const dict_index_t*	index,	/*!< in: record descriptor */
+	ulint*			offsets)/*!< in/out: array of offsets;
+					in: n=rec_offs_n_fields(offsets) */
+	__attribute__((nonnull));
+
+/*********************************************************//**
+Builds a temporary file record out of a data tuple.
+@see rec_init_offsets_temp() */
+UNIV_INTERN
+void
+rec_convert_dtuple_to_temp(
+/*=======================*/
+	rec_t*			rec,		/*!< out: record */
+	const dict_index_t*	index,		/*!< in: record descriptor */
+	const dfield_t*		fields,		/*!< in: array of data fields */
+	ulint			n_fields)	/*!< in: number of fields */
+	__attribute__((nonnull));
+
 /**************************************************************//**
 Copies the first n fields of a physical record to a new physical record in
 a buffer.
@@ -675,21 +697,6 @@ rec_fold(
 	__attribute__((pure));
 #endif /* !UNIV_HOTBACKUP */
 /*********************************************************//**
-Builds a ROW_FORMAT=COMPACT record out of a data tuple. */
-UNIV_INTERN
-void
-rec_convert_dtuple_to_rec_comp(
-/*===========================*/
-	rec_t*			rec,	/*!< in: origin of record */
-	ulint			extra,	/*!< in: number of bytes to
-					reserve between the record
-					header and the data payload
-					(normally REC_N_NEW_EXTRA_BYTES) */
-	const dict_index_t*	index,	/*!< in: record descriptor */
-	ulint			status,	/*!< in: status bits of the record */
-	const dfield_t*		fields,	/*!< in: array of data fields */
-	ulint			n_fields);/*!< in: number of data fields */
-/*********************************************************//**
 Builds a physical record out of a data tuple and
 stores it into the given buffer.
 @return	pointer to the origin of physical record */
@@ -722,10 +729,7 @@ UNIV_INTERN
 ulint
 rec_get_converted_size_comp_prefix(
 /*===============================*/
-	const dict_index_t*	index,	/*!< in: record descriptor;
-					dict_table_is_comp() is
-					assumed to hold, even if
-					it does not */
+	const dict_index_t*	index,	/*!< in: record descriptor */
 	const dfield_t*		fields,	/*!< in: array of data fields */
 	ulint			n_fields,/*!< in: number of data fields */
 	ulint*			extra);	/*!< out: extra size */
diff --git a/storage/innodb_plugin/rem/rem0rec.c b/storage/innodb_plugin/rem/rem0rec.c
index 9f90d2940dd..70d38262bce 100644
--- a/storage/innodb_plugin/rem/rem0rec.c
+++ b/storage/innodb_plugin/rem/rem0rec.c
@@ -167,7 +167,6 @@ rec_get_n_extern_new(
 {
 	const byte*	nulls;
 	const byte*	lens;
-	dict_field_t*	field;
 	ulint		null_mask;
 	ulint		n_extern;
 	ulint		i;
@@ -188,10 +187,13 @@ rec_get_n_extern_new(
 
 	/* read the lengths of fields 0..n */
 	do {
-		ulint	len;
+		const dict_field_t*	field
+			= dict_index_get_nth_field(index, i);
+		const dict_col_t*	col
+			= dict_field_get_col(field);
+		ulint			len;
 
-		field = dict_index_get_nth_field(index, i);
-		if (!(dict_field_get_col(field)->prtype & DATA_NOT_NULL)) {
+		if (!(col->prtype & DATA_NOT_NULL)) {
 			/* nullable field => read the null flag */
 
 			if (UNIV_UNLIKELY(!(byte) null_mask)) {
@@ -209,8 +211,6 @@ rec_get_n_extern_new(
 
 		if (UNIV_UNLIKELY(!field->fixed_len)) {
 			/* Variable-length field: read the length */
-			const dict_col_t*	col
-				= dict_field_get_col(field);
 			len = *lens--;
 			/* If the maximum length of the field is up
 			to 255 bytes, the actual length is always
@@ -239,16 +239,15 @@ rec_get_n_extern_new(
 Determine the offset to each field in a leaf-page record
 in ROW_FORMAT=COMPACT.  This is a special case of
 rec_init_offsets() and rec_get_offsets_func(). */
-UNIV_INTERN
+UNIV_INLINE __attribute__((nonnull))
 void
 rec_init_offsets_comp_ordinary(
 /*===========================*/
 	const rec_t*		rec,	/*!< in: physical record in
 					ROW_FORMAT=COMPACT */
-	ulint			extra,	/*!< in: number of bytes to reserve
-					between the record header and
-					the data payload
-					(usually REC_N_NEW_EXTRA_BYTES) */
+	ibool			temp,	/*!< in: whether to use the
+					format for temporary files in
+					index creation */
 	const dict_index_t*	index,	/*!< in: record descriptor */
 	ulint*			offsets)/*!< in/out: array of offsets;
 					in: n=rec_offs_n_fields(offsets) */
@@ -256,27 +255,38 @@ rec_init_offsets_comp_ordinary(
 	ulint		i		= 0;
 	ulint		offs		= 0;
 	ulint		any_ext		= 0;
-	const byte*	nulls		= rec - (extra + 1);
+	const byte*	nulls		= temp
+		? rec - 1
+		: rec - (1 + REC_N_NEW_EXTRA_BYTES);
 	const byte*	lens		= nulls
 		- UT_BITS_IN_BYTES(index->n_nullable);
-	dict_field_t*	field;
 	ulint		null_mask	= 1;
 
 #ifdef UNIV_DEBUG
-	/* We cannot invoke rec_offs_make_valid() here, because it can hold
-	that extra != REC_N_NEW_EXTRA_BYTES.  Similarly, rec_offs_validate()
-	will fail in that case, because it invokes rec_get_status(). */
+	/* We cannot invoke rec_offs_make_valid() here if temp=TRUE.
+	Similarly, rec_offs_validate() will fail in that case, because
+	it invokes rec_get_status(). */
 	offsets[2] = (ulint) rec;
 	offsets[3] = (ulint) index;
 #endif /* UNIV_DEBUG */
 
+	ut_ad(temp || dict_table_is_comp(index->table));
+
+	if (temp && dict_table_is_comp(index->table)) {
+		/* No need to do adjust fixed_len=0. We only need to
+		adjust it for ROW_FORMAT=REDUNDANT. */
+		temp = FALSE;
+	}
+
 	/* read the lengths of fields 0..n */
 	do {
-		ulint	len;
+		const dict_field_t*	field
+			= dict_index_get_nth_field(index, i);
+		const dict_col_t*	col
+			= dict_field_get_col(field);
+		ulint			len;
 
-		field = dict_index_get_nth_field(index, i);
-		if (!(dict_field_get_col(field)->prtype
-		      & DATA_NOT_NULL)) {
+		if (!(col->prtype & DATA_NOT_NULL)) {
 			/* nullable field => read the null flag */
 
 			if (UNIV_UNLIKELY(!(byte) null_mask)) {
@@ -296,10 +306,9 @@ rec_init_offsets_comp_ordinary(
 			null_mask <<= 1;
 		}
 
-		if (UNIV_UNLIKELY(!field->fixed_len)) {
+		if (!field->fixed_len
+		    || (temp && !dict_col_get_fixed_size(col, temp))) {
 			/* Variable-length field: read the length */
-			const dict_col_t*	col
-				= dict_field_get_col(field);
 			len = *lens--;
 			/* If the maximum length of the field is up
 			to 255 bytes, the actual length is always
@@ -393,9 +402,8 @@ rec_init_offsets(
 				= dict_index_get_n_unique_in_tree(index);
 			break;
 		case REC_STATUS_ORDINARY:
-			rec_init_offsets_comp_ordinary(rec,
-						       REC_N_NEW_EXTRA_BYTES,
-						       index, offsets);
+			rec_init_offsets_comp_ordinary(
+				rec, FALSE, index, offsets);
 			return;
 		}
 
@@ -766,17 +774,19 @@ rec_get_nth_field_offs_old(
 /**********************************************************//**
 Determines the size of a data tuple prefix in ROW_FORMAT=COMPACT.
 @return	total size */
-UNIV_INTERN
+UNIV_INLINE __attribute__((warn_unused_result, nonnull(1,2)))
 ulint
-rec_get_converted_size_comp_prefix(
-/*===============================*/
+rec_get_converted_size_comp_prefix_low(
+/*===================================*/
 	const dict_index_t*	index,	/*!< in: record descriptor;
 					dict_table_is_comp() is
 					assumed to hold, even if
 					it does not */
 	const dfield_t*		fields,	/*!< in: array of data fields */
 	ulint			n_fields,/*!< in: number of data fields */
-	ulint*			extra)	/*!< out: extra size */
+	ulint*			extra,	/*!< out: extra size */
+	ibool			temp)	/*!< in: whether this is a
+					temporary file record */
 {
 	ulint	extra_size;
 	ulint	data_size;
@@ -785,15 +795,25 @@ rec_get_converted_size_comp_prefix(
 	ut_ad(fields);
 	ut_ad(n_fields > 0);
 	ut_ad(n_fields <= dict_index_get_n_fields(index));
+	ut_ad(!temp || extra);
 
-	extra_size = REC_N_NEW_EXTRA_BYTES
+	extra_size = temp
+		? UT_BITS_IN_BYTES(index->n_nullable)
+		: REC_N_NEW_EXTRA_BYTES
 		+ UT_BITS_IN_BYTES(index->n_nullable);
 	data_size = 0;
 
+	if (temp && dict_table_is_comp(index->table)) {
+		/* No need to do adjust fixed_len=0. We only need to
+		adjust it for ROW_FORMAT=REDUNDANT. */
+		temp = FALSE;
+	}
+
 	/* read the lengths of fields 0..n */
 	for (i = 0; i < n_fields; i++) {
 		const dict_field_t*	field;
 		ulint			len;
+		ulint			fixed_len;
 		const dict_col_t*	col;
 
 		field = dict_index_get_nth_field(index, i);
@@ -809,8 +829,14 @@ rec_get_converted_size_comp_prefix(
 			continue;
 		}
 
-		ut_ad(len <= col->len || col->mtype == DATA_BLOB);
+		ut_ad(len <= col->len || col->mtype == DATA_BLOB
+		      || (col->len == 0 && col->mtype == DATA_VARCHAR));
 
+		fixed_len = field->fixed_len;
+		if (temp && fixed_len
+		    && !dict_col_get_fixed_size(col, temp)) {
+			fixed_len = 0;
+		}
 		/* If the maximum length of a variable-length field
 		is up to 255 bytes, the actual length is always stored
 		in one byte. If the maximum length is more than 255
@@ -818,11 +844,11 @@ rec_get_converted_size_comp_prefix(
 		0..127.  The length will be encoded in two bytes when
 		it is 128 or more, or when the field is stored externally. */
 
-		if (field->fixed_len) {
-			ut_ad(len == field->fixed_len);
+		if (fixed_len) {
+			ut_ad(len == fixed_len);
 			/* dict_index_add_col() should guarantee this */
 			ut_ad(!field->prefix_len
-			      || field->fixed_len == field->prefix_len);
+			      || fixed_len == field->prefix_len);
 		} else if (dfield_is_ext(&fields[i])) {
 			ut_ad(col->len >= 256 || col->mtype == DATA_BLOB);
 			extra_size += 2;
@@ -839,13 +865,30 @@ rec_get_converted_size_comp_prefix(
 		data_size += len;
 	}
 
-	if (UNIV_LIKELY_NULL(extra)) {
+	if (extra) {
 		*extra = extra_size;
 	}
 
 	return(extra_size + data_size);
 }
 
+/**********************************************************//**
+Determines the size of a data tuple prefix in ROW_FORMAT=COMPACT.
+@return	total size */
+UNIV_INTERN
+ulint
+rec_get_converted_size_comp_prefix(
+/*===============================*/
+	const dict_index_t*	index,	/*!< in: record descriptor */
+	const dfield_t*		fields,	/*!< in: array of data fields */
+	ulint			n_fields,/*!< in: number of data fields */
+	ulint*			extra)	/*!< out: extra size */
+{
+	ut_ad(dict_table_is_comp(index->table));
+	return(rec_get_converted_size_comp_prefix_low(
+		       index, fields, n_fields, extra, FALSE));
+}
+
 /**********************************************************//**
 Determines the size of a data tuple in ROW_FORMAT=COMPACT.
 @return	total size */
@@ -890,8 +933,8 @@ rec_get_converted_size_comp(
 		return(ULINT_UNDEFINED);
 	}
 
-	return(size + rec_get_converted_size_comp_prefix(index, fields,
-							 n_fields, extra));
+	return(size + rec_get_converted_size_comp_prefix_low(
+		       index, fields, n_fields, extra, FALSE));
 }
 
 /***********************************************************//**
@@ -1068,19 +1111,18 @@ rec_convert_dtuple_to_rec_old(
 
 /*********************************************************//**
 Builds a ROW_FORMAT=COMPACT record out of a data tuple. */
-UNIV_INTERN
+UNIV_INLINE __attribute__((nonnull))
 void
 rec_convert_dtuple_to_rec_comp(
 /*===========================*/
 	rec_t*			rec,	/*!< in: origin of record */
-	ulint			extra,	/*!< in: number of bytes to
-					reserve between the record
-					header and the data payload
-					(normally REC_N_NEW_EXTRA_BYTES) */
 	const dict_index_t*	index,	/*!< in: record descriptor */
-	ulint			status,	/*!< in: status bits of the record */
 	const dfield_t*		fields,	/*!< in: array of data fields */
-	ulint			n_fields)/*!< in: number of data fields */
+	ulint			n_fields,/*!< in: number of data fields */
+	ulint			status,	/*!< in: status bits of the record */
+	ibool			temp)	/*!< in: whether to use the
+					format for temporary files in
+					index creation */
 {
 	const dfield_t*	field;
 	const dtype_t*	type;
@@ -1092,31 +1134,44 @@ rec_convert_dtuple_to_rec_comp(
 	ulint		n_node_ptr_field;
 	ulint		fixed_len;
 	ulint		null_mask	= 1;
-	ut_ad(extra == 0 || dict_table_is_comp(index->table));
-	ut_ad(extra == 0 || extra == REC_N_NEW_EXTRA_BYTES);
+	ut_ad(temp || dict_table_is_comp(index->table));
 	ut_ad(n_fields > 0);
 
-	switch (UNIV_EXPECT(status, REC_STATUS_ORDINARY)) {
-	case REC_STATUS_ORDINARY:
+	if (temp) {
+		ut_ad(status == REC_STATUS_ORDINARY);
 		ut_ad(n_fields <= dict_index_get_n_fields(index));
 		n_node_ptr_field = ULINT_UNDEFINED;
-		break;
-	case REC_STATUS_NODE_PTR:
-		ut_ad(n_fields == dict_index_get_n_unique_in_tree(index) + 1);
-		n_node_ptr_field = n_fields - 1;
-		break;
-	case REC_STATUS_INFIMUM:
-	case REC_STATUS_SUPREMUM:
-		ut_ad(n_fields == 1);
-		n_node_ptr_field = ULINT_UNDEFINED;
-		break;
-	default:
-		ut_error;
-		return;
+		nulls = rec - 1;
+		if (dict_table_is_comp(index->table)) {
+			/* No need to do adjust fixed_len=0. We only
+			need to adjust it for ROW_FORMAT=REDUNDANT. */
+			temp = FALSE;
+		}
+	} else {
+		nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1);
+
+		switch (UNIV_EXPECT(status, REC_STATUS_ORDINARY)) {
+		case REC_STATUS_ORDINARY:
+			ut_ad(n_fields <= dict_index_get_n_fields(index));
+			n_node_ptr_field = ULINT_UNDEFINED;
+			break;
+		case REC_STATUS_NODE_PTR:
+			ut_ad(n_fields
+			      == dict_index_get_n_unique_in_tree(index) + 1);
+			n_node_ptr_field = n_fields - 1;
+			break;
+		case REC_STATUS_INFIMUM:
+		case REC_STATUS_SUPREMUM:
+			ut_ad(n_fields == 1);
+			n_node_ptr_field = ULINT_UNDEFINED;
+			break;
+		default:
+			ut_error;
+			return;
+		}
 	}
 
 	end = rec;
-	nulls = rec - (extra + 1);
 	lens = nulls - UT_BITS_IN_BYTES(index->n_nullable);
 	/* clear the SQL-null flags */
 	memset(lens + 1, 0, nulls - lens);
@@ -1162,6 +1217,10 @@ rec_convert_dtuple_to_rec_comp(
 
 		ifield = dict_index_get_nth_field(index, i);
 		fixed_len = ifield->fixed_len;
+		if (temp && fixed_len
+		    && !dict_col_get_fixed_size(ifield->col, temp)) {
+			fixed_len = 0;
+		}
 		/* If the maximum length of a variable-length field
 		is up to 255 bytes, the actual length is always stored
 		in one byte. If the maximum length is more than 255
@@ -1222,8 +1281,7 @@ rec_convert_dtuple_to_rec_new(
 	rec = buf + extra_size;
 
 	rec_convert_dtuple_to_rec_comp(
-		rec, REC_N_NEW_EXTRA_BYTES, index, status,
-		dtuple->fields, dtuple->n_fields);
+		rec, index, dtuple->fields, dtuple->n_fields, status, FALSE);
 
 	/* Set the info bits of the record */
 	rec_set_info_and_status_bits(rec, dtuple_get_info_bits(dtuple));
@@ -1285,6 +1343,54 @@ rec_convert_dtuple_to_rec(
 	return(rec);
 }
 
+#ifndef UNIV_HOTBACKUP
+/**********************************************************//**
+Determines the size of a data tuple prefix in ROW_FORMAT=COMPACT.
+@return	total size */
+UNIV_INTERN
+ulint
+rec_get_converted_size_temp(
+/*========================*/
+	const dict_index_t*	index,	/*!< in: record descriptor */
+	const dfield_t*		fields,	/*!< in: array of data fields */
+	ulint			n_fields,/*!< in: number of data fields */
+	ulint*			extra)	/*!< out: extra size */
+{
+	return(rec_get_converted_size_comp_prefix_low(
+		       index, fields, n_fields, extra, TRUE));
+}
+
+/******************************************************//**
+Determine the offset to each field in temporary file.
+@see rec_convert_dtuple_to_temp() */
+UNIV_INTERN
+void
+rec_init_offsets_temp(
+/*==================*/
+	const rec_t*		rec,	/*!< in: temporary file record */
+	const dict_index_t*	index,	/*!< in: record descriptor */
+	ulint*			offsets)/*!< in/out: array of offsets;
+					in: n=rec_offs_n_fields(offsets) */
+{
+	rec_init_offsets_comp_ordinary(rec, TRUE, index, offsets);
+}
+
+/*********************************************************//**
+Builds a temporary file record out of a data tuple.
+@see rec_init_offsets_temp() */
+UNIV_INTERN
+void
+rec_convert_dtuple_to_temp(
+/*=======================*/
+	rec_t*			rec,		/*!< out: record */
+	const dict_index_t*	index,		/*!< in: record descriptor */
+	const dfield_t*		fields,		/*!< in: array of data fields */
+	ulint			n_fields)	/*!< in: number of fields */
+{
+	rec_convert_dtuple_to_rec_comp(rec, index, fields, n_fields,
+				       REC_STATUS_ORDINARY, TRUE);
+}
+
 /**************************************************************//**
 Copies the first n fields of a physical record to a data tuple. The fields
 are copied to the memory heap. */
@@ -1495,6 +1601,7 @@ rec_copy_prefix_to_buf(
 
 	return(*buf + (rec - (lens + 1)));
 }
+#endif /* UNIV_HOTBACKUP */
 
 /***************************************************************//**
 Validates the consistency of an old-style physical record.
diff --git a/storage/innodb_plugin/row/row0merge.c b/storage/innodb_plugin/row/row0merge.c
index 342d81256f2..5a8d2de968f 100644
--- a/storage/innodb_plugin/row/row0merge.c
+++ b/storage/innodb_plugin/row/row0merge.c
@@ -291,6 +291,7 @@ row_merge_buf_add(
 		const dict_field_t*	ifield;
 		const dict_col_t*	col;
 		ulint			col_no;
+		ulint			fixed_len;
 		const dfield_t*		row_field;
 		ulint			len;
 
@@ -340,9 +341,21 @@ row_merge_buf_add(
 
 		ut_ad(len <= col->len || col->mtype == DATA_BLOB);
 
-		if (ifield->fixed_len) {
-			ut_ad(len == ifield->fixed_len);
+		fixed_len = ifield->fixed_len;
+		if (fixed_len && !dict_table_is_comp(index->table)
+		    && col->mbminlen != col->mbmaxlen) {
+			/* CHAR in ROW_FORMAT=REDUNDANT is always
+			fixed-length, but in the temporary file it is
+			variable-length for variable-length character
+			sets. */
+			fixed_len = 0;
+		}
+
+		if (fixed_len) {
+			ut_ad(len == fixed_len);
 			ut_ad(!dfield_is_ext(field));
+ 			ut_ad(!col->mbmaxlen || len >= col->mbminlen
+			      * (fixed_len / col->mbmaxlen));
 		} else if (dfield_is_ext(field)) {
 			extra_size += 2;
 		} else if (len < 128
@@ -363,12 +376,11 @@ row_merge_buf_add(
 		ulint	size;
 		ulint	extra;
 
-		size = rec_get_converted_size_comp(index,
-						   REC_STATUS_ORDINARY,
-						   entry, n_fields, &extra);
+		size = rec_get_converted_size_temp(
+			index, entry, n_fields, &extra);
 
-		ut_ad(data_size + extra_size + REC_N_NEW_EXTRA_BYTES == size);
-		ut_ad(extra_size + REC_N_NEW_EXTRA_BYTES == extra);
+		ut_ad(data_size + extra_size == size);
+		ut_ad(extra_size == extra);
 	}
 #endif /* UNIV_DEBUG */
 
@@ -572,14 +584,9 @@ row_merge_buf_write(
 		ulint		extra_size;
 		const dfield_t*	entry		= buf->tuples[i];
 
-		size = rec_get_converted_size_comp(index,
-						   REC_STATUS_ORDINARY,
-						   entry, n_fields,
-						   &extra_size);
+		size = rec_get_converted_size_temp(
+			index, entry, n_fields, &extra_size);
 		ut_ad(size >= extra_size);
-		ut_ad(extra_size >= REC_N_NEW_EXTRA_BYTES);
-		extra_size -= REC_N_NEW_EXTRA_BYTES;
-		size -= REC_N_NEW_EXTRA_BYTES;
 
 		/* Encode extra_size + 1 */
 		if (extra_size + 1 < 0x80) {
@@ -592,9 +599,8 @@ row_merge_buf_write(
 
 		ut_ad(b + size < block[1]);
 
-		rec_convert_dtuple_to_rec_comp(b + extra_size, 0, index,
-					       REC_STATUS_ORDINARY,
-					       entry, n_fields);
+		rec_convert_dtuple_to_temp(b + extra_size, index,
+					   entry, n_fields);
 
 		b += size;
 
@@ -841,7 +847,7 @@ err_exit:
 
 		*mrec = *buf + extra_size;
 
-		rec_init_offsets_comp_ordinary(*mrec, 0, index, offsets);
+		rec_init_offsets_temp(*mrec, index, offsets);
 
 		data_size = rec_offs_data_size(offsets);
 
@@ -860,7 +866,7 @@ err_exit:
 
 	*mrec = b + extra_size;
 
-	rec_init_offsets_comp_ordinary(*mrec, 0, index, offsets);
+	rec_init_offsets_temp(*mrec, index, offsets);
 
 	data_size = rec_offs_data_size(offsets);
 	ut_ad(extra_size + data_size < sizeof *buf);