%{ /*------------------------------------------------------------------------- * * pl_gram.y - Parser for the PL/pgSQL procedural language * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/pl/plpgsql/src/pl_gram.y * *------------------------------------------------------------------------- */ #include "postgres.h" #include "catalog/namespace.h" #include "catalog/pg_proc.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" #include "plpgsql.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. */ #define YYMALLOC palloc #define YYFREE pfree typedef struct { int location; } 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, RawParseMode parsemode, bool isexpression, bool valid_sql, 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(void); static PLpgSQL_type *read_datatype(int tok); static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location, PLword *word); 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_variable **target, 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, RawParseMode parseMode, int location); 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); static List *read_raise_options(void); static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %} %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_datum *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 %type expr_until_then expr_until_loop opt_expr_until_when %type opt_exitcond %type cursor_variable %type decl_cursor_arg %type for_variable %type foreach_slice %type for_control %type any_identifier opt_block_label opt_loop_label opt_label %type option_value %type proc_sect 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_assert stmt_execsql %type stmt_dynexecute stmt_for stmt_perform stmt_call stmt_getdiag %type stmt_open stmt_fetch stmt_move stmt_close stmt_null %type stmt_commit stmt_rollback %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_target %type getdiag_item %type opt_scrollable %type opt_fetch_direction %type opt_transaction_chain %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 UIDENT FCONST SCONST USCONST BCONST XCONST Op %token ICONST PARAM %token TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER %token LESS_EQUALS GREATER_EQUALS NOT_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_AND %token K_ARRAY %token K_ASSERT %token K_BACKWARD %token K_BEGIN %token K_BY %token K_CALL %token K_CASE %token K_CHAIN %token K_CLOSE %token K_COLLATE %token K_COLUMN %token K_COLUMN_NAME %token K_COMMIT %token K_CONSTANT %token K_CONSTRAINT %token K_CONSTRAINT_NAME %token K_CONTINUE %token K_CURRENT %token K_CURSOR %token K_DATATYPE %token K_DEBUG %token K_DECLARE %token K_DEFAULT %token K_DETAIL %token K_DIAGNOSTICS %token K_DO %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_IMPORT %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_MERGE %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_CONTEXT %token K_PG_DATATYPE_NAME %token K_PG_EXCEPTION_CONTEXT %token K_PG_EXCEPTION_DETAIL %token K_PG_EXCEPTION_HINT %token K_PG_ROUTINE_OID %token K_PRINT_STRICT_PARAMS %token K_PRIOR %token K_QUERY %token K_RAISE %token K_RELATIVE %token K_RETURN %token K_RETURNED_SQLSTATE %token K_REVERSE %token K_ROLLBACK %token K_ROW_COUNT %token K_ROWTYPE %token K_SCHEMA %token K_SCHEMA_NAME %token K_SCROLL %token K_SLICE %token K_SQLSTATE %token K_STACKED %token K_STRICT %token K_TABLE %token K_TABLE_NAME %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_PRINT_STRICT_PARAMS option_value { if (strcmp($3, "on") == 0) plpgsql_curr_compile->print_strict_params = true; else if (strcmp($3, "off") == 0) plpgsql_curr_compile->print_strict_params = false; else elog(ERROR, "unrecognized print_strict_params option %s", $3); } | '#' 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; } ; option_value : T_WORD { $$ = $1.ident; } | unreserved_keyword { $$ = pstrdup($1); } 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->stmtid = ++plpgsql_curr_compile->nstatements; 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); var->isconst = $2; var->notnull = $5; var->default_val = $6; /* * The combination of NOT NULL without an initializer * can't work, so let's reject it at compile time. */ if (var->notnull && var->default_val == NULL) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("variable \"%s\" must have a default value, since it's declared NOT NULL", var->refname), 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, PLPGSQL_LABEL_OTHER); } decl_cursor_args decl_is_for decl_cursor_query { PLpgSQL_var *new; /* 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, NULL), true); 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->refname = "(unnamed 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); Assert(!arg->isconst); 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"); if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_SHADOWVAR || plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR) { PLpgSQL_nsitem *nsi; nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false, $1.ident, NULL, NULL, NULL); if (nsi != NULL) ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR ? ERROR : WARNING, (errcode(ERRCODE_DUPLICATE_ALIAS), errmsg("variable \"%s\" shadows a previously defined variable", $1.ident), parser_errposition(@1))); } } | 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"); if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_SHADOWVAR || plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR) { PLpgSQL_nsitem *nsi; nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false, $1, NULL, NULL, NULL); if (nsi != NULL) ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR ? ERROR : WARNING, (errcode(ERRCODE_DUPLICATE_ALIAS), errmsg("variable \"%s\" shadows a previously defined variable", $1), parser_errposition(@1))); } } ; decl_const : { $$ = false; } | K_CONSTANT { $$ = true; } ; decl_datatype : { /* * If there's a lookahead token, read_datatype() will * consume it, and then we must tell bison to forget * 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 ; /* * Ada-based PL/SQL uses := for assignment and variable defaults, while * the SQL standard uses equals for these cases and for GET * DIAGNOSTICS, so we support both. FOR and OPEN only support :=. */ assign_operator : '=' | COLON_EQUALS ; proc_sect : { $$ = NIL; } | proc_sect proc_stmt { /* don't bother linking null statements into list */ if ($2 == NULL) $$ = $1; else $$ = lappend($1, $2); } ; 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_assert { $$ = $1; } | stmt_execsql { $$ = $1; } | stmt_dynexecute { $$ = $1; } | stmt_perform { $$ = $1; } | stmt_call { $$ = $1; } | stmt_getdiag { $$ = $1; } | stmt_open { $$ = $1; } | stmt_fetch { $$ = $1; } | stmt_move { $$ = $1; } | stmt_close { $$ = $1; } | stmt_null { $$ = $1; } | stmt_commit { $$ = $1; } | stmt_rollback { $$ = $1; } ; stmt_perform : K_PERFORM { PLpgSQL_stmt_perform *new; int startloc; new = palloc0(sizeof(PLpgSQL_stmt_perform)); new->cmd_type = PLPGSQL_STMT_PERFORM; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; plpgsql_push_back_token(K_PERFORM); /* * Since PERFORM isn't legal SQL, we have to cheat to * the extent of substituting "SELECT" for "PERFORM" * in the parsed text. It does not seem worth * inventing a separate parse mode for this one case. * We can't do syntax-checking until after we make the * substitution. */ new->expr = read_sql_construct(';', 0, 0, ";", RAW_PARSE_DEFAULT, false, false, &startloc, NULL); /* overwrite "perform" ... */ memcpy(new->expr->query, " SELECT", 7); /* left-justify to get rid of the leading space */ memmove(new->expr->query, new->expr->query + 1, strlen(new->expr->query)); /* offset syntax error position to account for that */ check_sql_expr(new->expr->query, new->expr->parseMode, startloc + 1); $$ = (PLpgSQL_stmt *) new; } ; stmt_call : K_CALL { PLpgSQL_stmt_call *new; new = palloc0(sizeof(PLpgSQL_stmt_call)); new->cmd_type = PLPGSQL_STMT_CALL; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; plpgsql_push_back_token(K_CALL); new->expr = read_sql_stmt(); new->is_call = true; /* Remember we may need a procedure resource owner */ plpgsql_curr_compile->requires_procedure_resowner = true; $$ = (PLpgSQL_stmt *) new; } | K_DO { /* use the same structures as for CALL, for simplicity */ PLpgSQL_stmt_call *new; new = palloc0(sizeof(PLpgSQL_stmt_call)); new->cmd_type = PLPGSQL_STMT_CALL; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; plpgsql_push_back_token(K_DO); new->expr = read_sql_stmt(); new->is_call = false; /* Remember we may need a procedure resource owner */ plpgsql_curr_compile->requires_procedure_resowner = true; $$ = (PLpgSQL_stmt *) new; } ; stmt_assign : T_DATUM { PLpgSQL_stmt_assign *new; RawParseMode pmode; /* see how many names identify the datum */ switch ($1.ident ? 1 : list_length($1.idents)) { case 1: pmode = RAW_PARSE_PLPGSQL_ASSIGN1; break; case 2: pmode = RAW_PARSE_PLPGSQL_ASSIGN2; break; case 3: pmode = RAW_PARSE_PLPGSQL_ASSIGN3; break; default: elog(ERROR, "unexpected number of names"); pmode = 0; /* keep compiler quiet */ } check_assignable($1.datum, @1); new = palloc0(sizeof(PLpgSQL_stmt_assign)); new->cmd_type = PLPGSQL_STMT_ASSIGN; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; new->varno = $1.datum->dno; /* Push back the head name to include it in the stmt */ plpgsql_push_back_token(T_DATUM); new->expr = read_sql_construct(';', 0, 0, ";", pmode, false, true, NULL, NULL); $$ = (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->stmtid = ++plpgsql_curr_compile->nstatements; 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_ROUTINE_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_COLUMN_NAME: case PLPGSQL_GETDIAG_CONSTRAINT_NAME: case PLPGSQL_GETDIAG_DATATYPE_NAME: case PLPGSQL_GETDIAG_MESSAGE_TEXT: case PLPGSQL_GETDIAG_TABLE_NAME: case PLPGSQL_GETDIAG_SCHEMA_NAME: 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; /* these fields are allowed in either case */ case PLPGSQL_GETDIAG_CONTEXT: 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->dno; 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_PG_ROUTINE_OID, "pg_routine_oid")) $$ = PLPGSQL_GETDIAG_ROUTINE_OID; else if (tok_is_keyword(tok, &yylval, K_PG_CONTEXT, "pg_context")) $$ = PLPGSQL_GETDIAG_CONTEXT; 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_COLUMN_NAME, "column_name")) $$ = PLPGSQL_GETDIAG_COLUMN_NAME; else if (tok_is_keyword(tok, &yylval, K_CONSTRAINT_NAME, "constraint_name")) $$ = PLPGSQL_GETDIAG_CONSTRAINT_NAME; else if (tok_is_keyword(tok, &yylval, K_PG_DATATYPE_NAME, "pg_datatype_name")) $$ = PLPGSQL_GETDIAG_DATATYPE_NAME; else if (tok_is_keyword(tok, &yylval, K_MESSAGE_TEXT, "message_text")) $$ = PLPGSQL_GETDIAG_MESSAGE_TEXT; else if (tok_is_keyword(tok, &yylval, K_TABLE_NAME, "table_name")) $$ = PLPGSQL_GETDIAG_TABLE_NAME; else if (tok_is_keyword(tok, &yylval, K_SCHEMA_NAME, "schema_name")) $$ = PLPGSQL_GETDIAG_SCHEMA_NAME; 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 { /* * In principle we should support a getdiag_target * that is an array element, but for now we don't, so * just throw an error if next token is '['. */ if ($1.datum->dtype == PLPGSQL_DTYPE_ROW || $1.datum->dtype == PLPGSQL_DTYPE_REC || plpgsql_peek() == '[') ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("\"%s\" is not a scalar variable", NameOfDatum(&($1))), parser_errposition(@1))); check_assignable($1.datum, @1); $$ = $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); } ; 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->stmtid = ++plpgsql_curr_compile->nstatements; 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_loop_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->stmtid = ++plpgsql_curr_compile->nstatements; 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_loop_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->stmtid = ++plpgsql_curr_compile->nstatements; 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_loop_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_loop_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; new->stmtid = ++plpgsql_curr_compile->nstatements; if ($1.row) { new->var = (PLpgSQL_variable *) $1.row; check_assignable($1.row, @1); } else if ($1.scalar) { /* convert single scalar to list */ new->var = (PLpgSQL_variable *) make_scalar_list1($1.name, $1.scalar, $1.lineno, @1); /* make_scalar_list1 did check_assignable */ } else { ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("loop variable of loop over rows must be a record 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->stmtid = ++plpgsql_curr_compile->nstatements; 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); /* create loop's private RECORD variable */ new->var = (PLpgSQL_variable *) plpgsql_build_record($1.name, $1.lineno, NULL, RECORDOID, 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 be either * an expression or a whole SQL statement, so * we need to invoke read_sql_construct directly, * and tell it not to check syntax yet. */ expr1 = read_sql_construct(DOT_DOT, K_LOOP, 0, "LOOP", RAW_PARSE_DEFAULT, true, false, &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; /* * Relabel first expression as an expression; * then we can check its syntax. */ expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR; check_sql_expr(expr1->query, expr1->parseMode, expr1loc); /* 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, NULL), true); new = palloc0(sizeof(PLpgSQL_stmt_fori)); new->cmd_type = PLPGSQL_STMT_FORI; new->stmtid = ++plpgsql_curr_compile->nstatements; 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. */ PLpgSQL_stmt_fors *new; if (reverse) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("cannot specify REVERSE in query FOR loop"), parser_errposition(tokloc))); /* Check syntax as a regular query */ check_sql_expr(expr1->query, expr1->parseMode, expr1loc); new = palloc0(sizeof(PLpgSQL_stmt_fors)); new->cmd_type = PLPGSQL_STMT_FORS; new->stmtid = ++plpgsql_curr_compile->nstatements; if ($1.row) { new->var = (PLpgSQL_variable *) $1.row; check_assignable($1.row, @1); } else if ($1.scalar) { /* convert single scalar to list */ new->var = (PLpgSQL_variable *) make_scalar_list1($1.name, $1.scalar, $1.lineno, @1); /* make_scalar_list1 did check_assignable */ } else { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("loop variable of loop over rows must be a record 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 || $1.datum->dtype == PLPGSQL_DTYPE_REC) { $$.scalar = NULL; $$.row = $1.datum; } else { int tok; $$.scalar = $1.datum; $$.row = NULL; /* check for comma-separated list */ tok = yylex(); plpgsql_push_back_token(tok); if (tok == ',') $$.row = (PLpgSQL_datum *) read_into_scalar_list($$.name, $$.scalar, @1); } } | T_WORD { int tok; $$.name = $1.ident; $$.lineno = plpgsql_location_to_lineno(@1); $$.scalar = 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_loop_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->stmtid = ++plpgsql_curr_compile->nstatements; new->label = $1; new->slice = $4; new->expr = $7; new->body = $8.stmts; if ($3.row) { new->varno = $3.row->dno; check_assignable($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->stmtid = ++plpgsql_curr_compile->nstatements; new->is_exit = $1; new->lineno = plpgsql_location_to_lineno(@1); new->label = $2; new->cond = $3; if ($2) { /* We have a label, so verify it exists */ PLpgSQL_nsitem *label; label = plpgsql_ns_lookup_label(plpgsql_ns_top(), $2); if (label == NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("there is no label \"%s\" " "attached to any block or loop enclosing this statement", $2), parser_errposition(@2))); /* CONTINUE only allows loop labels */ if (label->itemno != PLPGSQL_LABEL_LOOP && !new->is_exit) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("block label \"%s\" cannot be used in CONTINUE", $2), parser_errposition(@2))); } else { /* * No label, so make sure there is some loop (an * unlabeled EXIT does not match a block, so this * is the same test for both EXIT and CONTINUE) */ if (plpgsql_ns_find_nearest_loop(plpgsql_ns_top()) == NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), new->is_exit ? errmsg("EXIT cannot be used outside a loop, unless it has a label") : errmsg("CONTINUE cannot be used outside a loop"), parser_errposition(@1))); } $$ = (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->stmtid = ++plpgsql_curr_compile->nstatements; 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", RAW_PARSE_PLPGSQL_EXPR, 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(); } check_raise_parameters(new); $$ = (PLpgSQL_stmt *) new; } ; stmt_assert : K_ASSERT { PLpgSQL_stmt_assert *new; int tok; new = palloc(sizeof(PLpgSQL_stmt_assert)); new->cmd_type = PLPGSQL_STMT_ASSERT; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; new->cond = read_sql_expression2(',', ';', ", or ;", &tok); if (tok == ',') new->message = read_sql_expression(';', ";"); else new->message = NULL; $$ = (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 '[' 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_IMPORT { $$ = make_execsql_stmt(K_IMPORT, @1, NULL); } | K_INSERT { $$ = make_execsql_stmt(K_INSERT, @1, NULL); } | K_MERGE { $$ = make_execsql_stmt(K_MERGE, @1, NULL); } | T_WORD { int tok; tok = yylex(); plpgsql_push_back_token(tok); if (tok == '=' || tok == COLON_EQUALS || tok == '[' || tok == '.') word_is_not_variable(&($1), @1); $$ = make_execsql_stmt(T_WORD, @1, &($1)); } | T_CWORD { int tok; tok = yylex(); plpgsql_push_back_token(tok); if (tok == '=' || tok == COLON_EQUALS || tok == '[' || tok == '.') cword_is_not_variable(&($1), @1); $$ = make_execsql_stmt(T_CWORD, @1, NULL); } ; 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 ;", RAW_PARSE_PLPGSQL_EXPR, true, true, NULL, &endtoken); new = palloc(sizeof(PLpgSQL_stmt_dynexecute)); new->cmd_type = PLPGSQL_STMT_DYNEXECUTE; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; new->query = expr; new->into = false; new->strict = false; new->target = 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->target, &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", RAW_PARSE_PLPGSQL_EXPR, 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->stmtid = ++plpgsql_curr_compile->nstatements; 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_variable *target; /* We have already parsed everything through the INTO keyword */ read_into_target(&target, 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->target = target; 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->stmtid = ++plpgsql_curr_compile->nstatements; new->curvar = $2->dno; $$ = (PLpgSQL_stmt *) new; } ; stmt_null : K_NULL ';' { /* We do not bother building a node for NULL */ $$ = NULL; } ; stmt_commit : K_COMMIT opt_transaction_chain ';' { PLpgSQL_stmt_commit *new; new = palloc(sizeof(PLpgSQL_stmt_commit)); new->cmd_type = PLPGSQL_STMT_COMMIT; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; new->chain = $2; $$ = (PLpgSQL_stmt *) new; } ; stmt_rollback : K_ROLLBACK opt_transaction_chain ';' { PLpgSQL_stmt_rollback *new; new = palloc(sizeof(PLpgSQL_stmt_rollback)); new->cmd_type = PLPGSQL_STMT_ROLLBACK; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; new->chain = $2; $$ = (PLpgSQL_stmt *) new; } ; opt_transaction_chain: K_AND K_CHAIN { $$ = true; } | K_AND K_NO K_CHAIN { $$ = false; } | /* EMPTY */ { $$ = false; } ; cursor_variable : T_DATUM { /* * In principle we should support a cursor_variable * that is an array element, but for now we don't, so * just throw an error if next token is '['. */ if ($1.datum->dtype != PLPGSQL_DTYPE_VAR || plpgsql_peek() == '[') 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, NULL), true); 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, NULL), true); 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_then : { $$ = read_sql_expression(K_THEN, "THEN"); } ; expr_until_loop : { $$ = read_sql_expression(K_LOOP, "LOOP"); } ; opt_block_label : { plpgsql_ns_push(NULL, PLPGSQL_LABEL_BLOCK); $$ = NULL; } | LESS_LESS any_identifier GREATER_GREATER { plpgsql_ns_push($2, PLPGSQL_LABEL_BLOCK); $$ = $2; } ; opt_loop_label : { plpgsql_ns_push(NULL, PLPGSQL_LABEL_LOOP); $$ = NULL; } | LESS_LESS any_identifier GREATER_GREATER { plpgsql_ns_push($2, PLPGSQL_LABEL_LOOP); $$ = $2; } ; opt_label : { $$ = NULL; } | any_identifier { /* label validity will be checked by outer production */ $$ = $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_AND | K_ARRAY | K_ASSERT | K_BACKWARD | K_CALL | K_CHAIN | K_CLOSE | K_COLLATE | K_COLUMN | K_COLUMN_NAME | K_COMMIT | K_CONSTANT | K_CONSTRAINT | K_CONSTRAINT_NAME | K_CONTINUE | K_CURRENT | K_CURSOR | K_DATATYPE | K_DEBUG | K_DEFAULT | K_DETAIL | K_DIAGNOSTICS | K_DO | K_DUMP | K_ELSIF | K_ERRCODE | K_ERROR | K_EXCEPTION | K_EXIT | K_FETCH | K_FIRST | K_FORWARD | K_GET | K_HINT | K_IMPORT | K_INFO | K_INSERT | K_IS | K_LAST | K_LOG | K_MERGE | K_MESSAGE | K_MESSAGE_TEXT | K_MOVE | K_NEXT | K_NO | K_NOTICE | K_OPEN | K_OPTION | K_PERFORM | K_PG_CONTEXT | K_PG_DATATYPE_NAME | K_PG_EXCEPTION_CONTEXT | K_PG_EXCEPTION_DETAIL | K_PG_EXCEPTION_HINT | K_PG_ROUTINE_OID | K_PRINT_STRICT_PARAMS | K_PRIOR | K_QUERY | K_RAISE | K_RELATIVE | K_RETURN | K_RETURNED_SQLSTATE | K_REVERSE | K_ROLLBACK | K_ROW_COUNT | K_ROWTYPE | K_SCHEMA | K_SCHEMA_NAME | K_SCROLL | K_SLICE | K_SQLSTATE | K_STACKED | K_TABLE | K_TABLE_NAME | 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, RAW_PARSE_PLPGSQL_EXPR, 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, RAW_PARSE_PLPGSQL_EXPR, true, true, NULL, endtoken); } /* Convenience routine to read a SQL statement that must end with ';' */ static PLpgSQL_expr * read_sql_stmt(void) { return read_sql_construct(';', 0, 0, ";", RAW_PARSE_DEFAULT, false, 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 * parsemode: raw_parser() mode to use * isexpression: whether to say we're reading an "expression" or a "statement" * valid_sql: whether to check the syntax of the expr * 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, RawParseMode parsemode, bool isexpression, bool valid_sql, int *startloc, int *endtoken) { int tok; StringInfoData ds; IdentifierLookup save_IdentifierLookup; int startlocation = -1; int endlocation = -1; int parenlevel = 0; PLpgSQL_expr *expr; initStringInfo(&ds); /* 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))); } /* Remember end+1 location of last accepted token */ endlocation = yylloc + plpgsql_token_length(); } plpgsql_IdentifierLookup = save_IdentifierLookup; if (startloc) *startloc = startlocation; if (endtoken) *endtoken = tok; /* give helpful complaint about empty input */ if (startlocation >= endlocation) { if (isexpression) yyerror("missing expression"); else yyerror("missing SQL statement"); } /* * We save only the text from startlocation to endlocation-1. This * suppresses the "until" token as well as any whitespace or comments * following the last accepted token. (We used to strip such trailing * whitespace by hand, but that causes problems if there's a "-- comment" * in front of said whitespace.) */ plpgsql_append_source_text(&ds, startlocation, endlocation); expr = palloc0(sizeof(PLpgSQL_expr)); expr->query = pstrdup(ds.data); expr->parseMode = parsemode; expr->plan = NULL; expr->paramnos = NULL; expr->target_param = -1; expr->ns = plpgsql_ns_top(); pfree(ds.data); if (valid_sql) check_sql_expr(expr->query, expr->parseMode, startlocation); return expr; } /* * Read a datatype declaration, consuming the current lookahead token if any. * Returns a PLpgSQL_type struct. */ static PLpgSQL_type * read_datatype(int tok) { StringInfoData ds; char *type_name; int startlocation; PLpgSQL_type *result = NULL; 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(); /* The current token is the start of what we'll pass to parse_datatype */ 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); else if (tok_is_keyword(tok, &yylval, K_ROWTYPE, "rowtype")) result = plpgsql_parse_wordrowtype(dtname); } } 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); else if (tok_is_keyword(tok, &yylval, K_ROWTYPE, "rowtype")) result = plpgsql_parse_wordrowtype(dtname); } } 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); else if (tok_is_keyword(tok, &yylval, K_ROWTYPE, "rowtype")) result = plpgsql_parse_cwordrowtype(dtnames); } } /* * If we recognized a %TYPE or %ROWTYPE construct, see if it is followed * by array decoration: [ ARRAY ] [ '[' [ iconst ] ']' [ ... ] ] * * Like the core parser, we ignore the specific numbers and sizes of * dimensions; arrays of different dimensionality are still the same type * in Postgres. */ if (result) { bool is_array = false; tok = yylex(); if (tok_is_keyword(tok, &yylval, K_ARRAY, "array")) { is_array = true; tok = yylex(); } while (tok == '[') { is_array = true; tok = yylex(); if (tok == ICONST) tok = yylex(); if (tok != ']') yyerror("syntax error, expected \"]\""); tok = yylex(); } plpgsql_push_back_token(tok); if (is_array) result = plpgsql_build_datatype_arrayof(result); return result; } /* * Not %TYPE or %ROWTYPE, so scan to the end of the datatype declaration, * which could include typmod or array decoration. We are not very picky * here, instead relying on parse_datatype to complain about garbage. But * we must count parens to handle typmods within cursor_arg correctly. */ 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; } /* * Read a generic SQL statement. We have already read its first token; * firsttoken is that token's code and location its starting location. * If firsttoken == T_WORD, pass its yylval value as "word", else pass NULL. */ static PLpgSQL_stmt * make_execsql_stmt(int firsttoken, int location, PLword *word) { StringInfoData ds; IdentifierLookup save_IdentifierLookup; PLpgSQL_stmt_execsql *execsql; PLpgSQL_expr *expr; PLpgSQL_variable *target = NULL; int tok; int prev_tok; bool have_into = false; bool have_strict = false; int into_start_loc = -1; int into_end_loc = -1; int paren_depth = 0; int begin_depth = 0; bool in_routine_definition = false; int token_count = 0; char tokens[4]; /* records the first few tokens */ initStringInfo(&ds); memset(tokens, 0, sizeof(tokens)); /* special lookup mode for identifiers within the SQL text */ save_IdentifierLookup = plpgsql_IdentifierLookup; plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR; /* * Scan to the end of the SQL command. Identify any INTO-variables * clause lurking within it, and parse that via read_into_target(). * * The end of the statement is defined by a semicolon ... except that * semicolons within parentheses or BEGIN/END blocks don't terminate a * statement. We follow psql's lead in not recognizing BEGIN/END except * after CREATE [OR REPLACE] {FUNCTION|PROCEDURE}. END can also appear * within a CASE construct, so we treat CASE/END like BEGIN/END. * * Because INTO is sometimes used in the main SQL grammar, we have to be * careful not to take any such usage of INTO as a PL/pgSQL INTO clause. * There are currently three such cases: * * 1. SELECT ... INTO. We don't care, we just override that with the * PL/pgSQL definition. * * 2. INSERT INTO. This is relatively easy to recognize since the words * must appear adjacently; but we can't assume INSERT starts the command, * because it can appear in CREATE RULE or WITH. Unfortunately, INSERT is * *not* fully reserved, so that means there is a chance of a false match; * but it's not very likely. * * 3. IMPORT FOREIGN SCHEMA ... INTO. This is not allowed in CREATE RULE * or WITH, so we just check for IMPORT as the command's first token. * (If IMPORT FOREIGN SCHEMA returned data someone might wish to capture * with an INTO-variables clause, we'd have to work much harder here.) * * Fortunately, INTO is a fully reserved word in the main grammar, so * at least we need not worry about it appearing as an identifier. * * Any future additional uses of INTO in the main grammar will doubtless * break this logic again ... beware! */ tok = firsttoken; if (tok == T_WORD && strcmp(word->ident, "create") == 0) tokens[token_count] = 'c'; token_count++; for (;;) { prev_tok = tok; tok = yylex(); if (have_into && into_end_loc < 0) into_end_loc = yylloc; /* token after the INTO part */ /* Detect CREATE [OR REPLACE] {FUNCTION|PROCEDURE} */ if (tokens[0] == 'c' && token_count < sizeof(tokens)) { if (tok == K_OR) tokens[token_count] = 'o'; else if (tok == T_WORD && strcmp(yylval.word.ident, "replace") == 0) tokens[token_count] = 'r'; else if (tok == T_WORD && strcmp(yylval.word.ident, "function") == 0) tokens[token_count] = 'f'; else if (tok == T_WORD && strcmp(yylval.word.ident, "procedure") == 0) tokens[token_count] = 'f'; /* treat same as "function" */ if (tokens[1] == 'f' || (tokens[1] == 'o' && tokens[2] == 'r' && tokens[3] == 'f')) in_routine_definition = true; token_count++; } /* Track paren nesting (needed for CREATE RULE syntax) */ if (tok == '(') paren_depth++; else if (tok == ')' && paren_depth > 0) paren_depth--; /* We need track BEGIN/END nesting only in a routine definition */ if (in_routine_definition && paren_depth == 0) { if (tok == K_BEGIN || tok == K_CASE) begin_depth++; else if (tok == K_END && begin_depth > 0) begin_depth--; } /* Command-ending semicolon? */ if (tok == ';' && paren_depth == 0 && begin_depth == 0) break; if (tok == 0) yyerror("unexpected end of function definition"); if (tok == K_INTO) { if (prev_tok == K_INSERT) continue; /* INSERT INTO is not an INTO-target */ if (prev_tok == K_MERGE) continue; /* MERGE INTO is not an INTO-target */ if (firsttoken == K_IMPORT) continue; /* IMPORT ... INTO is not an INTO-target */ if (have_into) yyerror("INTO specified more than once"); have_into = true; into_start_loc = yylloc; plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL; read_into_target(&target, &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->query = pstrdup(ds.data); expr->parseMode = RAW_PARSE_DEFAULT; expr->plan = NULL; expr->paramnos = NULL; expr->target_param = -1; expr->ns = plpgsql_ns_top(); pfree(ds.data); check_sql_expr(expr->query, expr->parseMode, location); execsql = palloc0(sizeof(PLpgSQL_stmt_execsql)); execsql->cmd_type = PLPGSQL_STMT_EXECSQL; execsql->lineno = plpgsql_location_to_lineno(location); execsql->stmtid = ++plpgsql_curr_compile->nstatements; execsql->sqlstmt = expr; execsql->into = have_into; execsql->strict = have_strict; execsql->target = target; 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; fetch->stmtid = ++plpgsql_curr_compile->nstatements; /* 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->stmtid = ++plpgsql_curr_compile->nstatements; 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->fn_rettype == VOIDOID) { if (yylex() != ';') { if (plpgsql_curr_compile->fn_prokind == PROKIND_PROCEDURE) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("RETURN cannot have a parameter in a procedure"), parser_errposition(yylloc))); else ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("RETURN cannot have a parameter in function returning void"), 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 { /* * We want to special-case simple variable references for efficiency. * So peek ahead to see if that's what we have. */ int tok = yylex(); if (tok == T_DATUM && plpgsql_peek() == ';' && (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR || yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE || yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW || yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)) { new->retvarno = yylval.wdatum.datum->dno; /* eat the semicolon token that we only peeked at above */ tok = yylex(); Assert(tok == ';'); } else { /* * Not (just) a variable name, so treat as expression. * * Note that a well-formed expression is _required_ here; * anything else is a compile-time error. */ plpgsql_push_back_token(tok); 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->stmtid = ++plpgsql_curr_compile->nstatements; 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 { /* * We want to special-case simple variable references for efficiency. * So peek ahead to see if that's what we have. */ int tok = yylex(); if (tok == T_DATUM && plpgsql_peek() == ';' && (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR || yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE || yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW || yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)) { new->retvarno = yylval.wdatum.datum->dno; /* eat the semicolon token that we only peeked at above */ tok = yylex(); Assert(tok == ';'); } else { /* * Not (just) a variable name, so treat as expression. * * Note that a well-formed expression is _required_ here; * anything else is a compile-time error. */ plpgsql_push_back_token(tok); 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); new->stmtid = ++plpgsql_curr_compile->nstatements; /* 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: case PLPGSQL_DTYPE_PROMISE: case PLPGSQL_DTYPE_REC: if (((PLpgSQL_variable *) datum)->isconst) ereport(ERROR, (errcode(ERRCODE_ERROR_IN_ASSIGNMENT), errmsg("variable \"%s\" is declared CONSTANT", ((PLpgSQL_variable *) datum)->refname), parser_errposition(location))); break; case PLPGSQL_DTYPE_ROW: /* always assignable; member vars were checked at compile time */ break; case PLPGSQL_DTYPE_RECFIELD: /* assignable if parent record is */ check_assignable(plpgsql_Datums[((PLpgSQL_recfield *) datum)->recparentno], location); 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_variable **target, bool *strict) { int tok; /* Set default results */ *target = 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 || yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC) { check_assignable(yylval.wdatum.datum, yylloc); *target = (PLpgSQL_variable *) yylval.wdatum.datum; if ((tok = yylex()) == ',') ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("record variable cannot be part of multiple-item INTO list"), parser_errposition(yylloc))); plpgsql_push_back_token(tok); } else { *target = (PLpgSQL_variable *) 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 = palloc0(sizeof(PLpgSQL_row)); row->dtype = PLPGSQL_DTYPE_ROW; row->refname = "(unnamed row)"; 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 = palloc0(sizeof(PLpgSQL_row)); row->dtype = PLPGSQL_DTYPE_ROW; row->refname = "(unnamed row)"; 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". 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, RawParseMode parseMode, int location) { sql_error_callback_arg cbarg; ErrorContextCallback syntax_errcontext; MemoryContext oldCxt; if (!plpgsql_check_syntax) return; cbarg.location = location; syntax_errcontext.callback = plpgsql_sql_error_callback; syntax_errcontext.arg = &cbarg; syntax_errcontext.previous = error_context_stack; error_context_stack = &syntax_errcontext; oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt); (void) raw_parser(stmt, parseMode); 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 > 0) { int myerrpos = getinternalerrposition(); if (myerrpos > 0) /* safety check */ internalerrposition(myerrpos + errpos - 1); } /* In any case, flush errposition --- we want internalerrposition 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) { TypeName *typeName; Oid type_id; int32 typmod; sql_error_callback_arg cbarg; ErrorContextCallback syntax_errcontext; cbarg.location = location; 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 */ typeName = typeStringToTypeName(string, NULL); typenameTypeIdAndMod(NULL, typeName, &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, typeName); } /* * 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 unlabeled 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) { PLpgSQL_expr *expr; PLpgSQL_row *row; int tok; int argc; char **argv; StringInfoData ds; 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. */ item = read_sql_construct(',', ')', 0, ",\" or \")", RAW_PARSE_PLPGSQL_EXPR, true, true, NULL, &endtoken); argv[argpos] = item->query; 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); 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, ", "); } expr = palloc0(sizeof(PLpgSQL_expr)); expr->query = pstrdup(ds.data); expr->parseMode = RAW_PARSE_PLPGSQL_EXPR; expr->plan = NULL; expr->paramnos = NULL; expr->target_param = -1; 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 if (tok_is_keyword(tok, &yylval, K_COLUMN, "column")) opt->opt_type = PLPGSQL_RAISEOPTION_COLUMN; else if (tok_is_keyword(tok, &yylval, K_CONSTRAINT, "constraint")) opt->opt_type = PLPGSQL_RAISEOPTION_CONSTRAINT; else if (tok_is_keyword(tok, &yylval, K_DATATYPE, "datatype")) opt->opt_type = PLPGSQL_RAISEOPTION_DATATYPE; else if (tok_is_keyword(tok, &yylval, K_TABLE, "table")) opt->opt_type = PLPGSQL_RAISEOPTION_TABLE; else if (tok_is_keyword(tok, &yylval, K_SCHEMA, "schema")) opt->opt_type = PLPGSQL_RAISEOPTION_SCHEMA; 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; } /* * Check that the number of parameter placeholders in the message matches the * number of parameters passed to it, if a message was given. */ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt) { char *cp; int expected_nparams = 0; if (stmt->message == NULL) return; for (cp = stmt->message; *cp; cp++) { if (cp[0] == '%') { /* ignore literal % characters */ if (cp[1] == '%') cp++; else expected_nparams++; } } if (expected_nparams < list_length(stmt->params)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("too many parameters specified for RAISE"))); if (expected_nparams > list_length(stmt->params)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("too few parameters specified for RAISE"))); } /* * 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->stmtid = ++plpgsql_curr_compile->nstatements; 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, NULL), 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; /* We expect to have expressions not statements */ Assert(expr->parseMode == RAW_PARSE_PLPGSQL_EXPR); /* Do the string hacking */ initStringInfo(&ds); appendStringInfo(&ds, "\"%s\" IN (%s)", varname, expr->query); 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; }