diff --git a/sql/field.cc b/sql/field.cc index 5b5c724456b..d98f96c03d5 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -5021,15 +5021,12 @@ my_time_t Field_timestamp::get_timestamp(const uchar *pos, int Field_timestamp::store_TIME_with_warning(THD *thd, MYSQL_TIME *l_time, - const ErrConv *str, - int was_cut, - bool have_smth_to_conv) + const ErrConv *str, int was_cut) { ASSERT_COLUMN_MARKED_FOR_WRITE; uint error = 0; - my_time_t timestamp; - if (MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut) || !have_smth_to_conv) + if (MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut) || !l_time) { error= 1; set_datetime_warning(WARN_DATA_TRUNCATED, @@ -5042,10 +5039,10 @@ int Field_timestamp::store_TIME_with_warning(THD *thd, MYSQL_TIME *l_time, str, MYSQL_TIMESTAMP_DATETIME, 1); } /* Only convert a correct date (not a zero date) */ - if (have_smth_to_conv && l_time->month) + if (l_time && l_time->month) { uint conversion_error; - timestamp= TIME_to_timestamp(thd, l_time, &conversion_error); + my_time_t timestamp= TIME_to_timestamp(thd, l_time, &conversion_error); if (timestamp == 0 && l_time->second_part == 0) conversion_error= ER_WARN_DATA_OUT_OF_RANGE; if (unlikely(conversion_error)) @@ -5054,27 +5051,16 @@ int Field_timestamp::store_TIME_with_warning(THD *thd, MYSQL_TIME *l_time, str, MYSQL_TIMESTAMP_DATETIME, !error); error= 1; } + store_TIME(timestamp, l_time->second_part); } else { - timestamp= 0; - l_time->second_part= 0; + store_TIME(0, 0); } - store_TIME(timestamp, l_time->second_part); return error; } -static bool -copy_or_convert_to_datetime(THD *thd, const MYSQL_TIME *from, MYSQL_TIME *to) -{ - if (from->time_type == MYSQL_TIMESTAMP_TIME) - return time_to_datetime(thd, from, to); - *to= *from; - return false; -} - - sql_mode_t Field_timestamp::sql_mode_for_timestamp(THD *thd) const { // We don't want to store invalid or fuzzy datetime values in TIMESTAMP @@ -5084,14 +5070,13 @@ sql_mode_t Field_timestamp::sql_mode_for_timestamp(THD *thd) const int Field_timestamp::store_time_dec(const MYSQL_TIME *ltime, uint dec) { - int unused; + int warn; ErrConvTime str(ltime); THD *thd= get_thd(); - MYSQL_TIME l_time; - bool valid= !copy_or_convert_to_datetime(thd, ltime, &l_time) && - !check_date(&l_time, pack_time(&l_time) != 0, - sql_mode_for_timestamp(thd), &unused); - return store_TIME_with_warning(thd, &l_time, &str, false, valid); + Datetime dt(thd, &warn, ltime, sql_mode_for_timestamp(thd)); + ltime= dt.is_valid_datetime() ? dt.get_mysql_time() : NULL; + return store_TIME_with_warning(thd, const_cast(ltime), + &str, warn); } @@ -5099,14 +5084,12 @@ int Field_timestamp::store(const char *from,size_t len,CHARSET_INFO *cs) { MYSQL_TIME l_time; MYSQL_TIME_STATUS status; - bool have_smth_to_conv; ErrConvString str(from, len, cs); THD *thd= get_thd(); - - have_smth_to_conv= !str_to_datetime(cs, from, len, &l_time, - sql_mode_for_timestamp(thd), &status); - return store_TIME_with_warning(thd, &l_time, &str, - status.warnings, have_smth_to_conv); + bool rc= str_to_datetime(cs, from, len, &l_time, + sql_mode_for_timestamp(thd), &status); + return store_TIME_with_warning(thd, rc ? NULL : &l_time, &str, + status.warnings); } @@ -5117,7 +5100,7 @@ int Field_timestamp::store(double nr) ErrConvDouble str(nr); THD *thd= get_thd(); bool rc= Sec6(nr).to_datetime(&l_time, sql_mode_for_timestamp(thd), &error); - return store_TIME_with_warning(thd, &l_time, &str, error, !rc); + return store_TIME_with_warning(thd, rc ? NULL : &l_time, &str, error); } @@ -5130,7 +5113,7 @@ int Field_timestamp::store(longlong nr, bool unsigned_val) bool rc= Sec6(nr, unsigned_val).to_datetime(&l_time, sql_mode_for_timestamp(thd), &error); - return store_TIME_with_warning(thd, &l_time, &str, error, !rc); + return store_TIME_with_warning(thd, rc ? NULL : &l_time, &str, error); } @@ -5431,7 +5414,7 @@ int Field_timestamp::store_decimal(const my_decimal *d) THD *thd= get_thd(); ErrConvDecimal str(d); bool rc= Sec6(d).to_datetime(<ime, sql_mode_for_timestamp(thd), &error); - return store_TIME_with_warning(thd, <ime, &str, error, !rc); + return store_TIME_with_warning(thd, rc ? NULL : <ime, &str, error); } int Field_timestamp_with_dec::set_time() @@ -5557,22 +5540,23 @@ void Field_temporal::set_warnings(Sql_condition::enum_warning_level trunc_level, */ int Field_temporal_with_date::store_TIME_with_warning(MYSQL_TIME *ltime, const ErrConv *str, - int was_cut, - int have_smth_to_conv) + int was_cut) { Sql_condition::enum_warning_level trunc_level= Sql_condition::WARN_LEVEL_WARN; - int ret= 2; + int ret; ASSERT_COLUMN_MARKED_FOR_WRITE; - if (was_cut == 0 && have_smth_to_conv == 0) // special case: zero date + if (was_cut == 0 && !ltime) // special case: zero date { was_cut= MYSQL_TIME_WARN_OUT_OF_RANGE; + store_TIME(const_cast(Datetime().get_mysql_time())); + ret= 2; } - else if (!have_smth_to_conv) + else if (!ltime) { - bzero(ltime, sizeof(*ltime)); was_cut= MYSQL_TIME_WARN_TRUNCATED; + store_TIME(const_cast(Datetime().get_mysql_time())); ret= 1; } else if (!MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut) && @@ -5582,12 +5566,17 @@ int Field_temporal_with_date::store_TIME_with_warning(MYSQL_TIME *ltime, { trunc_level= Sql_condition::WARN_LEVEL_NOTE; was_cut|= MYSQL_TIME_WARN_TRUNCATED; + store_TIME(ltime); ret= 3; } + else + { + store_TIME(ltime); + ret= was_cut ? 2 : 0; + } set_warnings(trunc_level, str, was_cut, type_handler()->mysql_timestamp_type()); - store_TIME(ltime); - return was_cut ? ret : 0; + return ret; } @@ -5597,10 +5586,9 @@ int Field_temporal_with_date::store(const char *from, size_t len, CHARSET_INFO * MYSQL_TIME_STATUS status; THD *thd= get_thd(); ErrConvString str(from, len, cs); - bool func_res= !str_to_datetime(cs, from, len, <ime, - sql_mode_for_dates(thd), - &status); - return store_TIME_with_warning(<ime, &str, status.warnings, func_res); + bool rc= str_to_datetime(cs, from, len, <ime, sql_mode_for_dates(thd), + &status); + return store_TIME_with_warning(rc ? NULL : <ime, &str, status.warnings); } @@ -5611,7 +5599,7 @@ int Field_temporal_with_date::store(double nr) THD *thd= get_thd(); ErrConvDouble str(nr); bool rc= Sec6(nr).to_datetime(<ime, sql_mode_for_dates(thd), &error); - return store_TIME_with_warning(<ime, &str, error, !rc); + return store_TIME_with_warning(rc ? NULL : <ime, &str, error); } @@ -5623,35 +5611,18 @@ int Field_temporal_with_date::store(longlong nr, bool unsigned_val) ErrConvInteger str(nr, unsigned_val); bool rc= Sec6(nr, unsigned_val).to_datetime(<ime, sql_mode_for_dates(thd), &error); - return store_TIME_with_warning(<ime, &str, error, !rc); + return store_TIME_with_warning(rc ? NULL : <ime, &str, error); } int Field_temporal_with_date::store_time_dec(const MYSQL_TIME *ltime, uint dec) { - int error= 0, have_smth_to_conv= 1; + int error; ErrConvTime str(ltime); - MYSQL_TIME l_time; - - if (copy_or_convert_to_datetime(get_thd(), ltime, &l_time)) - { - /* - Set have_smth_to_conv and error in a way to have - store_TIME_with_warning do bzero(). - */ - have_smth_to_conv= false; - error= MYSQL_TIME_WARN_OUT_OF_RANGE; - } - else - { - /* - We don't perform range checking here since values stored in TIME - structure always fit into DATETIME range. - */ - have_smth_to_conv= !check_date(&l_time, pack_time(&l_time) != 0, - sql_mode_for_dates(get_thd()), &error); - } - return store_TIME_with_warning(&l_time, &str, error, have_smth_to_conv); + THD *thd= get_thd(); + Datetime dt(thd, &error, ltime, sql_mode_for_dates(thd)); + ltime= dt.is_valid_datetime() ? dt.get_mysql_time() : NULL; + return store_TIME_with_warning(const_cast(ltime), &str, error); } @@ -5738,16 +5709,14 @@ Item *Field_temporal::get_equal_const_item_datetime(THD *thd, ** Stored as a 3 byte unsigned int ****************************************************************************/ int Field_time::store_TIME_with_warning(MYSQL_TIME *ltime, - const ErrConv *str, - int was_cut, - int have_smth_to_conv) + const ErrConv *str, int was_cut) { ASSERT_COLUMN_MARKED_FOR_WRITE; - if (!have_smth_to_conv) + if (!ltime) { - bzero(ltime, sizeof(*ltime)); - store_TIME(ltime); + Datetime tmp; + store_TIME(const_cast(tmp.get_mysql_time())); set_warnings(Sql_condition::WARN_LEVEL_WARN, str, MYSQL_TIME_WARN_TRUNCATED); return 1; } @@ -5786,54 +5755,19 @@ int Field_time::store(const char *from,size_t len,CHARSET_INFO *cs) MYSQL_TIME ltime; MYSQL_TIME_STATUS status; ErrConvString str(from, len, cs); - bool have_smth_to_conv= - !str_to_time(cs, from, len, <ime, sql_mode_for_dates(get_thd()), - &status); - - return store_TIME_with_warning(<ime, &str, - status.warnings, have_smth_to_conv); -} - - -/** - subtract a given number of days from DATETIME, return TIME - - optimized version of calc_time_diff() - - @note it might generate TIME values outside of the valid TIME range! -*/ -static void calc_datetime_days_diff(MYSQL_TIME *ltime, long days) -{ - long daydiff= calc_daynr(ltime->year, ltime->month, ltime->day) - days; - ltime->year= ltime->month= 0; - if (daydiff >=0 ) - { - ltime->day= daydiff; - ltime->time_type= MYSQL_TIMESTAMP_TIME; - } - else - { - longlong timediff= ((((daydiff * 24LL + - ltime->hour) * 60LL + - ltime->minute) * 60LL + - ltime->second) * 1000000LL + - ltime->second_part); - unpack_time(timediff, ltime, MYSQL_TIMESTAMP_TIME); - } + bool rc= str_to_time(cs, from, len, <ime, sql_mode_for_dates(get_thd()), + &status); + return store_TIME_with_warning(rc ? NULL : <ime, &str, status.warnings); } int Field_time::store_time_dec(const MYSQL_TIME *ltime, uint dec) { - MYSQL_TIME l_time= *ltime; ErrConvTime str(ltime); - int was_cut= 0; - - if (curdays && l_time.time_type != MYSQL_TIMESTAMP_TIME) - calc_datetime_days_diff(&l_time, curdays); - - int have_smth_to_conv= !check_time_range(&l_time, decimals(), &was_cut); - return store_TIME_with_warning(&l_time, &str, was_cut, have_smth_to_conv); + int warn; + Time tm(&warn, ltime, curdays); + ltime= tm.is_valid_time() ? tm.get_mysql_time() : NULL; + return store_TIME_with_warning(const_cast(ltime), &str, warn); } @@ -5843,7 +5777,7 @@ int Field_time::store(double nr) ErrConvDouble str(nr); int was_cut; bool rc= Sec6(nr).to_time(<ime, &was_cut); - return store_TIME_with_warning(<ime, &str, was_cut, !rc); + return store_TIME_with_warning(rc ? NULL : <ime, &str, was_cut); } @@ -5853,7 +5787,7 @@ int Field_time::store(longlong nr, bool unsigned_val) ErrConvInteger str(nr, unsigned_val); int was_cut; bool rc= Sec6(nr, unsigned_val).to_time(<ime, &was_cut); - return store_TIME_with_warning(<ime, &str, was_cut, !rc); + return store_TIME_with_warning(rc ? NULL : <ime, &str, was_cut); } @@ -6012,7 +5946,7 @@ int Field_time::store_decimal(const my_decimal *d) MYSQL_TIME ltime; int was_cut; bool rc= Sec6(d).to_time(<ime, &was_cut); - return store_TIME_with_warning(<ime, &str, was_cut, !rc); + return store_TIME_with_warning(rc ? NULL : <ime, &str, was_cut); } @@ -6735,7 +6669,7 @@ int Field_temporal_with_date::store_decimal(const my_decimal *d) THD *thd= get_thd(); ErrConvDecimal str(d); bool rc= Sec6(d).to_datetime(<ime, sql_mode_for_dates(thd), &error); - return store_TIME_with_warning(<ime, &str, error, !rc); + return store_TIME_with_warning(rc ? NULL : <ime, &str, error); } bool Field_datetime_with_dec::send_binary(Protocol *protocol) diff --git a/sql/field.h b/sql/field.h index 2b2dd88ab17..f39c69cd273 100644 --- a/sql/field.h +++ b/sql/field.h @@ -2661,7 +2661,7 @@ public: class Field_temporal_with_date: public Field_temporal { protected: int store_TIME_with_warning(MYSQL_TIME *ltime, const ErrConv *str, - int was_cut, int have_smth_to_conv); + int was_cut); virtual void store_TIME(MYSQL_TIME *ltime) = 0; virtual bool get_TIME(MYSQL_TIME *ltime, const uchar *pos, ulonglong fuzzydate) const = 0; @@ -2694,8 +2694,7 @@ public: class Field_timestamp :public Field_temporal { protected: sql_mode_t sql_mode_for_timestamp(THD *thd) const; - int store_TIME_with_warning(THD *, MYSQL_TIME *, const ErrConv *, - int warnings, bool have_smth_to_conv); + int store_TIME_with_warning(THD *, MYSQL_TIME *, const ErrConv *, int warn); public: Field_timestamp(uchar *ptr_arg, uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, @@ -3020,8 +3019,7 @@ class Field_time :public Field_temporal { long curdays; protected: virtual void store_TIME(const MYSQL_TIME *ltime); - int store_TIME_with_warning(MYSQL_TIME *ltime, const ErrConv *str, - int was_cut, int have_smth_to_conv); + int store_TIME_with_warning(MYSQL_TIME *ltime, const ErrConv *str, int warn); void set_warnings(Sql_condition::enum_warning_level level, const ErrConv *str, int was_cut) { diff --git a/sql/sql_type.cc b/sql/sql_type.cc index cb2a9308d82..27c32cf11ad 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -368,6 +368,105 @@ void Time::make_from_item(Item *item, const Options opt) } +/** + Create from a DATETIME by subtracting a given number of days, + implementing an optimized version of calc_time_diff(). +*/ +void Time::make_from_datetime_with_days_diff(int *warn, const MYSQL_TIME *from, + long days) +{ + *warn= 0; + DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_DATETIME || + from->time_type == MYSQL_TIMESTAMP_DATE); + long daynr= calc_daynr(from->year, from->month, from->day); + long daydiff= daynr - days; + if (!daynr) // Zero date + { + set_zero_time(this, MYSQL_TIMESTAMP_TIME); + neg= true; + hour= TIME_MAX_HOUR + 1; // to report "out of range" in "warn" + } + else if (daydiff >=0) + { + neg= false; + year= month= day= 0; + hhmmssff_copy(from); + hour+= daydiff * 24; + time_type= MYSQL_TIMESTAMP_TIME; + } + else + { + longlong timediff= ((((daydiff * 24LL + + from->hour) * 60LL + + from->minute) * 60LL + + from->second) * 1000000LL + + from->second_part); + unpack_time(timediff, this, MYSQL_TIMESTAMP_TIME); + } + // The above code can generate TIME values outside of the valid TIME range. + adjust_time_range_or_invalidate(warn); +} + + +void Time::make_from_datetime_move_day_to_hour(int *warn, + const MYSQL_TIME *from) +{ + *warn= 0; + DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_DATE || + from->time_type == MYSQL_TIMESTAMP_DATETIME); + time_type= MYSQL_TIMESTAMP_TIME; + neg= false; + year= month= day= 0; + hhmmssff_copy(from); + datetime_to_time_YYYYMMDD_000000DD_mix_to_hours(warn, from->year, + from->month, from->day); + adjust_time_range_or_invalidate(warn); +} + + +void Time::make_from_datetime(int *warn, const MYSQL_TIME *from, long curdays) +{ + if (!curdays) + make_from_datetime_move_day_to_hour(warn, from); + else + make_from_datetime_with_days_diff(warn, from, curdays); +} + + +void Time::make_from_time(int *warn, const MYSQL_TIME *from) +{ + DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_TIME); + if (from->year || from->month) + make_from_out_of_range(warn); + else + { + *warn= 0; + DBUG_ASSERT(from->day == 0); + *(static_cast(this))= *from; + adjust_time_range_or_invalidate(warn); + } +} + + +Time::Time(int *warn, const MYSQL_TIME *from, long curdays) +{ + switch (from->time_type) { + case MYSQL_TIMESTAMP_NONE: + case MYSQL_TIMESTAMP_ERROR: + make_from_out_of_range(warn); + break; + case MYSQL_TIMESTAMP_DATE: + case MYSQL_TIMESTAMP_DATETIME: + make_from_datetime(warn, from, curdays); + break; + case MYSQL_TIMESTAMP_TIME: + make_from_time(warn, from); + break; + } + DBUG_ASSERT(is_valid_value_slow()); +} + + void Temporal_with_date::make_from_item(THD *thd, Item *item, sql_mode_t flags) { flags&= ~TIME_TIME_ONLY; @@ -400,6 +499,65 @@ void Temporal_with_date::make_from_item(THD *thd, Item *item) } +void Temporal_with_date::check_date_or_invalidate(int *warn, sql_mode_t flags) +{ + if (check_date(this, pack_time(this) != 0, flags, warn)) + time_type= MYSQL_TIMESTAMP_NONE; +} + + +void Datetime::make_from_time(THD *thd, int *warn, const MYSQL_TIME *from, + sql_mode_t flags) +{ + DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_TIME); + if (time_to_datetime(thd, from, this)) + make_from_out_of_range(warn); + else + { + *warn= 0; + check_date_or_invalidate(warn, flags); + } +} + + +void Datetime::make_from_datetime(THD *thd, int *warn, const MYSQL_TIME *from, + sql_mode_t flags) +{ + DBUG_ASSERT(from->time_type == MYSQL_TIMESTAMP_DATE || + from->time_type == MYSQL_TIMESTAMP_DATETIME); + if (from->neg || check_datetime_range(from)) + make_from_out_of_range(warn); + else + { + *warn= 0; + *(static_cast(this))= *from; + date_to_datetime(this); + check_date_or_invalidate(warn, flags); + } +} + + +Datetime::Datetime(THD *thd, int *warn, const MYSQL_TIME *from, + sql_mode_t flags) +{ + DBUG_ASSERT((flags & TIME_TIME_ONLY) == 0); + switch (from->time_type) { + case MYSQL_TIMESTAMP_ERROR: + case MYSQL_TIMESTAMP_NONE: + make_from_out_of_range(warn); + break; + case MYSQL_TIMESTAMP_TIME: + make_from_time(thd, warn, from, flags); + break; + case MYSQL_TIMESTAMP_DATETIME: + case MYSQL_TIMESTAMP_DATE: + make_from_datetime(thd, warn, from, flags); + break; + } + DBUG_ASSERT(is_valid_value_slow()); +} + + uint Type_std_attributes::count_max_decimals(Item **item, uint nitems) { uint res= 0; diff --git a/sql/sql_type.h b/sql/sql_type.h index a842983a137..68b4de7c052 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -460,6 +460,11 @@ protected: return negate ? -d : d; } longlong to_packed() const { return ::pack_time(this); } + void make_from_out_of_range(int *warn) + { + *warn= MYSQL_TIME_WARN_OUT_OF_RANGE; + time_type= MYSQL_TIMESTAMP_NONE; + } public: }; @@ -574,7 +579,23 @@ private: second <= TIME_MAX_SECOND && second_part <= TIME_MAX_SECOND_PART; } - + void hhmmssff_copy(const MYSQL_TIME *from) + { + hour= from->hour; + minute= from->minute; + second= from->second; + second_part= from->second_part; + } + void datetime_to_time_YYYYMMDD_000000DD_mix_to_hours(int *warn, + uint from_year, + uint from_month, + uint from_day) + { + if (from_year != 0 || from_month != 0) + *warn|= MYSQL_TIME_NOTE_TRUNCATED; + else + hour+= from_day * 24; + } /* Convert a valid DATE or DATETIME to TIME. Before this call, "this" must be a valid DATE or DATETIME value, @@ -635,11 +656,23 @@ private: break; } } + void adjust_time_range_or_invalidate(int *warn) + { + if (check_time_range(this, TIME_SECOND_PART_DIGITS, warn)) + time_type= MYSQL_TIMESTAMP_NONE; + DBUG_ASSERT(is_valid_value_slow()); + } + void make_from_datetime_move_day_to_hour(int *warn, const MYSQL_TIME *from); + void make_from_datetime_with_days_diff(int *warn, const MYSQL_TIME *from, + long curdays); + void make_from_time(int *warn, const MYSQL_TIME *from); + void make_from_datetime(int *warn, const MYSQL_TIME *from, long curdays); void make_from_item(class Item *item, const Options opt); public: Time() { time_type= MYSQL_TIMESTAMP_NONE; } Time(Item *item) { make_from_item(item, Options()); } Time(Item *item, const Options opt) { make_from_item(item, opt); } + Time(int *warn, const MYSQL_TIME *from, long curdays); static sql_mode_t flags_for_get_date() { return TIME_TIME_ONLY | TIME_INVALID_DATES; } static sql_mode_t comparison_flags_for_get_date() @@ -744,8 +777,13 @@ public: class Temporal_with_date: protected Temporal { protected: + void check_date_or_invalidate(int *warn, sql_mode_t flags); void make_from_item(THD *thd, Item *item, sql_mode_t flags); void make_from_item(THD *thd, Item *item); + Temporal_with_date() + { + time_type= MYSQL_TIMESTAMP_NONE; + } Temporal_with_date(THD *thd, Item *item, sql_mode_t flags) { make_from_item(thd, item, flags); @@ -878,6 +916,10 @@ class Datetime: public Temporal_with_date DBUG_ASSERT(time_type == MYSQL_TIMESTAMP_DATETIME); return !check_datetime_range(this); } + void make_from_time(THD *thd, int *warn, const MYSQL_TIME *from, + sql_mode_t flags); + void make_from_datetime(THD *thd, int *warn, const MYSQL_TIME *from, + sql_mode_t flags); public: Datetime(THD *thd, Item *item, sql_mode_t flags) :Temporal_with_date(thd, item, flags) @@ -900,6 +942,11 @@ public: date_to_datetime(this); DBUG_ASSERT(is_valid_value_slow()); } + Datetime(THD *thd, int *warn, const MYSQL_TIME *from, sql_mode_t flags); + Datetime() + { + set_zero_time(this, MYSQL_TIMESTAMP_DATETIME); + } bool is_valid_datetime() const { /* @@ -974,6 +1021,7 @@ public: } }; + /* Flags for collation aggregation modes, used in TDCollation::agg():