Skip to content

Commit f60a763

Browse files
author
pem@mysql.com
committed
Fixed BUG#15737: Stored procedure optimizer bug with LEAVE
Second version. The problem was that the optimizer didn't work correctly with forwards jumps to "no-op" hpop and cpop instructions. Don't generate "no-op" instructions (hpop 0 and cpop 0), it isn't actually necessary.
1 parent 378147a commit f60a763

File tree

5 files changed

+321
-39
lines changed

5 files changed

+321
-39
lines changed

mysql-test/r/sp-code.result

+139
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
drop procedure if exists empty;
2+
drop procedure if exists code_sample;
13
create procedure empty()
24
begin
35
end;
@@ -60,3 +62,140 @@ Pos Instruction
6062
20 cpop 1
6163
21 stmt 0 "select t.name, t.idx from t2 t order ..."
6264
drop procedure code_sample;
65+
drop procedure if exists sudoku_solve;
66+
create procedure sudoku_solve(p_naive boolean, p_all boolean)
67+
deterministic
68+
modifies sql data
69+
begin
70+
drop temporary table if exists sudoku_work, sudoku_schedule;
71+
create temporary table sudoku_work
72+
(
73+
row smallint not null,
74+
col smallint not null,
75+
dig smallint not null,
76+
cnt smallint,
77+
key using btree (cnt),
78+
key using btree (row),
79+
key using btree (col),
80+
unique key using hash (row,col)
81+
);
82+
create temporary table sudoku_schedule
83+
(
84+
idx int not null auto_increment primary key,
85+
row smallint not null,
86+
col smallint not null
87+
);
88+
call sudoku_init();
89+
if p_naive then
90+
update sudoku_work set cnt = 0 where dig = 0;
91+
else
92+
call sudoku_count();
93+
end if;
94+
insert into sudoku_schedule (row,col)
95+
select row,col from sudoku_work where cnt is not null order by cnt desc;
96+
begin
97+
declare v_scounter bigint default 0;
98+
declare v_i smallint default 1;
99+
declare v_dig smallint;
100+
declare v_schedmax smallint;
101+
select count(*) into v_schedmax from sudoku_schedule;
102+
more:
103+
loop
104+
begin
105+
declare v_tcounter bigint default 0;
106+
sched:
107+
while v_i <= v_schedmax do
108+
begin
109+
declare v_row, v_col smallint;
110+
select row,col into v_row,v_col from sudoku_schedule where v_i = idx;
111+
select dig into v_dig from sudoku_work
112+
where v_row = row and v_col = col;
113+
case v_dig
114+
when 0 then
115+
set v_dig = 1;
116+
update sudoku_work set dig = 1
117+
where v_row = row and v_col = col;
118+
when 9 then
119+
if v_i > 0 then
120+
update sudoku_work set dig = 0
121+
where v_row = row and v_col = col;
122+
set v_i = v_i - 1;
123+
iterate sched;
124+
else
125+
select v_scounter as 'Solutions';
126+
leave more;
127+
end if;
128+
else
129+
set v_dig = v_dig + 1;
130+
update sudoku_work set dig = v_dig
131+
where v_row = row and v_col = col;
132+
end case;
133+
set v_tcounter = v_tcounter + 1;
134+
if not sudoku_digit_ok(v_row, v_col, v_dig) then
135+
iterate sched;
136+
end if;
137+
set v_i = v_i + 1;
138+
end;
139+
end while sched;
140+
select dig from sudoku_work;
141+
select v_tcounter as 'Tests';
142+
set v_scounter = v_scounter + 1;
143+
if p_all and v_i > 0 then
144+
set v_i = v_i - 1;
145+
else
146+
leave more;
147+
end if;
148+
end;
149+
end loop more;
150+
end;
151+
drop temporary table sudoku_work, sudoku_schedule;
152+
end//
153+
show procedure code sudoku_solve;
154+
Pos Instruction
155+
0 stmt 9 "drop temporary table if exists sudoku..."
156+
1 stmt 1 "create temporary table sudoku_work ( ..."
157+
2 stmt 1 "create temporary table sudoku_schedul..."
158+
3 stmt 95 "call sudoku_init("
159+
4 jump_if_not 7(8) p_naive@0
160+
5 stmt 4 "update sudoku_work set cnt = 0 where ..."
161+
6 jump 8
162+
7 stmt 95 "call sudoku_count("
163+
8 stmt 6 "insert into sudoku_schedule (row,col)..."
164+
9 set v_scounter@2 0
165+
10 set v_i@3 1
166+
11 set v_dig@4 NULL
167+
12 set v_schedmax@5 NULL
168+
13 stmt 0 "select count(*) into v_schedmax from ..."
169+
14 set v_tcounter@6 0
170+
15 jump_if_not 39(39) (v_i@3 <= v_schedmax@5)
171+
16 set v_row@7 NULL
172+
17 set v_col@8 NULL
173+
18 stmt 0 "select row,col into v_row,v_col from ..."
174+
19 stmt 0 "select dig into v_dig from sudoku_wor..."
175+
20 set_case_expr 0 v_dig@4
176+
21 jump_if_not 25(34) (case_expr@0 = 0)
177+
22 set v_dig@4 1
178+
23 stmt 4 "update sudoku_work set dig = 1 where ..."
179+
24 jump 34
180+
25 jump_if_not 32(34) (case_expr@0 = 9)
181+
26 jump_if_not 30(34) (v_i@3 > 0)
182+
27 stmt 4 "update sudoku_work set dig = 0 where ..."
183+
28 set v_i@3 (v_i@3 - 1)
184+
29 jump 15
185+
30 stmt 0 "select v_scounter as 'Solutions'"
186+
31 jump 45
187+
32 set v_dig@4 (v_dig@4 + 1)
188+
33 stmt 4 "update sudoku_work set dig = v_dig wh..."
189+
34 set v_tcounter@6 (v_tcounter@6 + 1)
190+
35 jump_if_not 37(37) not(`test`.`sudoku_digit_ok`(v_row@7,v_col@8,v_dig@4))
191+
36 jump 15
192+
37 set v_i@3 (v_i@3 + 1)
193+
38 jump 15
194+
39 stmt 0 "select dig from sudoku_work"
195+
40 stmt 0 "select v_tcounter as 'Tests'"
196+
41 set v_scounter@2 (v_scounter@2 + 1)
197+
42 jump_if_not 45(14) (p_all@1 and (v_i@3 > 0))
198+
43 set v_i@3 (v_i@3 - 1)
199+
44 jump 14
200+
45 stmt 9 "drop temporary table sudoku_work, sud..."
201+
drop procedure sudoku_solve;

mysql-test/t/sp-code.test

+143
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
-- source include/is_debug_build.inc
66

7+
--disable_warnings
8+
drop procedure if exists empty;
9+
drop procedure if exists code_sample;
10+
--enable_warnings
11+
712
create procedure empty()
813
begin
914
end;
@@ -47,3 +52,141 @@ end//
4752
delimiter ;//
4853
show procedure code code_sample;
4954
drop procedure code_sample;
55+
56+
57+
#
58+
# BUG#15737: Stored procedure optimizer bug with LEAVE
59+
#
60+
# This is a much more extensive test case than is strictly needed,
61+
# but it was kept as is for two reasons:
62+
# - The bug occurs under some quite special circumstances, so it
63+
# wasn't trivial to create a smaller test,
64+
# - There's some value in having another more complex code sample
65+
# in this test file. This might catch future code generation bugs
66+
# that doesn't not show in behaviour in any obvious way.
67+
68+
--disable_warnings
69+
drop procedure if exists sudoku_solve;
70+
--enable_warnings
71+
72+
delimiter //;
73+
create procedure sudoku_solve(p_naive boolean, p_all boolean)
74+
deterministic
75+
modifies sql data
76+
begin
77+
drop temporary table if exists sudoku_work, sudoku_schedule;
78+
79+
create temporary table sudoku_work
80+
(
81+
row smallint not null,
82+
col smallint not null,
83+
dig smallint not null,
84+
cnt smallint,
85+
key using btree (cnt),
86+
key using btree (row),
87+
key using btree (col),
88+
unique key using hash (row,col)
89+
);
90+
91+
create temporary table sudoku_schedule
92+
(
93+
idx int not null auto_increment primary key,
94+
row smallint not null,
95+
col smallint not null
96+
);
97+
98+
call sudoku_init();
99+
100+
if p_naive then
101+
update sudoku_work set cnt = 0 where dig = 0;
102+
else
103+
call sudoku_count();
104+
end if;
105+
insert into sudoku_schedule (row,col)
106+
select row,col from sudoku_work where cnt is not null order by cnt desc;
107+
108+
begin
109+
declare v_scounter bigint default 0;
110+
declare v_i smallint default 1;
111+
declare v_dig smallint;
112+
declare v_schedmax smallint;
113+
114+
select count(*) into v_schedmax from sudoku_schedule;
115+
116+
more:
117+
loop
118+
begin
119+
declare v_tcounter bigint default 0;
120+
121+
sched:
122+
while v_i <= v_schedmax do
123+
begin
124+
declare v_row, v_col smallint;
125+
126+
select row,col into v_row,v_col from sudoku_schedule where v_i = idx;
127+
128+
select dig into v_dig from sudoku_work
129+
where v_row = row and v_col = col;
130+
131+
case v_dig
132+
when 0 then
133+
set v_dig = 1;
134+
update sudoku_work set dig = 1
135+
where v_row = row and v_col = col;
136+
when 9 then
137+
if v_i > 0 then
138+
update sudoku_work set dig = 0
139+
where v_row = row and v_col = col;
140+
set v_i = v_i - 1;
141+
iterate sched;
142+
else
143+
select v_scounter as 'Solutions';
144+
leave more;
145+
end if;
146+
else
147+
set v_dig = v_dig + 1;
148+
update sudoku_work set dig = v_dig
149+
where v_row = row and v_col = col;
150+
end case;
151+
152+
set v_tcounter = v_tcounter + 1;
153+
if not sudoku_digit_ok(v_row, v_col, v_dig) then
154+
iterate sched;
155+
end if;
156+
set v_i = v_i + 1;
157+
end;
158+
end while sched;
159+
160+
select dig from sudoku_work;
161+
select v_tcounter as 'Tests';
162+
set v_scounter = v_scounter + 1;
163+
164+
if p_all and v_i > 0 then
165+
set v_i = v_i - 1;
166+
else
167+
leave more;
168+
end if;
169+
end;
170+
end loop more;
171+
end;
172+
173+
drop temporary table sudoku_work, sudoku_schedule;
174+
end//
175+
delimiter ;//
176+
177+
# The interestings parts are where the code for the two "leave" are:
178+
# ...
179+
#| 26 | jump_if_not 30 (v_i@3 > 0) |
180+
# ...
181+
#| 30 | stmt 0 "select v_scounter as 'Solutions'" |
182+
#| 31 | jump 45 |
183+
# ...
184+
#| 42 | jump_if_not 45 (p_all@1 and (v_i@3 > 0)) |
185+
#| 43 | set v_i@3 (v_i@3 - 1) |
186+
#| 44 | jump 14 |
187+
#| 45 | stmt 9 "drop temporary table sudoku_work, sud..." |
188+
#+-----+-----------------------------------------------------------------------+
189+
# The bug appeared at position 42 (with the wrong destination).
190+
show procedure code sudoku_solve;
191+
192+
drop procedure sudoku_solve;

sql/sp_head.cc

+9-12
Original file line numberDiff line numberDiff line change
@@ -2011,6 +2011,15 @@ sp_head::show_create_function(THD *thd)
20112011
1) Mark used instructions
20122012
1.1) While doing this, shortcut jumps to jump instructions
20132013
2) Compact the code, removing unused instructions
2014+
2015+
This is the main mark and move loop; it relies on the following methods
2016+
in sp_instr and its subclasses:
2017+
2018+
opt_mark() Mark instruction as reachable (will recurse for jumps)
2019+
opt_shortcut_jump() Shortcut jumps to the final destination;
2020+
used by opt_mark().
2021+
opt_move() Update moved instruction
2022+
set_destination() Set the new destination (jump instructions only)
20142023
*/
20152024

20162025
void sp_head::optimize()
@@ -2703,12 +2712,6 @@ sp_instr_hpop::print(String *str)
27032712
str->qs_append(m_count);
27042713
}
27052714

2706-
void
2707-
sp_instr_hpop::backpatch(uint dest, sp_pcontext *dst_ctx)
2708-
{
2709-
m_count= m_ctx->diff_handlers(dst_ctx);
2710-
}
2711-
27122715

27132716
/*
27142717
sp_instr_hreturn class functions
@@ -2830,12 +2833,6 @@ sp_instr_cpop::print(String *str)
28302833
str->qs_append(m_count);
28312834
}
28322835

2833-
void
2834-
sp_instr_cpop::backpatch(uint dest, sp_pcontext *dst_ctx)
2835-
{
2836-
m_count= m_ctx->diff_cursors(dst_ctx);
2837-
}
2838-
28392836

28402837
/*
28412838
sp_instr_copen class functions

0 commit comments

Comments
 (0)