Description:
ALTER TABLE ... COALESCE PARTITION on a partitioned InnoDB table with a functional/expression index raises SIGSEGV (signal 11) in rec_convert_dtuple_to_rec_comp during the rebuild.
CREATE TABLE `t5` (
`c611` smallint DEFAULT NULL, `c612` date DEFAULT NULL, `c614` datetime(1) DEFAULT NULL,
`c615` timestamp NOT NULL, `c616` year DEFAULT NULL, `c617` varchar(230), `c620` double unsigned DEFAULT NULL,
KEY `i75` (`c612`,`c615`,(truncate(`c617`,1)),(lower(`c615`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8 PARTITION BY HASH(`c616`) PARTITIONS 8;
Trigger: ALTER TABLE t5 COALESCE PARTITION 3;
Stack (key frames; full backtrace attached):
#5 rec_convert_dtuple_to_rec_comp (rem0rec.cc:873) <- crash: memcpy(end, NULL, UNIV_SQL_NULL)
#9 btr_cur_optimistic_insert (btr0cur.cc:3411)
#16 row_insert_for_mysql_using_ins_graph (row0mysql.cc:1645)
#18 ha_innobase::write_row (ha_innodb.cc:13428)
#19 ha_innopart::write_row_in_new_part (handler0alter.cc:11389)
#20 Partition_helper::copy_partitions (partition_handler.cc:1117)
#22 ha_innopart::inplace_alter_partition (handler0alter.cc:11455)
#25 mysql_alter_table (sql_table.cc:18693)
Root cause (with [VCOL] trace-log evidence):
[VCOL] = trace logs added via probe logging at dict_mem_table_add_v_col and the row read/write paths, recording column order and which slot each value is written to / read from.
i75 hidden columns (name = MD5(index_name + key_part_number)):
- 9b1faf…=MD5("i752") = truncate(c617,1), key part 3, mtype 10 (double), prtype 0x2405 (nullable)
- ee28b6…=MD5("i753") = lower(c615), key part 4, mtype 12 (string), prtype 0x2e210f (NOT_NULL)
1. Existing table stores them in order [lower, truncate] ([VCOL] CALLER=dd_fill, identical for p0..p7):
i=7 field=ee28b6(lower) pos=7 prtype=3023119 (NOT_NULL)
i=8 field=9b1faf(truncate) pos=8 prtype=9221 (nullable)
DUMP v_pos=0 ind=7 prtype=3023119(lower); v_pos=1 ind=8 prtype=9221(truncate)
→ lower at pos 7 / slot 0, truncate at pos 8 / slot 1.
2. COALESCE rebuild re-sorts the temp partition to [truncate, lower] (prepare_fields_and_keys, sql/sql_table.cc:16064, name-ascending std::sort; [VCOL] CALLER=ha_create, identical for p0#tmp..p4#tmp):
i=7 field=9b1faf(truncate) pos=7
i=8 field=ee28b6(lower) pos=8
DUMP v_pos=0 ind=7 prtype=9221(truncate); v_pos=1 ind=8 prtype=3023119(lower)
→ truncate at pos 7 / slot 0, lower at pos 8 / slot 1 (opposite of the existing table).
3. Row copy crosses the two orders ([VCOL] WRITE/READ, temp p3#tmp):
WRITE slot=0 mysql_col_no=7 len=77 # value from old pos 7 (lower) → slot 0
WRITE slot=1 mysql_col_no=8 len=0 # value from old pos 8 (truncate; c617 NULL→NULL) → slot 1
READ index=i75 v_pos=1 ind=8 prtype=3023119 len=UNIV_SQL_NULL # lower's slot (slot 1) → NULL
Temp slot ownership (from 2): slot 0 = truncate, slot 1 = lower (NOT_NULL). WRITE put lower's value in slot 0 and truncate's NULL in slot 1 → lower's slot holds NULL.
4. lower is NOT_NULL but its slot is NULL → memcpy(end, NULL, UNIV_SQL_NULL) → SEGV (rem0rec.cc:873). Matches gdb: index->table=t5#p#p3#tmp, index->fields[3] (lower) prtype=0x2e210f (NOT_NULL), data=NULL,
len=UNIV_SQL_NULL.
How to repeat:
-- suite/innodb/t/innodb_funcidx_coalesce_vcol_reorder.test
SET @@sql_mode=''; -- (1) non-strict mode → lower hidden column becomes NOT_NULL
-- (crash needs this; strict → nullable → silent corruption, no crash)
SET SESSION original_server_version=0; -- (2) create without name-sorting → hidden columns keep definition order [lower,truncate]
CREATE TABLE t5 (c1 int, c2 int, c_nn varchar(20) NOT NULL, c_n double,
KEY idx (c1, c2, (lower(c_nn)), (truncate(c_n,1))))
PARTITION BY HASH(c1) PARTITIONS 4;
INSERT INTO t5 VALUES (1,1,'a',NULL),(2,2,'b',NULL),(3,3,'c',NULL),(4,4,'d',NULL);
SET SESSION original_server_version=80022; -- ALTER with name-sorting → rebuild re-sorts to [truncate,lower] → crossed → crash
ALTER TABLE t5 COALESCE PARTITION 1; -- signal 11 @ rec_convert_dtuple_to_rec_comp
Controls (both pass, proving both levers are needed):
- without SET original_server_version (default; create and rebuild use the same order) → PASS;
- without SET @@sql_mode='' (strict; lower nullable) → PASS.
[VCOL] logs confirm: lower's prtype goes 0x2405 (nullable) → 0x2e210f (NOT_NULL); existing table [lower,truncate], temp partition [truncate,lower]; crash point READ v_pos=1 ind=8 prtype=3023119
len=UNIV_SQL_NULL, matching the cloud.
Suggested fix:
In prepare_fields_and_keys (sql/sql_table.cc:16064), on the ALTER-rebuild path, do not std::sort the existing functional-index hidden columns; keep their existing-table order — i.e. skip the std::sort at
16064 and append directly at 16070 in original order.
Rationale: name-canonicalization belongs to CREATE (add_functional_index_to_create_list, sql/sql_table.cc:8546); an ALTER rebuild must not re-sort columns that already have data, because the re-sorted order
diverges from the data's column order and triggers the slot crossing above. COALESCE adds no new columns, so skipping 16064 fixes this case.
Boundary: ALTER TABLE ... ADD functional INDEX adds new hidden columns via 8546 (name-ascending insert); with 16064 removed, the new columns and the existing ones could end up in a mixed order, which needs
separate verification. This fix targets the COALESCE rebuild (no new columns).
Description: ALTER TABLE ... COALESCE PARTITION on a partitioned InnoDB table with a functional/expression index raises SIGSEGV (signal 11) in rec_convert_dtuple_to_rec_comp during the rebuild. CREATE TABLE `t5` ( `c611` smallint DEFAULT NULL, `c612` date DEFAULT NULL, `c614` datetime(1) DEFAULT NULL, `c615` timestamp NOT NULL, `c616` year DEFAULT NULL, `c617` varchar(230), `c620` double unsigned DEFAULT NULL, KEY `i75` (`c612`,`c615`,(truncate(`c617`,1)),(lower(`c615`))) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 PARTITION BY HASH(`c616`) PARTITIONS 8; Trigger: ALTER TABLE t5 COALESCE PARTITION 3; Stack (key frames; full backtrace attached): #5 rec_convert_dtuple_to_rec_comp (rem0rec.cc:873) <- crash: memcpy(end, NULL, UNIV_SQL_NULL) #9 btr_cur_optimistic_insert (btr0cur.cc:3411) #16 row_insert_for_mysql_using_ins_graph (row0mysql.cc:1645) #18 ha_innobase::write_row (ha_innodb.cc:13428) #19 ha_innopart::write_row_in_new_part (handler0alter.cc:11389) #20 Partition_helper::copy_partitions (partition_handler.cc:1117) #22 ha_innopart::inplace_alter_partition (handler0alter.cc:11455) #25 mysql_alter_table (sql_table.cc:18693) Root cause (with [VCOL] trace-log evidence): [VCOL] = trace logs added via probe logging at dict_mem_table_add_v_col and the row read/write paths, recording column order and which slot each value is written to / read from. i75 hidden columns (name = MD5(index_name + key_part_number)): - 9b1faf…=MD5("i752") = truncate(c617,1), key part 3, mtype 10 (double), prtype 0x2405 (nullable) - ee28b6…=MD5("i753") = lower(c615), key part 4, mtype 12 (string), prtype 0x2e210f (NOT_NULL) 1. Existing table stores them in order [lower, truncate] ([VCOL] CALLER=dd_fill, identical for p0..p7): i=7 field=ee28b6(lower) pos=7 prtype=3023119 (NOT_NULL) i=8 field=9b1faf(truncate) pos=8 prtype=9221 (nullable) DUMP v_pos=0 ind=7 prtype=3023119(lower); v_pos=1 ind=8 prtype=9221(truncate) → lower at pos 7 / slot 0, truncate at pos 8 / slot 1. 2. COALESCE rebuild re-sorts the temp partition to [truncate, lower] (prepare_fields_and_keys, sql/sql_table.cc:16064, name-ascending std::sort; [VCOL] CALLER=ha_create, identical for p0#tmp..p4#tmp): i=7 field=9b1faf(truncate) pos=7 i=8 field=ee28b6(lower) pos=8 DUMP v_pos=0 ind=7 prtype=9221(truncate); v_pos=1 ind=8 prtype=3023119(lower) → truncate at pos 7 / slot 0, lower at pos 8 / slot 1 (opposite of the existing table). 3. Row copy crosses the two orders ([VCOL] WRITE/READ, temp p3#tmp): WRITE slot=0 mysql_col_no=7 len=77 # value from old pos 7 (lower) → slot 0 WRITE slot=1 mysql_col_no=8 len=0 # value from old pos 8 (truncate; c617 NULL→NULL) → slot 1 READ index=i75 v_pos=1 ind=8 prtype=3023119 len=UNIV_SQL_NULL # lower's slot (slot 1) → NULL Temp slot ownership (from 2): slot 0 = truncate, slot 1 = lower (NOT_NULL). WRITE put lower's value in slot 0 and truncate's NULL in slot 1 → lower's slot holds NULL. 4. lower is NOT_NULL but its slot is NULL → memcpy(end, NULL, UNIV_SQL_NULL) → SEGV (rem0rec.cc:873). Matches gdb: index->table=t5#p#p3#tmp, index->fields[3] (lower) prtype=0x2e210f (NOT_NULL), data=NULL, len=UNIV_SQL_NULL. How to repeat: -- suite/innodb/t/innodb_funcidx_coalesce_vcol_reorder.test SET @@sql_mode=''; -- (1) non-strict mode → lower hidden column becomes NOT_NULL -- (crash needs this; strict → nullable → silent corruption, no crash) SET SESSION original_server_version=0; -- (2) create without name-sorting → hidden columns keep definition order [lower,truncate] CREATE TABLE t5 (c1 int, c2 int, c_nn varchar(20) NOT NULL, c_n double, KEY idx (c1, c2, (lower(c_nn)), (truncate(c_n,1)))) PARTITION BY HASH(c1) PARTITIONS 4; INSERT INTO t5 VALUES (1,1,'a',NULL),(2,2,'b',NULL),(3,3,'c',NULL),(4,4,'d',NULL); SET SESSION original_server_version=80022; -- ALTER with name-sorting → rebuild re-sorts to [truncate,lower] → crossed → crash ALTER TABLE t5 COALESCE PARTITION 1; -- signal 11 @ rec_convert_dtuple_to_rec_comp Controls (both pass, proving both levers are needed): - without SET original_server_version (default; create and rebuild use the same order) → PASS; - without SET @@sql_mode='' (strict; lower nullable) → PASS. [VCOL] logs confirm: lower's prtype goes 0x2405 (nullable) → 0x2e210f (NOT_NULL); existing table [lower,truncate], temp partition [truncate,lower]; crash point READ v_pos=1 ind=8 prtype=3023119 len=UNIV_SQL_NULL, matching the cloud. Suggested fix: In prepare_fields_and_keys (sql/sql_table.cc:16064), on the ALTER-rebuild path, do not std::sort the existing functional-index hidden columns; keep their existing-table order — i.e. skip the std::sort at 16064 and append directly at 16070 in original order. Rationale: name-canonicalization belongs to CREATE (add_functional_index_to_create_list, sql/sql_table.cc:8546); an ALTER rebuild must not re-sort columns that already have data, because the re-sorted order diverges from the data's column order and triggers the slot crossing above. COALESCE adds no new columns, so skipping 16064 fixes this case. Boundary: ALTER TABLE ... ADD functional INDEX adds new hidden columns via 8546 (name-ascending insert); with 16064 removed, the new columns and the existing ones could end up in a mixed order, which needs separate verification. This fix targets the COALESCE rebuild (no new columns).