From 05346c131a14f29f327518778811cba26444822b Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Thu, 4 Oct 2012 22:40:33 -0400 Subject: PL/pgSQL: rename gram.y to pl_gram.y This makes the naming inside plpgsql consistent and distinguishes the file from the backend's gram.y file. It will also allow easier refactoring of the bison make rules later on. --- src/pl/plpgsql/src/Makefile | 2 +- src/pl/plpgsql/src/gram.y | 3735 --------------------------------------- src/pl/plpgsql/src/pl_gram.y | 3735 +++++++++++++++++++++++++++++++++++++++ src/pl/plpgsql/src/pl_scanner.c | 4 +- src/tools/msvc/Mkvcbuild.pm | 2 +- 5 files changed, 3739 insertions(+), 3739 deletions(-) delete mode 100644 src/pl/plpgsql/src/gram.y create mode 100644 src/pl/plpgsql/src/pl_gram.y (limited to 'src') diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile index e3fef84b88..0db0dc5692 100644 --- a/src/pl/plpgsql/src/Makefile +++ b/src/pl/plpgsql/src/Makefile @@ -58,7 +58,7 @@ pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o: plpgsql.h pl pl_gram.h: pl_gram.c ; -pl_gram.c: gram.y +pl_gram.c: pl_gram.y ifdef BISON $(BISON) -d $(BISONFLAGS) -o $@ $< else diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y deleted file mode 100644 index 9c3d254a0f..0000000000 --- a/src/pl/plpgsql/src/gram.y +++ /dev/null @@ -1,3735 +0,0 @@ -%{ -/*------------------------------------------------------------------------- - * - * gram.y - Parser for the PL/pgSQL procedural language - * - * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * src/pl/plpgsql/src/gram.y - * - *------------------------------------------------------------------------- - */ - -#include "plpgsql.h" - -#include "catalog/namespace.h" -#include "catalog/pg_type.h" -#include "parser/parser.h" -#include "parser/parse_type.h" -#include "parser/scanner.h" -#include "parser/scansup.h" -#include "utils/builtins.h" - - -/* Location tracking support --- simpler than bison's default */ -#define YYLLOC_DEFAULT(Current, Rhs, N) \ - do { \ - if (N) \ - (Current) = (Rhs)[1]; \ - else \ - (Current) = (Rhs)[0]; \ - } while (0) - -/* - * Bison doesn't allocate anything that needs to live across parser calls, - * so we can easily have it use palloc instead of malloc. This prevents - * memory leaks if we error out during parsing. Note this only works with - * bison >= 2.0. However, in bison 1.875 the default is to use alloca() - * if possible, so there's not really much problem anyhow, at least if - * you're building with gcc. - */ -#define YYMALLOC palloc -#define YYFREE pfree - - -typedef struct -{ - int location; - int leaderlen; -} sql_error_callback_arg; - -#define parser_errposition(pos) plpgsql_scanner_errposition(pos) - -union YYSTYPE; /* need forward reference for tok_is_keyword */ - -static bool tok_is_keyword(int token, union YYSTYPE *lval, - int kw_token, const char *kw_str); -static void word_is_not_variable(PLword *word, int location); -static void cword_is_not_variable(PLcword *cword, int location); -static void current_token_is_not_variable(int tok); -static PLpgSQL_expr *read_sql_construct(int until, - int until2, - int until3, - const char *expected, - const char *sqlstart, - bool isexpression, - bool valid_sql, - bool trim, - int *startloc, - int *endtoken); -static PLpgSQL_expr *read_sql_expression(int until, - const char *expected); -static PLpgSQL_expr *read_sql_expression2(int until, int until2, - const char *expected, - int *endtoken); -static PLpgSQL_expr *read_sql_stmt(const char *sqlstart); -static PLpgSQL_type *read_datatype(int tok); -static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location); -static PLpgSQL_stmt_fetch *read_fetch_direction(void); -static void complete_direction(PLpgSQL_stmt_fetch *fetch, - bool *check_FROM); -static PLpgSQL_stmt *make_return_stmt(int location); -static PLpgSQL_stmt *make_return_next_stmt(int location); -static PLpgSQL_stmt *make_return_query_stmt(int location); -static PLpgSQL_stmt *make_case(int location, PLpgSQL_expr *t_expr, - List *case_when_list, List *else_stmts); -static char *NameOfDatum(PLwdatum *wdatum); -static void check_assignable(PLpgSQL_datum *datum, int location); -static void read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, - bool *strict); -static PLpgSQL_row *read_into_scalar_list(char *initial_name, - PLpgSQL_datum *initial_datum, - int initial_location); -static PLpgSQL_row *make_scalar_list1(char *initial_name, - PLpgSQL_datum *initial_datum, - int lineno, int location); -static void check_sql_expr(const char *stmt, int location, - int leaderlen); -static void plpgsql_sql_error_callback(void *arg); -static PLpgSQL_type *parse_datatype(const char *string, int location); -static void check_labels(const char *start_label, - const char *end_label, - int end_location); -static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor, - int until, const char *expected); -static List *read_raise_options(void); - -%} - -%expect 0 -%name-prefix="plpgsql_yy" -%locations - -%union { - core_YYSTYPE core_yystype; - /* these fields must match core_YYSTYPE: */ - int ival; - char *str; - const char *keyword; - - PLword word; - PLcword cword; - PLwdatum wdatum; - bool boolean; - Oid oid; - struct - { - char *name; - int lineno; - } varname; - struct - { - char *name; - int lineno; - PLpgSQL_datum *scalar; - PLpgSQL_rec *rec; - PLpgSQL_row *row; - } forvariable; - struct - { - char *label; - int n_initvars; - int *initvarnos; - } declhdr; - struct - { - List *stmts; - char *end_label; - int end_label_location; - } loop_body; - List *list; - PLpgSQL_type *dtype; - PLpgSQL_datum *datum; - PLpgSQL_var *var; - PLpgSQL_expr *expr; - PLpgSQL_stmt *stmt; - PLpgSQL_condition *condition; - PLpgSQL_exception *exception; - PLpgSQL_exception_block *exception_block; - PLpgSQL_nsitem *nsitem; - PLpgSQL_diag_item *diagitem; - PLpgSQL_stmt_fetch *fetch; - PLpgSQL_case_when *casewhen; -} - -%type decl_sect -%type decl_varname -%type decl_const decl_notnull exit_type -%type decl_defval decl_cursor_query -%type decl_datatype -%type decl_collate -%type decl_cursor_args -%type decl_cursor_arglist -%type decl_aliasitem - -%type expr_until_semi expr_until_rightbracket -%type expr_until_then expr_until_loop opt_expr_until_when -%type opt_exitcond - -%type assign_var foreach_slice -%type cursor_variable -%type decl_cursor_arg -%type for_variable -%type for_control - -%type any_identifier opt_block_label opt_label - -%type proc_sect proc_stmts stmt_elsifs stmt_else -%type loop_body -%type proc_stmt pl_block -%type stmt_assign stmt_if stmt_loop stmt_while stmt_exit -%type stmt_return stmt_raise stmt_execsql -%type stmt_dynexecute stmt_for stmt_perform stmt_getdiag -%type stmt_open stmt_fetch stmt_move stmt_close stmt_null -%type stmt_case stmt_foreach_a - -%type proc_exceptions -%type exception_sect -%type proc_exception -%type proc_conditions proc_condition - -%type case_when -%type case_when_list opt_case_else - -%type getdiag_area_opt -%type getdiag_list -%type getdiag_list_item -%type getdiag_item getdiag_target - -%type opt_scrollable -%type opt_fetch_direction - -%type unreserved_keyword - - -/* - * Basic non-keyword token types. These are hard-wired into the core lexer. - * They must be listed first so that their numeric codes do not depend on - * the set of keywords. Keep this list in sync with backend/parser/gram.y! - * - * Some of these are not directly referenced in this file, but they must be - * here anyway. - */ -%token IDENT FCONST SCONST BCONST XCONST Op -%token ICONST PARAM -%token TYPECAST DOT_DOT COLON_EQUALS - -/* - * Other tokens recognized by plpgsql's lexer interface layer (pl_scanner.c). - */ -%token T_WORD /* unrecognized simple identifier */ -%token T_CWORD /* unrecognized composite identifier */ -%token T_DATUM /* a VAR, ROW, REC, or RECFIELD variable */ -%token LESS_LESS -%token GREATER_GREATER - -/* - * Keyword tokens. Some of these are reserved and some are not; - * see pl_scanner.c for info. Be sure unreserved keywords are listed - * in the "unreserved_keyword" production below. - */ -%token K_ABSOLUTE -%token K_ALIAS -%token K_ALL -%token K_ARRAY -%token K_BACKWARD -%token K_BEGIN -%token K_BY -%token K_CASE -%token K_CLOSE -%token K_COLLATE -%token K_CONSTANT -%token K_CONTINUE -%token K_CURRENT -%token K_CURSOR -%token K_DEBUG -%token K_DECLARE -%token K_DEFAULT -%token K_DETAIL -%token K_DIAGNOSTICS -%token K_DUMP -%token K_ELSE -%token K_ELSIF -%token K_END -%token K_ERRCODE -%token K_ERROR -%token K_EXCEPTION -%token K_EXECUTE -%token K_EXIT -%token K_FETCH -%token K_FIRST -%token K_FOR -%token K_FOREACH -%token K_FORWARD -%token K_FROM -%token K_GET -%token K_HINT -%token K_IF -%token K_IN -%token K_INFO -%token K_INSERT -%token K_INTO -%token K_IS -%token K_LAST -%token K_LOG -%token K_LOOP -%token K_MESSAGE -%token K_MESSAGE_TEXT -%token K_MOVE -%token K_NEXT -%token K_NO -%token K_NOT -%token K_NOTICE -%token K_NULL -%token K_OPEN -%token K_OPTION -%token K_OR -%token K_PERFORM -%token K_PG_EXCEPTION_CONTEXT -%token K_PG_EXCEPTION_DETAIL -%token K_PG_EXCEPTION_HINT -%token K_PRIOR -%token K_QUERY -%token K_RAISE -%token K_RELATIVE -%token K_RESULT_OID -%token K_RETURN -%token K_RETURNED_SQLSTATE -%token K_REVERSE -%token K_ROWTYPE -%token K_ROW_COUNT -%token K_SCROLL -%token K_SLICE -%token K_SQLSTATE -%token K_STACKED -%token K_STRICT -%token K_THEN -%token K_TO -%token K_TYPE -%token K_USE_COLUMN -%token K_USE_VARIABLE -%token K_USING -%token K_VARIABLE_CONFLICT -%token K_WARNING -%token K_WHEN -%token K_WHILE - -%% - -pl_function : comp_options pl_block opt_semi - { - plpgsql_parse_result = (PLpgSQL_stmt_block *) $2; - } - ; - -comp_options : - | comp_options comp_option - ; - -comp_option : '#' K_OPTION K_DUMP - { - plpgsql_DumpExecTree = true; - } - | '#' K_VARIABLE_CONFLICT K_ERROR - { - plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_ERROR; - } - | '#' K_VARIABLE_CONFLICT K_USE_VARIABLE - { - plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_VARIABLE; - } - | '#' K_VARIABLE_CONFLICT K_USE_COLUMN - { - plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_COLUMN; - } - ; - -opt_semi : - | ';' - ; - -pl_block : decl_sect K_BEGIN proc_sect exception_sect K_END opt_label - { - PLpgSQL_stmt_block *new; - - new = palloc0(sizeof(PLpgSQL_stmt_block)); - - new->cmd_type = PLPGSQL_STMT_BLOCK; - new->lineno = plpgsql_location_to_lineno(@2); - new->label = $1.label; - new->n_initvars = $1.n_initvars; - new->initvarnos = $1.initvarnos; - new->body = $3; - new->exceptions = $4; - - check_labels($1.label, $6, @6); - plpgsql_ns_pop(); - - $$ = (PLpgSQL_stmt *)new; - } - ; - - -decl_sect : opt_block_label - { - /* done with decls, so resume identifier lookup */ - plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL; - $$.label = $1; - $$.n_initvars = 0; - $$.initvarnos = NULL; - } - | opt_block_label decl_start - { - plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL; - $$.label = $1; - $$.n_initvars = 0; - $$.initvarnos = NULL; - } - | opt_block_label decl_start decl_stmts - { - plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL; - $$.label = $1; - /* Remember variables declared in decl_stmts */ - $$.n_initvars = plpgsql_add_initdatums(&($$.initvarnos)); - } - ; - -decl_start : K_DECLARE - { - /* Forget any variables created before block */ - plpgsql_add_initdatums(NULL); - /* - * Disable scanner lookup of identifiers while - * we process the decl_stmts - */ - plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE; - } - ; - -decl_stmts : decl_stmts decl_stmt - | decl_stmt - ; - -decl_stmt : decl_statement - | K_DECLARE - { - /* We allow useless extra DECLAREs */ - } - | LESS_LESS any_identifier GREATER_GREATER - { - /* - * Throw a helpful error if user tries to put block - * label just before BEGIN, instead of before DECLARE. - */ - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("block label must be placed before DECLARE, not after"), - parser_errposition(@1))); - } - ; - -decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull decl_defval - { - PLpgSQL_variable *var; - - /* - * If a collation is supplied, insert it into the - * datatype. We assume decl_datatype always returns - * a freshly built struct not shared with other - * variables. - */ - if (OidIsValid($4)) - { - if (!OidIsValid($3->collation)) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("collations are not supported by type %s", - format_type_be($3->typoid)), - parser_errposition(@4))); - $3->collation = $4; - } - - var = plpgsql_build_variable($1.name, $1.lineno, - $3, true); - if ($2) - { - if (var->dtype == PLPGSQL_DTYPE_VAR) - ((PLpgSQL_var *) var)->isconst = $2; - else - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("row or record variable cannot be CONSTANT"), - parser_errposition(@2))); - } - if ($5) - { - if (var->dtype == PLPGSQL_DTYPE_VAR) - ((PLpgSQL_var *) var)->notnull = $5; - else - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("row or record variable cannot be NOT NULL"), - parser_errposition(@4))); - - } - if ($6 != NULL) - { - if (var->dtype == PLPGSQL_DTYPE_VAR) - ((PLpgSQL_var *) var)->default_val = $6; - else - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("default value for row or record variable is not supported"), - parser_errposition(@5))); - } - } - | decl_varname K_ALIAS K_FOR decl_aliasitem ';' - { - plpgsql_ns_additem($4->itemtype, - $4->itemno, $1.name); - } - | decl_varname opt_scrollable K_CURSOR - { plpgsql_ns_push($1.name); } - decl_cursor_args decl_is_for decl_cursor_query - { - PLpgSQL_var *new; - PLpgSQL_expr *curname_def; - char buf[1024]; - char *cp1; - char *cp2; - - /* pop local namespace for cursor args */ - plpgsql_ns_pop(); - - new = (PLpgSQL_var *) - plpgsql_build_variable($1.name, $1.lineno, - plpgsql_build_datatype(REFCURSOROID, - -1, - InvalidOid), - true); - - curname_def = palloc0(sizeof(PLpgSQL_expr)); - - curname_def->dtype = PLPGSQL_DTYPE_EXPR; - strcpy(buf, "SELECT "); - cp1 = new->refname; - cp2 = buf + strlen(buf); - /* - * Don't trust standard_conforming_strings here; - * it might change before we use the string. - */ - if (strchr(cp1, '\\') != NULL) - *cp2++ = ESCAPE_STRING_SYNTAX; - *cp2++ = '\''; - while (*cp1) - { - if (SQL_STR_DOUBLE(*cp1, true)) - *cp2++ = *cp1; - *cp2++ = *cp1++; - } - strcpy(cp2, "'::pg_catalog.refcursor"); - curname_def->query = pstrdup(buf); - new->default_val = curname_def; - - new->cursor_explicit_expr = $7; - if ($5 == NULL) - new->cursor_explicit_argrow = -1; - else - new->cursor_explicit_argrow = $5->dno; - new->cursor_options = CURSOR_OPT_FAST_PLAN | $2; - } - ; - -opt_scrollable : - { - $$ = 0; - } - | K_NO K_SCROLL - { - $$ = CURSOR_OPT_NO_SCROLL; - } - | K_SCROLL - { - $$ = CURSOR_OPT_SCROLL; - } - ; - -decl_cursor_query : - { - $$ = read_sql_stmt(""); - } - ; - -decl_cursor_args : - { - $$ = NULL; - } - | '(' decl_cursor_arglist ')' - { - PLpgSQL_row *new; - int i; - ListCell *l; - - new = palloc0(sizeof(PLpgSQL_row)); - new->dtype = PLPGSQL_DTYPE_ROW; - new->lineno = plpgsql_location_to_lineno(@1); - new->rowtupdesc = NULL; - new->nfields = list_length($2); - new->fieldnames = palloc(new->nfields * sizeof(char *)); - new->varnos = palloc(new->nfields * sizeof(int)); - - i = 0; - foreach (l, $2) - { - PLpgSQL_variable *arg = (PLpgSQL_variable *) lfirst(l); - new->fieldnames[i] = arg->refname; - new->varnos[i] = arg->dno; - i++; - } - list_free($2); - - plpgsql_adddatum((PLpgSQL_datum *) new); - $$ = (PLpgSQL_datum *) new; - } - ; - -decl_cursor_arglist : decl_cursor_arg - { - $$ = list_make1($1); - } - | decl_cursor_arglist ',' decl_cursor_arg - { - $$ = lappend($1, $3); - } - ; - -decl_cursor_arg : decl_varname decl_datatype - { - $$ = (PLpgSQL_datum *) - plpgsql_build_variable($1.name, $1.lineno, - $2, true); - } - ; - -decl_is_for : K_IS | /* Oracle */ - K_FOR; /* SQL standard */ - -decl_aliasitem : T_WORD - { - PLpgSQL_nsitem *nsi; - - nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false, - $1.ident, NULL, NULL, - NULL); - if (nsi == NULL) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("variable \"%s\" does not exist", - $1.ident), - parser_errposition(@1))); - $$ = nsi; - } - | unreserved_keyword - { - PLpgSQL_nsitem *nsi; - - nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false, - $1, NULL, NULL, - NULL); - if (nsi == NULL) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("variable \"%s\" does not exist", - $1), - parser_errposition(@1))); - $$ = nsi; - } - | T_CWORD - { - PLpgSQL_nsitem *nsi; - - if (list_length($1.idents) == 2) - nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false, - strVal(linitial($1.idents)), - strVal(lsecond($1.idents)), - NULL, - NULL); - else if (list_length($1.idents) == 3) - nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false, - strVal(linitial($1.idents)), - strVal(lsecond($1.idents)), - strVal(lthird($1.idents)), - NULL); - else - nsi = NULL; - if (nsi == NULL) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("variable \"%s\" does not exist", - NameListToString($1.idents)), - parser_errposition(@1))); - $$ = nsi; - } - ; - -decl_varname : T_WORD - { - $$.name = $1.ident; - $$.lineno = plpgsql_location_to_lineno(@1); - /* - * Check to make sure name isn't already declared - * in the current block. - */ - if (plpgsql_ns_lookup(plpgsql_ns_top(), true, - $1.ident, NULL, NULL, - NULL) != NULL) - yyerror("duplicate declaration"); - } - | unreserved_keyword - { - $$.name = pstrdup($1); - $$.lineno = plpgsql_location_to_lineno(@1); - /* - * Check to make sure name isn't already declared - * in the current block. - */ - if (plpgsql_ns_lookup(plpgsql_ns_top(), true, - $1, NULL, NULL, - NULL) != NULL) - yyerror("duplicate declaration"); - } - ; - -decl_const : - { $$ = false; } - | K_CONSTANT - { $$ = true; } - ; - -decl_datatype : - { - /* - * If there's a lookahead token, read_datatype - * should consume it. - */ - $$ = read_datatype(yychar); - yyclearin; - } - ; - -decl_collate : - { $$ = InvalidOid; } - | K_COLLATE T_WORD - { - $$ = get_collation_oid(list_make1(makeString($2.ident)), - false); - } - | K_COLLATE unreserved_keyword - { - $$ = get_collation_oid(list_make1(makeString(pstrdup($2))), - false); - } - | K_COLLATE T_CWORD - { - $$ = get_collation_oid($2.idents, false); - } - ; - -decl_notnull : - { $$ = false; } - | K_NOT K_NULL - { $$ = true; } - ; - -decl_defval : ';' - { $$ = NULL; } - | decl_defkey - { - $$ = read_sql_expression(';', ";"); - } - ; - -decl_defkey : assign_operator - | K_DEFAULT - ; - -assign_operator : '=' /* not documented because it might be removed someday */ - | COLON_EQUALS - ; - -proc_sect : - { $$ = NIL; } - | proc_stmts - { $$ = $1; } - ; - -proc_stmts : proc_stmts proc_stmt - { - if ($2 == NULL) - $$ = $1; - else - $$ = lappend($1, $2); - } - | proc_stmt - { - if ($1 == NULL) - $$ = NIL; - else - $$ = list_make1($1); - } - ; - -proc_stmt : pl_block ';' - { $$ = $1; } - | stmt_assign - { $$ = $1; } - | stmt_if - { $$ = $1; } - | stmt_case - { $$ = $1; } - | stmt_loop - { $$ = $1; } - | stmt_while - { $$ = $1; } - | stmt_for - { $$ = $1; } - | stmt_foreach_a - { $$ = $1; } - | stmt_exit - { $$ = $1; } - | stmt_return - { $$ = $1; } - | stmt_raise - { $$ = $1; } - | stmt_execsql - { $$ = $1; } - | stmt_dynexecute - { $$ = $1; } - | stmt_perform - { $$ = $1; } - | stmt_getdiag - { $$ = $1; } - | stmt_open - { $$ = $1; } - | stmt_fetch - { $$ = $1; } - | stmt_move - { $$ = $1; } - | stmt_close - { $$ = $1; } - | stmt_null - { $$ = $1; } - ; - -stmt_perform : K_PERFORM expr_until_semi - { - PLpgSQL_stmt_perform *new; - - new = palloc0(sizeof(PLpgSQL_stmt_perform)); - new->cmd_type = PLPGSQL_STMT_PERFORM; - new->lineno = plpgsql_location_to_lineno(@1); - new->expr = $2; - - $$ = (PLpgSQL_stmt *)new; - } - ; - -stmt_assign : assign_var assign_operator expr_until_semi - { - PLpgSQL_stmt_assign *new; - - new = palloc0(sizeof(PLpgSQL_stmt_assign)); - new->cmd_type = PLPGSQL_STMT_ASSIGN; - new->lineno = plpgsql_location_to_lineno(@1); - new->varno = $1; - new->expr = $3; - - $$ = (PLpgSQL_stmt *)new; - } - ; - -stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';' - { - PLpgSQL_stmt_getdiag *new; - ListCell *lc; - - new = palloc0(sizeof(PLpgSQL_stmt_getdiag)); - new->cmd_type = PLPGSQL_STMT_GETDIAG; - new->lineno = plpgsql_location_to_lineno(@1); - new->is_stacked = $2; - new->diag_items = $4; - - /* - * Check information items are valid for area option. - */ - foreach(lc, new->diag_items) - { - PLpgSQL_diag_item *ditem = (PLpgSQL_diag_item *) lfirst(lc); - - switch (ditem->kind) - { - /* these fields are disallowed in stacked case */ - case PLPGSQL_GETDIAG_ROW_COUNT: - case PLPGSQL_GETDIAG_RESULT_OID: - if (new->is_stacked) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS", - plpgsql_getdiag_kindname(ditem->kind)), - parser_errposition(@1))); - break; - /* these fields are disallowed in current case */ - case PLPGSQL_GETDIAG_ERROR_CONTEXT: - case PLPGSQL_GETDIAG_ERROR_DETAIL: - case PLPGSQL_GETDIAG_ERROR_HINT: - case PLPGSQL_GETDIAG_RETURNED_SQLSTATE: - case PLPGSQL_GETDIAG_MESSAGE_TEXT: - if (!new->is_stacked) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS", - plpgsql_getdiag_kindname(ditem->kind)), - parser_errposition(@1))); - break; - default: - elog(ERROR, "unrecognized diagnostic item kind: %d", - ditem->kind); - break; - } - } - - $$ = (PLpgSQL_stmt *)new; - } - ; - -getdiag_area_opt : - { - $$ = false; - } - | K_CURRENT - { - $$ = false; - } - | K_STACKED - { - $$ = true; - } - ; - -getdiag_list : getdiag_list ',' getdiag_list_item - { - $$ = lappend($1, $3); - } - | getdiag_list_item - { - $$ = list_make1($1); - } - ; - -getdiag_list_item : getdiag_target assign_operator getdiag_item - { - PLpgSQL_diag_item *new; - - new = palloc(sizeof(PLpgSQL_diag_item)); - new->target = $1; - new->kind = $3; - - $$ = new; - } - ; - -getdiag_item : - { - int tok = yylex(); - - if (tok_is_keyword(tok, &yylval, - K_ROW_COUNT, "row_count")) - $$ = PLPGSQL_GETDIAG_ROW_COUNT; - else if (tok_is_keyword(tok, &yylval, - K_RESULT_OID, "result_oid")) - $$ = PLPGSQL_GETDIAG_RESULT_OID; - else if (tok_is_keyword(tok, &yylval, - K_PG_EXCEPTION_DETAIL, "pg_exception_detail")) - $$ = PLPGSQL_GETDIAG_ERROR_DETAIL; - else if (tok_is_keyword(tok, &yylval, - K_PG_EXCEPTION_HINT, "pg_exception_hint")) - $$ = PLPGSQL_GETDIAG_ERROR_HINT; - else if (tok_is_keyword(tok, &yylval, - K_PG_EXCEPTION_CONTEXT, "pg_exception_context")) - $$ = PLPGSQL_GETDIAG_ERROR_CONTEXT; - else if (tok_is_keyword(tok, &yylval, - K_MESSAGE_TEXT, "message_text")) - $$ = PLPGSQL_GETDIAG_MESSAGE_TEXT; - else if (tok_is_keyword(tok, &yylval, - K_RETURNED_SQLSTATE, "returned_sqlstate")) - $$ = PLPGSQL_GETDIAG_RETURNED_SQLSTATE; - else - yyerror("unrecognized GET DIAGNOSTICS item"); - } - ; - -getdiag_target : T_DATUM - { - check_assignable($1.datum, @1); - if ($1.datum->dtype == PLPGSQL_DTYPE_ROW || - $1.datum->dtype == PLPGSQL_DTYPE_REC) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("\"%s\" is not a scalar variable", - NameOfDatum(&($1))), - parser_errposition(@1))); - $$ = $1.datum->dno; - } - | T_WORD - { - /* just to give a better message than "syntax error" */ - word_is_not_variable(&($1), @1); - } - | T_CWORD - { - /* just to give a better message than "syntax error" */ - cword_is_not_variable(&($1), @1); - } - ; - - -assign_var : T_DATUM - { - check_assignable($1.datum, @1); - $$ = $1.datum->dno; - } - | assign_var '[' expr_until_rightbracket - { - PLpgSQL_arrayelem *new; - - new = palloc0(sizeof(PLpgSQL_arrayelem)); - new->dtype = PLPGSQL_DTYPE_ARRAYELEM; - new->subscript = $3; - new->arrayparentno = $1; - /* initialize cached type data to "not valid" */ - new->parenttypoid = InvalidOid; - - plpgsql_adddatum((PLpgSQL_datum *) new); - - $$ = new->dno; - } - ; - -stmt_if : K_IF expr_until_then proc_sect stmt_elsifs stmt_else K_END K_IF ';' - { - PLpgSQL_stmt_if *new; - - new = palloc0(sizeof(PLpgSQL_stmt_if)); - new->cmd_type = PLPGSQL_STMT_IF; - new->lineno = plpgsql_location_to_lineno(@1); - new->cond = $2; - new->then_body = $3; - new->elsif_list = $4; - new->else_body = $5; - - $$ = (PLpgSQL_stmt *)new; - } - ; - -stmt_elsifs : - { - $$ = NIL; - } - | stmt_elsifs K_ELSIF expr_until_then proc_sect - { - PLpgSQL_if_elsif *new; - - new = palloc0(sizeof(PLpgSQL_if_elsif)); - new->lineno = plpgsql_location_to_lineno(@2); - new->cond = $3; - new->stmts = $4; - - $$ = lappend($1, new); - } - ; - -stmt_else : - { - $$ = NIL; - } - | K_ELSE proc_sect - { - $$ = $2; - } - ; - -stmt_case : K_CASE opt_expr_until_when case_when_list opt_case_else K_END K_CASE ';' - { - $$ = make_case(@1, $2, $3, $4); - } - ; - -opt_expr_until_when : - { - PLpgSQL_expr *expr = NULL; - int tok = yylex(); - - if (tok != K_WHEN) - { - plpgsql_push_back_token(tok); - expr = read_sql_expression(K_WHEN, "WHEN"); - } - plpgsql_push_back_token(K_WHEN); - $$ = expr; - } - ; - -case_when_list : case_when_list case_when - { - $$ = lappend($1, $2); - } - | case_when - { - $$ = list_make1($1); - } - ; - -case_when : K_WHEN expr_until_then proc_sect - { - PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when)); - - new->lineno = plpgsql_location_to_lineno(@1); - new->expr = $2; - new->stmts = $3; - $$ = new; - } - ; - -opt_case_else : - { - $$ = NIL; - } - | K_ELSE proc_sect - { - /* - * proc_sect could return an empty list, but we - * must distinguish that from not having ELSE at all. - * Simplest fix is to return a list with one NULL - * pointer, which make_case() must take care of. - */ - if ($2 != NIL) - $$ = $2; - else - $$ = list_make1(NULL); - } - ; - -stmt_loop : opt_block_label K_LOOP loop_body - { - PLpgSQL_stmt_loop *new; - - new = palloc0(sizeof(PLpgSQL_stmt_loop)); - new->cmd_type = PLPGSQL_STMT_LOOP; - new->lineno = plpgsql_location_to_lineno(@2); - new->label = $1; - new->body = $3.stmts; - - check_labels($1, $3.end_label, $3.end_label_location); - plpgsql_ns_pop(); - - $$ = (PLpgSQL_stmt *)new; - } - ; - -stmt_while : opt_block_label K_WHILE expr_until_loop loop_body - { - PLpgSQL_stmt_while *new; - - new = palloc0(sizeof(PLpgSQL_stmt_while)); - new->cmd_type = PLPGSQL_STMT_WHILE; - new->lineno = plpgsql_location_to_lineno(@2); - new->label = $1; - new->cond = $3; - new->body = $4.stmts; - - check_labels($1, $4.end_label, $4.end_label_location); - plpgsql_ns_pop(); - - $$ = (PLpgSQL_stmt *)new; - } - ; - -stmt_for : opt_block_label K_FOR for_control loop_body - { - /* This runs after we've scanned the loop body */ - if ($3->cmd_type == PLPGSQL_STMT_FORI) - { - PLpgSQL_stmt_fori *new; - - new = (PLpgSQL_stmt_fori *) $3; - new->lineno = plpgsql_location_to_lineno(@2); - new->label = $1; - new->body = $4.stmts; - $$ = (PLpgSQL_stmt *) new; - } - else - { - PLpgSQL_stmt_forq *new; - - Assert($3->cmd_type == PLPGSQL_STMT_FORS || - $3->cmd_type == PLPGSQL_STMT_FORC || - $3->cmd_type == PLPGSQL_STMT_DYNFORS); - /* forq is the common supertype of all three */ - new = (PLpgSQL_stmt_forq *) $3; - new->lineno = plpgsql_location_to_lineno(@2); - new->label = $1; - new->body = $4.stmts; - $$ = (PLpgSQL_stmt *) new; - } - - check_labels($1, $4.end_label, $4.end_label_location); - /* close namespace started in opt_block_label */ - plpgsql_ns_pop(); - } - ; - -for_control : for_variable K_IN - { - int tok = yylex(); - int tokloc = yylloc; - - if (tok == K_EXECUTE) - { - /* EXECUTE means it's a dynamic FOR loop */ - PLpgSQL_stmt_dynfors *new; - PLpgSQL_expr *expr; - int term; - - expr = read_sql_expression2(K_LOOP, K_USING, - "LOOP or USING", - &term); - - new = palloc0(sizeof(PLpgSQL_stmt_dynfors)); - new->cmd_type = PLPGSQL_STMT_DYNFORS; - if ($1.rec) - { - new->rec = $1.rec; - check_assignable((PLpgSQL_datum *) new->rec, @1); - } - else if ($1.row) - { - new->row = $1.row; - check_assignable((PLpgSQL_datum *) new->row, @1); - } - else if ($1.scalar) - { - /* convert single scalar to list */ - new->row = make_scalar_list1($1.name, $1.scalar, - $1.lineno, @1); - /* no need for check_assignable */ - } - else - { - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("loop variable of loop over rows must be a record or row variable or list of scalar variables"), - parser_errposition(@1))); - } - new->query = expr; - - if (term == K_USING) - { - do - { - expr = read_sql_expression2(',', K_LOOP, - ", or LOOP", - &term); - new->params = lappend(new->params, expr); - } while (term == ','); - } - - $$ = (PLpgSQL_stmt *) new; - } - else if (tok == T_DATUM && - yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR && - ((PLpgSQL_var *) yylval.wdatum.datum)->datatype->typoid == REFCURSOROID) - { - /* It's FOR var IN cursor */ - PLpgSQL_stmt_forc *new; - PLpgSQL_var *cursor = (PLpgSQL_var *) yylval.wdatum.datum; - - new = (PLpgSQL_stmt_forc *) palloc0(sizeof(PLpgSQL_stmt_forc)); - new->cmd_type = PLPGSQL_STMT_FORC; - new->curvar = cursor->dno; - - /* Should have had a single variable name */ - if ($1.scalar && $1.row) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("cursor FOR loop must have only one target variable"), - parser_errposition(@1))); - - /* can't use an unbound cursor this way */ - if (cursor->cursor_explicit_expr == NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("cursor FOR loop must use a bound cursor variable"), - parser_errposition(tokloc))); - - /* collect cursor's parameters if any */ - new->argquery = read_cursor_args(cursor, - K_LOOP, - "LOOP"); - - /* create loop's private RECORD variable */ - new->rec = plpgsql_build_record($1.name, - $1.lineno, - true); - - $$ = (PLpgSQL_stmt *) new; - } - else - { - PLpgSQL_expr *expr1; - int expr1loc; - bool reverse = false; - - /* - * We have to distinguish between two - * alternatives: FOR var IN a .. b and FOR - * var IN query. Unfortunately this is - * tricky, since the query in the second - * form needn't start with a SELECT - * keyword. We use the ugly hack of - * looking for two periods after the first - * token. We also check for the REVERSE - * keyword, which means it must be an - * integer loop. - */ - if (tok_is_keyword(tok, &yylval, - K_REVERSE, "reverse")) - reverse = true; - else - plpgsql_push_back_token(tok); - - /* - * Read tokens until we see either a ".." - * or a LOOP. The text we read may not - * necessarily be a well-formed SQL - * statement, so we need to invoke - * read_sql_construct directly. - */ - expr1 = read_sql_construct(DOT_DOT, - K_LOOP, - 0, - "LOOP", - "SELECT ", - true, - false, - true, - &expr1loc, - &tok); - - if (tok == DOT_DOT) - { - /* Saw "..", so it must be an integer loop */ - PLpgSQL_expr *expr2; - PLpgSQL_expr *expr_by; - PLpgSQL_var *fvar; - PLpgSQL_stmt_fori *new; - - /* Check first expression is well-formed */ - check_sql_expr(expr1->query, expr1loc, 7); - - /* Read and check the second one */ - expr2 = read_sql_expression2(K_LOOP, K_BY, - "LOOP", - &tok); - - /* Get the BY clause if any */ - if (tok == K_BY) - expr_by = read_sql_expression(K_LOOP, - "LOOP"); - else - expr_by = NULL; - - /* Should have had a single variable name */ - if ($1.scalar && $1.row) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("integer FOR loop must have only one target variable"), - parser_errposition(@1))); - - /* create loop's private variable */ - fvar = (PLpgSQL_var *) - plpgsql_build_variable($1.name, - $1.lineno, - plpgsql_build_datatype(INT4OID, - -1, - InvalidOid), - true); - - new = palloc0(sizeof(PLpgSQL_stmt_fori)); - new->cmd_type = PLPGSQL_STMT_FORI; - new->var = fvar; - new->reverse = reverse; - new->lower = expr1; - new->upper = expr2; - new->step = expr_by; - - $$ = (PLpgSQL_stmt *) new; - } - else - { - /* - * No "..", so it must be a query loop. We've - * prefixed an extra SELECT to the query text, - * so we need to remove that before performing - * syntax checking. - */ - char *tmp_query; - PLpgSQL_stmt_fors *new; - - if (reverse) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("cannot specify REVERSE in query FOR loop"), - parser_errposition(tokloc))); - - Assert(strncmp(expr1->query, "SELECT ", 7) == 0); - tmp_query = pstrdup(expr1->query + 7); - pfree(expr1->query); - expr1->query = tmp_query; - - check_sql_expr(expr1->query, expr1loc, 0); - - new = palloc0(sizeof(PLpgSQL_stmt_fors)); - new->cmd_type = PLPGSQL_STMT_FORS; - if ($1.rec) - { - new->rec = $1.rec; - check_assignable((PLpgSQL_datum *) new->rec, @1); - } - else if ($1.row) - { - new->row = $1.row; - check_assignable((PLpgSQL_datum *) new->row, @1); - } - else if ($1.scalar) - { - /* convert single scalar to list */ - new->row = make_scalar_list1($1.name, $1.scalar, - $1.lineno, @1); - /* no need for check_assignable */ - } - else - { - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("loop variable of loop over rows must be a record or row variable or list of scalar variables"), - parser_errposition(@1))); - } - - new->query = expr1; - $$ = (PLpgSQL_stmt *) new; - } - } - } - ; - -/* - * Processing the for_variable is tricky because we don't yet know if the - * FOR is an integer FOR loop or a loop over query results. In the former - * case, the variable is just a name that we must instantiate as a loop - * local variable, regardless of any other definition it might have. - * Therefore, we always save the actual identifier into $$.name where it - * can be used for that case. We also save the outer-variable definition, - * if any, because that's what we need for the loop-over-query case. Note - * that we must NOT apply check_assignable() or any other semantic check - * until we know what's what. - * - * However, if we see a comma-separated list of names, we know that it - * can't be an integer FOR loop and so it's OK to check the variables - * immediately. In particular, for T_WORD followed by comma, we should - * complain that the name is not known rather than say it's a syntax error. - * Note that the non-error result of this case sets *both* $$.scalar and - * $$.row; see the for_control production. - */ -for_variable : T_DATUM - { - $$.name = NameOfDatum(&($1)); - $$.lineno = plpgsql_location_to_lineno(@1); - if ($1.datum->dtype == PLPGSQL_DTYPE_ROW) - { - $$.scalar = NULL; - $$.rec = NULL; - $$.row = (PLpgSQL_row *) $1.datum; - } - else if ($1.datum->dtype == PLPGSQL_DTYPE_REC) - { - $$.scalar = NULL; - $$.rec = (PLpgSQL_rec *) $1.datum; - $$.row = NULL; - } - else - { - int tok; - - $$.scalar = $1.datum; - $$.rec = NULL; - $$.row = NULL; - /* check for comma-separated list */ - tok = yylex(); - plpgsql_push_back_token(tok); - if (tok == ',') - $$.row = read_into_scalar_list($$.name, - $$.scalar, - @1); - } - } - | T_WORD - { - int tok; - - $$.name = $1.ident; - $$.lineno = plpgsql_location_to_lineno(@1); - $$.scalar = NULL; - $$.rec = NULL; - $$.row = NULL; - /* check for comma-separated list */ - tok = yylex(); - plpgsql_push_back_token(tok); - if (tok == ',') - word_is_not_variable(&($1), @1); - } - | T_CWORD - { - /* just to give a better message than "syntax error" */ - cword_is_not_variable(&($1), @1); - } - ; - -stmt_foreach_a : opt_block_label K_FOREACH for_variable foreach_slice K_IN K_ARRAY expr_until_loop loop_body - { - PLpgSQL_stmt_foreach_a *new; - - new = palloc0(sizeof(PLpgSQL_stmt_foreach_a)); - new->cmd_type = PLPGSQL_STMT_FOREACH_A; - new->lineno = plpgsql_location_to_lineno(@2); - new->label = $1; - new->slice = $4; - new->expr = $7; - new->body = $8.stmts; - - if ($3.rec) - { - new->varno = $3.rec->dno; - check_assignable((PLpgSQL_datum *) $3.rec, @3); - } - else if ($3.row) - { - new->varno = $3.row->dno; - check_assignable((PLpgSQL_datum *) $3.row, @3); - } - else if ($3.scalar) - { - new->varno = $3.scalar->dno; - check_assignable($3.scalar, @3); - } - else - { - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("loop variable of FOREACH must be a known variable or list of variables"), - parser_errposition(@3))); - } - - check_labels($1, $8.end_label, $8.end_label_location); - plpgsql_ns_pop(); - - $$ = (PLpgSQL_stmt *) new; - } - ; - -foreach_slice : - { - $$ = 0; - } - | K_SLICE ICONST - { - $$ = $2; - } - ; - -stmt_exit : exit_type opt_label opt_exitcond - { - PLpgSQL_stmt_exit *new; - - new = palloc0(sizeof(PLpgSQL_stmt_exit)); - new->cmd_type = PLPGSQL_STMT_EXIT; - new->is_exit = $1; - new->lineno = plpgsql_location_to_lineno(@1); - new->label = $2; - new->cond = $3; - - $$ = (PLpgSQL_stmt *)new; - } - ; - -exit_type : K_EXIT - { - $$ = true; - } - | K_CONTINUE - { - $$ = false; - } - ; - -stmt_return : K_RETURN - { - int tok; - - tok = yylex(); - if (tok == 0) - yyerror("unexpected end of function definition"); - - if (tok_is_keyword(tok, &yylval, - K_NEXT, "next")) - { - $$ = make_return_next_stmt(@1); - } - else if (tok_is_keyword(tok, &yylval, - K_QUERY, "query")) - { - $$ = make_return_query_stmt(@1); - } - else - { - plpgsql_push_back_token(tok); - $$ = make_return_stmt(@1); - } - } - ; - -stmt_raise : K_RAISE - { - PLpgSQL_stmt_raise *new; - int tok; - - new = palloc(sizeof(PLpgSQL_stmt_raise)); - - new->cmd_type = PLPGSQL_STMT_RAISE; - new->lineno = plpgsql_location_to_lineno(@1); - new->elog_level = ERROR; /* default */ - new->condname = NULL; - new->message = NULL; - new->params = NIL; - new->options = NIL; - - tok = yylex(); - if (tok == 0) - yyerror("unexpected end of function definition"); - - /* - * We could have just RAISE, meaning to re-throw - * the current error. - */ - if (tok != ';') - { - /* - * First is an optional elog severity level. - */ - if (tok_is_keyword(tok, &yylval, - K_EXCEPTION, "exception")) - { - new->elog_level = ERROR; - tok = yylex(); - } - else if (tok_is_keyword(tok, &yylval, - K_WARNING, "warning")) - { - new->elog_level = WARNING; - tok = yylex(); - } - else if (tok_is_keyword(tok, &yylval, - K_NOTICE, "notice")) - { - new->elog_level = NOTICE; - tok = yylex(); - } - else if (tok_is_keyword(tok, &yylval, - K_INFO, "info")) - { - new->elog_level = INFO; - tok = yylex(); - } - else if (tok_is_keyword(tok, &yylval, - K_LOG, "log")) - { - new->elog_level = LOG; - tok = yylex(); - } - else if (tok_is_keyword(tok, &yylval, - K_DEBUG, "debug")) - { - new->elog_level = DEBUG1; - tok = yylex(); - } - if (tok == 0) - yyerror("unexpected end of function definition"); - - /* - * Next we can have a condition name, or - * equivalently SQLSTATE 'xxxxx', or a string - * literal that is the old-style message format, - * or USING to start the option list immediately. - */ - if (tok == SCONST) - { - /* old style message and parameters */ - new->message = yylval.str; - /* - * We expect either a semi-colon, which - * indicates no parameters, or a comma that - * begins the list of parameter expressions, - * or USING to begin the options list. - */ - tok = yylex(); - if (tok != ',' && tok != ';' && tok != K_USING) - yyerror("syntax error"); - - while (tok == ',') - { - PLpgSQL_expr *expr; - - expr = read_sql_construct(',', ';', K_USING, - ", or ; or USING", - "SELECT ", - true, true, true, - NULL, &tok); - new->params = lappend(new->params, expr); - } - } - else if (tok != K_USING) - { - /* must be condition name or SQLSTATE */ - if (tok_is_keyword(tok, &yylval, - K_SQLSTATE, "sqlstate")) - { - /* 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"); - new->condname = sqlstatestr; - } - else - { - if (tok == T_WORD) - new->condname = yylval.word.ident; - else if (plpgsql_token_is_unreserved_keyword(tok)) - new->condname = pstrdup(yylval.keyword); - else - yyerror("syntax error"); - plpgsql_recognize_err_condition(new->condname, - false); - } - tok = yylex(); - if (tok != ';' && tok != K_USING) - yyerror("syntax error"); - } - - if (tok == K_USING) - new->options = read_raise_options(); - } - - $$ = (PLpgSQL_stmt *)new; - } - ; - -loop_body : proc_sect K_END K_LOOP opt_label ';' - { - $$.stmts = $1; - $$.end_label = $4; - $$.end_label_location = @4; - } - ; - -/* - * T_WORD+T_CWORD match any initial identifier that is not a known plpgsql - * variable. (The composite case is probably a syntax error, but we'll let - * the core parser decide that.) Normally, we should assume that such a - * word is a SQL statement keyword that isn't also a plpgsql keyword. - * However, if the next token is assignment or '[', it can't be a valid - * SQL statement, and what we're probably looking at is an intended variable - * assignment. Give an appropriate complaint for that, instead of letting - * the core parser throw an unhelpful "syntax error". - */ -stmt_execsql : K_INSERT - { - $$ = make_execsql_stmt(K_INSERT, @1); - } - | T_WORD - { - int tok; - - tok = yylex(); - plpgsql_push_back_token(tok); - if (tok == '=' || tok == COLON_EQUALS || tok == '[') - word_is_not_variable(&($1), @1); - $$ = make_execsql_stmt(T_WORD, @1); - } - | T_CWORD - { - int tok; - - tok = yylex(); - plpgsql_push_back_token(tok); - if (tok == '=' || tok == COLON_EQUALS || tok == '[') - cword_is_not_variable(&($1), @1); - $$ = make_execsql_stmt(T_CWORD, @1); - } - ; - -stmt_dynexecute : K_EXECUTE - { - PLpgSQL_stmt_dynexecute *new; - PLpgSQL_expr *expr; - int endtoken; - - expr = read_sql_construct(K_INTO, K_USING, ';', - "INTO or USING or ;", - "SELECT ", - true, true, true, - NULL, &endtoken); - - new = palloc(sizeof(PLpgSQL_stmt_dynexecute)); - new->cmd_type = PLPGSQL_STMT_DYNEXECUTE; - new->lineno = plpgsql_location_to_lineno(@1); - new->query = expr; - new->into = false; - new->strict = false; - new->rec = NULL; - new->row = NULL; - new->params = NIL; - - /* - * We loop to allow the INTO and USING clauses to - * appear in either order, since people easily get - * that wrong. This coding also prevents "INTO foo" - * from getting absorbed into a USING expression, - * which is *really* confusing. - */ - for (;;) - { - if (endtoken == K_INTO) - { - if (new->into) /* multiple INTO */ - yyerror("syntax error"); - new->into = true; - read_into_target(&new->rec, &new->row, &new->strict); - endtoken = yylex(); - } - else if (endtoken == K_USING) - { - if (new->params) /* multiple USING */ - yyerror("syntax error"); - do - { - expr = read_sql_construct(',', ';', K_INTO, - ", or ; or INTO", - "SELECT ", - true, true, true, - NULL, &endtoken); - new->params = lappend(new->params, expr); - } while (endtoken == ','); - } - else if (endtoken == ';') - break; - else - yyerror("syntax error"); - } - - $$ = (PLpgSQL_stmt *)new; - } - ; - - -stmt_open : K_OPEN cursor_variable - { - PLpgSQL_stmt_open *new; - int tok; - - new = palloc0(sizeof(PLpgSQL_stmt_open)); - new->cmd_type = PLPGSQL_STMT_OPEN; - new->lineno = plpgsql_location_to_lineno(@1); - new->curvar = $2->dno; - new->cursor_options = CURSOR_OPT_FAST_PLAN; - - if ($2->cursor_explicit_expr == NULL) - { - /* be nice if we could use opt_scrollable here */ - tok = yylex(); - if (tok_is_keyword(tok, &yylval, - K_NO, "no")) - { - tok = yylex(); - if (tok_is_keyword(tok, &yylval, - K_SCROLL, "scroll")) - { - new->cursor_options |= CURSOR_OPT_NO_SCROLL; - tok = yylex(); - } - } - else if (tok_is_keyword(tok, &yylval, - K_SCROLL, "scroll")) - { - new->cursor_options |= CURSOR_OPT_SCROLL; - tok = yylex(); - } - - if (tok != K_FOR) - yyerror("syntax error, expected \"FOR\""); - - tok = yylex(); - if (tok == K_EXECUTE) - { - int endtoken; - - new->dynquery = - read_sql_expression2(K_USING, ';', - "USING or ;", - &endtoken); - - /* If we found "USING", collect argument(s) */ - if (endtoken == K_USING) - { - PLpgSQL_expr *expr; - - do - { - expr = read_sql_expression2(',', ';', - ", or ;", - &endtoken); - new->params = lappend(new->params, - expr); - } while (endtoken == ','); - } - } - else - { - plpgsql_push_back_token(tok); - new->query = read_sql_stmt(""); - } - } - else - { - /* predefined cursor query, so read args */ - new->argquery = read_cursor_args($2, ';', ";"); - } - - $$ = (PLpgSQL_stmt *)new; - } - ; - -stmt_fetch : K_FETCH opt_fetch_direction cursor_variable K_INTO - { - PLpgSQL_stmt_fetch *fetch = $2; - PLpgSQL_rec *rec; - PLpgSQL_row *row; - - /* We have already parsed everything through the INTO keyword */ - read_into_target(&rec, &row, NULL); - - if (yylex() != ';') - yyerror("syntax error"); - - /* - * We don't allow multiple rows in PL/pgSQL's FETCH - * statement, only in MOVE. - */ - if (fetch->returns_multiple_rows) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("FETCH statement cannot return multiple rows"), - parser_errposition(@1))); - - fetch->lineno = plpgsql_location_to_lineno(@1); - fetch->rec = rec; - fetch->row = row; - fetch->curvar = $3->dno; - fetch->is_move = false; - - $$ = (PLpgSQL_stmt *)fetch; - } - ; - -stmt_move : K_MOVE opt_fetch_direction cursor_variable ';' - { - PLpgSQL_stmt_fetch *fetch = $2; - - fetch->lineno = plpgsql_location_to_lineno(@1); - fetch->curvar = $3->dno; - fetch->is_move = true; - - $$ = (PLpgSQL_stmt *)fetch; - } - ; - -opt_fetch_direction : - { - $$ = read_fetch_direction(); - } - ; - -stmt_close : K_CLOSE cursor_variable ';' - { - PLpgSQL_stmt_close *new; - - new = palloc(sizeof(PLpgSQL_stmt_close)); - new->cmd_type = PLPGSQL_STMT_CLOSE; - new->lineno = plpgsql_location_to_lineno(@1); - new->curvar = $2->dno; - - $$ = (PLpgSQL_stmt *)new; - } - ; - -stmt_null : K_NULL ';' - { - /* We do not bother building a node for NULL */ - $$ = NULL; - } - ; - -cursor_variable : T_DATUM - { - if ($1.datum->dtype != PLPGSQL_DTYPE_VAR) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("cursor variable must be a simple variable"), - parser_errposition(@1))); - - if (((PLpgSQL_var *) $1.datum)->datatype->typoid != REFCURSOROID) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("variable \"%s\" must be of type cursor or refcursor", - ((PLpgSQL_var *) $1.datum)->refname), - parser_errposition(@1))); - $$ = (PLpgSQL_var *) $1.datum; - } - | T_WORD - { - /* just to give a better message than "syntax error" */ - word_is_not_variable(&($1), @1); - } - | T_CWORD - { - /* just to give a better message than "syntax error" */ - cword_is_not_variable(&($1), @1); - } - ; - -exception_sect : - { $$ = NULL; } - | K_EXCEPTION - { - /* - * We use a mid-rule action to add these - * special variables to the namespace before - * parsing the WHEN clauses themselves. The - * scope of the names extends to the end of the - * current block. - */ - int lineno = plpgsql_location_to_lineno(@1); - PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block)); - PLpgSQL_variable *var; - - var = plpgsql_build_variable("sqlstate", lineno, - plpgsql_build_datatype(TEXTOID, - -1, - plpgsql_curr_compile->fn_input_collation), - true); - ((PLpgSQL_var *) var)->isconst = true; - new->sqlstate_varno = var->dno; - - var = plpgsql_build_variable("sqlerrm", lineno, - plpgsql_build_datatype(TEXTOID, - -1, - plpgsql_curr_compile->fn_input_collation), - true); - ((PLpgSQL_var *) var)->isconst = true; - new->sqlerrm_varno = var->dno; - - $$ = new; - } - proc_exceptions - { - PLpgSQL_exception_block *new = $2; - new->exc_list = $3; - - $$ = new; - } - ; - -proc_exceptions : proc_exceptions proc_exception - { - $$ = lappend($1, $2); - } - | proc_exception - { - $$ = list_make1($1); - } - ; - -proc_exception : K_WHEN proc_conditions K_THEN proc_sect - { - PLpgSQL_exception *new; - - new = palloc0(sizeof(PLpgSQL_exception)); - new->lineno = plpgsql_location_to_lineno(@1); - new->conditions = $2; - new->action = $4; - - $$ = new; - } - ; - -proc_conditions : proc_conditions K_OR proc_condition - { - PLpgSQL_condition *old; - - for (old = $1; old->next != NULL; old = old->next) - /* skip */ ; - old->next = $3; - $$ = $1; - } - | proc_condition - { - $$ = $1; - } - ; - -proc_condition : any_identifier - { - if (strcmp($1, "sqlstate") != 0) - { - $$ = plpgsql_parse_err_condition($1); - } - else - { - PLpgSQL_condition *new; - char *sqlstatestr; - - /* next token should be a string literal */ - 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"); - - new = palloc(sizeof(PLpgSQL_condition)); - new->sqlerrstate = - MAKE_SQLSTATE(sqlstatestr[0], - sqlstatestr[1], - sqlstatestr[2], - sqlstatestr[3], - sqlstatestr[4]); - new->condname = sqlstatestr; - new->next = NULL; - - $$ = new; - } - } - ; - -expr_until_semi : - { $$ = read_sql_expression(';', ";"); } - ; - -expr_until_rightbracket : - { $$ = read_sql_expression(']', "]"); } - ; - -expr_until_then : - { $$ = read_sql_expression(K_THEN, "THEN"); } - ; - -expr_until_loop : - { $$ = read_sql_expression(K_LOOP, "LOOP"); } - ; - -opt_block_label : - { - plpgsql_ns_push(NULL); - $$ = NULL; - } - | LESS_LESS any_identifier GREATER_GREATER - { - plpgsql_ns_push($2); - $$ = $2; - } - ; - -opt_label : - { - $$ = NULL; - } - | any_identifier - { - if (plpgsql_ns_lookup_label(plpgsql_ns_top(), $1) == NULL) - yyerror("label does not exist"); - $$ = $1; - } - ; - -opt_exitcond : ';' - { $$ = NULL; } - | K_WHEN expr_until_semi - { $$ = $2; } - ; - -/* - * need to allow DATUM because scanner will have tried to resolve as variable - */ -any_identifier : T_WORD - { - $$ = $1.ident; - } - | unreserved_keyword - { - $$ = pstrdup($1); - } - | T_DATUM - { - if ($1.ident == NULL) /* composite name not OK */ - yyerror("syntax error"); - $$ = $1.ident; - } - ; - -unreserved_keyword : - K_ABSOLUTE - | K_ALIAS - | K_ARRAY - | K_BACKWARD - | K_CONSTANT - | K_CURRENT - | K_CURSOR - | K_DEBUG - | K_DETAIL - | K_DUMP - | K_ERRCODE - | K_ERROR - | K_FIRST - | K_FORWARD - | K_HINT - | K_INFO - | K_IS - | K_LAST - | K_LOG - | K_MESSAGE - | K_MESSAGE_TEXT - | K_NEXT - | K_NO - | K_NOTICE - | K_OPTION - | K_PG_EXCEPTION_CONTEXT - | K_PG_EXCEPTION_DETAIL - | K_PG_EXCEPTION_HINT - | K_PRIOR - | K_QUERY - | K_RELATIVE - | K_RESULT_OID - | K_RETURNED_SQLSTATE - | K_REVERSE - | K_ROW_COUNT - | K_ROWTYPE - | K_SCROLL - | K_SLICE - | K_SQLSTATE - | K_STACKED - | K_TYPE - | K_USE_COLUMN - | K_USE_VARIABLE - | K_VARIABLE_CONFLICT - | K_WARNING - ; - -%% - -/* - * Check whether a token represents an "unreserved keyword". - * We have various places where we want to recognize a keyword in preference - * to a variable name, but not reserve that keyword in other contexts. - * Hence, this kluge. - */ -static bool -tok_is_keyword(int token, union YYSTYPE *lval, - int kw_token, const char *kw_str) -{ - if (token == kw_token) - { - /* Normal case, was recognized by scanner (no conflicting variable) */ - return true; - } - else if (token == T_DATUM) - { - /* - * It's a variable, so recheck the string name. Note we will not - * match composite names (hence an unreserved word followed by "." - * will not be recognized). - */ - if (!lval->wdatum.quoted && lval->wdatum.ident != NULL && - strcmp(lval->wdatum.ident, kw_str) == 0) - return true; - } - return false; /* not the keyword */ -} - -/* - * Convenience routine to complain when we expected T_DATUM and got T_WORD, - * ie, unrecognized variable. - */ -static void -word_is_not_variable(PLword *word, int location) -{ - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("\"%s\" is not a known variable", - word->ident), - parser_errposition(location))); -} - -/* Same, for a CWORD */ -static void -cword_is_not_variable(PLcword *cword, int location) -{ - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("\"%s\" is not a known variable", - NameListToString(cword->idents)), - parser_errposition(location))); -} - -/* - * Convenience routine to complain when we expected T_DATUM and got - * something else. "tok" must be the current token, since we also - * look at yylval and yylloc. - */ -static void -current_token_is_not_variable(int tok) -{ - if (tok == T_WORD) - word_is_not_variable(&(yylval.word), yylloc); - else if (tok == T_CWORD) - cword_is_not_variable(&(yylval.cword), yylloc); - else - yyerror("syntax error"); -} - -/* Convenience routine to read an expression with one possible terminator */ -static PLpgSQL_expr * -read_sql_expression(int until, const char *expected) -{ - return read_sql_construct(until, 0, 0, expected, - "SELECT ", true, true, true, NULL, NULL); -} - -/* Convenience routine to read an expression with two possible terminators */ -static PLpgSQL_expr * -read_sql_expression2(int until, int until2, const char *expected, - int *endtoken) -{ - return read_sql_construct(until, until2, 0, expected, - "SELECT ", true, true, true, NULL, endtoken); -} - -/* Convenience routine to read a SQL statement that must end with ';' */ -static PLpgSQL_expr * -read_sql_stmt(const char *sqlstart) -{ - return read_sql_construct(';', 0, 0, ";", - sqlstart, false, true, true, NULL, NULL); -} - -/* - * Read a SQL construct and build a PLpgSQL_expr for it. - * - * until: token code for expected terminator - * until2: token code for alternate terminator (pass 0 if none) - * until3: token code for another alternate terminator (pass 0 if none) - * expected: text to use in complaining that terminator was not found - * sqlstart: text to prefix to the accumulated SQL text - * isexpression: whether to say we're reading an "expression" or a "statement" - * valid_sql: whether to check the syntax of the expr (prefixed with sqlstart) - * trim: trim trailing whitespace - * startloc: if not NULL, location of first token is stored at *startloc - * endtoken: if not NULL, ending token is stored at *endtoken - * (this is only interesting if until2 or until3 isn't zero) - */ -static PLpgSQL_expr * -read_sql_construct(int until, - int until2, - int until3, - const char *expected, - const char *sqlstart, - bool isexpression, - bool valid_sql, - bool trim, - int *startloc, - int *endtoken) -{ - int tok; - StringInfoData ds; - IdentifierLookup save_IdentifierLookup; - int startlocation = -1; - int parenlevel = 0; - PLpgSQL_expr *expr; - - initStringInfo(&ds); - appendStringInfoString(&ds, sqlstart); - - /* special lookup mode for identifiers within the SQL text */ - save_IdentifierLookup = plpgsql_IdentifierLookup; - plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR; - - for (;;) - { - tok = yylex(); - if (startlocation < 0) /* remember loc of first token */ - startlocation = yylloc; - if (tok == until && parenlevel == 0) - break; - if (tok == until2 && parenlevel == 0) - break; - if (tok == until3 && parenlevel == 0) - break; - if (tok == '(' || tok == '[') - parenlevel++; - else if (tok == ')' || tok == ']') - { - parenlevel--; - if (parenlevel < 0) - yyerror("mismatched parentheses"); - } - /* - * End of function definition is an error, and we don't expect to - * hit a semicolon either (unless it's the until symbol, in which - * case we should have fallen out above). - */ - if (tok == 0 || tok == ';') - { - if (parenlevel != 0) - yyerror("mismatched parentheses"); - if (isexpression) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("missing \"%s\" at end of SQL expression", - expected), - parser_errposition(yylloc))); - else - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("missing \"%s\" at end of SQL statement", - expected), - parser_errposition(yylloc))); - } - } - - plpgsql_IdentifierLookup = save_IdentifierLookup; - - if (startloc) - *startloc = startlocation; - if (endtoken) - *endtoken = tok; - - /* give helpful complaint about empty input */ - if (startlocation >= yylloc) - { - if (isexpression) - yyerror("missing expression"); - else - yyerror("missing SQL statement"); - } - - plpgsql_append_source_text(&ds, startlocation, yylloc); - - /* trim any trailing whitespace, for neatness */ - if (trim) - { - while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1])) - ds.data[--ds.len] = '\0'; - } - - expr = palloc0(sizeof(PLpgSQL_expr)); - expr->dtype = PLPGSQL_DTYPE_EXPR; - expr->query = pstrdup(ds.data); - expr->plan = NULL; - expr->paramnos = NULL; - expr->ns = plpgsql_ns_top(); - pfree(ds.data); - - if (valid_sql) - check_sql_expr(expr->query, startlocation, strlen(sqlstart)); - - return expr; -} - -static PLpgSQL_type * -read_datatype(int tok) -{ - StringInfoData ds; - char *type_name; - int startlocation; - PLpgSQL_type *result; - int parenlevel = 0; - - /* Should only be called while parsing DECLARE sections */ - Assert(plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_DECLARE); - - /* Often there will be a lookahead token, but if not, get one */ - if (tok == YYEMPTY) - tok = yylex(); - - startlocation = yylloc; - - /* - * If we have a simple or composite identifier, check for %TYPE - * and %ROWTYPE constructs. - */ - if (tok == T_WORD) - { - char *dtname = yylval.word.ident; - - tok = yylex(); - if (tok == '%') - { - tok = yylex(); - if (tok_is_keyword(tok, &yylval, - K_TYPE, "type")) - { - result = plpgsql_parse_wordtype(dtname); - if (result) - return result; - } - else if (tok_is_keyword(tok, &yylval, - K_ROWTYPE, "rowtype")) - { - result = plpgsql_parse_wordrowtype(dtname); - if (result) - return result; - } - } - } - else if (plpgsql_token_is_unreserved_keyword(tok)) - { - char *dtname = pstrdup(yylval.keyword); - - tok = yylex(); - if (tok == '%') - { - tok = yylex(); - if (tok_is_keyword(tok, &yylval, - K_TYPE, "type")) - { - result = plpgsql_parse_wordtype(dtname); - if (result) - return result; - } - else if (tok_is_keyword(tok, &yylval, - K_ROWTYPE, "rowtype")) - { - result = plpgsql_parse_wordrowtype(dtname); - if (result) - return result; - } - } - } - else if (tok == T_CWORD) - { - List *dtnames = yylval.cword.idents; - - tok = yylex(); - if (tok == '%') - { - tok = yylex(); - if (tok_is_keyword(tok, &yylval, - K_TYPE, "type")) - { - result = plpgsql_parse_cwordtype(dtnames); - if (result) - return result; - } - else if (tok_is_keyword(tok, &yylval, - K_ROWTYPE, "rowtype")) - { - result = plpgsql_parse_cwordrowtype(dtnames); - if (result) - return result; - } - } - } - - while (tok != ';') - { - if (tok == 0) - { - if (parenlevel != 0) - yyerror("mismatched parentheses"); - else - yyerror("incomplete data type declaration"); - } - /* Possible followers for datatype in a declaration */ - if (tok == K_COLLATE || tok == K_NOT || - tok == '=' || tok == COLON_EQUALS || tok == K_DEFAULT) - break; - /* Possible followers for datatype in a cursor_arg list */ - if ((tok == ',' || tok == ')') && parenlevel == 0) - break; - if (tok == '(') - parenlevel++; - else if (tok == ')') - parenlevel--; - - tok = yylex(); - } - - /* set up ds to contain complete typename text */ - initStringInfo(&ds); - plpgsql_append_source_text(&ds, startlocation, yylloc); - type_name = ds.data; - - if (type_name[0] == '\0') - yyerror("missing data type declaration"); - - result = parse_datatype(type_name, startlocation); - - pfree(ds.data); - - plpgsql_push_back_token(tok); - - return result; -} - -static PLpgSQL_stmt * -make_execsql_stmt(int firsttoken, int location) -{ - StringInfoData ds; - IdentifierLookup save_IdentifierLookup; - PLpgSQL_stmt_execsql *execsql; - PLpgSQL_expr *expr; - PLpgSQL_row *row = NULL; - PLpgSQL_rec *rec = NULL; - int tok; - int prev_tok; - bool have_into = false; - bool have_strict = false; - int into_start_loc = -1; - int into_end_loc = -1; - - initStringInfo(&ds); - - /* special lookup mode for identifiers within the SQL text */ - save_IdentifierLookup = plpgsql_IdentifierLookup; - plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR; - - /* - * We have to special-case the sequence INSERT INTO, because we don't want - * that to be taken as an INTO-variables clause. Fortunately, this is the - * only valid use of INTO in a pl/pgsql SQL command, and INTO is already a - * fully reserved word in the main grammar. We have to treat it that way - * anywhere in the string, not only at the start; consider CREATE RULE - * containing an INSERT statement. - */ - tok = firsttoken; - for (;;) - { - prev_tok = tok; - tok = yylex(); - if (have_into && into_end_loc < 0) - into_end_loc = yylloc; /* token after the INTO part */ - if (tok == ';') - break; - if (tok == 0) - yyerror("unexpected end of function definition"); - - if (tok == K_INTO && prev_tok != K_INSERT) - { - if (have_into) - yyerror("INTO specified more than once"); - have_into = true; - into_start_loc = yylloc; - plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL; - read_into_target(&rec, &row, &have_strict); - plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR; - } - } - - plpgsql_IdentifierLookup = save_IdentifierLookup; - - if (have_into) - { - /* - * Insert an appropriate number of spaces corresponding to the - * INTO text, so that locations within the redacted SQL statement - * still line up with those in the original source text. - */ - plpgsql_append_source_text(&ds, location, into_start_loc); - appendStringInfoSpaces(&ds, into_end_loc - into_start_loc); - plpgsql_append_source_text(&ds, into_end_loc, yylloc); - } - else - plpgsql_append_source_text(&ds, location, yylloc); - - /* trim any trailing whitespace, for neatness */ - while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1])) - ds.data[--ds.len] = '\0'; - - expr = palloc0(sizeof(PLpgSQL_expr)); - expr->dtype = PLPGSQL_DTYPE_EXPR; - expr->query = pstrdup(ds.data); - expr->plan = NULL; - expr->paramnos = NULL; - expr->ns = plpgsql_ns_top(); - pfree(ds.data); - - check_sql_expr(expr->query, location, 0); - - execsql = palloc(sizeof(PLpgSQL_stmt_execsql)); - execsql->cmd_type = PLPGSQL_STMT_EXECSQL; - execsql->lineno = plpgsql_location_to_lineno(location); - execsql->sqlstmt = expr; - execsql->into = have_into; - execsql->strict = have_strict; - execsql->rec = rec; - execsql->row = row; - - return (PLpgSQL_stmt *) execsql; -} - - -/* - * Read FETCH or MOVE direction clause (everything through FROM/IN). - */ -static PLpgSQL_stmt_fetch * -read_fetch_direction(void) -{ - PLpgSQL_stmt_fetch *fetch; - int tok; - bool check_FROM = true; - - /* - * We create the PLpgSQL_stmt_fetch struct here, but only fill in - * the fields arising from the optional direction clause - */ - fetch = (PLpgSQL_stmt_fetch *) palloc0(sizeof(PLpgSQL_stmt_fetch)); - fetch->cmd_type = PLPGSQL_STMT_FETCH; - /* set direction defaults: */ - fetch->direction = FETCH_FORWARD; - fetch->how_many = 1; - fetch->expr = NULL; - fetch->returns_multiple_rows = false; - - tok = yylex(); - if (tok == 0) - yyerror("unexpected end of function definition"); - - if (tok_is_keyword(tok, &yylval, - K_NEXT, "next")) - { - /* use defaults */ - } - else if (tok_is_keyword(tok, &yylval, - K_PRIOR, "prior")) - { - fetch->direction = FETCH_BACKWARD; - } - else if (tok_is_keyword(tok, &yylval, - K_FIRST, "first")) - { - fetch->direction = FETCH_ABSOLUTE; - } - else if (tok_is_keyword(tok, &yylval, - K_LAST, "last")) - { - fetch->direction = FETCH_ABSOLUTE; - fetch->how_many = -1; - } - else if (tok_is_keyword(tok, &yylval, - K_ABSOLUTE, "absolute")) - { - fetch->direction = FETCH_ABSOLUTE; - fetch->expr = read_sql_expression2(K_FROM, K_IN, - "FROM or IN", - NULL); - check_FROM = false; - } - else if (tok_is_keyword(tok, &yylval, - K_RELATIVE, "relative")) - { - fetch->direction = FETCH_RELATIVE; - fetch->expr = read_sql_expression2(K_FROM, K_IN, - "FROM or IN", - NULL); - check_FROM = false; - } - else if (tok_is_keyword(tok, &yylval, - K_ALL, "all")) - { - fetch->how_many = FETCH_ALL; - fetch->returns_multiple_rows = true; - } - else if (tok_is_keyword(tok, &yylval, - K_FORWARD, "forward")) - { - complete_direction(fetch, &check_FROM); - } - else if (tok_is_keyword(tok, &yylval, - K_BACKWARD, "backward")) - { - fetch->direction = FETCH_BACKWARD; - complete_direction(fetch, &check_FROM); - } - else if (tok == K_FROM || tok == K_IN) - { - /* empty direction */ - check_FROM = false; - } - else if (tok == T_DATUM) - { - /* Assume there's no direction clause and tok is a cursor name */ - plpgsql_push_back_token(tok); - check_FROM = false; - } - else - { - /* - * Assume it's a count expression with no preceding keyword. - * Note: we allow this syntax because core SQL does, but we don't - * document it because of the ambiguity with the omitted-direction - * case. For instance, "MOVE n IN c" will fail if n is a variable. - * Perhaps this can be improved someday, but it's hardly worth a - * lot of work. - */ - plpgsql_push_back_token(tok); - fetch->expr = read_sql_expression2(K_FROM, K_IN, - "FROM or IN", - NULL); - fetch->returns_multiple_rows = true; - check_FROM = false; - } - - /* check FROM or IN keyword after direction's specification */ - if (check_FROM) - { - tok = yylex(); - if (tok != K_FROM && tok != K_IN) - yyerror("expected FROM or IN"); - } - - return fetch; -} - -/* - * Process remainder of FETCH/MOVE direction after FORWARD or BACKWARD. - * Allows these cases: - * FORWARD expr, FORWARD ALL, FORWARD - * BACKWARD expr, BACKWARD ALL, BACKWARD - */ -static void -complete_direction(PLpgSQL_stmt_fetch *fetch, bool *check_FROM) -{ - int tok; - - tok = yylex(); - if (tok == 0) - yyerror("unexpected end of function definition"); - - if (tok == K_FROM || tok == K_IN) - { - *check_FROM = false; - return; - } - - if (tok == K_ALL) - { - fetch->how_many = FETCH_ALL; - fetch->returns_multiple_rows = true; - *check_FROM = true; - return; - } - - plpgsql_push_back_token(tok); - fetch->expr = read_sql_expression2(K_FROM, K_IN, - "FROM or IN", - NULL); - fetch->returns_multiple_rows = true; - *check_FROM = false; -} - - -static PLpgSQL_stmt * -make_return_stmt(int location) -{ - PLpgSQL_stmt_return *new; - - new = palloc0(sizeof(PLpgSQL_stmt_return)); - new->cmd_type = PLPGSQL_STMT_RETURN; - new->lineno = plpgsql_location_to_lineno(location); - new->expr = NULL; - new->retvarno = -1; - - if (plpgsql_curr_compile->fn_retset) - { - if (yylex() != ';') - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("RETURN cannot have a parameter in function returning set"), - errhint("Use RETURN NEXT or RETURN QUERY."), - parser_errposition(yylloc))); - } - else if (plpgsql_curr_compile->out_param_varno >= 0) - { - if (yylex() != ';') - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("RETURN cannot have a parameter in function with OUT parameters"), - parser_errposition(yylloc))); - new->retvarno = plpgsql_curr_compile->out_param_varno; - } - else if (plpgsql_curr_compile->fn_rettype == VOIDOID) - { - if (yylex() != ';') - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("RETURN cannot have a parameter in function returning void"), - parser_errposition(yylloc))); - } - else if (plpgsql_curr_compile->fn_retistuple) - { - switch (yylex()) - { - case K_NULL: - /* we allow this to support RETURN NULL in triggers */ - break; - - case T_DATUM: - if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW || - yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC) - new->retvarno = yylval.wdatum.datum->dno; - else - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("RETURN must specify a record or row variable in function returning row"), - parser_errposition(yylloc))); - break; - - default: - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("RETURN must specify a record or row variable in function returning row"), - parser_errposition(yylloc))); - break; - } - if (yylex() != ';') - yyerror("syntax error"); - } - else - { - /* - * Note that a well-formed expression is _required_ here; - * anything else is a compile-time error. - */ - new->expr = read_sql_expression(';', ";"); - } - - return (PLpgSQL_stmt *) new; -} - - -static PLpgSQL_stmt * -make_return_next_stmt(int location) -{ - PLpgSQL_stmt_return_next *new; - - if (!plpgsql_curr_compile->fn_retset) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("cannot use RETURN NEXT in a non-SETOF function"), - parser_errposition(location))); - - new = palloc0(sizeof(PLpgSQL_stmt_return_next)); - new->cmd_type = PLPGSQL_STMT_RETURN_NEXT; - new->lineno = plpgsql_location_to_lineno(location); - new->expr = NULL; - new->retvarno = -1; - - if (plpgsql_curr_compile->out_param_varno >= 0) - { - if (yylex() != ';') - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("RETURN NEXT cannot have a parameter in function with OUT parameters"), - parser_errposition(yylloc))); - new->retvarno = plpgsql_curr_compile->out_param_varno; - } - else if (plpgsql_curr_compile->fn_retistuple) - { - switch (yylex()) - { - case T_DATUM: - if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW || - yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC) - new->retvarno = yylval.wdatum.datum->dno; - else - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("RETURN NEXT must specify a record or row variable in function returning row"), - parser_errposition(yylloc))); - break; - - default: - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("RETURN NEXT must specify a record or row variable in function returning row"), - parser_errposition(yylloc))); - break; - } - if (yylex() != ';') - yyerror("syntax error"); - } - else - new->expr = read_sql_expression(';', ";"); - - return (PLpgSQL_stmt *) new; -} - - -static PLpgSQL_stmt * -make_return_query_stmt(int location) -{ - PLpgSQL_stmt_return_query *new; - int tok; - - if (!plpgsql_curr_compile->fn_retset) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("cannot use RETURN QUERY in a non-SETOF function"), - parser_errposition(location))); - - new = palloc0(sizeof(PLpgSQL_stmt_return_query)); - new->cmd_type = PLPGSQL_STMT_RETURN_QUERY; - new->lineno = plpgsql_location_to_lineno(location); - - /* check for RETURN QUERY EXECUTE */ - if ((tok = yylex()) != K_EXECUTE) - { - /* ordinary static query */ - plpgsql_push_back_token(tok); - new->query = read_sql_stmt(""); - } - else - { - /* dynamic SQL */ - int term; - - new->dynquery = read_sql_expression2(';', K_USING, "; or USING", - &term); - if (term == K_USING) - { - do - { - PLpgSQL_expr *expr; - - expr = read_sql_expression2(',', ';', ", or ;", &term); - new->params = lappend(new->params, expr); - } while (term == ','); - } - } - - return (PLpgSQL_stmt *) new; -} - - -/* convenience routine to fetch the name of a T_DATUM */ -static char * -NameOfDatum(PLwdatum *wdatum) -{ - if (wdatum->ident) - return wdatum->ident; - Assert(wdatum->idents != NIL); - return NameListToString(wdatum->idents); -} - -static void -check_assignable(PLpgSQL_datum *datum, int location) -{ - switch (datum->dtype) - { - case PLPGSQL_DTYPE_VAR: - if (((PLpgSQL_var *) datum)->isconst) - ereport(ERROR, - (errcode(ERRCODE_ERROR_IN_ASSIGNMENT), - errmsg("\"%s\" is declared CONSTANT", - ((PLpgSQL_var *) datum)->refname), - parser_errposition(location))); - break; - case PLPGSQL_DTYPE_ROW: - /* always assignable? */ - break; - case PLPGSQL_DTYPE_REC: - /* always assignable? What about NEW/OLD? */ - break; - case PLPGSQL_DTYPE_RECFIELD: - /* always assignable? */ - break; - case PLPGSQL_DTYPE_ARRAYELEM: - /* always assignable? */ - break; - default: - elog(ERROR, "unrecognized dtype: %d", datum->dtype); - break; - } -} - -/* - * Read the argument of an INTO clause. On entry, we have just read the - * INTO keyword. - */ -static void -read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict) -{ - int tok; - - /* Set default results */ - *rec = NULL; - *row = NULL; - if (strict) - *strict = false; - - tok = yylex(); - if (strict && tok == K_STRICT) - { - *strict = true; - tok = yylex(); - } - - /* - * Currently, a row or record variable can be the single INTO target, - * but not a member of a multi-target list. So we throw error if there - * is a comma after it, because that probably means the user tried to - * write a multi-target list. If this ever gets generalized, we should - * probably refactor read_into_scalar_list so it handles all cases. - */ - switch (tok) - { - case T_DATUM: - if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW) - { - check_assignable(yylval.wdatum.datum, yylloc); - *row = (PLpgSQL_row *) yylval.wdatum.datum; - - if ((tok = yylex()) == ',') - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("record or row variable cannot be part of multiple-item INTO list"), - parser_errposition(yylloc))); - plpgsql_push_back_token(tok); - } - else if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC) - { - check_assignable(yylval.wdatum.datum, yylloc); - *rec = (PLpgSQL_rec *) yylval.wdatum.datum; - - if ((tok = yylex()) == ',') - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("record or row variable cannot be part of multiple-item INTO list"), - parser_errposition(yylloc))); - plpgsql_push_back_token(tok); - } - else - { - *row = read_into_scalar_list(NameOfDatum(&(yylval.wdatum)), - yylval.wdatum.datum, yylloc); - } - break; - - default: - /* just to give a better message than "syntax error" */ - current_token_is_not_variable(tok); - } -} - -/* - * Given the first datum and name in the INTO list, continue to read - * comma-separated scalar variables until we run out. Then construct - * and return a fake "row" variable that represents the list of - * scalars. - */ -static PLpgSQL_row * -read_into_scalar_list(char *initial_name, - PLpgSQL_datum *initial_datum, - int initial_location) -{ - int nfields; - char *fieldnames[1024]; - int varnos[1024]; - PLpgSQL_row *row; - int tok; - - check_assignable(initial_datum, initial_location); - fieldnames[0] = initial_name; - varnos[0] = initial_datum->dno; - nfields = 1; - - while ((tok = yylex()) == ',') - { - /* Check for array overflow */ - if (nfields >= 1024) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("too many INTO variables specified"), - parser_errposition(yylloc))); - - tok = yylex(); - switch (tok) - { - case T_DATUM: - check_assignable(yylval.wdatum.datum, yylloc); - if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW || - yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("\"%s\" is not a scalar variable", - NameOfDatum(&(yylval.wdatum))), - parser_errposition(yylloc))); - fieldnames[nfields] = NameOfDatum(&(yylval.wdatum)); - varnos[nfields++] = yylval.wdatum.datum->dno; - break; - - default: - /* just to give a better message than "syntax error" */ - current_token_is_not_variable(tok); - } - } - - /* - * We read an extra, non-comma token from yylex(), so push it - * back onto the input stream - */ - plpgsql_push_back_token(tok); - - row = palloc(sizeof(PLpgSQL_row)); - row->dtype = PLPGSQL_DTYPE_ROW; - row->refname = pstrdup("*internal*"); - row->lineno = plpgsql_location_to_lineno(initial_location); - row->rowtupdesc = NULL; - row->nfields = nfields; - row->fieldnames = palloc(sizeof(char *) * nfields); - row->varnos = palloc(sizeof(int) * nfields); - while (--nfields >= 0) - { - row->fieldnames[nfields] = fieldnames[nfields]; - row->varnos[nfields] = varnos[nfields]; - } - - plpgsql_adddatum((PLpgSQL_datum *)row); - - return row; -} - -/* - * Convert a single scalar into a "row" list. This is exactly - * like read_into_scalar_list except we never consume any input. - * - * Note: lineno could be computed from location, but since callers - * have it at hand already, we may as well pass it in. - */ -static PLpgSQL_row * -make_scalar_list1(char *initial_name, - PLpgSQL_datum *initial_datum, - int lineno, int location) -{ - PLpgSQL_row *row; - - check_assignable(initial_datum, location); - - row = palloc(sizeof(PLpgSQL_row)); - row->dtype = PLPGSQL_DTYPE_ROW; - row->refname = pstrdup("*internal*"); - row->lineno = lineno; - row->rowtupdesc = NULL; - row->nfields = 1; - row->fieldnames = palloc(sizeof(char *)); - row->varnos = palloc(sizeof(int)); - row->fieldnames[0] = initial_name; - row->varnos[0] = initial_datum->dno; - - plpgsql_adddatum((PLpgSQL_datum *)row); - - return row; -} - -/* - * When the PL/pgSQL parser expects to see a SQL statement, it is very - * liberal in what it accepts; for example, we often assume an - * unrecognized keyword is the beginning of a SQL statement. This - * avoids the need to duplicate parts of the SQL grammar in the - * PL/pgSQL grammar, but it means we can accept wildly malformed - * input. To try and catch some of the more obviously invalid input, - * we run the strings we expect to be SQL statements through the main - * SQL parser. - * - * We only invoke the raw parser (not the analyzer); this doesn't do - * any database access and does not check any semantic rules, it just - * checks for basic syntactic correctness. We do this here, rather - * than after parsing has finished, because a malformed SQL statement - * may cause the PL/pgSQL parser to become confused about statement - * borders. So it is best to bail out as early as we can. - * - * It is assumed that "stmt" represents a copy of the function source text - * beginning at offset "location", with leader text of length "leaderlen" - * (typically "SELECT ") prefixed to the source text. We use this assumption - * to transpose any error cursor position back to the function source text. - * If no error cursor is provided, we'll just point at "location". - */ -static void -check_sql_expr(const char *stmt, int location, int leaderlen) -{ - sql_error_callback_arg cbarg; - ErrorContextCallback syntax_errcontext; - MemoryContext oldCxt; - - if (!plpgsql_check_syntax) - return; - - cbarg.location = location; - cbarg.leaderlen = leaderlen; - - syntax_errcontext.callback = plpgsql_sql_error_callback; - syntax_errcontext.arg = &cbarg; - syntax_errcontext.previous = error_context_stack; - error_context_stack = &syntax_errcontext; - - oldCxt = MemoryContextSwitchTo(compile_tmp_cxt); - (void) raw_parser(stmt); - MemoryContextSwitchTo(oldCxt); - - /* Restore former ereport callback */ - error_context_stack = syntax_errcontext.previous; -} - -static void -plpgsql_sql_error_callback(void *arg) -{ - sql_error_callback_arg *cbarg = (sql_error_callback_arg *) arg; - int errpos; - - /* - * First, set up internalerrposition to point to the start of the - * statement text within the function text. Note this converts - * location (a byte offset) to a character number. - */ - parser_errposition(cbarg->location); - - /* - * If the core parser provided an error position, transpose it. - * Note we are dealing with 1-based character numbers at this point. - */ - errpos = geterrposition(); - if (errpos > cbarg->leaderlen) - { - int myerrpos = getinternalerrposition(); - - if (myerrpos > 0) /* safety check */ - internalerrposition(myerrpos + errpos - cbarg->leaderlen - 1); - } - - /* In any case, flush errposition --- we want internalerrpos only */ - errposition(0); -} - -/* - * Parse a SQL datatype name and produce a PLpgSQL_type structure. - * - * The heavy lifting is done elsewhere. Here we are only concerned - * with setting up an errcontext link that will let us give an error - * cursor pointing into the plpgsql function source, if necessary. - * This is handled the same as in check_sql_expr(), and we likewise - * expect that the given string is a copy from the source text. - */ -static PLpgSQL_type * -parse_datatype(const char *string, int location) -{ - Oid type_id; - int32 typmod; - sql_error_callback_arg cbarg; - ErrorContextCallback syntax_errcontext; - - cbarg.location = location; - cbarg.leaderlen = 0; - - syntax_errcontext.callback = plpgsql_sql_error_callback; - syntax_errcontext.arg = &cbarg; - syntax_errcontext.previous = error_context_stack; - error_context_stack = &syntax_errcontext; - - /* Let the main parser try to parse it under standard SQL rules */ - parseTypeString(string, &type_id, &typmod); - - /* Restore former ereport callback */ - error_context_stack = syntax_errcontext.previous; - - /* Okay, build a PLpgSQL_type data structure for it */ - return plpgsql_build_datatype(type_id, typmod, - plpgsql_curr_compile->fn_input_collation); -} - -/* - * Check block starting and ending labels match. - */ -static void -check_labels(const char *start_label, const char *end_label, int end_location) -{ - if (end_label) - { - if (!start_label) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("end label \"%s\" specified for unlabelled block", - end_label), - parser_errposition(end_location))); - - if (strcmp(start_label, end_label) != 0) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("end label \"%s\" differs from block's label \"%s\"", - end_label, start_label), - parser_errposition(end_location))); - } -} - -/* - * Read the arguments (if any) for a cursor, followed by the until token - * - * If cursor has no args, just swallow the until token and return NULL. - * If it does have args, we expect to see "( arg [, arg ...] )" followed - * by the until token, where arg may be a plain expression, or a named - * parameter assignment of the form argname := expr. Consume all that and - * return a SELECT query that evaluates the expression(s) (without the outer - * parens). - */ -static PLpgSQL_expr * -read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected) -{ - PLpgSQL_expr *expr; - PLpgSQL_row *row; - int tok; - int argc; - char **argv; - StringInfoData ds; - char *sqlstart = "SELECT "; - bool any_named = false; - - tok = yylex(); - if (cursor->cursor_explicit_argrow < 0) - { - /* No arguments expected */ - if (tok == '(') - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("cursor \"%s\" has no arguments", - cursor->refname), - parser_errposition(yylloc))); - - if (tok != until) - yyerror("syntax error"); - - return NULL; - } - - /* Else better provide arguments */ - if (tok != '(') - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("cursor \"%s\" has arguments", - cursor->refname), - parser_errposition(yylloc))); - - /* - * Read the arguments, one by one. - */ - row = (PLpgSQL_row *) plpgsql_Datums[cursor->cursor_explicit_argrow]; - argv = (char **) palloc0(row->nfields * sizeof(char *)); - - for (argc = 0; argc < row->nfields; argc++) - { - PLpgSQL_expr *item; - int endtoken; - int argpos; - int tok1, - tok2; - int arglocation; - - /* Check if it's a named parameter: "param := value" */ - plpgsql_peek2(&tok1, &tok2, &arglocation, NULL); - if (tok1 == IDENT && tok2 == COLON_EQUALS) - { - char *argname; - IdentifierLookup save_IdentifierLookup; - - /* Read the argument name, ignoring any matching variable */ - save_IdentifierLookup = plpgsql_IdentifierLookup; - plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE; - yylex(); - argname = yylval.str; - plpgsql_IdentifierLookup = save_IdentifierLookup; - - /* Match argument name to cursor arguments */ - for (argpos = 0; argpos < row->nfields; argpos++) - { - if (strcmp(row->fieldnames[argpos], argname) == 0) - break; - } - if (argpos == row->nfields) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("cursor \"%s\" has no argument named \"%s\"", - cursor->refname, argname), - parser_errposition(yylloc))); - - /* - * Eat the ":=". We already peeked, so the error should never - * happen. - */ - tok2 = yylex(); - if (tok2 != COLON_EQUALS) - yyerror("syntax error"); - - any_named = true; - } - else - argpos = argc; - - if (argv[argpos] != NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("value for parameter \"%s\" of cursor \"%s\" specified more than once", - row->fieldnames[argpos], cursor->refname), - parser_errposition(arglocation))); - - /* - * Read the value expression. To provide the user with meaningful - * parse error positions, we check the syntax immediately, instead of - * checking the final expression that may have the arguments - * reordered. Trailing whitespace must not be trimmed, because - * otherwise input of the form (param -- comment\n, param) would be - * translated into a form where the second parameter is commented - * out. - */ - item = read_sql_construct(',', ')', 0, - ",\" or \")", - sqlstart, - true, true, - false, /* do not trim */ - NULL, &endtoken); - - argv[argpos] = item->query + strlen(sqlstart); - - if (endtoken == ')' && !(argc == row->nfields - 1)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("not enough arguments for cursor \"%s\"", - cursor->refname), - parser_errposition(yylloc))); - - if (endtoken == ',' && (argc == row->nfields - 1)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("too many arguments for cursor \"%s\"", - cursor->refname), - parser_errposition(yylloc))); - } - - /* Make positional argument list */ - initStringInfo(&ds); - appendStringInfoString(&ds, sqlstart); - for (argc = 0; argc < row->nfields; argc++) - { - Assert(argv[argc] != NULL); - - /* - * Because named notation allows permutated argument lists, include - * the parameter name for meaningful runtime errors. - */ - appendStringInfoString(&ds, argv[argc]); - if (any_named) - appendStringInfo(&ds, " AS %s", - quote_identifier(row->fieldnames[argc])); - if (argc < row->nfields - 1) - appendStringInfoString(&ds, ", "); - } - appendStringInfoChar(&ds, ';'); - - expr = palloc0(sizeof(PLpgSQL_expr)); - expr->dtype = PLPGSQL_DTYPE_EXPR; - expr->query = pstrdup(ds.data); - expr->plan = NULL; - expr->paramnos = NULL; - expr->ns = plpgsql_ns_top(); - pfree(ds.data); - - /* Next we'd better find the until token */ - tok = yylex(); - if (tok != until) - yyerror("syntax error"); - - return expr; -} - -/* - * Parse RAISE ... USING options - */ -static List * -read_raise_options(void) -{ - List *result = NIL; - - for (;;) - { - PLpgSQL_raise_option *opt; - int tok; - - if ((tok = yylex()) == 0) - yyerror("unexpected end of function definition"); - - opt = (PLpgSQL_raise_option *) palloc(sizeof(PLpgSQL_raise_option)); - - if (tok_is_keyword(tok, &yylval, - K_ERRCODE, "errcode")) - opt->opt_type = PLPGSQL_RAISEOPTION_ERRCODE; - else if (tok_is_keyword(tok, &yylval, - K_MESSAGE, "message")) - opt->opt_type = PLPGSQL_RAISEOPTION_MESSAGE; - else if (tok_is_keyword(tok, &yylval, - K_DETAIL, "detail")) - opt->opt_type = PLPGSQL_RAISEOPTION_DETAIL; - else if (tok_is_keyword(tok, &yylval, - K_HINT, "hint")) - opt->opt_type = PLPGSQL_RAISEOPTION_HINT; - else - yyerror("unrecognized RAISE statement option"); - - tok = yylex(); - if (tok != '=' && tok != COLON_EQUALS) - yyerror("syntax error, expected \"=\""); - - opt->expr = read_sql_expression2(',', ';', ", or ;", &tok); - - result = lappend(result, opt); - - if (tok == ';') - break; - } - - return result; -} - -/* - * Fix up CASE statement - */ -static PLpgSQL_stmt * -make_case(int location, PLpgSQL_expr *t_expr, - List *case_when_list, List *else_stmts) -{ - PLpgSQL_stmt_case *new; - - new = palloc(sizeof(PLpgSQL_stmt_case)); - new->cmd_type = PLPGSQL_STMT_CASE; - new->lineno = plpgsql_location_to_lineno(location); - new->t_expr = t_expr; - new->t_varno = 0; - new->case_when_list = case_when_list; - new->have_else = (else_stmts != NIL); - /* Get rid of list-with-NULL hack */ - if (list_length(else_stmts) == 1 && linitial(else_stmts) == NULL) - new->else_stmts = NIL; - else - new->else_stmts = else_stmts; - - /* - * When test expression is present, we create a var for it and then - * convert all the WHEN expressions to "VAR IN (original_expression)". - * This is a bit klugy, but okay since we haven't yet done more than - * read the expressions as text. (Note that previous parsing won't - * have complained if the WHEN ... THEN expression contained multiple - * comma-separated values.) - */ - if (t_expr) - { - char varname[32]; - PLpgSQL_var *t_var; - ListCell *l; - - /* use a name unlikely to collide with any user names */ - snprintf(varname, sizeof(varname), "__Case__Variable_%d__", - plpgsql_nDatums); - - /* - * We don't yet know the result datatype of t_expr. Build the - * variable as if it were INT4; we'll fix this at runtime if needed. - */ - t_var = (PLpgSQL_var *) - plpgsql_build_variable(varname, new->lineno, - plpgsql_build_datatype(INT4OID, - -1, - InvalidOid), - true); - new->t_varno = t_var->dno; - - foreach(l, case_when_list) - { - PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l); - PLpgSQL_expr *expr = cwt->expr; - StringInfoData ds; - - /* copy expression query without SELECT keyword (expr->query + 7) */ - Assert(strncmp(expr->query, "SELECT ", 7) == 0); - - /* And do the string hacking */ - initStringInfo(&ds); - - appendStringInfo(&ds, "SELECT \"%s\" IN (%s)", - varname, expr->query + 7); - - pfree(expr->query); - expr->query = pstrdup(ds.data); - /* Adjust expr's namespace to include the case variable */ - expr->ns = plpgsql_ns_top(); - - pfree(ds.data); - } - } - - return (PLpgSQL_stmt *) new; -} diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y new file mode 100644 index 0000000000..cf164d0e48 --- /dev/null +++ b/src/pl/plpgsql/src/pl_gram.y @@ -0,0 +1,3735 @@ +%{ +/*------------------------------------------------------------------------- + * + * pl_gram.y - Parser for the PL/pgSQL procedural language + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/pl/plpgsql/src/pl_gram.y + * + *------------------------------------------------------------------------- + */ + +#include "plpgsql.h" + +#include "catalog/namespace.h" +#include "catalog/pg_type.h" +#include "parser/parser.h" +#include "parser/parse_type.h" +#include "parser/scanner.h" +#include "parser/scansup.h" +#include "utils/builtins.h" + + +/* Location tracking support --- simpler than bison's default */ +#define YYLLOC_DEFAULT(Current, Rhs, N) \ + do { \ + if (N) \ + (Current) = (Rhs)[1]; \ + else \ + (Current) = (Rhs)[0]; \ + } while (0) + +/* + * Bison doesn't allocate anything that needs to live across parser calls, + * so we can easily have it use palloc instead of malloc. This prevents + * memory leaks if we error out during parsing. Note this only works with + * bison >= 2.0. However, in bison 1.875 the default is to use alloca() + * if possible, so there's not really much problem anyhow, at least if + * you're building with gcc. + */ +#define YYMALLOC palloc +#define YYFREE pfree + + +typedef struct +{ + int location; + int leaderlen; +} sql_error_callback_arg; + +#define parser_errposition(pos) plpgsql_scanner_errposition(pos) + +union YYSTYPE; /* need forward reference for tok_is_keyword */ + +static bool tok_is_keyword(int token, union YYSTYPE *lval, + int kw_token, const char *kw_str); +static void word_is_not_variable(PLword *word, int location); +static void cword_is_not_variable(PLcword *cword, int location); +static void current_token_is_not_variable(int tok); +static PLpgSQL_expr *read_sql_construct(int until, + int until2, + int until3, + const char *expected, + const char *sqlstart, + bool isexpression, + bool valid_sql, + bool trim, + int *startloc, + int *endtoken); +static PLpgSQL_expr *read_sql_expression(int until, + const char *expected); +static PLpgSQL_expr *read_sql_expression2(int until, int until2, + const char *expected, + int *endtoken); +static PLpgSQL_expr *read_sql_stmt(const char *sqlstart); +static PLpgSQL_type *read_datatype(int tok); +static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location); +static PLpgSQL_stmt_fetch *read_fetch_direction(void); +static void complete_direction(PLpgSQL_stmt_fetch *fetch, + bool *check_FROM); +static PLpgSQL_stmt *make_return_stmt(int location); +static PLpgSQL_stmt *make_return_next_stmt(int location); +static PLpgSQL_stmt *make_return_query_stmt(int location); +static PLpgSQL_stmt *make_case(int location, PLpgSQL_expr *t_expr, + List *case_when_list, List *else_stmts); +static char *NameOfDatum(PLwdatum *wdatum); +static void check_assignable(PLpgSQL_datum *datum, int location); +static void read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, + bool *strict); +static PLpgSQL_row *read_into_scalar_list(char *initial_name, + PLpgSQL_datum *initial_datum, + int initial_location); +static PLpgSQL_row *make_scalar_list1(char *initial_name, + PLpgSQL_datum *initial_datum, + int lineno, int location); +static void check_sql_expr(const char *stmt, int location, + int leaderlen); +static void plpgsql_sql_error_callback(void *arg); +static PLpgSQL_type *parse_datatype(const char *string, int location); +static void check_labels(const char *start_label, + const char *end_label, + int end_location); +static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor, + int until, const char *expected); +static List *read_raise_options(void); + +%} + +%expect 0 +%name-prefix="plpgsql_yy" +%locations + +%union { + core_YYSTYPE core_yystype; + /* these fields must match core_YYSTYPE: */ + int ival; + char *str; + const char *keyword; + + PLword word; + PLcword cword; + PLwdatum wdatum; + bool boolean; + Oid oid; + struct + { + char *name; + int lineno; + } varname; + struct + { + char *name; + int lineno; + PLpgSQL_datum *scalar; + PLpgSQL_rec *rec; + PLpgSQL_row *row; + } forvariable; + struct + { + char *label; + int n_initvars; + int *initvarnos; + } declhdr; + struct + { + List *stmts; + char *end_label; + int end_label_location; + } loop_body; + List *list; + PLpgSQL_type *dtype; + PLpgSQL_datum *datum; + PLpgSQL_var *var; + PLpgSQL_expr *expr; + PLpgSQL_stmt *stmt; + PLpgSQL_condition *condition; + PLpgSQL_exception *exception; + PLpgSQL_exception_block *exception_block; + PLpgSQL_nsitem *nsitem; + PLpgSQL_diag_item *diagitem; + PLpgSQL_stmt_fetch *fetch; + PLpgSQL_case_when *casewhen; +} + +%type decl_sect +%type decl_varname +%type decl_const decl_notnull exit_type +%type decl_defval decl_cursor_query +%type decl_datatype +%type decl_collate +%type decl_cursor_args +%type decl_cursor_arglist +%type decl_aliasitem + +%type expr_until_semi expr_until_rightbracket +%type expr_until_then expr_until_loop opt_expr_until_when +%type opt_exitcond + +%type assign_var foreach_slice +%type cursor_variable +%type decl_cursor_arg +%type for_variable +%type for_control + +%type any_identifier opt_block_label opt_label + +%type proc_sect proc_stmts stmt_elsifs stmt_else +%type loop_body +%type proc_stmt pl_block +%type stmt_assign stmt_if stmt_loop stmt_while stmt_exit +%type stmt_return stmt_raise stmt_execsql +%type stmt_dynexecute stmt_for stmt_perform stmt_getdiag +%type stmt_open stmt_fetch stmt_move stmt_close stmt_null +%type stmt_case stmt_foreach_a + +%type proc_exceptions +%type exception_sect +%type proc_exception +%type proc_conditions proc_condition + +%type case_when +%type case_when_list opt_case_else + +%type getdiag_area_opt +%type getdiag_list +%type getdiag_list_item +%type getdiag_item getdiag_target + +%type opt_scrollable +%type opt_fetch_direction + +%type unreserved_keyword + + +/* + * Basic non-keyword token types. These are hard-wired into the core lexer. + * They must be listed first so that their numeric codes do not depend on + * the set of keywords. Keep this list in sync with backend/parser/gram.y! + * + * Some of these are not directly referenced in this file, but they must be + * here anyway. + */ +%token IDENT FCONST SCONST BCONST XCONST Op +%token ICONST PARAM +%token TYPECAST DOT_DOT COLON_EQUALS + +/* + * Other tokens recognized by plpgsql's lexer interface layer (pl_scanner.c). + */ +%token T_WORD /* unrecognized simple identifier */ +%token T_CWORD /* unrecognized composite identifier */ +%token T_DATUM /* a VAR, ROW, REC, or RECFIELD variable */ +%token LESS_LESS +%token GREATER_GREATER + +/* + * Keyword tokens. Some of these are reserved and some are not; + * see pl_scanner.c for info. Be sure unreserved keywords are listed + * in the "unreserved_keyword" production below. + */ +%token K_ABSOLUTE +%token K_ALIAS +%token K_ALL +%token K_ARRAY +%token K_BACKWARD +%token K_BEGIN +%token K_BY +%token K_CASE +%token K_CLOSE +%token K_COLLATE +%token K_CONSTANT +%token K_CONTINUE +%token K_CURRENT +%token K_CURSOR +%token K_DEBUG +%token K_DECLARE +%token K_DEFAULT +%token K_DETAIL +%token K_DIAGNOSTICS +%token K_DUMP +%token K_ELSE +%token K_ELSIF +%token K_END +%token K_ERRCODE +%token K_ERROR +%token K_EXCEPTION +%token K_EXECUTE +%token K_EXIT +%token K_FETCH +%token K_FIRST +%token K_FOR +%token K_FOREACH +%token K_FORWARD +%token K_FROM +%token K_GET +%token K_HINT +%token K_IF +%token K_IN +%token K_INFO +%token K_INSERT +%token K_INTO +%token K_IS +%token K_LAST +%token K_LOG +%token K_LOOP +%token K_MESSAGE +%token K_MESSAGE_TEXT +%token K_MOVE +%token K_NEXT +%token K_NO +%token K_NOT +%token K_NOTICE +%token K_NULL +%token K_OPEN +%token K_OPTION +%token K_OR +%token K_PERFORM +%token K_PG_EXCEPTION_CONTEXT +%token K_PG_EXCEPTION_DETAIL +%token K_PG_EXCEPTION_HINT +%token K_PRIOR +%token K_QUERY +%token K_RAISE +%token K_RELATIVE +%token K_RESULT_OID +%token K_RETURN +%token K_RETURNED_SQLSTATE +%token K_REVERSE +%token K_ROWTYPE +%token K_ROW_COUNT +%token K_SCROLL +%token K_SLICE +%token K_SQLSTATE +%token K_STACKED +%token K_STRICT +%token K_THEN +%token K_TO +%token K_TYPE +%token K_USE_COLUMN +%token K_USE_VARIABLE +%token K_USING +%token K_VARIABLE_CONFLICT +%token K_WARNING +%token K_WHEN +%token K_WHILE + +%% + +pl_function : comp_options pl_block opt_semi + { + plpgsql_parse_result = (PLpgSQL_stmt_block *) $2; + } + ; + +comp_options : + | comp_options comp_option + ; + +comp_option : '#' K_OPTION K_DUMP + { + plpgsql_DumpExecTree = true; + } + | '#' K_VARIABLE_CONFLICT K_ERROR + { + plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_ERROR; + } + | '#' K_VARIABLE_CONFLICT K_USE_VARIABLE + { + plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_VARIABLE; + } + | '#' K_VARIABLE_CONFLICT K_USE_COLUMN + { + plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_COLUMN; + } + ; + +opt_semi : + | ';' + ; + +pl_block : decl_sect K_BEGIN proc_sect exception_sect K_END opt_label + { + PLpgSQL_stmt_block *new; + + new = palloc0(sizeof(PLpgSQL_stmt_block)); + + new->cmd_type = PLPGSQL_STMT_BLOCK; + new->lineno = plpgsql_location_to_lineno(@2); + new->label = $1.label; + new->n_initvars = $1.n_initvars; + new->initvarnos = $1.initvarnos; + new->body = $3; + new->exceptions = $4; + + check_labels($1.label, $6, @6); + plpgsql_ns_pop(); + + $$ = (PLpgSQL_stmt *)new; + } + ; + + +decl_sect : opt_block_label + { + /* done with decls, so resume identifier lookup */ + plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL; + $$.label = $1; + $$.n_initvars = 0; + $$.initvarnos = NULL; + } + | opt_block_label decl_start + { + plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL; + $$.label = $1; + $$.n_initvars = 0; + $$.initvarnos = NULL; + } + | opt_block_label decl_start decl_stmts + { + plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL; + $$.label = $1; + /* Remember variables declared in decl_stmts */ + $$.n_initvars = plpgsql_add_initdatums(&($$.initvarnos)); + } + ; + +decl_start : K_DECLARE + { + /* Forget any variables created before block */ + plpgsql_add_initdatums(NULL); + /* + * Disable scanner lookup of identifiers while + * we process the decl_stmts + */ + plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE; + } + ; + +decl_stmts : decl_stmts decl_stmt + | decl_stmt + ; + +decl_stmt : decl_statement + | K_DECLARE + { + /* We allow useless extra DECLAREs */ + } + | LESS_LESS any_identifier GREATER_GREATER + { + /* + * Throw a helpful error if user tries to put block + * label just before BEGIN, instead of before DECLARE. + */ + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("block label must be placed before DECLARE, not after"), + parser_errposition(@1))); + } + ; + +decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull decl_defval + { + PLpgSQL_variable *var; + + /* + * If a collation is supplied, insert it into the + * datatype. We assume decl_datatype always returns + * a freshly built struct not shared with other + * variables. + */ + if (OidIsValid($4)) + { + if (!OidIsValid($3->collation)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("collations are not supported by type %s", + format_type_be($3->typoid)), + parser_errposition(@4))); + $3->collation = $4; + } + + var = plpgsql_build_variable($1.name, $1.lineno, + $3, true); + if ($2) + { + if (var->dtype == PLPGSQL_DTYPE_VAR) + ((PLpgSQL_var *) var)->isconst = $2; + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("row or record variable cannot be CONSTANT"), + parser_errposition(@2))); + } + if ($5) + { + if (var->dtype == PLPGSQL_DTYPE_VAR) + ((PLpgSQL_var *) var)->notnull = $5; + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("row or record variable cannot be NOT NULL"), + parser_errposition(@4))); + + } + if ($6 != NULL) + { + if (var->dtype == PLPGSQL_DTYPE_VAR) + ((PLpgSQL_var *) var)->default_val = $6; + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("default value for row or record variable is not supported"), + parser_errposition(@5))); + } + } + | decl_varname K_ALIAS K_FOR decl_aliasitem ';' + { + plpgsql_ns_additem($4->itemtype, + $4->itemno, $1.name); + } + | decl_varname opt_scrollable K_CURSOR + { plpgsql_ns_push($1.name); } + decl_cursor_args decl_is_for decl_cursor_query + { + PLpgSQL_var *new; + PLpgSQL_expr *curname_def; + char buf[1024]; + char *cp1; + char *cp2; + + /* pop local namespace for cursor args */ + plpgsql_ns_pop(); + + new = (PLpgSQL_var *) + plpgsql_build_variable($1.name, $1.lineno, + plpgsql_build_datatype(REFCURSOROID, + -1, + InvalidOid), + true); + + curname_def = palloc0(sizeof(PLpgSQL_expr)); + + curname_def->dtype = PLPGSQL_DTYPE_EXPR; + strcpy(buf, "SELECT "); + cp1 = new->refname; + cp2 = buf + strlen(buf); + /* + * Don't trust standard_conforming_strings here; + * it might change before we use the string. + */ + if (strchr(cp1, '\\') != NULL) + *cp2++ = ESCAPE_STRING_SYNTAX; + *cp2++ = '\''; + while (*cp1) + { + if (SQL_STR_DOUBLE(*cp1, true)) + *cp2++ = *cp1; + *cp2++ = *cp1++; + } + strcpy(cp2, "'::pg_catalog.refcursor"); + curname_def->query = pstrdup(buf); + new->default_val = curname_def; + + new->cursor_explicit_expr = $7; + if ($5 == NULL) + new->cursor_explicit_argrow = -1; + else + new->cursor_explicit_argrow = $5->dno; + new->cursor_options = CURSOR_OPT_FAST_PLAN | $2; + } + ; + +opt_scrollable : + { + $$ = 0; + } + | K_NO K_SCROLL + { + $$ = CURSOR_OPT_NO_SCROLL; + } + | K_SCROLL + { + $$ = CURSOR_OPT_SCROLL; + } + ; + +decl_cursor_query : + { + $$ = read_sql_stmt(""); + } + ; + +decl_cursor_args : + { + $$ = NULL; + } + | '(' decl_cursor_arglist ')' + { + PLpgSQL_row *new; + int i; + ListCell *l; + + new = palloc0(sizeof(PLpgSQL_row)); + new->dtype = PLPGSQL_DTYPE_ROW; + new->lineno = plpgsql_location_to_lineno(@1); + new->rowtupdesc = NULL; + new->nfields = list_length($2); + new->fieldnames = palloc(new->nfields * sizeof(char *)); + new->varnos = palloc(new->nfields * sizeof(int)); + + i = 0; + foreach (l, $2) + { + PLpgSQL_variable *arg = (PLpgSQL_variable *) lfirst(l); + new->fieldnames[i] = arg->refname; + new->varnos[i] = arg->dno; + i++; + } + list_free($2); + + plpgsql_adddatum((PLpgSQL_datum *) new); + $$ = (PLpgSQL_datum *) new; + } + ; + +decl_cursor_arglist : decl_cursor_arg + { + $$ = list_make1($1); + } + | decl_cursor_arglist ',' decl_cursor_arg + { + $$ = lappend($1, $3); + } + ; + +decl_cursor_arg : decl_varname decl_datatype + { + $$ = (PLpgSQL_datum *) + plpgsql_build_variable($1.name, $1.lineno, + $2, true); + } + ; + +decl_is_for : K_IS | /* Oracle */ + K_FOR; /* SQL standard */ + +decl_aliasitem : T_WORD + { + PLpgSQL_nsitem *nsi; + + nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false, + $1.ident, NULL, NULL, + NULL); + if (nsi == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("variable \"%s\" does not exist", + $1.ident), + parser_errposition(@1))); + $$ = nsi; + } + | unreserved_keyword + { + PLpgSQL_nsitem *nsi; + + nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false, + $1, NULL, NULL, + NULL); + if (nsi == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("variable \"%s\" does not exist", + $1), + parser_errposition(@1))); + $$ = nsi; + } + | T_CWORD + { + PLpgSQL_nsitem *nsi; + + if (list_length($1.idents) == 2) + nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false, + strVal(linitial($1.idents)), + strVal(lsecond($1.idents)), + NULL, + NULL); + else if (list_length($1.idents) == 3) + nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false, + strVal(linitial($1.idents)), + strVal(lsecond($1.idents)), + strVal(lthird($1.idents)), + NULL); + else + nsi = NULL; + if (nsi == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("variable \"%s\" does not exist", + NameListToString($1.idents)), + parser_errposition(@1))); + $$ = nsi; + } + ; + +decl_varname : T_WORD + { + $$.name = $1.ident; + $$.lineno = plpgsql_location_to_lineno(@1); + /* + * Check to make sure name isn't already declared + * in the current block. + */ + if (plpgsql_ns_lookup(plpgsql_ns_top(), true, + $1.ident, NULL, NULL, + NULL) != NULL) + yyerror("duplicate declaration"); + } + | unreserved_keyword + { + $$.name = pstrdup($1); + $$.lineno = plpgsql_location_to_lineno(@1); + /* + * Check to make sure name isn't already declared + * in the current block. + */ + if (plpgsql_ns_lookup(plpgsql_ns_top(), true, + $1, NULL, NULL, + NULL) != NULL) + yyerror("duplicate declaration"); + } + ; + +decl_const : + { $$ = false; } + | K_CONSTANT + { $$ = true; } + ; + +decl_datatype : + { + /* + * If there's a lookahead token, read_datatype + * should consume it. + */ + $$ = read_datatype(yychar); + yyclearin; + } + ; + +decl_collate : + { $$ = InvalidOid; } + | K_COLLATE T_WORD + { + $$ = get_collation_oid(list_make1(makeString($2.ident)), + false); + } + | K_COLLATE unreserved_keyword + { + $$ = get_collation_oid(list_make1(makeString(pstrdup($2))), + false); + } + | K_COLLATE T_CWORD + { + $$ = get_collation_oid($2.idents, false); + } + ; + +decl_notnull : + { $$ = false; } + | K_NOT K_NULL + { $$ = true; } + ; + +decl_defval : ';' + { $$ = NULL; } + | decl_defkey + { + $$ = read_sql_expression(';', ";"); + } + ; + +decl_defkey : assign_operator + | K_DEFAULT + ; + +assign_operator : '=' /* not documented because it might be removed someday */ + | COLON_EQUALS + ; + +proc_sect : + { $$ = NIL; } + | proc_stmts + { $$ = $1; } + ; + +proc_stmts : proc_stmts proc_stmt + { + if ($2 == NULL) + $$ = $1; + else + $$ = lappend($1, $2); + } + | proc_stmt + { + if ($1 == NULL) + $$ = NIL; + else + $$ = list_make1($1); + } + ; + +proc_stmt : pl_block ';' + { $$ = $1; } + | stmt_assign + { $$ = $1; } + | stmt_if + { $$ = $1; } + | stmt_case + { $$ = $1; } + | stmt_loop + { $$ = $1; } + | stmt_while + { $$ = $1; } + | stmt_for + { $$ = $1; } + | stmt_foreach_a + { $$ = $1; } + | stmt_exit + { $$ = $1; } + | stmt_return + { $$ = $1; } + | stmt_raise + { $$ = $1; } + | stmt_execsql + { $$ = $1; } + | stmt_dynexecute + { $$ = $1; } + | stmt_perform + { $$ = $1; } + | stmt_getdiag + { $$ = $1; } + | stmt_open + { $$ = $1; } + | stmt_fetch + { $$ = $1; } + | stmt_move + { $$ = $1; } + | stmt_close + { $$ = $1; } + | stmt_null + { $$ = $1; } + ; + +stmt_perform : K_PERFORM expr_until_semi + { + PLpgSQL_stmt_perform *new; + + new = palloc0(sizeof(PLpgSQL_stmt_perform)); + new->cmd_type = PLPGSQL_STMT_PERFORM; + new->lineno = plpgsql_location_to_lineno(@1); + new->expr = $2; + + $$ = (PLpgSQL_stmt *)new; + } + ; + +stmt_assign : assign_var assign_operator expr_until_semi + { + PLpgSQL_stmt_assign *new; + + new = palloc0(sizeof(PLpgSQL_stmt_assign)); + new->cmd_type = PLPGSQL_STMT_ASSIGN; + new->lineno = plpgsql_location_to_lineno(@1); + new->varno = $1; + new->expr = $3; + + $$ = (PLpgSQL_stmt *)new; + } + ; + +stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';' + { + PLpgSQL_stmt_getdiag *new; + ListCell *lc; + + new = palloc0(sizeof(PLpgSQL_stmt_getdiag)); + new->cmd_type = PLPGSQL_STMT_GETDIAG; + new->lineno = plpgsql_location_to_lineno(@1); + new->is_stacked = $2; + new->diag_items = $4; + + /* + * Check information items are valid for area option. + */ + foreach(lc, new->diag_items) + { + PLpgSQL_diag_item *ditem = (PLpgSQL_diag_item *) lfirst(lc); + + switch (ditem->kind) + { + /* these fields are disallowed in stacked case */ + case PLPGSQL_GETDIAG_ROW_COUNT: + case PLPGSQL_GETDIAG_RESULT_OID: + if (new->is_stacked) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS", + plpgsql_getdiag_kindname(ditem->kind)), + parser_errposition(@1))); + break; + /* these fields are disallowed in current case */ + case PLPGSQL_GETDIAG_ERROR_CONTEXT: + case PLPGSQL_GETDIAG_ERROR_DETAIL: + case PLPGSQL_GETDIAG_ERROR_HINT: + case PLPGSQL_GETDIAG_RETURNED_SQLSTATE: + case PLPGSQL_GETDIAG_MESSAGE_TEXT: + if (!new->is_stacked) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS", + plpgsql_getdiag_kindname(ditem->kind)), + parser_errposition(@1))); + break; + default: + elog(ERROR, "unrecognized diagnostic item kind: %d", + ditem->kind); + break; + } + } + + $$ = (PLpgSQL_stmt *)new; + } + ; + +getdiag_area_opt : + { + $$ = false; + } + | K_CURRENT + { + $$ = false; + } + | K_STACKED + { + $$ = true; + } + ; + +getdiag_list : getdiag_list ',' getdiag_list_item + { + $$ = lappend($1, $3); + } + | getdiag_list_item + { + $$ = list_make1($1); + } + ; + +getdiag_list_item : getdiag_target assign_operator getdiag_item + { + PLpgSQL_diag_item *new; + + new = palloc(sizeof(PLpgSQL_diag_item)); + new->target = $1; + new->kind = $3; + + $$ = new; + } + ; + +getdiag_item : + { + int tok = yylex(); + + if (tok_is_keyword(tok, &yylval, + K_ROW_COUNT, "row_count")) + $$ = PLPGSQL_GETDIAG_ROW_COUNT; + else if (tok_is_keyword(tok, &yylval, + K_RESULT_OID, "result_oid")) + $$ = PLPGSQL_GETDIAG_RESULT_OID; + else if (tok_is_keyword(tok, &yylval, + K_PG_EXCEPTION_DETAIL, "pg_exception_detail")) + $$ = PLPGSQL_GETDIAG_ERROR_DETAIL; + else if (tok_is_keyword(tok, &yylval, + K_PG_EXCEPTION_HINT, "pg_exception_hint")) + $$ = PLPGSQL_GETDIAG_ERROR_HINT; + else if (tok_is_keyword(tok, &yylval, + K_PG_EXCEPTION_CONTEXT, "pg_exception_context")) + $$ = PLPGSQL_GETDIAG_ERROR_CONTEXT; + else if (tok_is_keyword(tok, &yylval, + K_MESSAGE_TEXT, "message_text")) + $$ = PLPGSQL_GETDIAG_MESSAGE_TEXT; + else if (tok_is_keyword(tok, &yylval, + K_RETURNED_SQLSTATE, "returned_sqlstate")) + $$ = PLPGSQL_GETDIAG_RETURNED_SQLSTATE; + else + yyerror("unrecognized GET DIAGNOSTICS item"); + } + ; + +getdiag_target : T_DATUM + { + check_assignable($1.datum, @1); + if ($1.datum->dtype == PLPGSQL_DTYPE_ROW || + $1.datum->dtype == PLPGSQL_DTYPE_REC) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("\"%s\" is not a scalar variable", + NameOfDatum(&($1))), + parser_errposition(@1))); + $$ = $1.datum->dno; + } + | T_WORD + { + /* just to give a better message than "syntax error" */ + word_is_not_variable(&($1), @1); + } + | T_CWORD + { + /* just to give a better message than "syntax error" */ + cword_is_not_variable(&($1), @1); + } + ; + + +assign_var : T_DATUM + { + check_assignable($1.datum, @1); + $$ = $1.datum->dno; + } + | assign_var '[' expr_until_rightbracket + { + PLpgSQL_arrayelem *new; + + new = palloc0(sizeof(PLpgSQL_arrayelem)); + new->dtype = PLPGSQL_DTYPE_ARRAYELEM; + new->subscript = $3; + new->arrayparentno = $1; + /* initialize cached type data to "not valid" */ + new->parenttypoid = InvalidOid; + + plpgsql_adddatum((PLpgSQL_datum *) new); + + $$ = new->dno; + } + ; + +stmt_if : K_IF expr_until_then proc_sect stmt_elsifs stmt_else K_END K_IF ';' + { + PLpgSQL_stmt_if *new; + + new = palloc0(sizeof(PLpgSQL_stmt_if)); + new->cmd_type = PLPGSQL_STMT_IF; + new->lineno = plpgsql_location_to_lineno(@1); + new->cond = $2; + new->then_body = $3; + new->elsif_list = $4; + new->else_body = $5; + + $$ = (PLpgSQL_stmt *)new; + } + ; + +stmt_elsifs : + { + $$ = NIL; + } + | stmt_elsifs K_ELSIF expr_until_then proc_sect + { + PLpgSQL_if_elsif *new; + + new = palloc0(sizeof(PLpgSQL_if_elsif)); + new->lineno = plpgsql_location_to_lineno(@2); + new->cond = $3; + new->stmts = $4; + + $$ = lappend($1, new); + } + ; + +stmt_else : + { + $$ = NIL; + } + | K_ELSE proc_sect + { + $$ = $2; + } + ; + +stmt_case : K_CASE opt_expr_until_when case_when_list opt_case_else K_END K_CASE ';' + { + $$ = make_case(@1, $2, $3, $4); + } + ; + +opt_expr_until_when : + { + PLpgSQL_expr *expr = NULL; + int tok = yylex(); + + if (tok != K_WHEN) + { + plpgsql_push_back_token(tok); + expr = read_sql_expression(K_WHEN, "WHEN"); + } + plpgsql_push_back_token(K_WHEN); + $$ = expr; + } + ; + +case_when_list : case_when_list case_when + { + $$ = lappend($1, $2); + } + | case_when + { + $$ = list_make1($1); + } + ; + +case_when : K_WHEN expr_until_then proc_sect + { + PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when)); + + new->lineno = plpgsql_location_to_lineno(@1); + new->expr = $2; + new->stmts = $3; + $$ = new; + } + ; + +opt_case_else : + { + $$ = NIL; + } + | K_ELSE proc_sect + { + /* + * proc_sect could return an empty list, but we + * must distinguish that from not having ELSE at all. + * Simplest fix is to return a list with one NULL + * pointer, which make_case() must take care of. + */ + if ($2 != NIL) + $$ = $2; + else + $$ = list_make1(NULL); + } + ; + +stmt_loop : opt_block_label K_LOOP loop_body + { + PLpgSQL_stmt_loop *new; + + new = palloc0(sizeof(PLpgSQL_stmt_loop)); + new->cmd_type = PLPGSQL_STMT_LOOP; + new->lineno = plpgsql_location_to_lineno(@2); + new->label = $1; + new->body = $3.stmts; + + check_labels($1, $3.end_label, $3.end_label_location); + plpgsql_ns_pop(); + + $$ = (PLpgSQL_stmt *)new; + } + ; + +stmt_while : opt_block_label K_WHILE expr_until_loop loop_body + { + PLpgSQL_stmt_while *new; + + new = palloc0(sizeof(PLpgSQL_stmt_while)); + new->cmd_type = PLPGSQL_STMT_WHILE; + new->lineno = plpgsql_location_to_lineno(@2); + new->label = $1; + new->cond = $3; + new->body = $4.stmts; + + check_labels($1, $4.end_label, $4.end_label_location); + plpgsql_ns_pop(); + + $$ = (PLpgSQL_stmt *)new; + } + ; + +stmt_for : opt_block_label K_FOR for_control loop_body + { + /* This runs after we've scanned the loop body */ + if ($3->cmd_type == PLPGSQL_STMT_FORI) + { + PLpgSQL_stmt_fori *new; + + new = (PLpgSQL_stmt_fori *) $3; + new->lineno = plpgsql_location_to_lineno(@2); + new->label = $1; + new->body = $4.stmts; + $$ = (PLpgSQL_stmt *) new; + } + else + { + PLpgSQL_stmt_forq *new; + + Assert($3->cmd_type == PLPGSQL_STMT_FORS || + $3->cmd_type == PLPGSQL_STMT_FORC || + $3->cmd_type == PLPGSQL_STMT_DYNFORS); + /* forq is the common supertype of all three */ + new = (PLpgSQL_stmt_forq *) $3; + new->lineno = plpgsql_location_to_lineno(@2); + new->label = $1; + new->body = $4.stmts; + $$ = (PLpgSQL_stmt *) new; + } + + check_labels($1, $4.end_label, $4.end_label_location); + /* close namespace started in opt_block_label */ + plpgsql_ns_pop(); + } + ; + +for_control : for_variable K_IN + { + int tok = yylex(); + int tokloc = yylloc; + + if (tok == K_EXECUTE) + { + /* EXECUTE means it's a dynamic FOR loop */ + PLpgSQL_stmt_dynfors *new; + PLpgSQL_expr *expr; + int term; + + expr = read_sql_expression2(K_LOOP, K_USING, + "LOOP or USING", + &term); + + new = palloc0(sizeof(PLpgSQL_stmt_dynfors)); + new->cmd_type = PLPGSQL_STMT_DYNFORS; + if ($1.rec) + { + new->rec = $1.rec; + check_assignable((PLpgSQL_datum *) new->rec, @1); + } + else if ($1.row) + { + new->row = $1.row; + check_assignable((PLpgSQL_datum *) new->row, @1); + } + else if ($1.scalar) + { + /* convert single scalar to list */ + new->row = make_scalar_list1($1.name, $1.scalar, + $1.lineno, @1); + /* no need for check_assignable */ + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("loop variable of loop over rows must be a record or row variable or list of scalar variables"), + parser_errposition(@1))); + } + new->query = expr; + + if (term == K_USING) + { + do + { + expr = read_sql_expression2(',', K_LOOP, + ", or LOOP", + &term); + new->params = lappend(new->params, expr); + } while (term == ','); + } + + $$ = (PLpgSQL_stmt *) new; + } + else if (tok == T_DATUM && + yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR && + ((PLpgSQL_var *) yylval.wdatum.datum)->datatype->typoid == REFCURSOROID) + { + /* It's FOR var IN cursor */ + PLpgSQL_stmt_forc *new; + PLpgSQL_var *cursor = (PLpgSQL_var *) yylval.wdatum.datum; + + new = (PLpgSQL_stmt_forc *) palloc0(sizeof(PLpgSQL_stmt_forc)); + new->cmd_type = PLPGSQL_STMT_FORC; + new->curvar = cursor->dno; + + /* Should have had a single variable name */ + if ($1.scalar && $1.row) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cursor FOR loop must have only one target variable"), + parser_errposition(@1))); + + /* can't use an unbound cursor this way */ + if (cursor->cursor_explicit_expr == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cursor FOR loop must use a bound cursor variable"), + parser_errposition(tokloc))); + + /* collect cursor's parameters if any */ + new->argquery = read_cursor_args(cursor, + K_LOOP, + "LOOP"); + + /* create loop's private RECORD variable */ + new->rec = plpgsql_build_record($1.name, + $1.lineno, + true); + + $$ = (PLpgSQL_stmt *) new; + } + else + { + PLpgSQL_expr *expr1; + int expr1loc; + bool reverse = false; + + /* + * We have to distinguish between two + * alternatives: FOR var IN a .. b and FOR + * var IN query. Unfortunately this is + * tricky, since the query in the second + * form needn't start with a SELECT + * keyword. We use the ugly hack of + * looking for two periods after the first + * token. We also check for the REVERSE + * keyword, which means it must be an + * integer loop. + */ + if (tok_is_keyword(tok, &yylval, + K_REVERSE, "reverse")) + reverse = true; + else + plpgsql_push_back_token(tok); + + /* + * Read tokens until we see either a ".." + * or a LOOP. The text we read may not + * necessarily be a well-formed SQL + * statement, so we need to invoke + * read_sql_construct directly. + */ + expr1 = read_sql_construct(DOT_DOT, + K_LOOP, + 0, + "LOOP", + "SELECT ", + true, + false, + true, + &expr1loc, + &tok); + + if (tok == DOT_DOT) + { + /* Saw "..", so it must be an integer loop */ + PLpgSQL_expr *expr2; + PLpgSQL_expr *expr_by; + PLpgSQL_var *fvar; + PLpgSQL_stmt_fori *new; + + /* Check first expression is well-formed */ + check_sql_expr(expr1->query, expr1loc, 7); + + /* Read and check the second one */ + expr2 = read_sql_expression2(K_LOOP, K_BY, + "LOOP", + &tok); + + /* Get the BY clause if any */ + if (tok == K_BY) + expr_by = read_sql_expression(K_LOOP, + "LOOP"); + else + expr_by = NULL; + + /* Should have had a single variable name */ + if ($1.scalar && $1.row) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("integer FOR loop must have only one target variable"), + parser_errposition(@1))); + + /* create loop's private variable */ + fvar = (PLpgSQL_var *) + plpgsql_build_variable($1.name, + $1.lineno, + plpgsql_build_datatype(INT4OID, + -1, + InvalidOid), + true); + + new = palloc0(sizeof(PLpgSQL_stmt_fori)); + new->cmd_type = PLPGSQL_STMT_FORI; + new->var = fvar; + new->reverse = reverse; + new->lower = expr1; + new->upper = expr2; + new->step = expr_by; + + $$ = (PLpgSQL_stmt *) new; + } + else + { + /* + * No "..", so it must be a query loop. We've + * prefixed an extra SELECT to the query text, + * so we need to remove that before performing + * syntax checking. + */ + char *tmp_query; + PLpgSQL_stmt_fors *new; + + if (reverse) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot specify REVERSE in query FOR loop"), + parser_errposition(tokloc))); + + Assert(strncmp(expr1->query, "SELECT ", 7) == 0); + tmp_query = pstrdup(expr1->query + 7); + pfree(expr1->query); + expr1->query = tmp_query; + + check_sql_expr(expr1->query, expr1loc, 0); + + new = palloc0(sizeof(PLpgSQL_stmt_fors)); + new->cmd_type = PLPGSQL_STMT_FORS; + if ($1.rec) + { + new->rec = $1.rec; + check_assignable((PLpgSQL_datum *) new->rec, @1); + } + else if ($1.row) + { + new->row = $1.row; + check_assignable((PLpgSQL_datum *) new->row, @1); + } + else if ($1.scalar) + { + /* convert single scalar to list */ + new->row = make_scalar_list1($1.name, $1.scalar, + $1.lineno, @1); + /* no need for check_assignable */ + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("loop variable of loop over rows must be a record or row variable or list of scalar variables"), + parser_errposition(@1))); + } + + new->query = expr1; + $$ = (PLpgSQL_stmt *) new; + } + } + } + ; + +/* + * Processing the for_variable is tricky because we don't yet know if the + * FOR is an integer FOR loop or a loop over query results. In the former + * case, the variable is just a name that we must instantiate as a loop + * local variable, regardless of any other definition it might have. + * Therefore, we always save the actual identifier into $$.name where it + * can be used for that case. We also save the outer-variable definition, + * if any, because that's what we need for the loop-over-query case. Note + * that we must NOT apply check_assignable() or any other semantic check + * until we know what's what. + * + * However, if we see a comma-separated list of names, we know that it + * can't be an integer FOR loop and so it's OK to check the variables + * immediately. In particular, for T_WORD followed by comma, we should + * complain that the name is not known rather than say it's a syntax error. + * Note that the non-error result of this case sets *both* $$.scalar and + * $$.row; see the for_control production. + */ +for_variable : T_DATUM + { + $$.name = NameOfDatum(&($1)); + $$.lineno = plpgsql_location_to_lineno(@1); + if ($1.datum->dtype == PLPGSQL_DTYPE_ROW) + { + $$.scalar = NULL; + $$.rec = NULL; + $$.row = (PLpgSQL_row *) $1.datum; + } + else if ($1.datum->dtype == PLPGSQL_DTYPE_REC) + { + $$.scalar = NULL; + $$.rec = (PLpgSQL_rec *) $1.datum; + $$.row = NULL; + } + else + { + int tok; + + $$.scalar = $1.datum; + $$.rec = NULL; + $$.row = NULL; + /* check for comma-separated list */ + tok = yylex(); + plpgsql_push_back_token(tok); + if (tok == ',') + $$.row = read_into_scalar_list($$.name, + $$.scalar, + @1); + } + } + | T_WORD + { + int tok; + + $$.name = $1.ident; + $$.lineno = plpgsql_location_to_lineno(@1); + $$.scalar = NULL; + $$.rec = NULL; + $$.row = NULL; + /* check for comma-separated list */ + tok = yylex(); + plpgsql_push_back_token(tok); + if (tok == ',') + word_is_not_variable(&($1), @1); + } + | T_CWORD + { + /* just to give a better message than "syntax error" */ + cword_is_not_variable(&($1), @1); + } + ; + +stmt_foreach_a : opt_block_label K_FOREACH for_variable foreach_slice K_IN K_ARRAY expr_until_loop loop_body + { + PLpgSQL_stmt_foreach_a *new; + + new = palloc0(sizeof(PLpgSQL_stmt_foreach_a)); + new->cmd_type = PLPGSQL_STMT_FOREACH_A; + new->lineno = plpgsql_location_to_lineno(@2); + new->label = $1; + new->slice = $4; + new->expr = $7; + new->body = $8.stmts; + + if ($3.rec) + { + new->varno = $3.rec->dno; + check_assignable((PLpgSQL_datum *) $3.rec, @3); + } + else if ($3.row) + { + new->varno = $3.row->dno; + check_assignable((PLpgSQL_datum *) $3.row, @3); + } + else if ($3.scalar) + { + new->varno = $3.scalar->dno; + check_assignable($3.scalar, @3); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("loop variable of FOREACH must be a known variable or list of variables"), + parser_errposition(@3))); + } + + check_labels($1, $8.end_label, $8.end_label_location); + plpgsql_ns_pop(); + + $$ = (PLpgSQL_stmt *) new; + } + ; + +foreach_slice : + { + $$ = 0; + } + | K_SLICE ICONST + { + $$ = $2; + } + ; + +stmt_exit : exit_type opt_label opt_exitcond + { + PLpgSQL_stmt_exit *new; + + new = palloc0(sizeof(PLpgSQL_stmt_exit)); + new->cmd_type = PLPGSQL_STMT_EXIT; + new->is_exit = $1; + new->lineno = plpgsql_location_to_lineno(@1); + new->label = $2; + new->cond = $3; + + $$ = (PLpgSQL_stmt *)new; + } + ; + +exit_type : K_EXIT + { + $$ = true; + } + | K_CONTINUE + { + $$ = false; + } + ; + +stmt_return : K_RETURN + { + int tok; + + tok = yylex(); + if (tok == 0) + yyerror("unexpected end of function definition"); + + if (tok_is_keyword(tok, &yylval, + K_NEXT, "next")) + { + $$ = make_return_next_stmt(@1); + } + else if (tok_is_keyword(tok, &yylval, + K_QUERY, "query")) + { + $$ = make_return_query_stmt(@1); + } + else + { + plpgsql_push_back_token(tok); + $$ = make_return_stmt(@1); + } + } + ; + +stmt_raise : K_RAISE + { + PLpgSQL_stmt_raise *new; + int tok; + + new = palloc(sizeof(PLpgSQL_stmt_raise)); + + new->cmd_type = PLPGSQL_STMT_RAISE; + new->lineno = plpgsql_location_to_lineno(@1); + new->elog_level = ERROR; /* default */ + new->condname = NULL; + new->message = NULL; + new->params = NIL; + new->options = NIL; + + tok = yylex(); + if (tok == 0) + yyerror("unexpected end of function definition"); + + /* + * We could have just RAISE, meaning to re-throw + * the current error. + */ + if (tok != ';') + { + /* + * First is an optional elog severity level. + */ + if (tok_is_keyword(tok, &yylval, + K_EXCEPTION, "exception")) + { + new->elog_level = ERROR; + tok = yylex(); + } + else if (tok_is_keyword(tok, &yylval, + K_WARNING, "warning")) + { + new->elog_level = WARNING; + tok = yylex(); + } + else if (tok_is_keyword(tok, &yylval, + K_NOTICE, "notice")) + { + new->elog_level = NOTICE; + tok = yylex(); + } + else if (tok_is_keyword(tok, &yylval, + K_INFO, "info")) + { + new->elog_level = INFO; + tok = yylex(); + } + else if (tok_is_keyword(tok, &yylval, + K_LOG, "log")) + { + new->elog_level = LOG; + tok = yylex(); + } + else if (tok_is_keyword(tok, &yylval, + K_DEBUG, "debug")) + { + new->elog_level = DEBUG1; + tok = yylex(); + } + if (tok == 0) + yyerror("unexpected end of function definition"); + + /* + * Next we can have a condition name, or + * equivalently SQLSTATE 'xxxxx', or a string + * literal that is the old-style message format, + * or USING to start the option list immediately. + */ + if (tok == SCONST) + { + /* old style message and parameters */ + new->message = yylval.str; + /* + * We expect either a semi-colon, which + * indicates no parameters, or a comma that + * begins the list of parameter expressions, + * or USING to begin the options list. + */ + tok = yylex(); + if (tok != ',' && tok != ';' && tok != K_USING) + yyerror("syntax error"); + + while (tok == ',') + { + PLpgSQL_expr *expr; + + expr = read_sql_construct(',', ';', K_USING, + ", or ; or USING", + "SELECT ", + true, true, true, + NULL, &tok); + new->params = lappend(new->params, expr); + } + } + else if (tok != K_USING) + { + /* must be condition name or SQLSTATE */ + if (tok_is_keyword(tok, &yylval, + K_SQLSTATE, "sqlstate")) + { + /* 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"); + new->condname = sqlstatestr; + } + else + { + if (tok == T_WORD) + new->condname = yylval.word.ident; + else if (plpgsql_token_is_unreserved_keyword(tok)) + new->condname = pstrdup(yylval.keyword); + else + yyerror("syntax error"); + plpgsql_recognize_err_condition(new->condname, + false); + } + tok = yylex(); + if (tok != ';' && tok != K_USING) + yyerror("syntax error"); + } + + if (tok == K_USING) + new->options = read_raise_options(); + } + + $$ = (PLpgSQL_stmt *)new; + } + ; + +loop_body : proc_sect K_END K_LOOP opt_label ';' + { + $$.stmts = $1; + $$.end_label = $4; + $$.end_label_location = @4; + } + ; + +/* + * T_WORD+T_CWORD match any initial identifier that is not a known plpgsql + * variable. (The composite case is probably a syntax error, but we'll let + * the core parser decide that.) Normally, we should assume that such a + * word is a SQL statement keyword that isn't also a plpgsql keyword. + * However, if the next token is assignment or '[', it can't be a valid + * SQL statement, and what we're probably looking at is an intended variable + * assignment. Give an appropriate complaint for that, instead of letting + * the core parser throw an unhelpful "syntax error". + */ +stmt_execsql : K_INSERT + { + $$ = make_execsql_stmt(K_INSERT, @1); + } + | T_WORD + { + int tok; + + tok = yylex(); + plpgsql_push_back_token(tok); + if (tok == '=' || tok == COLON_EQUALS || tok == '[') + word_is_not_variable(&($1), @1); + $$ = make_execsql_stmt(T_WORD, @1); + } + | T_CWORD + { + int tok; + + tok = yylex(); + plpgsql_push_back_token(tok); + if (tok == '=' || tok == COLON_EQUALS || tok == '[') + cword_is_not_variable(&($1), @1); + $$ = make_execsql_stmt(T_CWORD, @1); + } + ; + +stmt_dynexecute : K_EXECUTE + { + PLpgSQL_stmt_dynexecute *new; + PLpgSQL_expr *expr; + int endtoken; + + expr = read_sql_construct(K_INTO, K_USING, ';', + "INTO or USING or ;", + "SELECT ", + true, true, true, + NULL, &endtoken); + + new = palloc(sizeof(PLpgSQL_stmt_dynexecute)); + new->cmd_type = PLPGSQL_STMT_DYNEXECUTE; + new->lineno = plpgsql_location_to_lineno(@1); + new->query = expr; + new->into = false; + new->strict = false; + new->rec = NULL; + new->row = NULL; + new->params = NIL; + + /* + * We loop to allow the INTO and USING clauses to + * appear in either order, since people easily get + * that wrong. This coding also prevents "INTO foo" + * from getting absorbed into a USING expression, + * which is *really* confusing. + */ + for (;;) + { + if (endtoken == K_INTO) + { + if (new->into) /* multiple INTO */ + yyerror("syntax error"); + new->into = true; + read_into_target(&new->rec, &new->row, &new->strict); + endtoken = yylex(); + } + else if (endtoken == K_USING) + { + if (new->params) /* multiple USING */ + yyerror("syntax error"); + do + { + expr = read_sql_construct(',', ';', K_INTO, + ", or ; or INTO", + "SELECT ", + true, true, true, + NULL, &endtoken); + new->params = lappend(new->params, expr); + } while (endtoken == ','); + } + else if (endtoken == ';') + break; + else + yyerror("syntax error"); + } + + $$ = (PLpgSQL_stmt *)new; + } + ; + + +stmt_open : K_OPEN cursor_variable + { + PLpgSQL_stmt_open *new; + int tok; + + new = palloc0(sizeof(PLpgSQL_stmt_open)); + new->cmd_type = PLPGSQL_STMT_OPEN; + new->lineno = plpgsql_location_to_lineno(@1); + new->curvar = $2->dno; + new->cursor_options = CURSOR_OPT_FAST_PLAN; + + if ($2->cursor_explicit_expr == NULL) + { + /* be nice if we could use opt_scrollable here */ + tok = yylex(); + if (tok_is_keyword(tok, &yylval, + K_NO, "no")) + { + tok = yylex(); + if (tok_is_keyword(tok, &yylval, + K_SCROLL, "scroll")) + { + new->cursor_options |= CURSOR_OPT_NO_SCROLL; + tok = yylex(); + } + } + else if (tok_is_keyword(tok, &yylval, + K_SCROLL, "scroll")) + { + new->cursor_options |= CURSOR_OPT_SCROLL; + tok = yylex(); + } + + if (tok != K_FOR) + yyerror("syntax error, expected \"FOR\""); + + tok = yylex(); + if (tok == K_EXECUTE) + { + int endtoken; + + new->dynquery = + read_sql_expression2(K_USING, ';', + "USING or ;", + &endtoken); + + /* If we found "USING", collect argument(s) */ + if (endtoken == K_USING) + { + PLpgSQL_expr *expr; + + do + { + expr = read_sql_expression2(',', ';', + ", or ;", + &endtoken); + new->params = lappend(new->params, + expr); + } while (endtoken == ','); + } + } + else + { + plpgsql_push_back_token(tok); + new->query = read_sql_stmt(""); + } + } + else + { + /* predefined cursor query, so read args */ + new->argquery = read_cursor_args($2, ';', ";"); + } + + $$ = (PLpgSQL_stmt *)new; + } + ; + +stmt_fetch : K_FETCH opt_fetch_direction cursor_variable K_INTO + { + PLpgSQL_stmt_fetch *fetch = $2; + PLpgSQL_rec *rec; + PLpgSQL_row *row; + + /* We have already parsed everything through the INTO keyword */ + read_into_target(&rec, &row, NULL); + + if (yylex() != ';') + yyerror("syntax error"); + + /* + * We don't allow multiple rows in PL/pgSQL's FETCH + * statement, only in MOVE. + */ + if (fetch->returns_multiple_rows) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("FETCH statement cannot return multiple rows"), + parser_errposition(@1))); + + fetch->lineno = plpgsql_location_to_lineno(@1); + fetch->rec = rec; + fetch->row = row; + fetch->curvar = $3->dno; + fetch->is_move = false; + + $$ = (PLpgSQL_stmt *)fetch; + } + ; + +stmt_move : K_MOVE opt_fetch_direction cursor_variable ';' + { + PLpgSQL_stmt_fetch *fetch = $2; + + fetch->lineno = plpgsql_location_to_lineno(@1); + fetch->curvar = $3->dno; + fetch->is_move = true; + + $$ = (PLpgSQL_stmt *)fetch; + } + ; + +opt_fetch_direction : + { + $$ = read_fetch_direction(); + } + ; + +stmt_close : K_CLOSE cursor_variable ';' + { + PLpgSQL_stmt_close *new; + + new = palloc(sizeof(PLpgSQL_stmt_close)); + new->cmd_type = PLPGSQL_STMT_CLOSE; + new->lineno = plpgsql_location_to_lineno(@1); + new->curvar = $2->dno; + + $$ = (PLpgSQL_stmt *)new; + } + ; + +stmt_null : K_NULL ';' + { + /* We do not bother building a node for NULL */ + $$ = NULL; + } + ; + +cursor_variable : T_DATUM + { + if ($1.datum->dtype != PLPGSQL_DTYPE_VAR) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cursor variable must be a simple variable"), + parser_errposition(@1))); + + if (((PLpgSQL_var *) $1.datum)->datatype->typoid != REFCURSOROID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("variable \"%s\" must be of type cursor or refcursor", + ((PLpgSQL_var *) $1.datum)->refname), + parser_errposition(@1))); + $$ = (PLpgSQL_var *) $1.datum; + } + | T_WORD + { + /* just to give a better message than "syntax error" */ + word_is_not_variable(&($1), @1); + } + | T_CWORD + { + /* just to give a better message than "syntax error" */ + cword_is_not_variable(&($1), @1); + } + ; + +exception_sect : + { $$ = NULL; } + | K_EXCEPTION + { + /* + * We use a mid-rule action to add these + * special variables to the namespace before + * parsing the WHEN clauses themselves. The + * scope of the names extends to the end of the + * current block. + */ + int lineno = plpgsql_location_to_lineno(@1); + PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block)); + PLpgSQL_variable *var; + + var = plpgsql_build_variable("sqlstate", lineno, + plpgsql_build_datatype(TEXTOID, + -1, + plpgsql_curr_compile->fn_input_collation), + true); + ((PLpgSQL_var *) var)->isconst = true; + new->sqlstate_varno = var->dno; + + var = plpgsql_build_variable("sqlerrm", lineno, + plpgsql_build_datatype(TEXTOID, + -1, + plpgsql_curr_compile->fn_input_collation), + true); + ((PLpgSQL_var *) var)->isconst = true; + new->sqlerrm_varno = var->dno; + + $$ = new; + } + proc_exceptions + { + PLpgSQL_exception_block *new = $2; + new->exc_list = $3; + + $$ = new; + } + ; + +proc_exceptions : proc_exceptions proc_exception + { + $$ = lappend($1, $2); + } + | proc_exception + { + $$ = list_make1($1); + } + ; + +proc_exception : K_WHEN proc_conditions K_THEN proc_sect + { + PLpgSQL_exception *new; + + new = palloc0(sizeof(PLpgSQL_exception)); + new->lineno = plpgsql_location_to_lineno(@1); + new->conditions = $2; + new->action = $4; + + $$ = new; + } + ; + +proc_conditions : proc_conditions K_OR proc_condition + { + PLpgSQL_condition *old; + + for (old = $1; old->next != NULL; old = old->next) + /* skip */ ; + old->next = $3; + $$ = $1; + } + | proc_condition + { + $$ = $1; + } + ; + +proc_condition : any_identifier + { + if (strcmp($1, "sqlstate") != 0) + { + $$ = plpgsql_parse_err_condition($1); + } + else + { + PLpgSQL_condition *new; + char *sqlstatestr; + + /* next token should be a string literal */ + 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"); + + new = palloc(sizeof(PLpgSQL_condition)); + new->sqlerrstate = + MAKE_SQLSTATE(sqlstatestr[0], + sqlstatestr[1], + sqlstatestr[2], + sqlstatestr[3], + sqlstatestr[4]); + new->condname = sqlstatestr; + new->next = NULL; + + $$ = new; + } + } + ; + +expr_until_semi : + { $$ = read_sql_expression(';', ";"); } + ; + +expr_until_rightbracket : + { $$ = read_sql_expression(']', "]"); } + ; + +expr_until_then : + { $$ = read_sql_expression(K_THEN, "THEN"); } + ; + +expr_until_loop : + { $$ = read_sql_expression(K_LOOP, "LOOP"); } + ; + +opt_block_label : + { + plpgsql_ns_push(NULL); + $$ = NULL; + } + | LESS_LESS any_identifier GREATER_GREATER + { + plpgsql_ns_push($2); + $$ = $2; + } + ; + +opt_label : + { + $$ = NULL; + } + | any_identifier + { + if (plpgsql_ns_lookup_label(plpgsql_ns_top(), $1) == NULL) + yyerror("label does not exist"); + $$ = $1; + } + ; + +opt_exitcond : ';' + { $$ = NULL; } + | K_WHEN expr_until_semi + { $$ = $2; } + ; + +/* + * need to allow DATUM because scanner will have tried to resolve as variable + */ +any_identifier : T_WORD + { + $$ = $1.ident; + } + | unreserved_keyword + { + $$ = pstrdup($1); + } + | T_DATUM + { + if ($1.ident == NULL) /* composite name not OK */ + yyerror("syntax error"); + $$ = $1.ident; + } + ; + +unreserved_keyword : + K_ABSOLUTE + | K_ALIAS + | K_ARRAY + | K_BACKWARD + | K_CONSTANT + | K_CURRENT + | K_CURSOR + | K_DEBUG + | K_DETAIL + | K_DUMP + | K_ERRCODE + | K_ERROR + | K_FIRST + | K_FORWARD + | K_HINT + | K_INFO + | K_IS + | K_LAST + | K_LOG + | K_MESSAGE + | K_MESSAGE_TEXT + | K_NEXT + | K_NO + | K_NOTICE + | K_OPTION + | K_PG_EXCEPTION_CONTEXT + | K_PG_EXCEPTION_DETAIL + | K_PG_EXCEPTION_HINT + | K_PRIOR + | K_QUERY + | K_RELATIVE + | K_RESULT_OID + | K_RETURNED_SQLSTATE + | K_REVERSE + | K_ROW_COUNT + | K_ROWTYPE + | K_SCROLL + | K_SLICE + | K_SQLSTATE + | K_STACKED + | K_TYPE + | K_USE_COLUMN + | K_USE_VARIABLE + | K_VARIABLE_CONFLICT + | K_WARNING + ; + +%% + +/* + * Check whether a token represents an "unreserved keyword". + * We have various places where we want to recognize a keyword in preference + * to a variable name, but not reserve that keyword in other contexts. + * Hence, this kluge. + */ +static bool +tok_is_keyword(int token, union YYSTYPE *lval, + int kw_token, const char *kw_str) +{ + if (token == kw_token) + { + /* Normal case, was recognized by scanner (no conflicting variable) */ + return true; + } + else if (token == T_DATUM) + { + /* + * It's a variable, so recheck the string name. Note we will not + * match composite names (hence an unreserved word followed by "." + * will not be recognized). + */ + if (!lval->wdatum.quoted && lval->wdatum.ident != NULL && + strcmp(lval->wdatum.ident, kw_str) == 0) + return true; + } + return false; /* not the keyword */ +} + +/* + * Convenience routine to complain when we expected T_DATUM and got T_WORD, + * ie, unrecognized variable. + */ +static void +word_is_not_variable(PLword *word, int location) +{ + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("\"%s\" is not a known variable", + word->ident), + parser_errposition(location))); +} + +/* Same, for a CWORD */ +static void +cword_is_not_variable(PLcword *cword, int location) +{ + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("\"%s\" is not a known variable", + NameListToString(cword->idents)), + parser_errposition(location))); +} + +/* + * Convenience routine to complain when we expected T_DATUM and got + * something else. "tok" must be the current token, since we also + * look at yylval and yylloc. + */ +static void +current_token_is_not_variable(int tok) +{ + if (tok == T_WORD) + word_is_not_variable(&(yylval.word), yylloc); + else if (tok == T_CWORD) + cword_is_not_variable(&(yylval.cword), yylloc); + else + yyerror("syntax error"); +} + +/* Convenience routine to read an expression with one possible terminator */ +static PLpgSQL_expr * +read_sql_expression(int until, const char *expected) +{ + return read_sql_construct(until, 0, 0, expected, + "SELECT ", true, true, true, NULL, NULL); +} + +/* Convenience routine to read an expression with two possible terminators */ +static PLpgSQL_expr * +read_sql_expression2(int until, int until2, const char *expected, + int *endtoken) +{ + return read_sql_construct(until, until2, 0, expected, + "SELECT ", true, true, true, NULL, endtoken); +} + +/* Convenience routine to read a SQL statement that must end with ';' */ +static PLpgSQL_expr * +read_sql_stmt(const char *sqlstart) +{ + return read_sql_construct(';', 0, 0, ";", + sqlstart, false, true, true, NULL, NULL); +} + +/* + * Read a SQL construct and build a PLpgSQL_expr for it. + * + * until: token code for expected terminator + * until2: token code for alternate terminator (pass 0 if none) + * until3: token code for another alternate terminator (pass 0 if none) + * expected: text to use in complaining that terminator was not found + * sqlstart: text to prefix to the accumulated SQL text + * isexpression: whether to say we're reading an "expression" or a "statement" + * valid_sql: whether to check the syntax of the expr (prefixed with sqlstart) + * trim: trim trailing whitespace + * startloc: if not NULL, location of first token is stored at *startloc + * endtoken: if not NULL, ending token is stored at *endtoken + * (this is only interesting if until2 or until3 isn't zero) + */ +static PLpgSQL_expr * +read_sql_construct(int until, + int until2, + int until3, + const char *expected, + const char *sqlstart, + bool isexpression, + bool valid_sql, + bool trim, + int *startloc, + int *endtoken) +{ + int tok; + StringInfoData ds; + IdentifierLookup save_IdentifierLookup; + int startlocation = -1; + int parenlevel = 0; + PLpgSQL_expr *expr; + + initStringInfo(&ds); + appendStringInfoString(&ds, sqlstart); + + /* special lookup mode for identifiers within the SQL text */ + save_IdentifierLookup = plpgsql_IdentifierLookup; + plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR; + + for (;;) + { + tok = yylex(); + if (startlocation < 0) /* remember loc of first token */ + startlocation = yylloc; + if (tok == until && parenlevel == 0) + break; + if (tok == until2 && parenlevel == 0) + break; + if (tok == until3 && parenlevel == 0) + break; + if (tok == '(' || tok == '[') + parenlevel++; + else if (tok == ')' || tok == ']') + { + parenlevel--; + if (parenlevel < 0) + yyerror("mismatched parentheses"); + } + /* + * End of function definition is an error, and we don't expect to + * hit a semicolon either (unless it's the until symbol, in which + * case we should have fallen out above). + */ + if (tok == 0 || tok == ';') + { + if (parenlevel != 0) + yyerror("mismatched parentheses"); + if (isexpression) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("missing \"%s\" at end of SQL expression", + expected), + parser_errposition(yylloc))); + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("missing \"%s\" at end of SQL statement", + expected), + parser_errposition(yylloc))); + } + } + + plpgsql_IdentifierLookup = save_IdentifierLookup; + + if (startloc) + *startloc = startlocation; + if (endtoken) + *endtoken = tok; + + /* give helpful complaint about empty input */ + if (startlocation >= yylloc) + { + if (isexpression) + yyerror("missing expression"); + else + yyerror("missing SQL statement"); + } + + plpgsql_append_source_text(&ds, startlocation, yylloc); + + /* trim any trailing whitespace, for neatness */ + if (trim) + { + while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1])) + ds.data[--ds.len] = '\0'; + } + + expr = palloc0(sizeof(PLpgSQL_expr)); + expr->dtype = PLPGSQL_DTYPE_EXPR; + expr->query = pstrdup(ds.data); + expr->plan = NULL; + expr->paramnos = NULL; + expr->ns = plpgsql_ns_top(); + pfree(ds.data); + + if (valid_sql) + check_sql_expr(expr->query, startlocation, strlen(sqlstart)); + + return expr; +} + +static PLpgSQL_type * +read_datatype(int tok) +{ + StringInfoData ds; + char *type_name; + int startlocation; + PLpgSQL_type *result; + int parenlevel = 0; + + /* Should only be called while parsing DECLARE sections */ + Assert(plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_DECLARE); + + /* Often there will be a lookahead token, but if not, get one */ + if (tok == YYEMPTY) + tok = yylex(); + + startlocation = yylloc; + + /* + * If we have a simple or composite identifier, check for %TYPE + * and %ROWTYPE constructs. + */ + if (tok == T_WORD) + { + char *dtname = yylval.word.ident; + + tok = yylex(); + if (tok == '%') + { + tok = yylex(); + if (tok_is_keyword(tok, &yylval, + K_TYPE, "type")) + { + result = plpgsql_parse_wordtype(dtname); + if (result) + return result; + } + else if (tok_is_keyword(tok, &yylval, + K_ROWTYPE, "rowtype")) + { + result = plpgsql_parse_wordrowtype(dtname); + if (result) + return result; + } + } + } + else if (plpgsql_token_is_unreserved_keyword(tok)) + { + char *dtname = pstrdup(yylval.keyword); + + tok = yylex(); + if (tok == '%') + { + tok = yylex(); + if (tok_is_keyword(tok, &yylval, + K_TYPE, "type")) + { + result = plpgsql_parse_wordtype(dtname); + if (result) + return result; + } + else if (tok_is_keyword(tok, &yylval, + K_ROWTYPE, "rowtype")) + { + result = plpgsql_parse_wordrowtype(dtname); + if (result) + return result; + } + } + } + else if (tok == T_CWORD) + { + List *dtnames = yylval.cword.idents; + + tok = yylex(); + if (tok == '%') + { + tok = yylex(); + if (tok_is_keyword(tok, &yylval, + K_TYPE, "type")) + { + result = plpgsql_parse_cwordtype(dtnames); + if (result) + return result; + } + else if (tok_is_keyword(tok, &yylval, + K_ROWTYPE, "rowtype")) + { + result = plpgsql_parse_cwordrowtype(dtnames); + if (result) + return result; + } + } + } + + while (tok != ';') + { + if (tok == 0) + { + if (parenlevel != 0) + yyerror("mismatched parentheses"); + else + yyerror("incomplete data type declaration"); + } + /* Possible followers for datatype in a declaration */ + if (tok == K_COLLATE || tok == K_NOT || + tok == '=' || tok == COLON_EQUALS || tok == K_DEFAULT) + break; + /* Possible followers for datatype in a cursor_arg list */ + if ((tok == ',' || tok == ')') && parenlevel == 0) + break; + if (tok == '(') + parenlevel++; + else if (tok == ')') + parenlevel--; + + tok = yylex(); + } + + /* set up ds to contain complete typename text */ + initStringInfo(&ds); + plpgsql_append_source_text(&ds, startlocation, yylloc); + type_name = ds.data; + + if (type_name[0] == '\0') + yyerror("missing data type declaration"); + + result = parse_datatype(type_name, startlocation); + + pfree(ds.data); + + plpgsql_push_back_token(tok); + + return result; +} + +static PLpgSQL_stmt * +make_execsql_stmt(int firsttoken, int location) +{ + StringInfoData ds; + IdentifierLookup save_IdentifierLookup; + PLpgSQL_stmt_execsql *execsql; + PLpgSQL_expr *expr; + PLpgSQL_row *row = NULL; + PLpgSQL_rec *rec = NULL; + int tok; + int prev_tok; + bool have_into = false; + bool have_strict = false; + int into_start_loc = -1; + int into_end_loc = -1; + + initStringInfo(&ds); + + /* special lookup mode for identifiers within the SQL text */ + save_IdentifierLookup = plpgsql_IdentifierLookup; + plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR; + + /* + * We have to special-case the sequence INSERT INTO, because we don't want + * that to be taken as an INTO-variables clause. Fortunately, this is the + * only valid use of INTO in a pl/pgsql SQL command, and INTO is already a + * fully reserved word in the main grammar. We have to treat it that way + * anywhere in the string, not only at the start; consider CREATE RULE + * containing an INSERT statement. + */ + tok = firsttoken; + for (;;) + { + prev_tok = tok; + tok = yylex(); + if (have_into && into_end_loc < 0) + into_end_loc = yylloc; /* token after the INTO part */ + if (tok == ';') + break; + if (tok == 0) + yyerror("unexpected end of function definition"); + + if (tok == K_INTO && prev_tok != K_INSERT) + { + if (have_into) + yyerror("INTO specified more than once"); + have_into = true; + into_start_loc = yylloc; + plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL; + read_into_target(&rec, &row, &have_strict); + plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR; + } + } + + plpgsql_IdentifierLookup = save_IdentifierLookup; + + if (have_into) + { + /* + * Insert an appropriate number of spaces corresponding to the + * INTO text, so that locations within the redacted SQL statement + * still line up with those in the original source text. + */ + plpgsql_append_source_text(&ds, location, into_start_loc); + appendStringInfoSpaces(&ds, into_end_loc - into_start_loc); + plpgsql_append_source_text(&ds, into_end_loc, yylloc); + } + else + plpgsql_append_source_text(&ds, location, yylloc); + + /* trim any trailing whitespace, for neatness */ + while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1])) + ds.data[--ds.len] = '\0'; + + expr = palloc0(sizeof(PLpgSQL_expr)); + expr->dtype = PLPGSQL_DTYPE_EXPR; + expr->query = pstrdup(ds.data); + expr->plan = NULL; + expr->paramnos = NULL; + expr->ns = plpgsql_ns_top(); + pfree(ds.data); + + check_sql_expr(expr->query, location, 0); + + execsql = palloc(sizeof(PLpgSQL_stmt_execsql)); + execsql->cmd_type = PLPGSQL_STMT_EXECSQL; + execsql->lineno = plpgsql_location_to_lineno(location); + execsql->sqlstmt = expr; + execsql->into = have_into; + execsql->strict = have_strict; + execsql->rec = rec; + execsql->row = row; + + return (PLpgSQL_stmt *) execsql; +} + + +/* + * Read FETCH or MOVE direction clause (everything through FROM/IN). + */ +static PLpgSQL_stmt_fetch * +read_fetch_direction(void) +{ + PLpgSQL_stmt_fetch *fetch; + int tok; + bool check_FROM = true; + + /* + * We create the PLpgSQL_stmt_fetch struct here, but only fill in + * the fields arising from the optional direction clause + */ + fetch = (PLpgSQL_stmt_fetch *) palloc0(sizeof(PLpgSQL_stmt_fetch)); + fetch->cmd_type = PLPGSQL_STMT_FETCH; + /* set direction defaults: */ + fetch->direction = FETCH_FORWARD; + fetch->how_many = 1; + fetch->expr = NULL; + fetch->returns_multiple_rows = false; + + tok = yylex(); + if (tok == 0) + yyerror("unexpected end of function definition"); + + if (tok_is_keyword(tok, &yylval, + K_NEXT, "next")) + { + /* use defaults */ + } + else if (tok_is_keyword(tok, &yylval, + K_PRIOR, "prior")) + { + fetch->direction = FETCH_BACKWARD; + } + else if (tok_is_keyword(tok, &yylval, + K_FIRST, "first")) + { + fetch->direction = FETCH_ABSOLUTE; + } + else if (tok_is_keyword(tok, &yylval, + K_LAST, "last")) + { + fetch->direction = FETCH_ABSOLUTE; + fetch->how_many = -1; + } + else if (tok_is_keyword(tok, &yylval, + K_ABSOLUTE, "absolute")) + { + fetch->direction = FETCH_ABSOLUTE; + fetch->expr = read_sql_expression2(K_FROM, K_IN, + "FROM or IN", + NULL); + check_FROM = false; + } + else if (tok_is_keyword(tok, &yylval, + K_RELATIVE, "relative")) + { + fetch->direction = FETCH_RELATIVE; + fetch->expr = read_sql_expression2(K_FROM, K_IN, + "FROM or IN", + NULL); + check_FROM = false; + } + else if (tok_is_keyword(tok, &yylval, + K_ALL, "all")) + { + fetch->how_many = FETCH_ALL; + fetch->returns_multiple_rows = true; + } + else if (tok_is_keyword(tok, &yylval, + K_FORWARD, "forward")) + { + complete_direction(fetch, &check_FROM); + } + else if (tok_is_keyword(tok, &yylval, + K_BACKWARD, "backward")) + { + fetch->direction = FETCH_BACKWARD; + complete_direction(fetch, &check_FROM); + } + else if (tok == K_FROM || tok == K_IN) + { + /* empty direction */ + check_FROM = false; + } + else if (tok == T_DATUM) + { + /* Assume there's no direction clause and tok is a cursor name */ + plpgsql_push_back_token(tok); + check_FROM = false; + } + else + { + /* + * Assume it's a count expression with no preceding keyword. + * Note: we allow this syntax because core SQL does, but we don't + * document it because of the ambiguity with the omitted-direction + * case. For instance, "MOVE n IN c" will fail if n is a variable. + * Perhaps this can be improved someday, but it's hardly worth a + * lot of work. + */ + plpgsql_push_back_token(tok); + fetch->expr = read_sql_expression2(K_FROM, K_IN, + "FROM or IN", + NULL); + fetch->returns_multiple_rows = true; + check_FROM = false; + } + + /* check FROM or IN keyword after direction's specification */ + if (check_FROM) + { + tok = yylex(); + if (tok != K_FROM && tok != K_IN) + yyerror("expected FROM or IN"); + } + + return fetch; +} + +/* + * Process remainder of FETCH/MOVE direction after FORWARD or BACKWARD. + * Allows these cases: + * FORWARD expr, FORWARD ALL, FORWARD + * BACKWARD expr, BACKWARD ALL, BACKWARD + */ +static void +complete_direction(PLpgSQL_stmt_fetch *fetch, bool *check_FROM) +{ + int tok; + + tok = yylex(); + if (tok == 0) + yyerror("unexpected end of function definition"); + + if (tok == K_FROM || tok == K_IN) + { + *check_FROM = false; + return; + } + + if (tok == K_ALL) + { + fetch->how_many = FETCH_ALL; + fetch->returns_multiple_rows = true; + *check_FROM = true; + return; + } + + plpgsql_push_back_token(tok); + fetch->expr = read_sql_expression2(K_FROM, K_IN, + "FROM or IN", + NULL); + fetch->returns_multiple_rows = true; + *check_FROM = false; +} + + +static PLpgSQL_stmt * +make_return_stmt(int location) +{ + PLpgSQL_stmt_return *new; + + new = palloc0(sizeof(PLpgSQL_stmt_return)); + new->cmd_type = PLPGSQL_STMT_RETURN; + new->lineno = plpgsql_location_to_lineno(location); + new->expr = NULL; + new->retvarno = -1; + + if (plpgsql_curr_compile->fn_retset) + { + if (yylex() != ';') + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("RETURN cannot have a parameter in function returning set"), + errhint("Use RETURN NEXT or RETURN QUERY."), + parser_errposition(yylloc))); + } + else if (plpgsql_curr_compile->out_param_varno >= 0) + { + if (yylex() != ';') + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("RETURN cannot have a parameter in function with OUT parameters"), + parser_errposition(yylloc))); + new->retvarno = plpgsql_curr_compile->out_param_varno; + } + else if (plpgsql_curr_compile->fn_rettype == VOIDOID) + { + if (yylex() != ';') + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("RETURN cannot have a parameter in function returning void"), + parser_errposition(yylloc))); + } + else if (plpgsql_curr_compile->fn_retistuple) + { + switch (yylex()) + { + case K_NULL: + /* we allow this to support RETURN NULL in triggers */ + break; + + case T_DATUM: + if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW || + yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC) + new->retvarno = yylval.wdatum.datum->dno; + else + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("RETURN must specify a record or row variable in function returning row"), + parser_errposition(yylloc))); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("RETURN must specify a record or row variable in function returning row"), + parser_errposition(yylloc))); + break; + } + if (yylex() != ';') + yyerror("syntax error"); + } + else + { + /* + * Note that a well-formed expression is _required_ here; + * anything else is a compile-time error. + */ + new->expr = read_sql_expression(';', ";"); + } + + return (PLpgSQL_stmt *) new; +} + + +static PLpgSQL_stmt * +make_return_next_stmt(int location) +{ + PLpgSQL_stmt_return_next *new; + + if (!plpgsql_curr_compile->fn_retset) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot use RETURN NEXT in a non-SETOF function"), + parser_errposition(location))); + + new = palloc0(sizeof(PLpgSQL_stmt_return_next)); + new->cmd_type = PLPGSQL_STMT_RETURN_NEXT; + new->lineno = plpgsql_location_to_lineno(location); + new->expr = NULL; + new->retvarno = -1; + + if (plpgsql_curr_compile->out_param_varno >= 0) + { + if (yylex() != ';') + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("RETURN NEXT cannot have a parameter in function with OUT parameters"), + parser_errposition(yylloc))); + new->retvarno = plpgsql_curr_compile->out_param_varno; + } + else if (plpgsql_curr_compile->fn_retistuple) + { + switch (yylex()) + { + case T_DATUM: + if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW || + yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC) + new->retvarno = yylval.wdatum.datum->dno; + else + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("RETURN NEXT must specify a record or row variable in function returning row"), + parser_errposition(yylloc))); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("RETURN NEXT must specify a record or row variable in function returning row"), + parser_errposition(yylloc))); + break; + } + if (yylex() != ';') + yyerror("syntax error"); + } + else + new->expr = read_sql_expression(';', ";"); + + return (PLpgSQL_stmt *) new; +} + + +static PLpgSQL_stmt * +make_return_query_stmt(int location) +{ + PLpgSQL_stmt_return_query *new; + int tok; + + if (!plpgsql_curr_compile->fn_retset) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot use RETURN QUERY in a non-SETOF function"), + parser_errposition(location))); + + new = palloc0(sizeof(PLpgSQL_stmt_return_query)); + new->cmd_type = PLPGSQL_STMT_RETURN_QUERY; + new->lineno = plpgsql_location_to_lineno(location); + + /* check for RETURN QUERY EXECUTE */ + if ((tok = yylex()) != K_EXECUTE) + { + /* ordinary static query */ + plpgsql_push_back_token(tok); + new->query = read_sql_stmt(""); + } + else + { + /* dynamic SQL */ + int term; + + new->dynquery = read_sql_expression2(';', K_USING, "; or USING", + &term); + if (term == K_USING) + { + do + { + PLpgSQL_expr *expr; + + expr = read_sql_expression2(',', ';', ", or ;", &term); + new->params = lappend(new->params, expr); + } while (term == ','); + } + } + + return (PLpgSQL_stmt *) new; +} + + +/* convenience routine to fetch the name of a T_DATUM */ +static char * +NameOfDatum(PLwdatum *wdatum) +{ + if (wdatum->ident) + return wdatum->ident; + Assert(wdatum->idents != NIL); + return NameListToString(wdatum->idents); +} + +static void +check_assignable(PLpgSQL_datum *datum, int location) +{ + switch (datum->dtype) + { + case PLPGSQL_DTYPE_VAR: + if (((PLpgSQL_var *) datum)->isconst) + ereport(ERROR, + (errcode(ERRCODE_ERROR_IN_ASSIGNMENT), + errmsg("\"%s\" is declared CONSTANT", + ((PLpgSQL_var *) datum)->refname), + parser_errposition(location))); + break; + case PLPGSQL_DTYPE_ROW: + /* always assignable? */ + break; + case PLPGSQL_DTYPE_REC: + /* always assignable? What about NEW/OLD? */ + break; + case PLPGSQL_DTYPE_RECFIELD: + /* always assignable? */ + break; + case PLPGSQL_DTYPE_ARRAYELEM: + /* always assignable? */ + break; + default: + elog(ERROR, "unrecognized dtype: %d", datum->dtype); + break; + } +} + +/* + * Read the argument of an INTO clause. On entry, we have just read the + * INTO keyword. + */ +static void +read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict) +{ + int tok; + + /* Set default results */ + *rec = NULL; + *row = NULL; + if (strict) + *strict = false; + + tok = yylex(); + if (strict && tok == K_STRICT) + { + *strict = true; + tok = yylex(); + } + + /* + * Currently, a row or record variable can be the single INTO target, + * but not a member of a multi-target list. So we throw error if there + * is a comma after it, because that probably means the user tried to + * write a multi-target list. If this ever gets generalized, we should + * probably refactor read_into_scalar_list so it handles all cases. + */ + switch (tok) + { + case T_DATUM: + if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW) + { + check_assignable(yylval.wdatum.datum, yylloc); + *row = (PLpgSQL_row *) yylval.wdatum.datum; + + if ((tok = yylex()) == ',') + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("record or row variable cannot be part of multiple-item INTO list"), + parser_errposition(yylloc))); + plpgsql_push_back_token(tok); + } + else if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC) + { + check_assignable(yylval.wdatum.datum, yylloc); + *rec = (PLpgSQL_rec *) yylval.wdatum.datum; + + if ((tok = yylex()) == ',') + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("record or row variable cannot be part of multiple-item INTO list"), + parser_errposition(yylloc))); + plpgsql_push_back_token(tok); + } + else + { + *row = read_into_scalar_list(NameOfDatum(&(yylval.wdatum)), + yylval.wdatum.datum, yylloc); + } + break; + + default: + /* just to give a better message than "syntax error" */ + current_token_is_not_variable(tok); + } +} + +/* + * Given the first datum and name in the INTO list, continue to read + * comma-separated scalar variables until we run out. Then construct + * and return a fake "row" variable that represents the list of + * scalars. + */ +static PLpgSQL_row * +read_into_scalar_list(char *initial_name, + PLpgSQL_datum *initial_datum, + int initial_location) +{ + int nfields; + char *fieldnames[1024]; + int varnos[1024]; + PLpgSQL_row *row; + int tok; + + check_assignable(initial_datum, initial_location); + fieldnames[0] = initial_name; + varnos[0] = initial_datum->dno; + nfields = 1; + + while ((tok = yylex()) == ',') + { + /* Check for array overflow */ + if (nfields >= 1024) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many INTO variables specified"), + parser_errposition(yylloc))); + + tok = yylex(); + switch (tok) + { + case T_DATUM: + check_assignable(yylval.wdatum.datum, yylloc); + if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW || + yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("\"%s\" is not a scalar variable", + NameOfDatum(&(yylval.wdatum))), + parser_errposition(yylloc))); + fieldnames[nfields] = NameOfDatum(&(yylval.wdatum)); + varnos[nfields++] = yylval.wdatum.datum->dno; + break; + + default: + /* just to give a better message than "syntax error" */ + current_token_is_not_variable(tok); + } + } + + /* + * We read an extra, non-comma token from yylex(), so push it + * back onto the input stream + */ + plpgsql_push_back_token(tok); + + row = palloc(sizeof(PLpgSQL_row)); + row->dtype = PLPGSQL_DTYPE_ROW; + row->refname = pstrdup("*internal*"); + row->lineno = plpgsql_location_to_lineno(initial_location); + row->rowtupdesc = NULL; + row->nfields = nfields; + row->fieldnames = palloc(sizeof(char *) * nfields); + row->varnos = palloc(sizeof(int) * nfields); + while (--nfields >= 0) + { + row->fieldnames[nfields] = fieldnames[nfields]; + row->varnos[nfields] = varnos[nfields]; + } + + plpgsql_adddatum((PLpgSQL_datum *)row); + + return row; +} + +/* + * Convert a single scalar into a "row" list. This is exactly + * like read_into_scalar_list except we never consume any input. + * + * Note: lineno could be computed from location, but since callers + * have it at hand already, we may as well pass it in. + */ +static PLpgSQL_row * +make_scalar_list1(char *initial_name, + PLpgSQL_datum *initial_datum, + int lineno, int location) +{ + PLpgSQL_row *row; + + check_assignable(initial_datum, location); + + row = palloc(sizeof(PLpgSQL_row)); + row->dtype = PLPGSQL_DTYPE_ROW; + row->refname = pstrdup("*internal*"); + row->lineno = lineno; + row->rowtupdesc = NULL; + row->nfields = 1; + row->fieldnames = palloc(sizeof(char *)); + row->varnos = palloc(sizeof(int)); + row->fieldnames[0] = initial_name; + row->varnos[0] = initial_datum->dno; + + plpgsql_adddatum((PLpgSQL_datum *)row); + + return row; +} + +/* + * When the PL/pgSQL parser expects to see a SQL statement, it is very + * liberal in what it accepts; for example, we often assume an + * unrecognized keyword is the beginning of a SQL statement. This + * avoids the need to duplicate parts of the SQL grammar in the + * PL/pgSQL grammar, but it means we can accept wildly malformed + * input. To try and catch some of the more obviously invalid input, + * we run the strings we expect to be SQL statements through the main + * SQL parser. + * + * We only invoke the raw parser (not the analyzer); this doesn't do + * any database access and does not check any semantic rules, it just + * checks for basic syntactic correctness. We do this here, rather + * than after parsing has finished, because a malformed SQL statement + * may cause the PL/pgSQL parser to become confused about statement + * borders. So it is best to bail out as early as we can. + * + * It is assumed that "stmt" represents a copy of the function source text + * beginning at offset "location", with leader text of length "leaderlen" + * (typically "SELECT ") prefixed to the source text. We use this assumption + * to transpose any error cursor position back to the function source text. + * If no error cursor is provided, we'll just point at "location". + */ +static void +check_sql_expr(const char *stmt, int location, int leaderlen) +{ + sql_error_callback_arg cbarg; + ErrorContextCallback syntax_errcontext; + MemoryContext oldCxt; + + if (!plpgsql_check_syntax) + return; + + cbarg.location = location; + cbarg.leaderlen = leaderlen; + + syntax_errcontext.callback = plpgsql_sql_error_callback; + syntax_errcontext.arg = &cbarg; + syntax_errcontext.previous = error_context_stack; + error_context_stack = &syntax_errcontext; + + oldCxt = MemoryContextSwitchTo(compile_tmp_cxt); + (void) raw_parser(stmt); + MemoryContextSwitchTo(oldCxt); + + /* Restore former ereport callback */ + error_context_stack = syntax_errcontext.previous; +} + +static void +plpgsql_sql_error_callback(void *arg) +{ + sql_error_callback_arg *cbarg = (sql_error_callback_arg *) arg; + int errpos; + + /* + * First, set up internalerrposition to point to the start of the + * statement text within the function text. Note this converts + * location (a byte offset) to a character number. + */ + parser_errposition(cbarg->location); + + /* + * If the core parser provided an error position, transpose it. + * Note we are dealing with 1-based character numbers at this point. + */ + errpos = geterrposition(); + if (errpos > cbarg->leaderlen) + { + int myerrpos = getinternalerrposition(); + + if (myerrpos > 0) /* safety check */ + internalerrposition(myerrpos + errpos - cbarg->leaderlen - 1); + } + + /* In any case, flush errposition --- we want internalerrpos only */ + errposition(0); +} + +/* + * Parse a SQL datatype name and produce a PLpgSQL_type structure. + * + * The heavy lifting is done elsewhere. Here we are only concerned + * with setting up an errcontext link that will let us give an error + * cursor pointing into the plpgsql function source, if necessary. + * This is handled the same as in check_sql_expr(), and we likewise + * expect that the given string is a copy from the source text. + */ +static PLpgSQL_type * +parse_datatype(const char *string, int location) +{ + Oid type_id; + int32 typmod; + sql_error_callback_arg cbarg; + ErrorContextCallback syntax_errcontext; + + cbarg.location = location; + cbarg.leaderlen = 0; + + syntax_errcontext.callback = plpgsql_sql_error_callback; + syntax_errcontext.arg = &cbarg; + syntax_errcontext.previous = error_context_stack; + error_context_stack = &syntax_errcontext; + + /* Let the main parser try to parse it under standard SQL rules */ + parseTypeString(string, &type_id, &typmod); + + /* Restore former ereport callback */ + error_context_stack = syntax_errcontext.previous; + + /* Okay, build a PLpgSQL_type data structure for it */ + return plpgsql_build_datatype(type_id, typmod, + plpgsql_curr_compile->fn_input_collation); +} + +/* + * Check block starting and ending labels match. + */ +static void +check_labels(const char *start_label, const char *end_label, int end_location) +{ + if (end_label) + { + if (!start_label) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("end label \"%s\" specified for unlabelled block", + end_label), + parser_errposition(end_location))); + + if (strcmp(start_label, end_label) != 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("end label \"%s\" differs from block's label \"%s\"", + end_label, start_label), + parser_errposition(end_location))); + } +} + +/* + * Read the arguments (if any) for a cursor, followed by the until token + * + * If cursor has no args, just swallow the until token and return NULL. + * If it does have args, we expect to see "( arg [, arg ...] )" followed + * by the until token, where arg may be a plain expression, or a named + * parameter assignment of the form argname := expr. Consume all that and + * return a SELECT query that evaluates the expression(s) (without the outer + * parens). + */ +static PLpgSQL_expr * +read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected) +{ + PLpgSQL_expr *expr; + PLpgSQL_row *row; + int tok; + int argc; + char **argv; + StringInfoData ds; + char *sqlstart = "SELECT "; + bool any_named = false; + + tok = yylex(); + if (cursor->cursor_explicit_argrow < 0) + { + /* No arguments expected */ + if (tok == '(') + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cursor \"%s\" has no arguments", + cursor->refname), + parser_errposition(yylloc))); + + if (tok != until) + yyerror("syntax error"); + + return NULL; + } + + /* Else better provide arguments */ + if (tok != '(') + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cursor \"%s\" has arguments", + cursor->refname), + parser_errposition(yylloc))); + + /* + * Read the arguments, one by one. + */ + row = (PLpgSQL_row *) plpgsql_Datums[cursor->cursor_explicit_argrow]; + argv = (char **) palloc0(row->nfields * sizeof(char *)); + + for (argc = 0; argc < row->nfields; argc++) + { + PLpgSQL_expr *item; + int endtoken; + int argpos; + int tok1, + tok2; + int arglocation; + + /* Check if it's a named parameter: "param := value" */ + plpgsql_peek2(&tok1, &tok2, &arglocation, NULL); + if (tok1 == IDENT && tok2 == COLON_EQUALS) + { + char *argname; + IdentifierLookup save_IdentifierLookup; + + /* Read the argument name, ignoring any matching variable */ + save_IdentifierLookup = plpgsql_IdentifierLookup; + plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE; + yylex(); + argname = yylval.str; + plpgsql_IdentifierLookup = save_IdentifierLookup; + + /* Match argument name to cursor arguments */ + for (argpos = 0; argpos < row->nfields; argpos++) + { + if (strcmp(row->fieldnames[argpos], argname) == 0) + break; + } + if (argpos == row->nfields) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cursor \"%s\" has no argument named \"%s\"", + cursor->refname, argname), + parser_errposition(yylloc))); + + /* + * Eat the ":=". We already peeked, so the error should never + * happen. + */ + tok2 = yylex(); + if (tok2 != COLON_EQUALS) + yyerror("syntax error"); + + any_named = true; + } + else + argpos = argc; + + if (argv[argpos] != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("value for parameter \"%s\" of cursor \"%s\" specified more than once", + row->fieldnames[argpos], cursor->refname), + parser_errposition(arglocation))); + + /* + * Read the value expression. To provide the user with meaningful + * parse error positions, we check the syntax immediately, instead of + * checking the final expression that may have the arguments + * reordered. Trailing whitespace must not be trimmed, because + * otherwise input of the form (param -- comment\n, param) would be + * translated into a form where the second parameter is commented + * out. + */ + item = read_sql_construct(',', ')', 0, + ",\" or \")", + sqlstart, + true, true, + false, /* do not trim */ + NULL, &endtoken); + + argv[argpos] = item->query + strlen(sqlstart); + + if (endtoken == ')' && !(argc == row->nfields - 1)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("not enough arguments for cursor \"%s\"", + cursor->refname), + parser_errposition(yylloc))); + + if (endtoken == ',' && (argc == row->nfields - 1)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("too many arguments for cursor \"%s\"", + cursor->refname), + parser_errposition(yylloc))); + } + + /* Make positional argument list */ + initStringInfo(&ds); + appendStringInfoString(&ds, sqlstart); + for (argc = 0; argc < row->nfields; argc++) + { + Assert(argv[argc] != NULL); + + /* + * Because named notation allows permutated argument lists, include + * the parameter name for meaningful runtime errors. + */ + appendStringInfoString(&ds, argv[argc]); + if (any_named) + appendStringInfo(&ds, " AS %s", + quote_identifier(row->fieldnames[argc])); + if (argc < row->nfields - 1) + appendStringInfoString(&ds, ", "); + } + appendStringInfoChar(&ds, ';'); + + expr = palloc0(sizeof(PLpgSQL_expr)); + expr->dtype = PLPGSQL_DTYPE_EXPR; + expr->query = pstrdup(ds.data); + expr->plan = NULL; + expr->paramnos = NULL; + expr->ns = plpgsql_ns_top(); + pfree(ds.data); + + /* Next we'd better find the until token */ + tok = yylex(); + if (tok != until) + yyerror("syntax error"); + + return expr; +} + +/* + * Parse RAISE ... USING options + */ +static List * +read_raise_options(void) +{ + List *result = NIL; + + for (;;) + { + PLpgSQL_raise_option *opt; + int tok; + + if ((tok = yylex()) == 0) + yyerror("unexpected end of function definition"); + + opt = (PLpgSQL_raise_option *) palloc(sizeof(PLpgSQL_raise_option)); + + if (tok_is_keyword(tok, &yylval, + K_ERRCODE, "errcode")) + opt->opt_type = PLPGSQL_RAISEOPTION_ERRCODE; + else if (tok_is_keyword(tok, &yylval, + K_MESSAGE, "message")) + opt->opt_type = PLPGSQL_RAISEOPTION_MESSAGE; + else if (tok_is_keyword(tok, &yylval, + K_DETAIL, "detail")) + opt->opt_type = PLPGSQL_RAISEOPTION_DETAIL; + else if (tok_is_keyword(tok, &yylval, + K_HINT, "hint")) + opt->opt_type = PLPGSQL_RAISEOPTION_HINT; + else + yyerror("unrecognized RAISE statement option"); + + tok = yylex(); + if (tok != '=' && tok != COLON_EQUALS) + yyerror("syntax error, expected \"=\""); + + opt->expr = read_sql_expression2(',', ';', ", or ;", &tok); + + result = lappend(result, opt); + + if (tok == ';') + break; + } + + return result; +} + +/* + * Fix up CASE statement + */ +static PLpgSQL_stmt * +make_case(int location, PLpgSQL_expr *t_expr, + List *case_when_list, List *else_stmts) +{ + PLpgSQL_stmt_case *new; + + new = palloc(sizeof(PLpgSQL_stmt_case)); + new->cmd_type = PLPGSQL_STMT_CASE; + new->lineno = plpgsql_location_to_lineno(location); + new->t_expr = t_expr; + new->t_varno = 0; + new->case_when_list = case_when_list; + new->have_else = (else_stmts != NIL); + /* Get rid of list-with-NULL hack */ + if (list_length(else_stmts) == 1 && linitial(else_stmts) == NULL) + new->else_stmts = NIL; + else + new->else_stmts = else_stmts; + + /* + * When test expression is present, we create a var for it and then + * convert all the WHEN expressions to "VAR IN (original_expression)". + * This is a bit klugy, but okay since we haven't yet done more than + * read the expressions as text. (Note that previous parsing won't + * have complained if the WHEN ... THEN expression contained multiple + * comma-separated values.) + */ + if (t_expr) + { + char varname[32]; + PLpgSQL_var *t_var; + ListCell *l; + + /* use a name unlikely to collide with any user names */ + snprintf(varname, sizeof(varname), "__Case__Variable_%d__", + plpgsql_nDatums); + + /* + * We don't yet know the result datatype of t_expr. Build the + * variable as if it were INT4; we'll fix this at runtime if needed. + */ + t_var = (PLpgSQL_var *) + plpgsql_build_variable(varname, new->lineno, + plpgsql_build_datatype(INT4OID, + -1, + InvalidOid), + true); + new->t_varno = t_var->dno; + + foreach(l, case_when_list) + { + PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l); + PLpgSQL_expr *expr = cwt->expr; + StringInfoData ds; + + /* copy expression query without SELECT keyword (expr->query + 7) */ + Assert(strncmp(expr->query, "SELECT ", 7) == 0); + + /* And do the string hacking */ + initStringInfo(&ds); + + appendStringInfo(&ds, "SELECT \"%s\" IN (%s)", + varname, expr->query + 7); + + pfree(expr->query); + expr->query = pstrdup(ds.data); + /* Adjust expr's namespace to include the case variable */ + expr->ns = plpgsql_ns_top(); + + pfree(ds.data); + } + } + + return (PLpgSQL_stmt *) new; +} diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c index c78527c309..707afbad90 100644 --- a/src/pl/plpgsql/src/pl_scanner.c +++ b/src/pl/plpgsql/src/pl_scanner.c @@ -40,7 +40,7 @@ IdentifierLookup plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL; * * In certain contexts it is desirable to prefer recognizing an unreserved * keyword over recognizing a variable name. Those cases are handled in - * gram.y using tok_is_keyword(). + * pl_gram.y using tok_is_keyword(). * * For the most part, the reserved keywords are those that start a PL/pgSQL * statement (and so would conflict with an assignment to a variable of the @@ -55,7 +55,7 @@ IdentifierLookup plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL; * search is used to locate entries. * * Be careful not to put the same word in both lists. Also be sure that - * gram.y's unreserved_keyword production agrees with the second list. + * pl_gram.y's unreserved_keyword production agrees with the second list. */ static const ScanKeyword reserved_keywords[] = { diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 845e36d20c..a98cc45897 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -107,7 +107,7 @@ sub mkvcbuild my $plpgsql = $solution->AddProject('plpgsql', 'dll', 'PLs', 'src\pl\plpgsql\src'); - $plpgsql->AddFiles('src\pl\plpgsql\src', 'gram.y'); + $plpgsql->AddFiles('src\pl\plpgsql\src', 'pl_gram.y'); $plpgsql->AddReference($postgres); if ($solution->{options}->{perl}) -- cgit v1.2.3