mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
MDEV-14032 SEC_TO_TIME executes side effect two times
- Adding a helper class Sec6 to store (neg,seconds,microseconds) - Adding a helper class VSec6 (Sec6 with a flag for "IS NULL") - Wrapping related functions as methods of Sec6; * number_to_datetime() * number_to_time() * my_decimal2seconds() * Item::get_seconds() * A big piece of code in Item_func_sec_to_time::get_date() - Using the new classes in places where second-to-temporal conversion takes place: * Field_timestamp::store(double) * Field_timestamp::store(longlong) * Field_timestamp_with_dec::store_decimal(my_decimal) * Field_temporal_with_date::store(double) * Field_temporal_with_date::store(longlong) * Field_time::store(double) * Field_time::store(longlong) * Field_time::store_decimal(my_decimal) * Field_temporal_with_date::store_decimal(my_decimal) * get_interval_value() * Item_func_sec_to_time::get_date() * Item_func_from_unixtime::get_date() * Item_func_maketime::get_date() This change simplifies these methods and functions a lot. - Warnings are now sent at VSec6 initialization time, when the source data is available in its original data type representation. If Sec6::to_time() or Sec6::to_datetime() truncate data again during conversion to MYSQL_TIME, they send warnings, but only if no warnings were sent during VSec6 initialization. This helps prevents double warnings. The call for val_str() in Item_func_sec_to_time::get_date() is not needed any more, so it's removed. This change actually fixes the problem. As a good effect, FROM_UNIXTIME() and MAKETIME() now also send warnings in case if the seconds arguments is out of range. Previously these functions returned NULL silently. - Splitting the code in the global function make_truncated_value_warning() into a number of methods THD::raise_warning_xxxx(). This was needed to reuse the logic that chooses between: * ER_TRUNCATED_WRONG_VALUE * ER_WRONG_VALUE * ER_TRUNCATED_WRONG_VALUE_FOR_FIELD for non-temporal data types (Sec6). - Removing: * Item::get_seconds() * number_to_time_with_warn() as this code now resides inside methods of Sec6. - Cleanup (changes that are not directly related to the fix): * Removing calls for field_name_or_null() and passing NULL instead in Item_func_hybrid_field_type::get_date_from_{int|real}_op, because Item_func_hybrid_field_type::field_name_or_null() always returns NULL * Replacing a number of calls for make_truncated_value_warning() to calls for THD::raise_warning_xxx(). In these places we know that the execution went through a certain branch of make_truncated_value_warning(), (e.g. the exact error code is known, or field name is always NULL, or field name is always not-NULL). So calls for the entire make_truncated_value_warning() after splitting are not necessary.
This commit is contained in:
138
sql/sql_type.cc
138
sql/sql_type.cc
@ -181,6 +181,144 @@ Temporal_hybrid::Temporal_hybrid(THD *thd, Item *item)
|
||||
}
|
||||
|
||||
|
||||
void Sec6::make_from_decimal(const my_decimal *d)
|
||||
{
|
||||
m_neg= my_decimal2seconds(d, &m_sec, &m_usec);
|
||||
m_truncated= (m_sec >= LONGLONG_MAX);
|
||||
}
|
||||
|
||||
|
||||
void Sec6::make_from_double(double nr)
|
||||
{
|
||||
if ((m_neg= nr < 0))
|
||||
nr= -nr;
|
||||
if ((m_truncated= nr > (double) LONGLONG_MAX))
|
||||
{
|
||||
m_sec= LONGLONG_MAX;
|
||||
m_usec= 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sec= (ulonglong) nr;
|
||||
m_usec= (ulong) ((nr - floor(nr)) * 1000000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Sec6::make_truncated_warning(THD *thd, const char *type_str) const
|
||||
{
|
||||
char buff[1 + MAX_BIGINT_WIDTH + 1 + 6 + 1]; // '-' int '.' frac '\0'
|
||||
to_string(buff, sizeof(buff));
|
||||
current_thd->push_warning_truncated_wrong_value(type_str, buff);
|
||||
}
|
||||
|
||||
|
||||
bool Sec6::to_time_with_warn(MYSQL_TIME *to, const ErrConv *str,
|
||||
const char *field_name) const
|
||||
{
|
||||
int was_cut;
|
||||
bool res= to_time(to, &was_cut);
|
||||
if (res || MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut))
|
||||
current_thd->
|
||||
push_warning_wrong_or_truncated_value(Sql_condition::WARN_LEVEL_WARN,
|
||||
res, "time", str->ptr(),
|
||||
field_name);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
bool Sec6::to_datetime_with_warn(MYSQL_TIME *to, ulonglong fuzzydate,
|
||||
const ErrConv *str,
|
||||
const char *field_name) const
|
||||
{
|
||||
bool res, have_warnings= false;
|
||||
int was_cut;
|
||||
res= to_datetime(to, fuzzydate, &was_cut);
|
||||
have_warnings= was_cut && (fuzzydate & TIME_NO_ZERO_IN_DATE);
|
||||
if (res || have_warnings)
|
||||
current_thd->
|
||||
push_warning_wrong_or_truncated_value(Sql_condition::WARN_LEVEL_WARN,
|
||||
res, "datetime", str->ptr(),
|
||||
field_name);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
bool Sec6::convert_to_mysql_time(MYSQL_TIME *ltime, ulonglong fuzzydate,
|
||||
const ErrConv *str, const char *field_name)
|
||||
const
|
||||
{
|
||||
bool is_time= fuzzydate & TIME_TIME_ONLY;
|
||||
if (truncated())
|
||||
{
|
||||
/*
|
||||
The value was already truncated at the constructor call time,
|
||||
and a truncation warning was issued. Here we convert silently
|
||||
to avoid double warnings.
|
||||
*/
|
||||
current_thd->
|
||||
push_warning_wrong_or_truncated_value(Sql_condition::WARN_LEVEL_WARN,
|
||||
!is_time,
|
||||
is_time ? "time" : "datetime",
|
||||
str->ptr(), field_name);
|
||||
int warn;
|
||||
return is_time ? to_time(ltime, &warn) :
|
||||
to_datetime(ltime, fuzzydate, &warn);
|
||||
}
|
||||
return is_time ? to_time_with_warn(ltime, str, field_name) :
|
||||
to_datetime_with_warn(ltime, fuzzydate, str, field_name);
|
||||
}
|
||||
|
||||
|
||||
VSec6::VSec6(Item *item, const char *type_str, ulonglong limit)
|
||||
{
|
||||
if (item->decimals == 0)
|
||||
{ // optimize for an important special case
|
||||
longlong nr= item->val_int();
|
||||
make_from_int(nr, item->unsigned_flag);
|
||||
m_is_null= item->null_value;
|
||||
if (!m_is_null && m_sec > limit)
|
||||
{
|
||||
m_sec= limit;
|
||||
m_truncated= true;
|
||||
ErrConvInteger err(nr, item->unsigned_flag);
|
||||
current_thd->push_warning_truncated_wrong_value(type_str, err.ptr());
|
||||
}
|
||||
}
|
||||
else if (item->cmp_type() == REAL_RESULT)
|
||||
{
|
||||
double nr= item->val_real();
|
||||
make_from_double(nr);
|
||||
m_is_null= item->null_value;
|
||||
if (!m_is_null && m_sec > limit)
|
||||
{
|
||||
m_sec= limit;
|
||||
m_truncated= true;
|
||||
}
|
||||
if (m_truncated)
|
||||
{
|
||||
ErrConvDouble err(nr);
|
||||
current_thd->push_warning_truncated_wrong_value(type_str, err.ptr());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VDec tmp(item);
|
||||
(m_is_null= tmp.is_null()) ? reset() : make_from_decimal(tmp.ptr());
|
||||
if (!m_is_null && m_sec > limit)
|
||||
{
|
||||
m_sec= limit;
|
||||
m_truncated= true;
|
||||
}
|
||||
if (m_truncated)
|
||||
{
|
||||
ErrConvDecimal err(tmp.ptr());
|
||||
current_thd->push_warning_truncated_wrong_value(type_str, err.ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Time::make_from_item(Item *item, const Options opt)
|
||||
{
|
||||
if (item->get_date(this, opt.get_date_flags()))
|
||||
|
Reference in New Issue
Block a user