1
0
mirror of https://github.com/MariaDB/server.git synced 2025-08-01 03:47:19 +03:00

A cleanup for MDEV-15340 + fix MDEV-15363 Wrong result for CAST(LAST_DAY(TIME'00:00:00') AS TIME)

The change N7 in MDEV-15340 (see the commit message) introduced
a regression in how CAST(AS TIME), HOUR(), TIME_TO_SEC() treat datetimes
'0000-00-DD mm:hh:ss' (i.e. with zero YYYYMM part and a non-zero day).
These functions historically do not mix days to hours on datetime-to-time
conversion. Implementations of the underlying methods used get_arg0_time()
to fetch MYSQL_TIME. After MDEV-15340, get_arg0_time() went through the
Time() constructor, which always adds '0000-00-DD' to hours automatically
(as in all other places in the code we do mix days to hours).

Changes:
1. Extending Time() to make it possible to choose a desired way of treating
   '0000-00-DD' (ignore or mix to hours) on datetime-to-time conversion.
   Adding a helper class Time::Options for this, which now describes two aspects
   of Time() creation:
   1. Flags for get_date()
   2. Days/hours mixing behavior.

2. Removing Item_func::get_arg0_time(). Using Time() directly
   in all affected classes. Forcing Time() to ignore (rather than mix)
   '0000-00-DD' in these affected classes by passing a suitable Options value.

3. Adding Time::to_seconds(), to reuse the code between
   Item_func_time_to_sec::decimal_op() and Item_func_time_to_sec::int_op().

4. Item_func::get_arg0_date() now returns only a datetime value,
   with automatic time-to-datetime conversion if needed. An assert was
   added to catch attempts to pass TIME_TIME_ONLY to get_arg0_date().
   All callers were checked not to pass TIME_TIME_ONLY, this revealed
   a bug MDEV-15363.

5. Changing Item_func_last_day::get_date() to remove the TIME_TIME_ONLY flag
   before calling get_arg0_date(). This fixes MDEV-15363.
This commit is contained in:
Alexander Barkov
2018-02-21 08:18:44 +04:00
parent aef530bb69
commit 5417002dae
6 changed files with 127 additions and 51 deletions

View File

@ -94,6 +94,47 @@ struct SORT_FIELD_ATTR;
*/
class Time: private MYSQL_TIME
{
public:
enum datetime_to_time_mode_t
{
DATETIME_TO_TIME_YYYYMMDD_000000DD_MIX_TO_HOURS,
DATETIME_TO_TIME_YYYYMMDD_TRUNCATE
};
class Options
{
sql_mode_t m_get_date_flags;
datetime_to_time_mode_t m_datetime_to_time_mode;
public:
Options()
:m_get_date_flags(flags_for_get_date()),
m_datetime_to_time_mode(DATETIME_TO_TIME_YYYYMMDD_000000DD_MIX_TO_HOURS)
{ }
Options(sql_mode_t flags)
:m_get_date_flags(flags),
m_datetime_to_time_mode(DATETIME_TO_TIME_YYYYMMDD_000000DD_MIX_TO_HOURS)
{ }
Options(sql_mode_t flags, datetime_to_time_mode_t dtmode)
:m_get_date_flags(flags),
m_datetime_to_time_mode(dtmode)
{ }
sql_mode_t get_date_flags() const
{ return m_get_date_flags; }
datetime_to_time_mode_t datetime_to_time_mode() const
{ return m_datetime_to_time_mode; }
};
/*
CAST(AS TIME) historically does not mix days to hours.
This is different comparing to how implicit conversion
in Field::store_time_dec() works (e.g. on INSERT).
*/
class Options_for_cast: public Options
{
public:
Options_for_cast()
:Options(flags_for_get_date(), DATETIME_TO_TIME_YYYYMMDD_TRUNCATE)
{ }
};
private:
bool is_valid_value_slow() const
{
return time_type == MYSQL_TIMESTAMP_NONE || is_valid_time_slow();
@ -113,7 +154,7 @@ class Time: private MYSQL_TIME
e.g. returned from Item::get_date().
After this call, "this" is a valid TIME value.
*/
void valid_datetime_to_valid_time()
void valid_datetime_to_valid_time(const Options opt)
{
DBUG_ASSERT(time_type == MYSQL_TIMESTAMP_DATE ||
time_type == MYSQL_TIMESTAMP_DATETIME);
@ -123,7 +164,9 @@ class Time: private MYSQL_TIME
*/
DBUG_ASSERT(day < 32);
DBUG_ASSERT(hour < 24);
if (year == 0 && month == 0)
if (year == 0 && month == 0 &&
opt.datetime_to_time_mode() ==
DATETIME_TO_TIME_YYYYMMDD_000000DD_MIX_TO_HOURS)
{
/*
The maximum hour value after mixing days will be 31*24+23=767,
@ -148,12 +191,12 @@ class Time: private MYSQL_TIME
- either a valid TIME (within the supported TIME range),
- or MYSQL_TIMESTAMP_NONE
*/
void valid_MYSQL_TIME_to_valid_value()
void valid_MYSQL_TIME_to_valid_value(const Options opt)
{
switch (time_type) {
case MYSQL_TIMESTAMP_DATE:
case MYSQL_TIMESTAMP_DATETIME:
valid_datetime_to_valid_time();
valid_datetime_to_valid_time(opt);
break;
case MYSQL_TIMESTAMP_NONE:
break;
@ -165,11 +208,11 @@ class Time: private MYSQL_TIME
break;
}
}
void make_from_item(class Item *item, sql_mode_t flags);
void make_from_item(class Item *item, const Options opt);
public:
Time() { time_type= MYSQL_TIMESTAMP_NONE; }
Time(Item *item) { make_from_item(item, flags_for_get_date()); }
Time(Item *item, sql_mode_t flags) { make_from_item(item, flags); }
Time(Item *item) { make_from_item(item, Options()); }
Time(Item *item, const Options opt) { make_from_item(item, opt); }
static sql_mode_t flags_for_get_date()
{ return TIME_TIME_ONLY | TIME_INVALID_DATES; }
static sql_mode_t comparison_flags_for_get_date()
@ -207,6 +250,15 @@ public:
return 1;
return 0;
}
longlong to_seconds_abs() const
{
DBUG_ASSERT(is_valid_time_slow());
return hour * 3600L + minute * 60 + second;
}
longlong to_seconds() const
{
return neg ? -to_seconds_abs() : to_seconds_abs();
}
};