mirror of
https://github.com/MariaDB/server.git
synced 2025-08-08 11:22:35 +03:00
MDEV-15287 Bad result for LEAST/GREATEST(datetime_alike_string, time)
This commit is contained in:
@@ -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 @@ public:
|
||||
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);
|
||||
}
|
||||
|
||||
/***************************************************************************/
|
||||
|
||||
/**
|
||||
|
122
sql/sql_type.h
122
sql/sql_type.h
@@ -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 @@ public:
|
||||
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;
|
||||
|
Reference in New Issue
Block a user