From 81ba971d0334dee5dce880ea848300cc5ed45ccb Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Mon, 15 Aug 2016 16:25:27 +0400 Subject: [PATCH] MDEV-10411 Providing compatibility for basic PL/SQL constructs - Part 9: EXCEPTION handlers The top-most stored routine blocks now support EXCEPTION clause in its correct place: AS [ declarations ] BEGIN statements [ EXCEPTION exceptions ] END Inner block will be done in a separate commit. - Part 14: IN OUT instead of INOUT (in SP parameter declarations) --- .../suite/compat/oracle/r/sp-code.result | 178 ++++++++++++++++++ mysql-test/suite/compat/oracle/r/sp.result | 27 ++- mysql-test/suite/compat/oracle/t/sp-code.test | 145 ++++++++++++++ mysql-test/suite/compat/oracle/t/sp.test | 29 ++- sql/sp_head.cc | 27 +++ sql/sp_head.h | 9 + sql/sql_lex.cc | 49 +++++ sql/sql_lex.h | 5 + sql/sql_yacc_ora.yy | 35 +++- 9 files changed, 493 insertions(+), 11 deletions(-) create mode 100644 mysql-test/suite/compat/oracle/r/sp-code.result create mode 100644 mysql-test/suite/compat/oracle/t/sp-code.test diff --git a/mysql-test/suite/compat/oracle/r/sp-code.result b/mysql-test/suite/compat/oracle/r/sp-code.result new file mode 100644 index 00000000000..56339e636c3 --- /dev/null +++ b/mysql-test/suite/compat/oracle/r/sp-code.result @@ -0,0 +1,178 @@ +SET sql_mode=ORACLE; +# No HANDLER declarations, no exceptions +CREATE FUNCTION f1 RETURN INT +AS +BEGIN +RETURN 10; +END; +/ +SHOW FUNCTION CODE f1; +Pos Instruction +0 freturn 3 10 +SELECT f1(); +f1() +10 +DROP FUNCTION f1; +# No HANDLER declarations, no code, no exceptions +CREATE PROCEDURE p1 () +IS +BEGIN +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 jump 3 +CALL p1; +DROP PROCEDURE p1; +# No HANDLER declarations, no code, some exceptions +CREATE PROCEDURE p1 (v IN OUT INT) +IS +BEGIN +EXCEPTION +WHEN 1002 THEN v:=225; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 jump 1 +1 hpush_jump 4 1 EXIT +2 set v@0 225 +3 hreturn 0 4 +4 hpop 1 +set @v= 10; +CALL p1(@v); +SELECT @v; +@v +10 +DROP PROCEDURE p1; +# No HANDLER declarations, some code, some exceptions +CREATE PROCEDURE p1 (v IN OUT INT) +IS +BEGIN +v:=224; +EXCEPTION +WHEN 1002 THEN v:=225; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 jump 3 +1 set v@0 224 +2 jump 6 +3 hpush_jump 1 1 EXIT +4 set v@0 225 +5 hreturn 0 6 +6 hpop 1 +set @v= 10; +CALL p1(@v); +SELECT @v; +@v +224 +DROP PROCEDURE p1; +# Some HANDLER declarations, no code, no exceptions +CREATE PROCEDURE p1 (v IN OUT INT) +IS +EXIT HANDLER FOR 1000 +BEGIN +v:=123; +END; +BEGIN +EXCEPTION +WHEN 1002 THEN v:=225; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 hpush_jump 3 1 EXIT +1 set v@0 123 +2 hreturn 0 6 +3 hpush_jump 6 1 EXIT +4 set v@0 225 +5 hreturn 0 6 +6 hpop 2 +set @v= 10; +CALL p1(@v); +SELECT @v; +@v +10 +DROP PROCEDURE p1; +# Some HANDLER declarations, no code, some exceptions +CREATE PROCEDURE p1 (v IN OUT INT) +IS +EXIT HANDLER FOR 1000 +BEGIN +v:=123; +END; +BEGIN +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 hpush_jump 3 1 EXIT +1 set v@0 123 +2 hreturn 0 3 +3 hpop 1 +set @v= 10; +CALL p1(@v); +SELECT @v; +@v +10 +DROP PROCEDURE p1; +# Some HANDLER declarations, some code, no exceptions +CREATE PROCEDURE p1 (v IN OUT INT) +IS +EXIT HANDLER FOR 1000 +BEGIN +v:=123; +END; +BEGIN +v:=223; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 hpush_jump 3 1 EXIT +1 set v@0 123 +2 hreturn 0 5 +3 set v@0 223 +4 jump 5 +5 hpop 1 +set @v= 10; +CALL p1(@v); +SELECT @v; +@v +223 +DROP PROCEDURE p1; +# Some HANDLER declarations, some code, some exceptions +CREATE PROCEDURE p1 (v IN OUT VARCHAR2(20)) +IS +EXIT HANDLER FOR 1000 +BEGIN +v:=123; +END; +CONTINUE HANDLER FOR 1001 +BEGIN +SET v=223; +END; +BEGIN +v:= 1; +EXCEPTION +WHEN 1002 THEN SET v=225; +END; +/ +SHOW PROCEDURE CODE p1; +Pos Instruction +0 hpush_jump 3 1 EXIT +1 set v@0 123 +2 hreturn 0 12 +3 hpush_jump 8 1 CONTINUE +4 set v@0 223 +5 hreturn 1 +6 set v@0 1 +7 jump 12 +8 hpush_jump 6 1 EXIT +9 set v@0 225 +10 hreturn 0 12 +11 jump 6 +12 hpop 3 +DROP PROCEDURE p1; diff --git a/mysql-test/suite/compat/oracle/r/sp.result b/mysql-test/suite/compat/oracle/r/sp.result index 94e08d2fcd7..42416b6ca33 100644 --- a/mysql-test/suite/compat/oracle/r/sp.result +++ b/mysql-test/suite/compat/oracle/r/sp.result @@ -430,12 +430,13 @@ CREATE PROCEDURE sp1 (p1 IN VARCHAR2(20), p2 OUT VARCHAR2(30)) IS v1 INT; BEGIN -EXCEPTION WHEN NOT FOUND THEN +SELECT c1 INTO v1 FROM t1; +p2 := p1; +EXCEPTION +WHEN NOT FOUND THEN BEGIN p2 := 'def'; END; -SELECT c1 INTO v1 FROM t1; -p2 := p1; END; / CALL sp1('abc', @a); @@ -444,3 +445,23 @@ SELECT @a; def DROP PROCEDURE sp1; DROP TABLE t1; +CREATE PROCEDURE sp1 (v IN OUT INT, error IN INT) +IS +BEGIN +SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO=error, MESSAGE_TEXT='User defined error!'; +v:= 223; +EXCEPTION +WHEN 30001 THEN +BEGIN +v:= 113; +END; +END; +/ +SET @v=10; +CALL sp1(@v, 30001); +CALL sp1(@v, 30002); +ERROR 45000: User defined error! +SELECT @v; +@v +113 +DROP PROCEDURE sp1; diff --git a/mysql-test/suite/compat/oracle/t/sp-code.test b/mysql-test/suite/compat/oracle/t/sp-code.test new file mode 100644 index 00000000000..229fc053c7e --- /dev/null +++ b/mysql-test/suite/compat/oracle/t/sp-code.test @@ -0,0 +1,145 @@ +-- source include/have_debug.inc + +SET sql_mode=ORACLE; + +--echo # No HANDLER declarations, no exceptions +DELIMITER /; +CREATE FUNCTION f1 RETURN INT +AS +BEGIN + RETURN 10; +END; +/ +DELIMITER ;/ +SHOW FUNCTION CODE f1; +SELECT f1(); +DROP FUNCTION f1; + +--echo # No HANDLER declarations, no code, no exceptions +DELIMITER /; +CREATE PROCEDURE p1 () +IS +BEGIN +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +CALL p1; +DROP PROCEDURE p1; + + +--echo # No HANDLER declarations, no code, some exceptions +DELIMITER /; +CREATE PROCEDURE p1 (v IN OUT INT) +IS +BEGIN +EXCEPTION + WHEN 1002 THEN v:=225; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +set @v= 10; +CALL p1(@v); +SELECT @v; +DROP PROCEDURE p1; + + +--echo # No HANDLER declarations, some code, some exceptions +DELIMITER /; +CREATE PROCEDURE p1 (v IN OUT INT) +IS +BEGIN + v:=224; +EXCEPTION + WHEN 1002 THEN v:=225; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +set @v= 10; +CALL p1(@v); +SELECT @v; +DROP PROCEDURE p1; + + +--echo # Some HANDLER declarations, no code, no exceptions +DELIMITER /; +CREATE PROCEDURE p1 (v IN OUT INT) +IS + EXIT HANDLER FOR 1000 + BEGIN + v:=123; + END; +BEGIN +EXCEPTION + WHEN 1002 THEN v:=225; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +set @v= 10; +CALL p1(@v); +SELECT @v; +DROP PROCEDURE p1; + + +--echo # Some HANDLER declarations, no code, some exceptions +DELIMITER /; +CREATE PROCEDURE p1 (v IN OUT INT) +IS + EXIT HANDLER FOR 1000 + BEGIN + v:=123; + END; +BEGIN +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +set @v= 10; +CALL p1(@v); +SELECT @v; +DROP PROCEDURE p1; + + +--echo # Some HANDLER declarations, some code, no exceptions +DELIMITER /; +CREATE PROCEDURE p1 (v IN OUT INT) +IS + EXIT HANDLER FOR 1000 + BEGIN + v:=123; + END; +BEGIN + v:=223; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +set @v= 10; +CALL p1(@v); +SELECT @v; +DROP PROCEDURE p1; + +--echo # Some HANDLER declarations, some code, some exceptions +DELIMITER /; +CREATE PROCEDURE p1 (v IN OUT VARCHAR2(20)) +IS + EXIT HANDLER FOR 1000 + BEGIN + v:=123; + END; + CONTINUE HANDLER FOR 1001 + BEGIN + SET v=223; + END; +BEGIN + v:= 1; +EXCEPTION + WHEN 1002 THEN SET v=225; +END; +/ +DELIMITER ;/ +SHOW PROCEDURE CODE p1; +DROP PROCEDURE p1; diff --git a/mysql-test/suite/compat/oracle/t/sp.test b/mysql-test/suite/compat/oracle/t/sp.test index dc06f7c359b..a838a74d2da 100644 --- a/mysql-test/suite/compat/oracle/t/sp.test +++ b/mysql-test/suite/compat/oracle/t/sp.test @@ -458,12 +458,13 @@ CREATE PROCEDURE sp1 (p1 IN VARCHAR2(20), p2 OUT VARCHAR2(30)) IS v1 INT; BEGIN - EXCEPTION WHEN NOT FOUND THEN + SELECT c1 INTO v1 FROM t1; + p2 := p1; +EXCEPTION + WHEN NOT FOUND THEN BEGIN p2 := 'def'; END; - SELECT c1 INTO v1 FROM t1; - p2 := p1; END; / @@ -474,3 +475,25 @@ SELECT @a; DROP PROCEDURE sp1; DROP TABLE t1; + + +DELIMITER /; +CREATE PROCEDURE sp1 (v IN OUT INT, error IN INT) +IS +BEGIN + SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO=error, MESSAGE_TEXT='User defined error!'; + v:= 223; +EXCEPTION + WHEN 30001 THEN + BEGIN + v:= 113; + END; +END; +/ +DELIMITER ;/ +SET @v=10; +CALL sp1(@v, 30001); +--error 30002 +CALL sp1(@v, 30002); +SELECT @v; +DROP PROCEDURE sp1; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 3126b3cc46d..7951ef538bc 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -2555,6 +2555,33 @@ int sp_head::add_instr(sp_instr *instr) } +bool sp_head::add_instr_jump(THD *thd, sp_pcontext *spcont) +{ + sp_instr_jump *i= new (thd->mem_root) sp_instr_jump(instructions(), spcont); + return i == NULL || add_instr(i); +} + + +bool sp_head::add_instr_jump(THD *thd, sp_pcontext *spcont, uint dest) +{ + sp_instr_jump *i= new (thd->mem_root) sp_instr_jump(instructions(), + spcont, dest); + return i == NULL || add_instr(i); +} + + +bool sp_head::add_instr_jump_forward_with_backpatch(THD *thd, + sp_pcontext *spcont) +{ + sp_instr_jump *i= new (thd->mem_root) sp_instr_jump(instructions(), spcont); + if (i == NULL || add_instr(i)) + return true; + sp_label *lab= spcont->last_label(); + push_backpatch(thd, i, lab); + return false; +} + + /** Do some minimal optimization of the code: -# Mark used instructions diff --git a/sql/sp_head.h b/sql/sp_head.h index e5bc3ca9263..eb2f47ae2d8 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -337,6 +337,15 @@ public: int add_instr(sp_instr *instr); + bool + add_instr_jump(THD *thd, sp_pcontext *spcont); + + bool + add_instr_jump(THD *thd, sp_pcontext *spcont, uint dest); + + bool + add_instr_jump_forward_with_backpatch(THD *thd, sp_pcontext *spcont); + /** Returns true if any substatement in the routine directly (not through another routine) modifies data/changes table. diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 076f389f3a3..9c66aea04d9 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -5386,7 +5386,56 @@ sp_head *LEX::make_sp_head(THD *thd, sp_name *name, } +bool LEX::sp_block_with_exceptions_finalize_declarations(THD *thd) +{ + /* + [ DECLARE declarations ] + BEGIN executable_section + [ EXCEPTION exceptions ] + END + We are now at the "BEGIN" keyword. + We have collected all declarations, including DECLARE HANDLER directives. + But there will be possibly more handlers in the EXCEPTION section. + + Generate a forward jump from the end of the DECLARE section to the + beginning of the EXCEPTION section, over the executable section. + */ + return sphead->add_instr_jump(thd, spcont); +} + + +bool +LEX::sp_block_with_exceptions_finalize_executable_section(THD *thd, + uint executable_section_ip) +{ + /* + We're now at the end of "executable_section" of the block, + near the "EXCEPTION" or the "END" keyword. + Generate a jump to the END of the block over the EXCEPTION section. + */ + if (sphead->add_instr_jump_forward_with_backpatch(thd, spcont)) + return true; + /* + Set the destination for the jump that we added in + sp_block_with_exceptions_finalize_declarations(). + */ + sp_instr *instr= sphead->get_instr(executable_section_ip - 1); + instr->backpatch(sphead->instructions(), spcont); + return false; +} + + +bool +LEX::sp_block_with_exceptions_finalize_exceptions(THD *thd, + uint executable_section_ip) +{ + /* + Generate a jump from the end of the EXCEPTION code + to the executable section. + */ + return sphead->add_instr_jump(thd, spcont, executable_section_ip); +} #ifdef MYSQL_SERVER uint binlog_unsafe_map[256]; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 5d0b6a8b9e3..538cec38d43 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -3145,6 +3145,11 @@ public: res->join(b1, b2); return false; } + bool sp_block_with_exceptions_finalize_declarations(THD *thd); + bool sp_block_with_exceptions_finalize_executable_section(THD *thd, + uint executable_section_ip); + bool sp_block_with_exceptions_finalize_exceptions(THD *thd, + uint executable_section_ip); // Check if "KEY IF NOT EXISTS name" used outside of ALTER context bool check_add_key(DDL_options_st ddl) { diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy index 58efd058d33..99e7ee41827 100644 --- a/sql/sql_yacc_ora.yy +++ b/sql/sql_yacc_ora.yy @@ -207,6 +207,7 @@ static bool push_sp_empty_label(THD *thd) ulong ulong_num; ulonglong ulonglong_number; longlong longlong_number; + uint sp_instr_addr; /* structs */ LEX_STRING lex_str; @@ -1310,7 +1311,9 @@ END_OF_INPUT %type sp_decl_idents sp_handler_type sp_hcond_list %type sp_cond sp_hcond sqlstate signal_value opt_signal_value %type sp_decl_body sp_decl_body_list opt_sp_decl_body_list +%type sp_instr_addr %type opt_exception_clause exception_handlers +%type sp_block_statements_and_exceptions %type sp_cursor_stmt %type sp_name %type sp_param_name sp_param_name_and_type @@ -2329,6 +2332,7 @@ sp_opt_inout: | IN_SYM { $$= sp_variable::MODE_IN; } | OUT_SYM { $$= sp_variable::MODE_OUT; } | INOUT_SYM { $$= sp_variable::MODE_INOUT; } + | IN_SYM OUT_SYM { $$= sp_variable::MODE_INOUT; } ; sp_parenthesized_fdparam_list: @@ -3452,22 +3456,43 @@ sp_unlabeled_block: } ; +sp_instr_addr: + { $$= Lex->sphead->instructions(); } + ; + sp_body: { Lex->sp_block_init(thd); } opt_sp_decl_body_list - BEGIN_SYM - opt_exception_clause { - $2.hndlrs+= $4; + if (Lex->sp_block_with_exceptions_finalize_declarations(thd)) + MYSQL_YYABORT; } - sp_proc_stmts - END + BEGIN_SYM + sp_block_statements_and_exceptions { + $2.hndlrs+= $5; if (Lex->sp_block_finalize(thd, $2)) MYSQL_YYABORT; } + END + ; + +sp_block_statements_and_exceptions: + sp_instr_addr + sp_proc_stmts + { + if (Lex->sp_block_with_exceptions_finalize_executable_section(thd, + $1)) + MYSQL_YYABORT; + } + opt_exception_clause + { + if (Lex->sp_block_with_exceptions_finalize_exceptions(thd, $1)) + MYSQL_YYABORT; + $$= $4; + } ; sp_unlabeled_block_not_atomic: