Skip to content

Commit ea96168

Browse files
author
Martin Hansson
committed
Bug#30786762: FAILED ASSERTION FOR DATES WITH ZERO OR ABOVE
TIMESTAMP LIMIT This bug report unfortunately contained two independent bugs. The first one concerns bounds checking for timestamp literals and was fixed along with bug no 31239157. This patch fixes the second bug, failed assertion for timestamp literals with zero month and a time zone displacement. We calculate the number of seconds since the unix epoch in order to adjust time zone diplacement, and the function doing this, sec_since_epoch64(), asserts that the month be non-zero. We fix this by adding a check for zero month, and for consistency we disallow a day zero as well, for timestamp literals with a time zone displacement, raising an error. This check runs independent of sql_mode. Change-Id: Idfea577c6528afa2d9bd5f30b2c3d8db9525bd8d
1 parent db86fcf commit ea96168

9 files changed

+104
-21
lines changed

mysql-test/r/time_zone.result

+22
Original file line numberDiff line numberDiff line change
@@ -588,3 +588,25 @@ a
588588
1970-01-01 00:00:01.000000
589589
SET time_zone = DEFAULT;
590590
DROP TABLE t1, t2;
591+
#
592+
# Bug#30786762: FAILED ASSERTION FOR DATES WITH ZERO OR ABOVE TIMESTAMP
593+
# LIMIT
594+
#
595+
SET sql_mode = regexp_replace(@@sql_mode, 'NO_ZERO_IN_DATE', '');
596+
Warnings:
597+
Warning 3135 'NO_ZERO_DATE', 'NO_ZERO_IN_DATE' and 'ERROR_FOR_DIVISION_BY_ZERO' sql modes should be used with strict mode. They will be merged with strict mode in a future release.
598+
SELECT TIMESTAMP '2020-00-01 08:00:00.123456+00:00';
599+
ERROR 22007: Truncated incorrect temporal value: '2020-00-01 08:00:00+0:00'
600+
SELECT TIMESTAMP '2020-01-00 08:00:00.123456+00:00';
601+
ERROR 22007: Truncated incorrect temporal value: '2020-01-00 08:00:00+0:00'
602+
CREATE TABLE t1 ( a DATETIME(6) );
603+
INSERT INTO t1 VALUES ( '2020-00-01 00:00:00.123456+00:00' );
604+
ERROR 22007: Truncated incorrect temporal value: '2020-00-01 00:00:00+0:00'
605+
INSERT INTO t1 VALUES ( '2020-01-00 00:00:00.123456+00:00' );
606+
ERROR 22007: Truncated incorrect temporal value: '2020-01-00 00:00:00+0:00'
607+
SELECT * FROM t1 WHERE a = '2020-00-01 00:00:00.123456+00:00';
608+
ERROR 22007: Truncated incorrect temporal value: '2020-00-01 00:00:00+0:00'
609+
SELECT * FROM t1 WHERE a = '2020-01-00 00:00:00.123456+00:00';
610+
ERROR 22007: Truncated incorrect temporal value: '2020-01-00 00:00:00+0:00'
611+
DROP TABLE t1;
612+
SET sql_mode = DEFAULT;

mysql-test/t/time_zone.test

+28
Original file line numberDiff line numberDiff line change
@@ -455,3 +455,31 @@ SELECT * FROM t2;
455455
SET time_zone = DEFAULT;
456456

457457
DROP TABLE t1, t2;
458+
459+
--echo #
460+
--echo # Bug#30786762: FAILED ASSERTION FOR DATES WITH ZERO OR ABOVE TIMESTAMP
461+
--echo # LIMIT
462+
--echo #
463+
464+
SET sql_mode = regexp_replace(@@sql_mode, 'NO_ZERO_IN_DATE', '');
465+
466+
--error ER_TRUNCATED_WRONG_VALUE
467+
SELECT TIMESTAMP '2020-00-01 08:00:00.123456+00:00';
468+
--error ER_TRUNCATED_WRONG_VALUE
469+
SELECT TIMESTAMP '2020-01-00 08:00:00.123456+00:00';
470+
471+
CREATE TABLE t1 ( a DATETIME(6) );
472+
473+
--error ER_TRUNCATED_WRONG_VALUE
474+
INSERT INTO t1 VALUES ( '2020-00-01 00:00:00.123456+00:00' );
475+
--error ER_TRUNCATED_WRONG_VALUE
476+
INSERT INTO t1 VALUES ( '2020-01-00 00:00:00.123456+00:00' );
477+
478+
--error ER_TRUNCATED_WRONG_VALUE
479+
SELECT * FROM t1 WHERE a = '2020-00-01 00:00:00.123456+00:00';
480+
--error ER_TRUNCATED_WRONG_VALUE
481+
SELECT * FROM t1 WHERE a = '2020-01-00 00:00:00.123456+00:00';
482+
483+
DROP TABLE t1;
484+
485+
SET sql_mode = DEFAULT;

sql/field.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -4609,7 +4609,7 @@ type_conversion_status Field_temporal::store(const char *str, size_t len,
46094609
error = TYPE_ERR_BAD_VALUE;
46104610
} else {
46114611
if (ltime.time_type == MYSQL_TIMESTAMP_DATETIME_TZ) {
4612-
if (adjust_time_zone_displacement(current_thd->time_zone(), &ltime))
4612+
if (convert_time_zone_displacement(current_thd->time_zone(), &ltime))
46134613
return TYPE_ERR_BAD_VALUE;
46144614
}
46154615
error = time_warning_to_type_conversion_status(status.warnings);
@@ -5951,7 +5951,7 @@ type_conversion_status Field_datetimef::store_internal(const MYSQL_TIME *ltime,
59515951
- convert to the local time zone
59525952
*/
59535953
MYSQL_TIME temp_t = *ltime;
5954-
if (adjust_time_zone_displacement(current_thd->time_zone(), &temp_t))
5954+
if (convert_time_zone_displacement(current_thd->time_zone(), &temp_t))
59555955
return TYPE_ERR_BAD_VALUE;
59565956
store_packed(TIME_to_longlong_datetime_packed(temp_t));
59575957

sql/item_create.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
#include "sql/sql_udf.h"
8181
#include "sql/system_variables.h"
8282
#include "sql_string.h"
83-
#include "tztime.h" // adjust_time_zone
83+
#include "tztime.h" // convert_time_zone_displacement
8484

8585
/**
8686
@addtogroup GROUP_PARSER
@@ -2075,7 +2075,7 @@ Item *create_temporal_literal(THD *thd, const char *str, size_t length,
20752075
(ltime.time_type == MYSQL_TIMESTAMP_DATETIME ||
20762076
ltime.time_type == MYSQL_TIMESTAMP_DATETIME_TZ) &&
20772077
!status.warnings) {
2078-
if (adjust_time_zone_displacement(thd->time_zone(), &ltime))
2078+
if (convert_time_zone_displacement(thd->time_zone(), &ltime))
20792079
return nullptr;
20802080
item = new (thd->mem_root) Item_datetime_literal(
20812081
&ltime, status.fractional_digits, thd->time_zone());

sql/item_timefunc.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1780,7 +1780,7 @@ void MYSQL_TIME_cache::set_datetime(MYSQL_TIME *ltime, uint8 dec_arg,
17801780
DBUG_ASSERT(ltime->time_type == MYSQL_TIMESTAMP_DATETIME ||
17811781
ltime->time_type == MYSQL_TIMESTAMP_DATETIME_TZ);
17821782
time = *ltime;
1783-
if (adjust_time_zone_displacement(tz, &time)) {
1783+
if (convert_time_zone_displacement(tz, &time)) {
17841784
DBUG_ASSERT(false);
17851785
}
17861786
time_packed = TIME_to_longlong_datetime_packed(time);

sql/sql_const_folding.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,7 @@ static bool analyze_timestamp_field_constant(THD *thd, const Item_field *f,
898898
Item_func::DATETIME_LITERAL) {
899899
/* User supplied an ok literal */
900900
} else {
901-
Item *i;
901+
Item *i = nullptr;
902902
/*
903903
Make a DATETIME literal, unless the field is a DATE and the constant
904904
has zero time, in which case we make a DATE literal
@@ -917,7 +917,7 @@ static bool analyze_timestamp_field_constant(THD *thd, const Item_field *f,
917917
*place = RP_INSIDE_TRUNCATED;
918918
}
919919
i = new (thd->mem_root) Item_date_literal(&ltime);
920-
} else {
920+
} else if (!check_time_zone_convertibility(ltime)) {
921921
i = new (thd->mem_root) Item_datetime_literal(
922922
&ltime, actual_decimals(&ltime), thd->time_zone());
923923
}

sql/sql_time.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ bool str_to_datetime_with_warn(String *str, MYSQL_TIME *l_time,
185185
}
186186

187187
if (ret_val) return true;
188-
return adjust_time_zone_displacement(thd->time_zone(), l_time);
188+
return convert_time_zone_displacement(thd->time_zone(), l_time);
189189
}
190190

191191
/**
@@ -532,7 +532,7 @@ bool str_to_time_with_warn(String *str, MYSQL_TIME *l_time) {
532532
}
533533

534534
if (!ret_val)
535-
if (adjust_time_zone_displacement(thd->time_zone(), l_time)) return true;
535+
if (convert_time_zone_displacement(thd->time_zone(), l_time)) return true;
536536

537537
return ret_val;
538538
}

sql/tztime.cc

+43-11
Original file line numberDiff line numberDiff line change
@@ -1020,24 +1020,59 @@ static const String tz_SYSTEM_name("SYSTEM", 6, &my_charset_latin1);
10201020

10211021
Time_zone *my_tz_find(const int64 displacement);
10221022

1023+
static void raise_time_zone_conversion_error(const MYSQL_TIME &mt) {
1024+
char str[MAX_DATE_STRING_REP_LENGTH];
1025+
// TODO(mhansson) Get the correct number of decimal places into the error
1026+
// message. This is non-trivial, as this is part of the meta-data, which
1027+
// (for some reason) is not included in a MYSQL_TIME.
1028+
my_datetime_to_str(mt, str, 0);
1029+
1030+
my_error(ER_TRUNCATED_WRONG_VALUE, myf(0), "temporal", str);
1031+
}
1032+
1033+
/**
1034+
Checks that this temporal value can be converted from its specified time
1035+
zone (if any) to the current time zone. Specifically, temporal values with
1036+
zero months or days cannot be converted between time zones.
1037+
1038+
@param mt The time to check.
1039+
@retval false The temporal value has no time zone or can be converted.
1040+
@retval true Otherwise, and an error was raised.
1041+
*/
1042+
bool check_time_zone_convertibility(const MYSQL_TIME &mt) {
1043+
if (mt.time_type == MYSQL_TIMESTAMP_DATETIME_TZ &&
1044+
(mt.month < 1 || mt.day < 1)) {
1045+
raise_time_zone_conversion_error(mt);
1046+
return true;
1047+
}
1048+
return false;
1049+
}
1050+
10231051
/**
10241052
Converts a date/time value with time zone to the corresponding date/time value
1025-
without time zone, adjusted to be in time zone specified by argument @p tz.
1053+
without time zone, converted to be in time zone specified by argument @p tz.
1054+
1055+
Since MySQL doesn't have a data type for temporal values with time zone
1056+
information, all such values are converted to a value without time zone
1057+
using this function.
10261058
1027-
This function is intended only for the types with time zone, and is a no-op
1059+
This function is intended only for values with a time zone, and is a no-op
10281060
for all other types.
10291061
1030-
If the adjusted value falls outside the range of the `DATETIME` type, an error
1031-
is raised and `true` returned.
1062+
The converted value may not fall outside the range of the `DATETIME` type.
1063+
Also some invalid values cannot be converted because the conversion result
1064+
would be undefined. In these cases an error is raised.
10321065
1033-
@param tz The time zone to adjust according to.
1034-
@param[in,out] mt Date/Time value to be adjusted.
1066+
@param tz The time zone to convert according to.
1067+
@param[in,out] mt Date/Time value to be converted.
10351068
10361069
@return false on success. true if an error was raised.
10371070
*/
1038-
bool adjust_time_zone_displacement(const Time_zone *tz, MYSQL_TIME *mt) {
1071+
bool convert_time_zone_displacement(const Time_zone *tz, MYSQL_TIME *mt) {
10391072
if (mt->time_type != MYSQL_TIMESTAMP_DATETIME_TZ) return false;
10401073

1074+
if (check_time_zone_convertibility(*mt)) return true;
1075+
10411076
MYSQL_TIME out;
10421077
std::int64_t epoch_secs_in_utc =
10431078
sec_since_epoch64(*mt) - mt->time_zone_displacement;
@@ -1048,10 +1083,7 @@ bool adjust_time_zone_displacement(const Time_zone *tz, MYSQL_TIME *mt) {
10481083
out.second_part = microseconds;
10491084

10501085
if (check_datetime_range(out)) {
1051-
char str[MAX_DATE_STRING_REP_LENGTH];
1052-
// to do: Get the correct number of decimal places into the error message.
1053-
my_datetime_to_str(out, str, 0);
1054-
my_error(ER_TRUNCATED_WRONG_VALUE, myf(0), "temporal", str);
1086+
raise_time_zone_conversion_error(out);
10551087
return true;
10561088
}
10571089

sql/tztime.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ extern bool my_tz_init(THD *org_thd, const char *default_tzname,
9797
extern void my_tz_free();
9898
extern my_time_t sec_since_epoch_TIME(MYSQL_TIME *t);
9999

100-
bool adjust_time_zone_displacement(const Time_zone *tz, MYSQL_TIME *mt);
100+
bool check_time_zone_convertibility(const MYSQL_TIME &mt);
101+
bool convert_time_zone_displacement(const Time_zone *tz, MYSQL_TIME *mt);
101102
my_time_t use_input_time_zone(const MYSQL_TIME *input, bool *in_dst_time_gap);
102103
void sec_to_TIME(MYSQL_TIME *tmp, my_time_t t, int64 offset);
103104

0 commit comments

Comments
 (0)