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.
This commit is contained in:
Alexander Barkov 2018-08-09 06:31:05 +04:00
parent a12e6c5ba4
commit 8524bb6872
15 changed files with 475 additions and 292 deletions

View File

@ -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
#

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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, &ltime, sql_mode_for_timestamp(thd),
&error);
return store_TIME_with_warning(thd, &ltime, &str, error, tmp != -1);
bool rc= Sec6(d).to_datetime(&ltime, sql_mode_for_timestamp(thd), &error);
return store_TIME_with_warning(thd, &ltime, &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, &ltime,
(uint) sql_mode_for_dates(thd), &error);
return store_TIME_with_warning(&ltime, &str, error, tmp != -1);
bool rc= Sec6(nr).to_datetime(&ltime, sql_mode_for_dates(thd), &error);
return store_TIME_with_warning(&ltime, &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, &ltime, sql_mode_for_dates(thd), &error);
return store_TIME_with_warning(&ltime, &str, error, tmp != -1);
bool rc= Sec6(nr, unsigned_val).to_datetime(&ltime, sql_mode_for_dates(thd),
&error);
return store_TIME_with_warning(&ltime, &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),
&ltime, &was_cut);
return store_TIME_with_warning(&ltime, &str, was_cut, have_smth_to_conv);
bool rc= Sec6(nr).to_time(&ltime, &was_cut);
return store_TIME_with_warning(&ltime, &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, &ltime, &was_cut);
return store_TIME_with_warning(&ltime, &str, was_cut, have_smth_to_conv);
bool rc= Sec6(nr, unsigned_val).to_time(&ltime, &was_cut);
return store_TIME_with_warning(&ltime, &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, &ltime, &was_cut);
return store_TIME_with_warning(&ltime, &str, was_cut, have_smth_to_conv);
bool rc= Sec6(d).to_time(&ltime, &was_cut);
return store_TIME_with_warning(&ltime, &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, &ltime, sql_mode_for_dates(thd),
&error);
return store_TIME_with_warning(&ltime, &str, error, tmp != -1);
bool rc= Sec6(d).to_datetime(&ltime, sql_mode_for_dates(thd), &error);
return store_TIME_with_warning(&ltime, &str, error, !rc);
}
bool Field_datetime_with_dec::send_binary(Protocol *protocol)

View File

@ -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()
{

View File

@ -1665,7 +1665,6 @@ public:
return get_date_result(&ltime, fuzzydate) ? 0 : pack_time(&ltime);
}
bool get_seconds(ulonglong *sec, ulong *sec_part);
virtual bool get_date_result(MYSQL_TIME *ltime, ulonglong fuzzydate)
{ return get_date(ltime,fuzzydate); }
/*

View File

@ -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);
}

View File

@ -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, &microsecond);
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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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<double>(LONGLONG_MAX);
longlong nr= static_cast<ulonglong>(floor(value));
uint sec_part= static_cast<ulong>((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;

View File

@ -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);

View File

@ -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()))

View File

@ -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