Skip to content

Commit eb057dc

Browse files
committed
MDEV-15035 Wrong results when calling a stored procedure
multiple times with different arguments. If the ON expression of an outer join is an OR formula with one of the disjunct being a constant formula then the expression cannot be null-rejected if the constant formula is true. Otherwise it can be null-rejected and if so the outer join can be converted into inner join. This optimization was added in the patch for mdev-4817. Yet the code had a defect: if the query was used in a stored procedure with parameters and the constant item contained some of them then the value of this constant item depended on the values of the parameters. With some parameters it may be true, for others not. The validity of conversion to inner join is checked only once and it happens only for the first call of procedure. So if the parameters in the first call allowed the conversion it was done and next calls used the transformed query though there could be calls whose parameters made the conversion invalid. Fixed by cheking whether the constant disjunct in the ON expression originally contained an SP parameter. If so the expression is not considered as null-rejected. For this check a new item's attribute was intruduced: Item::with_param. It is calculated for each item by fix fields() functions. Also moved the call of optimize_constant_subqueries() in JOIN::optimize after the call of simplify_joins(). The reason for this is that after the optimization introduced by the patch for mdev-4817 simplify_joins() can use the results of execution of non-expensive constant subqueries and this is not valid.
1 parent adaa891 commit eb057dc

File tree

10 files changed

+130
-15
lines changed

10 files changed

+130
-15
lines changed

mysql-test/r/sp-innodb.result

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,37 @@ SET @@innodb_lock_wait_timeout= @innodb_lock_wait_timeout_saved;
138138
#
139139
# BUG 16041903: End of test case
140140
#
141+
#
142+
# MDEV-15035: SP using query with outer join and a parameter
143+
# in ON expression
144+
#
145+
CREATE TABLE t1 (
146+
id int NOT NULL,
147+
PRIMARY KEY (id)
148+
) ENGINE=InnoDB;
149+
INSERT INTO t1 VALUES (1), (2);
150+
CREATE TABLE t2 (
151+
id int NOT NULL,
152+
id_foo int NOT NULL,
153+
PRIMARY KEY (id)
154+
) ENGINE=InnoDB;
155+
INSERT INTO t2 VALUES (1, 1);
156+
DROP PROCEDURE IF EXISTS test_proc;
157+
CREATE PROCEDURE test_proc(IN param int)
158+
LANGUAGE SQL
159+
READS SQL DATA
160+
BEGIN
161+
SELECT DISTINCT f.id
162+
FROM t1 f
163+
LEFT OUTER JOIN t2 b ON b.id_foo = f.id
164+
WHERE (param <> 0 OR b.id IS NOT NULL);
165+
END|
166+
CALL test_proc(0);
167+
id
168+
1
169+
CALL test_proc(1);
170+
id
171+
1
172+
2
173+
DROP PROCEDURE IF EXISTS test_proc;
174+
DROP TABLE t1, t2;

mysql-test/t/sp-innodb.test

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,5 +158,47 @@ SET @@innodb_lock_wait_timeout= @innodb_lock_wait_timeout_saved;
158158
--echo # BUG 16041903: End of test case
159159
--echo #
160160

161+
--echo #
162+
--echo # MDEV-15035: SP using query with outer join and a parameter
163+
--echo # in ON expression
164+
--echo #
165+
166+
CREATE TABLE t1 (
167+
id int NOT NULL,
168+
PRIMARY KEY (id)
169+
) ENGINE=InnoDB;
170+
171+
INSERT INTO t1 VALUES (1), (2);
172+
173+
CREATE TABLE t2 (
174+
id int NOT NULL,
175+
id_foo int NOT NULL,
176+
PRIMARY KEY (id)
177+
) ENGINE=InnoDB;
178+
179+
INSERT INTO t2 VALUES (1, 1);
180+
181+
--disable_warnings
182+
DROP PROCEDURE IF EXISTS test_proc;
183+
--enable_warnings
184+
185+
DELIMITER |;
186+
CREATE PROCEDURE test_proc(IN param int)
187+
LANGUAGE SQL
188+
READS SQL DATA
189+
BEGIN
190+
SELECT DISTINCT f.id
191+
FROM t1 f
192+
LEFT OUTER JOIN t2 b ON b.id_foo = f.id
193+
WHERE (param <> 0 OR b.id IS NOT NULL);
194+
END|
195+
DELIMITER ;|
196+
197+
CALL test_proc(0);
198+
CALL test_proc(1);
199+
200+
DROP PROCEDURE IF EXISTS test_proc;
201+
DROP TABLE t1, t2;
202+
161203
# Wait till we reached the initial number of concurrent sessions
162204
--source include/wait_until_count_sessions.inc

sql/item.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,7 @@ Item::Item():
504504
in_rollup= 0;
505505
decimals= 0; max_length= 0;
506506
with_subselect= 0;
507+
with_param= 0;
507508
cmp_context= IMPOSSIBLE_RESULT;
508509
/* Initially this item is not attached to any JOIN_TAB. */
509510
join_tab_idx= MAX_TABLES;
@@ -550,6 +551,7 @@ Item::Item(THD *thd, Item *item):
550551
null_value(item->null_value),
551552
unsigned_flag(item->unsigned_flag),
552553
with_sum_func(item->with_sum_func),
554+
with_param(item->with_param),
553555
with_field(item->with_field),
554556
fixed(item->fixed),
555557
is_autogenerated_name(item->is_autogenerated_name),
@@ -1486,6 +1488,9 @@ bool Item_sp_variable::fix_fields(THD *thd, Item **)
14861488
max_length= it->max_length;
14871489
decimals= it->decimals;
14881490
unsigned_flag= it->unsigned_flag;
1491+
with_param= 1;
1492+
if (thd->lex->current_select->master_unit()->item)
1493+
thd->lex->current_select->master_unit()->item->with_param= 1;
14891494
fixed= 1;
14901495
collation.set(it->collation.collation, it->collation.derivation);
14911496

@@ -7234,6 +7239,7 @@ void Item_ref::set_properties()
72347239
split_sum_func() doesn't try to change the reference.
72357240
*/
72367241
with_sum_func= (*ref)->with_sum_func;
7242+
with_param= (*ref)->with_param;
72377243
with_field= (*ref)->with_field;
72387244
unsigned_flag= (*ref)->unsigned_flag;
72397245
fixed= 1;
@@ -7681,6 +7687,7 @@ Item_cache_wrapper::Item_cache_wrapper(Item *item_arg)
76817687
decimals= orig_item->decimals;
76827688
collation.set(orig_item->collation);
76837689
with_sum_func= orig_item->with_sum_func;
7690+
with_param= orig_item->with_param;
76847691
with_field= orig_item->with_field;
76857692
unsigned_flag= orig_item->unsigned_flag;
76867693
name= item_arg->name;

sql/item.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,7 @@ class Item {
644644
bool null_value; /* if item is null */
645645
bool unsigned_flag;
646646
bool with_sum_func; /* True if item contains a sum func */
647+
bool with_param; /* True if contains an SP parameter */
647648
/**
648649
True if any item except Item_sum_func contains a field. Set during parsing.
649650
*/

sql/item_cmpfunc.cc

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,6 +1546,7 @@ bool Item_in_optimizer::fix_left(THD *thd, Item **ref)
15461546
}
15471547
eval_not_null_tables(NULL);
15481548
with_sum_func= args[0]->with_sum_func;
1549+
with_param= args[0]->with_param || args[1]->with_param;
15491550
with_field= args[0]->with_field;
15501551
if ((const_item_cache= args[0]->const_item()))
15511552
{
@@ -1587,6 +1588,7 @@ bool Item_in_optimizer::fix_fields(THD *thd, Item **ref)
15871588
with_subselect= 1;
15881589
with_sum_func= with_sum_func || args[1]->with_sum_func;
15891590
with_field= with_field || args[1]->with_field;
1591+
with_param= args[0]->with_param || args[1]->with_param;
15901592
used_tables_cache|= args[1]->used_tables();
15911593
const_item_cache&= args[1]->const_item();
15921594
fixed= 1;
@@ -2108,6 +2110,7 @@ void Item_func_interval::fix_length_and_dec()
21082110
used_tables_cache|= row->used_tables();
21092111
not_null_tables_cache= row->not_null_tables();
21102112
with_sum_func= with_sum_func || row->with_sum_func;
2113+
with_param= with_param || row->with_param;
21112114
with_field= with_field || row->with_field;
21122115
const_item_cache&= row->const_item();
21132116
}
@@ -4335,6 +4338,7 @@ Item_cond::fix_fields(THD *thd, Item **ref)
43354338
List_iterator<Item> li(list);
43364339
Item *item;
43374340
uchar buff[sizeof(char*)]; // Max local vars in function
4341+
bool is_and_cond= functype() == Item_func::COND_AND_FUNC;
43384342
not_null_tables_cache= used_tables_cache= 0;
43394343
const_item_cache= 1;
43404344

@@ -4396,26 +4400,33 @@ Item_cond::fix_fields(THD *thd, Item **ref)
43964400
(item= *li.ref())->check_cols(1))
43974401
return TRUE; /* purecov: inspected */
43984402
used_tables_cache|= item->used_tables();
4399-
if (item->const_item())
4403+
if (item->const_item() && !item->with_param &&
4404+
!item->is_expensive() && !cond_has_datetime_is_null(item))
44004405
{
4401-
if (!item->is_expensive() && !cond_has_datetime_is_null(item) &&
4402-
item->val_int() == 0)
4406+
if (item->val_int() == is_and_cond && top_level())
44034407
{
44044408
/*
4405-
This is "... OR false_cond OR ..."
4409+
a. This is "... AND true_cond AND ..."
4410+
In this case, true_cond has no effect on cond_and->not_null_tables()
4411+
b. This is "... OR false_cond/null cond OR ..."
44064412
In this case, false_cond has no effect on cond_or->not_null_tables()
44074413
*/
44084414
}
44094415
else
44104416
{
44114417
/*
4412-
This is "... OR const_cond OR ..."
4418+
a. This is "... AND false_cond/null_cond AND ..."
4419+
The whole condition is FALSE/UNKNOWN.
4420+
b. This is "... OR const_cond OR ..."
44134421
In this case, cond_or->not_null_tables()=0, because the condition
44144422
const_cond might evaluate to true (regardless of whether some tables
44154423
were NULL-complemented).
44164424
*/
4425+
not_null_tables_cache= (table_map) 0;
44174426
and_tables_cache= (table_map) 0;
44184427
}
4428+
if (thd->is_error())
4429+
return TRUE;
44194430
}
44204431
else
44214432
{
@@ -4427,6 +4438,7 @@ Item_cond::fix_fields(THD *thd, Item **ref)
44274438
}
44284439

44294440
with_sum_func= with_sum_func || item->with_sum_func;
4441+
with_param= with_param || item->with_param;
44304442
with_field= with_field || item->with_field;
44314443
with_subselect|= item->has_subquery();
44324444
if (item->maybe_null)
@@ -4443,30 +4455,36 @@ bool
44434455
Item_cond::eval_not_null_tables(uchar *opt_arg)
44444456
{
44454457
Item *item;
4458+
bool is_and_cond= functype() == Item_func::COND_AND_FUNC;
44464459
List_iterator<Item> li(list);
44474460
not_null_tables_cache= (table_map) 0;
44484461
and_tables_cache= ~(table_map) 0;
44494462
while ((item=li++))
44504463
{
44514464
table_map tmp_table_map;
4452-
if (item->const_item())
4465+
if (item->const_item() && !item->with_param &&
4466+
!item->is_expensive() && !cond_has_datetime_is_null(item))
44534467
{
4454-
if (!item->is_expensive() && !cond_has_datetime_is_null(item) &&
4455-
item->val_int() == 0)
4468+
if (item->val_int() == is_and_cond && top_level())
44564469
{
44574470
/*
4458-
This is "... OR false_cond OR ..."
4471+
a. This is "... AND true_cond AND ..."
4472+
In this case, true_cond has no effect on cond_and->not_null_tables()
4473+
b. This is "... OR false_cond/null cond OR ..."
44594474
In this case, false_cond has no effect on cond_or->not_null_tables()
44604475
*/
44614476
}
44624477
else
44634478
{
44644479
/*
4465-
This is "... OR const_cond OR ..."
4480+
a. This is "... AND false_cond/null_cond AND ..."
4481+
The whole condition is FALSE/UNKNOWN.
4482+
b. This is "... OR const_cond OR ..."
44664483
In this case, cond_or->not_null_tables()=0, because the condition
4467-
some_cond_or might be true regardless of what tables are
4468-
NULL-complemented.
4484+
const_cond might evaluate to true (regardless of whether some tables
4485+
were NULL-complemented).
44694486
*/
4487+
not_null_tables_cache= (table_map) 0;
44704488
and_tables_cache= (table_map) 0;
44714489
}
44724490
}
@@ -5118,6 +5136,7 @@ Item_func_regex::fix_fields(THD *thd, Item **ref)
51185136
args[1]->fix_fields(thd, args + 1)) || args[1]->check_cols(1))
51195137
return TRUE; /* purecov: inspected */
51205138
with_sum_func=args[0]->with_sum_func || args[1]->with_sum_func;
5139+
with_param=args[0]->with_param || args[1]->with_param;
51215140
with_field= args[0]->with_field || args[1]->with_field;
51225141
with_subselect= args[0]->has_subquery() || args[1]->has_subquery();
51235142
max_length= 1;

sql/item_func.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ Item_func::fix_fields(THD *thd, Item **ref)
222222
maybe_null=1;
223223

224224
with_sum_func= with_sum_func || item->with_sum_func;
225+
with_param= with_param || item->with_param;
225226
with_field= with_field || item->with_field;
226227
used_tables_cache|= item->used_tables();
227228
const_item_cache&= item->const_item();

sql/item_func.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class Item_func :public Item_result_field
8383
args= tmp_arg;
8484
args[0]= a;
8585
with_sum_func= a->with_sum_func;
86+
with_param= a->with_param;
8687
with_field= a->with_field;
8788
}
8889
Item_func(Item *a,Item *b):
@@ -91,6 +92,7 @@ class Item_func :public Item_result_field
9192
args= tmp_arg;
9293
args[0]= a; args[1]= b;
9394
with_sum_func= a->with_sum_func || b->with_sum_func;
95+
with_param= a->with_param || b->with_param;
9496
with_field= a->with_field || b->with_field;
9597
}
9698
Item_func(Item *a,Item *b,Item *c):
@@ -102,6 +104,7 @@ class Item_func :public Item_result_field
102104
arg_count= 3;
103105
args[0]= a; args[1]= b; args[2]= c;
104106
with_sum_func= a->with_sum_func || b->with_sum_func || c->with_sum_func;
107+
with_param= a->with_param || b->with_param || c->with_param;
105108
with_field= a->with_field || b->with_field || c->with_field;
106109
}
107110
}
@@ -115,6 +118,8 @@ class Item_func :public Item_result_field
115118
args[0]= a; args[1]= b; args[2]= c; args[3]= d;
116119
with_sum_func= a->with_sum_func || b->with_sum_func ||
117120
c->with_sum_func || d->with_sum_func;
121+
with_param= a->with_param || b->with_param ||
122+
c->with_param || d->with_param;
118123
with_field= a->with_field || b->with_field ||
119124
c->with_field || d->with_field;
120125
}
@@ -128,6 +133,8 @@ class Item_func :public Item_result_field
128133
args[0]= a; args[1]= b; args[2]= c; args[3]= d; args[4]= e;
129134
with_sum_func= a->with_sum_func || b->with_sum_func ||
130135
c->with_sum_func || d->with_sum_func || e->with_sum_func ;
136+
with_param= a->with_param || b->with_param ||
137+
c->with_param || d->with_param || e->with_param;
131138
with_field= a->with_field || b->with_field ||
132139
c->with_field || d->with_field || e->with_field;
133140
}

sql/item_row.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ bool Item_row::fix_fields(THD *thd, Item **ref)
125125
with_sum_func= with_sum_func || item->with_sum_func;
126126
with_field= with_field || item->with_field;
127127
with_subselect|= item->with_subselect;
128+
with_param|= item->with_param;
128129
}
129130
fixed= 1;
130131
return FALSE;

sql/item_sum.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,7 @@ Item_sum_num::fix_fields(THD *thd, Item **ref)
11641164
return TRUE;
11651165
set_if_bigger(decimals, args[i]->decimals);
11661166
with_subselect|= args[i]->with_subselect;
1167+
with_param|= args[i]->with_param;
11671168
}
11681169
result_field=0;
11691170
max_length=float_length(decimals);
@@ -1195,6 +1196,7 @@ Item_sum_hybrid::fix_fields(THD *thd, Item **ref)
11951196
return TRUE;
11961197
decimals=item->decimals;
11971198
with_subselect= args[0]->with_subselect;
1199+
with_param= args[0]->with_param;
11981200

11991201
switch (hybrid_type= item->result_type()) {
12001202
case INT_RESULT:
@@ -3430,6 +3432,7 @@ Item_func_group_concat::fix_fields(THD *thd, Item **ref)
34303432
args[i]->check_cols(1))
34313433
return TRUE;
34323434
with_subselect|= args[i]->with_subselect;
3435+
with_param|= args[i]->with_param;
34333436
}
34343437

34353438
/* skip charset aggregation for order columns */

sql/sql_select.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,9 +1034,6 @@ JOIN::optimize()
10341034

10351035
eval_select_list_used_tables();
10361036

1037-
if (optimize_constant_subqueries())
1038-
DBUG_RETURN(1);
1039-
10401037
table_count= select_lex->leaf_tables.elements;
10411038

10421039
if (setup_ftfuncs(select_lex)) /* should be after having->fix_fields */
@@ -1098,6 +1095,9 @@ JOIN::optimize()
10981095
thd->restore_active_arena(arena, &backup);
10991096
}
11001097

1098+
if (optimize_constant_subqueries())
1099+
DBUG_RETURN(1);
1100+
11011101
if (setup_jtbm_semi_joins(this, join_list, &conds))
11021102
DBUG_RETURN(1);
11031103

0 commit comments

Comments
 (0)