diff --git a/mysql-test/main/type_time.result b/mysql-test/main/type_time.result index 7fdeba07ad2..69aa9561ebf 100644 --- a/mysql-test/main/type_time.result +++ b/mysql-test/main/type_time.result @@ -2286,5 +2286,127 @@ SELECT '1972-11-06 16:58:58' < TIME'20:31:05'; '1972-11-06 16:58:58' < TIME'20:31:05' 1 # +# MDEV-23525 Wrong result of MIN(time_expr) and MAX(time_expr) with GROUP BY +# +SET timestamp=UNIX_TIMESTAMP('2020-08-21 18:19:20'); +CREATE PROCEDURE p1() +BEGIN +SELECT MIN(t), MAX(t) FROM t1; +SELECT i, MIN(t), MAX(t) FROM t1 GROUP BY i; +SELECT i, MIN(COALESCE(t)), MAX(COALESCE(t)) FROM t1 GROUP BY i; +SELECT i, MIN(t+INTERVAL 1 SECOND), MAX(t+INTERVAL 1 SECOND) FROM t1 GROUP BY i; +SELECT i, MIN(TIME'10:20:30'+INTERVAL 1 SECOND) FROM t1 GROUP BY i; +SELECT i, MIN(CURRENT_TIME), MAX(CURRENT_TIME) FROM t1 GROUP BY i; +SELECT +i, +MIN((SELECT MAX(CURRENT_TIME) FROM t1)), +MAX((SELECT MAX(CURRENT_TIME) FROM t1)) +FROM t1 GROUP BY i; +SELECT +i, +MIN(NAME_CONST('name',TIME'10:20:30')), +MAX(NAME_CONST('name',TIME'10:20:30')) +FROM t1 GROUP BY i; +EXECUTE IMMEDIATE "SELECT i, MIN(?),MAX(?) FROM t1 GROUP BY i" + USING TIME'10:20:30', TIME'10:20:30'; +END; +$$ +CREATE TABLE t1 (i INT, t TIME); +INSERT INTO t1 VALUES (1,'10:20:30'); +INSERT INTO t1 VALUES (1,'100:20:20'); +CALL p1; +MIN(t) MAX(t) +10:20:30 100:20:20 +i MIN(t) MAX(t) +1 10:20:30 100:20:20 +i MIN(COALESCE(t)) MAX(COALESCE(t)) +1 10:20:30 100:20:20 +i MIN(t+INTERVAL 1 SECOND) MAX(t+INTERVAL 1 SECOND) +1 10:20:31 100:20:21 +i MIN(TIME'10:20:30'+INTERVAL 1 SECOND) +1 10:20:31 +i MIN(CURRENT_TIME) MAX(CURRENT_TIME) +1 18:19:20 18:19:20 +i MIN((SELECT MAX(CURRENT_TIME) FROM t1)) MAX((SELECT MAX(CURRENT_TIME) FROM t1)) +1 18:19:20 18:19:20 +i MIN(NAME_CONST('name',TIME'10:20:30')) MAX(NAME_CONST('name',TIME'10:20:30')) +1 10:20:30 10:20:30 +i MIN(?) MAX(?) +1 10:20:30 10:20:30 +DROP TABLE t1; +CREATE TABLE t1 (i INT, t TIME(3)); +INSERT INTO t1 VALUES (1,'10:20:30.123'); +INSERT INTO t1 VALUES (1,'100:20:20.123'); +CALL p1; +MIN(t) MAX(t) +10:20:30.123 100:20:20.123 +i MIN(t) MAX(t) +1 10:20:30.123 100:20:20.123 +i MIN(COALESCE(t)) MAX(COALESCE(t)) +1 10:20:30.123 100:20:20.123 +i MIN(t+INTERVAL 1 SECOND) MAX(t+INTERVAL 1 SECOND) +1 10:20:31.123 100:20:21.123 +i MIN(TIME'10:20:30'+INTERVAL 1 SECOND) +1 10:20:31 +i MIN(CURRENT_TIME) MAX(CURRENT_TIME) +1 18:19:20 18:19:20 +i MIN((SELECT MAX(CURRENT_TIME) FROM t1)) MAX((SELECT MAX(CURRENT_TIME) FROM t1)) +1 18:19:20 18:19:20 +i MIN(NAME_CONST('name',TIME'10:20:30')) MAX(NAME_CONST('name',TIME'10:20:30')) +1 10:20:30 10:20:30 +i MIN(?) MAX(?) +1 10:20:30 10:20:30 +DROP TABLE t1; +CREATE TABLE t1 (i INT, t TIME(6)); +INSERT INTO t1 VALUES (1,'10:20:30.123456'); +INSERT INTO t1 VALUES (1,'100:20:20.123456'); +CALL p1; +MIN(t) MAX(t) +10:20:30.123456 100:20:20.123456 +i MIN(t) MAX(t) +1 10:20:30.123456 100:20:20.123456 +i MIN(COALESCE(t)) MAX(COALESCE(t)) +1 10:20:30.123456 100:20:20.123456 +i MIN(t+INTERVAL 1 SECOND) MAX(t+INTERVAL 1 SECOND) +1 10:20:31.123456 100:20:21.123456 +i MIN(TIME'10:20:30'+INTERVAL 1 SECOND) +1 10:20:31 +i MIN(CURRENT_TIME) MAX(CURRENT_TIME) +1 18:19:20 18:19:20 +i MIN((SELECT MAX(CURRENT_TIME) FROM t1)) MAX((SELECT MAX(CURRENT_TIME) FROM t1)) +1 18:19:20 18:19:20 +i MIN(NAME_CONST('name',TIME'10:20:30')) MAX(NAME_CONST('name',TIME'10:20:30')) +1 10:20:30 10:20:30 +i MIN(?) MAX(?) +1 10:20:30 10:20:30 +DROP TABLE t1; +SET @@global.mysql56_temporal_format=false; +CREATE TABLE t1 (i INT, t TIME(6)); +INSERT INTO t1 VALUES (1,'10:20:30.123456'); +INSERT INTO t1 VALUES (1,'100:20:20.123456'); +CALL p1; +MIN(t) MAX(t) +10:20:30.123456 100:20:20.123456 +i MIN(t) MAX(t) +1 10:20:30.123456 100:20:20.123456 +i MIN(COALESCE(t)) MAX(COALESCE(t)) +1 10:20:30.123456 100:20:20.123456 +i MIN(t+INTERVAL 1 SECOND) MAX(t+INTERVAL 1 SECOND) +1 10:20:31.123456 100:20:21.123456 +i MIN(TIME'10:20:30'+INTERVAL 1 SECOND) +1 10:20:31 +i MIN(CURRENT_TIME) MAX(CURRENT_TIME) +1 18:19:20 18:19:20 +i MIN((SELECT MAX(CURRENT_TIME) FROM t1)) MAX((SELECT MAX(CURRENT_TIME) FROM t1)) +1 18:19:20 18:19:20 +i MIN(NAME_CONST('name',TIME'10:20:30')) MAX(NAME_CONST('name',TIME'10:20:30')) +1 10:20:30 10:20:30 +i MIN(?) MAX(?) +1 10:20:30 10:20:30 +DROP TABLE t1; +SET @@global.mysql56_temporal_format=default; +DROP PROCEDURE p1; +SET timestamp=DEFAULT; +# # End of 10.4 tests # diff --git a/mysql-test/main/type_time.test b/mysql-test/main/type_time.test index 521953a5078..1d4d157b976 100644 --- a/mysql-test/main/type_time.test +++ b/mysql-test/main/type_time.test @@ -1490,6 +1490,66 @@ DROP TABLE t1; SELECT '1972-11-06 16:58:58' < TIME'20:31:05'; +--echo # +--echo # MDEV-23525 Wrong result of MIN(time_expr) and MAX(time_expr) with GROUP BY +--echo # + +SET timestamp=UNIX_TIMESTAMP('2020-08-21 18:19:20'); + +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + SELECT MIN(t), MAX(t) FROM t1; + SELECT i, MIN(t), MAX(t) FROM t1 GROUP BY i; + SELECT i, MIN(COALESCE(t)), MAX(COALESCE(t)) FROM t1 GROUP BY i; + SELECT i, MIN(t+INTERVAL 1 SECOND), MAX(t+INTERVAL 1 SECOND) FROM t1 GROUP BY i; + SELECT i, MIN(TIME'10:20:30'+INTERVAL 1 SECOND) FROM t1 GROUP BY i; + SELECT i, MIN(CURRENT_TIME), MAX(CURRENT_TIME) FROM t1 GROUP BY i; + SELECT + i, + MIN((SELECT MAX(CURRENT_TIME) FROM t1)), + MAX((SELECT MAX(CURRENT_TIME) FROM t1)) + FROM t1 GROUP BY i; + SELECT + i, + MIN(NAME_CONST('name',TIME'10:20:30')), + MAX(NAME_CONST('name',TIME'10:20:30')) + FROM t1 GROUP BY i; + EXECUTE IMMEDIATE "SELECT i, MIN(?),MAX(?) FROM t1 GROUP BY i" + USING TIME'10:20:30', TIME'10:20:30'; +END; +$$ +DELIMITER ;$$ + +CREATE TABLE t1 (i INT, t TIME); +INSERT INTO t1 VALUES (1,'10:20:30'); +INSERT INTO t1 VALUES (1,'100:20:20'); +CALL p1; +DROP TABLE t1; + +CREATE TABLE t1 (i INT, t TIME(3)); +INSERT INTO t1 VALUES (1,'10:20:30.123'); +INSERT INTO t1 VALUES (1,'100:20:20.123'); +CALL p1; +DROP TABLE t1; + +CREATE TABLE t1 (i INT, t TIME(6)); +INSERT INTO t1 VALUES (1,'10:20:30.123456'); +INSERT INTO t1 VALUES (1,'100:20:20.123456'); +CALL p1; +DROP TABLE t1; + +SET @@global.mysql56_temporal_format=false; +CREATE TABLE t1 (i INT, t TIME(6)); +INSERT INTO t1 VALUES (1,'10:20:30.123456'); +INSERT INTO t1 VALUES (1,'100:20:20.123456'); +CALL p1; +DROP TABLE t1; +SET @@global.mysql56_temporal_format=default; + +DROP PROCEDURE p1; +SET timestamp=DEFAULT; + --echo # --echo # End of 10.4 tests --echo # diff --git a/sql/field.cc b/sql/field.cc index 49ee54883e4..65dc7f768a3 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -6011,6 +6011,24 @@ bool Field_time::get_date(MYSQL_TIME *ltime, date_mode_t fuzzydate) } +int Field_time::store_native(const Native &value) +{ + Time t(value); + DBUG_ASSERT(t.is_valid_time()); + store_TIME(t); + return 0; +} + + +bool Field_time::val_native(Native *to) +{ + MYSQL_TIME ltime; + get_date(<ime, date_mode_t(0)); + int warn; + return Time(&warn, <ime, 0).to_native(to, decimals()); +} + + bool Field_time::send_binary(Protocol *protocol) { MYSQL_TIME ltime; @@ -6254,6 +6272,22 @@ bool Field_timef::get_date(MYSQL_TIME *ltime, date_mode_t fuzzydate) return false; } +int Field_timef::store_native(const Native &value) +{ + DBUG_ASSERT(value.length() == my_time_binary_length(dec)); + DBUG_ASSERT(Time(value).is_valid_time()); + memcpy(ptr, value.ptr(), value.length()); + return 0; +} + + +bool Field_timef::val_native(Native *to) +{ + uint32 binlen= my_time_binary_length(dec); + return to->copy((const char*) ptr, binlen); +} + + /**************************************************************************** ** year type ** Save in a byte the year 0, 1901->2155 diff --git a/sql/field.h b/sql/field.h index 58ff9ea2554..0ee4aca5f69 100644 --- a/sql/field.h +++ b/sql/field.h @@ -3213,6 +3213,8 @@ public: decimals() == from->decimals(); } sql_mode_t conversion_depends_on_sql_mode(THD *, Item *) const; + int store_native(const Native &value); + bool val_native(Native *to); int store_time_dec(const MYSQL_TIME *ltime, uint dec); int store(const char *to,size_t length,CHARSET_INFO *charset); int store(double nr); @@ -3334,6 +3336,8 @@ public: } int reset(); bool get_date(MYSQL_TIME *ltime, date_mode_t fuzzydate); + int store_native(const Native &value); + bool val_native(Native *to); uint size_of() const { return sizeof(*this); } }; diff --git a/sql/item.cc b/sql/item.cc index f8c29c8974c..312effeb6c1 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -2011,6 +2011,11 @@ bool Item_name_const::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydat return rc; } +bool Item_name_const::val_native(THD *thd, Native *to) +{ + return val_native_from_item(thd, value_item, to); +} + bool Item_name_const::is_null() { return value_item->is_null(); diff --git a/sql/item.h b/sql/item.h index 2d5cedbee76..aced2ec5f0d 100644 --- a/sql/item.h +++ b/sql/item.h @@ -1311,16 +1311,13 @@ public: { /* The default implementation for the Items that do not need native format: - - Item_basic_value + - Item_basic_value (default implementation) - Item_copy - Item_exists_subselect - Item_sum_field - Item_sum_or_func (default implementation) - Item_proc - Item_type_holder (as val_xxx() are never called for it); - - TODO: Item_name_const will need val_native() in the future, - when we add this syntax: - TIMESTAMP WITH LOCAL TIMEZONE'2001-01-01 00:00:00' These hybrid Item types override val_native(): - Item_field @@ -1331,6 +1328,8 @@ public: - Item_direct_ref - Item_direct_view_ref - Item_ref_null_helper + - Item_name_const + - Item_time_literal - Item_sum_or_func Note, these hybrid type Item_sum_or_func descendants override the default implementation: @@ -3139,6 +3138,7 @@ public: String *val_str(String *sp); my_decimal *val_decimal(my_decimal *); bool get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate); + bool val_native(THD *thd, Native *to); bool is_null(); virtual void print(String *str, enum_query_type query_type); @@ -4897,6 +4897,10 @@ public: String *val_str(String *to) { return Time(this).to_string(to, decimals); } my_decimal *val_decimal(my_decimal *to) { return Time(this).to_decimal(to); } bool get_date(THD *thd, MYSQL_TIME *res, date_mode_t fuzzydate); + bool val_native(THD *thd, Native *to) + { + return Time(thd, this).to_native(to, decimals); + } Item *get_copy(THD *thd) { return get_item_copy(thd, this); } }; @@ -6872,6 +6876,10 @@ public: { return has_value() ? Time(this).to_decimal(to) : NULL; } + bool val_native(THD *thd, Native *to) + { + return has_value() ? Time(thd, this).to_native(to, decimals) : true; + } }; diff --git a/sql/item_func.h b/sql/item_func.h index d1292c3e07a..b368e5872fe 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -497,6 +497,12 @@ public: virtual longlong val_int(Item_handled_func *) const= 0; virtual my_decimal *val_decimal(Item_handled_func *, my_decimal *) const= 0; virtual bool get_date(THD *thd, Item_handled_func *, MYSQL_TIME *, date_mode_t fuzzydate) const= 0; + virtual bool val_native(THD *thd, Item_handled_func *, Native *to) const + { + DBUG_ASSERT(0); + to->length(0); + return true; + } virtual const Type_handler *return_type_handler() const= 0; virtual bool fix_length_and_dec(Item_handled_func *) const= 0; }; @@ -600,6 +606,10 @@ public: { return Time(item).to_string(to, item->decimals); } + bool val_native(THD *thd, Item_handled_func *item, Native *to) const + { + return Time(thd, item).to_native(to, item->decimals); + } }; @@ -668,6 +678,10 @@ public: { return m_func_handler->get_date(thd, this, to, fuzzydate); } + bool val_native(THD *thd, Native *to) + { + return m_func_handler->val_native(thd, this, to); + } }; diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index b3ecd1dfd4d..9917811142c 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -598,6 +598,10 @@ public: double val_real() { return Time(this).to_double(); } String *val_str(String *to) { return Time(this).to_string(to, decimals); } my_decimal *val_decimal(my_decimal *to) { return Time(this).to_decimal(to); } + bool val_native(THD *thd, Native *to) + { + return Time(thd, this).to_native(to, decimals); + } }; diff --git a/sql/sql_type.cc b/sql/sql_type.cc index 6763de17085..85052a1c1bc 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -628,6 +628,23 @@ uint Interval_DDhhmmssff::fsp(THD *thd, Item *item) } +bool Time::to_native(Native *to, uint decimals) const +{ + if (!is_valid_time()) + { + to->length(0); + return true; + } + uint len= my_time_binary_length(decimals); + if (to->reserve(len)) + return true; + longlong tmp= TIME_to_longlong_time_packed(get_mysql_time()); + my_time_packed_to_binary(tmp, (uchar*) to->ptr(), decimals); + to->length(len); + return false; +} + + void Time::make_from_item(THD *thd, int *warn, Item *item, const Options opt) { *warn= 0; @@ -812,6 +829,28 @@ void Time::make_from_time(int *warn, const MYSQL_TIME *from) } +uint Time::binary_length_to_precision(uint length) +{ + switch (length) { + case 3: return 0; + case 4: return 2; + case 5: return 4; + case 6: return 6; + } + DBUG_ASSERT(0); + return 0; +} + + +Time::Time(const Native &native) +{ + uint dec= binary_length_to_precision(native.length()); + longlong tmp= my_time_packed_from_binary((const uchar *) native.ptr(), dec); + TIME_from_longlong_time_packed(this, tmp); + DBUG_ASSERT(is_valid_time()); +} + + Time::Time(int *warn, const MYSQL_TIME *from, long curdays) { switch (from->time_type) { @@ -1456,6 +1495,13 @@ Type_handler_timestamp_common::type_handler_for_native_format() const } +const Type_handler * +Type_handler_time_common::type_handler_for_native_format() const +{ + return &type_handler_time2; +} + + /***************************************************************************/ const Type_handler *Type_handler_typelib::type_handler_for_item_field() const @@ -8391,6 +8437,51 @@ Type_handler_time_common::create_literal_item(THD *thd, } +bool +Type_handler_time_common::Item_val_native_with_conversion(THD *thd, + Item *item, + Native *to) const +{ + if (item->type_handler()->type_handler_for_native_format() == + &type_handler_time2) + return item->val_native(thd, to); + return Time(thd, item).to_native(to, item->time_precision(thd)); +} + + +bool +Type_handler_time_common::Item_val_native_with_conversion_result(THD *thd, + Item *item, + Native *to) + const +{ + if (item->type_handler()->type_handler_for_native_format() == + &type_handler_time2) + return item->val_native_result(thd, to); + MYSQL_TIME ltime; + if (item->get_date_result(thd, <ime, Time::Options(thd))) + return true; + int warn; + return Time(&warn, <ime, 0).to_native(to, item->time_precision(thd)); +} + + +int Type_handler_time_common::cmp_native(const Native &a, + const Native &b) const +{ + // Optimize a simple case: equal fractional precision: + if (a.length() == b.length()) + return memcmp(a.ptr(), b.ptr(), a.length()); + longlong lla= Time(a).to_packed(); + longlong llb= Time(b).to_packed(); + if (lla < llb) + return -1; + if (lla> llb) + return 1; + return 0; +} + + bool Type_handler_timestamp_common::TIME_to_native(THD *thd, const MYSQL_TIME *ltime, Native *to, @@ -8501,6 +8592,15 @@ Type_handler_timestamp_common::Item_param_val_native(THD *thd, } +bool +Type_handler_time_common::Item_param_val_native(THD *thd, + Item_param *item, + Native *to) const +{ + return Time(thd, item).to_native(to, item->decimals); +} + + LEX_CSTRING Charset::collation_specific_name() const { /* diff --git a/sql/sql_type.h b/sql/sql_type.h index b201ca46861..553d204d806 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -1308,6 +1308,7 @@ class Schema; */ class Time: public Temporal { + static uint binary_length_to_precision(uint length); public: enum datetime_to_time_mode_t { @@ -1540,6 +1541,7 @@ public: */ Time(int *warn, bool neg, ulonglong hour, uint minute, const Sec6 &second); Time() { time_type= MYSQL_TIMESTAMP_NONE; } + Time(const Native &native); Time(Item *item) :Time(current_thd, item) { } @@ -1695,6 +1697,7 @@ public: return !is_valid_time() ? 0 : Temporal::to_double(neg, TIME_to_ulonglong_time(this), second_part); } + bool to_native(Native *to, uint decimals) const; String *to_string(String *str, uint dec) const { if (!is_valid_time()) @@ -5324,6 +5327,12 @@ public: { return MYSQL_TIMESTAMP_TIME; } + bool is_val_native_ready() const { return true; } + const Type_handler *type_handler_for_native_format() const; + int cmp_native(const Native &a, const Native &b) const; + bool Item_val_native_with_conversion(THD *thd, Item *, Native *to) const; + bool Item_val_native_with_conversion_result(THD *thd, Item *, Native *to) const; + bool Item_param_val_native(THD *thd, Item_param *item, Native *to) const; Item_literal *create_literal_item(THD *thd, const char *str, size_t length, CHARSET_INFO *cs, bool send_error) const; Item *create_typecast_item(THD *thd, Item *item,