From: Alexander Barkov Date: November 18 2011 9:53am Subject: bzr push into mysql-trunk branch (alexander.barkov:3565 to 3566) Bug#13399082 WL#946 List-Archive: http://lists.mysql.com/commits/142057 X-Bug: 13399082 Message-Id: <201111180953.pAI9rB4F000605@bar.myoffice.izhnet.ru> MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit 3566 Alexander Barkov 2011-11-18 WL#946: Bug#13399082 ABARKOV ASSERTION `MON > 0 && MON < 13' FAILED IN TZTIME.CC:794 | SEC_SINCE_EPOCH modified: mysql-test/r/timezone2.result mysql-test/r/type_temporal_fractional.result mysql-test/t/timezone2.test mysql-test/t/type_temporal_fractional.test sql/field.cc sql/field.h sql/item.cc sql/item.h sql/item_timefunc.cc sql/sql_time.cc sql/sql_time.h 3565 Alexander Barkov 2011-11-17 WL#946: Bug#13401354 ITEM->FIELD_TYPE() == MYSQL_TYPE_TIME || ITEM->FIELD_TYPE() == MYSQL_TYPE_NULL modified: mysql-test/r/type_temporal_fractional.result mysql-test/t/type_temporal_fractional.test sql/item_cmpfunc.cc === modified file 'mysql-test/r/timezone2.result' --- a/mysql-test/r/timezone2.result 2010-08-06 19:29:37 +0000 +++ b/mysql-test/r/timezone2.result 2011-11-18 09:52:00 +0000 @@ -309,3 +309,33 @@ CONVERT_TZ(1, 1, a) NULL DROP TABLE t1; End of 5.1 tests +# +# Start of 5.6 tests +# +SET time_zone='Europe/Moscow'; +CREATE TABLE t1 (a TIMESTAMP, b VARCHAR(30)); +INSERT INTO t1 VALUES +('2003-03-30 01:59:59', 'Before the gap'), +('2003-03-30 02:30:00', 'Inside the gap'), +('2003-03-30 03:00:00', 'After the gap'); +Warnings: +Warning 1299 Invalid TIMESTAMP value in column 'a' at row 2 +SELECT a, UNIX_TIMESTAMP(a), b FROM t1; +a UNIX_TIMESTAMP(a) b +2003-03-30 01:59:59 1048978799 Before the gap +2003-03-30 03:00:00 1048978800 Inside the gap +2003-03-30 03:00:00 1048978800 After the gap +DROP TABLE t1; +SELECT UNIX_TIMESTAMP('2003-03-30 01:59:59'), 'Before the gap' AS b; +UNIX_TIMESTAMP('2003-03-30 01:59:59') b +1048978799 Before the gap +SELECT UNIX_TIMESTAMP('2003-03-30 02:30:00'), 'Inside the gap' AS b; +UNIX_TIMESTAMP('2003-03-30 02:30:00') b +1048978800 Inside the gap +SELECT UNIX_TIMESTAMP('2003-03-30 03:00:00'), 'After the gap' AS b; +UNIX_TIMESTAMP('2003-03-30 03:00:00') b +1048978800 After the gap +SET time_zone=DEFAULT; +# +# End of 5.6 tests +# === modified file 'mysql-test/r/type_temporal_fractional.result' --- a/mysql-test/r/type_temporal_fractional.result 2011-11-17 18:31:32 +0000 +++ b/mysql-test/r/type_temporal_fractional.result 2011-11-18 09:52:00 +0000 @@ -16689,3 +16689,70 @@ Warnings: Warning 1411 Incorrect datetime value: '00-0' for function str_to_date Warning 1411 Incorrect datetime value: '00-0' for function str_to_date DROP TABLE t1; +# +# Bug#13399082 - ASSERTION `MON > 0 && MON < 13' FAILED IN TZTIME.CC | SEC_SINCE_EPOCH +# +SET TIME_ZONE='+02:00'; +SELECT UNIX_TIMESTAMP(STR_TO_DATE('2020','%Y')); +UNIX_TIMESTAMP(STR_TO_DATE('2020','%Y')) +0 +SET TIME_ZONE=DEFAULT; +# This should return NULL +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES(NULL); +SELECT UNIX_TIMESTAMP(a) FROM t1; +UNIX_TIMESTAMP(a) +NULL +DROP TABLE t1; +SELECT UNIX_TIMESTAMP(NULL); +UNIX_TIMESTAMP(NULL) +NULL +SELECT UNIX_TIMESTAMP(DATE(NULL)); +UNIX_TIMESTAMP(DATE(NULL)) +NULL +SELECT UNIX_TIMESTAMP(TIME(NULL)); +UNIX_TIMESTAMP(TIME(NULL)) +NULL +SELECT UNIX_TIMESTAMP(TIMESTAMP(NULL)); +UNIX_TIMESTAMP(TIMESTAMP(NULL)) +NULL +# This should return 0 +CREATE TABLE t1 (a VARCHAR(30)); +INSERT INTO t1 VALUES +('0000-01-01 00:00:00'),('2001-00-01 00:00:01'),('2001-01-00 00:00:00'), +('0000-00-00 00:00:01'),('0000-00-00 00:00:00.1'); +SELECT UNIX_TIMESTAMP(a) FROM t1; +UNIX_TIMESTAMP(a) +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +DROP TABLE t1; +SELECT UNIX_TIMESTAMP('0000-01-01 00:00:00'); +UNIX_TIMESTAMP('0000-01-01 00:00:00') +0 +SELECT UNIX_TIMESTAMP('2001-00-01 00:00:00'); +UNIX_TIMESTAMP('2001-00-01 00:00:00') +0 +SELECT UNIX_TIMESTAMP('2001-01-00 00:00:00'); +UNIX_TIMESTAMP('2001-01-00 00:00:00') +0 +SELECT UNIX_TIMESTAMP('0000-00-00 10:10:20'); +UNIX_TIMESTAMP('0000-00-00 10:10:20') +0 +SELECT UNIX_TIMESTAMP('0000-00-00 00:00:00.1'); +UNIX_TIMESTAMP('0000-00-00 00:00:00.1') +0.0 +SELECT UNIX_TIMESTAMP(TIMESTAMP'0000-00-00 10:10:20'); +UNIX_TIMESTAMP(TIMESTAMP'0000-00-00 10:10:20') +0 +SELECT UNIX_TIMESTAMP(TIMESTAMP'0000-00-00 00:00:00.1'); +UNIX_TIMESTAMP(TIMESTAMP'0000-00-00 00:00:00.1') +0.0 +SELECT UNIX_TIMESTAMP(TIMESTAMP('0000-00-00 10:10:20')); +UNIX_TIMESTAMP(TIMESTAMP('0000-00-00 10:10:20')) +0 +SELECT UNIX_TIMESTAMP(TIMESTAMP('0000-00-00 00:00:00.1')); +UNIX_TIMESTAMP(TIMESTAMP('0000-00-00 00:00:00.1')) +0.0 === modified file 'mysql-test/t/timezone2.test' --- a/mysql-test/t/timezone2.test 2010-08-06 19:29:37 +0000 +++ b/mysql-test/t/timezone2.test 2011-11-18 09:52:00 +0000 @@ -284,3 +284,25 @@ SELECT CONVERT_TZ(1, 1, a) FROM t1; DROP TABLE t1; --echo End of 5.1 tests + +--echo # +--echo # Start of 5.6 tests +--echo # +SET time_zone='Europe/Moscow'; +CREATE TABLE t1 (a TIMESTAMP, b VARCHAR(30)); +# Testing values around winter/summer time change +# Expect non-zero UNIX_TIMESTAMP value for all of them +INSERT INTO t1 VALUES +('2003-03-30 01:59:59', 'Before the gap'), +('2003-03-30 02:30:00', 'Inside the gap'), +('2003-03-30 03:00:00', 'After the gap'); +SELECT a, UNIX_TIMESTAMP(a), b FROM t1; +DROP TABLE t1; +SELECT UNIX_TIMESTAMP('2003-03-30 01:59:59'), 'Before the gap' AS b; +SELECT UNIX_TIMESTAMP('2003-03-30 02:30:00'), 'Inside the gap' AS b; +SELECT UNIX_TIMESTAMP('2003-03-30 03:00:00'), 'After the gap' AS b; +SET time_zone=DEFAULT; + +--echo # +--echo # End of 5.6 tests +--echo # === modified file 'mysql-test/t/type_temporal_fractional.test' --- a/mysql-test/t/type_temporal_fractional.test 2011-11-17 18:31:32 +0000 +++ b/mysql-test/t/type_temporal_fractional.test 2011-11-18 09:52:00 +0000 @@ -7261,6 +7261,41 @@ AND col_timestamp_3_not_null_key < col_datetime; DROP TABLE t1; + +--echo # +--echo # Bug#13399082 - ASSERTION `MON > 0 && MON < 13' FAILED IN TZTIME.CC | SEC_SINCE_EPOCH +--echo # +SET TIME_ZONE='+02:00'; +SELECT UNIX_TIMESTAMP(STR_TO_DATE('2020','%Y')); +SET TIME_ZONE=DEFAULT; + +--echo # This should return NULL +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES(NULL); +SELECT UNIX_TIMESTAMP(a) FROM t1; +DROP TABLE t1; +SELECT UNIX_TIMESTAMP(NULL); +SELECT UNIX_TIMESTAMP(DATE(NULL)); +SELECT UNIX_TIMESTAMP(TIME(NULL)); +SELECT UNIX_TIMESTAMP(TIMESTAMP(NULL)); + +--echo # This should return 0 +CREATE TABLE t1 (a VARCHAR(30)); +INSERT INTO t1 VALUES +('0000-01-01 00:00:00'),('2001-00-01 00:00:01'),('2001-01-00 00:00:00'), +('0000-00-00 00:00:01'),('0000-00-00 00:00:00.1'); +SELECT UNIX_TIMESTAMP(a) FROM t1; +DROP TABLE t1; +SELECT UNIX_TIMESTAMP('0000-01-01 00:00:00'); +SELECT UNIX_TIMESTAMP('2001-00-01 00:00:00'); +SELECT UNIX_TIMESTAMP('2001-01-00 00:00:00'); +SELECT UNIX_TIMESTAMP('0000-00-00 10:10:20'); +SELECT UNIX_TIMESTAMP('0000-00-00 00:00:00.1'); +SELECT UNIX_TIMESTAMP(TIMESTAMP'0000-00-00 10:10:20'); +SELECT UNIX_TIMESTAMP(TIMESTAMP'0000-00-00 00:00:00.1'); +SELECT UNIX_TIMESTAMP(TIMESTAMP('0000-00-00 10:10:20')); +SELECT UNIX_TIMESTAMP(TIMESTAMP('0000-00-00 00:00:00.1')); + ## TS-TODO: SELECT CAST('00:00:00' AS DATETIME) -> should it use curdate? ## TS-TODO: reuse count_datetime_length()/count_string_result_length() === modified file 'sql/field.cc' --- a/sql/field.cc 2011-11-07 14:35:16 +0000 +++ b/sql/field.cc 2011-11-18 09:52:00 +0000 @@ -1794,15 +1794,12 @@ bool Field::get_time(MYSQL_TIME *ltime) } -bool Field::get_timestamp(struct timeval *tm) +bool Field::get_timestamp(struct timeval *tm, int *warnings) { MYSQL_TIME ltime; - if (get_date(<ime, TIME_FUZZY_DATE)) - return true; - my_bool not_used; - tm->tv_sec= TIME_to_timestamp(current_thd, <ime, ¬_used); - tm->tv_usec= ltime.second_part; - return false; + DBUG_ASSERT(!is_null()); + return get_date(<ime, TIME_FUZZY_DATE) || + datetime_to_timeval(current_thd, <ime, tm, warnings); } @@ -5156,30 +5153,14 @@ Field_temporal_with_date_and_time::conve struct timeval *tm, int *warnings) { - my_bool in_dst_time_gap; - /* Only convert a correct date (not a zero date) */ - if (!ltime->month) + /* + No needs to do check_date(TIME_NO_ZERO_IN_DATE), + because it has been done earlier in + store_time(), number_to_datetime() or str_to_datetime(). + */ + if (datetime_with_no_zero_in_date_to_timeval(thd, ltime, tm, warnings)) { tm->tv_sec= tm->tv_usec= 0; - if (ltime->second_part) - { - /* Don't allow zero timestamp with microseconds */ - *warnings|= MYSQL_TIME_WARN_TRUNCATED; - return true; - } - return false; - } - - tm->tv_usec= ltime->second_part; - - if (!(tm->tv_sec= TIME_to_timestamp(thd, ltime, &in_dst_time_gap))) - { - *warnings|= MYSQL_TIME_WARN_OUT_OF_RANGE; - return true; - } - else if (in_dst_time_gap) - { - *warnings|= MYSQL_TIME_WARN_INVALID_TIMESTAMP; return true; } return false; @@ -5358,7 +5339,7 @@ bool Field_timestamp::get_date_internal( /** Get TIMESTAMP field value as seconds since begging of Unix Epoch */ -bool Field_timestamp::get_timestamp(struct timeval *tm) +bool Field_timestamp::get_timestamp(struct timeval *tm, int *warnings) { if (is_null()) return true; @@ -5567,12 +5548,11 @@ Field_timestampf::get_date_internal(MYSQ } -bool Field_timestampf::get_timestamp(struct timeval *tm) +bool Field_timestampf::get_timestamp(struct timeval *tm, int *warnings) { THD *thd= table ? table->in_use : current_thd; thd->time_zone_used= 1; - if (is_null()) - return true; + DBUG_ASSERT(!is_null()); my_timestamp_from_binary(tm, ptr, dec); return false; } === modified file 'sql/field.h' --- a/sql/field.h 2011-11-07 14:35:16 +0000 +++ b/sql/field.h 2011-11-18 09:52:00 +0000 @@ -535,7 +535,7 @@ public: This method is used in "SELECT UNIX_TIMESTAMP(field)" to avoid conversion from timestamp to MYSQL_TIME and back. */ - virtual bool get_timestamp(struct timeval *tm); + virtual bool get_timestamp(struct timeval *tm, int *warnings); /** Stores a timestamp value in "struct timeval" format into a field. Note, store_timestamp(), get_timestamp() and store_time() @@ -2170,7 +2170,7 @@ public: Field::set_default(); } /* Get TIMESTAMP field value as seconds since begging of Unix Epoch */ - bool get_timestamp(struct timeval *tm); + bool get_timestamp(struct timeval *tm, int *warnings); bool get_date(MYSQL_TIME *ltime,uint fuzzydate); Field_timestamp *clone(MEM_ROOT *mem_root) const { DBUG_ASSERT(type() == MYSQL_TYPE_TIMESTAMP); @@ -2265,7 +2265,7 @@ public: else Field::set_default(); } - bool get_timestamp(struct timeval *tm); + bool get_timestamp(struct timeval *tm, int *warnings); }; === modified file 'sql/item.cc' --- a/sql/item.cc 2011-11-16 14:58:34 +0000 +++ b/sql/item.cc 2011-11-18 09:52:00 +0000 @@ -1372,17 +1372,14 @@ bool Item::get_time_from_non_temporal(MY } return (null_value= true); // Impossible result type } - -bool Item::get_timeval(struct timeval *tm) + +bool Item::get_timeval(struct timeval *tm, int *warnings) { MYSQL_TIME ltime; - if (get_date(<ime, 0)) - return true; - my_bool not_used; - tm->tv_sec= TIME_to_timestamp(current_thd, <ime, ¬_used); - tm->tv_usec= ltime.second_part; - return false; + return (null_value= + (get_date(<ime, TIME_FUZZY_DATE) || + datetime_to_timeval(current_thd, <ime, tm, warnings))); } @@ -2674,9 +2671,10 @@ bool Item_field::get_time(MYSQL_TIME *lt return 0; } -bool Item_field::get_timeval(struct timeval *tm) +bool Item_field::get_timeval(struct timeval *tm, int *warnings) { - return ((null_value= field->get_timestamp(tm))); + return (null_value= (field->is_null() || + field->get_timestamp(tm, warnings))); } double Item_field::val_result() === modified file 'sql/item.h' --- a/sql/item.h 2011-11-10 14:58:23 +0000 +++ b/sql/item.h 2011-11-18 09:52:00 +0000 @@ -1082,7 +1082,7 @@ public: @retval false on success @retval true on error */ - virtual bool get_timeval(struct timeval *tm); + virtual bool get_timeval(struct timeval *tm, int *warnings); virtual bool get_date_result(MYSQL_TIME *ltime,uint fuzzydate) { return get_date(ltime,fuzzydate); } /* @@ -1989,7 +1989,7 @@ public: bool get_date(MYSQL_TIME *ltime,uint fuzzydate); bool get_date_result(MYSQL_TIME *ltime,uint fuzzydate); bool get_time(MYSQL_TIME *ltime); - bool get_timeval(struct timeval *tm); + bool get_timeval(struct timeval *tm, int *warnings); bool is_null() { return field->is_null(); } void update_null_value(); Item *get_tmp_table_item(THD *thd); === modified file 'sql/item_timefunc.cc' --- a/sql/item_timefunc.cc 2011-11-16 13:45:51 +0000 +++ b/sql/item_timefunc.cc 2011-11-18 09:52:00 +0000 @@ -1471,14 +1471,19 @@ bool Item_func_unix_timestamp::val_timev tm->tv_usec= 0; return false; // no args: null_value is set in constructor and is always 0. } - if (args[0]->get_timeval(tm)) // Don't set null_value here + int warnings= 0; + if (args[0]->get_timeval(tm, &warnings)) // Don't set null_value here { - tm->tv_sec= tm->tv_usec= 0; /* - We set null_value only if args[0] returned null. + We set null_value only if args[0]->get_date() invoked + inside args[0]->get_timeval() returned null, + which is indicated by "warnings == 0". In case of wrong non-null datetime parameter we return 0. + "warnings" will not be equal to 0 in this case. */ - return (null_value= args[0]->null_value); + if (warnings == 0) + return (null_value= true); + tm->tv_sec= tm->tv_usec= 0; } return (null_value= false); } @@ -1499,10 +1504,9 @@ longlong Item_func_unix_timestamp::val_i DBUG_ASSERT(arg_count == 1 && args[0]->type() == Item::FIELD_ITEM && args[0]->field_type() == MYSQL_TYPE_TIMESTAMP); - Field *field=((Item_field*) args[0])->field; /* Leave the incl_endp intact */ struct timeval tm; - return (null_value= field->get_timestamp(&tm)) ? 0 : tm.tv_sec; + return val_timeval(&tm) ? 0 : tm.tv_sec; } === modified file 'sql/sql_time.cc' --- a/sql/sql_time.cc 2011-11-03 09:33:45 +0000 +++ b/sql/sql_time.cc 2011-11-18 09:52:00 +0000 @@ -637,6 +637,116 @@ my_time_t TIME_to_timestamp(THD *thd, co } +/** + Convert a datetime MYSQL_TIME representation + to corresponding "struct timeval" value. + + ltime must previously be checked for TIME_NO_ZERO_IN_DATE. + Things like '0000-01-01', '2000-00-01', '2000-01-00' are not allowed + and asserted. + + Things like '0000-00-00 10:30:30' or '0000-00-00 00:00:00.123456' + (i.e. empty date with non-empty time) return error. + + Zero datetime '0000-00-00 00:00:00.000000' + is allowed and is mapper to {tv_sec=0, tv_usec=0}. + + Note: In case of error, tm value is not initialized. + + Note: "warnings" is not initialized to zero, + so new warnings are added to the old ones. + Caller must make sure to initialize "warnings". + + @param IN thd current thd + @param IN ltime datetime value + @param OUT tm timeval value + @param OUT warnings pointer to warnings vector + @return + @retval false on success + @retval true on error +*/ +bool datetime_with_no_zero_in_date_to_timeval(THD *thd, + const MYSQL_TIME *ltime, + struct timeval *tm, + int *warnings) +{ + if (!ltime->month) /* Zero date */ + { + DBUG_ASSERT(!ltime->year && !ltime->day); + if (non_zero_time(ltime)) + { + /* + Return error for zero date with non-zero time, e.g.: + '0000-00-00 10:20:30' or '0000-00-00 00:00:00.123456' + */ + *warnings|= MYSQL_TIME_WARN_TRUNCATED; + return true; + } + tm->tv_sec= tm->tv_usec= 0; // '0000-00-00 00:00:00.000000' + return false; + } + + my_bool in_dst_time_gap; + if (!(tm->tv_sec= TIME_to_timestamp(current_thd, ltime, &in_dst_time_gap))) + { + /* + Date was outside of the supported timestamp range. + For example: '3001-01-01 00:00:00' or '1000-01-01 00:00:00' + */ + *warnings|= MYSQL_TIME_WARN_OUT_OF_RANGE; + return true; + } + else if (in_dst_time_gap) + { + /* + Set MYSQL_TIME_WARN_INVALID_TIMESTAMP warning to indicate + that date was fine but pointed to winter/summer time switch gap. + In this case tm is set to the fist second after gap. + For example: '2003-03-30 02:30:00 MSK' -> '2003-03-30 03:00:00 MSK' + */ + *warnings|= MYSQL_TIME_WARN_INVALID_TIMESTAMP; + } + tm->tv_usec= ltime->second_part; + return false; +} + + +/** + Convert a datetime MYSQL_TIME representation + to corresponding "struct timeval" value. + + Things like '0000-01-01', '2000-00-01', '2000-01-00' + (i.e. incomplete date) return error. + + Things like '0000-00-00 10:30:30' or '0000-00-00 00:00:00.123456' + (i.e. empty date with non-empty time) return error. + + Zero datetime '0000-00-00 00:00:00.000000' + is allowed and is mapper to {tv_sec=0, tv_usec=0}. + + Note: In case of error, tm value is not initialized. + + Note: "warnings" is not initialized to zero, + so new warnings are added to the old ones. + Caller must make sure to initialize "warnings". + + @param IN thd current thd + @param IN ltime datetime value + @param OUT tm timeval value + @param OUT warnings pointer to warnings vector + @return + @retval false on success + @retval true on error +*/ +bool datetime_to_timeval(THD *thd, const MYSQL_TIME *ltime, + struct timeval *tm, int *warnings) +{ + return + check_date(ltime, non_zero_date(ltime), TIME_NO_ZERO_IN_DATE, warnings) || + datetime_with_no_zero_in_date_to_timeval(current_thd, ltime, tm, warnings); +} + + /* Convert a time string to a MYSQL_TIME struct and produce a warning if string was cut during conversion. === modified file 'sql/sql_time.h' --- a/sql/sql_time.h 2011-11-07 11:50:35 +0000 +++ b/sql/sql_time.h 2011-11-18 09:52:00 +0000 @@ -36,6 +36,11 @@ ulong convert_month_to_period(ulong mont void mix_date_and_time(MYSQL_TIME *ldate, const MYSQL_TIME *ltime); void get_date_from_daynr(long daynr,uint *year, uint *month, uint *day); my_time_t TIME_to_timestamp(THD *thd, const MYSQL_TIME *t, my_bool *not_exist); +bool datetime_with_no_zero_in_date_to_timeval(THD *thd, const MYSQL_TIME *t, + struct timeval *tm, + int *warnings); +bool datetime_to_timeval(THD *thd, const MYSQL_TIME *t, + struct timeval *tm, int *warnings); bool str_to_datetime_with_warn(String *str, MYSQL_TIME *l_time, uint flags); bool my_decimal_to_datetime_with_warn(const my_decimal *decimal, MYSQL_TIME *ltime, uint flags); No bundle (reason: useless for push emails).