[MDEV-18627] Wrong result after instant size change of integer Created: 2019-02-18  Updated: 2020-07-20  Resolved: 2019-02-18

Status: Closed
Project: MariaDB Server
Component/s: Storage Engine - InnoDB
Affects Version/s: 10.4.3
Fix Version/s: 10.4.3

Type: Bug Priority: Blocker
Reporter: Marko Mäkelä Assignee: Marko Mäkelä
Resolution: Fixed Votes: 0
Labels: regression

Issue Links:
Problem/Incident
is caused by MDEV-15563 Instant failure-free data type conver... Closed
Relates
relates to MDEV-17520 Instant ALTER TABLE for failure-free ... Stalled
relates to MDEV-22771 Assertion `fields[i].same(instant.fie... Closed

 Description   

The following test fails for ROW_FORMAT=REDUNDANT:

--source include/have_innodb.inc
--source include/innodb_row_format.inc
CREATE TABLE t1 (a SERIAL, b INT, c TINYINT UNIQUE) ENGINE=InnoDB;
INSERT INTO t1 (c) VALUES(1),(2),(3);
--enable_info
ALTER TABLE t1 MODIFY c BIGINT;
--disable_info
UPDATE t1 SET b=1 WHERE c=2;
UPDATE t1 SET c=4 WHERE a=3;
UPDATE t1 SET b=2 WHERE c>3;
--error ER_DUP_ENTRY
UPDATE t1 SET c=c+1;
SELECT * FROM t1;
DROP TABLE t1;

It fails to flag the duplicate key error in the UPDATE, and the results are wrong. The most likely explanation is that row_build() is not (sign-)extending the integer column c to the correct size. For example, the value 0x83 should be properly sign-extended to 0x8000000000000003.



 Comments   
Comment by Marko Mäkelä [ 2019-02-18 ]

I tried the following, but it revealed a further problem:

diff --git a/storage/innobase/row/row0row.cc b/storage/innobase/row/row0row.cc
index 400faba0d88..ffcb9581de3 100644
--- a/storage/innobase/row/row0row.cc
+++ b/storage/innobase/row/row0row.cc
@@ -377,6 +377,89 @@ row_build_index_entry_low(
 	return entry;
 }
 
+/** Pad an instanly extended fixed-length column to the current size.
+@param[in,out]	heap	memory heap for allocations
+@param[in,out]	d	column data
+@param[in,out]	len	length of the data
+@param[in]	c	column metadata */
+ATTRIBUTE_COLD
+static void
+row_build_pad(mem_heap_t* heap, const void*& d, ulint& len, const dict_col_t&c)
+{
+	DBUG_ASSERT(len);
+	DBUG_ASSERT(c.len > len);
+	byte* b = static_cast<byte*>(mem_heap_alloc(heap, c.len));
+
+	/* This is similar to row_sel_field_store_in_mysql_format(). */
+	switch (c.mtype) {
+	default: DBUG_ASSERT(!"unhandled type"); break;
+	case DATA_FIXBINARY:
+		memcpy(b, d, len);
+		memset(b + len, 0, c.len - len);
+		break;
+	case DATA_INT:
+		if (c.prtype & DATA_UNSIGNED) {
+			memset(b, 0, c.len - len);
+			memcpy(&b[c.len - len], d, len);
+		} else if (*static_cast<const byte*>(d) & 0x80) {
+			/* non-negative signed (the sign bit is inverted) */
+			*b = 0x80;
+			memset(b + 1, 0, c.len - len - 1);
+			memcpy(&b[c.len - len], d, len);
+			b[c.len - len] &= 0x7f;
+		} else {
+			/* negative (the sign bit is inverted) */
+			*b = 0x7f;
+			memset(b + 1, 0xff, c.len - len - 1);
+			memcpy(&b[c.len - len], d, len);
+			b[c.len - len] |= 0x80;
+		}
+		break;
+	case DATA_CHAR:
+		memcpy(b, d, len);
+		byte* pad = b + len;
+		const byte* const end = b + c.len;
+		switch (c.mbminlen) {
+		default:
+			ut_error;
+		case 1:
+			memset(pad, 0x20, c.len - len);
+			break;
+		case 2:
+			/* A space char is two bytes,
+			0x0020 in UCS2 and UTF-16 */
+
+			if (UNIV_UNLIKELY(len & 1)) {
+				/* A 0x20 has been stripped from the column.
+				Pad it back. */
+				if (pad < end) {
+					*pad++ = 0x20;
+				}
+			}
+			while (pad < end) {
+				*pad++ = 0x00;
+				*pad++ = 0x20;
+			}
+			break;
+		case 4:
+			/* InnoDB should never have stripped partial
+			UTF-32 characters. */
+			DBUG_ASSERT(!(len & 3));
+			DBUG_ASSERT(!(c.len & 3));
+			while (pad < end) {
+				*pad++ = 0x00;
+				*pad++ = 0x00;
+				*pad++ = 0x00;
+				*pad++ = 0x20;
+			}
+			break;
+		}
+	}
+
+	d = b;
+	len = c.len;
+}
+
 /** An inverse function to row_build_index_entry. Builds a row from a
 record in a clustered index, with possible indexing on ongoing
 addition of new virtual columns.
@@ -561,7 +644,25 @@ row_build_low(
 			if (field && type != ROW_COPY_POINTERS) {
 				field = mem_heap_dup(heap, field, len);
 			}
+		} else if (len == UNIV_SQL_NULL) {
+			DBUG_ASSERT(index->fields[i].col->is_nullable());
+		} else if (index->fields[i].fixed_len) {
+			DBUG_ASSERT(len == index->fields[i].fixed_len);
+		} else if (UNIV_LIKELY(index->table->not_redundant())) {
+		} else {
+			dict_col_t& col = *index->fields[i].col;
+			switch (col.mtype) {
+			case DATA_CHAR: case DATA_FIXBINARY: case DATA_INT:
+				if (len == col.len) {
+					break;
+				}
+				/* dtype_get_fixed_size_low() set
+				fixed_len=0 for these fixed-length.
+				Pad to the current size. */
+				row_build_pad(heap, field, len, col);
+			}
 		}
+		DBUG_ASSERT(len >= index->fields[i].fixed_len);
 		dfield_set_data(dfield, field, len);
 
 		if (rec_offs_nth_extern(offsets, i)) {

With this patch applied, we would get the following:

10.4 33fd3998d253aad13913cef00a5f0d3629e423ec (patched)

2019-02-18 16:39:34 8 [ERROR] InnoDB: Record in index `c` of table `test`.`t1` was not found on update: TUPLE (info_bits=0, 2 fields): {[8]        (0x8000000000000003),[8]        (0x0000000000000003)} at: RECORD(info_bits=0, 1 fields): {[8]infimum (0x696E66696D756D00)}
mysqld: /mariadb/10.4m/storage/innobase/row/row0upd.cc:2430: dberr_t row_upd_sec_index_entry(upd_node_t *, que_thr_t *): Assertion `0' failed.
#4  0x000055555652cc5c in row_upd_sec_index_entry (node=0x7fff8c02f910, thr=0x7fff8c02fc48)
    at /mariadb/10.4m/storage/innobase/row/row0upd.cc:2430
#5  0x00005555565284c7 in row_upd_sec_step (node=0x7fff8c02f910, thr=0x7fff8c02fc48)
    at /mariadb/10.4m/storage/innobase/row/row0upd.cc:2543
#6  0x00005555565249ec in row_upd (node=0x7fff8c02f910, thr=0x7fff8c02fc48)
    at /mariadb/10.4m/storage/innobase/row/row0upd.cc:3322
#7  0x00005555565242f5 in row_upd_step (thr=0x7fff8c02fc48)
    at /mariadb/10.4m/storage/innobase/row/row0upd.cc:3437
#8  0x00005555564bcf73 in row_update_for_mysql (prebuilt=0x7fff8c02f118)
    at /mariadb/10.4m/storage/innobase/row/row0mysql.cc:1894
#9  0x00005555562fb208 in ha_innobase::update_row (this=0x7fff8c073490, 
    old_row=0x7fff8c01e400 "\373\003", new_row=0x7fff8c01e3e8 "\373\003")
    at /mariadb/10.4m/storage/innobase/handler/ha_innodb.cc:8843
#10 0x0000555556079d89 in handler::ha_update_row (this=0x7fff8c073490, 
    old_data=0x7fff8c01e400 "\373\003", new_data=0x7fff8c01e3e8 "\373\003")
    at /mariadb/10.4m/sql/handler.cc:6539
#11 0x0000555555e17ed2 in mysql_update (thd=0x7fff8c000cf8, table_list=0x7fff8c016218, fields=..., 
    values=..., conds=0x7fff8c016be0, order_num=0, order=0x0, limit=18446744073709551615, 
    handle_duplicates=DUP_ERROR, ignore=false, found_return=0x7ffff08cb358, 
    updated_return=0x7ffff08cb350) at /mariadb/10.4m/sql/sql_update.cc:946
#12 0x0000555555ce2c8e in mysql_execute_command (thd=0x7fff8c000cf8)
    at /mariadb/10.4m/sql/sql_parse.cc:4623
#13 0x0000555555cd9170 in mysql_parse (thd=0x7fff8c000cf8, 
    rawbuf=0x7fff8c016130 "UPDATE t1 SET c=4 WHERE a=3", length=27, parser_state=0x7ffff08ccea0, 
    is_com_multi=false, is_next_command=false) at /mariadb/10.4m/sql/sql_parse.cc:8141

The reason is that even though the secondary index was rebuilt during the ALTER TABLE, the wrong values were inserted into it (1-byte TINYINT values instead of the 8-byte BIGINT values).

I do not think that for MariaDB 10.4, there is any other option than to revert the part of MDEV-15563 that deals with extending fixed-size columns. We can revisit this later, perhaps as part of MDEV-17520.

Comment by Marko Mäkelä [ 2019-02-18 ]

I reverted the part of MDEV-15563 that instantly extends fixed-size columns.
We can revive that change in a later version of MariaDB, possibly as part of MDEV-17520.

Generated at Thu Feb 08 08:45:32 UTC 2024 using Jira 8.20.16#820016-sha1:9d11dbea5f4be3d4cc21f03a88dd11d8c8687422.