Commit ccbf81d5 authored by Evgeny Potemkin's avatar Evgeny Potemkin

Bug#57278: Crash on min/max + with date out of range.

MySQL officially supports DATE values starting from 1000-01-01. This is
enforced for int values, but not for string values, thus one
could easily insert '0001-01-01' value. Int values are checked by
number_to_datetime function and Item_cache_datetime::val_str uses it
to fill MYSQL_TIME struct out of cached int value. This leads to the
scenario where Item_cache_datetime caches a non-null datetime value and when
it tries to convert it from int to string number_to_datetime function
treats the value as out-of-range and returns an error and
Item_cache_datetime::val_str returns NULL for a non-null value. Due to this
inconsistency server crashes.

Now number_to_datetime allows DATE values below 1000-01-01 if the
TIME_FUZZY_DATE flag is set. Better NULL handling for Item_cache_datetime.
Added the Item_cache_datetime::store function to reset str_value_cached flag
when an item is stored.
parent 2a4f50a5
......@@ -318,4 +318,12 @@ f1 date YES NULL
f2 date YES NULL
DROP TABLE t1;
#
#
# Bug#57278: Crash on min/max + with date out of range.
#
set @a=(select min(makedate('111','1'))) ;
select @a;
@a
0111-01-01
#
End of 6.0 tests
......@@ -284,4 +284,12 @@ DROP TABLE t1;
--echo #
--echo #
--echo # Bug#57278: Crash on min/max + with date out of range.
--echo #
set @a=(select min(makedate('111','1'))) ;
select @a;
--echo #
--echo End of 6.0 tests
......@@ -1127,7 +1127,12 @@ longlong number_to_datetime(longlong nr, MYSQL_TIME *time_res,
nr= (nr+19000000L)*1000000L; /* YYMMDD, year: 1970-1999 */
goto ok;
}
if (nr < 10000101L)
/*
Though officially we support DATE values from 1000-01-01 only, one can
easily insert a value like 1-1-1. So, for consistency reasons such dates
are allowed when TIME_FUZZY_DATE is set.
*/
if (nr < 10000101L && !(flags & TIME_FUZZY_DATE))
goto err;
if (nr <= 99991231L)
{
......
......@@ -7493,9 +7493,19 @@ void Item_cache_datetime::store(Item *item, longlong val_arg)
}
void Item_cache_datetime::store(Item *item)
{
Item_cache::store(item);
str_value_cached= FALSE;
}
String *Item_cache_datetime::val_str(String *str)
{
DBUG_ASSERT(fixed == 1);
if ((value_cached || str_value_cached) && null_value)
return NULL;
if (!str_value_cached)
{
/*
......@@ -7509,6 +7519,8 @@ String *Item_cache_datetime::val_str(String *str)
if (value_cached)
{
MYSQL_TIME ltime;
/* Return NULL in case of OOM/conversion error. */
null_value= TRUE;
if (str_value.alloc(MAX_DATE_STRING_REP_LENGTH))
return NULL;
if (cached_field_type == MYSQL_TYPE_TIME)
......@@ -7531,13 +7543,14 @@ String *Item_cache_datetime::val_str(String *str)
{
int was_cut;
longlong res;
res= number_to_datetime(val_int(), &ltime, TIME_FUZZY_DATE, &was_cut);
res= number_to_datetime(int_value, &ltime, TIME_FUZZY_DATE, &was_cut);
if (res == -1)
return NULL;
}
str_value.length(my_TIME_to_str(&ltime,
const_cast<char*>(str_value.ptr())));
str_value_cached= TRUE;
null_value= FALSE;
}
else if (!cache_value())
return NULL;
......@@ -7558,7 +7571,7 @@ my_decimal *Item_cache_datetime::val_decimal(my_decimal *decimal_val)
double Item_cache_datetime::val_real()
{
DBUG_ASSERT(fixed == 1);
if (!value_cached && !cache_value_int())
if ((!value_cached && !cache_value_int()) || null_value)
return 0.0;
return (double) int_value;
}
......@@ -7566,7 +7579,7 @@ double Item_cache_datetime::val_real()
longlong Item_cache_datetime::val_int()
{
DBUG_ASSERT(fixed == 1);
if (!value_cached && !cache_value_int())
if ((!value_cached && !cache_value_int()) || null_value)
return 0;
return int_value;
}
......
......@@ -3451,8 +3451,8 @@ public:
cmp_context= STRING_RESULT;
}
virtual void store(Item *item) { Item_cache::store(item); }
void store(Item *item, longlong val_arg);
void store(Item *item);
double val_real();
longlong val_int();
String* val_str(String *str);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment