1
0
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:
Alexander Barkov
2018-08-09 06:31:05 +04:00
parent a12e6c5ba4
commit 8524bb6872
15 changed files with 475 additions and 292 deletions

View File

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