summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Barkov <bar@mariadb.com>2018-11-16 19:18:17 +0400
committerAlexander Barkov <bar@mariadb.com>2018-11-17 10:14:44 +0400
commitd2ba9edd664e27a97aff1c73da5717d6157789d4 (patch)
tree59ebc89ac84a7fc5e328f8d16f025c7b7c744b99
parentf92d223fe21485c83c71efe9116c02046f3d3b46 (diff)
downloadmariadb-git-d2ba9edd664e27a97aff1c73da5717d6157789d4.tar.gz
MDEV-17740 Extend EXTRACT(x AS DAY*) to understand long time intervals
-rw-r--r--include/my_time.h28
-rw-r--r--libmysqld/libmysql.c3
-rw-r--r--mysql-test/main/func_extract.result555
-rw-r--r--mysql-test/main/func_extract.test250
-rw-r--r--mysql-test/main/func_time.result8
-rw-r--r--mysql-test/main/str_to_datetime_457.result5
-rw-r--r--sql-common/my_time.c342
-rw-r--r--sql/item_timefunc.cc12
-rw-r--r--sql/item_timefunc.h4
-rw-r--r--sql/sql_basic_types.h14
-rw-r--r--sql/sql_time.cc26
-rw-r--r--sql/sql_type.cc11
-rw-r--r--sql/sql_type.h116
13 files changed, 1234 insertions, 140 deletions
diff --git a/include/my_time.h b/include/my_time.h
index a7911f0f8da..ab0d2b0661d 100644
--- a/include/my_time.h
+++ b/include/my_time.h
@@ -79,6 +79,16 @@ extern uchar days_in_month[];
/* Useful constants */
#define SECONDS_IN_24H 86400L
+/* Limits for the INTERVAL data type */
+
+ /* Number of hours between '0001-01-01 00h' and '9999-12-31 23h' */
+#define TIME_MAX_INTERVAL_HOUR 87649415
+#define TIME_MAX_INTERVAL_HOUR_CHAR_LENGTH 8
+
+/* Number of full days between '0001-01-01' and '9999-12-31'*/
+#define TIME_MAX_INTERVAL_DAY 3652058 /*87649415/24*/
+#define TIME_MAX_INTERVAL_DAY_CHAR_LENGTH 7
+
/* Limits for the TIME data type */
#define TIME_MAX_HOUR 838
#define TIME_MAX_MINUTE 59
@@ -112,7 +122,21 @@ my_bool str_to_DDhhmmssff(const char *str, size_t length, MYSQL_TIME *l_time,
ulong max_hour, MYSQL_TIME_STATUS *status);
my_bool str_to_datetime_or_date_or_time(const char *str, size_t length,
MYSQL_TIME *to, ulonglong flag,
- MYSQL_TIME_STATUS *status);
+ MYSQL_TIME_STATUS *status,
+ ulong time_max_hour,
+ ulong time_err_hour);
+my_bool
+str_to_datetime_or_date_or_interval_hhmmssff(const char *str, size_t length,
+ MYSQL_TIME *to, ulonglong flag,
+ MYSQL_TIME_STATUS *status,
+ ulong time_max_hour,
+ ulong time_err_hour);
+my_bool
+str_to_datetime_or_date_or_interval_day(const char *str, size_t length,
+ MYSQL_TIME *to, ulonglong flag,
+ MYSQL_TIME_STATUS *status,
+ ulong time_max_hour,
+ ulong time_err_hour);
my_bool str_to_datetime_or_date(const char *str, size_t length, MYSQL_TIME *to,
ulonglong flags, MYSQL_TIME_STATUS *status);
@@ -120,7 +144,7 @@ longlong number_to_datetime_or_date(longlong nr, ulong sec_part,
MYSQL_TIME *time_res,
ulonglong flags, int *was_cut);
int number_to_time_only(my_bool neg, ulonglong nr, ulong sec_part,
- MYSQL_TIME *ltime, int *was_cut);
+ ulong max_hour, MYSQL_TIME *to, int *was_cut);
ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME *);
ulonglong TIME_to_ulonglong_date(const MYSQL_TIME *);
diff --git a/libmysqld/libmysql.c b/libmysqld/libmysql.c
index 98dc9f17f60..cd170b42b42 100644
--- a/libmysqld/libmysql.c
+++ b/libmysqld/libmysql.c
@@ -3215,7 +3215,8 @@ static void fetch_string_with_conversion(MYSQL_BIND *param, char *value, size_t
{
MYSQL_TIME *tm= (MYSQL_TIME *)buffer;
MYSQL_TIME_STATUS status;
- str_to_datetime_or_date_or_time(value, length, tm, 0, &status);
+ str_to_datetime_or_date_or_time(value, length, tm, 0, &status,
+ TIME_MAX_HOUR, UINT_MAX32);
err= status.warnings;
*param->error= MY_TEST(err);
break;
diff --git a/mysql-test/main/func_extract.result b/mysql-test/main/func_extract.result
new file mode 100644
index 00000000000..7a751a8dd72
--- /dev/null
+++ b/mysql-test/main/func_extract.result
@@ -0,0 +1,555 @@
+#
+# MDEV-17385 MICROSECOND() returns confusing results with an out-of-range TIME-alike argument
+#
+CREATE TABLE t1 (v VARCHAR(64), ll BIGINT, t TIME, dt DATETIME, d DATE);
+CREATE TABLE t2 AS SELECT
+EXTRACT(DAY FROM t),
+EXTRACT(DAY_HOUR FROM t),
+EXTRACT(DAY_MINUTE FROM t),
+EXTRACT(DAY_SECOND FROM t),
+EXTRACT(DAY_MICROSECOND FROM t),
+EXTRACT(DAY FROM d),
+EXTRACT(DAY_HOUR FROM d),
+EXTRACT(DAY_MINUTE FROM d),
+EXTRACT(DAY_SECOND FROM d),
+EXTRACT(DAY_MICROSECOND FROM d),
+EXTRACT(DAY FROM v),
+EXTRACT(DAY_HOUR FROM v),
+EXTRACT(DAY_MINUTE FROM v),
+EXTRACT(DAY_SECOND FROM v),
+EXTRACT(DAY_MICROSECOND FROM v),
+EXTRACT(DAY FROM ll),
+EXTRACT(DAY_HOUR FROM ll),
+EXTRACT(DAY_MINUTE FROM ll),
+EXTRACT(DAY_SECOND FROM ll),
+EXTRACT(DAY_MICROSECOND FROM ll)
+FROM t1;
+SHOW CREATE TABLE t2;
+Table Create Table
+t2 CREATE TABLE `t2` (
+ `EXTRACT(DAY FROM t)` int(3) DEFAULT NULL,
+ `EXTRACT(DAY_HOUR FROM t)` int(5) DEFAULT NULL,
+ `EXTRACT(DAY_MINUTE FROM t)` int(7) DEFAULT NULL,
+ `EXTRACT(DAY_SECOND FROM t)` int(9) DEFAULT NULL,
+ `EXTRACT(DAY_MICROSECOND FROM t)` bigint(15) DEFAULT NULL,
+ `EXTRACT(DAY FROM d)` int(3) DEFAULT NULL,
+ `EXTRACT(DAY_HOUR FROM d)` int(5) DEFAULT NULL,
+ `EXTRACT(DAY_MINUTE FROM d)` int(7) DEFAULT NULL,
+ `EXTRACT(DAY_SECOND FROM d)` int(9) DEFAULT NULL,
+ `EXTRACT(DAY_MICROSECOND FROM d)` bigint(15) DEFAULT NULL,
+ `EXTRACT(DAY FROM v)` int(8) DEFAULT NULL,
+ `EXTRACT(DAY_HOUR FROM v)` int(10) DEFAULT NULL,
+ `EXTRACT(DAY_MINUTE FROM v)` bigint(12) DEFAULT NULL,
+ `EXTRACT(DAY_SECOND FROM v)` bigint(14) DEFAULT NULL,
+ `EXTRACT(DAY_MICROSECOND FROM v)` bigint(20) DEFAULT NULL,
+ `EXTRACT(DAY FROM ll)` int(8) DEFAULT NULL,
+ `EXTRACT(DAY_HOUR FROM ll)` int(10) DEFAULT NULL,
+ `EXTRACT(DAY_MINUTE FROM ll)` bigint(12) DEFAULT NULL,
+ `EXTRACT(DAY_SECOND FROM ll)` bigint(14) DEFAULT NULL,
+ `EXTRACT(DAY_MICROSECOND FROM ll)` bigint(20) DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+DROP TABLE t2;
+DROP TABLE t1;
+CREATE TABLE t1 (a VARCHAR(64), b DECIMAL(32,9));
+INSERT INTO t1 VALUES
+('9999-12-31 23:59:59.123456', 99991231235959.123456),
+('2001-01-01 10:20:30.123456', 20010101102030.123456),
+('4294967296:59:59.123456', 42949672965959.123456),
+('4294967295:59:59.123456', 42949672955959.123456),
+('87649416:59:59.123456', 876494165959.123456),
+('87649415:59:59.123456', 876494155959.123456),
+('87649414:59:59.123456', 876494145959.123456),
+('9999:59:59.123456', 99995959.123456),
+('9999:01:01.123456', 99990101.123456),
+('9999:01:01', 99990101),
+('0.999999', 0.999999),
+('0.99999', 0.99999),
+('0.9999', 0.9999),
+('0.999', 0.999),
+('0.99', 0.99),
+('0.9', 0.9),
+('000000',0);
+# Summary:
+# Check that FUNC(varchar) and FUNC(decimal) give equal results
+# Expect empty sets
+SELECT a, b, EXTRACT(DAY_HOUR FROM a), EXTRACT(DAY_HOUR FROM b) FROM t1 WHERE NOT (EXTRACT(DAY_HOUR FROM a)<=>EXTRACT(DAY_HOUR FROM b));
+a b EXTRACT(DAY_HOUR FROM a) EXTRACT(DAY_HOUR FROM b)
+SELECT a, b, EXTRACT(DAY FROM a), EXTRACT(DAY FROM b) FROM t1 WHERE NOT (EXTRACT(DAY FROM a)<=>EXTRACT(DAY FROM b));
+a b EXTRACT(DAY FROM a) EXTRACT(DAY FROM b)
+SELECT a, b, EXTRACT(HOUR FROM a), EXTRACT(HOUR FROM b) FROM t1 WHERE NOT (EXTRACT(HOUR FROM a)<=>EXTRACT(HOUR FROM b));
+a b EXTRACT(HOUR FROM a) EXTRACT(HOUR FROM b)
+SELECT a, b, EXTRACT(MINUTE FROM a), EXTRACT(MINUTE FROM b) FROM t1 WHERE NOT (EXTRACT(MINUTE FROM a)<=>EXTRACT(MINUTE FROM b));
+a b EXTRACT(MINUTE FROM a) EXTRACT(MINUTE FROM b)
+SELECT a, b, EXTRACT(SECOND FROM a), EXTRACT(SECOND FROM b) FROM t1 WHERE NOT (EXTRACT(SECOND FROM a)<=>EXTRACT(SECOND FROM b));
+a b EXTRACT(SECOND FROM a) EXTRACT(SECOND FROM b)
+SELECT a, b, EXTRACT(MICROSECOND FROM a), EXTRACT(MICROSECOND FROM b) FROM t1 WHERE NOT (EXTRACT(MICROSECOND FROM a)<=>EXTRACT(MICROSECOND FROM b));
+a b EXTRACT(MICROSECOND FROM a) EXTRACT(MICROSECOND FROM b)
+# Detailed results
+SELECT
+a,
+EXTRACT(DAY FROM a) * 24 + EXTRACT(HOUR FROM a) AS dh,
+EXTRACT(DAY_HOUR FROM a),
+EXTRACT(DAY FROM a),
+EXTRACT(HOUR FROM a),
+EXTRACT(MINUTE FROM a),
+EXTRACT(SECOND FROM a),
+EXTRACT(MICROSECOND FROM a)
+FROM t1;
+a dh EXTRACT(DAY_HOUR FROM a) EXTRACT(DAY FROM a) EXTRACT(HOUR FROM a) EXTRACT(MINUTE FROM a) EXTRACT(SECOND FROM a) EXTRACT(MICROSECOND FROM a)
+9999-12-31 23:59:59.123456 767 3123 31 23 59 59 123456
+2001-01-01 10:20:30.123456 34 110 1 10 20 30 123456
+4294967296:59:59.123456 NULL NULL NULL NULL NULL NULL NULL
+4294967295:59:59.123456 NULL NULL NULL NULL NULL NULL NULL
+87649416:59:59.123456 NULL NULL NULL NULL NULL NULL NULL
+87649415:59:59.123456 87649415 365205823 3652058 23 59 59 123456
+87649414:59:59.123456 87649414 365205822 3652058 22 59 59 123456
+9999:59:59.123456 9999 41615 416 15 59 59 123456
+9999:01:01.123456 9999 41615 416 15 1 1 123456
+9999:01:01 9999 41615 416 15 1 1 0
+0.999999 0 0 0 0 0 0 999999
+0.99999 0 0 0 0 0 0 999990
+0.9999 0 0 0 0 0 0 999900
+0.999 0 0 0 0 0 0 999000
+0.99 0 0 0 0 0 0 990000
+0.9 0 0 0 0 0 0 900000
+000000 0 0 0 0 0 0 0
+Warnings:
+Warning 1292 Incorrect interval value: '4294967296:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967296:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967296:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967296:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967296:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967296:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967296:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967296:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967295:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967295:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967295:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967295:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967295:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967295:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967295:59:59.123456'
+Warning 1292 Incorrect interval value: '4294967295:59:59.123456'
+Warning 1292 Incorrect interval value: '87649416:59:59.123456'
+Warning 1292 Incorrect interval value: '87649416:59:59.123456'
+Warning 1292 Incorrect interval value: '87649416:59:59.123456'
+Warning 1292 Incorrect interval value: '87649416:59:59.123456'
+Warning 1292 Incorrect interval value: '87649416:59:59.123456'
+Warning 1292 Incorrect interval value: '87649416:59:59.123456'
+Warning 1292 Incorrect interval value: '87649416:59:59.123456'
+Warning 1292 Incorrect interval value: '87649416:59:59.123456'
+SELECT
+b,
+EXTRACT(DAY FROM b) * 24 + EXTRACT(HOUR FROM b) AS dh,
+EXTRACT(DAY_HOUR FROM b),
+EXTRACT(DAY FROM b),
+EXTRACT(HOUR FROM b),
+EXTRACT(MINUTE FROM b),
+EXTRACT(SECOND FROM b),
+EXTRACT(MICROSECOND FROM b)
+FROM t1;
+b dh EXTRACT(DAY_HOUR FROM b) EXTRACT(DAY FROM b) EXTRACT(HOUR FROM b) EXTRACT(MINUTE FROM b) EXTRACT(SECOND FROM b) EXTRACT(MICROSECOND FROM b)
+99991231235959.123456000 767 3123 31 23 59 59 123456
+20010101102030.123456000 34 110 1 10 20 30 123456
+42949672965959.123456000 NULL NULL NULL NULL NULL NULL NULL
+42949672955959.123456000 NULL NULL NULL NULL NULL NULL NULL
+876494165959.123456000 NULL NULL NULL NULL NULL NULL NULL
+876494155959.123456000 87649415 365205823 3652058 23 59 59 123456
+876494145959.123456000 87649414 365205822 3652058 22 59 59 123456
+99995959.123456000 9999 41615 416 15 59 59 123456
+99990101.123456000 9999 41615 416 15 1 1 123456
+99990101.000000000 9999 41615 416 15 1 1 0
+0.999999000 0 0 0 0 0 0 999999
+0.999990000 0 0 0 0 0 0 999990
+0.999900000 0 0 0 0 0 0 999900
+0.999000000 0 0 0 0 0 0 999000
+0.990000000 0 0 0 0 0 0 990000
+0.900000000 0 0 0 0 0 0 900000
+0.000000000 0 0 0 0 0 0 0
+Warnings:
+Warning 1292 Incorrect interval value: '42949672965959.123456000' for column 'b' at row 3
+Warning 1292 Incorrect interval value: '42949672965959.123456000' for column 'b' at row 3
+Warning 1292 Incorrect interval value: '42949672965959.123456000' for column 'b' at row 3
+Warning 1292 Incorrect interval value: '42949672965959.123456000' for column 'b' at row 3
+Warning 1292 Incorrect interval value: '42949672965959.123456000' for column 'b' at row 3
+Warning 1292 Incorrect interval value: '42949672965959.123456000' for column 'b' at row 3
+Warning 1292 Incorrect interval value: '42949672965959.123456000' for column 'b' at row 3
+Warning 1292 Incorrect interval value: '42949672965959.123456000' for column 'b' at row 3
+Warning 1292 Incorrect interval value: '42949672955959.123456000' for column 'b' at row 4
+Warning 1292 Incorrect interval value: '42949672955959.123456000' for column 'b' at row 4
+Warning 1292 Incorrect interval value: '42949672955959.123456000' for column 'b' at row 4
+Warning 1292 Incorrect interval value: '42949672955959.123456000' for column 'b' at row 4
+Warning 1292 Incorrect interval value: '42949672955959.123456000' for column 'b' at row 4
+Warning 1292 Incorrect interval value: '42949672955959.123456000' for column 'b' at row 4
+Warning 1292 Incorrect interval value: '42949672955959.123456000' for column 'b' at row 4
+Warning 1292 Incorrect interval value: '42949672955959.123456000' for column 'b' at row 4
+Warning 1292 Incorrect interval value: '876494165959.123456000' for column 'b' at row 5
+Warning 1292 Incorrect interval value: '876494165959.123456000' for column 'b' at row 5
+Warning 1292 Incorrect interval value: '876494165959.123456000' for column 'b' at row 5
+Warning 1292 Incorrect interval value: '876494165959.123456000' for column 'b' at row 5
+Warning 1292 Incorrect interval value: '876494165959.123456000' for column 'b' at row 5
+Warning 1292 Incorrect interval value: '876494165959.123456000' for column 'b' at row 5
+Warning 1292 Incorrect interval value: '876494165959.123456000' for column 'b' at row 5
+Warning 1292 Incorrect interval value: '876494165959.123456000' for column 'b' at row 5
+DROP TABLE t1;
+# Special case: DAY + TIME
+CREATE TABLE t1 (a VARCHAR(64));
+INSERT INTO t1 VALUES ('9999-01-01');
+SELECT a,
+EXTRACT(DAY_HOUR FROM a),
+EXTRACT(DAY_MINUTE FROM a),
+EXTRACT(DAY_SECOND FROM a),
+EXTRACT(DAY_MICROSECOND FROM a),
+EXTRACT(DAY FROM a),
+EXTRACT(HOUR FROM a),
+EXTRACT(MINUTE FROM a),
+EXTRACT(SECOND FROM a),
+EXTRACT(MICROSECOND FROM a)
+FROM t1;
+a EXTRACT(DAY_HOUR FROM a) EXTRACT(DAY_MINUTE FROM a) EXTRACT(DAY_SECOND FROM a) EXTRACT(DAY_MICROSECOND FROM a) EXTRACT(DAY FROM a) EXTRACT(HOUR FROM a) EXTRACT(MINUTE FROM a) EXTRACT(SECOND FROM a) EXTRACT(MICROSECOND FROM a)
+9999-01-01 100 10000 1000000 1000000000000 1 0 0 0 0
+DROP TABLE t1;
+# Bad values
+CREATE TABLE t1 (a VARCHAR(64));
+INSERT INTO t1 VALUES ('');
+SELECT a,
+EXTRACT(DAY_HOUR FROM a),
+EXTRACT(DAY_MINUTE FROM a),
+EXTRACT(DAY_SECOND FROM a),
+EXTRACT(DAY_MICROSECOND FROM a),
+EXTRACT(DAY FROM a),
+EXTRACT(HOUR FROM a),
+EXTRACT(MINUTE FROM a),
+EXTRACT(SECOND FROM a),
+EXTRACT(MICROSECOND FROM a)
+FROM t1;
+a EXTRACT(DAY_HOUR FROM a) EXTRACT(DAY_MINUTE FROM a) EXTRACT(DAY_SECOND FROM a) EXTRACT(DAY_MICROSECOND FROM a) EXTRACT(DAY FROM a) EXTRACT(HOUR FROM a) EXTRACT(MINUTE FROM a) EXTRACT(SECOND FROM a) EXTRACT(MICROSECOND FROM a)
+ NULL NULL NULL NULL NULL NULL NULL NULL NULL
+Warnings:
+Warning 1292 Incorrect interval value: ''
+Warning 1292 Incorrect interval value: ''
+Warning 1292 Incorrect interval value: ''
+Warning 1292 Incorrect interval value: ''
+Warning 1292 Incorrect interval value: ''
+Warning 1292 Incorrect interval value: ''
+Warning 1292 Incorrect interval value: ''
+Warning 1292 Incorrect interval value: ''
+Warning 1292 Incorrect interval value: ''
+DROP TABLE t1;
+# Backward compatibility
+# This still parses as DATETIME
+SELECT EXTRACT(YEAR FROM '2001/02/03 10:20:30');
+EXTRACT(YEAR FROM '2001/02/03 10:20:30')
+2001
+SELECT EXTRACT(MONTH FROM '2001/02/03 10:20:30');
+EXTRACT(MONTH FROM '2001/02/03 10:20:30')
+2
+SELECT EXTRACT(DAY FROM '2001/02/03 10:20:30');
+EXTRACT(DAY FROM '2001/02/03 10:20:30')
+3
+SELECT EXTRACT(YEAR FROM '01/02/03 10:20:30');
+EXTRACT(YEAR FROM '01/02/03 10:20:30')
+2001
+SELECT EXTRACT(MONTH FROM '01/02/03 10:20:30');
+EXTRACT(MONTH FROM '01/02/03 10:20:30')
+2
+SELECT EXTRACT(DAY FROM '01/02/03 10:20:30');
+EXTRACT(DAY FROM '01/02/03 10:20:30')
+3
+SELECT EXTRACT(YEAR FROM '01:02:03 10:20:30');
+EXTRACT(YEAR FROM '01:02:03 10:20:30')
+2001
+SELECT EXTRACT(MONTH FROM '01:02:03 10:20:30');
+EXTRACT(MONTH FROM '01:02:03 10:20:30')
+2
+SELECT EXTRACT(DAY FROM '01:02:03 10:20:30');
+EXTRACT(DAY FROM '01:02:03 10:20:30')
+3
+# This still parses as DATETIME and returns NULL
+SELECT EXTRACT(YEAR FROM "2011-02-32 8:46:06.23434");
+EXTRACT(YEAR FROM "2011-02-32 8:46:06.23434")
+NULL
+Warnings:
+Warning 1292 Incorrect datetime value: '2011-02-32 8:46:06.23434'
+SELECT EXTRACT(MONTH FROM "2011-02-32 8:46:06.23434");
+EXTRACT(MONTH FROM "2011-02-32 8:46:06.23434")
+NULL
+Warnings:
+Warning 1292 Incorrect datetime value: '2011-02-32 8:46:06.23434'
+SELECT EXTRACT(DAY FROM "2011-02-32 8:46:06.23434");
+EXTRACT(DAY FROM "2011-02-32 8:46:06.23434")
+NULL
+Warnings:
+Warning 1292 Incorrect interval value: '2011-02-32 8:46:06.23434'
+SELECT EXTRACT(HOUR FROM "2011-02-32 8:46:06.23434");
+EXTRACT(HOUR FROM "2011-02-32 8:46:06.23434")
+NULL
+Warnings:
+Warning 1292 Incorrect interval value: '2011-02-32 8:46:06.23434'
+# This still parses as DATE
+SELECT EXTRACT(YEAR FROM '2001/02/03');
+EXTRACT(YEAR FROM '2001/02/03')
+2001
+SELECT EXTRACT(MONTH FROM '2001/02/03');
+EXTRACT(MONTH FROM '2001/02/03')
+2
+SELECT EXTRACT(DAY FROM '2001/02/03');
+EXTRACT(DAY FROM '2001/02/03')
+3
+SELECT EXTRACT(YEAR FROM '01/02/03');
+EXTRACT(YEAR FROM '01/02/03')
+2001
+SELECT EXTRACT(MONTH FROM '01/02/03');
+EXTRACT(MONTH FROM '01/02/03')
+2
+SELECT EXTRACT(DAY FROM '01/02/03');
+EXTRACT(DAY FROM '01/02/03')
+3
+SELECT EXTRACT(YEAR FROM '01-02-03');
+EXTRACT(YEAR FROM '01-02-03')
+2001
+SELECT EXTRACT(MONTH FROM '01-02-03');
+EXTRACT(MONTH FROM '01-02-03')
+2
+SELECT EXTRACT(DAY FROM '01-02-03');
+EXTRACT(DAY FROM '01-02-03')
+3
+SELECT EXTRACT(YEAR FROM '1-2-3');
+EXTRACT(YEAR FROM '1-2-3')
+1
+SELECT EXTRACT(MONTH FROM '1-2-3');
+EXTRACT(MONTH FROM '1-2-3')
+2
+SELECT EXTRACT(DAY FROM '1-2-3');
+EXTRACT(DAY FROM '1-2-3')
+3
+SELECT EXTRACT(HOUR FROM '1-2-3');
+EXTRACT(HOUR FROM '1-2-3')
+0
+SELECT EXTRACT(DAY FROM '2024-01-03 garbage /////');
+EXTRACT(DAY FROM '2024-01-03 garbage /////')
+3
+Warnings:
+Warning 1292 Truncated incorrect date value: '2024-01-03 garbage /////'
+SELECT EXTRACT(DAY FROM '24-01-03 garbage /////');
+EXTRACT(DAY FROM '24-01-03 garbage /////')
+3
+Warnings:
+Warning 1292 Truncated incorrect date value: '24-01-03 garbage /////'
+SELECT EXTRACT(DAY FROM '01-02-03');
+EXTRACT(DAY FROM '01-02-03')
+3
+SELECT EXTRACT(DAY FROM '24:02:03T');
+EXTRACT(DAY FROM '24:02:03T')
+3
+SELECT EXTRACT(DAY FROM '24-02-03');
+EXTRACT(DAY FROM '24-02-03')
+3
+SELECT EXTRACT(DAY FROM '24/02/03');
+EXTRACT(DAY FROM '24/02/03')
+3
+SELECT EXTRACT(DAY FROM '11111');
+EXTRACT(DAY FROM '11111')
+1
+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('2001/01/01T'), TIME('2001/01/01T ');
+TIME('2001/01/01T') TIME('2001/01/01T ')
+00:00:00 00:00:00
+SELECT TIME('2001:01:01T'), TIME('2001:01:01T ');
+TIME('2001:01:01T') TIME('2001:01:01T ')
+00:00:00 00:00:00
+SELECT EXTRACT(DAY FROM '2001-01-01T'), EXTRACT(DAY FROM '2001-01-01T ');
+EXTRACT(DAY FROM '2001-01-01T') EXTRACT(DAY FROM '2001-01-01T ')
+1 1
+SELECT EXTRACT(DAY FROM '2001/01/01T'), EXTRACT(DAY FROM '2001/01/01T ');
+EXTRACT(DAY FROM '2001/01/01T') EXTRACT(DAY FROM '2001/01/01T ')
+1 1
+SELECT EXTRACT(DAY FROM '2001:01:01T'), EXTRACT(DAY FROM '2001:01:01T ');
+EXTRACT(DAY FROM '2001:01:01T') EXTRACT(DAY FROM '2001:01:01T ')
+1 1
+SELECT TIME('2001:01:01T'), TIME('2001:01:01T ');
+TIME('2001:01:01T') TIME('2001:01:01T ')
+00:00:00 00:00:00
+SELECT EXTRACT(HOUR FROM '2001-01-01T'), EXTRACT(HOUR FROM '2001-01-01T ');
+EXTRACT(HOUR FROM '2001-01-01T') EXTRACT(HOUR FROM '2001-01-01T ')
+0 0
+SELECT EXTRACT(HOUR FROM '2001/01/01T'), EXTRACT(HOUR FROM '2001/01/01T ');
+EXTRACT(HOUR FROM '2001/01/01T') EXTRACT(HOUR FROM '2001/01/01T ')
+0 0
+SELECT EXTRACT(HOUR FROM '2001:01:01T'), EXTRACT(HOUR FROM '2001:01:01T ');
+EXTRACT(HOUR FROM '2001:01:01T') EXTRACT(HOUR FROM '2001:01:01T ')
+0 0
+# This still parses as DATE and returns NULL (without trying TIME)
+SELECT EXTRACT(DAY FROM '100000:02:03T');
+EXTRACT(DAY FROM '100000:02:03T')
+NULL
+Warnings:
+Warning 1292 Incorrect interval value: '100000:02:03T'
+SELECT EXTRACT(DAY FROM '100000/02/03');
+EXTRACT(DAY FROM '100000/02/03')
+NULL
+Warnings:
+Warning 1292 Incorrect interval value: '100000/02/03'
+SELECT EXTRACT(DAY FROM '100000-02-03');
+EXTRACT(DAY FROM '100000-02-03')
+NULL
+Warnings:
+Warning 1292 Incorrect interval value: '100000-02-03'
+SELECT EXTRACT(DAY FROM '1111');
+EXTRACT(DAY FROM '1111')
+NULL
+Warnings:
+Warning 1292 Incorrect interval value: '1111'
+SELECT EXTRACT(DAY FROM '111');
+EXTRACT(DAY FROM '111')
+NULL
+Warnings:
+Warning 1292 Incorrect interval value: '111'
+SELECT EXTRACT(DAY FROM '11');
+EXTRACT(DAY FROM '11')
+NULL
+Warnings:
+Warning 1292 Incorrect interval value: '11'
+SELECT EXTRACT(DAY FROM '1');
+EXTRACT(DAY FROM '1')
+NULL
+Warnings:
+Warning 1292 Incorrect interval value: '1'
+# This still parses as TIME
+SELECT EXTRACT(HOUR FROM '11111');
+EXTRACT(HOUR FROM '11111')
+1
+SELECT EXTRACT(HOUR FROM '1111');
+EXTRACT(HOUR FROM '1111')
+0
+SELECT EXTRACT(HOUR FROM '111');
+EXTRACT(HOUR FROM '111')
+0
+SELECT EXTRACT(HOUR FROM '11');
+EXTRACT(HOUR FROM '11')
+0
+SELECT EXTRACT(HOUR FROM '1');
+EXTRACT(HOUR FROM '1')
+0
+SELECT TIME('01:02:03:');
+TIME('01:02:03:')
+01:02:03
+Warnings:
+Warning 1292 Truncated incorrect time value: '01:02:03:'
+SELECT TIME('01:02:03-');
+TIME('01:02:03-')
+01:02:03
+Warnings:
+Warning 1292 Truncated incorrect time value: '01:02:03-'
+SELECT TIME('01:02:03;');
+TIME('01:02:03;')
+01:02:03
+Warnings:
+Warning 1292 Truncated incorrect time value: '01:02:03;'
+SELECT TIME('01:02:03/');
+TIME('01:02:03/')
+01:02:03
+Warnings:
+Warning 1292 Truncated incorrect time value: '01:02:03/'
+SELECT EXTRACT(HOUR FROM '01:02:03:');
+EXTRACT(HOUR FROM '01:02:03:')
+1
+Warnings:
+Warning 1292 Truncated incorrect time value: '01:02:03:'
+SELECT EXTRACT(HOUR FROM '01:02:03-');
+EXTRACT(HOUR FROM '01:02:03-')
+1
+Warnings:
+Warning 1292 Truncated incorrect time value: '01:02:03-'
+SELECT EXTRACT(HOUR FROM '01:02:03;');
+EXTRACT(HOUR FROM '01:02:03;')
+1
+Warnings:
+Warning 1292 Truncated incorrect time value: '01:02:03;'
+SELECT EXTRACT(HOUR FROM '01:02:03/');
+EXTRACT(HOUR FROM '01:02:03/')
+1
+Warnings:
+Warning 1292 Truncated incorrect time value: '01:02:03/'
+# Backward compatibility preserved for YEAR and MONTH only
+# (behavior has changed for DAY, see below)
+SELECT EXTRACT(YEAR FROM '01:02:03');
+EXTRACT(YEAR FROM '01:02:03')
+2001
+SELECT EXTRACT(MONTH FROM '01:02:03');
+EXTRACT(MONTH FROM '01:02:03')
+2
+SELECT EXTRACT(YEAR FROM '24:01:03 garbage /////');
+EXTRACT(YEAR FROM '24:01:03 garbage /////')
+2024
+Warnings:
+Warning 1292 Truncated incorrect date value: '24:01:03 garbage /////'
+SELECT EXTRACT(MONTH FROM '24:01:03 garbage /////');
+EXTRACT(MONTH FROM '24:01:03 garbage /////')
+1
+Warnings:
+Warning 1292 Truncated incorrect date value: '24:01:03 garbage /////'
+# This still parses as TIME 00:20:01
+SELECT TIME('2001/01/01');
+TIME('2001/01/01')
+00:20:01
+Warnings:
+Warning 1292 Truncated incorrect time value: '2001/01/01'
+SELECT TIME('2001-01-01');
+TIME('2001-01-01')
+00:20:01
+Warnings:
+Warning 1292 Truncated incorrect time value: '2001-01-01'
+# This still parses as TIME and overflows to '838:59:59'
+SELECT TIME('2001:01:01');
+TIME('2001:01:01')
+838:59:59
+Warnings:
+Warning 1292 Truncated incorrect time value: '2001:01:01'
+# This used to parse as DATE, now parses as TIME interval
+CREATE TABLE t1 (a VARCHAR(64));
+INSERT INTO t1 VALUES
+('2024:01:03 garbage /////'),
+('24:01:03 garbage /////'),
+('01:01:03 garbage /////'),
+('2024:02:03'),
+('100000:02:03'),
+('24:02:03'),
+('01:02:03'),
+('01:02:03:'),
+('01:02:03-'),
+('01:02:03;'),
+('01:02:03/'),
+('20 10:20:30');
+SELECT EXTRACT(DAY FROM a), EXTRACT(DAY_SECOND FROM a), a FROM t1;
+EXTRACT(DAY FROM a) EXTRACT(DAY_SECOND FROM a) a
+84 84080103 2024:01:03 garbage /////
+1 1000103 24:01:03 garbage /////
+0 10103 01:01:03 garbage /////
+84 84080203 2024:02:03
+4166 4166160203 100000:02:03
+1 1000203 24:02:03
+0 10203 01:02:03
+0 10203 01:02:03:
+0 10203 01:02:03-
+0 10203 01:02:03;
+0 10203 01:02:03/
+20 20102030 20 10:20:30
+Warnings:
+Warning 1292 Truncated incorrect time value: '2024:01:03 garbage /////'
+Warning 1292 Truncated incorrect time value: '2024:01:03 garbage /////'
+Warning 1292 Truncated incorrect time value: '24:01:03 garbage /////'
+Warning 1292 Truncated incorrect time value: '24:01:03 garbage /////'
+Warning 1292 Truncated incorrect time value: '01:01:03 garbage /////'
+Warning 1292 Truncated incorrect time value: '01:01:03 garbage /////'
+Warning 1292 Truncated incorrect time value: '01:02:03:'
+Warning 1292 Truncated incorrect time value: '01:02:03:'
+Warning 1292 Truncated incorrect time value: '01:02:03-'
+Warning 1292 Truncated incorrect time value: '01:02:03-'
+Warning 1292 Truncated incorrect time value: '01:02:03;'
+Warning 1292 Truncated incorrect time value: '01:02:03;'
+Warning 1292 Truncated incorrect time value: '01:02:03/'
+Warning 1292 Truncated incorrect time value: '01:02:03/'
+DROP TABLE t1;
diff --git a/mysql-test/main/func_extract.test b/mysql-test/main/func_extract.test
new file mode 100644
index 00000000000..f128b6509a2
--- /dev/null
+++ b/mysql-test/main/func_extract.test
@@ -0,0 +1,250 @@
+--echo #
+--echo # MDEV-17385 MICROSECOND() returns confusing results with an out-of-range TIME-alike argument
+--echo #
+
+CREATE TABLE t1 (v VARCHAR(64), ll BIGINT, t TIME, dt DATETIME, d DATE);
+CREATE TABLE t2 AS SELECT
+ EXTRACT(DAY FROM t),
+ EXTRACT(DAY_HOUR FROM t),
+ EXTRACT(DAY_MINUTE FROM t),
+ EXTRACT(DAY_SECOND FROM t),
+ EXTRACT(DAY_MICROSECOND FROM t),
+ EXTRACT(DAY FROM d),
+ EXTRACT(DAY_HOUR FROM d),
+ EXTRACT(DAY_MINUTE FROM d),
+ EXTRACT(DAY_SECOND FROM d),
+ EXTRACT(DAY_MICROSECOND FROM d),
+ EXTRACT(DAY FROM v),
+ EXTRACT(DAY_HOUR FROM v),
+ EXTRACT(DAY_MINUTE FROM v),
+ EXTRACT(DAY_SECOND FROM v),
+ EXTRACT(DAY_MICROSECOND FROM v),
+ EXTRACT(DAY FROM ll),
+ EXTRACT(DAY_HOUR FROM ll),
+ EXTRACT(DAY_MINUTE FROM ll),
+ EXTRACT(DAY_SECOND FROM ll),
+ EXTRACT(DAY_MICROSECOND FROM ll)
+FROM t1;
+SHOW CREATE TABLE t2;
+DROP TABLE t2;
+DROP TABLE t1;
+
+
+CREATE TABLE t1 (a VARCHAR(64), b DECIMAL(32,9));
+INSERT INTO t1 VALUES
+('9999-12-31 23:59:59.123456', 99991231235959.123456),
+('2001-01-01 10:20:30.123456', 20010101102030.123456),
+('4294967296:59:59.123456', 42949672965959.123456),
+('4294967295:59:59.123456', 42949672955959.123456),
+('87649416:59:59.123456', 876494165959.123456),
+('87649415:59:59.123456', 876494155959.123456),
+('87649414:59:59.123456', 876494145959.123456),
+('9999:59:59.123456', 99995959.123456),
+('9999:01:01.123456', 99990101.123456),
+('9999:01:01', 99990101),
+('0.999999', 0.999999),
+('0.99999', 0.99999),
+('0.9999', 0.9999),
+('0.999', 0.999),
+('0.99', 0.99),
+('0.9', 0.9),
+('000000',0);
+
+--echo # Summary:
+--echo # Check that FUNC(varchar) and FUNC(decimal) give equal results
+--echo # Expect empty sets
+--disable_warnings
+SELECT a, b, EXTRACT(DAY_HOUR FROM a), EXTRACT(DAY_HOUR FROM b) FROM t1 WHERE NOT (EXTRACT(DAY_HOUR FROM a)<=>EXTRACT(DAY_HOUR FROM b));
+SELECT a, b, EXTRACT(DAY FROM a), EXTRACT(DAY FROM b) FROM t1 WHERE NOT (EXTRACT(DAY FROM a)<=>EXTRACT(DAY FROM b));
+SELECT a, b, EXTRACT(HOUR FROM a), EXTRACT(HOUR FROM b) FROM t1 WHERE NOT (EXTRACT(HOUR FROM a)<=>EXTRACT(HOUR FROM b));
+SELECT a, b, EXTRACT(MINUTE FROM a), EXTRACT(MINUTE FROM b) FROM t1 WHERE NOT (EXTRACT(MINUTE FROM a)<=>EXTRACT(MINUTE FROM b));
+SELECT a, b, EXTRACT(SECOND FROM a), EXTRACT(SECOND FROM b) FROM t1 WHERE NOT (EXTRACT(SECOND FROM a)<=>EXTRACT(SECOND FROM b));
+SELECT a, b, EXTRACT(MICROSECOND FROM a), EXTRACT(MICROSECOND FROM b) FROM t1 WHERE NOT (EXTRACT(MICROSECOND FROM a)<=>EXTRACT(MICROSECOND FROM b));
+--enable_warnings
+
+--echo # Detailed results
+SELECT
+ a,
+ EXTRACT(DAY FROM a) * 24 + EXTRACT(HOUR FROM a) AS dh,
+ EXTRACT(DAY_HOUR FROM a),
+ EXTRACT(DAY FROM a),
+ EXTRACT(HOUR FROM a),
+ EXTRACT(MINUTE FROM a),
+ EXTRACT(SECOND FROM a),
+ EXTRACT(MICROSECOND FROM a)
+FROM t1;
+SELECT
+ b,
+ EXTRACT(DAY FROM b) * 24 + EXTRACT(HOUR FROM b) AS dh,
+ EXTRACT(DAY_HOUR FROM b),
+ EXTRACT(DAY FROM b),
+ EXTRACT(HOUR FROM b),
+ EXTRACT(MINUTE FROM b),
+ EXTRACT(SECOND FROM b),
+ EXTRACT(MICROSECOND FROM b)
+FROM t1;
+DROP TABLE t1;
+
+--echo # Special case: DAY + TIME
+CREATE TABLE t1 (a VARCHAR(64));
+INSERT INTO t1 VALUES ('9999-01-01');
+SELECT a,
+ EXTRACT(DAY_HOUR FROM a),
+ EXTRACT(DAY_MINUTE FROM a),
+ EXTRACT(DAY_SECOND FROM a),
+ EXTRACT(DAY_MICROSECOND FROM a),
+ EXTRACT(DAY FROM a),
+ EXTRACT(HOUR FROM a),
+ EXTRACT(MINUTE FROM a),
+ EXTRACT(SECOND FROM a),
+ EXTRACT(MICROSECOND FROM a)
+FROM t1;
+DROP TABLE t1;
+
+--echo # Bad values
+CREATE TABLE t1 (a VARCHAR(64));
+INSERT INTO t1 VALUES ('');
+SELECT a,
+ EXTRACT(DAY_HOUR FROM a),
+ EXTRACT(DAY_MINUTE FROM a),
+ EXTRACT(DAY_SECOND FROM a),
+ EXTRACT(DAY_MICROSECOND FROM a),
+ EXTRACT(DAY FROM a),
+ EXTRACT(HOUR FROM a),
+ EXTRACT(MINUTE FROM a),
+ EXTRACT(SECOND FROM a),
+ EXTRACT(MICROSECOND FROM a)
+FROM t1;
+DROP TABLE t1;
+
+
+--echo # Backward compatibility
+
+--echo # This still parses as DATETIME
+SELECT EXTRACT(YEAR FROM '2001/02/03 10:20:30');
+SELECT EXTRACT(MONTH FROM '2001/02/03 10:20:30');
+SELECT EXTRACT(DAY FROM '2001/02/03 10:20:30');
+
+SELECT EXTRACT(YEAR FROM '01/02/03 10:20:30');
+SELECT EXTRACT(MONTH FROM '01/02/03 10:20:30');
+SELECT EXTRACT(DAY FROM '01/02/03 10:20:30');
+
+SELECT EXTRACT(YEAR FROM '01:02:03 10:20:30');
+SELECT EXTRACT(MONTH FROM '01:02:03 10:20:30');
+SELECT EXTRACT(DAY FROM '01:02:03 10:20:30');
+
+--echo # This still parses as DATETIME and returns NULL
+
+SELECT EXTRACT(YEAR FROM "2011-02-32 8:46:06.23434");
+SELECT EXTRACT(MONTH FROM "2011-02-32 8:46:06.23434");
+SELECT EXTRACT(DAY FROM "2011-02-32 8:46:06.23434");
+SELECT EXTRACT(HOUR FROM "2011-02-32 8:46:06.23434");
+
+--echo # This still parses as DATE
+
+SELECT EXTRACT(YEAR FROM '2001/02/03');
+SELECT EXTRACT(MONTH FROM '2001/02/03');
+SELECT EXTRACT(DAY FROM '2001/02/03');
+
+SELECT EXTRACT(YEAR FROM '01/02/03');
+SELECT EXTRACT(MONTH FROM '01/02/03');
+SELECT EXTRACT(DAY FROM '01/02/03');
+
+SELECT EXTRACT(YEAR FROM '01-02-03');
+SELECT EXTRACT(MONTH FROM '01-02-03');
+SELECT EXTRACT(DAY FROM '01-02-03');
+
+SELECT EXTRACT(YEAR FROM '1-2-3');
+SELECT EXTRACT(MONTH FROM '1-2-3');
+SELECT EXTRACT(DAY FROM '1-2-3');
+SELECT EXTRACT(HOUR FROM '1-2-3');
+
+SELECT EXTRACT(DAY FROM '2024-01-03 garbage /////');
+SELECT EXTRACT(DAY FROM '24-01-03 garbage /////');
+SELECT EXTRACT(DAY FROM '01-02-03');
+
+SELECT EXTRACT(DAY FROM '24:02:03T');
+SELECT EXTRACT(DAY FROM '24-02-03');
+SELECT EXTRACT(DAY FROM '24/02/03');
+
+SELECT EXTRACT(DAY FROM '11111');
+
+SELECT TIME('2001-01-01T'), TIME('2001-01-01T ');
+SELECT TIME('2001/01/01T'), TIME('2001/01/01T ');
+SELECT TIME('2001:01:01T'), TIME('2001:01:01T ');
+
+SELECT EXTRACT(DAY FROM '2001-01-01T'), EXTRACT(DAY FROM '2001-01-01T ');
+SELECT EXTRACT(DAY FROM '2001/01/01T'), EXTRACT(DAY FROM '2001/01/01T ');
+SELECT EXTRACT(DAY FROM '2001:01:01T'), EXTRACT(DAY FROM '2001:01:01T ');
+
+
+SELECT TIME('2001:01:01T'), TIME('2001:01:01T ');
+SELECT EXTRACT(HOUR FROM '2001-01-01T'), EXTRACT(HOUR FROM '2001-01-01T ');
+SELECT EXTRACT(HOUR FROM '2001/01/01T'), EXTRACT(HOUR FROM '2001/01/01T ');
+SELECT EXTRACT(HOUR FROM '2001:01:01T'), EXTRACT(HOUR FROM '2001:01:01T ');
+
+--echo # This still parses as DATE and returns NULL (without trying TIME)
+SELECT EXTRACT(DAY FROM '100000:02:03T');
+SELECT EXTRACT(DAY FROM '100000/02/03');
+SELECT EXTRACT(DAY FROM '100000-02-03');
+
+SELECT EXTRACT(DAY FROM '1111');
+SELECT EXTRACT(DAY FROM '111');
+SELECT EXTRACT(DAY FROM '11');
+SELECT EXTRACT(DAY FROM '1');
+
+
+--echo # This still parses as TIME
+
+SELECT EXTRACT(HOUR FROM '11111');
+SELECT EXTRACT(HOUR FROM '1111');
+SELECT EXTRACT(HOUR FROM '111');
+SELECT EXTRACT(HOUR FROM '11');
+SELECT EXTRACT(HOUR FROM '1');
+
+SELECT TIME('01:02:03:');
+SELECT TIME('01:02:03-');
+SELECT TIME('01:02:03;');
+SELECT TIME('01:02:03/');
+
+SELECT EXTRACT(HOUR FROM '01:02:03:');
+SELECT EXTRACT(HOUR FROM '01:02:03-');
+SELECT EXTRACT(HOUR FROM '01:02:03;');
+SELECT EXTRACT(HOUR FROM '01:02:03/');
+
+--echo # Backward compatibility preserved for YEAR and MONTH only
+--echo # (behavior has changed for DAY, see below)
+SELECT EXTRACT(YEAR FROM '01:02:03');
+SELECT EXTRACT(MONTH FROM '01:02:03');
+
+SELECT EXTRACT(YEAR FROM '24:01:03 garbage /////');
+SELECT EXTRACT(MONTH FROM '24:01:03 garbage /////');
+
+--echo # This still parses as TIME 00:20:01
+
+SELECT TIME('2001/01/01');
+SELECT TIME('2001-01-01');
+
+--echo # This still parses as TIME and overflows to '838:59:59'
+SELECT TIME('2001:01:01');
+
+
+--echo # This used to parse as DATE, now parses as TIME interval
+
+CREATE TABLE t1 (a VARCHAR(64));
+INSERT INTO t1 VALUES
+('2024:01:03 garbage /////'),
+('24:01:03 garbage /////'),
+('01:01:03 garbage /////'),
+('2024:02:03'),
+('100000:02:03'),
+('24:02:03'),
+('01:02:03'),
+('01:02:03:'),
+('01:02:03-'),
+('01:02:03;'),
+('01:02:03/'),
+('20 10:20:30');
+
+SELECT EXTRACT(DAY FROM a), EXTRACT(DAY_SECOND FROM a), a FROM t1;
+DROP TABLE t1;
diff --git a/mysql-test/main/func_time.result b/mysql-test/main/func_time.result
index a6e1ed9cb19..2315f5cd645 100644
--- a/mysql-test/main/func_time.result
+++ b/mysql-test/main/func_time.result
@@ -367,9 +367,7 @@ extract(DAY_MINUTE FROM "02 10:11:12")
21011
select extract(DAY_SECOND FROM "225 10:11:12");
extract(DAY_SECOND FROM "225 10:11:12")
-34225959
-Warnings:
-Warning 1292 Truncated incorrect time value: '225 10:11:12'
+225101112
select extract(HOUR FROM "1999-01-02 10:11:12");
extract(HOUR FROM "1999-01-02 10:11:12")
10
@@ -1033,9 +1031,7 @@ Note 1105 Cast to unsigned converted negative integer to it's positive complemen
Warning 1292 Truncated incorrect time value: '18446744073709551615:00:00'
SELECT EXTRACT(HOUR FROM '10000:02:03');
EXTRACT(HOUR FROM '10000:02:03')
-22
-Warnings:
-Warning 1292 Truncated incorrect time value: '10000:02:03'
+16
CREATE TABLE t1(f1 TIME);
INSERT IGNORE INTO t1 VALUES('916:00:00 a');
Warnings:
diff --git a/mysql-test/main/str_to_datetime_457.result b/mysql-test/main/str_to_datetime_457.result
index a024b30944c..e365c303c81 100644
--- a/mysql-test/main/str_to_datetime_457.result
+++ b/mysql-test/main/str_to_datetime_457.result
@@ -17,10 +17,7 @@ Warnings:
Warning 1292 Incorrect datetime value: '0'
select extract(hour from '100000:02:03'), extract(hour from '100000:02:03 ');
extract(hour from '100000:02:03') extract(hour from '100000:02:03 ')
-NULL NULL
-Warnings:
-Warning 1292 Incorrect time value: '100000:02:03'
-Warning 1292 Incorrect time value: '100000:02:03 '
+16 16
#
# backward compatibility craziness
#
diff --git a/sql-common/my_time.c b/sql-common/my_time.c
index e0c894023f6..cc4249074e0 100644
--- a/sql-common/my_time.c
+++ b/sql-common/my_time.c
@@ -262,12 +262,25 @@ static void get_microseconds(ulong *val, MYSQL_TIME_STATUS *status,
static int check_time_range_internal(MYSQL_TIME *ltime,
- ulong max_hour, uint dec,
- int *warning);
+ ulong max_hour, ulong err_hour,
+ uint dec, int *warning);
int check_time_range(MYSQL_TIME *ltime, uint dec, int *warning)
{
- return check_time_range_internal(ltime, TIME_MAX_HOUR, dec, warning);
+ return check_time_range_internal(ltime, TIME_MAX_HOUR, UINT_MAX32,
+ dec, warning);
+}
+
+
+static my_bool
+set_neg(my_bool neg, MYSQL_TIME_STATUS *st, MYSQL_TIME *ltime)
+{
+ if ((ltime->neg= neg) && ltime->time_type != MYSQL_TIMESTAMP_TIME)
+ {
+ st->warnings|= MYSQL_TIME_WARN_OUT_OF_RANGE;
+ return TRUE;
+ }
+ return FALSE;
}
@@ -351,20 +364,51 @@ static my_bool find_body(my_bool *neg, const char *str, size_t length,
}
+typedef struct
+{
+ uint count_punct;
+ uint count_colon;
+ uint count_iso_date_time_separator;
+} MYSQL_TIME_USED_CHAR_STATISTICS;
+
+
+static void
+mysql_time_used_char_statistics_init(MYSQL_TIME_USED_CHAR_STATISTICS *to,
+ const char *str, const char *end)
+{
+ const char *s;
+ bzero((void *) to, sizeof(MYSQL_TIME_USED_CHAR_STATISTICS));
+ for (s= str; s < end; s++)
+ {
+ if (my_ispunct(&my_charset_latin1, *s))
+ to->count_punct++;
+ if (*s == ':')
+ to->count_colon++;
+ if (*s == 'T')
+ to->count_iso_date_time_separator++;
+ }
+}
+
+
static my_bool
-is_datetime_body_candidate(const char *str, size_t length)
+is_datetime_body_candidate(const char *str, size_t length,
+ my_bool allow_dates_delimited,
+ my_bool allow_dates_numeric)
{
static uint min_date_length= 5; /* '1-1-1' -> '0001-01-01' */
- uint pos;
+ uint pos, count_punct= 0;
+ uint date_time_separator_length= MY_TEST(!allow_dates_delimited);
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'.
+ To make a date it should be just not less that 5 characters.
*/
- if (length < min_date_length + 1/* DATE/TIME separator */)
+ if (length < min_date_length + date_time_separator_length &&
+ !allow_dates_numeric)
return FALSE;
- for (pos= min_date_length; pos < length; pos++)
+ for (pos= 0; pos < length; pos++)
{
if (str[pos] == 'T') /* Date/time separator */
return TRUE;
@@ -381,21 +425,23 @@ is_datetime_body_candidate(const char *str, size_t length)
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 count_punct > 0; /* Can be a DATE if already had separators*/
+ }
+ if (my_ispunct(&my_charset_latin1, str[pos]))
+ {
+ if (allow_dates_delimited && str[pos] != ':')
+ return TRUE;
+ count_punct++;
}
}
- return FALSE;
+ return allow_dates_numeric && count_punct == 0;
}
static my_bool
str_to_DDhhmmssff_internal(my_bool neg, const char *str, size_t length,
- MYSQL_TIME *l_time, ulong max_hour,
+ MYSQL_TIME *l_time,
+ ulong max_hour, ulong err_hour,
MYSQL_TIME_STATUS *status,
const char **endptr);
@@ -452,14 +498,18 @@ static my_bool
str_to_datetime_or_date_body(const char *str, size_t length, MYSQL_TIME *l_time,
ulonglong flags,
my_bool punct_is_date_time_separator,
- MYSQL_TIME_STATUS *status)
+ MYSQL_TIME_STATUS *status,
+ uint *number_of_fields,
+ const char **endptr)
{
const char *end=str+length, *pos;
- uint number_of_fields= 0, digits, year_length, not_zero_date;
+ uint digits, year_length, not_zero_date;
int warn= 0;
DBUG_ENTER("str_to_datetime_or_date_body");
DBUG_ASSERT(C_FLAGS_OK(flags));
bzero(l_time, sizeof(*l_time));
+ *number_of_fields= 0;
+ *endptr= str;
/*
Calculate number of digits in first part.
@@ -487,40 +537,41 @@ str_to_datetime_or_date_body(const char *str, size_t length, MYSQL_TIME *l_time,
(only numbers like [YY]YYMMDD[T][hhmmss[.uuuuuu]])
*/
year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
- if (get_digits(&l_time->year, &number_of_fields, &str, end, year_length)
- || get_digits(&l_time->month, &number_of_fields, &str, end, 2)
- || get_digits(&l_time->day, &number_of_fields, &str, end, 2)
+ if (get_digits(&l_time->year, number_of_fields, &str, end, year_length)
+ || get_digits(&l_time->month, number_of_fields, &str, end, 2)
+ || get_digits(&l_time->day, number_of_fields, &str, end, 2)
|| get_maybe_T(&str, end)
- || get_digits(&l_time->hour, &number_of_fields, &str, end, 2)
- || get_digits(&l_time->minute, &number_of_fields, &str, end, 2)
- || get_digits(&l_time->second, &number_of_fields, &str, end, 2))
+ || get_digits(&l_time->hour, number_of_fields, &str, end, 2)
+ || get_digits(&l_time->minute, number_of_fields, &str, end, 2)
+ || get_digits(&l_time->second, number_of_fields, &str, end, 2))
warn|= MYSQL_TIME_WARN_TRUNCATED;
}
else
{
const char *start= str;
- if (get_number(&l_time->year, &number_of_fields, &str, end))
+ if (get_number(&l_time->year, number_of_fields, &str, end))
warn|= MYSQL_TIME_WARN_TRUNCATED;
year_length= (uint)(str - start);
if (!warn &&
(get_punct(&str, end)
- || get_number(&l_time->month, &number_of_fields, &str, end)
+ || get_number(&l_time->month, number_of_fields, &str, end)
|| get_punct(&str, end)
- || get_number(&l_time->day, &number_of_fields, &str, end)
- || get_date_time_separator(&number_of_fields,
+ || get_number(&l_time->day, number_of_fields, &str, end)
+ || get_date_time_separator(number_of_fields,
punct_is_date_time_separator, &str, end)
- || get_number(&l_time->hour, &number_of_fields, &str, end)
+ || get_number(&l_time->hour, number_of_fields, &str, end)
|| get_punct(&str, end)
- || get_number(&l_time->minute, &number_of_fields, &str, end)
+ || get_number(&l_time->minute, number_of_fields, &str, end)
|| get_punct(&str, end)
- || get_number(&l_time->second, &number_of_fields, &str, end)))
+ || get_number(&l_time->second, number_of_fields, &str, end)))
warn|= MYSQL_TIME_WARN_TRUNCATED;
}
status->warnings|= warn;
+ *endptr= str;
/* we're ok if date part is correct. even if the rest is truncated */
- if (number_of_fields < 3)
+ if (*number_of_fields < 3)
{
l_time->time_type= MYSQL_TIMESTAMP_NONE;
status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
@@ -531,7 +582,8 @@ str_to_datetime_or_date_body(const char *str, size_t length, MYSQL_TIME *l_time,
{
str++;
get_microseconds(&l_time->second_part, status,
- &number_of_fields, &str, end);
+ number_of_fields, &str, end);
+ *endptr= str;
}
not_zero_date = l_time->year || l_time->month || l_time->day ||
@@ -551,7 +603,7 @@ str_to_datetime_or_date_body(const char *str, size_t length, MYSQL_TIME *l_time,
if (check_date(l_time, not_zero_date, flags, &status->warnings))
goto err;
- l_time->time_type= (number_of_fields <= 3 ?
+ l_time->time_type= (*number_of_fields <= 3 ?
MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME);
if (str != end)
@@ -594,27 +646,104 @@ err:
TRUE on error
*/
-static
-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)
+static 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,
+ ulong time_max_hour,
+ ulong time_err_hour,
+ my_bool allow_dates_delimited,
+ my_bool allow_dates_numeric)
{
const char *endptr;
DBUG_ASSERT(C_FLAGS_OK(fuzzydate));
/* Check first if this is a full TIMESTAMP */
- if (is_datetime_body_candidate(str, length))
+ if (is_datetime_body_candidate(str, length,
+ allow_dates_delimited,
+ allow_dates_numeric))
{ /* Probably full timestamp */
int warn_copy= status->warnings; /* could already be set by find_body() */
- (void) str_to_datetime_or_date_body(str, length, l_time,
- fuzzydate, FALSE, status);
- if (l_time->time_type >= MYSQL_TIMESTAMP_ERROR)
- return l_time->time_type == MYSQL_TIMESTAMP_ERROR;
+ uint number_of_fields;
+ (void) str_to_datetime_or_date_body(str, length, l_time, fuzzydate,
+ FALSE, status,
+ &number_of_fields, &endptr);
+ DBUG_ASSERT(endptr >= str);
+ DBUG_ASSERT(endptr <= str + length);
+ switch (l_time->time_type) {
+ case MYSQL_TIMESTAMP_DATETIME:
+ return FALSE;
+ case MYSQL_TIMESTAMP_DATE:
+ {
+ /*
+ Successfully parsed as DATE, but it can also be a TIME:
+ '24:02:03' - continue and parse as TIME
+ '24:02:03 garbage /////' - continue and parse as TIME
+ '24:02:03T' - return DATE
+ '24-02-03' - return DATE
+ '24/02/03' - return DATE
+ '11111' - return DATE
+ */
+ MYSQL_TIME_USED_CHAR_STATISTICS used_chars;
+ mysql_time_used_char_statistics_init(&used_chars, str, endptr);
+ if (used_chars.count_iso_date_time_separator || !used_chars.count_colon)
+ return FALSE;
+ }
+ break;
+ case MYSQL_TIMESTAMP_ERROR:
+ {
+ MYSQL_TIME_USED_CHAR_STATISTICS used_chars;
+ /*
+ Check if it parsed as DATETIME but then failed as out of range:
+ '2011-02-32 8:46:06.23434' - return error
+ */
+ if (number_of_fields > 3)
+ return TRUE;
+ /*
+ Check if it parsed as DATE but then failed as out of range:
+ '100000:02:03' - continue and parse as TIME
+ '100000:02:03T' - return error
+ '100000/02/03' - return error
+ '100000-02-03' - return error
+ */
+ mysql_time_used_char_statistics_init(&used_chars, str, endptr);
+ if (used_chars.count_iso_date_time_separator || !used_chars.count_colon)
+ return TRUE;
+ }
+ break;
+ case MYSQL_TIMESTAMP_NONE:
+ {
+ if (allow_dates_numeric && endptr >= str + length)
+ {
+ /*
+ For backward compatibility this parses as DATE and fails:
+ EXTRACT(DAY FROM '1111') -- return error
+ EXTRACT(DAY FROM '1') -- return error
+ */
+ MYSQL_TIME_USED_CHAR_STATISTICS used_chars;
+ mysql_time_used_char_statistics_init(&used_chars, str, endptr);
+ if (!used_chars.count_iso_date_time_separator &&
+ !used_chars.count_colon &&
+ !used_chars.count_punct)
+ return TRUE;
+ }
+ /*
+ - '256 10:30:30' - continue and parse as TIME
+ - '4294967296:59:59.123456456' - continue and parse as TIME
+ */
+ }
+ break;
+ case MYSQL_TIMESTAMP_TIME:
+ DBUG_ASSERT(0);
+ break;
+ }
+ my_time_status_init(status);
status->warnings= warn_copy;
}
- if (!str_to_DDhhmmssff_internal(FALSE, str, length, l_time, TIME_MAX_HOUR,
+ if (!str_to_DDhhmmssff_internal(FALSE, str, length, l_time,
+ time_max_hour, time_err_hour,
status, &endptr))
return FALSE;
@@ -640,10 +769,11 @@ my_bool str_to_DDhhmmssff(const char *str, size_t length, MYSQL_TIME *ltime,
return TRUE;
/* Reject anything that might be parsed as a full TIMESTAMP */
- if (is_datetime_body_candidate(str, length))
+ if (is_datetime_body_candidate(str, length, FALSE, FALSE))
{
- (void) str_to_datetime_or_date_body(str, length, ltime,
- 0, FALSE, status);
+ uint number_of_fields;
+ (void) str_to_datetime_or_date_body(str, length, ltime, 0, FALSE,
+ status, &number_of_fields, &endptr);
if (ltime->time_type > MYSQL_TIMESTAMP_ERROR)
{
status->warnings|= MYSQL_TIME_WARN_TRUNCATED;
@@ -659,7 +789,7 @@ my_bool str_to_DDhhmmssff(const char *str, size_t length, MYSQL_TIME *ltime,
will scan only '2001'.
*/
if (str_to_DDhhmmssff_internal(neg, str, length, ltime, max_hour,
- status, &endptr) ||
+ UINT_MAX32, status, &endptr) ||
(endptr < str + length && endptr[0] == '-'))
return TRUE;
return FALSE;
@@ -668,29 +798,63 @@ my_bool str_to_DDhhmmssff(const char *str, size_t length, MYSQL_TIME *ltime,
my_bool
str_to_datetime_or_date_or_time(const char *str, size_t length,
- MYSQL_TIME *l_time,
- ulonglong fuzzydate,
- MYSQL_TIME_STATUS *status)
+ MYSQL_TIME *to, ulonglong mode,
+ MYSQL_TIME_STATUS *status,
+ ulong time_max_hour,
+ ulong time_err_hour)
{
- my_bool neg, rc;
- DBUG_ASSERT(C_FLAGS_OK(fuzzydate));
+ my_bool neg;
+ DBUG_ASSERT(C_FLAGS_OK(mode));
+ my_time_status_init(status);
+ return
+ find_body(&neg, str, length, to, &status->warnings, &str, &length) ||
+ str_to_datetime_or_date_or_time_body(str, length, to, mode, status,
+ time_max_hour, time_err_hour,
+ FALSE, FALSE) ||
+ set_neg(neg, status, to);
+}
+
+
+my_bool
+str_to_datetime_or_date_or_interval_hhmmssff(const char *str, size_t length,
+ MYSQL_TIME *to, ulonglong mode,
+ MYSQL_TIME_STATUS *status,
+ ulong time_max_hour,
+ ulong time_err_hour)
+{
+ my_bool neg;
+ DBUG_ASSERT(C_FLAGS_OK(mode));
+ my_time_status_init(status);
+ return
+ find_body(&neg, str, length, to, &status->warnings, &str, &length) ||
+ str_to_datetime_or_date_or_time_body(str, length, to, mode, status,
+ time_max_hour, time_err_hour,
+ TRUE, FALSE) ||
+ set_neg(neg, status, to);
+}
+
+
+my_bool
+str_to_datetime_or_date_or_interval_day(const char *str, size_t length,
+ MYSQL_TIME *to, ulonglong mode,
+ MYSQL_TIME_STATUS *status,
+ ulong time_max_hour,
+ ulong time_err_hour)
+{
+ my_bool neg;
+ DBUG_ASSERT(C_FLAGS_OK(mode));
my_time_status_init(status);
- if (find_body(&neg, str, length, l_time, &status->warnings, &str, &length))
- return TRUE;
/*
- QQ: Perhaps we should modify xxx_body() to return endptr.
- If endptr points to '-', return an error.
+ For backward compatibility we allow to parse non-delimited
+ values as DATE rather than as TIME:
+ EXTRACT(DAY FROM '11111')
*/
- rc= str_to_datetime_or_date_or_time_body(str, length, l_time,
- fuzzydate, status);
- if (rc)
- return rc;
- if ((l_time->neg= neg) && l_time->time_type != MYSQL_TIMESTAMP_TIME)
- {
- status->warnings|= MYSQL_TIME_WARN_OUT_OF_RANGE;
- return TRUE;
- }
- return FALSE;
+ return
+ find_body(&neg, str, length, to, &status->warnings, &str, &length) ||
+ str_to_datetime_or_date_or_time_body(str, length, to, mode, status,
+ time_max_hour, time_err_hour,
+ TRUE, TRUE) ||
+ set_neg(neg, status, to);
}
@@ -698,20 +862,16 @@ my_bool
str_to_datetime_or_date(const char *str, size_t length, MYSQL_TIME *l_time,
ulonglong flags, MYSQL_TIME_STATUS *status)
{
- my_bool neg, rc;
+ my_bool neg;
+ uint number_of_fields;
+ const char *endptr;
DBUG_ASSERT(C_FLAGS_OK(flags));
my_time_status_init(status);
- if (find_body(&neg, str, length, l_time, &status->warnings, &str, &length))
- return TRUE;
- rc= str_to_datetime_or_date_body(str, length, l_time, flags, TRUE, status);
- if (rc)
- return rc;
- if ((l_time->neg= neg))
- {
- status->warnings|= MYSQL_TIME_WARN_OUT_OF_RANGE;
- return TRUE;
- }
- return FALSE;
+ return
+ find_body(&neg, str, length, l_time, &status->warnings, &str, &length) ||
+ str_to_datetime_or_date_body(str, length, l_time, flags, TRUE,
+ status, &number_of_fields, &endptr) ||
+ set_neg(neg, status, l_time);
}
@@ -738,7 +898,8 @@ str_to_datetime_or_date(const char *str, size_t length, MYSQL_TIME *l_time,
*/
static my_bool
str_to_DDhhmmssff_internal(my_bool neg, const char *str, size_t length,
- MYSQL_TIME *l_time, ulong max_hour,
+ MYSQL_TIME *l_time,
+ ulong max_hour, ulong err_hour,
MYSQL_TIME_STATUS *status, const char **endptr)
{
ulong date[5];
@@ -891,7 +1052,8 @@ fractional:
*endptr= str;
/* Check if the value is valid and fits into MYSQL_TIME range */
- if (check_time_range_internal(l_time, max_hour, 6, &status->warnings))
+ if (check_time_range_internal(l_time, max_hour, err_hour,
+ 6, &status->warnings))
return TRUE;
/* Check if there is garbage at end of the MYSQL_TIME specification */
@@ -911,7 +1073,9 @@ err:
SYNOPSIS:
check_time_range_internal()
time pointer to MYSQL_TIME value
- ulong max_hour - maximum allowed hour value
+ ulong max_hour - maximum allowed hour value. if the hour is greater,
+ cut the time value to 'max_hour:59:59.999999'
+ ulong err_hour - if hour is greater than this value, return an error
uint dec
warning set MYSQL_TIME_WARN_OUT_OF_RANGE flag if the value is out of range
@@ -926,13 +1090,15 @@ err:
*/
int check_time_range_internal(struct st_mysql_time *my_time,
- ulong max_hour, uint dec, int *warning)
+ ulong max_hour, ulong err_hour,
+ uint dec, int *warning)
{
ulonglong hour;
static ulong max_sec_part[TIME_SECOND_PART_DIGITS+1]= {000000, 900000, 990000,
999000, 999900, 999990, 999999};
- if (my_time->minute >= 60 || my_time->second >= 60)
+ if (my_time->minute >= 60 || my_time->second >= 60 ||
+ my_time->hour > err_hour)
{
*warning|= MYSQL_TIME_WARN_TRUNCATED;
return 1;
@@ -1581,17 +1747,19 @@ longlong number_to_datetime_or_date(longlong nr, ulong sec_part,
-1 time value is invalid
*/
int number_to_time_only(my_bool neg, ulonglong nr, ulong sec_part,
- MYSQL_TIME *ltime, int *was_cut)
+ ulong max_hour, MYSQL_TIME *ltime, int *was_cut)
{
+ static const ulonglong TIME_MAX_mmss= TIME_MAX_MINUTE*100 + TIME_MAX_SECOND;
+ ulonglong time_max_value= max_hour * 10000ULL + TIME_MAX_mmss;
*was_cut= 0;
ltime->year= ltime->month= ltime->day= 0;
ltime->time_type= MYSQL_TIMESTAMP_TIME;
ltime->neg= neg;
- if (nr > TIME_MAX_VALUE)
+ if (nr > time_max_value)
{
- nr= TIME_MAX_VALUE;
+ nr= time_max_value;
sec_part= TIME_MAX_SECOND_PART;
*was_cut= MYSQL_TIME_WARN_OUT_OF_RANGE;
}
diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc
index 2da92f971d1..41a5798c3b5 100644
--- a/sql/item_timefunc.cc
+++ b/sql/item_timefunc.cc
@@ -2092,16 +2092,18 @@ void Item_extract::print(String *str, enum_query_type query_type)
bool Item_extract::fix_length_and_dec()
{
maybe_null=1; // If wrong date
+ uint32 daylen= args[0]->cmp_type() == TIME_RESULT ? 2 :
+ TIME_MAX_INTERVAL_DAY_CHAR_LENGTH;
switch (int_type) {
case INTERVAL_YEAR: set_date_length(4); break; // YYYY
case INTERVAL_YEAR_MONTH: set_date_length(6); break; // YYYYMM
case INTERVAL_QUARTER: set_date_length(2); break; // 1..4
case INTERVAL_MONTH: set_date_length(2); break; // MM
case INTERVAL_WEEK: set_date_length(2); break; // 0..52
- case INTERVAL_DAY: set_day_length(2); break; // DD
- case INTERVAL_DAY_HOUR: set_time_length(4); break; // DDhh
- case INTERVAL_DAY_MINUTE: set_time_length(6); break; // DDhhmm
- case INTERVAL_DAY_SECOND: set_time_length(8); break; // DDhhmmss
+ case INTERVAL_DAY: set_day_length(daylen); break; // DD
+ case INTERVAL_DAY_HOUR: set_day_length(daylen+2); break; // DDhh
+ case INTERVAL_DAY_MINUTE: set_day_length(daylen+4); break; // DDhhmm
+ case INTERVAL_DAY_SECOND: set_day_length(daylen+6); break; // DDhhmmss
case INTERVAL_HOUR: set_time_length(2); break; // hh
case INTERVAL_HOUR_MINUTE: set_time_length(4); break; // hhmm
case INTERVAL_HOUR_SECOND: set_time_length(6); break; // hhmmss
@@ -2109,7 +2111,7 @@ bool Item_extract::fix_length_and_dec()
case INTERVAL_MINUTE_SECOND: set_time_length(4); break; // mmss
case INTERVAL_SECOND: set_time_length(2); break; // ss
case INTERVAL_MICROSECOND: set_time_length(6); break; // ffffff
- case INTERVAL_DAY_MICROSECOND: set_time_length(14); break; // DDhhmmssffffff
+ case INTERVAL_DAY_MICROSECOND: set_time_length(daylen+12); break; // DDhhmmssffffff
case INTERVAL_HOUR_MICROSECOND: set_time_length(12); break; // hhmmssffffff
case INTERVAL_MINUTE_MICROSECOND: set_time_length(10); break; // mmssffffff
case INTERVAL_SECOND_MICROSECOND: set_time_length(8); break; // ssffffff
diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h
index d2a4e87a3d1..45f10659a76 100644
--- a/sql/item_timefunc.h
+++ b/sql/item_timefunc.h
@@ -993,12 +993,12 @@ class Item_extract :public Item_int_func,
EXTRACT(DAY FROM '-24:00:00') -> -1
*/
set_handler(handler_by_length(max_length= length + 1/*sign*/, 11));
- m_date_mode= date_mode_t(0);
+ m_date_mode= TIME_INTERVAL_DAY;
}
void set_time_length(uint32 length)
{
set_handler(handler_by_length(max_length= length + 1/*sign*/, 11));
- m_date_mode= TIME_TIME_ONLY;
+ m_date_mode= TIME_INTERVAL_hhmmssff;
}
public:
const interval_type int_type; // keep it public
diff --git a/sql/sql_basic_types.h b/sql/sql_basic_types.h
index 84bd165a23f..5062817f911 100644
--- a/sql/sql_basic_types.h
+++ b/sql/sql_basic_types.h
@@ -37,11 +37,13 @@ public:
FUZZY_DATES is used for the result will only be used for comparison
purposes. Conversion is as relaxed as possible.
*/
- FUZZY_DATES= 1U,
- TIME_ONLY= 4U,
- NO_ZERO_IN_DATE= (1UL << 23), // MODE_NO_ZERO_IN_DATE
- NO_ZERO_DATE= (1UL << 24), // MODE_NO_ZERO_DATE
- INVALID_DATES= (1UL << 25) // MODE_INVALID_DATES
+ FUZZY_DATES= 1U,
+ TIME_ONLY= 4U,
+ INTERVAL_hhmmssff= 8U,
+ INTERVAL_DAY= 16U,
+ NO_ZERO_IN_DATE= (1UL << 23), // MODE_NO_ZERO_IN_DATE
+ NO_ZERO_DATE= (1UL << 24), // MODE_NO_ZERO_DATE
+ INVALID_DATES= (1UL << 25) // MODE_INVALID_DATES
};
private:
@@ -98,6 +100,8 @@ public:
const date_mode_t
TIME_FUZZY_DATES (date_mode_t::value_t::FUZZY_DATES),
TIME_TIME_ONLY (date_mode_t::value_t::TIME_ONLY),
+ TIME_INTERVAL_hhmmssff (date_mode_t::value_t::INTERVAL_hhmmssff),
+ TIME_INTERVAL_DAY (date_mode_t::value_t::INTERVAL_DAY),
TIME_NO_ZERO_IN_DATE (date_mode_t::value_t::NO_ZERO_IN_DATE),
TIME_NO_ZERO_DATE (date_mode_t::value_t::NO_ZERO_DATE),
TIME_INVALID_DATES (date_mode_t::value_t::INVALID_DATES);
diff --git a/sql/sql_time.cc b/sql/sql_time.cc
index ff02bfa0ae4..35a4e1e47c3 100644
--- a/sql/sql_time.cc
+++ b/sql/sql_time.cc
@@ -371,18 +371,14 @@ public:
};
-/* Character set-aware version of str_to_datetime_or_date_or_time() */
-bool Temporal::str_to_datetime_or_date_or_time(MYSQL_TIME_STATUS *status,
+/* Character set-aware version of ascii_to_datetime_or_date_or_time() */
+bool Temporal::str_to_datetime_or_date_or_time(MYSQL_TIME_STATUS *st,
const char *str, size_t length,
CHARSET_INFO *cs,
date_mode_t fuzzydate)
{
TemporalAsciiBuffer tmp(str, length, cs);
- bool rc= ::str_to_datetime_or_date_or_time(tmp.str, tmp.length, this,
- ulonglong(fuzzydate & TIME_MODE_FOR_XXX_TO_DATE),
- status);
- DBUG_ASSERT(status->warnings || !rc);
- return rc;
+ return ascii_to_datetime_or_date_or_time(st, tmp.str, tmp.length, fuzzydate);
}
@@ -393,11 +389,17 @@ bool Temporal::str_to_datetime_or_date(MYSQL_TIME_STATUS *status,
date_mode_t flags)
{
TemporalAsciiBuffer tmp(str, length, cs);
- bool rc= ::str_to_datetime_or_date(tmp.str, tmp.length, this,
- ulonglong(flags & TIME_MODE_FOR_XXX_TO_DATE),
- status);
- DBUG_ASSERT(status->warnings || !rc);
- return rc;
+ return ascii_to_datetime_or_date(status, tmp.str, tmp.length, flags);
+}
+
+
+/* Character set-aware version of ascii_to_temporal() */
+bool Temporal::str_to_temporal(MYSQL_TIME_STATUS *status,
+ const char *str, size_t length, CHARSET_INFO *cs,
+ date_mode_t flags)
+{
+ TemporalAsciiBuffer tmp(str, length, cs);
+ return ascii_to_temporal(status, tmp.str, tmp.length, flags);
}
diff --git a/sql/sql_type.cc b/sql/sql_type.cc
index 6fa4684884d..31b25853514 100644
--- a/sql/sql_type.cc
+++ b/sql/sql_type.cc
@@ -181,9 +181,7 @@ void Temporal::make_from_str(THD *thd, Warn *warn,
push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_YES, ErrConvString(str, length,cs).ptr()););
- if (fuzzydate & TIME_TIME_ONLY ?
- str_to_datetime_or_date_or_time(warn, str, length, cs, fuzzydate) :
- str_to_datetime_or_date(warn, str, length, cs, fuzzydate))
+ if (str_to_temporal(warn, str, length, cs, fuzzydate))
make_fuzzy_date(&warn->warnings, fuzzydate);
if (warn->warnings)
warn->set_str(str, length, &my_charset_bin);
@@ -232,8 +230,11 @@ void Sec6::make_truncated_warning(THD *thd, const char *type_str) const
bool Sec6::convert_to_mysql_time(THD *thd, int *warn, MYSQL_TIME *ltime,
date_mode_t fuzzydate) const
{
- bool is_time= bool(fuzzydate & TIME_TIME_ONLY);
- bool rc= is_time ? to_time(ltime, warn) : to_datetime(ltime, fuzzydate, warn);
+ bool rc= fuzzydate & (TIME_INTERVAL_hhmmssff | TIME_INTERVAL_DAY) ?
+ to_datetime_or_to_interval_hhmmssff(ltime, warn) :
+ fuzzydate & TIME_TIME_ONLY ?
+ to_datetime_or_time(ltime, warn, fuzzydate) :
+ to_datetime_or_date(ltime, warn, fuzzydate);
DBUG_ASSERT(*warn || !rc);
if (truncated())
*warn|= MYSQL_TIME_WARN_TRUNCATED;
diff --git a/sql/sql_type.h b/sql/sql_type.h
index 2080152e538..3aeb2ad2788 100644
--- a/sql/sql_type.h
+++ b/sql/sql_type.h
@@ -264,21 +264,59 @@ public:
MYSQL_TIME *ltime,
date_mode_t fuzzydate) const;
- // Convert a number in format hhhmmss.ff to TIME'hhh:mm:ss.ff'
- bool to_time(MYSQL_TIME *to, int *warn) const
+protected:
+
+ bool to_interval_hhmmssff_only(MYSQL_TIME *to, int *warn) const
+ {
+ return number_to_time_only(m_neg, m_sec, m_usec,
+ TIME_MAX_INTERVAL_HOUR, to, warn);
+ }
+ bool to_datetime_or_to_interval_hhmmssff(MYSQL_TIME *to, int *warn) const
{
- bool rc= (m_sec > 9999999 && m_sec <= 99991231235959ULL && !neg()) ?
- number_to_datetime_or_date(m_sec, m_usec, to,
- C_TIME_INVALID_DATES, warn) < 0 :
- number_to_time_only(m_neg, m_sec, m_usec, to, warn);
+ /*
+ Convert a number to a time interval.
+ The following formats are understood:
+ - 0 <= x <= 999999995959 - parse as hhhhmmss
+ - 999999995959 < x <= 99991231235959 - parse as YYYYMMDDhhmmss
+ (YYMMDDhhmmss) (YYYYMMDDhhmmss)
+
+ Note, these formats are NOT understood:
+ - YYMMDD - overlaps with INTERVAL range
+ - YYYYMMDD - overlaps with INTERVAL range
+ - YYMMDDhhmmss - overlaps with INTERVAL range, partially
+ (see TIME_MAX_INTERVAL_HOUR)
+
+ If we ever need wider intervals, this code switching between
+ full datetime and interval-only should be rewised.
+ */
+ DBUG_ASSERT(TIME_MAX_INTERVAL_HOUR <= 999999995959);
+ /* (YYMMDDhhmmss) */
+ if (m_sec > 999999995959ULL &&
+ m_sec <= 99991231235959ULL && m_neg == 0)
+ return to_datetime_or_date(to, warn, TIME_INVALID_DATES);
+ if (m_sec / 10000 > TIME_MAX_INTERVAL_HOUR)
+ {
+ *warn= MYSQL_TIME_WARN_OUT_OF_RANGE;
+ return true;
+ }
+ return to_interval_hhmmssff_only(to, warn);
+ }
+public:
+ // [-][DD]hhhmmss.ff, YYMMDDhhmmss.ff, YYYYMMDDhhmmss.ff
+ bool to_datetime_or_time(MYSQL_TIME *to, int *warn, date_mode_t mode) const
+ {
+ bool rc= m_sec > 9999999 && m_sec <= 99991231235959ULL && !m_neg ?
+ ::number_to_datetime_or_date(m_sec, m_usec, to,
+ ulonglong(mode & TIME_MODE_FOR_XXX_TO_DATE), warn) < 0 :
+ ::number_to_time_only(m_neg, m_sec, m_usec, TIME_MAX_HOUR, to, warn);
DBUG_ASSERT(*warn || !rc);
return rc;
}
/*
- Convert a number in format YYYYMMDDhhmmss.ff to
+ Convert a number in formats YYYYMMDDhhmmss.ff or YYMMDDhhmmss.ff to
TIMESTAMP'YYYY-MM-DD hh:mm:ss.ff'
*/
- bool to_datetime(MYSQL_TIME *to, date_mode_t flags, int *warn) const
+ bool to_datetime_or_date(MYSQL_TIME *to, int *warn, date_mode_t flags) const
{
if (m_neg)
{
@@ -458,6 +496,8 @@ public:
timestamp_type tstype, const char *name)
{
const char *typestr= tstype >= 0 ? type_name_by_timestamp_type(tstype) :
+ mode & (TIME_INTERVAL_hhmmssff | TIME_INTERVAL_DAY) ?
+ "interval" :
mode & TIME_TIME_ONLY ? "time" : "datetime";
Temporal::push_conversion_warnings(thd, totally_useless_value, warnings, typestr,
name, ptr());
@@ -579,6 +619,60 @@ protected:
if (warn->warnings)
warn->set_decimal(nr);
}
+ bool ascii_to_temporal(MYSQL_TIME_STATUS *st,
+ const char *str, size_t length,
+ date_mode_t mode)
+ {
+ if (mode & (TIME_INTERVAL_hhmmssff | TIME_INTERVAL_DAY))
+ return ascii_to_datetime_or_date_or_interval_DDhhmmssff(st, str, length,
+ mode);
+ if (mode & TIME_TIME_ONLY)
+ return ascii_to_datetime_or_date_or_time(st, str, length, mode);
+ return ascii_to_datetime_or_date(st, str, length, mode);
+ }
+ bool ascii_to_datetime_or_date_or_interval_DDhhmmssff(MYSQL_TIME_STATUS *st,
+ const char *str,
+ size_t length,
+ date_mode_t mode)
+ {
+ longlong cflags= ulonglong(mode & TIME_MODE_FOR_XXX_TO_DATE);
+ bool rc= mode & TIME_INTERVAL_DAY ?
+ ::str_to_datetime_or_date_or_interval_day(str, length, this, cflags, st,
+ TIME_MAX_INTERVAL_HOUR,
+ TIME_MAX_INTERVAL_HOUR) :
+ ::str_to_datetime_or_date_or_interval_hhmmssff(str, length, this,
+ cflags, st,
+ TIME_MAX_INTERVAL_HOUR,
+ TIME_MAX_INTERVAL_HOUR);
+ DBUG_ASSERT(!rc || st->warnings);
+ return rc;
+ }
+ bool ascii_to_datetime_or_date_or_time(MYSQL_TIME_STATUS *status,
+ const char *str, size_t length,
+ date_mode_t fuzzydate)
+ {
+ ulonglong cflags= ulonglong(fuzzydate & TIME_MODE_FOR_XXX_TO_DATE);
+ bool rc= ::str_to_datetime_or_date_or_time(str, length, this,
+ cflags, status,
+ TIME_MAX_HOUR, UINT_MAX32);
+ DBUG_ASSERT(!rc || status->warnings);
+ return rc;
+ }
+ bool ascii_to_datetime_or_date(MYSQL_TIME_STATUS *status,
+ const char *str, size_t length,
+ date_mode_t fuzzydate)
+ {
+ DBUG_ASSERT(bool(fuzzydate & TIME_TIME_ONLY) == false);
+ bool rc= ::str_to_datetime_or_date(str, length, this,
+ ulonglong(fuzzydate & TIME_MODE_FOR_XXX_TO_DATE),
+ status);
+ DBUG_ASSERT(!rc || status->warnings);
+ return rc;
+ }
+ // Character set aware versions for string conversion routines
+ bool str_to_temporal(MYSQL_TIME_STATUS *st,
+ const char *str, size_t length,
+ CHARSET_INFO *cs, date_mode_t fuzzydate);
bool str_to_datetime_or_date_or_time(MYSQL_TIME_STATUS *st,
const char *str, size_t length,
CHARSET_INFO *cs, date_mode_t fuzzydate);
@@ -858,7 +952,7 @@ public:
*/
static uint max_useful_hour()
{
- return 87649415;
+ return TIME_MAX_INTERVAL_HOUR;
}
public:
Interval_DDhhmmssff(THD *thd, Status *st, bool push_warnings,
@@ -1120,7 +1214,7 @@ public:
}
Time(THD *thd, int *warn, const Sec6 &nr, const Options opt)
{
- if (nr.to_time(this, warn))
+ if (nr.to_datetime_or_time(this, warn, TIME_INVALID_DATES))
time_type= MYSQL_TIMESTAMP_NONE;
xxx_to_time_result_to_valid_value(thd, warn, opt);
}
@@ -1305,7 +1399,7 @@ protected:
Temporal_with_date(int *warn, const Sec6 &nr, date_mode_t flags)
{
DBUG_ASSERT(bool(flags & TIME_TIME_ONLY) == false);
- if (nr.to_datetime(this, flags, warn))
+ if (nr.to_datetime_or_date(this, warn, flags))
time_type= MYSQL_TIMESTAMP_NONE;
}
Temporal_with_date(MYSQL_TIME_STATUS *status,