mirror of
https://github.com/MariaDB/server.git
synced 2025-07-27 18:02:13 +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:
120
sql/sql_type.h
120
sql/sql_type.h
@ -210,6 +210,126 @@ public:
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Class Sec6 represents a fixed point value with 6 fractional digits.
|
||||
Used e.g. to convert double and my_decimal values to TIME/DATETIME.
|
||||
*/
|
||||
|
||||
class Sec6
|
||||
{
|
||||
protected:
|
||||
ulonglong m_sec; // The integer part, between 0 and LONGLONG_MAX
|
||||
ulong m_usec; // The fractional part, between 0 and 999999
|
||||
bool m_neg; // false if positive, true of negative
|
||||
bool m_truncated; // Indicates if the constructor truncated the value
|
||||
void make_from_decimal(const my_decimal *d);
|
||||
void make_from_double(double d);
|
||||
void make_from_int(longlong nr, bool unsigned_val)
|
||||
{
|
||||
m_neg= nr < 0 && !unsigned_val;
|
||||
m_sec= m_neg ? (ulonglong) -nr : (ulonglong) nr;
|
||||
m_usec= 0;
|
||||
m_truncated= false;
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
m_sec= m_usec= m_neg= m_truncated= 0;
|
||||
}
|
||||
Sec6() { }
|
||||
public:
|
||||
Sec6(bool neg, ulonglong nr, ulong frac)
|
||||
:m_sec(nr), m_usec(frac), m_neg(neg), m_truncated(false)
|
||||
{ }
|
||||
Sec6(double nr)
|
||||
{
|
||||
make_from_double(nr);
|
||||
}
|
||||
Sec6(const my_decimal *d)
|
||||
{
|
||||
make_from_decimal(d);
|
||||
}
|
||||
Sec6(longlong nr, bool unsigned_val)
|
||||
{
|
||||
make_from_int(nr, unsigned_val);
|
||||
}
|
||||
bool neg() const { return m_neg; }
|
||||
bool truncated() const { return m_truncated; }
|
||||
ulonglong sec() const { return m_sec; }
|
||||
long usec() const { return m_usec; }
|
||||
/**
|
||||
Converts Sec6 to MYSQL_TIME
|
||||
|
||||
@param ltime converted value will be written here
|
||||
@param fuzzydate conversion flags (TIME_INVALID_DATE, etc)
|
||||
@param str original number, as an ErrConv. For the warning
|
||||
@param field_name field name or NULL if not a field. For the warning
|
||||
@returns false for success, true for a failure
|
||||
*/
|
||||
bool convert_to_mysql_time(MYSQL_TIME *ltime, ulonglong fuzzydate,
|
||||
const ErrConv *str, const char *field_name) const;
|
||||
|
||||
// Convert a number in format hhhmmss.ff to TIME'hhh:mm:ss.ff'
|
||||
bool to_time(MYSQL_TIME *to, int *warn) const
|
||||
{
|
||||
return number_to_time(m_neg, m_sec, m_usec, to, warn);
|
||||
}
|
||||
bool to_time_with_warn(MYSQL_TIME *to, const ErrConv *str,
|
||||
const char *field_name) const;
|
||||
/*
|
||||
Convert a number in format YYYYMMDDhhmmss.ff to
|
||||
TIMESTAMP'YYYY-MM-DD hh:mm:ss.ff'
|
||||
*/
|
||||
bool to_datetime(MYSQL_TIME *to, ulonglong flags, int *warn) const
|
||||
{
|
||||
if (m_neg)
|
||||
{
|
||||
*warn= MYSQL_TIME_WARN_OUT_OF_RANGE;
|
||||
return true;
|
||||
}
|
||||
return number_to_datetime(m_sec, m_usec, to, flags, warn) == -1;
|
||||
}
|
||||
bool to_datetime_with_warn(MYSQL_TIME *to, ulonglong fuzzydate,
|
||||
const ErrConv *str, const char *field_name) const;
|
||||
// Convert elapsed seconds to TIME
|
||||
bool sec_to_time(MYSQL_TIME *ltime, uint dec) const
|
||||
{
|
||||
set_zero_time(ltime, MYSQL_TIMESTAMP_TIME);
|
||||
ltime->neg= m_neg;
|
||||
if (m_sec > TIME_MAX_VALUE_SECONDS)
|
||||
{
|
||||
// use check_time_range() to set ltime to the max value depending on dec
|
||||
int unused;
|
||||
ltime->hour= TIME_MAX_HOUR + 1;
|
||||
check_time_range(ltime, dec, &unused);
|
||||
return true;
|
||||
}
|
||||
DBUG_ASSERT(usec() <= TIME_MAX_SECOND_PART);
|
||||
ltime->hour= (uint) (m_sec / 3600);
|
||||
ltime->minute= (uint) (m_sec % 3600) / 60;
|
||||
ltime->second= (uint) m_sec % 60;
|
||||
ltime->second_part= m_usec;
|
||||
return false;
|
||||
}
|
||||
size_t to_string(char *to, size_t nbytes) const
|
||||
{
|
||||
return m_usec ?
|
||||
my_snprintf(to, nbytes, "%s%llu.%06lu",
|
||||
m_neg ? "-" : "", m_sec, (uint) m_usec) :
|
||||
my_snprintf(to, nbytes, "%s%llu", m_neg ? "-" : "", m_sec);
|
||||
}
|
||||
void make_truncated_warning(THD *thd, const char *type_str) const;
|
||||
};
|
||||
|
||||
|
||||
class VSec6: public Sec6
|
||||
{
|
||||
bool m_is_null;
|
||||
public:
|
||||
VSec6(Item *item, const char *type_str, ulonglong limit);
|
||||
bool is_null() const { return m_is_null; }
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
A heler class to perform additive operations between
|
||||
two MYSQL_TIME structures and return the result as a
|
||||
|
Reference in New Issue
Block a user