From 68d1a598bcda36d375fd295e3e65cdf8aef027f1 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Thu, 21 Sep 2017 21:21:36 +0200 Subject: [PATCH] bugfix: copy timestamps correctly in INSERT...SELECT Implement Field_timestamp::save_in_field(timestamp_field) that stores timestamp values without converting them to MYSQL_TIME and back, because this conversion is lossy around DST change time. This fixes main.old-mode test. This is 10.2 version of f8a800bec81 --- sql/field.cc | 67 ++++++++++++++++++++++++++++++++++++----------- sql/field.h | 3 +++ sql/field_conv.cc | 7 ++--- 3 files changed, 57 insertions(+), 20 deletions(-) diff --git a/sql/field.cc b/sql/field.cc index f4775ef712833..a18bb80857369 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -1808,6 +1808,33 @@ int Field::store(const char *to, uint length, CHARSET_INFO *cs, } +static int timestamp_to_TIME(THD *thd, MYSQL_TIME *ltime, my_time_t ts, + ulong sec_part, ulonglong fuzzydate) +{ + thd->time_zone_used= 1; + if (ts == 0 && sec_part == 0) + { + if (fuzzydate & TIME_NO_ZERO_DATE) + return 1; + set_zero_time(ltime, MYSQL_TIMESTAMP_DATETIME); + } + else + { + thd->variables.time_zone->gmt_sec_to_TIME(ltime, ts); + ltime->second_part= sec_part; + } + return 0; +} + + +int Field::store_timestamp(my_time_t ts, ulong sec_part) +{ + MYSQL_TIME ltime; + THD *thd= get_thd(); + timestamp_to_TIME(thd, <ime, ts, sec_part, 0); + return store_time_dec(<ime, decimals()); +} + /** Pack the field into a format suitable for storage and transfer. @@ -4966,6 +4993,13 @@ Field_timestamp::Field_timestamp(uchar *ptr_arg, uint32 len_arg, } +int Field_timestamp::save_in_field(Field *to) +{ + ulong sec_part; + my_time_t ts= get_timestamp(&sec_part); + return to->store_timestamp(ts, sec_part); +} + my_time_t Field_timestamp::get_timestamp(const uchar *pos, ulong *sec_part) const { @@ -5092,6 +5126,22 @@ int Field_timestamp::store(longlong nr, bool unsigned_val) } +int Field_timestamp::store_timestamp(my_time_t ts, ulong sec_part) +{ + store_TIME(ts, sec_part); + if (ts == 0 && sec_part == 0 && + get_thd()->variables.sql_mode & TIME_NO_ZERO_DATE) + { + ErrConvString s( + STRING_WITH_LEN("0000-00-00 00:00:00.000000") - (decimals() ? 6 - decimals() : 7), + system_charset_info); + set_datetime_warning(WARN_DATA_TRUNCATED, &s, MYSQL_TIMESTAMP_DATETIME, 1); + return 1; + } + return 0; +} + + double Field_timestamp::val_real(void) { return (double) Field_timestamp::val_int(); @@ -5195,22 +5245,9 @@ Field_timestamp::validate_value_in_record(THD *thd, const uchar *record) const bool Field_timestamp::get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) { - THD *thd= get_thd(); - thd->time_zone_used= 1; ulong sec_part; - my_time_t temp= get_timestamp(&sec_part); - if (temp == 0 && sec_part == 0) - { /* Zero time is "000000" */ - if (fuzzydate & TIME_NO_ZERO_DATE) - return 1; - set_zero_time(ltime, MYSQL_TIMESTAMP_DATETIME); - } - else - { - thd->variables.time_zone->gmt_sec_to_TIME(ltime, (my_time_t)temp); - ltime->second_part= sec_part; - } - return 0; + my_time_t ts= get_timestamp(&sec_part); + return timestamp_to_TIME(get_thd(), ltime, ts, sec_part, fuzzydate); } diff --git a/sql/field.h b/sql/field.h index cd65cb9c58096..2167b90e26cfb 100644 --- a/sql/field.h +++ b/sql/field.h @@ -842,6 +842,7 @@ class Field: public Value_source virtual int store(longlong nr, bool unsigned_val)=0; virtual int store_decimal(const my_decimal *d)=0; virtual int store_time_dec(MYSQL_TIME *ltime, uint dec); + virtual int store_timestamp(my_time_t timestamp, ulong sec_part); int store_time(MYSQL_TIME *ltime) { return store_time_dec(ltime, TIME_SECOND_PART_DIGITS); } int store(const char *to, uint length, CHARSET_INFO *cs, @@ -2395,6 +2396,8 @@ class Field_timestamp :public Field_temporal { int store(longlong nr, bool unsigned_val); int store_time_dec(MYSQL_TIME *ltime, uint dec); int store_decimal(const my_decimal *); + int store_timestamp(my_time_t timestamp, ulong sec_part); + int save_in_field(Field *to); double val_real(void); longlong val_int(void); String *val_str(String*,String *); diff --git a/sql/field_conv.cc b/sql/field_conv.cc index 15df2c398a3d0..c3ad0c878b552 100644 --- a/sql/field_conv.cc +++ b/sql/field_conv.cc @@ -419,11 +419,8 @@ void Field::do_field_decimal(Copy_field *copy) void Field::do_field_timestamp(Copy_field *copy) { - Field_timestamp *f= static_cast(copy->from_field); - Field_timestamp *t= static_cast(copy->to_field); - ulong sec_part; - my_time_t ts= f->get_timestamp(&sec_part); - t->store_TIME(ts, sec_part); + // XXX why couldn't we do it everywhere? + copy->from_field->save_in_field(copy->to_field); }