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)) {
|
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);
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.