diff options
author | Alexander Barkov <bar@mariadb.org> | 2018-02-04 16:43:02 +0400 |
---|---|---|
committer | Alexander Barkov <bar@mariadb.org> | 2018-02-04 16:43:02 +0400 |
commit | 28d4cf0c1b3366c6471866d144ef28fced1e5390 (patch) | |
tree | 33739324408063de53681fe4ac8dfb6abdc45689 /sql | |
parent | 2ecf2f9b2a2c2126b9c9bd0ba8b58280331f5849 (diff) | |
download | mariadb-git-28d4cf0c1b3366c6471866d144ef28fced1e5390.tar.gz |
MDEV-15176 Storing DATETIME-alike VARCHAR data into TIME produces wrong results
When storing '0001-01-01 10:20:30x', execution went throw the last code
branch in Field_time::store_TIME_with_warning(), around the test
for (ltime->year || ltime->month). This then resulted into wrong results
because:
1. Field_time::store_TIME() does not check YYYYMM against zero.
It assumes that ltime->days and ltime->hours are already properly set.
So it mixed days to hours, even when YYYYMM was not zero.
2. Field_time_hires::store_TIME() does not check YYYYMM against zero.
It assumes that ltime->year, ltime->month, ltime->days and ltime->hours
are already properly set. So it always mixed days and even months(!) and years(!)
to hours, using pack_time(). This gave even worse results comparing to #2.
3. Field_timef::store_TIME() did not check the entire YYYYMM for being zero.
It only checked MM, but did not check YYYY. In case of a zero MM,
it mixed days to hours, even if YYYY was not zero.
The wrong code was in TIME_to_longlong_time_packed().
In the new reduction Field_time::store_TIME_with_warning() is responsible
to prepare the YYYYYMMDD part properly in all code branches
(with trailing garbage like 'x' and without trailing garbage).
It was reorganized into a more straightforward style.
Field_time:store_TIME(), Field_time_hires::store_TIME() and
TIME_to_longlong_time_packed() were fixed to do a DBUG_ASSERT
on non-zero ltime->year or ltime->month. The code testing ltime->month
was removed from TIME_to_longlong_time_packed(), as it's now
properly done on the caller level.
Truncation was moved from Field_timef::store_TIME() to
Field_time::store_TIME_with_warning().
So now all thee methods Field_time*::store_TIME() assume a properly
set input value:
- Only zero ltime->year and ltime->month are allowed.
- The value must be already properly truncated according to decimals()
(this will help to add rounding soon, see MDEV-8894)
A "const" qualifier was added to the argument of Field_time*::store_TIME().
Diffstat (limited to 'sql')
-rw-r--r-- | sql/compat56.cc | 6 | ||||
-rw-r--r-- | sql/field.cc | 41 | ||||
-rw-r--r-- | sql/field.h | 11 |
3 files changed, 35 insertions, 23 deletions
diff --git a/sql/compat56.cc b/sql/compat56.cc index 704d1db9a98..be54a8760aa 100644 --- a/sql/compat56.cc +++ b/sql/compat56.cc @@ -45,8 +45,10 @@ */ longlong TIME_to_longlong_time_packed(const MYSQL_TIME *ltime) { - /* If month is 0, we mix day with hours: "1 00:10:10" -> "24:00:10" */ - long hms= (((ltime->month ? 0 : ltime->day * 24) + ltime->hour) << 12) | + DBUG_ASSERT(ltime->year == 0); + DBUG_ASSERT(ltime->month == 0); + // Mix days with hours: "1 00:10:10" -> "24:00:10" + long hms= ((ltime->day * 24 + ltime->hour) << 12) | (ltime->minute << 6) | ltime->second; longlong tmp= MY_PACKED_TIME_MAKE(hms, ltime->second_part); return ltime->neg ? -tmp : tmp; diff --git a/sql/field.cc b/sql/field.cc index e954ac5b431..f6c48d7792b 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -5699,34 +5699,38 @@ int Field_time::store_TIME_with_warning(MYSQL_TIME *ltime, int was_cut, int have_smth_to_conv) { - Sql_condition::enum_warning_level trunc_level= Sql_condition::WARN_LEVEL_WARN; - int ret= 2; ASSERT_COLUMN_MARKED_FOR_WRITE_OR_COMPUTED; if (!have_smth_to_conv) { bzero(ltime, sizeof(*ltime)); - was_cut= MYSQL_TIME_WARN_TRUNCATED; - ret= 1; + store_TIME(ltime); + set_warnings(Sql_condition::WARN_LEVEL_WARN, str, MYSQL_TIME_WARN_TRUNCATED); + return 1; } - else if (!MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut) && - ((ltime->year || ltime->month) || - MYSQL_TIME_WARN_HAVE_NOTES(was_cut))) + if (ltime->year != 0 || ltime->month != 0) { - if (ltime->year || ltime->month) - ltime->year= ltime->month= ltime->day= 0; - trunc_level= Sql_condition::WARN_LEVEL_NOTE; - was_cut|= MYSQL_TIME_WARN_TRUNCATED; - ret= 3; + ltime->year= ltime->month= ltime->day= 0; + was_cut|= MYSQL_TIME_NOTE_TRUNCATED; } - set_warnings(trunc_level, str, was_cut, MYSQL_TIMESTAMP_TIME); + my_time_trunc(ltime, decimals()); store_TIME(ltime); - return was_cut ? ret : 0; + if (!MYSQL_TIME_WARN_HAVE_WARNINGS(was_cut) && + MYSQL_TIME_WARN_HAVE_NOTES(was_cut)) + { + set_warnings(Sql_condition::WARN_LEVEL_NOTE, str, + was_cut | MYSQL_TIME_WARN_TRUNCATED); + return 3; + } + set_warnings(Sql_condition::WARN_LEVEL_WARN, str, was_cut); + return was_cut ? 2 : 0; } -void Field_time::store_TIME(MYSQL_TIME *ltime) +void Field_time::store_TIME(const MYSQL_TIME *ltime) { + DBUG_ASSERT(ltime->year == 0); + DBUG_ASSERT(ltime->month == 0); long tmp= (ltime->day*24L+ltime->hour)*10000L + (ltime->minute*100+ltime->second); if (ltime->neg) @@ -5960,8 +5964,10 @@ int Field_time_hires::reset() } -void Field_time_hires::store_TIME(MYSQL_TIME *ltime) +void Field_time_hires::store_TIME(const MYSQL_TIME *ltime) { + DBUG_ASSERT(ltime->year == 0); + DBUG_ASSERT(ltime->month == 0); ulonglong packed= sec_part_shift(pack_time(ltime), dec) + zero_point; store_bigendian(packed, ptr, Field_time_hires::pack_length()); } @@ -6143,9 +6149,8 @@ int Field_timef::reset() return 0; } -void Field_timef::store_TIME(MYSQL_TIME *ltime) +void Field_timef::store_TIME(const MYSQL_TIME *ltime) { - my_time_trunc(ltime, decimals()); longlong tmp= TIME_to_longlong_time_packed(ltime); my_time_packed_to_binary(tmp, ptr, dec); } diff --git a/sql/field.h b/sql/field.h index f225b6269d3..281e95c31bb 100644 --- a/sql/field.h +++ b/sql/field.h @@ -2682,9 +2682,14 @@ class Field_time :public Field_temporal { */ long curdays; protected: - virtual void store_TIME(MYSQL_TIME *ltime); + 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); + void set_warnings(Sql_condition::enum_warning_level level, + const ErrConv *str, int was_cut) + { + Field_temporal::set_warnings(level, str, was_cut, MYSQL_TIMESTAMP_TIME); + } bool check_zero_in_date_with_warn(ulonglong fuzzydate); static void do_field_time(Copy_field *copy); public: @@ -2766,7 +2771,7 @@ public: */ class Field_time_hires :public Field_time_with_dec { longlong zero_point; - void store_TIME(MYSQL_TIME *ltime); + void store_TIME(const MYSQL_TIME *); public: Field_time_hires(uchar *ptr_arg, uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, const LEX_CSTRING *field_name_arg, @@ -2792,7 +2797,7 @@ public: TIME(0..6) - MySQL56 version */ class Field_timef :public Field_time_with_dec { - void store_TIME(MYSQL_TIME *ltime); + void store_TIME(const MYSQL_TIME *); int do_save_field_metadata(uchar *metadata_ptr) { *metadata_ptr= (uchar) decimals(); |