diff options
author | Alexander Barkov <bar@mariadb.com> | 2018-08-11 06:47:48 +0400 |
---|---|---|
committer | Alexander Barkov <bar@mariadb.com> | 2018-08-11 06:47:48 +0400 |
commit | 2085f14a8dddf175765d97dcb5306bc2a0578b00 (patch) | |
tree | 24979de5af9a1fef91752d548be6a296e68338c1 | |
parent | 2966c1e422866f48b68e109ac9e27d52be77fb60 (diff) | |
download | mariadb-git-2085f14a8dddf175765d97dcb5306bc2a0578b00.tar.gz |
MDEV-16938 Move Item::get_time_with_conversion() to Time
The affected code is well covered by tests for MDEV-8766.
Adding only the missing part: the old mode OLD_MODE_ZERO_DATE_TIME_CAST
in combination with 0000-MM-00 and YYYY-00-00.
The old mode in combination with 0000-00-DD was already covered,
so was the new mode with all types of DATETIME values.
-rw-r--r-- | mysql-test/main/type_time.result | 12 | ||||
-rw-r--r-- | mysql-test/main/type_time.test | 5 | ||||
-rw-r--r-- | sql/field.cc | 32 | ||||
-rw-r--r-- | sql/item.cc | 44 | ||||
-rw-r--r-- | sql/item.h | 34 | ||||
-rw-r--r-- | sql/sql_time.cc | 20 | ||||
-rw-r--r-- | sql/sql_time.h | 44 | ||||
-rw-r--r-- | sql/sql_type.h | 43 |
8 files changed, 77 insertions, 157 deletions
diff --git a/mysql-test/main/type_time.result b/mysql-test/main/type_time.result index 51b2aecaa6b..8f50d49d4b9 100644 --- a/mysql-test/main/type_time.result +++ b/mysql-test/main/type_time.result @@ -841,6 +841,18 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 8 100.00 Using where Warnings: Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = TIMESTAMP'0000-00-01 10:20:30' and octet_length(`test`.`t1`.`a`) = 30 + rand() +EXPLAIN EXTENDED +SELECT * FROM t1 WHERE a=TIMESTAMP'0000-01-00 10:20:30' AND LENGTH(a)=8; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 8 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = TIMESTAMP'0000-01-00 10:20:30' and octet_length(`test`.`t1`.`a`) = 8 +EXPLAIN EXTENDED +SELECT * FROM t1 WHERE a=TIMESTAMP'0001-00-00 10:20:30' AND LENGTH(a)=8; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 8 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = TIMESTAMP'0001-00-00 10:20:30' and octet_length(`test`.`t1`.`a`) = 8 # Old mode, TIMESTAMP-alike string literal, zero YYYYMMDD, Ok to propagate SELECT * FROM t1 WHERE a='0000-00-00 10:20:30'; a diff --git a/mysql-test/main/type_time.test b/mysql-test/main/type_time.test index 17494cec04f..f4a1a474046 100644 --- a/mysql-test/main/type_time.test +++ b/mysql-test/main/type_time.test @@ -514,6 +514,11 @@ SELECT * FROM t1 WHERE a=TIMESTAMP'0000-00-01 10:20:30' AND LENGTH(a)=8; EXPLAIN EXTENDED SELECT * FROM t1 WHERE a=TIMESTAMP'0000-00-01 10:20:30' AND LENGTH(a)=30+RAND(); +EXPLAIN EXTENDED +SELECT * FROM t1 WHERE a=TIMESTAMP'0000-01-00 10:20:30' AND LENGTH(a)=8; +EXPLAIN EXTENDED +SELECT * FROM t1 WHERE a=TIMESTAMP'0001-00-00 10:20:30' AND LENGTH(a)=8; + --echo # Old mode, TIMESTAMP-alike string literal, zero YYYYMMDD, Ok to propagate SELECT * FROM t1 WHERE a='0000-00-00 10:20:30'; SELECT * FROM t1 WHERE a='0000-00-00 10:20:30' AND LENGTH(a)=8; diff --git a/sql/field.cc b/sql/field.cc index 50644bf4f05..217fd2bb6e8 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -5952,14 +5952,28 @@ bool Field_time::can_be_substituted_to_equal_item(const Context &ctx, Item *Field_time::get_equal_const_item(THD *thd, const Context &ctx, Item *const_item) { + /* + Old mode conversion from DATETIME with non-zero YYYYMMDD part + to TIME works very inconsistently. Possible variants: + - truncate the YYYYMMDD part + - add (MM*33+DD)*24 to hours + - add (MM*31+DD)*24 to hours + Let's disallow propagation of DATETIME with non-zero YYYYMMDD + as an equal constant for a TIME field. + */ + Time::datetime_to_time_mode_t mode= + (thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST) ? + Time::DATETIME_TO_TIME_YYYYMMDD_00000000_ONLY : + Time::DATETIME_TO_TIME_MINUS_CURRENT_DATE; + switch (ctx.subst_constraint()) { case ANY_SUBST: if (const_item->field_type() != MYSQL_TYPE_TIME) { - MYSQL_TIME ltime; // Get the value of const_item with conversion from DATETIME to TIME - ulonglong fuzzydate= Time::comparison_flags_for_get_date(); - if (const_item->get_time_with_conversion(thd, <ime, fuzzydate)) + Time tm(const_item, + Time::Options(Time::comparison_flags_for_get_date(), mode)); + if (!tm.is_valid_time()) return NULL; /* Replace a DATE/DATETIME constant to a TIME constant: @@ -5971,8 +5985,9 @@ Item *Field_time::get_equal_const_item(THD *thd, const Context &ctx, (assuming CURRENT_DATE is '2015-08-30' */ - return new (thd->mem_root) Item_time_literal(thd, <ime, - ltime.second_part ? + return new (thd->mem_root) Item_time_literal(thd, tm.get_mysql_time(), + tm.get_mysql_time()-> + second_part ? TIME_SECOND_PART_DIGITS : 0); } @@ -5981,8 +5996,8 @@ Item *Field_time::get_equal_const_item(THD *thd, const Context &ctx, if (const_item->field_type() != MYSQL_TYPE_TIME || const_item->decimals != decimals()) { - MYSQL_TIME ltime; - if (const_item->get_time_with_conversion(thd, <ime, TIME_TIME_ONLY)) + Time tm(const_item, Time::Options(TIME_TIME_ONLY, mode)); + if (!tm.is_valid_time()) return NULL; /* Note, the value returned in "ltime" can have more fractional @@ -5998,7 +6013,8 @@ Item *Field_time::get_equal_const_item(THD *thd, const Context &ctx, The optimized WHERE will return with "Impossible WHERE", without having to do the full table scan. */ - return new (thd->mem_root) Item_time_literal(thd, <ime, decimals()); + return new (thd->mem_root) Item_time_literal(thd, tm.get_mysql_time(), + decimals()); } break; } diff --git a/sql/item.cc b/sql/item.cc index 8b962d1706d..4422d73787b 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -128,50 +128,6 @@ longlong Item::val_datetime_packed_result() } -/** - Get date/time/datetime. - If DATETIME or DATE result is returned, it's converted to TIME. -*/ -bool Item::get_time_with_conversion(THD *thd, MYSQL_TIME *ltime, - ulonglong fuzzydate) -{ - if (get_date(ltime, fuzzydate)) - return true; - if (ltime->time_type != MYSQL_TIMESTAMP_TIME) - { - MYSQL_TIME ltime2; - if ((thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST) && - (ltime->year || ltime->day || ltime->month)) - { - /* - Old mode conversion from DATETIME with non-zero YYYYMMDD part - to TIME works very inconsistently. Possible variants: - - truncate the YYYYMMDD part - - add (MM*33+DD)*24 to hours - - add (MM*31+DD)*24 to hours - Let's return TRUE here, to disallow equal field propagation. - Note, If we start to use this method in more pieces of the code other - than equal field propagation, we should probably return - TRUE only if some flag in fuzzydate is set. - */ - return true; - } - if (datetime_to_time_with_warn(thd, ltime, <ime2, TIME_SECOND_PART_DIGITS)) - { - /* - If the time difference between CURRENT_DATE and ltime - did not fit into the supported TIME range, then we set the - difference to the maximum possible value in the supported TIME range - */ - DBUG_ASSERT(0); - return (null_value= true); - } - *ltime= ltime2; - } - return false; -} - - /* For the items which don't have its own fast val_str_ascii() implementation we provide a generic slower version, diff --git a/sql/item.h b/sql/item.h index a96406fa0cd..73584fd3293 100644 --- a/sql/item.h +++ b/sql/item.h @@ -1611,40 +1611,6 @@ public: bool get_date_from_string(MYSQL_TIME *ltime, ulonglong fuzzydate); bool get_time(MYSQL_TIME *ltime) { return get_date(ltime, Time::flags_for_get_date()); } - /* - Get time with automatic DATE/DATETIME to TIME conversion, - by subtracting CURRENT_DATE. - - Performce a reverse operation to CAST(time AS DATETIME) - Suppose: - - we have a set of items (typically with the native MYSQL_TYPE_TIME type) - whose item->get_date() return TIME1 value, and - - CAST(AS DATETIME) for the same Items return DATETIME1, - after applying time-to-datetime conversion to TIME1. - - then all items (typically of the native MYSQL_TYPE_{DATE|DATETIME} types) - whose get_date() return DATETIME1 must also return TIME1 from - get_time_with_conversion() - - @param thd - the thread, its variables.old_mode is checked - to decide if use simple YYYYMMDD truncation (old mode), - or perform full DATETIME-to-TIME conversion with - CURRENT_DATE subtraction. - @param[out] ltime - store the result here - @param fuzzydate - flags to be used for the get_date() call. - Normally, should include TIME_TIME_ONLY, to let - the called low-level routines, e.g. str_to_date(), - know that we prefer TIME rather that DATE/DATETIME - and do less conversion outside of the low-level - routines. - - @returns true - on error, e.g. get_date() returned NULL value, - or get_date() returned DATETIME/DATE with non-zero - YYYYMMDD part. - @returns false - on success - */ - bool get_time_with_conversion(THD *thd, MYSQL_TIME *ltime, - ulonglong fuzzydate); // Get a DATE or DATETIME value in numeric packed format for comparison virtual longlong val_datetime_packed() { diff --git a/sql/sql_time.cc b/sql/sql_time.cc index c75b8b19e1a..430eb192052 100644 --- a/sql/sql_time.cc +++ b/sql/sql_time.cc @@ -1365,26 +1365,6 @@ time_to_datetime_with_warn(THD *thd, } -bool datetime_to_time_with_warn(THD *thd, const MYSQL_TIME *dt, - MYSQL_TIME *tm, uint dec) -{ - if (thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST) - { - *tm= *dt; - datetime_to_time(tm); - return false; - } - else /* new mode */ - { - MYSQL_TIME current_date; - set_current_date(thd, ¤t_date); - calc_time_diff(dt, ¤t_date, 1, tm, 0); - } - int warnings= 0; - return check_time_range(tm, dec, &warnings); -} - - longlong pack_time(const MYSQL_TIME *my_time) { return ((((((my_time->year * 13ULL + diff --git a/sql/sql_time.h b/sql/sql_time.h index cfa1468841b..8c6e58856e6 100644 --- a/sql/sql_time.h +++ b/sql/sql_time.h @@ -54,50 +54,6 @@ bool time_to_datetime(THD *thd, const MYSQL_TIME *tm, MYSQL_TIME *dt); bool time_to_datetime_with_warn(THD *thd, const MYSQL_TIME *tm, MYSQL_TIME *dt, ulonglong fuzzydate); -/* - Simply truncate the YYYY-MM-DD part to 0000-00-00 - and change time_type to MYSQL_TIMESTAMP_TIME -*/ -inline void datetime_to_time(MYSQL_TIME *ltime) -{ - DBUG_ASSERT(ltime->time_type == MYSQL_TIMESTAMP_DATE || - ltime->time_type == MYSQL_TIMESTAMP_DATETIME); - DBUG_ASSERT(ltime->neg == 0); - ltime->year= ltime->month= ltime->day= 0; - ltime->time_type= MYSQL_TIMESTAMP_TIME; -} - - -/** - Convert DATE/DATETIME to TIME(dec) - using CURRENT_DATE in a non-old mode, - or using simple truncation in old mode (OLD_MODE_ZERO_DATE_TIME_CAST). - - @param thd - the thread to get the variables.old_behaviour value from - @param dt - the DATE of DATETIME value to convert - @param[out] tm - store result here - @param dec - the desired scale. The fractional part of the result - is checked according to this parameter before returning - the conversion result. "dec" is important in the corner - cases near the max/min limits. - If the result is '838:59:59.999999' and the desired scale - is less than 6, an error is returned. - Note, dec is not important in the - OLD_MODE_ZERO_DATE_TIME_CAST old mode. - - - in case of OLD_MODE_ZERO_DATE_TIME_CAST - the TIME part is simply truncated and "false" is returned. - - otherwise, the result is calculated effectively similar to: - TIMEDIFF(dt, CAST(CURRENT_DATE AS DATETIME)) - If the difference fits into the supported TIME range, "false" is returned, - otherwise a warning is issued and "true" is returned. - - @return false - on success - @return true - on error -*/ -bool datetime_to_time_with_warn(THD *, const MYSQL_TIME *dt, - MYSQL_TIME *tm, uint dec); - inline void datetime_to_date(MYSQL_TIME *ltime) { diff --git a/sql/sql_type.h b/sql/sql_type.h index 3465708f768..40433fdaabd 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -534,7 +534,9 @@ public: enum datetime_to_time_mode_t { DATETIME_TO_TIME_YYYYMMDD_000000DD_MIX_TO_HOURS, - DATETIME_TO_TIME_YYYYMMDD_TRUNCATE + DATETIME_TO_TIME_YYYYMMDD_TRUNCATE, + DATETIME_TO_TIME_YYYYMMDD_00000000_ONLY, + DATETIME_TO_TIME_MINUS_CURRENT_DATE }; class Options { @@ -601,6 +603,21 @@ private: hour+= from_day * 24; } /* + The result is calculated effectively similar to: + TIMEDIFF(dt, CAST(CURRENT_DATE AS DATETIME)) + If the difference does not fit to the supported TIME range, it's truncated. + */ + void datetime_to_time_minus_current_date(THD *thd) + { + MYSQL_TIME current_date, tmp; + set_current_date(thd, ¤t_date); + calc_time_diff(this, ¤t_date, 1, &tmp, 0); + static_cast<MYSQL_TIME*>(this)[0]= tmp; + int warnings= 0; + (void) check_time_range(this, TIME_SECOND_PART_DIGITS, &warnings); + DBUG_ASSERT(is_valid_time()); + } + /* Convert a valid DATE or DATETIME to TIME. Before this call, "this" must be a valid DATE or DATETIME value, e.g. returned from Item::get_date(), str_to_time(), number_to_time(). @@ -621,11 +638,18 @@ private: */ DBUG_ASSERT(day < 32); DBUG_ASSERT(hour < 24); - if (opt.datetime_to_time_mode() == - DATETIME_TO_TIME_YYYYMMDD_000000DD_MIX_TO_HOURS) - datetime_to_time_YYYYMMDD_000000DD_mix_to_hours(warn, year, month, day); - year= month= day= 0; - time_type= MYSQL_TIMESTAMP_TIME; + if (opt.datetime_to_time_mode() == DATETIME_TO_TIME_MINUS_CURRENT_DATE) + { + datetime_to_time_minus_current_date(current_thd); + } + else + { + if (opt.datetime_to_time_mode() == + DATETIME_TO_TIME_YYYYMMDD_000000DD_MIX_TO_HOURS) + datetime_to_time_YYYYMMDD_000000DD_mix_to_hours(warn, year, month, day); + year= month= day= 0; + time_type= MYSQL_TIMESTAMP_TIME; + } DBUG_ASSERT(is_valid_time_slow()); } /** @@ -646,7 +670,12 @@ private: switch (time_type) { case MYSQL_TIMESTAMP_DATE: case MYSQL_TIMESTAMP_DATETIME: - valid_datetime_to_valid_time(warn, opt); + if (opt.datetime_to_time_mode() == + DATETIME_TO_TIME_YYYYMMDD_00000000_ONLY && + (year || month || day)) + make_from_out_of_range(warn); + else + valid_datetime_to_valid_time(warn, opt); break; case MYSQL_TIMESTAMP_NONE: break; |