Commit 4087683e authored by Alexander Barkov's avatar Alexander Barkov

MDEV-15287 Bad result for LEAST/GREATEST(datetime_alike_string, time)

parent da99e086
......@@ -1412,3 +1412,78 @@ d t0 t1
DROP TABLE t1;
DROP TABLE t0;
SET sql_mode=DEFAULT;
#
# MDEV-15287 Bad result for LEAST/GREATEST(datetime_alike_string, time)
#
SELECT
GREATEST('2010-01-01 10:10:10',TIME('-20:20:20')) AS gt_minus20_implicit,
GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-20:20:20')) AS gt_minis20_explicit,
GREATEST('2010-01-01 10:10:10',TIME('20:20:20')) AS gt_plus20_implicit,
GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('20:20:20')) AS gt_plus20_explicit;
gt_minus20_implicit 10:10:10.000000
gt_minis20_explicit 10:10:10.000000
gt_plus20_implicit 20:20:20.000000
gt_plus20_explicit 20:20:20.000000
SELECT
HOUR(GREATEST('2010-01-01 10:10:10',TIME('-20:20:20'))) AS gt_minus20_implicit,
HOUR(GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-20:20:20'))) AS gt_minis20_explicit,
HOUR(GREATEST('2010-01-01 10:10:10',TIME('20:20:20'))) AS gt_plus20_implicit,
HOUR(GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('20:20:20'))) AS gt_plus20_explicit;
gt_minus20_implicit 10
gt_minis20_explicit 10
gt_plus20_implicit 20
gt_plus20_explicit 20
SELECT
LEAST('2010-01-01 10:10:10',TIME('-20:20:20')) AS lt_minus20_implicit,
LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-20:20:20')) AS lt_minus20_explicit,
LEAST('2010-01-01 10:10:10',TIME('20:20:20')) AS lt_plus20_implicit,
LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('20:20:20')) AS lt_plus20_explicit;
lt_minus20_implicit -20:20:20.000000
lt_minus20_explicit -20:20:20.000000
lt_plus20_implicit 10:10:10.000000
lt_plus20_explicit 10:10:10.000000
SELECT
HOUR(LEAST('2010-01-01 10:10:10',TIME('-20:20:20'))) AS lt_minus20_implicit,
HOUR(LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-20:20:20'))) AS lt_minus20_explicit,
HOUR(LEAST('2010-01-01 10:10:10',TIME('20:20:20'))) AS lt_plus20_implicit,
HOUR(LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('20:20:20'))) AS lt_plus20_explicit;
lt_minus20_implicit 20
lt_minus20_explicit 20
lt_plus20_implicit 10
lt_plus20_explicit 10
SELECT
GREATEST('2010-01-01 10:10:10',TIME('-200:20:20')) AS gt_minus200_implicit,
GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-200:20:20')) AS gt_minus200_explictit,
GREATEST('2010-01-01 10:10:10',TIME('200:20:20')) AS gt_plus200_implicit,
GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('200:20:20')) AS gt_plus200_explicit;
gt_minus200_implicit 10:10:10.000000
gt_minus200_explictit 10:10:10.000000
gt_plus200_implicit 200:20:20.000000
gt_plus200_explicit 200:20:20.000000
SELECT
HOUR(GREATEST('2010-01-01 10:10:10',TIME('-200:20:20'))) AS gt_minus200_implicit,
HOUR(GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-200:20:20'))) AS gt_minus200_explictit,
HOUR(GREATEST('2010-01-01 10:10:10',TIME('200:20:20'))) AS gt_plus200_implicit,
HOUR(GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('200:20:20'))) AS gt_plus200_explicit;
gt_minus200_implicit 10
gt_minus200_explictit 10
gt_plus200_implicit 200
gt_plus200_explicit 200
SELECT
LEAST('2010-01-01 10:10:10',TIME('-200:20:20')) AS lt_minus200_implicit,
LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-200:20:20')) AS lt_minus200_explictit,
LEAST('2010-01-01 10:10:10',TIME('200:20:20')) AS lt_plus200_implicit,
LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('200:20:20')) AS lt_plus200_explicit;
lt_minus200_implicit -200:20:20.000000
lt_minus200_explictit -200:20:20.000000
lt_plus200_implicit 10:10:10.000000
lt_plus200_explicit 10:10:10.000000
SELECT
HOUR(LEAST('2010-01-01 10:10:10',TIME('-200:20:20'))) AS lt_minus200_implicit,
HOUR(LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-200:20:20'))) AS lt_minus200_explictit,
HOUR(LEAST('2010-01-01 10:10:10',TIME('200:20:20'))) AS lt_plus200_implicit,
HOUR(LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('200:20:20'))) AS lt_plus200_explicit;
lt_minus200_implicit 200
lt_minus200_explictit 200
lt_plus200_implicit 10
lt_plus200_explicit 10
......@@ -814,3 +814,55 @@ DROP TABLE t1;
DROP TABLE t0;
SET sql_mode=DEFAULT;
--echo #
--echo # MDEV-15287 Bad result for LEAST/GREATEST(datetime_alike_string, time)
--echo #
--vertical_results
SELECT
GREATEST('2010-01-01 10:10:10',TIME('-20:20:20')) AS gt_minus20_implicit,
GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-20:20:20')) AS gt_minis20_explicit,
GREATEST('2010-01-01 10:10:10',TIME('20:20:20')) AS gt_plus20_implicit,
GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('20:20:20')) AS gt_plus20_explicit;
SELECT
HOUR(GREATEST('2010-01-01 10:10:10',TIME('-20:20:20'))) AS gt_minus20_implicit,
HOUR(GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-20:20:20'))) AS gt_minis20_explicit,
HOUR(GREATEST('2010-01-01 10:10:10',TIME('20:20:20'))) AS gt_plus20_implicit,
HOUR(GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('20:20:20'))) AS gt_plus20_explicit;
SELECT
LEAST('2010-01-01 10:10:10',TIME('-20:20:20')) AS lt_minus20_implicit,
LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-20:20:20')) AS lt_minus20_explicit,
LEAST('2010-01-01 10:10:10',TIME('20:20:20')) AS lt_plus20_implicit,
LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('20:20:20')) AS lt_plus20_explicit;
SELECT
HOUR(LEAST('2010-01-01 10:10:10',TIME('-20:20:20'))) AS lt_minus20_implicit,
HOUR(LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-20:20:20'))) AS lt_minus20_explicit,
HOUR(LEAST('2010-01-01 10:10:10',TIME('20:20:20'))) AS lt_plus20_implicit,
HOUR(LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('20:20:20'))) AS lt_plus20_explicit;
SELECT
GREATEST('2010-01-01 10:10:10',TIME('-200:20:20')) AS gt_minus200_implicit,
GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-200:20:20')) AS gt_minus200_explictit,
GREATEST('2010-01-01 10:10:10',TIME('200:20:20')) AS gt_plus200_implicit,
GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('200:20:20')) AS gt_plus200_explicit;
SELECT
HOUR(GREATEST('2010-01-01 10:10:10',TIME('-200:20:20'))) AS gt_minus200_implicit,
HOUR(GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-200:20:20'))) AS gt_minus200_explictit,
HOUR(GREATEST('2010-01-01 10:10:10',TIME('200:20:20'))) AS gt_plus200_implicit,
HOUR(GREATEST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('200:20:20'))) AS gt_plus200_explicit;
SELECT
LEAST('2010-01-01 10:10:10',TIME('-200:20:20')) AS lt_minus200_implicit,
LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-200:20:20')) AS lt_minus200_explictit,
LEAST('2010-01-01 10:10:10',TIME('200:20:20')) AS lt_plus200_implicit,
LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('200:20:20')) AS lt_plus200_explicit;
SELECT
HOUR(LEAST('2010-01-01 10:10:10',TIME('-200:20:20'))) AS lt_minus200_implicit,
HOUR(LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('-200:20:20'))) AS lt_minus200_explictit,
HOUR(LEAST('2010-01-01 10:10:10',TIME('200:20:20'))) AS lt_plus200_implicit,
HOUR(LEAST(CAST('2010-01-01 10:10:10' AS TIME(6)),TIME('200:20:20'))) AS lt_plus200_explicit;
--horizontal_results
......@@ -2739,6 +2739,29 @@ bool Item_func_min_max::get_date_native(MYSQL_TIME *ltime, ulonglong fuzzy_date)
}
bool Item_func_min_max::get_time_native(MYSQL_TIME *ltime)
{
DBUG_ASSERT(fixed == 1);
Time value(args[0]);
if (!value.is_valid_time())
return (null_value= true);
for (uint i= 1; i < arg_count ; i++)
{
Time tmp(args[i]);
if (!tmp.is_valid_time())
return (null_value= true);
int cmp= value.cmp(&tmp);
if ((cmp_sign < 0 ? cmp : -cmp) < 0)
value= tmp;
}
value.copy_to_mysql_time(ltime);
return (null_value= 0);
}
String *Item_func_min_max::val_str_native(String *str)
{
String *UNINIT_VAR(res);
......
......@@ -1496,6 +1496,7 @@ class Item_func_min_max :public Item_hybrid_func
longlong val_int_native();
my_decimal *val_decimal_native(my_decimal *);
bool get_date_native(MYSQL_TIME *res, ulonglong fuzzydate);
bool get_time_native(MYSQL_TIME *res);
double val_real()
{
......
......@@ -121,6 +121,15 @@ bool Type_handler_data::init()
Type_handler_data *type_handler_data= NULL;
void Time::make_from_item(Item *item)
{
if (item->get_time(this))
time_type= MYSQL_TIMESTAMP_NONE;
else
valid_MYSQL_TIME_to_valid_value();
}
void Type_std_attributes::set(const Field *field)
{
decimals= field->decimals();
......@@ -3902,6 +3911,13 @@ bool Type_handler_temporal_result::
return func->get_date_native(ltime, fuzzydate);
}
bool Type_handler_time_common::
Item_func_min_max_get_date(Item_func_min_max *func,
MYSQL_TIME *ltime, ulonglong fuzzydate) const
{
return func->get_time_native(ltime);
}
/***************************************************************************/
/**
......
......@@ -74,6 +74,126 @@ struct TABLE;
struct SORT_FIELD_ATTR;
/**
Class Time is designed to store valid TIME values.
1. Valid value:
a. MYSQL_TIMESTAMP_TIME - a valid TIME within the supported TIME range
b. MYSQL_TIMESTAMP_NONE - an undefined value
2. Invalid value (internally only):
a. MYSQL_TIMESTAMP_TIME outside of the supported TIME range
a. MYSQL_TIMESTAMP_{DATE|DATETIME|ERROR}
Temporarily Time is allowed to have an invalid value, but only internally,
during initialization time. All constructors and modification methods must
leave the Time value as described above (see "Valid values").
Time derives from MYSQL_TIME privately to make sure it is accessed
externally only in the valid state.
*/
class Time: private MYSQL_TIME
{
bool is_valid_value_slow() const
{
return time_type == MYSQL_TIMESTAMP_NONE || is_valid_time_slow();
}
bool is_valid_time_slow() const
{
return time_type == MYSQL_TIMESTAMP_TIME &&
year == 0 && month == 0 && day == 0 &&
minute <= TIME_MAX_MINUTE &&
second <= TIME_MAX_SECOND &&
second_part <= TIME_MAX_SECOND_PART;
}
/*
Convert a valid DATE or DATETIME to TIME.
Before this call, "this" must be a valid DATE or DATETIME value,
e.g. returned from Item::get_date().
After this call, "this" is a valid TIME value.
*/
void valid_datetime_to_valid_time()
{
DBUG_ASSERT(time_type == MYSQL_TIMESTAMP_DATE ||
time_type == MYSQL_TIMESTAMP_DATETIME);
/*
Make sure that day and hour are valid, so the result hour value
after mixing days to hours does not go out of the valid TIME range.
*/
DBUG_ASSERT(day < 32);
DBUG_ASSERT(hour < 24);
if (year == 0 && month == 0)
{
/*
The maximum hour value after mixing days will be 31*24+23=767,
which is within the supported TIME range.
Thus no adjust_time_range_or_invalidate() is needed here.
*/
hour+= day * 24;
}
year= month= day= 0;
time_type= MYSQL_TIMESTAMP_TIME;
DBUG_ASSERT(is_valid_time_slow());
}
/**
Convert valid DATE/DATETIME to valid TIME if needed.
This method is called after Item::get_date(),
which can return only valid TIME/DATE/DATETIME values.
Before this call, "this" is:
- either a valid TIME/DATE/DATETIME value
(within the supported range for the corresponding type),
- or MYSQL_TIMESTAMP_NONE
After this call, "this" is:
- either a valid TIME (within the supported TIME range),
- or MYSQL_TIMESTAMP_NONE
*/
void valid_MYSQL_TIME_to_valid_value()
{
switch (time_type) {
case MYSQL_TIMESTAMP_DATE:
case MYSQL_TIMESTAMP_DATETIME:
valid_datetime_to_valid_time();
break;
case MYSQL_TIMESTAMP_NONE:
break;
case MYSQL_TIMESTAMP_ERROR:
set_zero_time(this, MYSQL_TIMESTAMP_TIME);
break;
case MYSQL_TIMESTAMP_TIME:
DBUG_ASSERT(is_valid_time_slow());
break;
}
}
void make_from_item(class Item *item);
public:
Time() { time_type= MYSQL_TIMESTAMP_NONE; }
Time(Item *item) { make_from_item(item); }
bool is_valid_time() const
{
DBUG_ASSERT(is_valid_value_slow());
return time_type == MYSQL_TIMESTAMP_TIME;
}
void copy_to_mysql_time(MYSQL_TIME *ltime) const
{
DBUG_ASSERT(is_valid_time_slow());
*ltime= *this;
}
int cmp(const Time *other) const
{
DBUG_ASSERT(is_valid_time_slow());
DBUG_ASSERT(other->is_valid_time_slow());
longlong p0= pack_time(this);
longlong p1= pack_time(other);
if (p0 < p1)
return -1;
if (p0 > p1)
return 1;
return 0;
}
};
/*
Flags for collation aggregation modes, used in TDCollation::agg():
......@@ -2086,6 +2206,8 @@ class Type_handler_time_common: public Type_handler_temporal_result
Type_handler_hybrid_field_type *,
Type_all_attributes *atrr,
Item **items, uint nitems) const;
bool Item_func_min_max_get_date(Item_func_min_max*,
MYSQL_TIME *, ulonglong fuzzydate) const;
Item *make_const_item_for_comparison(THD *, Item *src, const Item *cmp) const;
bool set_comparator_func(Arg_comparator *cmp) const;
cmp_item *make_cmp_item(THD *thd, CHARSET_INFO *cs) const;
......
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