Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-18627

Wrong result after instant size change of integer

Details

    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.

      Attachments

        Issue Links

          Activity

            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.

            marko Marko Mäkelä added a comment - 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 .

            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.

            marko Marko Mäkelä added a comment - 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 .

            People

              marko Marko Mäkelä
              marko Marko Mäkelä
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:

                Git Integration

                  Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.