From 8524bb68725f6c1bfc96ff4b913f14c6544f2f72 Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Thu, 9 Aug 2018 06:31:05 +0400 Subject: [PATCH] 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. --- mysql-test/main/ctype_ucs.result | 2 +- mysql-test/main/func_time.result | 64 +++++++++--- mysql-test/main/func_time.test | 26 +++++ mysql-test/main/type_timestamp.result | 4 + sql/field.cc | 87 ++++------------ sql/item.cc | 13 --- sql/item.h | 1 - sql/item_func.cc | 6 +- sql/item_timefunc.cc | 109 ++++++-------------- sql/sql_class.h | 63 ++++++++++++ sql/sql_table.cc | 15 ++- sql/sql_time.cc | 111 +++------------------ sql/sql_time.h | 8 -- sql/sql_type.cc | 138 ++++++++++++++++++++++++++ sql/sql_type.h | 120 ++++++++++++++++++++++ 15 files changed, 475 insertions(+), 292 deletions(-) diff --git a/mysql-test/main/ctype_ucs.result b/mysql-test/main/ctype_ucs.result index f350ba3a1cb..a68309831c6 100644 --- a/mysql-test/main/ctype_ucs.result +++ b/mysql-test/main/ctype_ucs.result @@ -4575,7 +4575,7 @@ SELECT SEC_TO_TIME(CONVERT(900*24*60*60 USING ucs2)); SEC_TO_TIME(CONVERT(900*24*60*60 USING ucs2)) 838:59:59.999999 Warnings: -Warning 1292 Truncated incorrect time value: '77760000' +Warning 1292 Truncated incorrect seconds value: '77760000' # # MDEV-13530 VARBINARY doesn't convert to to BLOB for sizes 65533, 65534 and 65535 # diff --git a/mysql-test/main/func_time.result b/mysql-test/main/func_time.result index 58a0897b9d0..075f5b9ec51 100644 --- a/mysql-test/main/func_time.result +++ b/mysql-test/main/func_time.result @@ -26,7 +26,7 @@ select sec_to_time('9001.1'), sec_to_time('1234567890123.123'); sec_to_time('9001.1') sec_to_time('1234567890123.123') 02:30:01.100000 838:59:59.999999 Warnings: -Warning 1292 Truncated incorrect time value: '1234567890123.123' +Warning 1292 Truncated incorrect seconds value: '1234567890123.123000' select sec_to_time(-9001.1), sec_to_time(-9001.1) / 1, sec_to_time(-9001.1) / 1e0, sec_to_time(-9001) div 1; sec_to_time(-9001.1) sec_to_time(-9001.1) / 1 sec_to_time(-9001.1) / 1e0 sec_to_time(-9001) div 1 @@ -35,13 +35,13 @@ select sec_to_time(90011e-1), sec_to_time(1234567890123e30); sec_to_time(90011e-1) sec_to_time(1234567890123e30) 02:30:01.100000 838:59:59.999999 Warnings: -Warning 1292 Truncated incorrect time value: '1.234567890123e42' +Warning 1292 Truncated incorrect seconds value: '1.234567890123e42' select sec_to_time(1234567890123), sec_to_time('99999999999999999999999999999'); sec_to_time(1234567890123) sec_to_time('99999999999999999999999999999') 838:59:59 838:59:59.999999 Warnings: -Warning 1292 Truncated incorrect time value: '1234567890123' -Warning 1292 Truncated incorrect time value: '99999999999999999999999999999' +Warning 1292 Truncated incorrect seconds value: '1234567890123' +Warning 1292 Truncated incorrect seconds value: '99999999999999999999999999999' select now()-curdate()*1000000-curtime(); now()-curdate()*1000000-curtime() 0 @@ -584,6 +584,8 @@ from_unixtime(2147483647) select from_unixtime(2147483648); from_unixtime(2147483648) NULL +Warnings: +Warning 1292 Truncated incorrect unixtime value: '2147483648' select from_unixtime(0); from_unixtime(0) 1970-01-01 03:00:00 @@ -593,6 +595,8 @@ unix_timestamp(from_unixtime(2147483647)) select unix_timestamp(from_unixtime(2147483648)); unix_timestamp(from_unixtime(2147483648)) NULL +Warnings: +Warning 1292 Truncated incorrect unixtime value: '2147483648' select unix_timestamp('2039-01-20 01:00:00'); unix_timestamp('2039-01-20 01:00:00') NULL @@ -960,17 +964,17 @@ SELECT SEC_TO_TIME(3300000); SEC_TO_TIME(3300000) 838:59:59 Warnings: -Warning 1292 Truncated incorrect time value: '3300000' +Warning 1292 Truncated incorrect seconds value: '3300000' SELECT SEC_TO_TIME(3300000)+0; SEC_TO_TIME(3300000)+0 8385959 Warnings: -Warning 1292 Truncated incorrect time value: '3300000' +Warning 1292 Truncated incorrect seconds value: '3300000' SELECT SEC_TO_TIME(3600 * 4294967296); SEC_TO_TIME(3600 * 4294967296) 838:59:59 Warnings: -Warning 1292 Truncated incorrect time value: '15461882265600' +Warning 1292 Truncated incorrect seconds value: '15461882265600' SELECT TIME_TO_SEC('916:40:00'); TIME_TO_SEC('916:40:00') 3020399 @@ -1019,6 +1023,8 @@ NULL SELECT MAKETIME(0, 0, 4294967296); MAKETIME(0, 0, 4294967296) NULL +Warnings: +Warning 1292 Truncated incorrect seconds value: '4294967296' SELECT MAKETIME(CAST(-1 AS UNSIGNED), 0, 0); MAKETIME(CAST(-1 AS UNSIGNED), 0, 0) 838:59:59 @@ -1044,8 +1050,7 @@ SEC_TO_TIME(CAST(-1 AS UNSIGNED)) 838:59:59 Warnings: Note 1105 Cast to unsigned converted negative integer to it's positive complement -Note 1105 Cast to unsigned converted negative integer to it's positive complement -Warning 1292 Truncated incorrect time value: '18446744073709551615' +Warning 1292 Truncated incorrect seconds value: '18446744073709551615' SET NAMES latin1; SET character_set_results = NULL; SHOW VARIABLES LIKE 'character_set_results'; @@ -1058,6 +1063,7 @@ fmtddate field2 Sep-4 12:00AM abcd DROP TABLE testBug8868; SET NAMES DEFAULT; +SET TIMESTAMP=UNIX_TIMESTAMP('2001-01-01 11:22:33'); CREATE TABLE t1 ( a TIMESTAMP ); @@ -1066,7 +1072,11 @@ SELECT 1 FROM t1 ORDER BY MAKETIME(1, 1, a); 1 1 1 +Warnings: +Warning 1292 Truncated incorrect seconds value: '20010101112233' +Warning 1292 Truncated incorrect seconds value: '20010101112233' DROP TABLE t1; +SET TIMESTAMP=DEFAULT; (select time_format(timediff(now(), DATE_SUB(now(),INTERVAL 5 DAY)),'%H') As H) union (select time_format(timediff(now(), DATE_SUB(now(),INTERVAL 5 DAY)),'%H') As H); @@ -2630,7 +2640,7 @@ SELECT DATE_ADD('2001-01-01 10:20:30',INTERVAL 250000000000.0 SECOND) AS c1, DAT c1 c2 9923-03-10 22:47:10.0 NULL Warnings: -Warning 1292 Truncated incorrect DECIMAL value: '2000000000000000000.0' +Warning 1292 Truncated incorrect seconds value: '2000000000000000000.0' # # MDEV-4838 Wrong metadata for DATE_ADD('string', INVERVAL) # @@ -2789,13 +2799,12 @@ DO TO_DAYS(SEC_TO_TIME(MAKEDATE('',RAND(~(''))))); Warnings: Warning 1292 Truncated incorrect INTEGER value: '' Warning 1292 Truncated incorrect INTEGER value: '' -Warning 1292 Truncated incorrect INTEGER value: '' -Warning 1292 Truncated incorrect time value: '20000101' +Warning 1292 Truncated incorrect seconds value: '20000101' SELECT SEC_TO_TIME(MAKEDATE(0,RAND(~0))); SEC_TO_TIME(MAKEDATE(0,RAND(~0))) 838:59:59 Warnings: -Warning 1292 Truncated incorrect time value: '20000101' +Warning 1292 Truncated incorrect seconds value: '20000101' # # End of 5.5 tests # @@ -3544,3 +3553,32 @@ SET @@session.slow_query_log= @sav_slow_query_log; DROP FUNCTION fn_sleep_before_now; DROP TRIGGER trg_insert_t_ts; DROP TABLE t_ts, t_trig; +# +# MDEV-14032 SEC_TO_TIME executes side effect two times +# +SET @a=10000000; +SELECT SEC_TO_TIME(@a:=@a+1); +SEC_TO_TIME(@a:=@a+1) +838:59:59 +Warnings: +Warning 1292 Truncated incorrect seconds value: '10000001' +SELECT @a; +@a +10000001 +CREATE TABLE t1 (a TEXT); +CREATE FUNCTION f1() RETURNS INT +BEGIN +INSERT INTO t1 VALUES ('f1 was called'); +RETURN 10000000; +END; +$$ +SELECT SEC_TO_TIME(f1()); +SEC_TO_TIME(f1()) +838:59:59 +Warnings: +Warning 1292 Truncated incorrect seconds value: '10000000' +SELECT * FROM t1; +a +f1 was called +DROP TABLE t1; +DROP FUNCTION f1; diff --git a/mysql-test/main/func_time.test b/mysql-test/main/func_time.test index ecbf23f8e36..f6f39d967f2 100644 --- a/mysql-test/main/func_time.test +++ b/mysql-test/main/func_time.test @@ -564,12 +564,15 @@ SET NAMES DEFAULT; # Bug #31160: MAKETIME() crashes server when returning NULL in ORDER BY using # filesort # +SET TIMESTAMP=UNIX_TIMESTAMP('2001-01-01 11:22:33'); CREATE TABLE t1 ( a TIMESTAMP ); INSERT INTO t1 VALUES (now()), (now()); SELECT 1 FROM t1 ORDER BY MAKETIME(1, 1, a); DROP TABLE t1; +SET TIMESTAMP=DEFAULT; + # # Bug #19844 time_format in Union truncates values # @@ -2135,3 +2138,26 @@ DROP TABLE t_ts, t_trig; # # End of MDEV-13727 ################### + + +--echo # +--echo # MDEV-14032 SEC_TO_TIME executes side effect two times +--echo # + +SET @a=10000000; +SELECT SEC_TO_TIME(@a:=@a+1); +SELECT @a; + +CREATE TABLE t1 (a TEXT); +DELIMITER $$; +CREATE FUNCTION f1() RETURNS INT +BEGIN + INSERT INTO t1 VALUES ('f1 was called'); + RETURN 10000000; +END; +$$ +DELIMITER ;$$ +SELECT SEC_TO_TIME(f1()); +SELECT * FROM t1; +DROP TABLE t1; +DROP FUNCTION f1; diff --git a/mysql-test/main/type_timestamp.result b/mysql-test/main/type_timestamp.result index b0405bc4ad7..6fcc802b91a 100644 --- a/mysql-test/main/type_timestamp.result +++ b/mysql-test/main/type_timestamp.result @@ -492,8 +492,12 @@ INSERT INTO t1 (f2,f3) VALUES (NOW(), "0000-00-00 00:00:00"); INSERT INTO t1 (f2,f3) VALUES (NOW(), NULL); INSERT INTO t1 (f2,f3) VALUES (NOW(), ASCII(NULL)); INSERT INTO t1 (f2,f3) VALUES (NOW(), FROM_UNIXTIME('9999999999')); +Warnings: +Warning 1292 Truncated incorrect unixtime value: '9999999999' INSERT INTO t1 (f2,f3) VALUES (NOW(), TIME(NULL)); UPDATE t1 SET f2=NOW(), f3=FROM_UNIXTIME('9999999999') WHERE f1=1; +Warnings: +Warning 1292 Truncated incorrect unixtime value: '9999999999' SELECT f1,f2-f3 FROM t1; f1 f2-f3 1 0 diff --git a/sql/field.cc b/sql/field.cc index ced7d0656df..5b5c724456b 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -5116,10 +5116,8 @@ int Field_timestamp::store(double nr) int error; ErrConvDouble str(nr); THD *thd= get_thd(); - - longlong tmp= double_to_datetime(nr, &l_time, sql_mode_for_timestamp(thd), - &error); - return store_TIME_with_warning(thd, &l_time, &str, error, tmp != -1); + bool rc= Sec6(nr).to_datetime(&l_time, sql_mode_for_timestamp(thd), &error); + return store_TIME_with_warning(thd, &l_time, &str, error, !rc); } @@ -5129,10 +5127,10 @@ int Field_timestamp::store(longlong nr, bool unsigned_val) int error; ErrConvInteger str(nr, unsigned_val); THD *thd= get_thd(); - - longlong tmp= number_to_datetime(nr, 0, &l_time, sql_mode_for_timestamp(thd), - &error); - return store_TIME_with_warning(thd, &l_time, &str, error, tmp != -1); + bool rc= Sec6(nr, unsigned_val).to_datetime(&l_time, + sql_mode_for_timestamp(thd), + &error); + return store_TIME_with_warning(thd, &l_time, &str, error, !rc); } @@ -5428,23 +5426,12 @@ my_decimal *Field_timestamp_with_dec::val_decimal(my_decimal *d) int Field_timestamp::store_decimal(const my_decimal *d) { - ulonglong nr; - ulong sec_part; int error; MYSQL_TIME ltime; - longlong tmp; THD *thd= get_thd(); ErrConvDecimal str(d); - - if (my_decimal2seconds(d, &nr, &sec_part)) - { - tmp= -1; - error= 2; - } - else - tmp= number_to_datetime(nr, sec_part, <ime, sql_mode_for_timestamp(thd), - &error); - return store_TIME_with_warning(thd, <ime, &str, error, tmp != -1); + bool rc= Sec6(d).to_datetime(<ime, sql_mode_for_timestamp(thd), &error); + return store_TIME_with_warning(thd, <ime, &str, error, !rc); } int Field_timestamp_with_dec::set_time() @@ -5623,10 +5610,8 @@ int Field_temporal_with_date::store(double nr) MYSQL_TIME ltime; THD *thd= get_thd(); ErrConvDouble str(nr); - - longlong tmp= double_to_datetime(nr, <ime, - (uint) sql_mode_for_dates(thd), &error); - return store_TIME_with_warning(<ime, &str, error, tmp != -1); + bool rc= Sec6(nr).to_datetime(<ime, sql_mode_for_dates(thd), &error); + return store_TIME_with_warning(<ime, &str, error, !rc); } @@ -5634,13 +5619,11 @@ int Field_temporal_with_date::store(longlong nr, bool unsigned_val) { int error; MYSQL_TIME ltime; - longlong tmp; THD *thd= get_thd(); ErrConvInteger str(nr, unsigned_val); - - tmp= number_to_datetime(nr, 0, <ime, sql_mode_for_dates(thd), &error); - - return store_TIME_with_warning(<ime, &str, error, tmp != -1); + bool rc= Sec6(nr, unsigned_val).to_datetime(<ime, sql_mode_for_dates(thd), + &error); + return store_TIME_with_warning(<ime, &str, error, !rc); } @@ -5859,14 +5842,8 @@ int Field_time::store(double nr) MYSQL_TIME ltime; ErrConvDouble str(nr); int was_cut; - bool neg= nr < 0; - if (neg) - nr= -nr; - int have_smth_to_conv= !number_to_time(neg, (ulonglong) nr, - (ulong)((nr - floor(nr)) * TIME_SECOND_PART_FACTOR), - <ime, &was_cut); - - return store_TIME_with_warning(<ime, &str, was_cut, have_smth_to_conv); + bool rc= Sec6(nr).to_time(<ime, &was_cut); + return store_TIME_with_warning(<ime, &str, was_cut, !rc); } @@ -5875,13 +5852,8 @@ int Field_time::store(longlong nr, bool unsigned_val) MYSQL_TIME ltime; ErrConvInteger str(nr, unsigned_val); int was_cut; - if (nr < 0 && unsigned_val) - nr= 99991231235959LL + 1; - int have_smth_to_conv= !number_to_time(nr < 0, - (ulonglong) (nr < 0 ? -nr : nr), - 0, <ime, &was_cut); - - return store_TIME_with_warning(<ime, &str, was_cut, have_smth_to_conv); + bool rc= Sec6(nr, unsigned_val).to_time(<ime, &was_cut); + return store_TIME_with_warning(<ime, &str, was_cut, !rc); } @@ -6036,16 +6008,11 @@ void Field_time_hires::store_TIME(const MYSQL_TIME *ltime) int Field_time::store_decimal(const my_decimal *d) { - ulonglong nr; - ulong sec_part; ErrConvDecimal str(d); MYSQL_TIME ltime; int was_cut; - bool neg= my_decimal2seconds(d, &nr, &sec_part); - - int have_smth_to_conv= !number_to_time(neg, nr, sec_part, <ime, &was_cut); - - return store_TIME_with_warning(<ime, &str, was_cut, have_smth_to_conv); + bool rc= Sec6(d).to_time(<ime, &was_cut); + return store_TIME_with_warning(<ime, &str, was_cut, !rc); } @@ -6763,24 +6730,12 @@ void Field_datetime_hires::store_TIME(MYSQL_TIME *ltime) int Field_temporal_with_date::store_decimal(const my_decimal *d) { - ulonglong nr; - ulong sec_part; int error; MYSQL_TIME ltime; - longlong tmp; THD *thd= get_thd(); ErrConvDecimal str(d); - - if (my_decimal2seconds(d, &nr, &sec_part)) - { - tmp= -1; - error= 2; - } - else - tmp= number_to_datetime(nr, sec_part, <ime, sql_mode_for_dates(thd), - &error); - - return store_TIME_with_warning(<ime, &str, error, tmp != -1); + bool rc= Sec6(d).to_datetime(<ime, sql_mode_for_dates(thd), &error); + return store_TIME_with_warning(<ime, &str, error, !rc); } bool Field_datetime_with_dec::send_binary(Protocol *protocol) diff --git a/sql/item.cc b/sql/item.cc index f51994bee06..3381d6daaed 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1396,19 +1396,6 @@ bool Item::make_zero_date(MYSQL_TIME *ltime, ulonglong fuzzydate) return !(fuzzydate & TIME_FUZZY_DATES); } -bool Item::get_seconds(ulonglong *sec, ulong *sec_part) -{ - if (decimals == 0) - { // optimize for an important special case - longlong val= val_int(); - bool neg= val < 0 && !unsigned_flag; - *sec= neg ? -val : val; - *sec_part= 0; - return neg; - } - VDec tmp(this); - return tmp.is_null() ? 0 : my_decimal2seconds(tmp.ptr(), sec, sec_part); -} const MY_LOCALE *Item::locale_from_val_str() { diff --git a/sql/item.h b/sql/item.h index f18315fa838..06fd7df2549 100644 --- a/sql/item.h +++ b/sql/item.h @@ -1665,7 +1665,6 @@ public: return get_date_result(<ime, fuzzydate) ? 0 : pack_time(<ime); } - bool get_seconds(ulonglong *sec, ulong *sec_part); virtual bool get_date_result(MYSQL_TIME *ltime, ulonglong fuzzydate) { return get_date(ltime,fuzzydate); } /* diff --git a/sql/item_func.cc b/sql/item_func.cc index 3d4d2d73135..fa14bc58f2e 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -840,8 +840,7 @@ bool Item_func_hybrid_field_type::get_date_from_int_op(MYSQL_TIME *ltime, longlong value= int_op(); bool neg= !unsigned_flag && value < 0; if (null_value || int_to_datetime_with_warn(neg, neg ? -value : value, - ltime, fuzzydate, - field_name_or_null())) + ltime, fuzzydate, NULL)) return make_zero_mysql_time(ltime, fuzzydate); return (null_value= 0); } @@ -875,8 +874,7 @@ bool Item_func_hybrid_field_type::get_date_from_real_op(MYSQL_TIME *ltime, ulonglong fuzzydate) { double value= real_op(); - if (null_value || double_to_datetime_with_warn(value, ltime, fuzzydate, - field_name_or_null())) + if (null_value || double_to_datetime_with_warn(value, ltime, fuzzydate, NULL)) return make_zero_mysql_time(ltime, fuzzydate); return (null_value= 0); } diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 8b8ea9a01ec..abb143add8d 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -450,10 +450,10 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, { if (!my_isspace(&my_charset_latin1,*val)) { + ErrConvString err(val_begin, length, &my_charset_bin); make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, - val_begin, length, - cached_timestamp_type, NullS); + &err, cached_timestamp_type, NullS); break; } } while (++val != val_end); @@ -1335,24 +1335,18 @@ bool get_interval_value(Item *args,interval_type int_type, INTERVAL *interval) if (int_type == INTERVAL_SECOND && args->decimals) { VDec val(args); - ulonglong second; - ulong second_part; if (val.is_null()) return true; - interval->neg= my_decimal2seconds(val.ptr(), &second, &second_part); - if (second == LONGLONG_MAX) + Sec6 d(val.ptr()); + interval->neg= d.neg(); + if (d.sec() >= LONGLONG_MAX) { - THD *thd= current_thd; ErrConvDecimal err(val.ptr()); - push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - ER_TRUNCATED_WRONG_VALUE, - ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), "DECIMAL", - err.ptr()); + current_thd->push_warning_truncated_wrong_value("seconds", err.ptr()); return true; } - - interval->second= second; - interval->second_part= second_part; + interval->second= d.sec(); + interval->second_part= d.usec(); return false; } else if ((int) int_type <= INTERVAL_MICROSECOND) @@ -1740,52 +1734,12 @@ bool Item_func_sysdate_local::get_date(MYSQL_TIME *res, bool Item_func_sec_to_time::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) { DBUG_ASSERT(fixed == 1); - bool sign; - ulonglong sec; - ulong sec_part; - - bzero((char *)ltime, sizeof(*ltime)); - ltime->time_type= MYSQL_TIMESTAMP_TIME; - - sign= args[0]->get_seconds(&sec, &sec_part); - - if ((null_value= args[0]->null_value)) - return 1; - - ltime->neg= sign; - if (sec > TIME_MAX_VALUE_SECONDS) - goto overflow; - - DBUG_ASSERT(sec_part <= TIME_MAX_SECOND_PART); - - ltime->hour= (uint) (sec/3600); - ltime->minute= (uint) (sec % 3600) /60; - ltime->second= (uint) sec % 60; - ltime->second_part= sec_part; - - return 0; - -overflow: - /* use check_time_range() to set ltime to the max value depending on dec */ - int unused; - char buf[100]; - String tmp(buf, sizeof(buf), &my_charset_bin), *err= args[0]->val_str(&tmp); - - ltime->hour= TIME_MAX_HOUR+1; - check_time_range(ltime, decimals, &unused); - if (!err) - { - ErrConvInteger err2(sec, unsigned_flag); - make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, - &err2, MYSQL_TIMESTAMP_TIME, NullS); - } - else - { - ErrConvString err2(err); - make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, - &err2, MYSQL_TIMESTAMP_TIME, NullS); - } - return 0; + VSec6 sec(args[0], "seconds", LONGLONG_MAX); + if ((null_value= sec.is_null())) + return true; + if (sec.sec_to_time(ltime, decimals) && !sec.truncated()) + sec.make_truncated_warning(current_thd, "seconds"); + return false; } bool Item_func_date_format::fix_length_and_dec() @@ -1995,21 +1949,17 @@ bool Item_func_from_unixtime::fix_length_and_dec() bool Item_func_from_unixtime::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date __attribute__((unused))) { - bool sign; - ulonglong sec; - ulong sec_part; - bzero((char *)ltime, sizeof(*ltime)); ltime->time_type= MYSQL_TIMESTAMP_TIME; - sign= args[0]->get_seconds(&sec, &sec_part); + VSec6 sec(args[0], "unixtime", TIMESTAMP_MAX_VALUE); + DBUG_ASSERT(sec.sec() <= TIMESTAMP_MAX_VALUE); - if (args[0]->null_value || sign || sec > TIMESTAMP_MAX_VALUE) + if (sec.is_null() || sec.truncated() || sec.neg()) return (null_value= 1); - tz->gmt_sec_to_TIME(ltime, (my_time_t)sec); - - ltime->second_part= sec_part; + tz->gmt_sec_to_TIME(ltime, (my_time_t) sec.sec()); + ltime->second_part= sec.usec(); return (null_value= 0); } @@ -2694,12 +2644,11 @@ bool Item_func_maketime::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) bool overflow= 0; longlong hour= args[0]->val_int(); longlong minute= args[1]->val_int(); - ulonglong second; - ulong microsecond; - bool neg= args[2]->get_seconds(&second, µsecond); + VSec6 sec(args[2], "seconds", 59); - if (args[0]->null_value || args[1]->null_value || args[2]->null_value || - minute < 0 || minute > 59 || neg || second > 59) + DBUG_ASSERT(sec.sec() <= 59); + if (args[0]->null_value || args[1]->null_value || sec.is_null() || + minute < 0 || minute > 59 || sec.neg() || sec.truncated()) return (null_value= 1); bzero(ltime, sizeof(*ltime)); @@ -2720,8 +2669,8 @@ bool Item_func_maketime::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) { ltime->hour= (uint) ((hour < 0 ? -hour : hour)); ltime->minute= (uint) minute; - ltime->second= (uint) second; - ltime->second_part= microsecond; + ltime->second= (uint) sec.sec(); + ltime->second_part= sec.usec(); } else { @@ -2730,10 +2679,10 @@ bool Item_func_maketime::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) ltime->second= TIME_MAX_SECOND; char buf[28]; char *ptr= longlong10_to_str(hour, buf, args[0]->unsigned_flag ? 10 : -10); - int len = (int)(ptr - buf) + sprintf(ptr, ":%02u:%02u", (uint)minute, (uint)second); - make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, - buf, len, MYSQL_TIMESTAMP_TIME, - NullS); + int len = (int)(ptr - buf) + sprintf(ptr, ":%02u:%02u", + (uint) minute, (uint) sec.sec()); + ErrConvString err(buf, len, &my_charset_bin); + current_thd->push_warning_truncated_wrong_value("time", err.ptr()); } return (null_value= 0); diff --git a/sql/sql_class.h b/sql/sql_class.h index 7f3be3353fb..083b5ef955c 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -4372,6 +4372,69 @@ private: return raised; } +private: + void push_warning_truncated_priv(Sql_condition::enum_warning_level level, + uint sql_errno, + const char *type_str, const char *val) + { + DBUG_ASSERT(sql_errno == ER_TRUNCATED_WRONG_VALUE || + sql_errno == ER_WRONG_VALUE); + char buff[MYSQL_ERRMSG_SIZE]; + CHARSET_INFO *cs= &my_charset_latin1; + cs->cset->snprintf(cs, buff, sizeof(buff), + ER_THD(this, sql_errno), type_str, val); + /* + Note: the format string can vary between ER_TRUNCATED_WRONG_VALUE + and ER_WRONG_VALUE, but the code passed to push_warning() is + always ER_TRUNCATED_WRONG_VALUE. This is intentional. + */ + push_warning(this, level, ER_TRUNCATED_WRONG_VALUE, buff); + } +public: + void push_warning_truncated_wrong_value(Sql_condition::enum_warning_level level, + const char *type_str, const char *val) + { + return push_warning_truncated_priv(level, ER_TRUNCATED_WRONG_VALUE, + type_str, val); + } + void push_warning_wrong_value(Sql_condition::enum_warning_level level, + const char *type_str, const char *val) + { + return push_warning_truncated_priv(level, ER_WRONG_VALUE, type_str, val); + } + void push_warning_truncated_wrong_value(const char *type_str, const char *val) + { + return push_warning_truncated_wrong_value(Sql_condition::WARN_LEVEL_WARN, + type_str, val); + } + void push_warning_truncated_value_for_field(Sql_condition::enum_warning_level + level, const char *type_str, + const char *val, const char *name) + { + DBUG_ASSERT(name); + char buff[MYSQL_ERRMSG_SIZE]; + CHARSET_INFO *cs= &my_charset_latin1; + cs->cset->snprintf(cs, buff, sizeof(buff), + ER_THD(this, ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), + type_str, val, name, + (ulong) get_stmt_da()->current_row_for_warning()); + push_warning(this, level, ER_TRUNCATED_WRONG_VALUE, buff); + + } + void push_warning_wrong_or_truncated_value(Sql_condition::enum_warning_level level, + bool totally_useless_value, + const char *type_str, + const char *val, + const char *field_name) + { + if (field_name) + push_warning_truncated_value_for_field(level, type_str, val, field_name); + else if (totally_useless_value) + push_warning_wrong_value(level, type_str, val); + else + push_warning_truncated_wrong_value(level, type_str, val); + } + public: /** Overloaded to guard query/query_length fields */ virtual void set_statement(Statement *stmt); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 2c36c552354..2b7333b4c24 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -10063,19 +10063,17 @@ err_new_table_cleanup: if (unlikely(alter_ctx.error_if_not_empty && thd->get_stmt_da()->current_row_for_warning())) { - const char *f_val= 0; - enum enum_mysql_timestamp_type t_type= MYSQL_TIMESTAMP_DATE; + const char *f_val= "0000-00-00"; + const char *f_type= "date"; switch (alter_ctx.datetime_field->real_field_type()) { case MYSQL_TYPE_DATE: case MYSQL_TYPE_NEWDATE: - f_val= "0000-00-00"; - t_type= MYSQL_TIMESTAMP_DATE; break; case MYSQL_TYPE_DATETIME: case MYSQL_TYPE_DATETIME2: f_val= "0000-00-00 00:00:00"; - t_type= MYSQL_TIMESTAMP_DATETIME; + f_type= "datetime"; break; default: /* Shouldn't get here. */ @@ -10083,9 +10081,10 @@ err_new_table_cleanup: } bool save_abort_on_warning= thd->abort_on_warning; thd->abort_on_warning= true; - make_truncated_value_warning(thd, Sql_condition::WARN_LEVEL_WARN, - f_val, strlength(f_val), t_type, - alter_ctx.datetime_field->field_name.str); + thd->push_warning_truncated_value_for_field(Sql_condition::WARN_LEVEL_WARN, + f_type, f_val, + alter_ctx.datetime_field-> + field_name.str); thd->abort_on_warning= save_abort_on_warning; } diff --git a/sql/sql_time.cc b/sql/sql_time.cc index 6c8e1048ada..c75b8b19e1a 100644 --- a/sql/sql_time.cc +++ b/sql/sql_time.cc @@ -313,8 +313,7 @@ adjust_time_range_with_warn(MYSQL_TIME *ltime, uint dec) if (check_time_range(ltime, dec, &warnings)) return true; if (warnings) - make_truncated_value_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, - &str, MYSQL_TIMESTAMP_TIME, NullS); + current_thd->push_warning_truncated_wrong_value("time", str.ptr()); return false; } @@ -399,11 +398,14 @@ str_to_datetime_with_warn(CHARSET_INFO *cs, THD *thd= current_thd; bool ret_val= str_to_datetime(cs, str, length, l_time, flags, &status); if (ret_val || status.warnings) + { + const ErrConvString err(str, length, &my_charset_bin); make_truncated_value_warning(thd, ret_val ? Sql_condition::WARN_LEVEL_WARN : Sql_condition::time_warn_level(status.warnings), - str, length, flags & TIME_TIME_ONLY ? + &err, flags & TIME_TIME_ONLY ? MYSQL_TIMESTAMP_TIME : l_time->time_type, NullS); + } DBUG_EXECUTE_IF("str_to_datetime_warn", push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_YES, str);); @@ -411,77 +413,11 @@ str_to_datetime_with_warn(CHARSET_INFO *cs, } -/** - converts a pair of numbers (integer part, microseconds) to MYSQL_TIME - - @param neg sign of the time value - @param nr integer part of the number to convert - @param sec_part microsecond part of the number - @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 0 for success, 1 for a failure -*/ -static bool number_to_time_with_warn(bool neg, ulonglong nr, ulong sec_part, - MYSQL_TIME *ltime, ulonglong fuzzydate, - const ErrConv *str, - const char *field_name) -{ - int was_cut; - longlong res; - enum_mysql_timestamp_type ts_type; - bool have_warnings; - - if (fuzzydate & TIME_TIME_ONLY) - { - fuzzydate= TIME_TIME_ONLY; // clear other flags - ts_type= MYSQL_TIMESTAMP_TIME; - res= number_to_time(neg, nr, sec_part, ltime, &was_cut); - have_warnings= MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut); - } - else - { - ts_type= MYSQL_TIMESTAMP_DATETIME; - if (neg) - { - res= -1; - } - else - { - res= number_to_datetime(nr, sec_part, ltime, fuzzydate, &was_cut); - have_warnings= was_cut && (fuzzydate & TIME_NO_ZERO_IN_DATE); - } - } - - if (res < 0 || have_warnings) - { - make_truncated_value_warning(current_thd, - Sql_condition::WARN_LEVEL_WARN, str, - res < 0 ? MYSQL_TIMESTAMP_ERROR : ts_type, - field_name); - } - return res < 0; -} - - bool double_to_datetime_with_warn(double value, MYSQL_TIME *ltime, ulonglong fuzzydate, const char *field_name) { const ErrConvDouble str(value); - bool neg= value < 0; - - if (neg) - value= -value; - - if (value > LONGLONG_MAX) - value= static_cast(LONGLONG_MAX); - - longlong nr= static_cast(floor(value)); - uint sec_part= static_cast((value - floor(value))*TIME_SECOND_PART_FACTOR); - return number_to_time_with_warn(neg, nr, sec_part, ltime, fuzzydate, &str, - field_name); + return Sec6(value).convert_to_mysql_time(ltime, fuzzydate, &str, field_name); } @@ -489,11 +425,7 @@ bool decimal_to_datetime_with_warn(const my_decimal *value, MYSQL_TIME *ltime, ulonglong fuzzydate, const char *field_name) { const ErrConvDecimal str(value); - ulonglong nr; - ulong sec_part; - bool neg= my_decimal2seconds(value, &nr, &sec_part); - return number_to_time_with_warn(neg, nr, sec_part, ltime, fuzzydate, &str, - field_name); + return Sec6(value).convert_to_mysql_time(ltime, fuzzydate, &str, field_name); } @@ -501,8 +433,8 @@ bool int_to_datetime_with_warn(bool neg, ulonglong value, MYSQL_TIME *ltime, ulonglong fuzzydate, const char *field_name) { const ErrConvInteger str(neg ? - (longlong) value : (longlong) value, !neg); - return number_to_time_with_warn(neg, value, 0, ltime, - fuzzydate, &str, field_name); + Sec6 sec(neg, value, 0); + return sec.convert_to_mysql_time(ltime, fuzzydate, &str, field_name); } @@ -932,9 +864,7 @@ void make_truncated_value_warning(THD *thd, timestamp_type time_type, const char *field_name) { - char warn_buff[MYSQL_ERRMSG_SIZE]; const char *type_str; - CHARSET_INFO *cs= &my_charset_latin1; switch (time_type) { case MYSQL_TIMESTAMP_DATE: @@ -948,23 +878,9 @@ void make_truncated_value_warning(THD *thd, type_str= "datetime"; break; } - if (field_name) - cs->cset->snprintf(cs, warn_buff, sizeof(warn_buff), - ER_THD(thd, ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), - type_str, sval->ptr(), field_name, - (ulong) thd->get_stmt_da()->current_row_for_warning()); - else - { - if (time_type > MYSQL_TIMESTAMP_ERROR) - cs->cset->snprintf(cs, warn_buff, sizeof(warn_buff), - ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), - type_str, sval->ptr()); - else - cs->cset->snprintf(cs, warn_buff, sizeof(warn_buff), - ER_THD(thd, ER_WRONG_VALUE), type_str, sval->ptr()); - } - push_warning(thd, level, - ER_TRUNCATED_WRONG_VALUE, warn_buff); + return thd->push_warning_wrong_or_truncated_value(level, + time_type <= MYSQL_TIMESTAMP_ERROR, + type_str, sval->ptr(), field_name); } @@ -1442,8 +1358,7 @@ time_to_datetime_with_warn(THD *thd, check_date(to, fuzzydate, &warn))) { ErrConvTime str(from); - make_truncated_value_warning(thd, Sql_condition::WARN_LEVEL_WARN, - &str, MYSQL_TIMESTAMP_DATETIME, 0); + thd->push_warning_truncated_wrong_value("datetime", str.ptr()); return true; } return false; diff --git a/sql/sql_time.h b/sql/sql_time.h index b8cc17074d9..cfa1468841b 100644 --- a/sql/sql_time.h +++ b/sql/sql_time.h @@ -120,14 +120,6 @@ void make_truncated_value_warning(THD *thd, timestamp_type time_type, const char *field_name); -static inline void make_truncated_value_warning(THD *thd, - Sql_condition::enum_warning_level level, const char *str_val, size_t str_length, timestamp_type time_type, - const char *field_name) -{ - const ErrConvString str(str_val, str_length, &my_charset_bin); - make_truncated_value_warning(thd, level, &str, time_type, field_name); -} - extern DATE_TIME_FORMAT *date_time_format_make(timestamp_type format_type, const char *format_str, uint format_length); diff --git a/sql/sql_type.cc b/sql/sql_type.cc index 9bf2ab63a39..cdcd37f1006 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -181,6 +181,144 @@ Temporal_hybrid::Temporal_hybrid(THD *thd, Item *item) } +void Sec6::make_from_decimal(const my_decimal *d) +{ + m_neg= my_decimal2seconds(d, &m_sec, &m_usec); + m_truncated= (m_sec >= LONGLONG_MAX); +} + + +void Sec6::make_from_double(double nr) +{ + if ((m_neg= nr < 0)) + nr= -nr; + if ((m_truncated= nr > (double) LONGLONG_MAX)) + { + m_sec= LONGLONG_MAX; + m_usec= 0; + } + else + { + m_sec= (ulonglong) nr; + m_usec= (ulong) ((nr - floor(nr)) * 1000000); + } +} + + +void Sec6::make_truncated_warning(THD *thd, const char *type_str) const +{ + char buff[1 + MAX_BIGINT_WIDTH + 1 + 6 + 1]; // '-' int '.' frac '\0' + to_string(buff, sizeof(buff)); + current_thd->push_warning_truncated_wrong_value(type_str, buff); +} + + +bool Sec6::to_time_with_warn(MYSQL_TIME *to, const ErrConv *str, + const char *field_name) const +{ + int was_cut; + bool res= to_time(to, &was_cut); + if (res || MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut)) + current_thd-> + push_warning_wrong_or_truncated_value(Sql_condition::WARN_LEVEL_WARN, + res, "time", str->ptr(), + field_name); + return res; +} + + +bool Sec6::to_datetime_with_warn(MYSQL_TIME *to, ulonglong fuzzydate, + const ErrConv *str, + const char *field_name) const +{ + bool res, have_warnings= false; + int was_cut; + res= to_datetime(to, fuzzydate, &was_cut); + have_warnings= was_cut && (fuzzydate & TIME_NO_ZERO_IN_DATE); + if (res || have_warnings) + current_thd-> + push_warning_wrong_or_truncated_value(Sql_condition::WARN_LEVEL_WARN, + res, "datetime", str->ptr(), + field_name); + return res; +} + + +bool Sec6::convert_to_mysql_time(MYSQL_TIME *ltime, ulonglong fuzzydate, + const ErrConv *str, const char *field_name) + const +{ + bool is_time= fuzzydate & TIME_TIME_ONLY; + if (truncated()) + { + /* + The value was already truncated at the constructor call time, + and a truncation warning was issued. Here we convert silently + to avoid double warnings. + */ + current_thd-> + push_warning_wrong_or_truncated_value(Sql_condition::WARN_LEVEL_WARN, + !is_time, + is_time ? "time" : "datetime", + str->ptr(), field_name); + int warn; + return is_time ? to_time(ltime, &warn) : + to_datetime(ltime, fuzzydate, &warn); + } + return is_time ? to_time_with_warn(ltime, str, field_name) : + to_datetime_with_warn(ltime, fuzzydate, str, field_name); +} + + +VSec6::VSec6(Item *item, const char *type_str, ulonglong limit) +{ + if (item->decimals == 0) + { // optimize for an important special case + longlong nr= item->val_int(); + make_from_int(nr, item->unsigned_flag); + m_is_null= item->null_value; + if (!m_is_null && m_sec > limit) + { + m_sec= limit; + m_truncated= true; + ErrConvInteger err(nr, item->unsigned_flag); + current_thd->push_warning_truncated_wrong_value(type_str, err.ptr()); + } + } + else if (item->cmp_type() == REAL_RESULT) + { + double nr= item->val_real(); + make_from_double(nr); + m_is_null= item->null_value; + if (!m_is_null && m_sec > limit) + { + m_sec= limit; + m_truncated= true; + } + if (m_truncated) + { + ErrConvDouble err(nr); + current_thd->push_warning_truncated_wrong_value(type_str, err.ptr()); + } + } + else + { + VDec tmp(item); + (m_is_null= tmp.is_null()) ? reset() : make_from_decimal(tmp.ptr()); + if (!m_is_null && m_sec > limit) + { + m_sec= limit; + m_truncated= true; + } + if (m_truncated) + { + ErrConvDecimal err(tmp.ptr()); + current_thd->push_warning_truncated_wrong_value(type_str, err.ptr()); + } + } +} + + void Time::make_from_item(Item *item, const Options opt) { if (item->get_date(this, opt.get_date_flags())) diff --git a/sql/sql_type.h b/sql/sql_type.h index a1eda3df1c9..c8bc64f72d2 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -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