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.
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.