From bc4409ab4ee3729f09151a4d7bdc6d95fdcaa3a1 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Fri, 22 Sep 2017 01:12:00 +0200 Subject: [PATCH] MDEV-13868 cannot insert 1288481126 in a timestamp column in Europe/Moscow make insert NULL into a timestamp mark the field as having an explicit value. So that the field won't be assigned the value again in TABLE::update_default_field() make Item_func_now_local::save_in_field(timestamp_field) not to go through MYSQL_TIME - this conversion is lossy around DST change times. This fixes inserting a default value into a timestamp field. --- mysql-test/r/old-mode.result | 8 ++++++++ mysql-test/t/old-mode.test | 2 ++ sql/field.cc | 30 ------------------------------ sql/field.h | 3 +-- sql/item_timefunc.cc | 19 +++++++++++++++++++ sql/item_timefunc.h | 1 + 6 files changed, 31 insertions(+), 32 deletions(-) diff --git a/mysql-test/r/old-mode.result b/mysql-test/r/old-mode.result index 61987c398b9..73ad613048a 100644 --- a/mysql-test/r/old-mode.result +++ b/mysql-test/r/old-mode.result @@ -132,21 +132,29 @@ set global mysql56_temporal_format=false; create table t1 (a timestamp); set timestamp=1288477526; insert t1 values (null); +insert t1 values (); set timestamp=1288481126; insert t1 values (null); +insert t1 values (); select a, unix_timestamp(a) from t1; a unix_timestamp(a) 2010-10-31 02:25:26 1288477526 +2010-10-31 02:25:26 1288477526 +2010-10-31 02:25:26 1288481126 2010-10-31 02:25:26 1288481126 set global mysql56_temporal_format=true; select a, unix_timestamp(a) from t1; a unix_timestamp(a) 2010-10-31 02:25:26 1288477526 +2010-10-31 02:25:26 1288477526 +2010-10-31 02:25:26 1288481126 2010-10-31 02:25:26 1288481126 alter table t1 modify a timestamp; select a, unix_timestamp(a) from t1; a unix_timestamp(a) 2010-10-31 02:25:26 1288477526 +2010-10-31 02:25:26 1288477526 +2010-10-31 02:25:26 1288481126 2010-10-31 02:25:26 1288481126 drop table t1; set global mysql56_temporal_format=false; diff --git a/mysql-test/t/old-mode.test b/mysql-test/t/old-mode.test index 3b763ae5158..d7e8ce8ee55 100644 --- a/mysql-test/t/old-mode.test +++ b/mysql-test/t/old-mode.test @@ -93,8 +93,10 @@ set global mysql56_temporal_format=false; create table t1 (a timestamp); set timestamp=1288477526; insert t1 values (null); +insert t1 values (); set timestamp=1288481126; insert t1 values (null); +insert t1 values (); select a, unix_timestamp(a) from t1; set global mysql56_temporal_format=true; select a, unix_timestamp(a) from t1; diff --git a/sql/field.cc b/sql/field.cc index a872fab2520..f4775ef7128 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -5260,36 +5260,6 @@ int Field_timestamp::set_time() return 0; } -/** - Mark the field as having an explicit default value. - - @param value if available, the value that the field is being set to - @returns whether the explicit default bit was set - - @note - Fields that have an explicit default value should not be updated - automatically via the DEFAULT or ON UPDATE functions. The functions - that deal with data change functionality (INSERT/UPDATE/LOAD), - determine if there is an explicit value for each field before performing - the data change, and call this method to mark the field. - - For timestamp columns, the only case where a column is not marked - as been given a value are: - - It's explicitly assigned with DEFAULT - - We assign NULL to a timestamp field that is defined as NOT NULL. - This is how MySQL has worked since it's start. -*/ - -bool Field_timestamp::set_explicit_default(Item *value) -{ - if (((value->type() == Item::DEFAULT_VALUE_ITEM && - !((Item_default_value*)value)->arg) || - (!maybe_null() && value->null_value))) - return false; - set_has_explicit_value(); - return true; -} - #ifdef NOT_USED static void store_native(ulonglong num, uchar *to, uint bytes) { diff --git a/sql/field.h b/sql/field.h index 91c24327b28..13d43201c6a 100644 --- a/sql/field.h +++ b/sql/field.h @@ -975,7 +975,7 @@ public: { return bitmap_is_set(&table->has_value_set, field_index); } - virtual bool set_explicit_default(Item *value); + bool set_explicit_default(Item *value); /** Evaluates the @c UPDATE default function, if one exists, and stores the @@ -2403,7 +2403,6 @@ public: void sql_type(String &str) const; bool zero_pack() const { return 0; } int set_time(); - bool set_explicit_default(Item *value); int evaluate_update_default_function() { int res= 0; diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 6e476dfa746..8dbd47444f2 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -1734,6 +1734,25 @@ void Item_func_now::print(String *str, enum_query_type query_type) str->append(')'); } + +int Item_func_now_local::save_in_field(Field *field, bool no_conversions) +{ + if (field->type() == MYSQL_TYPE_TIMESTAMP) + { + THD *thd= field->get_thd(); + my_time_t ts= thd->query_start(); + uint dec= MY_MIN(decimals, field->decimals()); + ulong sec_part= dec ? thd->query_start_sec_part() : 0; + sec_part-= my_time_fraction_remainder(sec_part, dec); + field->set_notnull(); + ((Field_timestamp*)field)->store_TIME(ts, sec_part); + return 0; + } + else + return Item_temporal_func::save_in_field(field, no_conversions); +} + + /** Converts current time in my_time_t to MYSQL_TIME represenatation for local time zone. Defines time zone (local) used for whole NOW function. diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index b0a083bf24f..dccccc479ef 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -748,6 +748,7 @@ class Item_func_now_local :public Item_func_now public: Item_func_now_local(THD *thd, uint dec): Item_func_now(thd, dec) {} const char *func_name() const { return "current_timestamp"; } + int save_in_field(Field *field, bool no_conversions); virtual void store_now_in_TIME(THD *thd, MYSQL_TIME *now_time); virtual enum Functype functype() const { return NOW_FUNC; } Item *get_copy(THD *thd, MEM_ROOT *mem_root)