diff --git a/src/common/pl/plpgsql/src/gram.y b/src/common/pl/plpgsql/src/gram.y index 4606b7ed7..3ac951721 100755 --- a/src/common/pl/plpgsql/src/gram.y +++ b/src/common/pl/plpgsql/src/gram.y @@ -396,6 +396,7 @@ static void processFunctionRecordOutParam(int varno, Oid funcoid, int* outparam) %type opt_fetch_direction %type fetch_limit_expr %type fetch_into_target +%type condition_value %type unreserved_keyword @@ -441,6 +442,8 @@ static void processFunctionRecordOutParam(int varno, Oid funcoid, int* outparam) %token T_CURSOR_FOUND %token T_CURSOR_NOTFOUND %token T_CURSOR_ROWCOUNT +%token T_DECLARE_CURSOR +%token T_DECLARE_CONDITION /* * Keyword tokens. Some of these are reserved and some are not; @@ -463,6 +466,7 @@ static void processFunctionRecordOutParam(int varno, Oid funcoid, int* outparam) %token K_COLLATE %token K_COLLECT %token K_COMMIT +%token K_CONDITION %token K_CONSTANT %token K_CONTINUE %token K_CURRENT @@ -662,7 +666,7 @@ opt_semi : | ';' ; -pl_block : decl_sect K_BEGIN proc_sect exception_sect K_END opt_label +pl_block : decl_sect begin_decl proc_sect exception_sect K_END opt_label { PLpgSQL_stmt_block *newp; @@ -688,6 +692,103 @@ pl_block : decl_sect K_BEGIN proc_sect exception_sect K_END opt_label } ; +begin_decl : K_BEGIN + | K_BEGIN declare_stmts + ; + +declare_stmts : declare_stmts declare_stmt + | declare_stmt + ; + +declare_stmt : T_DECLARE_CURSOR decl_varname K_CURSOR opt_scrollable + { + IsInPublicNamespace($2->name); + plpgsql_ns_push($2->name); + } + decl_cursor_args decl_is_for decl_cursor_query + { + PLpgSQL_var *newp; + + /* pop local namespace for cursor args */ + plpgsql_ns_pop(); + + newp = (PLpgSQL_var *) + plpgsql_build_variable($2->name, $2->lineno, + plpgsql_build_datatype(REFCURSOROID, + -1, + InvalidOid), + true); + + newp->cursor_explicit_expr = $8; + if ($6 == NULL) + newp->cursor_explicit_argrow = -1; + else + newp->cursor_explicit_argrow = $6->dno; + newp->cursor_options = CURSOR_OPT_FAST_PLAN | $4; + u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc; + newp->datatype->cursorCompositeOid = IS_ANONYMOUS_BLOCK ? + InvalidOid : createCompositeTypeForCursor(newp, $8); + pfree_ext($2->name); + pfree($2); + } + | T_DECLARE_CONDITION decl_varname K_CONDITION K_FOR condition_value ';' + { + IsInPublicNamespace($2->name); + PLpgSQL_var *var; + + var = (PLpgSQL_var *)plpgsql_build_variable($2->name, $2->lineno, + plpgsql_build_datatype(INT4OID, + -1, + InvalidOid), + true); + var->customCondition = $5; + pfree_ext($2->name); + pfree($2); + } + ; + +condition_value : any_identifier + { + if (strcmp($1, "sqlstate") == 0) { + /* next token should be a string literal */ + char *sqlstatestr; + if (yylex() != SCONST) + yyerror("syntax error"); + sqlstatestr = yylval.str; + + if (strlen(sqlstatestr) != 5) + yyerror("invalid SQLSTATE code"); + if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5) + yyerror("invalid SQLSTATE code"); + if (strncmp(sqlstatestr, "00", 2) == 0) { + const char* message = "bad SQLSTATE"; + InsertErrorMessage(message, plpgsql_yylloc); + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION), + errmsg("bad SQLSTATE '%s'",sqlstatestr))); + } + + $$ = MAKE_SQLSTATE(sqlstatestr[0], + sqlstatestr[1], + sqlstatestr[2], + sqlstatestr[3], + sqlstatestr[4]); + } else { + yyerror("syntax error"); + } + } + | ICONST + { + if ($1 == 0){ + const char* message = "Incorrect CONDITION value: '0'"; + InsertErrorMessage(message, plpgsql_yylloc); + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION), + errmsg("Incorrect CONDITION value: '0'"))); + } + $$ = $1; + } + ; decl_sect : opt_block_label { @@ -5582,6 +5683,7 @@ unreserved_keyword : | K_BACKWARD | K_CALL | K_COMMIT + | K_CONDITION | K_CONSTANT | K_CONTINUE | K_CURRENT diff --git a/src/common/pl/plpgsql/src/pl_comp.cpp b/src/common/pl/plpgsql/src/pl_comp.cpp index 241a081d9..534c325ec 100644 --- a/src/common/pl/plpgsql/src/pl_comp.cpp +++ b/src/common/pl/plpgsql/src/pl_comp.cpp @@ -3429,6 +3429,7 @@ PLpgSQL_variable* plpgsql_build_variable(const char* refname, int lineno, PLpgSQ var->lineno = lineno; var->datatype = dtype; var->pkg = NULL; + var->customCondition = 0; /* other fields might be filled by caller */ /* preset to NULL */ @@ -4258,15 +4259,30 @@ PLpgSQL_condition* plpgsql_parse_err_condition(char* condname) } if (prev == NULL) { - char message[MAXSTRLEN]; - errno_t rc = 0; - rc = sprintf_s(message, MAXSTRLEN, "unrecognized exception condition \"%s\"", condname); - securec_check_ss(rc, "", ""); - InsertErrorMessage(message, u_sess->plsql_cxt.plpgsql_yylloc); - ereport(ERROR, - (errmodule(MOD_PLSQL), - errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("unrecognized exception condition \"%s\"", condname))); + PLpgSQL_nsitem* ns = plpgsql_ns_lookup(plpgsql_ns_top(), false, condname, NULL, NULL, NULL); + if (ns != NULL) { + PLpgSQL_var* var = NULL; + + var = (PLpgSQL_var*)(u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[ns->itemno]); + if (var->customCondition != 0) { + newm = (PLpgSQL_condition*)palloc(sizeof(PLpgSQL_condition)); + newm->sqlerrstate = var->customCondition; + newm->condname = condname; + newm->next = prev; + prev = newm; + } + } + if (prev == NULL) { + char message[MAXSTRLEN]; + errno_t rc = 0; + rc = sprintf_s(message, MAXSTRLEN, "unrecognized exception condition \"%s\"", condname); + securec_check_ss(rc, "", ""); + InsertErrorMessage(message, u_sess->plsql_cxt.plpgsql_yylloc); + ereport(ERROR, + (errmodule(MOD_PLSQL), + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("unrecognized exception condition \"%s\"", condname))); + } } return prev; } diff --git a/src/common/pl/plpgsql/src/pl_scanner.cpp b/src/common/pl/plpgsql/src/pl_scanner.cpp index 3d452870f..408978ec7 100644 --- a/src/common/pl/plpgsql/src/pl_scanner.cpp +++ b/src/common/pl/plpgsql/src/pl_scanner.cpp @@ -124,6 +124,7 @@ static const ScanKeyword unreserved_keywords[] = { PG_KEYWORD("call", K_CALL, UNRESERVED_KEYWORD) PG_KEYWORD("collect", K_COLLECT, UNRESERVED_KEYWORD) PG_KEYWORD("commit", K_COMMIT, UNRESERVED_KEYWORD) + PG_KEYWORD("condition", K_CONDITION, UNRESERVED_KEYWORD) PG_KEYWORD("constant", K_CONSTANT, UNRESERVED_KEYWORD) PG_KEYWORD("continue", K_CONTINUE, UNRESERVED_KEYWORD) PG_KEYWORD("current", K_CURRENT, UNRESERVED_KEYWORD) @@ -226,6 +227,7 @@ static void push_back_token(int token, TokenAuxData* auxdata); static void location_lineno_init(void); static int get_self_defined_tok(int tok_flag); static int plpgsql_parse_cursor_attribute(int* loc); +static int plpgsql_parse_declare(int* loc); static void RecordDependencyOfPkg(PLpgSQL_package* pkg, Oid pkgOid, Oid currCompPkgOid); static PLpgSQL_datum* SearchPackageDatum(PLpgSQL_package* pkg, Oid pkgOid, Oid currCompPkgOid, const char* pkgname, const char* objname); @@ -258,6 +260,14 @@ int plpgsql_yylex(void) plpgsql_yylloc = loc; return tok1; } + if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT) { + /* parse declare condition */ + tok1 = plpgsql_parse_declare(&loc); + if (tok1 != -1) { + plpgsql_yylloc = loc; + return tok1; + } + } tok1 = internal_yylex(&aux1); if (tok1 == IDENT || tok1 == PARAM || tok1 == T_SQL_BULK_EXCEPTIONS) { @@ -1120,6 +1130,55 @@ static int plpgsql_parse_cursor_attribute(int* loc) return token; } +static int plpgsql_parse_declare(int* loc) +{ + TokenAuxData aux1; + int tok1 = -1; + int token = -1; + + if (u_sess->parser_cxt.in_package_function_compile) { + return token; + } + + tok1 = internal_yylex(&aux1); + if (tok1 == K_DECLARE) { + TokenAuxData aux2; + TokenAuxData aux3; + + int tok2 = -1; + int tok3 = -1; + tok2 = internal_yylex(&aux2); + tok3 = internal_yylex(&aux3); + if (tok2 != IDENT || tok3 != IDENT) { + push_back_token(tok3, &aux3); + push_back_token(tok2, &aux2); + push_back_token(tok1, &aux1); + return token; + } + if (strcasecmp(aux3.lval.str, "cursor") == 0) { + token = T_DECLARE_CURSOR; + push_back_token(tok3, &aux3); + push_back_token(tok2, &aux2); + /* get the declare attribute location */ + *loc = aux1.lloc; + plpgsql_yylval = aux1.lval; + } else if (strcasecmp(aux3.lval.str, "condition") == 0) { + token = T_DECLARE_CONDITION; + push_back_token(tok3, &aux3); + push_back_token(tok2, &aux2); + /* get the declare attribute location */ + *loc = aux1.lloc; + plpgsql_yylval = aux1.lval; + } else { + push_back_token(tok3, &aux3); + push_back_token(tok2, &aux2); + push_back_token(tok1, &aux1); + } + } else { + push_back_token(tok1, &aux1); + } + return token; +} /* * a convenient method to see if the next two tokens are what we expected */ diff --git a/src/include/utils/plpgsql.h b/src/include/utils/plpgsql.h index 07a51f63a..ccdbc8273 100644 --- a/src/include/utils/plpgsql.h +++ b/src/include/utils/plpgsql.h @@ -370,6 +370,7 @@ typedef struct PLpgSQL_var { /* Scalar variable */ PLpgSQL_expr* cursor_explicit_expr; int cursor_explicit_argrow; int cursor_options; + int customCondition; /* only for declare condition variable. */ Datum value; bool isnull; diff --git a/src/test/regress/expected/mysql_syntax.out b/src/test/regress/expected/mysql_syntax.out index 2fed3d13b..b4bbb8c4b 100644 --- a/src/test/regress/expected/mysql_syntax.out +++ b/src/test/regress/expected/mysql_syntax.out @@ -188,5 +188,177 @@ insert into t_func_trigger(rep) values('after insert');END; / drop trigger trigger_insert on t_trigger; drop user vbadmin; +-- test declare cursor +create table company(name varchar(100), loc varchar(100), no integer); +insert into company values ('macrosoft', 'usa', 001); +insert into company values ('oracle', 'usa', 002); +insert into company values ('backberry', 'canada', 003); +create or replace procedure test_cursor_1 +as + company_name varchar(100); + company_loc varchar(100); + company_no integer; + +begin + declare c1_all cursor is --cursor without args + select name, loc, no from company order by 1, 2, 3; + if not c1_all%isopen then + open c1_all; + end if; + loop + fetch c1_all into company_name, company_loc, company_no; + exit when c1_all%notfound; + raise notice '% : % : %',company_name,company_loc,company_no; + end loop; + if c1_all%isopen then + close c1_all; + end if; +end; +/ +call test_cursor_1(); +NOTICE: backberry : canada : 3 +NOTICE: macrosoft : usa : 1 +NOTICE: oracle : usa : 2 + test_cursor_1 +--------------- + +(1 row) + +-- test declare condition +create or replace procedure test_condition_1 as +declare + a int; +BEGIN + declare DIVISION_ZERO condition for SQLSTATE '22012'; + a := 1/0; +exception + when DIVISION_ZERO then + BEGIN + RAISE NOTICE 'SQLSTATE = %, SQLERRM = %', SQLSTATE,SQLERRM; + END; +END; +/ +call test_condition_1(); +NOTICE: SQLSTATE = 22012, SQLERRM = division by zero + test_condition_1 +------------------ + +(1 row) + +-- test rename condition +create or replace procedure test_condition_2 as +declare + a int; +BEGIN + declare DIVISION_ZERO condition for SQLSTATE '22012'; + declare DIVISION_ZERO_two condition for SQLSTATE '22012'; + a := 1/0; +exception + when DIVISION_ZERO then + BEGIN + RAISE NOTICE 'SQLSTATE = %, SQLERRM = %', SQLSTATE,SQLERRM; + END; +END; +/ +call test_condition_2(); +NOTICE: SQLSTATE = 22012, SQLERRM = division by zero + test_condition_2 +------------------ + +(1 row) + +-- test reuse condition name +create or replace procedure test_condition_3 as +declare + a int; +BEGIN + declare DIVISION_ZERO condition for SQLSTATE '22012'; + declare DIVISION_ZERO condition for SQLSTATE '22005'; + a := 1/0; +exception + when DIVISION_ZERO then + BEGIN + RAISE NOTICE 'SQLSTATE = %, SQLERRM = %', SQLSTATE,SQLERRM; + END; +END; +/ +ERROR: syntax error at or near "DIVISION_ZERO" +LINE 6: declare DIVISION_ZERO condition for SQLSTATE '22005'; + ^ +QUERY: +declare + a int; +BEGIN + declare DIVISION_ZERO condition for SQLSTATE '22012'; + declare DIVISION_ZERO condition for SQLSTATE '22005'; + a := 1/0; +exception + when DIVISION_ZERO then + BEGIN + RAISE NOTICE 'SQLSTATE = %, SQLERRM = %', SQLSTATE,SQLERRM; + END; +END +-- declare condition sqlcode +create or replace procedure test_condition_4 as +BEGIN + declare DIVISION_ZERO condition for 1; + RAISE NOTICE 'declare condition successed'; +END; +/ +call test_condition_4(); +NOTICE: declare condition successed + test_condition_4 +------------------ + +(1 row) + +-- declare condition sqlcode 0 +create or replace procedure test_condition_5 as +BEGIN + declare DIVISION_ZERO condition for 0; + RAISE NOTICE 'declare condition successed'; +END; +/ +ERROR: Incorrect CONDITION value: '0' +CONTEXT: compilation of PL/pgSQL function "test_condition_5" near line 3 +-- declare condition sqlstate begin with '00' +create or replace procedure test_condition_6 as +BEGIN + declare DIVISION_ZERO condition for sqlstate '00000'; + RAISE NOTICE 'declare condition successed'; +END; +/ +ERROR: bad SQLSTATE '00000' +CONTEXT: compilation of PL/pgSQL function "test_condition_6" near line 3 \c regression drop database db_mysql; +-- test declare condition in other compatibility +create or replace procedure test_condition_1 as +declare + a int; +BEGIN + declare DIVISION_ZERO condition for SQLSTATE '22012'; + a := 1/0; +exception + when DIVISION_ZERO then + BEGIN + RAISE NOTICE 'SQLSTATE = %, SQLERRM = %', SQLSTATE,SQLERRM; + END; +END; +/ +ERROR: syntax error at or near "SQLSTATE" +LINE 5: declare DIVISION_ZERO condition for SQLSTATE '22012'; + ^ +QUERY: +declare + a int; +BEGIN + declare DIVISION_ZERO condition for SQLSTATE '22012'; + a := 1/0; +exception + when DIVISION_ZERO then + BEGIN + RAISE NOTICE 'SQLSTATE = %, SQLERRM = %', SQLSTATE,SQLERRM; + END; +END +CONTEXT: invalid type name "condition for SQLSTATE '22012'" diff --git a/src/test/regress/sql/mysql_syntax.sql b/src/test/regress/sql/mysql_syntax.sql index ec0dfdafb..8a5cb2aaa 100644 --- a/src/test/regress/sql/mysql_syntax.sql +++ b/src/test/regress/sql/mysql_syntax.sql @@ -164,5 +164,118 @@ insert into t_func_trigger(rep) values('after insert');END; / drop trigger trigger_insert on t_trigger; drop user vbadmin; +-- test declare cursor +create table company(name varchar(100), loc varchar(100), no integer); +insert into company values ('macrosoft', 'usa', 001); +insert into company values ('oracle', 'usa', 002); +insert into company values ('backberry', 'canada', 003); +create or replace procedure test_cursor_1 +as + company_name varchar(100); + company_loc varchar(100); + company_no integer; + +begin + declare c1_all cursor is --cursor without args + select name, loc, no from company order by 1, 2, 3; + if not c1_all%isopen then + open c1_all; + end if; + loop + fetch c1_all into company_name, company_loc, company_no; + exit when c1_all%notfound; + raise notice '% : % : %',company_name,company_loc,company_no; + end loop; + if c1_all%isopen then + close c1_all; + end if; +end; +/ +call test_cursor_1(); +-- test declare condition +create or replace procedure test_condition_1 as +declare + a int; +BEGIN + declare DIVISION_ZERO condition for SQLSTATE '22012'; + a := 1/0; +exception + when DIVISION_ZERO then + BEGIN + RAISE NOTICE 'SQLSTATE = %, SQLERRM = %', SQLSTATE,SQLERRM; + END; +END; +/ +call test_condition_1(); +-- test rename condition +create or replace procedure test_condition_2 as +declare + a int; +BEGIN + declare DIVISION_ZERO condition for SQLSTATE '22012'; + declare DIVISION_ZERO_two condition for SQLSTATE '22012'; + a := 1/0; +exception + when DIVISION_ZERO then + BEGIN + RAISE NOTICE 'SQLSTATE = %, SQLERRM = %', SQLSTATE,SQLERRM; + END; +END; +/ +call test_condition_2(); + +-- test reuse condition name +create or replace procedure test_condition_3 as +declare + a int; +BEGIN + declare DIVISION_ZERO condition for SQLSTATE '22012'; + declare DIVISION_ZERO condition for SQLSTATE '22005'; + a := 1/0; +exception + when DIVISION_ZERO then + BEGIN + RAISE NOTICE 'SQLSTATE = %, SQLERRM = %', SQLSTATE,SQLERRM; + END; +END; +/ +-- declare condition sqlcode +create or replace procedure test_condition_4 as +BEGIN + declare DIVISION_ZERO condition for 1; + RAISE NOTICE 'declare condition successed'; +END; +/ +call test_condition_4(); +-- declare condition sqlcode 0 +create or replace procedure test_condition_5 as +BEGIN + declare DIVISION_ZERO condition for 0; + RAISE NOTICE 'declare condition successed'; +END; +/ +-- declare condition sqlstate begin with '00' +create or replace procedure test_condition_6 as +BEGIN + declare DIVISION_ZERO condition for sqlstate '00000'; + RAISE NOTICE 'declare condition successed'; +END; +/ + \c regression drop database db_mysql; + +-- test declare condition in other compatibility +create or replace procedure test_condition_1 as +declare + a int; +BEGIN + declare DIVISION_ZERO condition for SQLSTATE '22012'; + a := 1/0; +exception + when DIVISION_ZERO then + BEGIN + RAISE NOTICE 'SQLSTATE = %, SQLERRM = %', SQLSTATE,SQLERRM; + END; +END; +/