diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 51fadd48184..5b638fd58ab 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -499,3 +499,28 @@ SELECT FOUND_ROWS(); FOUND_ROWS() 2 deallocate prepare stmt; +create table t1 (a char(3) not null, b char(3) not null, +c char(3) not null, primary key (a, b, c)); +create table t2 like t1; +prepare stmt from +"select t1.a from (t1 left outer join t2 on t2.a=1 and t1.b=t2.b) + where t1.a=1"; +execute stmt; +a +execute stmt; +a +execute stmt; +a +prepare stmt from +"select t1.a, t1.b, t1.c, t2.a, t2.b, t2.c from +(t1 left outer join t2 on t2.a=? and t1.b=t2.b) +left outer join t2 t3 on t3.a=? where t1.a=?"; +set @a:=1, @b:=1, @c:=1; +execute stmt using @a, @b, @c; +a b c a b c +execute stmt using @a, @b, @c; +a b c a b c +execute stmt using @a, @b, @c; +a b c a b c +deallocate prepare stmt; +drop table t1,t2; diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index 24045bae430..803cb89cecf 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -2542,3 +2542,41 @@ drop procedure bug7992| drop table t3| drop table t1; drop table t2; +CREATE TABLE t1 ( +lpitnumber int(11) default NULL, +lrecordtype int(11) default NULL +); +CREATE TABLE t2 ( +lbsiid int(11) NOT NULL default '0', +ltradingmodeid int(11) NOT NULL default '0', +ltradingareaid int(11) NOT NULL default '0', +csellingprice decimal(19,4) default NULL, +PRIMARY KEY (lbsiid,ltradingmodeid,ltradingareaid) +); +CREATE TABLE t3 ( +lbsiid int(11) NOT NULL default '0', +ltradingareaid int(11) NOT NULL default '0', +PRIMARY KEY (lbsiid,ltradingareaid) +); +CREATE PROCEDURE bug8849() +begin +insert into t3 +( +t3.lbsiid, +t3.ltradingareaid +) +select distinct t1.lpitnumber, t2.ltradingareaid +from +t2 join t1 on +t1.lpitnumber = t2.lbsiid +and t1.lrecordtype = 1 +left join t2 as price01 on +price01.lbsiid = t2.lbsiid and +price01.ltradingmodeid = 1 and +t2.ltradingareaid = price01.ltradingareaid; +end| +call bug8849(); +call bug8849(); +call bug8849(); +drop procedure bug8849; +drop tables t1,t2,t3; diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index b65226dae6d..1af84119b79 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -507,3 +507,36 @@ SELECT FOUND_ROWS(); execute stmt; SELECT FOUND_ROWS(); deallocate prepare stmt; + +# +# Bug#8115: equality propagation and prepared statements +# + +create table t1 (a char(3) not null, b char(3) not null, + c char(3) not null, primary key (a, b, c)); +create table t2 like t1; + +# reduced query +prepare stmt from + "select t1.a from (t1 left outer join t2 on t2.a=1 and t1.b=t2.b) + where t1.a=1"; +execute stmt; +execute stmt; +execute stmt; + +# original query +prepare stmt from +"select t1.a, t1.b, t1.c, t2.a, t2.b, t2.c from +(t1 left outer join t2 on t2.a=? and t1.b=t2.b) +left outer join t2 t3 on t3.a=? where t1.a=?"; + +set @a:=1, @b:=1, @c:=1; + +execute stmt using @a, @b, @c; +execute stmt using @a, @b, @c; +execute stmt using @a, @b, @c; + +deallocate prepare stmt; + +drop table t1,t2; + diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index 439ace881d1..69bdeb7a059 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -3086,3 +3086,52 @@ delimiter ;| drop table t1; drop table t2; +# +# Bug#8849: rolling back changes to AND/OR structure of ON and WHERE clauses +# in SP +# + +CREATE TABLE t1 ( + lpitnumber int(11) default NULL, + lrecordtype int(11) default NULL +); + +CREATE TABLE t2 ( + lbsiid int(11) NOT NULL default '0', + ltradingmodeid int(11) NOT NULL default '0', + ltradingareaid int(11) NOT NULL default '0', + csellingprice decimal(19,4) default NULL, + PRIMARY KEY (lbsiid,ltradingmodeid,ltradingareaid) +); + +CREATE TABLE t3 ( + lbsiid int(11) NOT NULL default '0', + ltradingareaid int(11) NOT NULL default '0', + PRIMARY KEY (lbsiid,ltradingareaid) +); + +delimiter |; +CREATE PROCEDURE bug8849() +begin + insert into t3 + ( + t3.lbsiid, + t3.ltradingareaid + ) + select distinct t1.lpitnumber, t2.ltradingareaid + from + t2 join t1 on + t1.lpitnumber = t2.lbsiid + and t1.lrecordtype = 1 + left join t2 as price01 on + price01.lbsiid = t2.lbsiid and + price01.ltradingmodeid = 1 and + t2.ltradingareaid = price01.ltradingareaid; +end| +delimiter ;| + +call bug8849(); +call bug8849(); +call bug8849(); +drop procedure bug8849; +drop tables t1,t2,t3; diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index dae5f0fc0c6..c0cb0704852 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -2289,6 +2289,21 @@ Item_cond::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) if (check_stack_overrun(thd, buff)) return TRUE; // Fatal error flag is set! + /* + The following optimization reduces the depth of an AND-OR tree. + E.g. a WHERE clause like + F1 AND (F2 AND (F2 AND F4)) + is parsed into a tree with the same nested structure as defined + by braces. This optimization will transform such tree into + AND (F1, F2, F3, F4). + Trees of OR items are flattened as well: + ((F1 OR F2) OR (F3 OR F4)) => OR (F1, F2, F3, F4) + Items for removed AND/OR levels will dangle until the death of the + entire statement. + The optimization is currently prepared statements and stored procedures + friendly as it doesn't allocate any memory and its effects are durable + (i.e. do not depend on PS/SP arguments). + */ while ((item=li++)) { table_map tmp_table_map; @@ -3265,6 +3280,7 @@ Item_equal::Item_equal(Item *c, Item_field *f) const_item= c; } + Item_equal::Item_equal(Item_equal *item_equal) : Item_bool_func(), eval_item(0), cond_false(0) { @@ -3301,12 +3317,7 @@ void Item_equal::add(Item_field *f) uint Item_equal::members() { - uint count= 0; - List_iterator_fast li(fields); - Item_field *item; - while ((item= li++)) - count++; - return count; + return fields.elements; } diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 212a3dd4501..5a87635a4dc 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -1095,6 +1095,12 @@ public: predicates that can not be used to access tables in the investigated plan for those, obtained by substitution of some fields for equal fields, that can be used. + + Prepared Statements/Stored Procedures note: instances of class + Item_equal are created only at the time a PS/SP is executed and + are deleted in the end of execution. All changes made to these + objects need not be registered in the list of changes of the parse + tree and do not harm PS/SP re-execution. */ class Item_equal: public Item_bool_func diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index eb7ab7131dd..97331d3c1a6 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -805,6 +805,7 @@ void mysql_stmt_free(THD *thd, char *packet); void mysql_stmt_reset(THD *thd, char *packet); void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length); void reset_stmt_for_execute(THD *thd, LEX *lex); +void init_stmt_after_parse(THD*, LEX*); /* sql_error.cc */ MYSQL_ERROR *push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, uint code, diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 06669e398ec..ad527d39d21 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -819,6 +819,7 @@ sp_head::restore_lex(THD *thd) LEX *sublex= thd->lex; LEX *oldlex= (LEX *)m_lex.pop(); + init_stmt_after_parse(thd, sublex); if (! oldlex) return; // Nothing to restore diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 7da5c13e18a..d1594edc3f9 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1809,20 +1809,33 @@ bool mysql_stmt_prepare(THD *thd, char *packet, uint packet_length, else { stmt->setup_set_params(); - SELECT_LEX *sl= stmt->lex->all_selects_list; - /* - Save WHERE clause pointers, because they may be changed during query - optimisation. - */ - for (; sl; sl= sl->next_select_in_list()) - sl->prep_where= sl->where; + init_stmt_after_parse(thd, stmt->lex); stmt->state= Item_arena::PREPARED; } DBUG_RETURN(!stmt); } -/* Reinit statement before execution */ +/* + Init PS/SP specific parse tree members. +*/ + +void init_stmt_after_parse(THD *thd, LEX *lex) +{ + SELECT_LEX *sl= lex->all_selects_list; + /* + Save WHERE clause pointers, because they may be changed during query + optimisation. + */ + for (; sl; sl= sl->next_select_in_list()) + sl->prep_where= sl->where; + + for (TABLE_LIST *table= lex->query_tables; table; table= table->next_global) + table->prep_on_expr= table->on_expr; +} + + +/* Reinit prepared statement/stored procedure before execution */ void reset_stmt_for_execute(THD *thd, LEX *lex) { @@ -1883,6 +1896,12 @@ void reset_stmt_for_execute(THD *thd, LEX *lex) tables->table= 0; if (tables->nested_join) tables->nested_join->counter= 0; + + if (tables->prep_on_expr) + { + tables->on_expr= tables->prep_on_expr->copy_andor_structure(thd); + tables->on_expr->cleanup(); + } } lex->current_select= &lex->select_lex; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index e886c658444..76d8ec1740a 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -6192,9 +6192,9 @@ finish: For b=c it will be called with *cond_equal=(0,[Item_equal(a,b)]) and will transform *cond_equal into CE=(0,[Item_equal(a,b,c)]). For b=2 it will be called with *cond_equal=(ptr(CE),[]) - and will transform *cond_equal into (ptr(CE,[Item_equal(2,a,b,c)]). + and will transform *cond_equal into (ptr(CE),[Item_equal(2,a,b,c)]). For f=e it will be called with *cond_equal=(ptr(CE), []) - and will transform *cond_equal into (ptr(CE,[Item_equal(f,e)]). + and will transform *cond_equal into (ptr(CE),[Item_equal(f,e)]). NOTES Now only fields that have the same type defintions (verified by @@ -6463,6 +6463,11 @@ static COND *build_equal_items_for_cond(COND *cond, */ while ((item= li++)) { + /* + PS/SP note: we can safely remove a node from AND-OR + structure here because it's restored before each + re-execution of any prepared statement/stored procedure. + */ if (check_equality(item, &cond_equal)) li.remove(); } @@ -6501,6 +6506,11 @@ static COND *build_equal_items_for_cond(COND *cond, if ((new_item = build_equal_items_for_cond(item, inherited))!= item) { /* This replacement happens only for standalone equalities */ + /* + This is ok with PS/SP as the replacement is done for + arguments of an AND/OR item, which are restored for each + execution of PS/SP. + */ li.replace(new_item); } } @@ -6636,10 +6646,12 @@ static COND *build_equal_items(THD *thd, COND *cond, Item *expr; List *join_list= table->nested_join ? &table->nested_join->join_list : NULL; - expr= build_equal_items(thd, table->on_expr, inherited, join_list, - &table->cond_equal); - if (expr != table->on_expr) - thd->change_item_tree(&table->on_expr, expr); + /* + We can modify table->on_expr because its old value will + be restored before re-execution of PS/SP. + */ + table->on_expr= build_equal_items(thd, table->on_expr, inherited, + join_list, &table->cond_equal); } } } @@ -6866,10 +6878,14 @@ static COND* substitute_for_best_equal_field(COND *cond, while ((item= li++)) { Item *new_item =substitute_for_best_equal_field(item, cond_equal, - table_join_idx); + table_join_idx); + /* + This works OK with PS/SP re-execution as changes are made to + the arguments of AND/OR items only + */ if (new_item != item) li.replace(new_item); - } + } if (and_level) { @@ -7198,7 +7214,7 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool top) */ expr= simplify_joins(join, &nested_join->join_list, table->on_expr, FALSE); - table->on_expr= expr; + table->prep_on_expr= table->on_expr= expr; } nested_join->used_tables= (table_map) 0; nested_join->not_null_tables=(table_map) 0; @@ -7238,7 +7254,7 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool top) } else conds= table->on_expr; - table->on_expr= 0; + table->prep_on_expr= table->on_expr= 0; } } @@ -7319,10 +7335,7 @@ optimize_cond(JOIN *join, COND *conds, List *join_list, DBUG_ENTER("optimize_cond"); if (!conds) - { *cond_value= Item::COND_TRUE; - select->prep_where= 0; - } else { /* diff --git a/sql/table.h b/sql/table.h index 5ab1f900195..4306d3733c4 100644 --- a/sql/table.h +++ b/sql/table.h @@ -339,6 +339,15 @@ typedef struct st_table_list char *db, *alias, *table_name, *schema_table_name; char *option; /* Used by cache index */ Item *on_expr; /* Used with outer join */ + /* + The scturcture of ON expression presented in the member above + can be changed during certain optimizations. This member + contains a snapshot of AND-OR structure of the ON expression + made after permanent transformations of the parse tree, and is + used to restore ON clause before every reexecution of a prepared + statement or stored procedure. + */ + Item *prep_on_expr; COND_EQUAL *cond_equal; /* Used with outer join */ struct st_table_list *natural_join; /* natural join on this table*/ /* ... join ... USE INDEX ... IGNORE INDEX */