Bug #121117 SIGSEGV in rec_convert_dtuple_to_rec_comp during ALTER TABLE ... COALESCE PARTITION on a partitioned InnoDB
Submitted: 18 Aug 12:41
Reporter: Alice Alice Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: Data Dictionary Severity:S1 (Critical)
Version:8.0 all version OS:Linux
Assigned to: CPU Architecture:x86

[18 Aug 12:41] Alice Alice
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).