mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
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.
This commit is contained in:
@ -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
|
1 SIMPLE t1 ALL NULL NULL NULL NULL 8 100.00 Using where
|
||||||
Warnings:
|
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()
|
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
|
# 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';
|
||||||
a
|
a
|
||||||
|
@ -514,6 +514,11 @@ SELECT * FROM t1 WHERE a=TIMESTAMP'0000-00-01 10:20:30' AND LENGTH(a)=8;
|
|||||||
EXPLAIN EXTENDED
|
EXPLAIN EXTENDED
|
||||||
SELECT * FROM t1 WHERE a=TIMESTAMP'0000-00-01 10:20:30' AND LENGTH(a)=30+RAND();
|
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
|
--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';
|
||||||
SELECT * FROM t1 WHERE a='0000-00-00 10:20:30' AND LENGTH(a)=8;
|
SELECT * FROM t1 WHERE a='0000-00-00 10:20:30' AND LENGTH(a)=8;
|
||||||
|
32
sql/field.cc
32
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 *Field_time::get_equal_const_item(THD *thd, const Context &ctx,
|
||||||
Item *const_item)
|
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()) {
|
switch (ctx.subst_constraint()) {
|
||||||
case ANY_SUBST:
|
case ANY_SUBST:
|
||||||
if (const_item->field_type() != MYSQL_TYPE_TIME)
|
if (const_item->field_type() != MYSQL_TYPE_TIME)
|
||||||
{
|
{
|
||||||
MYSQL_TIME ltime;
|
|
||||||
// Get the value of const_item with conversion from DATETIME to TIME
|
// Get the value of const_item with conversion from DATETIME to TIME
|
||||||
ulonglong fuzzydate= Time::comparison_flags_for_get_date();
|
Time tm(const_item,
|
||||||
if (const_item->get_time_with_conversion(thd, <ime, fuzzydate))
|
Time::Options(Time::comparison_flags_for_get_date(), mode));
|
||||||
|
if (!tm.is_valid_time())
|
||||||
return NULL;
|
return NULL;
|
||||||
/*
|
/*
|
||||||
Replace a DATE/DATETIME constant to a TIME constant:
|
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'
|
(assuming CURRENT_DATE is '2015-08-30'
|
||||||
*/
|
*/
|
||||||
return new (thd->mem_root) Item_time_literal(thd, <ime,
|
return new (thd->mem_root) Item_time_literal(thd, tm.get_mysql_time(),
|
||||||
ltime.second_part ?
|
tm.get_mysql_time()->
|
||||||
|
second_part ?
|
||||||
TIME_SECOND_PART_DIGITS :
|
TIME_SECOND_PART_DIGITS :
|
||||||
0);
|
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 ||
|
if (const_item->field_type() != MYSQL_TYPE_TIME ||
|
||||||
const_item->decimals != decimals())
|
const_item->decimals != decimals())
|
||||||
{
|
{
|
||||||
MYSQL_TIME ltime;
|
Time tm(const_item, Time::Options(TIME_TIME_ONLY, mode));
|
||||||
if (const_item->get_time_with_conversion(thd, <ime, TIME_TIME_ONLY))
|
if (!tm.is_valid_time())
|
||||||
return NULL;
|
return NULL;
|
||||||
/*
|
/*
|
||||||
Note, the value returned in "ltime" can have more fractional
|
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
|
The optimized WHERE will return with "Impossible WHERE", without
|
||||||
having to do the full table scan.
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
44
sql/item.cc
44
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()
|
For the items which don't have its own fast val_str_ascii()
|
||||||
implementation we provide a generic slower version,
|
implementation we provide a generic slower version,
|
||||||
|
34
sql/item.h
34
sql/item.h
@ -1611,40 +1611,6 @@ public:
|
|||||||
bool get_date_from_string(MYSQL_TIME *ltime, ulonglong fuzzydate);
|
bool get_date_from_string(MYSQL_TIME *ltime, ulonglong fuzzydate);
|
||||||
bool get_time(MYSQL_TIME *ltime)
|
bool get_time(MYSQL_TIME *ltime)
|
||||||
{ return get_date(ltime, Time::flags_for_get_date()); }
|
{ 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
|
// Get a DATE or DATETIME value in numeric packed format for comparison
|
||||||
virtual longlong val_datetime_packed()
|
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, ¤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)
|
longlong pack_time(const MYSQL_TIME *my_time)
|
||||||
{
|
{
|
||||||
return ((((((my_time->year * 13ULL +
|
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,
|
bool time_to_datetime_with_warn(THD *thd,
|
||||||
const MYSQL_TIME *tm, MYSQL_TIME *dt,
|
const MYSQL_TIME *tm, MYSQL_TIME *dt,
|
||||||
ulonglong fuzzydate);
|
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)
|
inline void datetime_to_date(MYSQL_TIME *ltime)
|
||||||
{
|
{
|
||||||
|
@ -534,7 +534,9 @@ public:
|
|||||||
enum datetime_to_time_mode_t
|
enum datetime_to_time_mode_t
|
||||||
{
|
{
|
||||||
DATETIME_TO_TIME_YYYYMMDD_000000DD_MIX_TO_HOURS,
|
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
|
class Options
|
||||||
{
|
{
|
||||||
@ -600,6 +602,21 @@ private:
|
|||||||
else
|
else
|
||||||
hour+= from_day * 24;
|
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.
|
Convert a valid DATE or DATETIME to TIME.
|
||||||
Before this call, "this" must be a valid DATE or DATETIME value,
|
Before this call, "this" must be a valid DATE or DATETIME value,
|
||||||
@ -621,11 +638,18 @@ private:
|
|||||||
*/
|
*/
|
||||||
DBUG_ASSERT(day < 32);
|
DBUG_ASSERT(day < 32);
|
||||||
DBUG_ASSERT(hour < 24);
|
DBUG_ASSERT(hour < 24);
|
||||||
|
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() ==
|
if (opt.datetime_to_time_mode() ==
|
||||||
DATETIME_TO_TIME_YYYYMMDD_000000DD_MIX_TO_HOURS)
|
DATETIME_TO_TIME_YYYYMMDD_000000DD_MIX_TO_HOURS)
|
||||||
datetime_to_time_YYYYMMDD_000000DD_mix_to_hours(warn, year, month, day);
|
datetime_to_time_YYYYMMDD_000000DD_mix_to_hours(warn, year, month, day);
|
||||||
year= month= day= 0;
|
year= month= day= 0;
|
||||||
time_type= MYSQL_TIMESTAMP_TIME;
|
time_type= MYSQL_TIMESTAMP_TIME;
|
||||||
|
}
|
||||||
DBUG_ASSERT(is_valid_time_slow());
|
DBUG_ASSERT(is_valid_time_slow());
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -646,6 +670,11 @@ private:
|
|||||||
switch (time_type) {
|
switch (time_type) {
|
||||||
case MYSQL_TIMESTAMP_DATE:
|
case MYSQL_TIMESTAMP_DATE:
|
||||||
case MYSQL_TIMESTAMP_DATETIME:
|
case MYSQL_TIMESTAMP_DATETIME:
|
||||||
|
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);
|
valid_datetime_to_valid_time(warn, opt);
|
||||||
break;
|
break;
|
||||||
case MYSQL_TIMESTAMP_NONE:
|
case MYSQL_TIMESTAMP_NONE:
|
||||||
|
Reference in New Issue
Block a user