From 476eaae84db86837716c3c1ab689e3f2be9da0f4 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 17 Nov 2006 12:14:29 -0700 Subject: [PATCH 1/8] Bug#19194 (Right recursion in parser for CASE causes excessive stack usage, limitation) Note to the reviewer ==================== Warning: reviewing this patch is somewhat involved. Due to the nature of several issues all affecting the same area, fixing separately each issue is not practical, since each fix can not be implemented and tested independently. In particular, the issues with - rule recursion - nested case statements - forward jump resolution (backpatch list) are tightly coupled (see below). Definitions =========== The expression CASE expr WHEN expr THEN expr WHEN expr THEN expr ... END is a "Simple Case Expression". The expression CASE WHEN expr THEN expr WHEN expr THEN expr ... END is a "Searched Case Expression". The statement CASE expr WHEN expr THEN stmts WHEN expr THEN stmts ... END CASE is a "Simple Case Statement". The statement CASE WHEN expr THEN stmts WHEN expr THEN stmts ... END CASE is a "Searched Case Statement". A "Left Recursive" rule is like list: element | list element ; A "Right Recursive" rule is like list: element | element list ; Left and right recursion produces the same language, the difference only affects the *order* in which the text is parsed. In a descendant parser (usually written manually), right recursion works very well, and is typically implemented with a while loop. In an ascendant parser (yacc/bison) left recursion works very well, and is implemented naturally by the parser stack. In both cases, using the wrong type or recursion is very bad and should be avoided, as it causes technical issues with the parser implementation. Before this change ================== The "Simple Case Expression" and "Searched Case Expression" were both implemented by the "when_list" and "when_list2" rules, which are left recursive (ok). These rules, however, used lex->when_list instead of using the parser stack, which is more complex that necessary, and potentially dangerous because of other rules using THD::reset_lex. The "Simple Case Statement" and "Searched Case Statements" were implemented by the "sp_case", "sp_whens" and in part by "sp_proc_stmt" rules. Both cases were right recursive (bad). The grammar involved was convoluted, and is assumed to be the results of tweaks to get the code generation to work, but is not what someone would naturally write. In addition, using a common rule for both "Simple" and "Searched" case statements was implemented with sp_head::m_flags |= IN_SIMPLE_CASE, which is a flag and not a stack, and therefore does not take into account *nested* case statements. This leads to incorrect generated code, and either a server crash or an incorrect result. With regards to the backpatch mechanism, a *different* backpatch list was created for each jump from "WHEN expr THEN stmt" to "END CASE", which relied on the grammar to be right recursive. This is a mis-use of the backpatch list, since this list can resolve multiple references to the same target at once. The optimizer algorithm used to detect dead code in the "assembly" SQL instructions, implemented by sp_head::opt_mark(uint ip), was recursive in some cases (a conditional jump pointing forward to another conditional jump). In case of specially crafted code, like - a long list of "IF expr THEN stmt END IF" - a long CASE statement this would actually cause a server crash with a stack overflow. In general, having a stack that grows proportionally with user data (the SQL code given by the client in a CREATE PROCEDURE) is to be avoided. In debug builds only, creating a SP / SF / Trigger which had a significant amount of code would spend --literally-- several minutes in sp_head::create, because of the debug code involved with DBUG_PRINT("info", ("Code %s ... There are several issues with this code: - in a CASE with 5 000 WHEN, there are 15 000 instructions generated, which create a sting representation of the code which is 500 000 bytes long, - using a String instead of an io stream causes performances to degrade to a total server freeze, as time is spent doing realloc of a buffer always too short, - Printing a 500 000 long string in the debug log is too verbose, - Generating this string even when DBUG_PRINT is off is useless, - Having code that potentially can affect the server behavior, used with #ifdef / #endif is useful in some cases, but is also a bad practice. After this change ================= "Case Expressions" (both simple and searched) have been simplified to not use LEX::when_list, which has been removed. Considering all the issues affecting case statements, the grammar for these has been totally re written. The existing actions, used to generate "assembly" sp_inst* code, have been preserved but moved in the new grammar, with the following changes: a) Bison rules are no longer shared between "Simple" and "Searched" case statements, because a stack instead of a flag is required to handle them. Nested statements are handled naturally by the parser stack, which by definition uses the correct rule in the correct context. Nested statements of the opposite type (simple vs searched) works correctly. The flag sp_head::IN_SIMPLE_CASE is no longer used. This is a step towards resolution of WL#2999, which correctly identified that temporary parsing flags do not belong to sp_head. The code in the action is shared by mean of the case_stmt_action_xxx() helpers. b) The backpatch mechanism, used to resolve forward jumps in the generated code, has been changed to: - create a label for the instruction following 'END CASE', - register each jump at the end of a "WHEN expr THEN stmt" in a *unique* backpatch list associated with the 'END CASE' label - resolve all the forward jumps for this label at once. In addition, the code involving backpatch has been commented, so that a reader can now understand by reading matching "Registering" and "Resolving" comments how the forward jumps are resolved and what target they resolve to, as this is far from evident when reading the code alone. The implementation of sp_head::opt_mark() has been revised to avoid recursive calls from jump instructions, and instead add the jump location to the list of paths to explore during the flow analysis of the instruction graph, with a call to sp_head::add_mark_lead(). In addition, the flow analysis will stop if an instruction has already been marked as reachable, which the previous code failed to do in the recursive case. sp_head::opt_mark() is now private, to prevent new calls to this method from being introduced. The debug code present in sp_head::create() has been removed. Considering that SHOW PROCEDURE CODE is also available in debug builds, and can be used anytime regardless of the trace level, as opposed to "CREATE PROCEDURE" time and only if the trace was on, removing the code actually makes debugging easier (usable trace). Tests have been written to cover the parser overflow (big CASE), and to cover nested CASE statements. mysql-test/r/sp-code.result: Test cases for nested CASE statements. mysql-test/t/sp-code.test: Test cases for nested CASE statements. sql/sp_head.cc: Re factored opt_mark() to avoid recursion, clean up. sql/sp_head.h: Re factored opt_mark() to avoid recursion, clean up. sql/sql_lex.cc: Removed when_list. sql/sql_lex.h: Removed when_list. sql/sql_yacc.yy: Minor clean up for case expressions, Major re write for case statements (Bug#19194). mysql-test/r/sp_stress_case.result: New test for massive CASE statements. mysql-test/t/sp_stress_case.sh: New test for massive CASE statements. mysql-test/t/sp_stress_case.test: New test for massive CASE statements. --- mysql-test/r/sp-code.result | 415 +++++++++++++++++++++++++++++ mysql-test/r/sp_stress_case.result | 42 +++ mysql-test/t/sp-code.test | 235 ++++++++++++++++ mysql-test/t/sp_stress_case.sh | 68 +++++ mysql-test/t/sp_stress_case.test | 35 +++ sql/sp_head.cc | 100 ++++--- sql/sp_head.h | 39 ++- sql/sql_lex.cc | 1 - sql/sql_lex.h | 1 - sql/sql_yacc.yy | 373 ++++++++++++++++++-------- 10 files changed, 1138 insertions(+), 171 deletions(-) create mode 100644 mysql-test/r/sp_stress_case.result create mode 100755 mysql-test/t/sp_stress_case.sh create mode 100644 mysql-test/t/sp_stress_case.test diff --git a/mysql-test/r/sp-code.result b/mysql-test/r/sp-code.result index 4ae38861d29..0b0ad802b54 100644 --- a/mysql-test/r/sp-code.result +++ b/mysql-test/r/sp-code.result @@ -199,6 +199,421 @@ Pos Instruction 44 jump 14 45 stmt 9 "drop temporary table sudoku_work, sud..." drop procedure sudoku_solve; +DROP PROCEDURE IF EXISTS proc_19194_simple; +DROP PROCEDURE IF EXISTS proc_19194_searched; +DROP PROCEDURE IF EXISTS proc_19194_nested_1; +DROP PROCEDURE IF EXISTS proc_19194_nested_2; +DROP PROCEDURE IF EXISTS proc_19194_nested_3; +DROP PROCEDURE IF EXISTS proc_19194_nested_4; +CREATE PROCEDURE proc_19194_simple(i int) +BEGIN +DECLARE str CHAR(10); +CASE i +WHEN 1 THEN SET str="1"; +WHEN 2 THEN SET str="2"; +WHEN 3 THEN SET str="3"; +ELSE SET str="unknown"; +END CASE; +SELECT str; +END| +CREATE PROCEDURE proc_19194_searched(i int) +BEGIN +DECLARE str CHAR(10); +CASE +WHEN i=1 THEN SET str="1"; +WHEN i=2 THEN SET str="2"; +WHEN i=3 THEN SET str="3"; +ELSE SET str="unknown"; +END CASE; +SELECT str; +END| +CREATE PROCEDURE proc_19194_nested_1(i int, j int) +BEGIN +DECLARE str_i CHAR(10); +DECLARE str_j CHAR(10); +CASE i +WHEN 10 THEN SET str_i="10"; +WHEN 20 THEN +BEGIN +set str_i="20"; +CASE +WHEN j=1 THEN SET str_j="1"; +WHEN j=2 THEN SET str_j="2"; +WHEN j=3 THEN SET str_j="3"; +ELSE SET str_j="unknown"; +END CASE; +select "i was 20"; +END; +WHEN 30 THEN SET str_i="30"; +WHEN 40 THEN SET str_i="40"; +ELSE SET str_i="unknown"; +END CASE; +SELECT str_i, str_j; +END| +CREATE PROCEDURE proc_19194_nested_2(i int, j int) +BEGIN +DECLARE str_i CHAR(10); +DECLARE str_j CHAR(10); +CASE +WHEN i=10 THEN SET str_i="10"; +WHEN i=20 THEN +BEGIN +set str_i="20"; +CASE j +WHEN 1 THEN SET str_j="1"; +WHEN 2 THEN SET str_j="2"; +WHEN 3 THEN SET str_j="3"; +ELSE SET str_j="unknown"; +END CASE; +select "i was 20"; +END; +WHEN i=30 THEN SET str_i="30"; +WHEN i=40 THEN SET str_i="40"; +ELSE SET str_i="unknown"; +END CASE; +SELECT str_i, str_j; +END| +CREATE PROCEDURE proc_19194_nested_3(i int, j int) +BEGIN +DECLARE str_i CHAR(10); +DECLARE str_j CHAR(10); +CASE i +WHEN 10 THEN SET str_i="10"; +WHEN 20 THEN +BEGIN +set str_i="20"; +CASE j +WHEN 1 THEN SET str_j="1"; +WHEN 2 THEN SET str_j="2"; +WHEN 3 THEN SET str_j="3"; +ELSE SET str_j="unknown"; +END CASE; +select "i was 20"; +END; +WHEN 30 THEN SET str_i="30"; +WHEN 40 THEN SET str_i="40"; +ELSE SET str_i="unknown"; +END CASE; +SELECT str_i, str_j; +END| +CREATE PROCEDURE proc_19194_nested_4(i int, j int) +BEGIN +DECLARE str_i CHAR(10); +DECLARE str_j CHAR(10); +CASE +WHEN i=10 THEN SET str_i="10"; +WHEN i=20 THEN +BEGIN +set str_i="20"; +CASE +WHEN j=1 THEN SET str_j="1"; +WHEN j=2 THEN SET str_j="2"; +WHEN j=3 THEN SET str_j="3"; +ELSE SET str_j="unknown"; +END CASE; +select "i was 20"; +END; +WHEN i=30 THEN SET str_i="30"; +WHEN i=40 THEN SET str_i="40"; +ELSE SET str_i="unknown"; +END CASE; +SELECT str_i, str_j; +END| +SHOW PROCEDURE CODE proc_19194_simple; +Pos Instruction +0 set str@1 NULL +1 set_case_expr (12) 0 i@0 +2 jump_if_not 5(12) (case_expr@0 = 1) +3 set str@1 _latin1'1' +4 jump 12 +5 jump_if_not 8(12) (case_expr@0 = 2) +6 set str@1 _latin1'2' +7 jump 12 +8 jump_if_not 11(12) (case_expr@0 = 3) +9 set str@1 _latin1'3' +10 jump 12 +11 set str@1 _latin1'unknown' +12 stmt 0 "SELECT str" +SHOW PROCEDURE CODE proc_19194_searched; +Pos Instruction +0 set str@1 NULL +1 jump_if_not 4(11) (i@0 = 1) +2 set str@1 _latin1'1' +3 jump 11 +4 jump_if_not 7(11) (i@0 = 2) +5 set str@1 _latin1'2' +6 jump 11 +7 jump_if_not 10(11) (i@0 = 3) +8 set str@1 _latin1'3' +9 jump 11 +10 set str@1 _latin1'unknown' +11 stmt 0 "SELECT str" +SHOW PROCEDURE CODE proc_19194_nested_1; +Pos Instruction +0 set str_i@2 NULL +1 set str_j@3 NULL +2 set_case_expr (27) 0 i@0 +3 jump_if_not 6(27) (case_expr@0 = 10) +4 set str_i@2 _latin1'10' +5 jump 27 +6 jump_if_not 20(27) (case_expr@0 = 20) +7 set str_i@2 _latin1'20' +8 jump_if_not 11(18) (j@1 = 1) +9 set str_j@3 _latin1'1' +10 jump 18 +11 jump_if_not 14(18) (j@1 = 2) +12 set str_j@3 _latin1'2' +13 jump 18 +14 jump_if_not 17(18) (j@1 = 3) +15 set str_j@3 _latin1'3' +16 jump 18 +17 set str_j@3 _latin1'unknown' +18 stmt 0 "select "i was 20"" +19 jump 27 +20 jump_if_not 23(27) (case_expr@0 = 30) +21 set str_i@2 _latin1'30' +22 jump 27 +23 jump_if_not 26(27) (case_expr@0 = 40) +24 set str_i@2 _latin1'40' +25 jump 27 +26 set str_i@2 _latin1'unknown' +27 stmt 0 "SELECT str_i, str_j" +SHOW PROCEDURE CODE proc_19194_nested_2; +Pos Instruction +0 set str_i@2 NULL +1 set str_j@3 NULL +2 jump_if_not 5(27) (i@0 = 10) +3 set str_i@2 _latin1'10' +4 jump 27 +5 jump_if_not 20(27) (i@0 = 20) +6 set str_i@2 _latin1'20' +7 set_case_expr (18) 0 j@1 +8 jump_if_not 11(18) (case_expr@0 = 1) +9 set str_j@3 _latin1'1' +10 jump 18 +11 jump_if_not 14(18) (case_expr@0 = 2) +12 set str_j@3 _latin1'2' +13 jump 18 +14 jump_if_not 17(18) (case_expr@0 = 3) +15 set str_j@3 _latin1'3' +16 jump 18 +17 set str_j@3 _latin1'unknown' +18 stmt 0 "select "i was 20"" +19 jump 27 +20 jump_if_not 23(27) (i@0 = 30) +21 set str_i@2 _latin1'30' +22 jump 27 +23 jump_if_not 26(27) (i@0 = 40) +24 set str_i@2 _latin1'40' +25 jump 27 +26 set str_i@2 _latin1'unknown' +27 stmt 0 "SELECT str_i, str_j" +SHOW PROCEDURE CODE proc_19194_nested_3; +Pos Instruction +0 set str_i@2 NULL +1 set str_j@3 NULL +2 set_case_expr (28) 0 i@0 +3 jump_if_not 6(28) (case_expr@0 = 10) +4 set str_i@2 _latin1'10' +5 jump 28 +6 jump_if_not 21(28) (case_expr@0 = 20) +7 set str_i@2 _latin1'20' +8 set_case_expr (19) 1 j@1 +9 jump_if_not 12(19) (case_expr@1 = 1) +10 set str_j@3 _latin1'1' +11 jump 19 +12 jump_if_not 15(19) (case_expr@1 = 2) +13 set str_j@3 _latin1'2' +14 jump 19 +15 jump_if_not 18(19) (case_expr@1 = 3) +16 set str_j@3 _latin1'3' +17 jump 19 +18 set str_j@3 _latin1'unknown' +19 stmt 0 "select "i was 20"" +20 jump 28 +21 jump_if_not 24(28) (case_expr@0 = 30) +22 set str_i@2 _latin1'30' +23 jump 28 +24 jump_if_not 27(28) (case_expr@0 = 40) +25 set str_i@2 _latin1'40' +26 jump 28 +27 set str_i@2 _latin1'unknown' +28 stmt 0 "SELECT str_i, str_j" +SHOW PROCEDURE CODE proc_19194_nested_4; +Pos Instruction +0 set str_i@2 NULL +1 set str_j@3 NULL +2 jump_if_not 5(26) (i@0 = 10) +3 set str_i@2 _latin1'10' +4 jump 26 +5 jump_if_not 19(26) (i@0 = 20) +6 set str_i@2 _latin1'20' +7 jump_if_not 10(17) (j@1 = 1) +8 set str_j@3 _latin1'1' +9 jump 17 +10 jump_if_not 13(17) (j@1 = 2) +11 set str_j@3 _latin1'2' +12 jump 17 +13 jump_if_not 16(17) (j@1 = 3) +14 set str_j@3 _latin1'3' +15 jump 17 +16 set str_j@3 _latin1'unknown' +17 stmt 0 "select "i was 20"" +18 jump 26 +19 jump_if_not 22(26) (i@0 = 30) +20 set str_i@2 _latin1'30' +21 jump 26 +22 jump_if_not 25(26) (i@0 = 40) +23 set str_i@2 _latin1'40' +24 jump 26 +25 set str_i@2 _latin1'unknown' +26 stmt 0 "SELECT str_i, str_j" +CALL proc_19194_nested_1(10, 1); +str_i str_j +10 NULL +CALL proc_19194_nested_1(25, 1); +str_i str_j +unknown NULL +CALL proc_19194_nested_1(20, 1); +i was 20 +i was 20 +str_i str_j +20 1 +CALL proc_19194_nested_1(20, 2); +i was 20 +i was 20 +str_i str_j +20 2 +CALL proc_19194_nested_1(20, 3); +i was 20 +i was 20 +str_i str_j +20 3 +CALL proc_19194_nested_1(20, 4); +i was 20 +i was 20 +str_i str_j +20 unknown +CALL proc_19194_nested_1(30, 1); +str_i str_j +30 NULL +CALL proc_19194_nested_1(40, 1); +str_i str_j +40 NULL +CALL proc_19194_nested_1(0, 0); +str_i str_j +unknown NULL +CALL proc_19194_nested_2(10, 1); +str_i str_j +10 NULL +CALL proc_19194_nested_2(25, 1); +str_i str_j +unknown NULL +CALL proc_19194_nested_2(20, 1); +i was 20 +i was 20 +str_i str_j +20 1 +CALL proc_19194_nested_2(20, 2); +i was 20 +i was 20 +str_i str_j +20 2 +CALL proc_19194_nested_2(20, 3); +i was 20 +i was 20 +str_i str_j +20 3 +CALL proc_19194_nested_2(20, 4); +i was 20 +i was 20 +str_i str_j +20 unknown +CALL proc_19194_nested_2(30, 1); +str_i str_j +30 NULL +CALL proc_19194_nested_2(40, 1); +str_i str_j +40 NULL +CALL proc_19194_nested_2(0, 0); +str_i str_j +unknown NULL +CALL proc_19194_nested_3(10, 1); +str_i str_j +10 NULL +CALL proc_19194_nested_3(25, 1); +str_i str_j +unknown NULL +CALL proc_19194_nested_3(20, 1); +i was 20 +i was 20 +str_i str_j +20 1 +CALL proc_19194_nested_3(20, 2); +i was 20 +i was 20 +str_i str_j +20 2 +CALL proc_19194_nested_3(20, 3); +i was 20 +i was 20 +str_i str_j +20 3 +CALL proc_19194_nested_3(20, 4); +i was 20 +i was 20 +str_i str_j +20 unknown +CALL proc_19194_nested_3(30, 1); +str_i str_j +30 NULL +CALL proc_19194_nested_3(40, 1); +str_i str_j +40 NULL +CALL proc_19194_nested_3(0, 0); +str_i str_j +unknown NULL +CALL proc_19194_nested_4(10, 1); +str_i str_j +10 NULL +CALL proc_19194_nested_4(25, 1); +str_i str_j +unknown NULL +CALL proc_19194_nested_4(20, 1); +i was 20 +i was 20 +str_i str_j +20 1 +CALL proc_19194_nested_4(20, 2); +i was 20 +i was 20 +str_i str_j +20 2 +CALL proc_19194_nested_4(20, 3); +i was 20 +i was 20 +str_i str_j +20 3 +CALL proc_19194_nested_4(20, 4); +i was 20 +i was 20 +str_i str_j +20 unknown +CALL proc_19194_nested_4(30, 1); +str_i str_j +30 NULL +CALL proc_19194_nested_4(40, 1); +str_i str_j +40 NULL +CALL proc_19194_nested_4(0, 0); +str_i str_j +unknown NULL +DROP PROCEDURE proc_19194_simple; +DROP PROCEDURE proc_19194_searched; +DROP PROCEDURE proc_19194_nested_1; +DROP PROCEDURE proc_19194_nested_2; +DROP PROCEDURE proc_19194_nested_3; +DROP PROCEDURE proc_19194_nested_4; DROP PROCEDURE IF EXISTS p1; CREATE PROCEDURE p1() CREATE INDEX idx ON t1 (c1); SHOW PROCEDURE CODE p1; diff --git a/mysql-test/r/sp_stress_case.result b/mysql-test/r/sp_stress_case.result new file mode 100644 index 00000000000..b85fb41d0bf --- /dev/null +++ b/mysql-test/r/sp_stress_case.result @@ -0,0 +1,42 @@ +DROP PROCEDURE IF EXISTS bug_19194_a; +DROP PROCEDURE IF EXISTS bug_19194_b; +'Silently creating PROCEDURE bug_19194_a' +'Silently creating PROCEDURE bug_19194_b' +CALL bug_19194_a(1); +str +1 +CALL bug_19194_a(2); +str +2 +CALL bug_19194_a(1000); +str +1000 +CALL bug_19194_a(4998); +str +4998 +CALL bug_19194_a(4999); +str +4999 +CALL bug_19194_a(9999); +str +unknown +CALL bug_19194_b(1); +str +1 +CALL bug_19194_b(2); +str +2 +CALL bug_19194_b(1000); +str +1000 +CALL bug_19194_b(4998); +str +4998 +CALL bug_19194_b(4999); +str +4999 +CALL bug_19194_b(9999); +str +unknown +DROP PROCEDURE bug_19194_a; +DROP PROCEDURE bug_19194_b; diff --git a/mysql-test/t/sp-code.test b/mysql-test/t/sp-code.test index 72efa831059..97bc29fcad2 100644 --- a/mysql-test/t/sp-code.test +++ b/mysql-test/t/sp-code.test @@ -191,6 +191,241 @@ show procedure code sudoku_solve; drop procedure sudoku_solve; +# +# Bug#19194 (Right recursion in parser for CASE causes excessive stack +# usage, limitation) +# This bug also exposed a flaw in the generated code with nested case +# statements +# + +--disable_warnings +DROP PROCEDURE IF EXISTS proc_19194_simple; +DROP PROCEDURE IF EXISTS proc_19194_searched; +DROP PROCEDURE IF EXISTS proc_19194_nested_1; +DROP PROCEDURE IF EXISTS proc_19194_nested_2; +DROP PROCEDURE IF EXISTS proc_19194_nested_3; +DROP PROCEDURE IF EXISTS proc_19194_nested_4; +--enable_warnings + +delimiter |; + +CREATE PROCEDURE proc_19194_simple(i int) +BEGIN + DECLARE str CHAR(10); + + CASE i + WHEN 1 THEN SET str="1"; + WHEN 2 THEN SET str="2"; + WHEN 3 THEN SET str="3"; + ELSE SET str="unknown"; + END CASE; + + SELECT str; +END| + +CREATE PROCEDURE proc_19194_searched(i int) +BEGIN + DECLARE str CHAR(10); + + CASE + WHEN i=1 THEN SET str="1"; + WHEN i=2 THEN SET str="2"; + WHEN i=3 THEN SET str="3"; + ELSE SET str="unknown"; + END CASE; + + SELECT str; +END| + +# Outer SIMPLE case, inner SEARCHED case +CREATE PROCEDURE proc_19194_nested_1(i int, j int) +BEGIN + DECLARE str_i CHAR(10); + DECLARE str_j CHAR(10); + + CASE i + WHEN 10 THEN SET str_i="10"; + WHEN 20 THEN + BEGIN + set str_i="20"; + CASE + WHEN j=1 THEN SET str_j="1"; + WHEN j=2 THEN SET str_j="2"; + WHEN j=3 THEN SET str_j="3"; + ELSE SET str_j="unknown"; + END CASE; + select "i was 20"; + END; + WHEN 30 THEN SET str_i="30"; + WHEN 40 THEN SET str_i="40"; + ELSE SET str_i="unknown"; + END CASE; + + SELECT str_i, str_j; +END| + +# Outer SEARCHED case, inner SIMPLE case +CREATE PROCEDURE proc_19194_nested_2(i int, j int) +BEGIN + DECLARE str_i CHAR(10); + DECLARE str_j CHAR(10); + + CASE + WHEN i=10 THEN SET str_i="10"; + WHEN i=20 THEN + BEGIN + set str_i="20"; + CASE j + WHEN 1 THEN SET str_j="1"; + WHEN 2 THEN SET str_j="2"; + WHEN 3 THEN SET str_j="3"; + ELSE SET str_j="unknown"; + END CASE; + select "i was 20"; + END; + WHEN i=30 THEN SET str_i="30"; + WHEN i=40 THEN SET str_i="40"; + ELSE SET str_i="unknown"; + END CASE; + + SELECT str_i, str_j; +END| + +# Outer SIMPLE case, inner SIMPLE case +CREATE PROCEDURE proc_19194_nested_3(i int, j int) +BEGIN + DECLARE str_i CHAR(10); + DECLARE str_j CHAR(10); + + CASE i + WHEN 10 THEN SET str_i="10"; + WHEN 20 THEN + BEGIN + set str_i="20"; + CASE j + WHEN 1 THEN SET str_j="1"; + WHEN 2 THEN SET str_j="2"; + WHEN 3 THEN SET str_j="3"; + ELSE SET str_j="unknown"; + END CASE; + select "i was 20"; + END; + WHEN 30 THEN SET str_i="30"; + WHEN 40 THEN SET str_i="40"; + ELSE SET str_i="unknown"; + END CASE; + + SELECT str_i, str_j; +END| + +# Outer SEARCHED case, inner SEARCHED case +CREATE PROCEDURE proc_19194_nested_4(i int, j int) +BEGIN + DECLARE str_i CHAR(10); + DECLARE str_j CHAR(10); + + CASE + WHEN i=10 THEN SET str_i="10"; + WHEN i=20 THEN + BEGIN + set str_i="20"; + CASE + WHEN j=1 THEN SET str_j="1"; + WHEN j=2 THEN SET str_j="2"; + WHEN j=3 THEN SET str_j="3"; + ELSE SET str_j="unknown"; + END CASE; + select "i was 20"; + END; + WHEN i=30 THEN SET str_i="30"; + WHEN i=40 THEN SET str_i="40"; + ELSE SET str_i="unknown"; + END CASE; + + SELECT str_i, str_j; +END| + +delimiter ;| + +SHOW PROCEDURE CODE proc_19194_simple; +SHOW PROCEDURE CODE proc_19194_searched; +SHOW PROCEDURE CODE proc_19194_nested_1; +SHOW PROCEDURE CODE proc_19194_nested_2; +SHOW PROCEDURE CODE proc_19194_nested_3; +SHOW PROCEDURE CODE proc_19194_nested_4; + +CALL proc_19194_nested_1(10, 1); + +# +# Before 19194, the generated code was: +# 20 jump_if_not 23(27) 30 +# 21 set str_i@2 _latin1'30' +# As opposed to the expected: +# 20 jump_if_not 23(27) (case_expr@0 = 30) +# 21 set str_i@2 _latin1'30' +# +# and as a result, this call returned "30", +# because the expression 30 is always true, +# masking the case 40, case 0 and the else. +# +CALL proc_19194_nested_1(25, 1); + +CALL proc_19194_nested_1(20, 1); +CALL proc_19194_nested_1(20, 2); +CALL proc_19194_nested_1(20, 3); +CALL proc_19194_nested_1(20, 4); +CALL proc_19194_nested_1(30, 1); +CALL proc_19194_nested_1(40, 1); +CALL proc_19194_nested_1(0, 0); + +CALL proc_19194_nested_2(10, 1); + +# +# Before 19194, the generated code was: +# 20 jump_if_not 23(27) (case_expr@0 = (i@0 = 30)) +# 21 set str_i@2 _latin1'30' +# As opposed to the expected: +# 20 jump_if_not 23(27) (i@0 = 30) +# 21 set str_i@2 _latin1'30' +# and as a result, this call crashed the server, because there is no +# such variable as "case_expr@0". +# +CALL proc_19194_nested_2(25, 1); + +CALL proc_19194_nested_2(20, 1); +CALL proc_19194_nested_2(20, 2); +CALL proc_19194_nested_2(20, 3); +CALL proc_19194_nested_2(20, 4); +CALL proc_19194_nested_2(30, 1); +CALL proc_19194_nested_2(40, 1); +CALL proc_19194_nested_2(0, 0); + +CALL proc_19194_nested_3(10, 1); +CALL proc_19194_nested_3(25, 1); +CALL proc_19194_nested_3(20, 1); +CALL proc_19194_nested_3(20, 2); +CALL proc_19194_nested_3(20, 3); +CALL proc_19194_nested_3(20, 4); +CALL proc_19194_nested_3(30, 1); +CALL proc_19194_nested_3(40, 1); +CALL proc_19194_nested_3(0, 0); + +CALL proc_19194_nested_4(10, 1); +CALL proc_19194_nested_4(25, 1); +CALL proc_19194_nested_4(20, 1); +CALL proc_19194_nested_4(20, 2); +CALL proc_19194_nested_4(20, 3); +CALL proc_19194_nested_4(20, 4); +CALL proc_19194_nested_4(30, 1); +CALL proc_19194_nested_4(40, 1); +CALL proc_19194_nested_4(0, 0); + +DROP PROCEDURE proc_19194_simple; +DROP PROCEDURE proc_19194_searched; +DROP PROCEDURE proc_19194_nested_1; +DROP PROCEDURE proc_19194_nested_2; +DROP PROCEDURE proc_19194_nested_3; +DROP PROCEDURE proc_19194_nested_4; # # Bug#19207: Final parenthesis omitted for CREATE INDEX in Stored diff --git a/mysql-test/t/sp_stress_case.sh b/mysql-test/t/sp_stress_case.sh new file mode 100755 index 00000000000..a2ac948e110 --- /dev/null +++ b/mysql-test/t/sp_stress_case.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +# +# Bug#19194 (Right recursion in parser for CASE causes excessive stack +# usage, limitation) +# +# Because the code for the CASE statement is so massive, +# checking in an already generated .test is not practical, +# due to it's size (10 000 lines or 356 000 bytes). +# +# Patches are sent by email, which introduce size limitations. +# +# As a result, code is generated dynamically here. +# This script takes no argument, and generates code in stdout. +# + +cat <&1 + +CALL bug_19194_a(1); +CALL bug_19194_a(2); +CALL bug_19194_a(1000); +CALL bug_19194_a(4998); +CALL bug_19194_a(4999); +CALL bug_19194_a(9999); + +CALL bug_19194_b(1); +CALL bug_19194_b(2); +CALL bug_19194_b(1000); +CALL bug_19194_b(4998); +CALL bug_19194_b(4999); +CALL bug_19194_b(9999); + +DROP PROCEDURE bug_19194_a; +DROP PROCEDURE bug_19194_b; + diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 9761de625be..689922cfa37 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -603,27 +603,6 @@ sp_head::create(THD *thd) DBUG_PRINT("info", ("type: %d name: %s params: %s body: %s", m_type, m_name.str, m_params.str, m_body.str)); -#ifndef DBUG_OFF - optimize(); - { - String s; - sp_instr *i; - uint ip= 0; - while ((i = get_instr(ip))) - { - char buf[8]; - - sprintf(buf, "%4u: ", ip); - s.append(buf); - i->print(&s); - s.append('\n'); - ip+= 1; - } - s.append('\0'); - DBUG_PRINT("info", ("Code %s\n%s", m_qname.str, s.ptr())); - } -#endif - if (m_type == TYPE_ENUM_FUNCTION) ret= sp_create_function(thd, this); else @@ -2172,7 +2151,7 @@ sp_head::show_create_function(THD *thd) This is the main mark and move loop; it relies on the following methods in sp_instr and its subclasses: - opt_mark() Mark instruction as reachable (will recurse for jumps) + opt_mark() Mark instruction as reachable opt_shortcut_jump() Shortcut jumps to the final destination; used by opt_mark(). opt_move() Update moved instruction @@ -2185,7 +2164,7 @@ void sp_head::optimize() sp_instr *i; uint src, dst; - opt_mark(0); + opt_mark(); bp.empty(); src= dst= 0; @@ -2219,13 +2198,50 @@ void sp_head::optimize() bp.empty(); } -void -sp_head::opt_mark(uint ip) +void sp_head::add_mark_lead(uint ip, List *leads) { - sp_instr *i; + sp_instr *i= get_instr(ip); - while ((i= get_instr(ip)) && !i->marked) - ip= i->opt_mark(this); + if (i && ! i->marked) + leads->push_front(i); +} + +void +sp_head::opt_mark() +{ + uint ip; + sp_instr *i; + List leads; + + /* + Forward flow analysis algorithm in the instruction graph: + - first, add the entry point in the graph (the first instruction) to the + 'leads' list of paths to explore. + - while there are still leads to explore: + - pick one lead, and follow the path forward. Mark instruction reached. + Stop only if the end of the routine is reached, or the path converge + to code already explored (marked). + - while following a path, collect in the 'leads' list any fork to + another path (caused by conditional jumps instructions), so that these + paths can be explored as well. + */ + + /* Add the entry point */ + i= get_instr(0); + leads.push_front(i); + + /* For each path of code ... */ + while (leads.elements != 0) + { + i= leads.pop(); + + /* Mark the entire path, collecting new leads. */ + while (i && ! i->marked) + { + ip= i->opt_mark(this, & leads); + i= get_instr(ip); + } + } } @@ -2618,7 +2634,7 @@ sp_instr_jump::print(String *str) } uint -sp_instr_jump::opt_mark(sp_head *sp) +sp_instr_jump::opt_mark(sp_head *sp, List *leads) { m_dest= opt_shortcut_jump(sp, this); if (m_dest != m_ip+1) /* Jumping to following instruction? */ @@ -2712,7 +2728,7 @@ sp_instr_jump_if_not::print(String *str) uint -sp_instr_jump_if_not::opt_mark(sp_head *sp) +sp_instr_jump_if_not::opt_mark(sp_head *sp, List *leads) { sp_instr *i; @@ -2722,13 +2738,13 @@ sp_instr_jump_if_not::opt_mark(sp_head *sp) m_dest= i->opt_shortcut_jump(sp, this); m_optdest= sp->get_instr(m_dest); } - sp->opt_mark(m_dest); + sp->add_mark_lead(m_dest, leads); if ((i= sp->get_instr(m_cont_dest))) { m_cont_dest= i->opt_shortcut_jump(sp, this); m_cont_optdest= sp->get_instr(m_cont_dest); } - sp->opt_mark(m_cont_dest); + sp->add_mark_lead(m_cont_dest, leads); return m_ip+1; } @@ -2849,7 +2865,7 @@ sp_instr_hpush_jump::print(String *str) uint -sp_instr_hpush_jump::opt_mark(sp_head *sp) +sp_instr_hpush_jump::opt_mark(sp_head *sp, List *leads) { sp_instr *i; @@ -2859,7 +2875,7 @@ sp_instr_hpush_jump::opt_mark(sp_head *sp) m_dest= i->opt_shortcut_jump(sp, this); m_optdest= sp->get_instr(m_dest); } - sp->opt_mark(m_dest); + sp->add_mark_lead(m_dest, leads); return m_ip+1; } @@ -2924,15 +2940,13 @@ sp_instr_hreturn::print(String *str) uint -sp_instr_hreturn::opt_mark(sp_head *sp) +sp_instr_hreturn::opt_mark(sp_head *sp, List *leads) { if (m_dest) - return sp_instr_jump::opt_mark(sp); - else - { - marked= 1; - return UINT_MAX; - } + return sp_instr_jump::opt_mark(sp, leads); + + marked= 1; + return UINT_MAX; } @@ -3275,7 +3289,7 @@ sp_instr_set_case_expr::print(String *str) } uint -sp_instr_set_case_expr::opt_mark(sp_head *sp) +sp_instr_set_case_expr::opt_mark(sp_head *sp, List *leads) { sp_instr *i; @@ -3285,7 +3299,7 @@ sp_instr_set_case_expr::opt_mark(sp_head *sp) m_cont_dest= i->opt_shortcut_jump(sp, this); m_cont_optdest= sp->get_instr(m_cont_dest); } - sp->opt_mark(m_cont_dest); + sp->add_mark_lead(m_cont_dest, leads); return m_ip+1; } diff --git a/sql/sp_head.h b/sql/sp_head.h index 7f2da69aa0c..6a377e6f188 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -302,8 +302,19 @@ public: void restore_thd_mem_root(THD *thd); + /** + Optimize the code. + */ void optimize(); - void opt_mark(uint ip); + + /** + Helper used during flow analysis during code optimization. + See the implementation of opt_mark(). + @param ip the instruction to add to the leads list + @param leads the list of remaining paths to explore in the graph that + represents the code, during flow analysis. + */ + void add_mark_lead(uint ip, List *leads); void recursion_level_error(THD *thd); @@ -393,6 +404,12 @@ private: bool execute(THD *thd); + /** + Perform a forward flow analysis in the generated code. + Mark reachable instructions, for the optimizer. + */ + void opt_mark(); + /* Merge the list of tables used by query into the multi-set of tables used by routine. @@ -460,10 +477,10 @@ public: /* Mark this instruction as reachable during optimization and return the - index to the next instruction. Jump instruction will mark their - destination too recursively. + index to the next instruction. Jump instruction will add their + destination to the leads list. */ - virtual uint opt_mark(sp_head *sp) + virtual uint opt_mark(sp_head *sp, List *leads) { marked= 1; return m_ip+1; @@ -735,7 +752,7 @@ public: virtual void print(String *str); - virtual uint opt_mark(sp_head *sp); + virtual uint opt_mark(sp_head *sp, List *leads); virtual uint opt_shortcut_jump(sp_head *sp, sp_instr *start); @@ -785,7 +802,7 @@ public: virtual void print(String *str); - virtual uint opt_mark(sp_head *sp); + virtual uint opt_mark(sp_head *sp, List *leads); /* Override sp_instr_jump's shortcut; we stop here */ virtual uint opt_shortcut_jump(sp_head *sp, sp_instr *start) @@ -831,7 +848,7 @@ public: virtual void print(String *str); - virtual uint opt_mark(sp_head *sp) + virtual uint opt_mark(sp_head *sp, List *leads) { marked= 1; return UINT_MAX; @@ -868,7 +885,7 @@ public: virtual void print(String *str); - virtual uint opt_mark(sp_head *sp); + virtual uint opt_mark(sp_head *sp, List *leads); /* Override sp_instr_jump's shortcut; we stop here. */ virtual uint opt_shortcut_jump(sp_head *sp, sp_instr *start) @@ -933,7 +950,7 @@ public: virtual void print(String *str); - virtual uint opt_mark(sp_head *sp); + virtual uint opt_mark(sp_head *sp, List *leads); private: @@ -1103,7 +1120,7 @@ public: virtual void print(String *str); - virtual uint opt_mark(sp_head *sp) + virtual uint opt_mark(sp_head *sp, List *leads) { marked= 1; return UINT_MAX; @@ -1136,7 +1153,7 @@ public: virtual void print(String *str); - virtual uint opt_mark(sp_head *sp); + virtual uint opt_mark(sp_head *sp, List *leads); virtual void opt_move(uint dst, List *ibp); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 405f576ac04..71e7d1cf86f 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1163,7 +1163,6 @@ void st_select_lex::init_select() options= 0; sql_cache= SQL_CACHE_UNSPECIFIED; braces= 0; - when_list.empty(); expr_list.empty(); interval_list.empty(); use_index.empty(); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 378f968118e..8f4b021ca00 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -518,7 +518,6 @@ public: SQL_LIST order_list; /* ORDER clause */ List expr_list; - List when_list; /* WHEN clause (expression) */ SQL_LIST *gorder_list; Item *select_limit, *offset_limit; /* LIMIT clause parameters */ // Arrays of pointers to top elements of all_fields list diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 6f24a42c07c..57b6201d4a9 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -96,6 +96,146 @@ void turn_parser_debug_on() } #endif + +/** + Helper action for a case statement (entering the CASE). + This helper is used for both 'simple' and 'searched' cases. + @param lex the parser lex context +*/ + +void case_stmt_action_case(LEX *lex) +{ + lex->sphead->new_cont_backpatch(NULL); + + /* + BACKPATCH: Creating target label for the jump to + "case_stmt_action_end_case" + */ + + lex->spcont->push_label((char *)"", lex->sphead->instructions()); +} + +/** + Helper action for a case expression statement (the expr in 'CASE expr'). + This helper is used for 'searched' cases only. + @param lex the parser lex context + @param expr the parsed expression + @return 0 on success +*/ + +int case_stmt_action_expr(LEX *lex, Item* expr) +{ + sp_head *sp= lex->sphead; + sp_pcontext *parsing_ctx= lex->spcont; + int case_expr_id= parsing_ctx->register_case_expr(); + sp_instr_set_case_expr *i; + + if (parsing_ctx->push_case_expr_id(case_expr_id)) + return 1; + + i= new sp_instr_set_case_expr(sp->instructions(), + parsing_ctx, case_expr_id, expr, lex); + + sp->add_cont_backpatch(i); + sp->add_instr(i); + + return 0; +} + +/** + Helper action for a case when condition. + This helper is used for both 'simple' and 'searched' cases. + @param lex the parser lex context + @param when the parsed expression for the WHEN clause + @param simple true for simple cases, false for searched cases +*/ + +void case_stmt_action_when(LEX *lex, Item *when, bool simple) +{ + sp_head *sp= lex->sphead; + sp_pcontext *ctx= lex->spcont; + uint ip= sp->instructions(); + sp_instr_jump_if_not *i; + Item_case_expr *var; + Item *expr; + + if (simple) + { + var= new Item_case_expr(ctx->get_current_case_expr_id()); + +#ifndef DBUG_OFF + if (var) + { + var->m_sp= sp; + } +#endif + + expr= new Item_func_eq(var, when); + i= new sp_instr_jump_if_not(ip, ctx, expr, lex); + } + else + i= new sp_instr_jump_if_not(ip, ctx, when, lex); + + /* + BACKPATCH: Registering forward jump from + "case_stmt_action_when" to "case_stmt_action_then" + */ + + sp->push_backpatch(i, ctx->push_label((char *)"", 0)); + sp->add_cont_backpatch(i); + sp->add_instr(i); +} + +/** + Helper action for a case then statements. + This helper is used for both 'simple' and 'searched' cases. + @param lex the parser lex context +*/ + +void case_stmt_action_then(LEX *lex) +{ + sp_head *sp= lex->sphead; + sp_pcontext *ctx= lex->spcont; + uint ip= sp->instructions(); + sp_instr_jump *i = new sp_instr_jump(ip, ctx); + sp->add_instr(i); + + /* + BACKPATCH: Resolving forward jump from + "case_stmt_action_when" to "case_stmt_action_then" + */ + + sp->backpatch(ctx->pop_label()); + + /* + BACKPATCH: Registering forward jump from + "case_stmt_action_when" to "case_stmt_action_end_case" + */ + + sp->push_backpatch(i, ctx->last_label()); +} + +/** + Helper action for an end case. + This helper is used for both 'simple' and 'searched' cases. + @param lex the parser lex context + @param simple true for simple cases, false for searched cases +*/ + +void case_stmt_action_end_case(LEX *lex, bool simple) +{ + /* + BACKPATCH: Resolving forward jump from + "case_stmt_action_then" to "case_stmt_action_end_case" + */ + lex->sphead->backpatch(lex->spcont->pop_label()); + + if (simple) + lex->spcont->pop_case_expr_id(); + + lex->sphead->do_cont_backpatch(); +} + %} %union { int num; @@ -832,7 +972,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); select_item_list select_item values_list no_braces opt_limit_clause delete_limit_clause fields opt_values values procedure_list procedure_list2 procedure_item - when_list2 expr_list2 udf_expr_list3 handler + expr_list2 udf_expr_list3 handler opt_precision opt_ignore opt_column opt_restrict grant revoke set lock unlock string_list field_options field_option field_opt_list opt_binary table_lock_list table_lock @@ -860,6 +1000,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); view_algorithm view_or_trigger_or_sp view_or_trigger_or_sp_tail view_suid view_tail view_list_opt view_list view_select view_check_option trigger_tail sp_tail + case_stmt_specification simple_case_stmt searched_case_stmt END_OF_INPUT %type call sp_proc_stmts sp_proc_stmts1 sp_proc_stmt @@ -1995,43 +2136,7 @@ sp_proc_stmt: { Lex->sphead->new_cont_backpatch(NULL); } sp_if END IF { Lex->sphead->do_cont_backpatch(); } - | CASE_SYM WHEN_SYM - { - Lex->sphead->m_flags&= ~sp_head::IN_SIMPLE_CASE; - Lex->sphead->new_cont_backpatch(NULL); - } - sp_case END CASE_SYM { Lex->sphead->do_cont_backpatch(); } - | CASE_SYM - { - Lex->sphead->reset_lex(YYTHD); - Lex->sphead->new_cont_backpatch(NULL); - } - expr WHEN_SYM - { - LEX *lex= Lex; - sp_head *sp= lex->sphead; - sp_pcontext *parsing_ctx= lex->spcont; - int case_expr_id= parsing_ctx->register_case_expr(); - sp_instr_set_case_expr *i; - - if (parsing_ctx->push_case_expr_id(case_expr_id)) - YYABORT; - - i= new sp_instr_set_case_expr(sp->instructions(), - parsing_ctx, - case_expr_id, - $3, - lex); - sp->add_cont_backpatch(i); - sp->add_instr(i); - sp->m_flags|= sp_head::IN_SIMPLE_CASE; - sp->restore_lex(YYTHD); - } - sp_case END CASE_SYM - { - Lex->spcont->pop_case_expr_id(); - Lex->sphead->do_cont_backpatch(); - } + | case_stmt_specification | sp_labeled_control {} | { /* Unlabeled controls get a secret label. */ @@ -2242,72 +2347,114 @@ sp_elseifs: | ELSE sp_proc_stmts1 ; -sp_case: - { Lex->sphead->reset_lex(YYTHD); } - expr THEN_SYM - { +case_stmt_specification: + simple_case_stmt + | searched_case_stmt + ; + +simple_case_stmt: + CASE_SYM + { LEX *lex= Lex; - sp_head *sp= lex->sphead; - sp_pcontext *ctx= Lex->spcont; - uint ip= sp->instructions(); - sp_instr_jump_if_not *i; + case_stmt_action_case(lex); + lex->sphead->reset_lex(YYTHD); /* For expr $3 */ + } + expr + { + LEX *lex= Lex; + if (case_stmt_action_expr(lex, $3)) + YYABORT; - if (! (sp->m_flags & sp_head::IN_SIMPLE_CASE)) - i= new sp_instr_jump_if_not(ip, ctx, $2, lex); - else - { /* Simple case: = */ + lex->sphead->restore_lex(YYTHD); /* For expr $3 */ + } + simple_when_clause_list + else_clause_opt + END + CASE_SYM + { + LEX *lex= Lex; + case_stmt_action_end_case(lex, true); + } + ; - Item_case_expr *var; - Item *expr; +searched_case_stmt: + CASE_SYM + { + LEX *lex= Lex; + case_stmt_action_case(lex); + } + searched_when_clause_list + else_clause_opt + END + CASE_SYM + { + LEX *lex= Lex; + case_stmt_action_end_case(lex, false); + } + ; - var= new Item_case_expr(ctx->get_current_case_expr_id()); +simple_when_clause_list: + simple_when_clause + | simple_when_clause_list simple_when_clause + ; -#ifndef DBUG_OFF - if (var) - var->m_sp= sp; -#endif +searched_when_clause_list: + searched_when_clause + | searched_when_clause_list searched_when_clause + ; - expr= new Item_func_eq(var, $2); +simple_when_clause: + WHEN_SYM + { + Lex->sphead->reset_lex(YYTHD); /* For expr $3 */ + } + expr + { + /* Simple case: = */ - i= new sp_instr_jump_if_not(ip, ctx, expr, lex); - } - sp->push_backpatch(i, ctx->push_label((char *)"", 0)); - sp->add_cont_backpatch(i); + LEX *lex= Lex; + case_stmt_action_when(lex, $3, true); + lex->sphead->restore_lex(YYTHD); /* For expr $3 */ + } + THEN_SYM + sp_proc_stmts1 + { + LEX *lex= Lex; + case_stmt_action_then(lex); + } + ; + +searched_when_clause: + WHEN_SYM + { + Lex->sphead->reset_lex(YYTHD); /* For expr $3 */ + } + expr + { + LEX *lex= Lex; + case_stmt_action_when(lex, $3, false); + lex->sphead->restore_lex(YYTHD); /* For expr $3 */ + } + THEN_SYM + sp_proc_stmts1 + { + LEX *lex= Lex; + case_stmt_action_then(lex); + } + ; + +else_clause_opt: + /* empty */ + { + LEX *lex= Lex; + sp_head *sp= lex->sphead; + uint ip= sp->instructions(); + sp_instr_error *i= new sp_instr_error(ip, lex->spcont, + ER_SP_CASE_NOT_FOUND); sp->add_instr(i); - sp->restore_lex(YYTHD); - } - sp_proc_stmts1 - { - sp_head *sp= Lex->sphead; - sp_pcontext *ctx= Lex->spcont; - uint ip= sp->instructions(); - sp_instr_jump *i = new sp_instr_jump(ip, ctx); - - sp->add_instr(i); - sp->backpatch(ctx->pop_label()); - sp->push_backpatch(i, ctx->push_label((char *)"", 0)); - } - sp_whens - { - LEX *lex= Lex; - - lex->sphead->backpatch(lex->spcont->pop_label()); - } - ; - -sp_whens: - /* Empty */ - { - sp_head *sp= Lex->sphead; - uint ip= sp->instructions(); - sp_instr_error *i= new sp_instr_error(ip, Lex->spcont, - ER_SP_CASE_NOT_FOUND); - - sp->add_instr(i); - } - | ELSE sp_proc_stmts1 {} - | WHEN_SYM sp_case {} - ; + } + | ELSE sp_proc_stmts1 + ; sp_labeled_control: label_ident ':' @@ -4374,8 +4521,8 @@ simple_expr: if (!$$) YYABORT; } - | CASE_SYM opt_expr WHEN_SYM when_list opt_else END - { $$= new Item_func_case(* $4, $2, $5 ); } + | CASE_SYM opt_expr when_list opt_else END + { $$= new Item_func_case(* $3, $2, $4 ); } | CONVERT_SYM '(' expr ',' cast_type ')' { $$= create_func_cast($3, $5, @@ -5162,23 +5309,19 @@ opt_else: | ELSE expr { $$= $2; }; when_list: - { Select->when_list.push_front(new List); } - when_list2 - { $$= Select->when_list.pop(); }; - -when_list2: - expr THEN_SYM expr - { - SELECT_LEX *sel=Select; - sel->when_list.head()->push_back($1); - sel->when_list.head()->push_back($3); - } - | when_list2 WHEN_SYM expr THEN_SYM expr - { - SELECT_LEX *sel=Select; - sel->when_list.head()->push_back($3); - sel->when_list.head()->push_back($5); - }; + WHEN_SYM expr THEN_SYM expr + { + $$= new List; + $$->push_back($2); + $$->push_back($4); + } + | when_list WHEN_SYM expr THEN_SYM expr + { + $1->push_back($3); + $1->push_back($5); + $$= $1; + } + ; /* Warning - may return NULL in case of incomplete SELECT */ table_ref: From fe84b016e1fd62d92c9f13a1e21784ea8cb200cd Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 1 Dec 2006 13:25:06 +0300 Subject: [PATCH 2/8] A fix and a test case for Bug#24179 "select b into $var" fails with --cursor_protocol": fix a misleading error message in case of SELECT .. INTO. sql/sql_class.cc: Implement select_result::check_simple_select hierarchy to support correct error messages in case of SELECT .. INTO and C API cursors. sql/sql_class.h: Set the error message inside the function that checks for the error condition (simple_select, renamed to check_simple_select). sql/sql_prepare.cc: Use a new method that now sets the error. tests/mysql_client_test.c: Add a test case for Bug#24179 "select b into $var" fails with --cursor_protocol" (check for the right error message and error code). --- sql/sql_class.cc | 14 ++++++++++++++ sql/sql_class.h | 12 ++++++++++-- sql/sql_prepare.cc | 3 +-- tests/mysql_client_test.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/sql/sql_class.cc b/sql/sql_class.cc index d2f1e9ed0d9..f46cd2353eb 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -885,6 +885,13 @@ void select_result::cleanup() /* do nothing */ } +bool select_result::check_simple_select() const +{ + my_error(ER_SP_BAD_CURSOR_QUERY, MYF(0)); + return TRUE; +} + + static String default_line_term("\n",default_charset_info); static String default_escaped("\\",default_charset_info); static String default_field_term("\t",default_charset_info); @@ -1553,6 +1560,13 @@ int select_dumpvar::prepare(List &list, SELECT_LEX_UNIT *u) } +bool select_dumpvar::check_simple_select() const +{ + my_error(ER_SP_BAD_CURSOR_SELECT, MYF(0)); + return TRUE; +} + + void select_dumpvar::cleanup() { vars.empty(); diff --git a/sql/sql_class.h b/sql/sql_class.h index 41845dc5c76..ec03b9cb4a6 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1724,7 +1724,14 @@ public: virtual bool initialize_tables (JOIN *join=0) { return 0; } virtual void send_error(uint errcode,const char *err); virtual bool send_eof()=0; - virtual bool simple_select() { return 0; } + /** + Check if this query returns a result set and therefore is allowed in + cursors and set an error message if it is not the case. + + @retval FALSE success + @retval TRUE error, an error message is set + */ + virtual bool check_simple_select() const; virtual void abort() {} /* Cleanup instance of this class for next execution of a prepared @@ -1762,7 +1769,7 @@ public: bool send_fields(List &list, uint flags); bool send_data(List &items); bool send_eof(); - bool simple_select() { return 1; } + virtual bool check_simple_select() const { return FALSE; } void abort(); }; @@ -2202,6 +2209,7 @@ public: int prepare(List &list, SELECT_LEX_UNIT *u); bool send_data(List &items); bool send_eof(); + virtual bool check_simple_select() const; void cleanup(); }; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 1e7601c0951..8b2c2cd3674 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2906,10 +2906,9 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) in INSERT ... SELECT and similar commands. */ - if (open_cursor && lex->result && !lex->result->simple_select()) + if (open_cursor && lex->result && lex->result->check_simple_select()) { DBUG_PRINT("info",("Cursor asked for not SELECT stmt")); - my_error(ER_SP_BAD_CURSOR_QUERY, MYF(0)); return TRUE; } diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index c596a589d55..0b8d4d81558 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -15456,6 +15456,33 @@ static void test_bug21635() DBUG_VOID_RETURN; } +/* + Bug#24179 "select b into $var" fails with --cursor_protocol" + The failure is correct, check that the returned message is meaningful. +*/ + +static void test_bug24179() +{ + int rc; + MYSQL_STMT *stmt; + + DBUG_ENTER("test_bug24179"); + myheader("test_bug24179"); + + stmt= open_cursor("select 1 into @a"); + rc= mysql_stmt_execute(stmt); + DIE_UNLESS(rc); + if (!opt_silent) + { + printf("Got error (as expected): %d %s\n", + mysql_stmt_errno(stmt), + mysql_stmt_error(stmt)); + } + DIE_UNLESS(mysql_stmt_errno(stmt) == 1323); + + DBUG_VOID_RETURN; +} + /* Read and parse arguments and MySQL options from my.cnf @@ -15735,6 +15762,7 @@ static struct my_tests_st my_tests[]= { { "test_bug21726", test_bug21726 }, { "test_bug23383", test_bug23383 }, { "test_bug21635", test_bug21635 }, + { "test_bug24179", test_bug24179 }, { 0, 0 } }; From 85e15fb3e7dda4a7bfb42767b55a1266b5d69982 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Dec 2006 14:05:27 +0300 Subject: [PATCH 3/8] Use standard shell instead of BASH. --- mysql-test/t/log.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/t/log.sh b/mysql-test/t/log.sh index 20b265087cc..29cf8d3e1a3 100755 --- a/mysql-test/t/log.sh +++ b/mysql-test/t/log.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh ########################################################################### From 1adc13681283fe637ece524c9b1a4311700cfbd7 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Dec 2006 19:48:49 +0300 Subject: [PATCH 4/8] Fix Bug #19044 IM aborts on exit On windows IM aborted on assert once one stoppped it. The reason is that we didn't close the sockets on windows and therefore, the listener thread wasn't able to finish. This happened because we used close() call for it. While on windows one should use closesocket(). On other platfroms we have appropriate defines for closesocket(), so this is the function which should be used. server-tools/instance-manager/listener.cc: close -> closesocket --- server-tools/instance-manager/listener.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server-tools/instance-manager/listener.cc b/server-tools/instance-manager/listener.cc index 3f60c2cc74a..70cd7360f0d 100644 --- a/server-tools/instance-manager/listener.cc +++ b/server-tools/instance-manager/listener.cc @@ -188,7 +188,7 @@ void Listener_thread::run() else { shutdown(client_fd, SHUT_RDWR); - close(client_fd); + closesocket(client_fd); } } } @@ -200,7 +200,7 @@ void Listener_thread::run() log_info("Listener_thread::run(): shutdown requested, exiting..."); for (i= 0; i < num_sockets; i++) - close(sockets[i]); + closesocket(sockets[i]); #ifndef __WIN__ unlink(unix_socket_address.sun_path); @@ -213,7 +213,7 @@ void Listener_thread::run() err: // we have to close the ip sockets in case of error for (i= 0; i < num_sockets; i++) - close(sockets[i]); + closesocket(sockets[i]); thread_registry.unregister_thread(&thread_info); thread_registry.request_shutdown(); @@ -260,7 +260,7 @@ int Listener_thread::create_tcp_socket() { log_error("Listener_thread::run(): bind(ip socket) failed, '%s'", strerror(errno)); - close(ip_socket); + closesocket(ip_socket); return -1; } @@ -268,7 +268,7 @@ int Listener_thread::create_tcp_socket() { log_error("Listener_thread::run(): listen(ip socket) failed, %s", strerror(errno)); - close(ip_socket); + closesocket(ip_socket); return -1; } From c01c4cc3590a619d907544d7bdb3f9a4bd523ce1 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Dec 2006 16:59:02 -0700 Subject: [PATCH 5/8] Bug#19194 (Right recursion in parser for CASE causes excessive stack usage, limitation) Bug#24854 (Mixing Searched Case with Simple Case inside Stored Procedure crashes Mysqld) Implemented code review (19194) comments mysql-test/r/sp_stress_case.result: Implemented code review comments : use SQL instead of a shell script to generate the code mysql-test/t/sp_stress_case.test: Adjusted sql/sql_yacc.yy: Added more explicit comments BitKeeper/deleted/.del-sp_stress_case.sh: Delete: mysql-test/t/sp_stress_case.sh --- mysql-test/r/sp_stress_case.result | 114 ++++++++++++++++++++++++----- mysql-test/t/sp_stress_case.sh | 68 ----------------- mysql-test/t/sp_stress_case.test | 105 ++++++++++++++++++++------ sql/sql_yacc.yy | 43 ++++++++++- 4 files changed, 220 insertions(+), 110 deletions(-) delete mode 100755 mysql-test/t/sp_stress_case.sh diff --git a/mysql-test/r/sp_stress_case.result b/mysql-test/r/sp_stress_case.result index b85fb41d0bf..8ec68363c8d 100644 --- a/mysql-test/r/sp_stress_case.result +++ b/mysql-test/r/sp_stress_case.result @@ -1,42 +1,120 @@ -DROP PROCEDURE IF EXISTS bug_19194_a; -DROP PROCEDURE IF EXISTS bug_19194_b; -'Silently creating PROCEDURE bug_19194_a' -'Silently creating PROCEDURE bug_19194_b' -CALL bug_19194_a(1); +DROP PROCEDURE IF EXISTS proc_19194_codegen; +DROP PROCEDURE IF EXISTS bug_19194_simple; +DROP PROCEDURE IF EXISTS bug_19194_searched; +CREATE PROCEDURE proc_19194_codegen( +IN proc_name VARCHAR(50), +IN count INTEGER, +IN simple INTEGER, +OUT body MEDIUMTEXT) +BEGIN +DECLARE code MEDIUMTEXT; +DECLARE i INT DEFAULT 1; +SET code = concat("CREATE PROCEDURE ", proc_name, "(i INT)\n"); +SET code = concat(code, "BEGIN\n"); +SET code = concat(code, " DECLARE str CHAR(10);\n"); +IF (simple) +THEN +SET code = concat(code, " CASE i\n"); +ELSE +SET code = concat(code, " CASE\n"); +END IF; +WHILE (i <= count) +DO +IF (simple) +THEN +SET code = concat(code, " WHEN ", i, " THEN SET str=\"", i, "\";\n"); +ELSE +SET code = concat(code, " WHEN i=", i, " THEN SET str=\"", i, "\";\n"); +END IF; +SET i = i + 1; +END WHILE; +SET code = concat(code, " ELSE SET str=\"unknown\";\n"); +SET code = concat(code, " END CASE;\n"); +SET code = concat(code, " SELECT str;\n"); +SET code = concat(code, "END\n"); +SET body = code; +END| +set @body=""; +call proc_19194_codegen("test_simple", 10, 1, @body); +select @body; +@body +CREATE PROCEDURE test_simple(i INT) +BEGIN + DECLARE str CHAR(10); + CASE i + WHEN 1 THEN SET str="1"; + WHEN 2 THEN SET str="2"; + WHEN 3 THEN SET str="3"; + WHEN 4 THEN SET str="4"; + WHEN 5 THEN SET str="5"; + WHEN 6 THEN SET str="6"; + WHEN 7 THEN SET str="7"; + WHEN 8 THEN SET str="8"; + WHEN 9 THEN SET str="9"; + WHEN 10 THEN SET str="10"; + ELSE SET str="unknown"; + END CASE; + SELECT str; +END + +call proc_19194_codegen("test_searched", 10, 0, @body); +select @body; +@body +CREATE PROCEDURE test_searched(i INT) +BEGIN + DECLARE str CHAR(10); + CASE + WHEN i=1 THEN SET str="1"; + WHEN i=2 THEN SET str="2"; + WHEN i=3 THEN SET str="3"; + WHEN i=4 THEN SET str="4"; + WHEN i=5 THEN SET str="5"; + WHEN i=6 THEN SET str="6"; + WHEN i=7 THEN SET str="7"; + WHEN i=8 THEN SET str="8"; + WHEN i=9 THEN SET str="9"; + WHEN i=10 THEN SET str="10"; + ELSE SET str="unknown"; + END CASE; + SELECT str; +END + +CALL bug_19194_simple(1); str 1 -CALL bug_19194_a(2); +CALL bug_19194_simple(2); str 2 -CALL bug_19194_a(1000); +CALL bug_19194_simple(1000); str 1000 -CALL bug_19194_a(4998); +CALL bug_19194_simple(4998); str 4998 -CALL bug_19194_a(4999); +CALL bug_19194_simple(4999); str 4999 -CALL bug_19194_a(9999); +CALL bug_19194_simple(9999); str unknown -CALL bug_19194_b(1); +CALL bug_19194_searched(1); str 1 -CALL bug_19194_b(2); +CALL bug_19194_searched(2); str 2 -CALL bug_19194_b(1000); +CALL bug_19194_searched(1000); str 1000 -CALL bug_19194_b(4998); +CALL bug_19194_searched(4998); str 4998 -CALL bug_19194_b(4999); +CALL bug_19194_searched(4999); str 4999 -CALL bug_19194_b(9999); +CALL bug_19194_searched(9999); str unknown -DROP PROCEDURE bug_19194_a; -DROP PROCEDURE bug_19194_b; +DROP PROCEDURE proc_19194_codegen; +DROP PROCEDURE bug_19194_simple; +DROP PROCEDURE bug_19194_searched; diff --git a/mysql-test/t/sp_stress_case.sh b/mysql-test/t/sp_stress_case.sh deleted file mode 100755 index a2ac948e110..00000000000 --- a/mysql-test/t/sp_stress_case.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/sh - -# -# Bug#19194 (Right recursion in parser for CASE causes excessive stack -# usage, limitation) -# -# Because the code for the CASE statement is so massive, -# checking in an already generated .test is not practical, -# due to it's size (10 000 lines or 356 000 bytes). -# -# Patches are sent by email, which introduce size limitations. -# -# As a result, code is generated dynamically here. -# This script takes no argument, and generates code in stdout. -# - -cat <&1 +delimiter |; -CALL bug_19194_a(1); -CALL bug_19194_a(2); -CALL bug_19194_a(1000); -CALL bug_19194_a(4998); -CALL bug_19194_a(4999); -CALL bug_19194_a(9999); +CREATE PROCEDURE proc_19194_codegen( + IN proc_name VARCHAR(50), + IN count INTEGER, + IN simple INTEGER, + OUT body MEDIUMTEXT) +BEGIN + DECLARE code MEDIUMTEXT; + DECLARE i INT DEFAULT 1; -CALL bug_19194_b(1); -CALL bug_19194_b(2); -CALL bug_19194_b(1000); -CALL bug_19194_b(4998); -CALL bug_19194_b(4999); -CALL bug_19194_b(9999); + SET code = concat("CREATE PROCEDURE ", proc_name, "(i INT)\n"); + SET code = concat(code, "BEGIN\n"); + SET code = concat(code, " DECLARE str CHAR(10);\n"); -DROP PROCEDURE bug_19194_a; -DROP PROCEDURE bug_19194_b; + IF (simple) + THEN + SET code = concat(code, " CASE i\n"); + ELSE + SET code = concat(code, " CASE\n"); + END IF; + + WHILE (i <= count) + DO + IF (simple) + THEN + SET code = concat(code, " WHEN ", i, " THEN SET str=\"", i, "\";\n"); + ELSE + SET code = concat(code, " WHEN i=", i, " THEN SET str=\"", i, "\";\n"); + END IF; + + SET i = i + 1; + END WHILE; + + SET code = concat(code, " ELSE SET str=\"unknown\";\n"); + SET code = concat(code, " END CASE;\n"); + SET code = concat(code, " SELECT str;\n"); + + SET code = concat(code, "END\n"); + + SET body = code; +END| + +delimiter ;| + +set @body=""; +call proc_19194_codegen("test_simple", 10, 1, @body); +select @body; +call proc_19194_codegen("test_searched", 10, 0, @body); +select @body; + +--disable_query_log +call proc_19194_codegen("bug_19194_simple", 5000, 1, @body); +let $proc_body = `select @body`; +eval $proc_body; +call proc_19194_codegen("bug_19194_searched", 5000, 1, @body); +let $proc_body = `select @body`; +eval $proc_body; +--enable_query_log + +CALL bug_19194_simple(1); +CALL bug_19194_simple(2); +CALL bug_19194_simple(1000); +CALL bug_19194_simple(4998); +CALL bug_19194_simple(4999); +CALL bug_19194_simple(9999); + +CALL bug_19194_searched(1); +CALL bug_19194_searched(2); +CALL bug_19194_searched(1000); +CALL bug_19194_searched(4998); +CALL bug_19194_searched(4999); +CALL bug_19194_searched(9999); + +DROP PROCEDURE proc_19194_codegen; +DROP PROCEDURE bug_19194_simple; +DROP PROCEDURE bug_19194_searched; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 57b6201d4a9..014be54eef6 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -100,6 +100,42 @@ void turn_parser_debug_on() /** Helper action for a case statement (entering the CASE). This helper is used for both 'simple' and 'searched' cases. + This helper, with the other case_stmt_action_..., is executed when + the following SQL code is parsed: +
+CREATE PROCEDURE proc_19194_simple(i int)
+BEGIN
+  DECLARE str CHAR(10);
+
+  CASE i
+    WHEN 1 THEN SET str="1";
+    WHEN 2 THEN SET str="2";
+    WHEN 3 THEN SET str="3";
+    ELSE SET str="unknown";
+  END CASE;
+
+  SELECT str;
+END
+
+ The actions are used to generate the following code: +
+SHOW PROCEDURE CODE proc_19194_simple;
+Pos     Instruction
+0       set str@1 NULL
+1       set_case_expr (12) 0 i@0
+2       jump_if_not 5(12) (case_expr@0 = 1)
+3       set str@1 _latin1'1'
+4       jump 12
+5       jump_if_not 8(12) (case_expr@0 = 2)
+6       set str@1 _latin1'2'
+7       jump 12
+8       jump_if_not 11(12) (case_expr@0 = 3)
+9       set str@1 _latin1'3'
+10      jump 12
+11      set str@1 _latin1'unknown'
+12      stmt 0 "SELECT str"
+
+ @param lex the parser lex context */ @@ -110,6 +146,7 @@ void case_stmt_action_case(LEX *lex) /* BACKPATCH: Creating target label for the jump to "case_stmt_action_end_case" + (Instruction 12 in the example) */ lex->spcont->push_label((char *)"", lex->sphead->instructions()); @@ -179,6 +216,7 @@ void case_stmt_action_when(LEX *lex, Item *when, bool simple) /* BACKPATCH: Registering forward jump from "case_stmt_action_when" to "case_stmt_action_then" + (jump_if_not from instruction 2 to 5, 5 to 8 ... in the example) */ sp->push_backpatch(i, ctx->push_label((char *)"", 0)); @@ -203,13 +241,15 @@ void case_stmt_action_then(LEX *lex) /* BACKPATCH: Resolving forward jump from "case_stmt_action_when" to "case_stmt_action_then" + (jump_if_not from instruction 2 to 5, 5 to 8 ... in the example) */ sp->backpatch(ctx->pop_label()); /* BACKPATCH: Registering forward jump from - "case_stmt_action_when" to "case_stmt_action_end_case" + "case_stmt_action_then" to "case_stmt_action_end_case" + (jump from instruction 4 to 12, 7 to 12 ... in the example) */ sp->push_backpatch(i, ctx->last_label()); @@ -227,6 +267,7 @@ void case_stmt_action_end_case(LEX *lex, bool simple) /* BACKPATCH: Resolving forward jump from "case_stmt_action_then" to "case_stmt_action_end_case" + (jump from instruction 4 to 12, 7 to 12 ... in the example) */ lex->sphead->backpatch(lex->spcont->pop_label()); From d501b2dd3966d4f236b46a3b3bb7b89929e25716 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Dec 2006 18:52:24 -0700 Subject: [PATCH 6/8] minor cleanup mysql-test/t/sp_stress_case.test: Minor cleanup ... the test is now faster, even in debug builds --- mysql-test/t/sp_stress_case.test | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mysql-test/t/sp_stress_case.test b/mysql-test/t/sp_stress_case.test index 5b278802e26..1b5bd8991a9 100644 --- a/mysql-test/t/sp_stress_case.test +++ b/mysql-test/t/sp_stress_case.test @@ -3,11 +3,6 @@ # usage, limitation) # -# This test takes some time (8 min) in debug builds -# It's provided as a separate file so that the next line can be uncommented -# later if needed: -# -- source include/big_test.inc - --disable_warnings DROP PROCEDURE IF EXISTS proc_19194_codegen; DROP PROCEDURE IF EXISTS bug_19194_simple; From b66ab7f80b629c6d33d7e4ac01002cbb7676df18 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 19 Dec 2006 15:32:02 +0300 Subject: [PATCH 7/8] Fix for BUG#24293: '\Z' token is not handled correctly in views. If SELECT-part of CREATE VIEW statement contains '\Z', it is not handled correctly. The problem was in String::print(). Symbol with code 032 (26) is replaced with '\z', which is not supported by the lexer. The fix is to replace the symbol with '\Z'. mysql-test/r/view.result: Update result file. mysql-test/t/view.test: Add test case for BUG#24293. sql/sql_string.cc: We should replace 032 with \Z, since lexer does not understand \z. --- mysql-test/r/view.result | 9 +++++++++ mysql-test/t/view.test | 15 +++++++++++++++ sql/sql_string.cc | 4 ++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index 81711f95ae6..caa494c26b3 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -3014,4 +3014,13 @@ i j 6 3 DROP VIEW v1, v2; DROP TABLE t1; +DROP VIEW IF EXISTS v1; +CREATE VIEW v1 AS SELECT 'The\ZEnd'; +SELECT * FROM v1; +TheEnd +TheEnd +SHOW CREATE VIEW v1; +View Create View +v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select _latin1'The\ZEnd' AS `TheEnd` +DROP VIEW v1; End of 5.0 tests. diff --git a/mysql-test/t/view.test b/mysql-test/t/view.test index 8473458ae15..a34a1ba117d 100644 --- a/mysql-test/t/view.test +++ b/mysql-test/t/view.test @@ -2960,4 +2960,19 @@ DROP VIEW v1, v2; DROP TABLE t1; +# +# BUG#24293: '\Z' token is not handled correctly in views +# + +--disable_warnings +DROP VIEW IF EXISTS v1; +--enable_warnings + +CREATE VIEW v1 AS SELECT 'The\ZEnd'; +SELECT * FROM v1; + +SHOW CREATE VIEW v1; + +DROP VIEW v1; + --echo End of 5.0 tests. diff --git a/sql/sql_string.cc b/sql/sql_string.cc index 85ff1fddc45..10ce72e9b9f 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -1033,8 +1033,8 @@ void String::print(String *str) case '\r': str->append(STRING_WITH_LEN("\\r")); break; - case 26: //Ctrl-Z - str->append(STRING_WITH_LEN("\\z")); + case '\032': // Ctrl-Z + str->append(STRING_WITH_LEN("\\Z")); break; default: str->append(c); From 4fe1561f980c052f8bebd1c38e1fac6c3291fa7a Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 9 Jan 2007 12:24:25 +0300 Subject: [PATCH 8/8] BUG#23443: user-defined variables can consume too much memory in the server The problem was that when memory was exhausted HEAP engine could crash (GROUP BY uses HEAP TABLE). Alternatively, if SET was used, it could report an error "You may only use constant expressions with SET" instead of "Out of memory (Needed NNNNNN bytes)". The solution is: - pass MY_WME to (some) calls to my_malloc() to get correct message. - fix heap_write() so that the first key is skipped during cleanup on ENOMEM because it wasn't inserted and doesn't have to be deleted. No test case is provided because we can't test out-of-memory behaviour in our current test framework. heap/hp_block.c: If allocation fails, write an error message. heap/hp_write.c: On ENOMEM, skip the first key in cleanup, as it wasn't inserted yet. sql/item_func.cc: Add MY_WME so that OOM error will be reported. --- heap/hp_block.c | 2 +- heap/hp_write.c | 15 ++++++++++++--- sql/item_func.cc | 5 +++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/heap/hp_block.c b/heap/hp_block.c index 5c052218e58..8afcaf8d945 100644 --- a/heap/hp_block.c +++ b/heap/hp_block.c @@ -47,7 +47,7 @@ int _hp_get_new_block(HP_BLOCK *block, ulong *alloc_length) break; *alloc_length=sizeof(HP_PTRS)*i+block->records_in_block* block->recbuffer; - if (!(root=(HP_PTRS*) my_malloc(*alloc_length,MYF(0)))) + if (!(root=(HP_PTRS*) my_malloc(*alloc_length,MYF(MY_WME)))) return 1; if (i == 0) diff --git a/heap/hp_write.c b/heap/hp_write.c index 18fa95e7760..b5349d74691 100644 --- a/heap/hp_write.c +++ b/heap/hp_write.c @@ -66,13 +66,22 @@ int heap_write(HP_INFO *info, const byte *record) DBUG_RETURN(0); err: - DBUG_PRINT("info",("Duplicate key: %d",key)); + if (my_errno == HA_ERR_FOUND_DUPP_KEY) + DBUG_PRINT("info",("Duplicate key: %d",key)); info->errkey= key; - do + /* + Because 'key' is unsigned, we increase it before the loop, unless + we have to skip the key that wasn't inserted yet due to OOM. In + the loop we test 'key' before decreasing it as the protection + against value wraparound. + */ + if (my_errno != ENOMEM) + key++; + while (key-- > 0) { if (_hp_delete_key(info,share->keydef+key,record,pos,0)) break; - } while (key-- > 0); + } share->deleted++; *((byte**) pos)=share->del_link; diff --git a/sql/item_func.cc b/sql/item_func.cc index e83d1f35db8..421f72ab69b 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -1892,8 +1892,9 @@ bool Item_func_set_user_var::update_hash(const void *ptr, uint length, char *pos= (char*) entry+ ALIGN_SIZE(sizeof(user_var_entry)); if (entry->value == pos) entry->value=0; - if (!(entry->value=(char*) my_realloc(entry->value, length, - MYF(MY_ALLOW_ZERO_PTR)))) + entry->value= (char*) my_realloc(entry->value, length, + MYF(MY_ALLOW_ZERO_PTR | MY_WME)); + if (!entry->value) goto err; } }