1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-29 05:21:33 +03:00

MDEV-33496 Out of range error in AVG(YEAR(datetime)) due to a wrong data type

Functions extracting non-negative datetime components:

- YEAR(dt),        EXTRACT(YEAR FROM dt)
- QUARTER(td),     EXTRACT(QUARTER FROM dt)
- MONTH(dt),       EXTRACT(MONTH FROM dt)
- WEEK(dt),        EXTRACT(WEEK FROM dt)
- HOUR(dt),
- MINUTE(dt),
- SECOND(dt),
- MICROSECOND(dt),
- DAYOFYEAR(dt)
- EXTRACT(YEAR_MONTH FROM dt)

did not set their max_length properly, so in the DECIMAL
context they created a too small DECIMAL column, which
led to the 'Out of range value' error.

The problem is that most of these functions historically
returned the signed INT data type.

There were two simple ways to fix these functions:
1. Add +1 to max_length.
   But this would also change their size in the string context
   and create too long VARCHAR columns, with +1 excessive size.

2. Preserve max_length, but change the data type from INT to INT UNSIGNED.
   But this would break backward compatibility.
   Also, using UNSIGNED is generally not desirable,
   it's better to stay with signed when possible.

This fix implements another solution, which it makes all these functions
work well in all contexts: int, decimal, string.

Fix details:

- Adding a new special class Type_handler_long_ge0 - the data type
  handler for expressions which:
  * should look like normal signed INT
  * but which known not to return negative values
  Expressions handled by Type_handler_long_ge0 store in Item::max_length
  only the number of digits, without adding +1 for the sign.

- Fixing Item_extract to use Type_handler_long_ge0
  for non-negative datetime components:
   YEAR, YEAR_MONTH, QUARTER, MONTH, WEEK

- Adding a new abstract class Item_long_ge0_func, for functions
  returning non-negative datetime components.
  Item_long_ge0_func uses Type_handler_long_ge0 as the type handler.
  The class hierarchy now looks as follows:

Item_long_ge0_func
  Item_long_func_date_field
    Item_func_to_days
    Item_func_dayofmonth
    Item_func_dayofyear
    Item_func_quarter
    Item_func_year
  Item_long_func_time_field
    Item_func_hour
    Item_func_minute
    Item_func_second
    Item_func_microsecond

- Cleanup: EXTRACT(QUARTER FROM dt) created an excessive VARCHAR column
  in string context. Changing its length from 2 to 1.
This commit is contained in:
Alexander Barkov
2024-02-21 11:41:50 +04:00
parent d57c44f626
commit e63311c2cf
16 changed files with 1373 additions and 38 deletions

View File

@ -41,6 +41,7 @@ Named_type_handler<Type_handler_bool> type_handler_bool("boolean");
Named_type_handler<Type_handler_tiny> type_handler_stiny("tinyint");
Named_type_handler<Type_handler_short> type_handler_sshort("smallint");
Named_type_handler<Type_handler_long> type_handler_slong("int");
Named_type_handler<Type_handler_long_ge0> type_handler_slong_ge0("int");
Named_type_handler<Type_handler_int24> type_handler_sint24("mediumint");
Named_type_handler<Type_handler_longlong> type_handler_slonglong("bigint");
Named_type_handler<Type_handler_utiny> type_handler_utiny("tiny unsigned");
@ -4629,6 +4630,10 @@ bool Type_handler_general_purpose_int::
bool unsigned_flag= items[0]->unsigned_flag;
for (uint i= 1; i < nitems; i++)
{
/*
TODO: avoid creating DECIMAL for a mix of ulong and slong_ge0.
It's too late for 10.5. Let's do it in a higher version.
*/
if (unsigned_flag != items[i]->unsigned_flag)
{
// Convert a mixture of signed and unsigned int to decimal
@ -4638,6 +4643,21 @@ bool Type_handler_general_purpose_int::
}
}
func->aggregate_attributes_int(items, nitems);
for (uint i= 0; i < nitems; i++)
{
if (items[i]->type_handler() == &type_handler_slong_ge0)
{
/*
A slong_ge0 argument found.
We need to add an extra character for the sign.
TODO: rewrite aggregate_attributes_int() to find
the maximum decimal_precision() instead of the maximum max_length.
This change is too late for 10.5, so let's do it in a higher version.
*/
uint digits_and_sign= items[i]->decimal_precision() + 1;
set_if_bigger(func->max_length, digits_and_sign);
}
}
handler->set_handler(func->unsigned_flag ?
handler->type_handler()->type_handler_unsigned() :
handler->type_handler()->type_handler_signed());
@ -4931,6 +4951,13 @@ bool Type_handler_real_result::
/*************************************************************************/
bool Type_handler_long_ge0::
Item_sum_hybrid_fix_length_and_dec(Item_sum_hybrid *func) const
{
return func->fix_length_and_dec_sint_ge0();
}
bool Type_handler_int_result::
Item_sum_hybrid_fix_length_and_dec(Item_sum_hybrid *func) const
{
@ -6373,6 +6400,14 @@ bool Type_handler_int_result::
}
bool Type_handler_long_ge0::
Item_func_round_fix_length_and_dec(Item_func_round *item) const
{
item->fix_arg_slong_ge0();
return false;
}
bool Type_handler_year::
Item_func_round_fix_length_and_dec(Item_func_round *item) const
{
@ -6594,6 +6629,14 @@ bool Type_handler_int_result::
}
bool Type_handler_long_ge0::
Item_func_abs_fix_length_and_dec(Item_func_abs *item) const
{
item->fix_length_and_dec_sint_ge0();
return false;
}
bool Type_handler_real_result::
Item_func_abs_fix_length_and_dec(Item_func_abs *item) const
{
@ -6704,6 +6747,22 @@ bool Type_handler::
}
bool Type_handler_long_ge0::
Item_func_signed_fix_length_and_dec(Item_func_signed *item) const
{
item->fix_length_and_dec_sint_ge0();
return false;
}
bool Type_handler_long_ge0::
Item_func_unsigned_fix_length_and_dec(Item_func_unsigned *item) const
{
item->fix_length_and_dec_sint_ge0();
return false;
}
bool Type_handler_string_result::
Item_func_signed_fix_length_and_dec(Item_func_signed *item) const
{
@ -7189,6 +7248,18 @@ uint Type_handler_int_result::Item_decimal_precision(const Item *item) const
return MY_MIN(prec, DECIMAL_MAX_PRECISION);
}
uint Type_handler_long_ge0::Item_decimal_precision(const Item *item) const
{
DBUG_ASSERT(item->max_length);
DBUG_ASSERT(!item->decimals);
/*
Unlinke in Type_handler_long, Type_handler_long_ge does
not reserve one character for the sign. All max_length
characters are digits.
*/
return MY_MIN(item->max_length, DECIMAL_MAX_PRECISION);
}
uint Type_handler_time_common::Item_decimal_precision(const Item *item) const
{
return 7 + MY_MIN(item->decimals, TIME_SECOND_PART_DIGITS);
@ -8190,6 +8261,26 @@ Field *Type_handler_long::
}
Field *Type_handler_long_ge0::
make_table_field_from_def(TABLE_SHARE *share, MEM_ROOT *mem_root,
const LEX_CSTRING *name,
const Record_addr &rec, const Bit_addr &bit,
const Column_definition_attributes *attr,
uint32 flags) const
{
/*
We're converting signed long_ge0 to signed long.
So add one character for the sign.
*/
return new (mem_root)
Field_long(rec.ptr(), (uint32) attr->length + 1/*sign*/,
rec.null_ptr(), rec.null_bit(),
attr->unireg_check, name,
f_is_zerofill(attr->pack_flag) != 0,
f_is_dec(attr->pack_flag) == 0);
}
Field *Type_handler_longlong::
make_table_field_from_def(TABLE_SHARE *share, MEM_ROOT *mem_root,
const LEX_CSTRING *name,