summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
authorAlexander Barkov <bar@mariadb.org>2018-02-04 16:43:02 +0400
committerAlexander Barkov <bar@mariadb.org>2018-02-04 16:43:02 +0400
commit28d4cf0c1b3366c6471866d144ef28fced1e5390 (patch)
tree33739324408063de53681fe4ac8dfb6abdc45689 /sql
parent2ecf2f9b2a2c2126b9c9bd0ba8b58280331f5849 (diff)
downloadmariadb-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.cc6
-rw-r--r--sql/field.cc41
-rw-r--r--sql/field.h11
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();