Loading mysql-test/t/ps.test +27 −0 Original line number Diff line number Diff line Loading @@ -587,3 +587,30 @@ deallocate prepare stmt| drop procedure p1| drop table t1| delimiter ;| # # Bug#9096 "select doesn't return all matched records if prepared statements # is used" # The bug was is bad co-operation of the optimizer's algorithm which determines # which keys can be used to execute a query, constants propagation # part of the optimizer and parameter markers used by prepared statements. drop table if exists t1; create table t1 (c1 int(11) not null, c2 int(11) not null, primary key (c1,c2), key c2 (c2), key c1 (c1)); insert into t1 values (200887, 860); insert into t1 values (200887, 200887); select * from t1 where (c1=200887 and c2=200887) or c2=860; prepare stmt from "select * from t1 where (c1=200887 and c2=200887) or c2=860"; execute stmt; prepare stmt from "select * from t1 where (c1=200887 and c2=?) or c2=?"; set @a=200887, @b=860; # this query did not return all matching rows execute stmt using @a, @b; deallocate prepare stmt; sql/item.cc +114 −1 Original line number Diff line number Diff line Loading @@ -557,6 +557,11 @@ void Item::set_name(const char *str, uint length, CHARSET_INFO *cs) bool Item::eq(const Item *item, bool binary_cmp) const { /* Note, that this is never TRUE if item is a Item_param: for all basic constants we have special checks, and Item_param's type() can be only among basic constant types. */ return type() == item->type() && name && item->name && !my_strcasecmp(system_charset_info,name,item->name); } Loading Loading @@ -601,7 +606,7 @@ Item *Item_string::safe_charset_converter(CHARSET_INFO *tocs) bool Item_string::eq(const Item *item, bool binary_cmp) const { if (type() == item->type()) if (type() == item->type() && item->basic_const_item()) { if (binary_cmp) return !stringcmp(&str_value, &item->str_value); Loading Loading @@ -2194,6 +2199,72 @@ bool Item_param::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) } bool Item_param::basic_const_item() const { if (state == NO_VALUE || state == TIME_VALUE) return FALSE; return TRUE; } Item * Item_param::new_item() { /* see comments in the header file */ switch (state) { case NULL_VALUE: return new Item_null(name); case INT_VALUE: return new Item_int(name, value.integer, max_length); case REAL_VALUE: return new Item_real(name, value.real, decimals, max_length); case STRING_VALUE: case LONG_DATA_VALUE: return new Item_string(name, str_value.c_ptr_quick(), str_value.length(), str_value.charset()); case TIME_VALUE: break; case NO_VALUE: default: DBUG_ASSERT(0); }; return 0; } bool Item_param::eq(const Item *arg, bool binary_cmp) const { Item *item; if (!basic_const_item() || !arg->basic_const_item() || arg->type() != type()) 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) { case NULL_VALUE: return TRUE; case INT_VALUE: return value.integer == item->val_int() && unsigned_flag == item->unsigned_flag; case REAL_VALUE: return value.real == item->val(); case STRING_VALUE: case LONG_DATA_VALUE: if (binary_cmp) return !stringcmp(&str_value, &item->str_value); return !sortcmp(&str_value, &item->str_value, collation.collation); default: break; } return FALSE; } /* End of Item_param related */ void Item_param::print(String *str) { if (state == NO_VALUE) Loading Loading @@ -3346,6 +3417,22 @@ 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 && item->unsigned_flag == unsigned_flag; } return FALSE; } Item_num *Item_uint::neg() { Item_decimal *item= new Item_decimal(value, 0); Loading Loading @@ -3433,6 +3520,21 @@ void Item_float::print(String *str) In number context this is a longlong value. */ bool Item_real::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() == value; } return FALSE; } inline uint char_val(char X) { return (uint) (X >= '0' && X <= '9' ? X-'0' : Loading Loading @@ -3503,6 +3605,17 @@ int Item_hex_string::save_in_field(Field *field, bool no_conversions) } bool Item_varbinary::eq(const Item *arg, bool binary_cmp) const { if (arg->basic_const_item() && arg->type() == type()) { if (binary_cmp) return !stringcmp(&str_value, &arg->str_value); return !sortcmp(&str_value, &arg->str_value, collation.collation); } return FALSE; } /* bin item. In string context this is a binary string. Loading sql/item.h +23 −4 Original line number Diff line number Diff line Loading @@ -434,7 +434,7 @@ class Item { virtual table_map not_null_tables() const { return used_tables(); } /* Returns true if this is a simple constant item like an integer, not a constant expression a constant expression. Used in the optimizer to propagate basic constants. */ virtual bool basic_const_item() const { return 0; } /* cloning of constant items (0 if it is not const) */ Loading Loading @@ -914,7 +914,6 @@ class Item_param :public Item bool convert_str_value(THD *thd); Item *new_item() { return new Item_param(pos_in_query); } /* If value for parameter was not set we treat it as non-const so noone will use parameters value in fix_fields still Loading @@ -923,12 +922,29 @@ class Item_param :public Item virtual table_map used_tables() const { return state != NO_VALUE ? (table_map)0 : PARAM_TABLE_BIT; } void print(String *str); /* parameter never equal to other parameter of other item */ bool eq(const Item *item, bool binary_cmp) const { return 0; } bool is_null() { DBUG_ASSERT(state != NO_VALUE); return state == NULL_VALUE; } bool basic_const_item() const; /* This method is used to make a copy of a basic constant item when propagating constants in the optimizer. The reason to create a new item and not use the existing one is not precisely known (2005/04/16). Probably we are trying to preserve tree structure of items, in other words, avoid pointing at one item from two different nodes of the tree. Return a new basic constant item if parameter value is a basic constant, assert otherwise. This method is called only if basic_const_item returned TRUE. */ Item *new_item(); /* Implement by-value equality evaluation if parameter value is set and is a basic constant (integer, real or string). Otherwise return FALSE. */ bool eq(const Item *item, bool binary_cmp) const; }; class Item_int :public Item_num { public: Loading Loading @@ -956,6 +972,7 @@ class Item_int :public Item_num void cleanup() {} void print(String *str); Item_num *neg() { value= -value; return this; } bool eq(const Item *, bool binary_cmp) const; }; Loading Loading @@ -1059,6 +1076,7 @@ class Item_float :public Item_num { return new Item_float(name, value, decimals, max_length); } Item_num *neg() { value= -value; return this; } void print(String *str); bool eq(const Item *, bool binary_cmp) const; }; Loading Loading @@ -1198,6 +1216,7 @@ class Item_hex_string: public Item enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; } // to prevent drop fixed flag (no need parent cleanup call) void cleanup() {} bool eq(const Item *item, bool binary_cmp) const; }; Loading sql/item_func.cc +3 −2 Original line number Diff line number Diff line Loading @@ -1395,11 +1395,12 @@ void Item_func_neg::fix_length_and_dec() /* If this is in integer context keep the context as integer if possible (This is how multiplication and other integer functions works) Use val() to get value as arg_type doesn't mean that item is Item_int or Item_real due to existence of Item_param. */ if (hybrid_type == INT_RESULT && args[0]->type() == INT_ITEM && ((ulonglong) ((Item_uint*) args[0])->value >= (ulonglong) LONGLONG_MIN)) ((ulonglong) args[0]->val_int() >= (ulonglong) LONGLONG_MIN)) { /* Ensure that result is converted to DECIMAL, as longlong can't hold Loading Loading
mysql-test/t/ps.test +27 −0 Original line number Diff line number Diff line Loading @@ -587,3 +587,30 @@ deallocate prepare stmt| drop procedure p1| drop table t1| delimiter ;| # # Bug#9096 "select doesn't return all matched records if prepared statements # is used" # The bug was is bad co-operation of the optimizer's algorithm which determines # which keys can be used to execute a query, constants propagation # part of the optimizer and parameter markers used by prepared statements. drop table if exists t1; create table t1 (c1 int(11) not null, c2 int(11) not null, primary key (c1,c2), key c2 (c2), key c1 (c1)); insert into t1 values (200887, 860); insert into t1 values (200887, 200887); select * from t1 where (c1=200887 and c2=200887) or c2=860; prepare stmt from "select * from t1 where (c1=200887 and c2=200887) or c2=860"; execute stmt; prepare stmt from "select * from t1 where (c1=200887 and c2=?) or c2=?"; set @a=200887, @b=860; # this query did not return all matching rows execute stmt using @a, @b; deallocate prepare stmt;
sql/item.cc +114 −1 Original line number Diff line number Diff line Loading @@ -557,6 +557,11 @@ void Item::set_name(const char *str, uint length, CHARSET_INFO *cs) bool Item::eq(const Item *item, bool binary_cmp) const { /* Note, that this is never TRUE if item is a Item_param: for all basic constants we have special checks, and Item_param's type() can be only among basic constant types. */ return type() == item->type() && name && item->name && !my_strcasecmp(system_charset_info,name,item->name); } Loading Loading @@ -601,7 +606,7 @@ Item *Item_string::safe_charset_converter(CHARSET_INFO *tocs) bool Item_string::eq(const Item *item, bool binary_cmp) const { if (type() == item->type()) if (type() == item->type() && item->basic_const_item()) { if (binary_cmp) return !stringcmp(&str_value, &item->str_value); Loading Loading @@ -2194,6 +2199,72 @@ bool Item_param::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) } bool Item_param::basic_const_item() const { if (state == NO_VALUE || state == TIME_VALUE) return FALSE; return TRUE; } Item * Item_param::new_item() { /* see comments in the header file */ switch (state) { case NULL_VALUE: return new Item_null(name); case INT_VALUE: return new Item_int(name, value.integer, max_length); case REAL_VALUE: return new Item_real(name, value.real, decimals, max_length); case STRING_VALUE: case LONG_DATA_VALUE: return new Item_string(name, str_value.c_ptr_quick(), str_value.length(), str_value.charset()); case TIME_VALUE: break; case NO_VALUE: default: DBUG_ASSERT(0); }; return 0; } bool Item_param::eq(const Item *arg, bool binary_cmp) const { Item *item; if (!basic_const_item() || !arg->basic_const_item() || arg->type() != type()) 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) { case NULL_VALUE: return TRUE; case INT_VALUE: return value.integer == item->val_int() && unsigned_flag == item->unsigned_flag; case REAL_VALUE: return value.real == item->val(); case STRING_VALUE: case LONG_DATA_VALUE: if (binary_cmp) return !stringcmp(&str_value, &item->str_value); return !sortcmp(&str_value, &item->str_value, collation.collation); default: break; } return FALSE; } /* End of Item_param related */ void Item_param::print(String *str) { if (state == NO_VALUE) Loading Loading @@ -3346,6 +3417,22 @@ 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 && item->unsigned_flag == unsigned_flag; } return FALSE; } Item_num *Item_uint::neg() { Item_decimal *item= new Item_decimal(value, 0); Loading Loading @@ -3433,6 +3520,21 @@ void Item_float::print(String *str) In number context this is a longlong value. */ bool Item_real::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() == value; } return FALSE; } inline uint char_val(char X) { return (uint) (X >= '0' && X <= '9' ? X-'0' : Loading Loading @@ -3503,6 +3605,17 @@ int Item_hex_string::save_in_field(Field *field, bool no_conversions) } bool Item_varbinary::eq(const Item *arg, bool binary_cmp) const { if (arg->basic_const_item() && arg->type() == type()) { if (binary_cmp) return !stringcmp(&str_value, &arg->str_value); return !sortcmp(&str_value, &arg->str_value, collation.collation); } return FALSE; } /* bin item. In string context this is a binary string. Loading
sql/item.h +23 −4 Original line number Diff line number Diff line Loading @@ -434,7 +434,7 @@ class Item { virtual table_map not_null_tables() const { return used_tables(); } /* Returns true if this is a simple constant item like an integer, not a constant expression a constant expression. Used in the optimizer to propagate basic constants. */ virtual bool basic_const_item() const { return 0; } /* cloning of constant items (0 if it is not const) */ Loading Loading @@ -914,7 +914,6 @@ class Item_param :public Item bool convert_str_value(THD *thd); Item *new_item() { return new Item_param(pos_in_query); } /* If value for parameter was not set we treat it as non-const so noone will use parameters value in fix_fields still Loading @@ -923,12 +922,29 @@ class Item_param :public Item virtual table_map used_tables() const { return state != NO_VALUE ? (table_map)0 : PARAM_TABLE_BIT; } void print(String *str); /* parameter never equal to other parameter of other item */ bool eq(const Item *item, bool binary_cmp) const { return 0; } bool is_null() { DBUG_ASSERT(state != NO_VALUE); return state == NULL_VALUE; } bool basic_const_item() const; /* This method is used to make a copy of a basic constant item when propagating constants in the optimizer. The reason to create a new item and not use the existing one is not precisely known (2005/04/16). Probably we are trying to preserve tree structure of items, in other words, avoid pointing at one item from two different nodes of the tree. Return a new basic constant item if parameter value is a basic constant, assert otherwise. This method is called only if basic_const_item returned TRUE. */ Item *new_item(); /* Implement by-value equality evaluation if parameter value is set and is a basic constant (integer, real or string). Otherwise return FALSE. */ bool eq(const Item *item, bool binary_cmp) const; }; class Item_int :public Item_num { public: Loading Loading @@ -956,6 +972,7 @@ class Item_int :public Item_num void cleanup() {} void print(String *str); Item_num *neg() { value= -value; return this; } bool eq(const Item *, bool binary_cmp) const; }; Loading Loading @@ -1059,6 +1076,7 @@ class Item_float :public Item_num { return new Item_float(name, value, decimals, max_length); } Item_num *neg() { value= -value; return this; } void print(String *str); bool eq(const Item *, bool binary_cmp) const; }; Loading Loading @@ -1198,6 +1216,7 @@ class Item_hex_string: public Item enum_field_types field_type() const { return MYSQL_TYPE_VARCHAR; } // to prevent drop fixed flag (no need parent cleanup call) void cleanup() {} bool eq(const Item *item, bool binary_cmp) const; }; Loading
sql/item_func.cc +3 −2 Original line number Diff line number Diff line Loading @@ -1395,11 +1395,12 @@ void Item_func_neg::fix_length_and_dec() /* If this is in integer context keep the context as integer if possible (This is how multiplication and other integer functions works) Use val() to get value as arg_type doesn't mean that item is Item_int or Item_real due to existence of Item_param. */ if (hybrid_type == INT_RESULT && args[0]->type() == INT_ITEM && ((ulonglong) ((Item_uint*) args[0])->value >= (ulonglong) LONGLONG_MIN)) ((ulonglong) args[0]->val_int() >= (ulonglong) LONGLONG_MIN)) { /* Ensure that result is converted to DECIMAL, as longlong can't hold Loading