mirror of
https://github.com/MariaDB/server.git
synced 2025-08-05 13:16:09 +03:00
Fixes for bug#8115 "Server Crash with prepared statement"
and bug#8849 "problem with insert statement with table alias's": make equality propagation work in stored procedures and prepared statements. Equality propagation can change AND/OR structure of ON expressions, so the fix is to provide each execution of PS/SP with it's own copy of AND/OR tree. We have been doing that already for WHERE clauses, now ON clauses are also copied.
This commit is contained in:
@@ -499,3 +499,28 @@ SELECT FOUND_ROWS();
|
|||||||
FOUND_ROWS()
|
FOUND_ROWS()
|
||||||
2
|
2
|
||||||
deallocate prepare stmt;
|
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;
|
||||||
|
@@ -2542,3 +2542,41 @@ drop procedure bug7992|
|
|||||||
drop table t3|
|
drop table t3|
|
||||||
drop table t1;
|
drop table t1;
|
||||||
drop table t2;
|
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;
|
||||||
|
@@ -507,3 +507,36 @@ SELECT FOUND_ROWS();
|
|||||||
execute stmt;
|
execute stmt;
|
||||||
SELECT FOUND_ROWS();
|
SELECT FOUND_ROWS();
|
||||||
deallocate prepare stmt;
|
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;
|
||||||
|
|
||||||
|
@@ -3086,3 +3086,52 @@ delimiter ;|
|
|||||||
drop table t1;
|
drop table t1;
|
||||||
drop table t2;
|
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;
|
||||||
|
@@ -2289,6 +2289,21 @@ Item_cond::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref)
|
|||||||
|
|
||||||
if (check_stack_overrun(thd, buff))
|
if (check_stack_overrun(thd, buff))
|
||||||
return TRUE; // Fatal error flag is set!
|
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++))
|
while ((item=li++))
|
||||||
{
|
{
|
||||||
table_map tmp_table_map;
|
table_map tmp_table_map;
|
||||||
@@ -3265,6 +3280,7 @@ Item_equal::Item_equal(Item *c, Item_field *f)
|
|||||||
const_item= c;
|
const_item= c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Item_equal::Item_equal(Item_equal *item_equal)
|
Item_equal::Item_equal(Item_equal *item_equal)
|
||||||
: Item_bool_func(), eval_item(0), cond_false(0)
|
: 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 Item_equal::members()
|
||||||
{
|
{
|
||||||
uint count= 0;
|
return fields.elements;
|
||||||
List_iterator_fast<Item_field> li(fields);
|
|
||||||
Item_field *item;
|
|
||||||
while ((item= li++))
|
|
||||||
count++;
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1095,6 +1095,12 @@ public:
|
|||||||
predicates that can not be used to access tables in the investigated
|
predicates that can not be used to access tables in the investigated
|
||||||
plan for those, obtained by substitution of some fields for equal fields,
|
plan for those, obtained by substitution of some fields for equal fields,
|
||||||
that can be used.
|
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
|
class Item_equal: public Item_bool_func
|
||||||
|
@@ -805,6 +805,7 @@ void mysql_stmt_free(THD *thd, char *packet);
|
|||||||
void mysql_stmt_reset(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 mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length);
|
||||||
void reset_stmt_for_execute(THD *thd, LEX *lex);
|
void reset_stmt_for_execute(THD *thd, LEX *lex);
|
||||||
|
void init_stmt_after_parse(THD*, LEX*);
|
||||||
|
|
||||||
/* sql_error.cc */
|
/* sql_error.cc */
|
||||||
MYSQL_ERROR *push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, uint code,
|
MYSQL_ERROR *push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, uint code,
|
||||||
|
@@ -819,6 +819,7 @@ sp_head::restore_lex(THD *thd)
|
|||||||
LEX *sublex= thd->lex;
|
LEX *sublex= thd->lex;
|
||||||
LEX *oldlex= (LEX *)m_lex.pop();
|
LEX *oldlex= (LEX *)m_lex.pop();
|
||||||
|
|
||||||
|
init_stmt_after_parse(thd, sublex);
|
||||||
if (! oldlex)
|
if (! oldlex)
|
||||||
return; // Nothing to restore
|
return; // Nothing to restore
|
||||||
|
|
||||||
|
@@ -1809,20 +1809,33 @@ bool mysql_stmt_prepare(THD *thd, char *packet, uint packet_length,
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
stmt->setup_set_params();
|
stmt->setup_set_params();
|
||||||
SELECT_LEX *sl= stmt->lex->all_selects_list;
|
init_stmt_after_parse(thd, stmt->lex);
|
||||||
/*
|
|
||||||
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;
|
|
||||||
stmt->state= Item_arena::PREPARED;
|
stmt->state= Item_arena::PREPARED;
|
||||||
}
|
}
|
||||||
DBUG_RETURN(!stmt);
|
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)
|
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;
|
tables->table= 0;
|
||||||
if (tables->nested_join)
|
if (tables->nested_join)
|
||||||
tables->nested_join->counter= 0;
|
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;
|
lex->current_select= &lex->select_lex;
|
||||||
|
|
||||||
|
@@ -6192,9 +6192,9 @@ finish:
|
|||||||
For b=c it will be called with *cond_equal=(0,[Item_equal(a,b)])
|
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)]).
|
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),[])
|
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), [])
|
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
|
NOTES
|
||||||
Now only fields that have the same type defintions (verified by
|
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++))
|
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))
|
if (check_equality(item, &cond_equal))
|
||||||
li.remove();
|
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)
|
if ((new_item = build_equal_items_for_cond(item, inherited))!= item)
|
||||||
{
|
{
|
||||||
/* This replacement happens only for standalone equalities */
|
/* 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);
|
li.replace(new_item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6636,10 +6646,12 @@ static COND *build_equal_items(THD *thd, COND *cond,
|
|||||||
Item *expr;
|
Item *expr;
|
||||||
List<TABLE_LIST> *join_list= table->nested_join ?
|
List<TABLE_LIST> *join_list= table->nested_join ?
|
||||||
&table->nested_join->join_list : NULL;
|
&table->nested_join->join_list : NULL;
|
||||||
expr= build_equal_items(thd, table->on_expr, inherited, join_list,
|
/*
|
||||||
&table->cond_equal);
|
We can modify table->on_expr because its old value will
|
||||||
if (expr != table->on_expr)
|
be restored before re-execution of PS/SP.
|
||||||
thd->change_item_tree(&table->on_expr, expr);
|
*/
|
||||||
|
table->on_expr= build_equal_items(thd, table->on_expr, inherited,
|
||||||
|
join_list, &table->cond_equal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6867,6 +6879,10 @@ static COND* substitute_for_best_equal_field(COND *cond,
|
|||||||
{
|
{
|
||||||
Item *new_item =substitute_for_best_equal_field(item, cond_equal,
|
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)
|
if (new_item != item)
|
||||||
li.replace(new_item);
|
li.replace(new_item);
|
||||||
}
|
}
|
||||||
@@ -7198,7 +7214,7 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top)
|
|||||||
*/
|
*/
|
||||||
expr= simplify_joins(join, &nested_join->join_list,
|
expr= simplify_joins(join, &nested_join->join_list,
|
||||||
table->on_expr, FALSE);
|
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->used_tables= (table_map) 0;
|
||||||
nested_join->not_null_tables=(table_map) 0;
|
nested_join->not_null_tables=(table_map) 0;
|
||||||
@@ -7238,7 +7254,7 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
conds= table->on_expr;
|
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<TABLE_LIST> *join_list,
|
|||||||
DBUG_ENTER("optimize_cond");
|
DBUG_ENTER("optimize_cond");
|
||||||
|
|
||||||
if (!conds)
|
if (!conds)
|
||||||
{
|
|
||||||
*cond_value= Item::COND_TRUE;
|
*cond_value= Item::COND_TRUE;
|
||||||
select->prep_where= 0;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@@ -339,6 +339,15 @@ typedef struct st_table_list
|
|||||||
char *db, *alias, *table_name, *schema_table_name;
|
char *db, *alias, *table_name, *schema_table_name;
|
||||||
char *option; /* Used by cache index */
|
char *option; /* Used by cache index */
|
||||||
Item *on_expr; /* Used with outer join */
|
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 */
|
COND_EQUAL *cond_equal; /* Used with outer join */
|
||||||
struct st_table_list *natural_join; /* natural join on this table*/
|
struct st_table_list *natural_join; /* natural join on this table*/
|
||||||
/* ... join ... USE INDEX ... IGNORE INDEX */
|
/* ... join ... USE INDEX ... IGNORE INDEX */
|
||||||
|
Reference in New Issue
Block a user