Commit 37066594 authored by MySQL Build Team's avatar MySQL Build Team

Backport into build-200911241145-5.1.40sp1

> ------------------------------------------------------------
> revno: 3148.9.6
> revision-id: martin.hansson@sun.com-20091102122407-krzh4h0i052lbwr5
> parent: davi.arnaut@sun.com-20091102112236-k3myix2xy8miyv4s
> committer: Martin Hansson <martin.hansson@sun.com>
> branch nick: 5.1bt
> timestamp: Mon 2009-11-02 13:24:07 +0100
> message:
>   Bug#47925: regression of range optimizer and date comparison in 5.1.39!
>   
>   When a query was using a DATE or DATETIME value formatted
>   using any other separator characters beside hyphen '-', a
>   query with a greater-or-equal '>=' condition matching only
>   the greatest value in an indexed column, the result was
>   empty if index range scan was employed.
>   
>   The range optimizer got a new feature between 5.1.38 and
>   5.1.39 that changes a greater-or-equal condition to a
>   greater-than if the value matching that in the query was not
>   present in the table. But the value comparison function
>   compared the dates as strings instead of dates.
>   
>   The bug was fixed by splitting the function
>   get_date_from_str in two: One part that parses and does
>   error checking. This function is now visible outside the
>   module. The old get_date_from_str now calls the new
>   function.
parent 54d239f7
......@@ -1219,3 +1219,201 @@ explain select * from t2 where a=1000 and b<11;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t2 ref a a 5 const 502 Using where
drop table t1, t2;
#
# Bug#47925: regression of range optimizer and date comparison in 5.1.39!
#
CREATE TABLE t1 ( a DATE, KEY ( a ) );
CREATE TABLE t2 ( a DATETIME, KEY ( a ) );
# Make optimizer choose range scan
INSERT INTO t1 VALUES ('2009-09-22'), ('2009-09-22'), ('2009-09-22');
INSERT INTO t1 VALUES ('2009-09-23'), ('2009-09-23'), ('2009-09-23');
INSERT INTO t2 VALUES ('2009-09-22 12:00:00'), ('2009-09-22 12:00:00'),
('2009-09-22 12:00:00');
INSERT INTO t2 VALUES ('2009-09-23 12:00:00'), ('2009-09-23 12:00:00'),
('2009-09-23 12:00:00');
# DATE vs DATE
EXPLAIN
SELECT * FROM t1 WHERE a >= '2009/09/23';
id select_type table type possible_keys key key_len ref rows Extra
X X X range a a X X X X
SELECT * FROM t1 WHERE a >= '2009/09/23';
a
2009-09-23
2009-09-23
2009-09-23
SELECT * FROM t1 WHERE a >= '20090923';
a
2009-09-23
2009-09-23
2009-09-23
SELECT * FROM t1 WHERE a >= 20090923;
a
2009-09-23
2009-09-23
2009-09-23
SELECT * FROM t1 WHERE a >= '2009-9-23';
a
2009-09-23
2009-09-23
2009-09-23
SELECT * FROM t1 WHERE a >= '2009.09.23';
a
2009-09-23
2009-09-23
2009-09-23
SELECT * FROM t1 WHERE a >= '2009:09:23';
a
2009-09-23
2009-09-23
2009-09-23
# DATE vs DATETIME
EXPLAIN
SELECT * FROM t2 WHERE a >= '2009/09/23';
id select_type table type possible_keys key key_len ref rows Extra
X X X range a a X X X X
SELECT * FROM t2 WHERE a >= '2009/09/23';
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
SELECT * FROM t2 WHERE a >= '2009/09/23';
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
SELECT * FROM t2 WHERE a >= '20090923';
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
SELECT * FROM t2 WHERE a >= 20090923;
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
SELECT * FROM t2 WHERE a >= '2009-9-23';
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
SELECT * FROM t2 WHERE a >= '2009.09.23';
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
SELECT * FROM t2 WHERE a >= '2009:09:23';
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
# DATETIME vs DATETIME
EXPLAIN
SELECT * FROM t2 WHERE a >= '2009/09/23 12:00:00';
id select_type table type possible_keys key key_len ref rows Extra
X X X range a a X X X X
SELECT * FROM t2 WHERE a >= '2009/09/23 12:00:00';
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
SELECT * FROM t2 WHERE a >= '20090923120000';
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
SELECT * FROM t2 WHERE a >= 20090923120000;
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
SELECT * FROM t2 WHERE a >= '2009-9-23 12:00:00';
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
SELECT * FROM t2 WHERE a >= '2009.09.23 12:00:00';
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
SELECT * FROM t2 WHERE a >= '2009:09:23 12:00:00';
a
2009-09-23 12:00:00
2009-09-23 12:00:00
2009-09-23 12:00:00
# DATETIME vs DATE
EXPLAIN
SELECT * FROM t1 WHERE a >= '2009/09/23 00:00:00';
id select_type table type possible_keys key key_len ref rows Extra
X X X range a a X X X X
SELECT * FROM t1 WHERE a >= '2009/09/23 00:00:00';
a
2009-09-23
2009-09-23
2009-09-23
SELECT * FROM t1 WHERE a >= '2009/09/23 00:00:00';
a
2009-09-23
2009-09-23
2009-09-23
SELECT * FROM t1 WHERE a >= '20090923000000';
a
2009-09-23
2009-09-23
2009-09-23
SELECT * FROM t1 WHERE a >= 20090923000000;
a
2009-09-23
2009-09-23
2009-09-23
SELECT * FROM t1 WHERE a >= '2009-9-23 00:00:00';
a
2009-09-23
2009-09-23
2009-09-23
SELECT * FROM t1 WHERE a >= '2009.09.23 00:00:00';
a
2009-09-23
2009-09-23
2009-09-23
SELECT * FROM t1 WHERE a >= '2009:09:23 00:00:00';
a
2009-09-23
2009-09-23
2009-09-23
# Test of the new get_date_from_str implementation
# Behavior differs slightly between the trunk and mysql-pe.
# The former may give errors for the truncated values, while the latter
# gives warnings. The purpose of this test is not to interfere, and only
# preserve existing behavior.
SELECT str_to_date('2007-10-00', '%Y-%m-%d') >= '' AND
str_to_date('2007-10-00', '%Y-%m-%d') <= '2007/10/20';
str_to_date('2007-10-00', '%Y-%m-%d') >= '' AND
str_to_date('2007-10-00', '%Y-%m-%d') <= '2007/10/20'
1
Warnings:
Warning 1292 Truncated incorrect date value: ''
SELECT str_to_date('2007-20-00', '%Y-%m-%d') >= '2007/10/20' AND
str_to_date('2007-20-00', '%Y-%m-%d') <= '';
str_to_date('2007-20-00', '%Y-%m-%d') >= '2007/10/20' AND
str_to_date('2007-20-00', '%Y-%m-%d') <= ''
NULL
Warnings:
Warning 1292 Truncated incorrect date value: ''
Error 1411 Incorrect datetime value: '2007-20-00' for function str_to_date
Error 1411 Incorrect datetime value: '2007-20-00' for function str_to_date
SELECT str_to_date('2007-10-00', '%Y-%m-%d') BETWEEN '' AND '2007/10/20';
str_to_date('2007-10-00', '%Y-%m-%d') BETWEEN '' AND '2007/10/20'
1
Warnings:
Warning 1292 Truncated incorrect datetime value: ''
SELECT str_to_date('2007-20-00', '%Y-%m-%d') BETWEEN '2007/10/20' AND '';
str_to_date('2007-20-00', '%Y-%m-%d') BETWEEN '2007/10/20' AND ''
NULL
Warnings:
Error 1411 Incorrect datetime value: '2007-20-00' for function str_to_date
SELECT str_to_date('', '%Y-%m-%d');
str_to_date('', '%Y-%m-%d')
0000-00-00
DROP TABLE t1, t2;
End of 5.1 tests
......@@ -1046,3 +1046,83 @@ explain select * from t2 where a=1000 and b<11;
drop table t1, t2;
--echo #
--echo # Bug#47925: regression of range optimizer and date comparison in 5.1.39!
--echo #
CREATE TABLE t1 ( a DATE, KEY ( a ) );
CREATE TABLE t2 ( a DATETIME, KEY ( a ) );
--echo # Make optimizer choose range scan
INSERT INTO t1 VALUES ('2009-09-22'), ('2009-09-22'), ('2009-09-22');
INSERT INTO t1 VALUES ('2009-09-23'), ('2009-09-23'), ('2009-09-23');
INSERT INTO t2 VALUES ('2009-09-22 12:00:00'), ('2009-09-22 12:00:00'),
('2009-09-22 12:00:00');
INSERT INTO t2 VALUES ('2009-09-23 12:00:00'), ('2009-09-23 12:00:00'),
('2009-09-23 12:00:00');
--echo # DATE vs DATE
--replace_column 1 X 2 X 3 X 7 X 8 X 9 X 10 X
EXPLAIN
SELECT * FROM t1 WHERE a >= '2009/09/23';
SELECT * FROM t1 WHERE a >= '2009/09/23';
SELECT * FROM t1 WHERE a >= '20090923';
SELECT * FROM t1 WHERE a >= 20090923;
SELECT * FROM t1 WHERE a >= '2009-9-23';
SELECT * FROM t1 WHERE a >= '2009.09.23';
SELECT * FROM t1 WHERE a >= '2009:09:23';
--echo # DATE vs DATETIME
--replace_column 1 X 2 X 3 X 7 X 8 X 9 X 10 X
EXPLAIN
SELECT * FROM t2 WHERE a >= '2009/09/23';
SELECT * FROM t2 WHERE a >= '2009/09/23';
SELECT * FROM t2 WHERE a >= '2009/09/23';
SELECT * FROM t2 WHERE a >= '20090923';
SELECT * FROM t2 WHERE a >= 20090923;
SELECT * FROM t2 WHERE a >= '2009-9-23';
SELECT * FROM t2 WHERE a >= '2009.09.23';
SELECT * FROM t2 WHERE a >= '2009:09:23';
--echo # DATETIME vs DATETIME
--replace_column 1 X 2 X 3 X 7 X 8 X 9 X 10 X
EXPLAIN
SELECT * FROM t2 WHERE a >= '2009/09/23 12:00:00';
SELECT * FROM t2 WHERE a >= '2009/09/23 12:00:00';
SELECT * FROM t2 WHERE a >= '20090923120000';
SELECT * FROM t2 WHERE a >= 20090923120000;
SELECT * FROM t2 WHERE a >= '2009-9-23 12:00:00';
SELECT * FROM t2 WHERE a >= '2009.09.23 12:00:00';
SELECT * FROM t2 WHERE a >= '2009:09:23 12:00:00';
--echo # DATETIME vs DATE
--replace_column 1 X 2 X 3 X 7 X 8 X 9 X 10 X
EXPLAIN
SELECT * FROM t1 WHERE a >= '2009/09/23 00:00:00';
SELECT * FROM t1 WHERE a >= '2009/09/23 00:00:00';
SELECT * FROM t1 WHERE a >= '2009/09/23 00:00:00';
SELECT * FROM t1 WHERE a >= '20090923000000';
SELECT * FROM t1 WHERE a >= 20090923000000;
SELECT * FROM t1 WHERE a >= '2009-9-23 00:00:00';
SELECT * FROM t1 WHERE a >= '2009.09.23 00:00:00';
SELECT * FROM t1 WHERE a >= '2009:09:23 00:00:00';
--echo # Test of the new get_date_from_str implementation
--echo # Behavior differs slightly between the trunk and mysql-pe.
--echo # The former may give errors for the truncated values, while the latter
--echo # gives warnings. The purpose of this test is not to interfere, and only
--echo # preserve existing behavior.
SELECT str_to_date('2007-10-00', '%Y-%m-%d') >= '' AND
str_to_date('2007-10-00', '%Y-%m-%d') <= '2007/10/20';
SELECT str_to_date('2007-20-00', '%Y-%m-%d') >= '2007/10/20' AND
str_to_date('2007-20-00', '%Y-%m-%d') <= '';
SELECT str_to_date('2007-10-00', '%Y-%m-%d') BETWEEN '' AND '2007/10/20';
SELECT str_to_date('2007-20-00', '%Y-%m-%d') BETWEEN '2007/10/20' AND '';
SELECT str_to_date('', '%Y-%m-%d');
DROP TABLE t1, t2;
--echo End of 5.1 tests
......@@ -6859,52 +6859,61 @@ void resolve_const_item(THD *thd, Item **ref, Item *comp_item)
}
/**
Compare the value stored in field, with the original item.
Compare the value stored in field with the expression from the query.
@param field field which the item is converted and stored in
@param item original item
@param field Field which the Item is stored in after conversion
@param item Original expression from query
@return Return an integer greater than, equal to, or less than 0 if
@return Returns an integer greater than, equal to, or less than 0 if
the value stored in the field is greater than, equal to,
or less than the original item
or less than the original Item. A 0 may also be returned if
out of memory.
@note We only use this on the range optimizer/partition pruning,
because in some cases we can't store the value in the field
without some precision/character loss.
*/
int stored_field_cmp_to_item(Field *field, Item *item)
int stored_field_cmp_to_item(THD *thd, Field *field, Item *item)
{
Item_result res_type=item_cmp_type(field->result_type(),
item->result_type());
if (res_type == STRING_RESULT)
{
char item_buff[MAX_FIELD_WIDTH];
char field_buff[MAX_FIELD_WIDTH];
String item_tmp(item_buff,sizeof(item_buff),&my_charset_bin),*item_result;
String item_tmp(item_buff,sizeof(item_buff),&my_charset_bin);
String field_tmp(field_buff,sizeof(field_buff),&my_charset_bin);
enum_field_types field_type;
item_result=item->val_str(&item_tmp);
String *item_result= item->val_str(&item_tmp);
/*
Some implementations of Item::val_str(String*) actually modify
the field Item::null_value, hence we can't check it earlier.
*/
if (item->null_value)
return 0;
field->val_str(&field_tmp);
String *field_result= field->val_str(&field_tmp);
/*
If comparing DATE with DATETIME, append the time-part to the DATE.
So that the strings are equally formatted.
A DATE converted to string is 10 characters, and a DATETIME converted
to string is 19 characters.
*/
field_type= field->type();
if (field_type == MYSQL_TYPE_DATE &&
item_result->length() == 19)
field_tmp.append(" 00:00:00");
else if (field_type == MYSQL_TYPE_DATETIME &&
item_result->length() == 10)
item_result->append(" 00:00:00");
return stringcmp(&field_tmp,item_result);
enum_field_types field_type= field->type();
if (field_type == MYSQL_TYPE_DATE || field_type == MYSQL_TYPE_DATETIME)
{
enum_mysql_timestamp_type type= MYSQL_TIMESTAMP_ERROR;
if (field_type == MYSQL_TYPE_DATE)
type= MYSQL_TIMESTAMP_DATE;
if (field_type == MYSQL_TYPE_DATETIME)
type= MYSQL_TIMESTAMP_DATETIME;
const char *field_name= field->field_name;
MYSQL_TIME field_time, item_time;
get_mysql_time_from_str(thd, field_result, type, field_name, &field_time);
get_mysql_time_from_str(thd, item_result, type, field_name, &item_time);
return my_time_compare(&field_time, &item_time);
}
return stringcmp(field_result, item_result);
}
if (res_type == INT_RESULT)
return 0; // Both are of type int
......
......@@ -3124,4 +3124,4 @@ void mark_select_range_as_dependent(THD *thd,
extern Cached_item *new_Cached_item(THD *thd, Item *item);
extern Item_result item_cmp_type(Item_result a,Item_result b);
extern void resolve_const_item(THD *thd, Item **ref, Item *cmp_item);
extern int stored_field_cmp_to_item(Field *field, Item *item);
extern int stored_field_cmp_to_item(THD *thd, Field *field, Item *item);
......@@ -636,56 +636,51 @@ int Arg_comparator::set_compare_func(Item_bool_func2 *item, Item_result type)
return 0;
}
/**
@brief Convert date provided in a string to the int representation.
@param[in] thd thread handle
@param[in] str a string to convert
@param[in] warn_type type of the timestamp for issuing the warning
@param[in] warn_name field name for issuing the warning
@param[out] error_arg could not extract a DATE or DATETIME
@details Convert date provided in the string str to the int
representation. If the string contains wrong date or doesn't
contain it at all then a warning is issued. The warn_type and
the warn_name arguments are used as the name and the type of the
field when issuing the warning. If any input was discarded
(trailing or non-timestampy characters), was_cut will be non-zero.
was_type will return the type str_to_datetime() could correctly
extract.
@return
converted value. 0 on error and on zero-dates -- check 'failure'
Parse date provided in a string to a MYSQL_TIME.
@param[in] thd Thread handle
@param[in] str A string to convert
@param[in] warn_type Type of the timestamp for issuing the warning
@param[in] warn_name Field name for issuing the warning
@param[out] l_time The MYSQL_TIME objects is initialized.
Parses a date provided in the string str into a MYSQL_TIME object. If the
string contains an incorrect date or doesn't correspond to a date at all
then a warning is issued. The warn_type and the warn_name arguments are used
as the name and the type of the field when issuing the warning. If any input
was discarded (trailing or non-timestamp-y characters), return value will be
TRUE.
@return Status flag
@retval FALSE Success.
@retval True Indicates failure.
*/
static ulonglong
get_date_from_str(THD *thd, String *str, timestamp_type warn_type,
char *warn_name, bool *error_arg)
bool get_mysql_time_from_str(THD *thd, String *str, timestamp_type warn_type,
const char *warn_name, MYSQL_TIME *l_time)
{
ulonglong value= 0;
bool value;
int error;
MYSQL_TIME l_time;
enum_mysql_timestamp_type ret;
enum_mysql_timestamp_type timestamp_type;
ret= str_to_datetime(str->ptr(), str->length(), &l_time,
timestamp_type=
str_to_datetime(str->ptr(), str->length(), l_time,
(TIME_FUZZY_DATE | MODE_INVALID_DATES |
(thd->variables.sql_mode &
(MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE))),
&error);
if (ret == MYSQL_TIMESTAMP_DATETIME || ret == MYSQL_TIMESTAMP_DATE)
{
if (timestamp_type == MYSQL_TIMESTAMP_DATETIME ||
timestamp_type == MYSQL_TIMESTAMP_DATE)
/*
Do not return yet, we may still want to throw a "trailing garbage"
warning.
*/
*error_arg= FALSE;
value= TIME_to_ulonglong_datetime(&l_time);
}
value= FALSE;
else
{
*error_arg= TRUE;
value= TRUE;
error= 1; /* force warning */
}
......@@ -698,6 +693,37 @@ get_date_from_str(THD *thd, String *str, timestamp_type warn_type,
}
/**
@brief Convert date provided in a string to the int representation.
@param[in] thd thread handle
@param[in] str a string to convert
@param[in] warn_type type of the timestamp for issuing the warning
@param[in] warn_name field name for issuing the warning
@param[out] error_arg could not extract a DATE or DATETIME
@details Convert date provided in the string str to the int
representation. If the string contains wrong date or doesn't
contain it at all then a warning is issued. The warn_type and
the warn_name arguments are used as the name and the type of the
field when issuing the warning.
@return
converted value. 0 on error and on zero-dates -- check 'failure'
*/
static ulonglong get_date_from_str(THD *thd, String *str,
timestamp_type warn_type,
const char *warn_name, bool *error_arg)
{
MYSQL_TIME l_time;
*error_arg= get_mysql_time_from_str(thd, str, warn_type, warn_name, &l_time);
if (*error_arg)
return 0;
return TIME_to_ulonglong_datetime(&l_time);
}
/*
Check whether compare_datetime() can be used to compare items.
......
......@@ -1721,3 +1721,6 @@ inline Item *and_conds(Item *a, Item *b)
}
Item *and_expressions(Item *a, Item *b, Item **org_item);
bool get_mysql_time_from_str(THD *thd, String *str, timestamp_type warn_type,
const char *warn_name, MYSQL_TIME *l_time);
......@@ -5954,7 +5954,7 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field,
switch (type) {
case Item_func::LT_FUNC:
if (stored_field_cmp_to_item(field,value) == 0)
if (stored_field_cmp_to_item(param->thd, field, value) == 0)
tree->max_flag=NEAR_MAX;
/* fall through */
case Item_func::LE_FUNC:
......@@ -5969,14 +5969,14 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field,
case Item_func::GT_FUNC:
/* Don't use open ranges for partial key_segments */
if ((!(key_part->flag & HA_PART_KEY_SEG)) &&
(stored_field_cmp_to_item(field, value) <= 0))
(stored_field_cmp_to_item(param->thd, field, value) <= 0))
tree->min_flag=NEAR_MIN;
tree->max_flag= NO_MAX_RANGE;
break;
case Item_func::GE_FUNC:
/* Don't use open ranges for partial key_segments */
if ((!(key_part->flag & HA_PART_KEY_SEG)) &&
(stored_field_cmp_to_item(field,value) < 0))
(stored_field_cmp_to_item(param->thd, field, value) < 0))
tree->min_flag= NEAR_MIN;
tree->max_flag=NO_MAX_RANGE;
break;
......
......@@ -965,20 +965,22 @@ calc_time_diff(MYSQL_TIME *l_time1, MYSQL_TIME *l_time2, int l_sign, longlong *s
0 - a == b
1 - a > b
NOTES
TIME.second_part is not considered during comparison
*/
int
my_time_compare(MYSQL_TIME *a, MYSQL_TIME *b)
int my_time_compare(MYSQL_TIME *a, MYSQL_TIME *b)
{
my_ulonglong a_t= TIME_to_ulonglong_datetime(a);
my_ulonglong b_t= TIME_to_ulonglong_datetime(b);
ulonglong a_t= TIME_to_ulonglong_datetime(a);
ulonglong b_t= TIME_to_ulonglong_datetime(b);
if (a_t < b_t)
return -1;
if (a_t > b_t)
return 1;
else if (a_t < b_t)
if (a->second_part < b->second_part)
return -1;
if (a->second_part > b->second_part)
return 1;
return 0;
}
......
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