Commit 2085f14a authored by Alexander Barkov's avatar Alexander Barkov

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.
parent 2966c1e4
......@@ -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
......
......@@ -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;
......
......@@ -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, &ltime, 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, &ltime,
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, &ltime, 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, &ltime, decimals());
return new (thd->mem_root) Item_time_literal(thd, tm.get_mysql_time(),
decimals());
}
break;
}
......
......@@ -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, &ltime2, 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,
......
......@@ -1611,40 +1611,6 @@ class Item: public Value_source,
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()
{
......
......@@ -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, &current_date);
calc_time_diff(dt, &current_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 +
......
......@@ -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)
{
......
......@@ -534,7 +534,9 @@ class Time: public Temporal
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
{
......@@ -600,6 +602,21 @@ class Time: public Temporal
else
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, &current_date);
calc_time_diff(this, &current_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,
......@@ -621,11 +638,18 @@ class Time: public Temporal
*/
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 @@ class Time: public Temporal
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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment