Commit f6a20205 authored by Alexander Barkov's avatar Alexander Barkov

MDEV-17477 Wrong result for TIME('-2001-01-01 10:20:30') and numerous other...

MDEV-17477 Wrong result for TIME('-2001-01-01 10:20:30') and numerous other str-to-time conversion problems
MDEV-17478 Wrong result for TIME('+100:20:30')
parent 0e5a4ac2
......@@ -6145,3 +6145,101 @@ t2 CREATE TABLE `t2` (
) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t2;
DROP TABLE t1;
#
# MDEV-17478 Wrong result for TIME('+100:20:30')
#
SELECT TIME('+100:20:30');
TIME('+100:20:30')
100:20:30
#
# MDEV-17477 Wrong result for TIME('-2001-01-01 10:20:30')
#
SELECT TIME('-2001-01-01 10:20:30');
TIME('-2001-01-01 10:20:30')
NULL
Warnings:
Warning 1292 Truncated incorrect time value: '-2001-01-01 10:20:30'
SELECT TIME('2001-01-01') AS c1, TIME('2001-01-01 ') AS c2;
c1 c2
00:20:01 00:20:01
Warnings:
Warning 1292 Truncated incorrect time value: '2001-01-01'
Warning 1292 Truncated incorrect time value: '2001-01-01 '
SELECT TIME('0001:01:01 '), TIME('0001:01:01 ');
TIME('0001:01:01 ') TIME('0001:01:01 ')
01:01:01 01:01:01
SELECT TIME('1 2'), TIME('1 2 ');
TIME('1 2') TIME('1 2 ')
00:00:01 00:00:01
Warnings:
Warning 1292 Truncated incorrect time value: '1 2'
Warning 1292 Truncated incorrect time value: '1 2 '
SELECT TIME('2001-01-01T'), TIME('2001-01-01T ');
TIME('2001-01-01T') TIME('2001-01-01T ')
00:00:00 00:00:00
SELECT TIME('901-01-01T1'), TIME('901-01-01T10');
TIME('901-01-01T1') TIME('901-01-01T10')
01:00:00 10:00:00
SELECT TIME('091-01-01T1'), TIME('091-01-01T10');
TIME('091-01-01T1') TIME('091-01-01T10')
01:00:00 10:00:00
SELECT TIME('0001:01:01x'), TIME('0001:01:01xx');
TIME('0001:01:01x') TIME('0001:01:01xx')
01:01:01 01:01:01
Warnings:
Warning 1292 Truncated incorrect time value: '0001:01:01x'
Warning 1292 Truncated incorrect time value: '0001:01:01xx'
SELECT TIME('0001:01:01.'), TIME('0001:01:01..');
TIME('0001:01:01.') TIME('0001:01:01..')
01:01:01 01:01:01
Warnings:
Warning 1292 Truncated incorrect time value: '0001:01:01..'
SELECT TIME('0001:01:01-'), TIME('0001:01:01--');
TIME('0001:01:01-') TIME('0001:01:01--')
01:01:01 01:01:01
Warnings:
Warning 1292 Truncated incorrect time value: '0001:01:01-'
Warning 1292 Truncated incorrect time value: '0001:01:01--'
SELECT TIME('0001:01:01-'), TIME('0001:01:01--');
TIME('0001:01:01-') TIME('0001:01:01--')
01:01:01 01:01:01
Warnings:
Warning 1292 Truncated incorrect time value: '0001:01:01-'
Warning 1292 Truncated incorrect time value: '0001:01:01--'
SELECT TIME('-xxx'), TIME('-xxxxxxxxxxxxxxxxxxxx');
TIME('-xxx') TIME('-xxxxxxxxxxxxxxxxxxxx')
NULL NULL
Warnings:
Warning 1292 Truncated incorrect time value: '-xxx'
Warning 1292 Truncated incorrect time value: '-xxxxxxxxxxxxxxxxxxxx'
SELECT TIME('- '), TIME('- ');
TIME('- ') TIME('- ')
NULL NULL
Warnings:
Warning 1292 Truncated incorrect time value: '- '
Warning 1292 Truncated incorrect time value: '- '
SELECT TIME('-'), TIME('-');
TIME('-') TIME('-')
NULL NULL
Warnings:
Warning 1292 Truncated incorrect time value: '-'
Warning 1292 Truncated incorrect time value: '-'
SELECT TIME('1-1-1 1:1:1'), TIME('1-1-1 1:1:1.0');
TIME('1-1-1 1:1:1') TIME('1-1-1 1:1:1.0')
01:01:01 01:01:01.0
SELECT TIME('1-1-1 1:2:3'), TIME('1-1-1 1:2:3.0');
TIME('1-1-1 1:2:3') TIME('1-1-1 1:2:3.0')
01:02:03 01:02:03.0
SELECT
CAST('20050326112233 garbage' as datetime),
CAST('20050326 garbage' as date),
CAST('50326 garbage' as time);
CAST('20050326112233 garbage' as datetime) CAST('20050326 garbage' as date) CAST('50326 garbage' as time)
2005-03-26 11:22:33 2005-03-26 05:03:26
Warnings:
Warning 1292 Truncated incorrect datetime value: '20050326112233 garbage'
Warning 1292 Truncated incorrect date value: '20050326 garbage'
Warning 1292 Truncated incorrect time value: '50326 garbage'
SELECT TIME('- 01:00:00'), TIME('- 1 01:00:00');
TIME('- 01:00:00') TIME('- 1 01:00:00')
-01:00:00 -25:00:00
......@@ -3056,3 +3056,41 @@ FROM t1;
SHOW CREATE TABLE t2;
DROP TABLE t2;
DROP TABLE t1;
--echo #
--echo # MDEV-17478 Wrong result for TIME('+100:20:30')
--echo #
SELECT TIME('+100:20:30');
--echo #
--echo # MDEV-17477 Wrong result for TIME('-2001-01-01 10:20:30')
--echo #
SELECT TIME('-2001-01-01 10:20:30');
SELECT TIME('2001-01-01') AS c1, TIME('2001-01-01 ') AS c2;
SELECT TIME('0001:01:01 '), TIME('0001:01:01 ');
SELECT TIME('1 2'), TIME('1 2 ');
SELECT TIME('2001-01-01T'), TIME('2001-01-01T ');
SELECT TIME('901-01-01T1'), TIME('901-01-01T10');
SELECT TIME('091-01-01T1'), TIME('091-01-01T10');
SELECT TIME('0001:01:01x'), TIME('0001:01:01xx');
SELECT TIME('0001:01:01.'), TIME('0001:01:01..');
SELECT TIME('0001:01:01-'), TIME('0001:01:01--');
SELECT TIME('0001:01:01-'), TIME('0001:01:01--');
SELECT TIME('-xxx'), TIME('-xxxxxxxxxxxxxxxxxxxx');
SELECT TIME('- '), TIME('- ');
SELECT TIME('-'), TIME('-');
SELECT TIME('1-1-1 1:1:1'), TIME('1-1-1 1:1:1.0');
SELECT TIME('1-1-1 1:2:3'), TIME('1-1-1 1:2:3.0');
SELECT
CAST('20050326112233 garbage' as datetime),
CAST('20050326 garbage' as date),
CAST('50326 garbage' as time);
SELECT TIME('- 01:00:00'), TIME('- 1 01:00:00');
......@@ -967,19 +967,19 @@ INSERT INTO t1 VALUES ('20050326');
INSERT INTO t1 VALUES ('20050325');
SELECT COUNT(*) FROM t1 WHERE date BETWEEN '20050326' AND '20050327 invalid';
COUNT(*)
0
1
Warnings:
Warning 1292 Incorrect datetime value: '20050327 invalid'
Warning 1292 Truncated incorrect date value: '20050327 invalid'
SELECT COUNT(*) FROM t1 WHERE date BETWEEN '20050326' AND '20050328 invalid';
COUNT(*)
0
1
Warnings:
Warning 1292 Incorrect datetime value: '20050328 invalid'
Warning 1292 Truncated incorrect date value: '20050328 invalid'
SELECT COUNT(*) FROM t1 WHERE date BETWEEN '20050326' AND '20050327 invalid';
COUNT(*)
0
1
Warnings:
Warning 1292 Incorrect datetime value: '20050327 invalid'
Warning 1292 Truncated incorrect date value: '20050327 invalid'
show status like "Qcache_queries_in_cache";
Variable_name Value
Qcache_queries_in_cache 0
......
select cast('01:02:03 ' as time), cast('01:02:03 ' as time);
cast('01:02:03 ' as time) cast('01:02:03 ' as time)
01:02:03 00:00:00
01:02:03 01:02:03
select cast('2002-011-012' as date), cast('2002.11.12' as date), cast('2002.011.012' as date);
cast('2002-011-012' as date) cast('2002.11.12' as date) cast('2002.011.012' as date)
2002-11-12 2002-11-12 2002-11-12
......
......@@ -271,6 +271,128 @@ int check_time_range(MYSQL_TIME *ltime, uint dec, int *warning)
}
/* Remove trailing spaces and garbage */
static my_bool get_suffix(const char *str, size_t length, size_t *new_length)
{
/*
QQ: perhaps 'T' should be considered as a date/time delimiter only
if it's followed by a digit. Learn ISO 8601 details.
*/
my_bool garbage= FALSE;
for ( ; length > 0 ; length--)
{
char ch= str[length - 1];
if (my_isdigit(&my_charset_latin1, ch) ||
my_ispunct(&my_charset_latin1, ch))
break;
if (my_isspace(&my_charset_latin1, ch))
continue;
if (ch == 'T')
{
/* 'T' has a meaning only after a digit. Otherwise it's a garbage */
if (length >= 2 && my_isdigit(&my_charset_latin1, str[length - 2]))
break;
}
garbage= TRUE;
}
*new_length= length;
return garbage;
}
static size_t get_prefix(const char *str, size_t length, const char **endptr)
{
const char *str0= str, *end= str + length;
for (; str < end && my_isspace(&my_charset_latin1, *str) ; str++)
{ }
*endptr= str;
return str - str0;
}
static size_t get_sign(my_bool *neg, const char *str, size_t length,
const char **endptr)
{
const char *str0= str;
if (length)
{
if ((*neg= (*str == '-')) || (*str == '+'))
str++;
}
else
*neg= FALSE;
*endptr= str;
return str - str0;
}
static my_bool find_body(my_bool *neg, const char *str, size_t length,
MYSQL_TIME *to, int *warn,
const char **new_str, size_t *new_length)
{
size_t sign_length;
*warn= 0;
length-= get_prefix(str, length, &str);
sign_length= get_sign(neg, str, length, &str);
length-= sign_length;
/* There can be a space after a sign again: '- 10:20:30' or '- 1 10:20:30' */
length-= get_prefix(str, length, &str);
if (get_suffix(str, length, &length))
*warn|= MYSQL_TIME_WARN_TRUNCATED;
*new_str= str;
*new_length= length;
if (!length || !my_isdigit(&my_charset_latin1, *str))
{
*warn|= MYSQL_TIME_WARN_TRUNCATED;
set_zero_time(to, MYSQL_TIMESTAMP_ERROR);
return TRUE;
}
return FALSE;
}
static my_bool
is_datetime_body_candidate(const char *str, size_t length)
{
static uint min_date_length= 5; /* '1-1-1' -> '0001-01-01' */
uint pos;
if (length >= 12)
return TRUE;
/*
The shortest possible DATE is '1-1-1', which is 5 characters.
To make a full datetime it should be at least followed by a space or a 'T'.
*/
if (length < min_date_length + 1/* DATE/TIME separator */)
return FALSE;
for (pos= min_date_length; pos < length; pos++)
{
if (str[pos] == 'T') /* Date/time separator */
return TRUE;
if (str[pos] == ' ')
{
/*
We found a space. If can be a DATE/TIME separator:
TIME('1-1-1 1:1:1.0) -> '0001-01-01 01:01:01.0'
But it can be also a DAY/TIME separator:
TIME('1 11') -> 35:00:00 = 1 day 11 hours
TIME('1 111') -> 135:00:00 = 1 day 111 hours
TIME('11 11') -> 275:00:00 = 11 days 11 hours
TIME('111 11') -> 838:59:59 = 111 days 11 hours with overflow
TIME('1111 11') -> 838:59:59 = 1111 days 11 hours with overflow
*/
for (pos= 0 ; pos < min_date_length; pos++)
{
if (my_ispunct(&my_charset_latin1, str[pos])) /* Can be a DATE */
return TRUE;
}
return FALSE;
}
}
return FALSE;
}
static my_bool
str_to_DDhhmmssff_internal(my_bool neg, const char *str, size_t length,
MYSQL_TIME *l_time, ulong max_hour,
......@@ -282,7 +404,7 @@ str_to_DDhhmmssff_internal(my_bool neg, const char *str, size_t length,
Convert a timestamp string to a MYSQL_TIME value.
SYNOPSIS
str_to_datetime()
str_to_datetime_or_date_body()
str String to parse
length Length of string
l_time Date is stored here
......@@ -323,34 +445,16 @@ str_to_DDhhmmssff_internal(my_bool neg, const char *str, size_t length,
#define MAX_DATE_PARTS 8
my_bool
str_to_datetime(const char *str, size_t length, MYSQL_TIME *l_time,
ulonglong flags, MYSQL_TIME_STATUS *status)
static my_bool
str_to_datetime_or_date_body(const char *str, size_t length, MYSQL_TIME *l_time,
ulonglong flags, MYSQL_TIME_STATUS *status)
{
const char *end=str+length, *pos;
uint number_of_fields= 0, digits, year_length, not_zero_date;
DBUG_ENTER("str_to_datetime");
DBUG_ENTER("str_to_datetime_or_date_body");
DBUG_ASSERT(C_FLAGS_OK(flags));
bzero(l_time, sizeof(*l_time));
if (flags & C_TIME_TIME_ONLY)
{
my_bool ret= str_to_time(str, length, l_time, flags, status);
DBUG_RETURN(ret);
}
my_time_status_init(status);
/* Skip space at start */
for (; str != end && my_isspace(&my_charset_latin1, *str) ; str++)
;
if (str == end || ! my_isdigit(&my_charset_latin1, *str))
{
status->warnings= MYSQL_TIME_WARN_TRUNCATED;
l_time->time_type= MYSQL_TIMESTAMP_NONE;
DBUG_RETURN(1);
}
/*
Calculate number of digits in first part.
If length= 8 or >= 14 then year is of format YYYY.
......@@ -442,43 +546,22 @@ str_to_datetime(const char *str, size_t length, MYSQL_TIME *l_time,
l_time->time_type= (number_of_fields <= 3 ?
MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME);
for (; str != end ; str++)
{
if (!my_isspace(&my_charset_latin1,*str))
{
status->warnings= MYSQL_TIME_WARN_TRUNCATED;
break;
}
}
if (str != end)
status->warnings= MYSQL_TIME_WARN_TRUNCATED;
DBUG_RETURN(FALSE);
err:
bzero((char*) l_time, sizeof(*l_time));
l_time->time_type= MYSQL_TIMESTAMP_ERROR;
set_zero_time(l_time, MYSQL_TIMESTAMP_ERROR);
DBUG_RETURN(TRUE);
}
static size_t get_prefix_and_sign(my_bool *neg, const char *str, size_t length)
{
const char *str0= str, *end= str + length;
for (; str < end && my_isspace(&my_charset_latin1, *str) ; str++)
{ }
if (str < end && *str == '-')
{
*neg= TRUE;
str++;
}
return str - str0;
}
/*
Convert a time string to a MYSQL_TIME struct.
SYNOPSIS
str_to_time()
str_to_datetime_or_date_or_time_body()
str A string in full TIMESTAMP format or
[-] DAYS [H]H:MM:SS, [H]H:MM:SS, [M]M:SS, [H]HMMSS,
[M]MSS or [S]S
......@@ -503,44 +586,31 @@ static size_t get_prefix_and_sign(my_bool *neg, const char *str, size_t length)
TRUE on error
*/
my_bool str_to_time(const char *str, size_t length, MYSQL_TIME *l_time,
ulonglong fuzzydate, MYSQL_TIME_STATUS *status)
my_bool str_to_datetime_or_date_or_time_body(const char *str, size_t length,
MYSQL_TIME *l_time,
ulonglong fuzzydate,
MYSQL_TIME_STATUS *status)
{
my_bool neg= 0;
size_t tmp_length;
const char *endptr;
DBUG_ASSERT(C_FLAGS_OK(fuzzydate));
my_time_status_init(status);
if ((tmp_length= get_prefix_and_sign(&neg, str, length)))
{
str+= tmp_length;
length-= tmp_length;
}
if (!length)
{
status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
goto err;
}
/* Check first if this is a full TIMESTAMP */
if (length >= 12)
if (is_datetime_body_candidate(str, length))
{ /* Probably full timestamp */
(void) str_to_datetime(str, length, l_time,
(fuzzydate & ~C_TIME_TIME_ONLY) | C_TIME_DATETIME_ONLY,
status);
(void) str_to_datetime_or_date_body(str, length, l_time,
(fuzzydate & ~C_TIME_TIME_ONLY) |
C_TIME_DATETIME_ONLY,
status);
if (l_time->time_type >= MYSQL_TIMESTAMP_ERROR)
return l_time->time_type == MYSQL_TIMESTAMP_ERROR;
my_time_status_init(status);
}
if (!str_to_DDhhmmssff_internal(neg, str, length, l_time, TIME_MAX_HOUR,
if (!str_to_DDhhmmssff_internal(FALSE, str, length, l_time, TIME_MAX_HOUR,
status, &endptr))
return FALSE;
err:
bzero((char*) l_time, sizeof(*l_time));
l_time->time_type= MYSQL_TIMESTAMP_ERROR;
set_zero_time(l_time, MYSQL_TIMESTAMP_ERROR);
return TRUE;
}
......@@ -548,31 +618,22 @@ my_bool str_to_time(const char *str, size_t length, MYSQL_TIME *l_time,
my_bool str_to_DDhhmmssff(const char *str, size_t length, MYSQL_TIME *ltime,
ulong max_hour, MYSQL_TIME_STATUS *status)
{
my_bool neg= 0;
size_t tmp_length;
my_bool neg;
const char *endptr;
my_time_status_init(status);
int warn;
/* Remove trailing spaces */
for ( ; length > 0 && my_isspace(&my_charset_latin1, str[length - 1]) ; )
length--;
if ((tmp_length= get_prefix_and_sign(&neg, str, length)))
{
str+= tmp_length;
length-= tmp_length;
}
if (!length)
my_time_status_init(status);
if (find_body(&neg, str, length, ltime, &warn, &str, &length))
{
status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
set_zero_time(ltime, MYSQL_TIMESTAMP_ERROR);
status->warnings= warn;
return TRUE;
}
/* Reject anything that might be parsed as a full TIMESTAMP */
if (length >= 12) /* The same condition with str_to_time() */
if (is_datetime_body_candidate(str, length))
{
(void) str_to_datetime(str, length, ltime, C_TIME_DATETIME_ONLY, status);
(void) str_to_datetime_or_date_body(str, length, ltime,
C_TIME_DATETIME_ONLY, status);
if (ltime->time_type > MYSQL_TIMESTAMP_ERROR)
{
status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
......@@ -591,6 +652,57 @@ my_bool str_to_DDhhmmssff(const char *str, size_t length, MYSQL_TIME *ltime,
status, &endptr) ||
(endptr < str + length && endptr[0] == '-'))
return TRUE;
status->warnings|= warn;
return FALSE;
}
my_bool str_to_time(const char *str, size_t length, MYSQL_TIME *l_time,
ulonglong fuzzydate, MYSQL_TIME_STATUS *status)
{
my_bool neg;
int warn;
DBUG_ASSERT(C_FLAGS_OK(fuzzydate));
my_time_status_init(status);
if (find_body(&neg, str, length, l_time, &warn, &str, &length))
{
status->warnings= warn;
return TRUE;
}
/*
QQ: Perhaps we should modify xxx_body() to return endptr.
If endptr points to '-', return an error.
*/
if (str_to_datetime_or_date_or_time_body(str, length, l_time,
fuzzydate, status))
return TRUE;
status->warnings|= warn;
l_time->neg= neg;
return FALSE;
}
my_bool
str_to_datetime(const char *str, size_t length, MYSQL_TIME *l_time,
ulonglong flags, MYSQL_TIME_STATUS *status)
{
my_bool neg, rc;
int warn;
DBUG_ASSERT(C_FLAGS_OK(flags));
my_time_status_init(status);
if (find_body(&neg, str, length, l_time, &warn, &str, &length))
{
status->warnings= warn;
return TRUE;
}
rc= (flags & C_TIME_TIME_ONLY) ?
str_to_datetime_or_date_or_time_body(str, length, l_time, flags, status) :
str_to_datetime_or_date_body(str, length, l_time, flags, status);
status->warnings|= warn;
if (rc)
return rc;
if ((l_time->neg= neg) && l_time->time_type != MYSQL_TIMESTAMP_TIME)
return TRUE;
return FALSE;
}
......@@ -749,16 +861,7 @@ str_to_DDhhmmssff_internal(my_bool neg, const char *str, size_t length,
/* Check if there is garbage at end of the MYSQL_TIME specification */
if (str != end)
{
do
{
if (!my_isspace(&my_charset_latin1,*str))
{
status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
break;
}
} while (++str != end);
}
status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
return FALSE;
err:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment