Bug #120082 Debug assert failure in Item::tmp_table_field_from_field_type() when using CAST(expr AS YEAR) with UNION
Submitted: 17 Mar 8:09 Modified: 17 Mar 11:52
Reporter: Zike Wang Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S6 (Debug Builds)
Version:8.0.44 OS:Any
Assigned to: CPU Architecture:Any
Tags: assert, cast, debug, regression, UNION, year

[17 Mar 8:09] Zike Wang
Description:
CAST(expr AS YEAR) combined with UNION causes debug assert failure (signal 6 / SIGABRT).

The assert is at sql/item.cc:6507:
  assert(max_length == 4);  // Field_year is only for length 4.

Root cause: a mismatch between two pieces of code introduced in 8.0.22.

  1) set_data_type_year() in item.h sets max_length = 5 via fix_char_length(5).
     This is intentional -- decimal_precision() needs the extra sign byte:
       precision = max_char_length - decimals - sign = 5 - 0 - 1 = 4

  2) tmp_table_field_from_field_type() in item.cc asserts max_length == 4,
     matching Field_year's field_length, but not the Item-level max_length.

A plain SELECT CAST(50 AS YEAR) never hits this assert, because FUNC_ITEM
goes through create_tmp_field_from_item() -> INT_RESULT -> Field_long,
bypassing the MYSQL_TYPE_YEAR branch entirely.

UNION triggers it because Item_type_holder (TYPE_HOLDER) goes through
make_field_by_type() -> tmp_table_field_from_field_type(), which switches
on data_type() and enters the MYSQL_TYPE_YEAR case with max_length still = 5.

Call stack:
  Query_result_union::create_result_table()
    -> create_tmp_table()
      -> create_tmp_field()
        -> Item_aggregate_type::make_field_by_type()
          -> Item::tmp_table_field_from_field_type()
            -> assert(max_length == 4)  *** FAILS ***

Introduced in 8.0.22 (when CAST(expr AS YEAR) and Item_typecast_year were added).
Confirmed on 8.0.44-debug. Likely affects all versions from 8.0.22 onward.
Release builds are not affected (assert compiles to no-op, query returns correct results).

How to repeat:
Build MySQL with -DCMAKE_BUILD_TYPE=Debug -DWITH_DEBUG=ON, then:

  SELECT CAST(50 AS YEAR) UNION ALL SELECT CAST(70 AS YEAR);

mysqld crashes immediately. The error log shows:
  mysqld: sql/item.cc:6507: Assertion `max_length == 4' failed.

Other triggering statements:
  SELECT CAST(50 AS YEAR) UNION SELECT CAST(70 AS YEAR);
  SELECT * FROM (SELECT CAST(1 AS YEAR) UNION ALL SELECT CAST(2 AS YEAR)) t;

Suggested fix:
Relax the assert in tmp_table_field_from_field_type() (sql/item.cc):

  case MYSQL_TYPE_YEAR:
-   assert(max_length == 4);
+   assert(max_length == 4 || max_length == 5);
    field = new (*THR_MALLOC) Field_year(m_nullable, item_name.ptr());
    break;

max_length == 4 comes from Field_year (field_length = 4).
max_length == 5 comes from set_data_type_year() which uses fix_char_length(5)
to make decimal_precision() return 4 via the formula (5 - 0 - 1 = 4).
Both are valid; the assert just did not account for the Item-level convention.
[17 Mar 11:31] Roy Lyseng
Thank you for the bug report.
Verified as described.
Note that this problem is fixed in release 8.4 and later,
and we recommend an upgrade.
[17 Mar 11:52] Zike Wang
Thank you for the confirmation and the fix reference.

I've located the relevant commit: https://github.com/mysql/mysql-server/commit/2469231b1a5f2596d708bfa92aa84dac7c808092

The fix modifies set_data_type_year() to use fix_char_length(4) with unsigned_flag = true, which is indeed a more thorough approach than the workaround we applied (relaxing the assert to accept both max_length == 4 and max_length == 5). The root cause alignment makes sense — Field_year is fundamentally a 4-digit unsigned type, so the Item-level metadata should reflect that.

Thanks again for the quick verification.