Commit c70cacac authored by Alexander Barkov's avatar Alexander Barkov

MDEV-6679 Different optimizer plan for "a BETWEEN 'string' AND ?" and "a BETWEEN ? AND 'string'"

Item_string::eq() and Item_param::eq() in string context behaved differently.
Introducing a new class Item_basic_value to share the eq() code between
literals (Item_int, Item_double, Item_string, Item_null) and Item_param.
parent e2bf6027
...@@ -5962,5 +5962,32 @@ NULL ...@@ -5962,5 +5962,32 @@ NULL
DEALLOCATE PREPARE stmt; DEALLOCATE PREPARE stmt;
SET NAMES utf8; SET NAMES utf8;
# #
# MDEV-6679 Different optimizer plan for "a BETWEEN 'string' AND ?" and "a BETWEEN ? AND 'string'"
#
SET NAMES utf8, collation_connection=utf8_swedish_ci;
CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET utf8, b INT NOT NULL DEFAULT 0, key(a));
INSERT INTO t1 (a) VALUES ('a'),('b'),('c'),('d'),('¢');
SET @arg='¢';
PREPARE stmt FROM "EXPLAIN SELECT * FROM t1 WHERE a BETWEEN _utf8'¢' and ?";
EXECUTE stmt USING @arg;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range a a 33 NULL 1 Using index condition
PREPARE stmt FROM "EXPLAIN SELECT * FROM t1 WHERE a between ? and _utf8'¢'";
EXECUTE stmt USING @arg;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range a a 33 NULL 1 Using index condition
DEALLOCATE PREPARE stmt;
ALTER TABLE t1 CONVERT TO CHARACTER SET latin1;
PREPARE stmt FROM "EXPLAIN SELECT * FROM t1 WHERE a BETWEEN _utf8'¢' and ?";
EXECUTE stmt USING @arg;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range a a 13 NULL 1 Using index condition
PREPARE stmt FROM "EXPLAIN SELECT * FROM t1 WHERE a between ? and _utf8'¢'";
EXECUTE stmt USING @arg;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range a a 13 NULL 1 Using index condition
DEALLOCATE PREPARE stmt;
DROP TABLE t1;
#
# End of 10.0 tests # End of 10.0 tests
# #
...@@ -1681,6 +1681,27 @@ EXECUTE stmt USING @no_such_var; ...@@ -1681,6 +1681,27 @@ EXECUTE stmt USING @no_such_var;
DEALLOCATE PREPARE stmt; DEALLOCATE PREPARE stmt;
SET NAMES utf8; SET NAMES utf8;
--echo #
--echo # MDEV-6679 Different optimizer plan for "a BETWEEN 'string' AND ?" and "a BETWEEN ? AND 'string'"
--echo #
SET NAMES utf8, collation_connection=utf8_swedish_ci;
CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET utf8, b INT NOT NULL DEFAULT 0, key(a));
INSERT INTO t1 (a) VALUES ('a'),('b'),('c'),('d'),('¢');
SET @arg='¢';
PREPARE stmt FROM "EXPLAIN SELECT * FROM t1 WHERE a BETWEEN _utf8'¢' and ?";
EXECUTE stmt USING @arg;
PREPARE stmt FROM "EXPLAIN SELECT * FROM t1 WHERE a between ? and _utf8'¢'";
EXECUTE stmt USING @arg;
DEALLOCATE PREPARE stmt;
ALTER TABLE t1 CONVERT TO CHARACTER SET latin1;
PREPARE stmt FROM "EXPLAIN SELECT * FROM t1 WHERE a BETWEEN _utf8'¢' and ?";
EXECUTE stmt USING @arg;
PREPARE stmt FROM "EXPLAIN SELECT * FROM t1 WHERE a between ? and _utf8'¢'";
EXECUTE stmt USING @arg;
DEALLOCATE PREPARE stmt;
DROP TABLE t1;
--echo # --echo #
--echo # End of 10.0 tests --echo # End of 10.0 tests
--echo # --echo #
...@@ -1295,19 +1295,6 @@ Item *Item_param::safe_charset_converter(CHARSET_INFO *tocs) ...@@ -1295,19 +1295,6 @@ Item *Item_param::safe_charset_converter(CHARSET_INFO *tocs)
} }
bool Item_string::eq(const Item *item, bool binary_cmp) const
{
if (type() == item->type() && item->basic_const_item())
{
if (binary_cmp)
return !stringcmp(&str_value, &item->str_value);
return (collation.collation == item->collation.collation &&
!sortcmp(&str_value, &item->str_value, collation.collation));
}
return 0;
}
/** /**
Get the value of the function as a MYSQL_TIME structure. Get the value of the function as a MYSQL_TIME structure.
As a extra convenience the time structure is reset on error or NULL values! As a extra convenience the time structure is reset on error or NULL values!
...@@ -3121,10 +3108,6 @@ my_decimal *Item_string::val_decimal(my_decimal *decimal_value) ...@@ -3121,10 +3108,6 @@ my_decimal *Item_string::val_decimal(my_decimal *decimal_value)
} }
bool Item_null::eq(const Item *item, bool binary_cmp) const
{ return item->type() == type(); }
double Item_null::val_real() double Item_null::val_real()
{ {
// following assert is redundant, because fixed=1 assigned in constructor // following assert is redundant, because fixed=1 assigned in constructor
...@@ -3808,30 +3791,21 @@ Item_param::clone_item() ...@@ -3808,30 +3791,21 @@ Item_param::clone_item()
bool bool
Item_param::eq(const Item *arg, bool binary_cmp) const Item_param::eq(const Item *item, bool binary_cmp) const
{ {
Item *item; if (!basic_const_item())
if (!basic_const_item() || !arg->basic_const_item() || arg->type() != type())
return FALSE; return FALSE;
/*
We need to cast off const to call val_int(). This should be OK for
a basic constant.
*/
item= (Item*) arg;
switch (state) { switch (state) {
case NULL_VALUE: case NULL_VALUE:
return TRUE; return null_eq(item);
case INT_VALUE: case INT_VALUE:
return value.integer == item->val_int() && return int_eq(value.integer, item);
unsigned_flag == item->unsigned_flag;
case REAL_VALUE: case REAL_VALUE:
return value.real == item->val_real(); return real_eq(value.real, item);
case STRING_VALUE: case STRING_VALUE:
case LONG_DATA_VALUE: case LONG_DATA_VALUE:
if (binary_cmp) return str_eq(&str_value, item, binary_cmp);
return !stringcmp(&str_value, &item->str_value);
return !sortcmp(&str_value, &item->str_value, collation.collation);
default: default:
break; break;
} }
...@@ -6115,24 +6089,6 @@ int Item_decimal::save_in_field(Field *field, bool no_conversions) ...@@ -6115,24 +6089,6 @@ int Item_decimal::save_in_field(Field *field, bool no_conversions)
} }
bool Item_int::eq(const Item *arg, bool binary_cmp) const
{
/* No need to check for null value as basic constant can't be NULL */
if (arg->basic_const_item() && arg->type() == type())
{
/*
We need to cast off const to call val_int(). This should be OK for
a basic constant.
*/
Item *item= (Item*) arg;
return (item->val_int() == value &&
((longlong) value >= 0 ||
(item->unsigned_flag == unsigned_flag)));
}
return FALSE;
}
Item *Item_int_with_ref::clone_item() Item *Item_int_with_ref::clone_item()
{ {
DBUG_ASSERT(ref->const_item()); DBUG_ASSERT(ref->const_item());
...@@ -6250,27 +6206,6 @@ void Item_float::print(String *str, enum_query_type query_type) ...@@ -6250,27 +6206,6 @@ void Item_float::print(String *str, enum_query_type query_type)
} }
/*
hex item
In string context this is a binary string.
In number context this is a longlong value.
*/
bool Item_float::eq(const Item *arg, bool binary_cmp) const
{
if (arg->basic_const_item() && arg->type() == type())
{
/*
We need to cast off const to call val_int(). This should be OK for
a basic constant.
*/
Item *item= (Item*) arg;
return item->val_real() == value;
}
return FALSE;
}
inline uint char_val(char X) inline uint char_val(char X)
{ {
return (uint) (X >= '0' && X <= '9' ? X-'0' : return (uint) (X >= '0' && X <= '9' ? X-'0' :
...@@ -6365,19 +6300,6 @@ void Item_hex_string::print(String *str, enum_query_type query_type) ...@@ -6365,19 +6300,6 @@ void Item_hex_string::print(String *str, enum_query_type query_type)
} }
bool Item_hex_constant::eq(const Item *arg, bool binary_cmp) const
{
if (arg->basic_const_item() && arg->type() == type() &&
arg->cast_to_int_type() == cast_to_int_type())
{
if (binary_cmp)
return !stringcmp(&str_value, &arg->str_value);
return !sortcmp(&str_value, &arg->str_value, collation.collation);
}
return FALSE;
}
/* /*
bin item. bin item.
In string context this is a binary string. In string context this is a binary string.
......
...@@ -1663,11 +1663,66 @@ class Field_enumerator ...@@ -1663,11 +1663,66 @@ class Field_enumerator
class sp_head; class sp_head;
class Item_basic_constant :public Item
/**
A common class for Item_basic_constant and Item_param
*/
class Item_basic_value :public Item
{
bool is_basic_value(const Item *item, Type type_arg) const
{
return item->basic_const_item() && item->type() == type_arg;
}
bool is_basic_value(Type type_arg) const
{
return basic_const_item() && type() == type_arg;
}
bool str_eq(const String *value,
const String *other, CHARSET_INFO *cs, bool binary_cmp) const
{
return binary_cmp ?
value->bin_eq(other) :
collation.collation == cs && value->eq(other, collation.collation);
}
protected:
Item_basic_value(): Item() {}
/*
In the xxx_eq() methods below we need to cast off "const" to
call val_xxx(). This is OK for Item_basic_constant and Item_param.
*/
bool null_eq(const Item *item) const
{
DBUG_ASSERT(is_basic_value(NULL_ITEM));
return item->type() == NULL_ITEM;
}
bool str_eq(const String *value, const Item *item, bool binary_cmp) const
{
DBUG_ASSERT(is_basic_value(STRING_ITEM));
return is_basic_value(item, STRING_ITEM) &&
str_eq(value, ((Item_basic_value*)item)->val_str(NULL),
item->collation.collation, binary_cmp);
}
bool real_eq(double value, const Item *item) const
{
DBUG_ASSERT(is_basic_value(REAL_ITEM));
return is_basic_value(item, REAL_ITEM) &&
value == ((Item_basic_value*)item)->val_real();
}
bool int_eq(longlong value, const Item *item) const
{
DBUG_ASSERT(is_basic_value(INT_ITEM));
return is_basic_value(item, INT_ITEM) &&
value == ((Item_basic_value*)item)->val_int() &&
(value >= 0 || item->unsigned_flag == unsigned_flag);
}
};
class Item_basic_constant :public Item_basic_value
{ {
table_map used_table_map; table_map used_table_map;
public: public:
Item_basic_constant(): Item(), used_table_map(0) {}; Item_basic_constant(): Item_basic_value(), used_table_map(0) {};
void set_used_tables(table_map map) { used_table_map= map; } void set_used_tables(table_map map) { used_table_map= map; }
table_map used_tables() const { return used_table_map; } table_map used_tables() const { return used_table_map; }
/* to prevent drop fixed flag (no need parent cleanup call) */ /* to prevent drop fixed flag (no need parent cleanup call) */
...@@ -2263,7 +2318,7 @@ class Item_null :public Item_basic_constant ...@@ -2263,7 +2318,7 @@ class Item_null :public Item_basic_constant
collation.set(cs, DERIVATION_IGNORABLE); collation.set(cs, DERIVATION_IGNORABLE);
} }
enum Type type() const { return NULL_ITEM; } enum Type type() const { return NULL_ITEM; }
bool eq(const Item *item, bool binary_cmp) const; bool eq(const Item *item, bool binary_cmp) const { return null_eq(item); }
double val_real(); double val_real();
longlong val_int(); longlong val_int();
String *val_str(String *str); String *val_str(String *str);
...@@ -2306,7 +2361,7 @@ class Item_null_result :public Item_null ...@@ -2306,7 +2361,7 @@ class Item_null_result :public Item_null
/* Item represents one placeholder ('?') of prepared statement */ /* Item represents one placeholder ('?') of prepared statement */
class Item_param :public Item, class Item_param :public Item_basic_value,
private Settable_routine_parameter private Settable_routine_parameter
{ {
char cnvbuf[MAX_FIELD_WIDTH]; char cnvbuf[MAX_FIELD_WIDTH];
...@@ -2496,7 +2551,8 @@ class Item_int :public Item_num ...@@ -2496,7 +2551,8 @@ class Item_int :public Item_num
Item_num *neg() { value= -value; return this; } Item_num *neg() { value= -value; return this; }
uint decimal_precision() const uint decimal_precision() const
{ return (uint) (max_length - MY_TEST(value < 0)); } { return (uint) (max_length - MY_TEST(value < 0)); }
bool eq(const Item *, bool binary_cmp) const; bool eq(const Item *item, bool binary_cmp) const
{ return int_eq(value, item); }
bool check_partition_func_processor(uchar *bool_arg) { return FALSE;} bool check_partition_func_processor(uchar *bool_arg) { return FALSE;}
bool check_vcol_func_processor(uchar *arg) { return FALSE;} bool check_vcol_func_processor(uchar *arg) { return FALSE;}
}; };
...@@ -2617,7 +2673,8 @@ class Item_float :public Item_num ...@@ -2617,7 +2673,8 @@ class Item_float :public Item_num
{ return new Item_float(name, value, decimals, max_length); } { return new Item_float(name, value, decimals, max_length); }
Item_num *neg() { value= -value; return this; } Item_num *neg() { value= -value; return this; }
virtual void print(String *str, enum_query_type query_type); virtual void print(String *str, enum_query_type query_type);
bool eq(const Item *, bool binary_cmp) const; bool eq(const Item *item, bool binary_cmp) const
{ return real_eq(value, item); }
}; };
...@@ -2729,7 +2786,10 @@ class Item_string :public Item_basic_constant ...@@ -2729,7 +2786,10 @@ class Item_string :public Item_basic_constant
enum Item_result result_type () const { return STRING_RESULT; } enum Item_result result_type () const { return STRING_RESULT; }
enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; } enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; }
bool basic_const_item() const { return 1; } bool basic_const_item() const { return 1; }
bool eq(const Item *item, bool binary_cmp) const; bool eq(const Item *item, bool binary_cmp) const
{
return str_eq(&str_value, item, binary_cmp);
}
Item *clone_item() Item *clone_item()
{ {
return new Item_string(name, str_value.ptr(), return new Item_string(name, str_value.ptr(),
...@@ -2931,7 +2991,12 @@ class Item_hex_constant: public Item_basic_constant ...@@ -2931,7 +2991,12 @@ class Item_hex_constant: public Item_basic_constant
bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_partition_func_processor(uchar *int_arg) {return FALSE;}
bool check_vcol_func_processor(uchar *arg) { return FALSE;} bool check_vcol_func_processor(uchar *arg) { return FALSE;}
bool basic_const_item() const { return 1; } bool basic_const_item() const { return 1; }
bool eq(const Item *item, bool binary_cmp) const; bool eq(const Item *item, bool binary_cmp) const
{
return item->basic_const_item() && item->type() == type() &&
item->cast_to_int_type() == cast_to_int_type() &&
str_value.bin_eq(&item->str_value);
}
String *val_str(String*) { DBUG_ASSERT(fixed == 1); return &str_value; } String *val_str(String*) { DBUG_ASSERT(fixed == 1); return &str_value; }
}; };
......
...@@ -540,6 +540,15 @@ class String ...@@ -540,6 +540,15 @@ class String
} }
return TRUE; return TRUE;
} }
bool bin_eq(const String *other) const
{
return length() == other->length() &&
!memcmp(ptr(), other->ptr(), length());
}
bool eq(const String *other, CHARSET_INFO *cs) const
{
return !sortcmp(this, other, cs);
}
}; };
......
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