summaryrefslogtreecommitdiffstats
path: root/src/pl/plpgsql/src/pl_gram.y
diff options
context:
space:
mode:
Diffstat (limited to 'src/pl/plpgsql/src/pl_gram.y')
-rw-r--r--src/pl/plpgsql/src/pl_gram.y4120
1 files changed, 4120 insertions, 0 deletions
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
new file mode 100644
index 0000000..0b8aea9
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -0,0 +1,4120 @@
+%{
+/*-------------------------------------------------------------------------
+ *
+ * pl_gram.y - Parser for the PL/pgSQL procedural language
+ *
+ * Portions Copyright (c) 1996-2022, 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. Note this only works with
+ * bison >= 2.0. However, in bison 1.875 the default is to use alloca()
+ * if possible, so there's not really much problem anyhow, at least if
+ * you're building with gcc.
+ */
+#define YYMALLOC palloc
+#define YYFREE pfree
+
+
+typedef struct
+{
+ int location;
+} 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,
+ bool trim,
+ int *startloc,
+ int *endtoken);
+static PLpgSQL_expr *read_sql_expression(int until,
+ const char *expected);
+static PLpgSQL_expr *read_sql_expression2(int until, int until2,
+ const char *expected,
+ int *endtoken);
+static PLpgSQL_expr *read_sql_stmt(void);
+static PLpgSQL_type *read_datatype(int tok);
+static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location);
+static PLpgSQL_stmt_fetch *read_fetch_direction(void);
+static void complete_direction(PLpgSQL_stmt_fetch *fetch,
+ bool *check_FROM);
+static PLpgSQL_stmt *make_return_stmt(int location);
+static PLpgSQL_stmt *make_return_next_stmt(int location);
+static PLpgSQL_stmt *make_return_query_stmt(int location);
+static PLpgSQL_stmt *make_case(int location, PLpgSQL_expr *t_expr,
+ List *case_when_list, List *else_stmts);
+static char *NameOfDatum(PLwdatum *wdatum);
+static void check_assignable(PLpgSQL_datum *datum, int location);
+static void read_into_target(PLpgSQL_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 <declhdr> decl_sect
+%type <varname> decl_varname
+%type <boolean> decl_const decl_notnull exit_type
+%type <expr> decl_defval decl_cursor_query
+%type <dtype> decl_datatype
+%type <oid> decl_collate
+%type <datum> decl_cursor_args
+%type <list> decl_cursor_arglist
+%type <nsitem> decl_aliasitem
+
+%type <expr> expr_until_semi
+%type <expr> expr_until_then expr_until_loop opt_expr_until_when
+%type <expr> opt_exitcond
+
+%type <var> cursor_variable
+%type <datum> decl_cursor_arg
+%type <forvariable> for_variable
+%type <ival> foreach_slice
+%type <stmt> for_control
+
+%type <str> any_identifier opt_block_label opt_loop_label opt_label
+%type <str> option_value
+
+%type <list> proc_sect stmt_elsifs stmt_else
+%type <loop_body> loop_body
+%type <stmt> proc_stmt pl_block
+%type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit
+%type <stmt> stmt_return stmt_raise stmt_assert stmt_execsql
+%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_call stmt_getdiag
+%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
+%type <stmt> stmt_commit stmt_rollback
+%type <stmt> stmt_case stmt_foreach_a
+
+%type <list> proc_exceptions
+%type <exception_block> exception_sect
+%type <exception> proc_exception
+%type <condition> proc_conditions proc_condition
+
+%type <casewhen> case_when
+%type <list> case_when_list opt_case_else
+
+%type <boolean> getdiag_area_opt
+%type <list> getdiag_list
+%type <diagitem> getdiag_list_item
+%type <datum> getdiag_target
+%type <ival> getdiag_item
+
+%type <ival> opt_scrollable
+%type <fetch> opt_fetch_direction
+
+%type <ival> opt_transaction_chain
+
+%type <keyword> 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 <str> IDENT UIDENT FCONST SCONST USCONST BCONST XCONST Op
+%token <ival> 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 <word> T_WORD /* unrecognized simple identifier */
+%token <cword> T_CWORD /* unrecognized composite identifier */
+%token <wdatum> 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 <keyword> K_ABSOLUTE
+%token <keyword> K_ALIAS
+%token <keyword> K_ALL
+%token <keyword> K_AND
+%token <keyword> K_ARRAY
+%token <keyword> K_ASSERT
+%token <keyword> K_BACKWARD
+%token <keyword> K_BEGIN
+%token <keyword> K_BY
+%token <keyword> K_CALL
+%token <keyword> K_CASE
+%token <keyword> K_CHAIN
+%token <keyword> K_CLOSE
+%token <keyword> K_COLLATE
+%token <keyword> K_COLUMN
+%token <keyword> K_COLUMN_NAME
+%token <keyword> K_COMMIT
+%token <keyword> K_CONSTANT
+%token <keyword> K_CONSTRAINT
+%token <keyword> K_CONSTRAINT_NAME
+%token <keyword> K_CONTINUE
+%token <keyword> K_CURRENT
+%token <keyword> K_CURSOR
+%token <keyword> K_DATATYPE
+%token <keyword> K_DEBUG
+%token <keyword> K_DECLARE
+%token <keyword> K_DEFAULT
+%token <keyword> K_DETAIL
+%token <keyword> K_DIAGNOSTICS
+%token <keyword> K_DO
+%token <keyword> K_DUMP
+%token <keyword> K_ELSE
+%token <keyword> K_ELSIF
+%token <keyword> K_END
+%token <keyword> K_ERRCODE
+%token <keyword> K_ERROR
+%token <keyword> K_EXCEPTION
+%token <keyword> K_EXECUTE
+%token <keyword> K_EXIT
+%token <keyword> K_FETCH
+%token <keyword> K_FIRST
+%token <keyword> K_FOR
+%token <keyword> K_FOREACH
+%token <keyword> K_FORWARD
+%token <keyword> K_FROM
+%token <keyword> K_GET
+%token <keyword> K_HINT
+%token <keyword> K_IF
+%token <keyword> K_IMPORT
+%token <keyword> K_IN
+%token <keyword> K_INFO
+%token <keyword> K_INSERT
+%token <keyword> K_INTO
+%token <keyword> K_IS
+%token <keyword> K_LAST
+%token <keyword> K_LOG
+%token <keyword> K_LOOP
+%token <keyword> K_MERGE
+%token <keyword> K_MESSAGE
+%token <keyword> K_MESSAGE_TEXT
+%token <keyword> K_MOVE
+%token <keyword> K_NEXT
+%token <keyword> K_NO
+%token <keyword> K_NOT
+%token <keyword> K_NOTICE
+%token <keyword> K_NULL
+%token <keyword> K_OPEN
+%token <keyword> K_OPTION
+%token <keyword> K_OR
+%token <keyword> K_PERFORM
+%token <keyword> K_PG_CONTEXT
+%token <keyword> K_PG_DATATYPE_NAME
+%token <keyword> K_PG_EXCEPTION_CONTEXT
+%token <keyword> K_PG_EXCEPTION_DETAIL
+%token <keyword> K_PG_EXCEPTION_HINT
+%token <keyword> K_PRINT_STRICT_PARAMS
+%token <keyword> K_PRIOR
+%token <keyword> K_QUERY
+%token <keyword> K_RAISE
+%token <keyword> K_RELATIVE
+%token <keyword> K_RETURN
+%token <keyword> K_RETURNED_SQLSTATE
+%token <keyword> K_REVERSE
+%token <keyword> K_ROLLBACK
+%token <keyword> K_ROW_COUNT
+%token <keyword> K_ROWTYPE
+%token <keyword> K_SCHEMA
+%token <keyword> K_SCHEMA_NAME
+%token <keyword> K_SCROLL
+%token <keyword> K_SLICE
+%token <keyword> K_SQLSTATE
+%token <keyword> K_STACKED
+%token <keyword> K_STRICT
+%token <keyword> K_TABLE
+%token <keyword> K_TABLE_NAME
+%token <keyword> K_THEN
+%token <keyword> K_TO
+%token <keyword> K_TYPE
+%token <keyword> K_USE_COLUMN
+%token <keyword> K_USE_VARIABLE
+%token <keyword> K_USING
+%token <keyword> K_VARIABLE_CONFLICT
+%token <keyword> K_WARNING
+%token <keyword> K_WHEN
+%token <keyword> 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;
+ PLpgSQL_expr *curname_def;
+ char buf[NAMEDATALEN * 2 + 64];
+ char *cp1;
+ char *cp2;
+
+ /* pop local namespace for cursor args */
+ plpgsql_ns_pop();
+
+ new = (PLpgSQL_var *)
+ plpgsql_build_variable($1.name, $1.lineno,
+ plpgsql_build_datatype(REFCURSOROID,
+ -1,
+ InvalidOid,
+ NULL),
+ true);
+
+ curname_def = palloc0(sizeof(PLpgSQL_expr));
+
+ /* Note: refname has been truncated to NAMEDATALEN */
+ cp1 = new->refname;
+ cp2 = buf;
+ /*
+ * Don't trust standard_conforming_strings here;
+ * it might change before we use the string.
+ */
+ if (strchr(cp1, '\\') != NULL)
+ *cp2++ = ESCAPE_STRING_SYNTAX;
+ *cp2++ = '\'';
+ while (*cp1)
+ {
+ if (SQL_STR_DOUBLE(*cp1, true))
+ *cp2++ = *cp1;
+ *cp2++ = *cp1++;
+ }
+ strcpy(cp2, "'::pg_catalog.refcursor");
+ curname_def->query = pstrdup(buf);
+ curname_def->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ new->default_val = curname_def;
+
+ new->cursor_explicit_expr = $7;
+ if ($5 == NULL)
+ new->cursor_explicit_argrow = -1;
+ else
+ new->cursor_explicit_argrow = $5->dno;
+ new->cursor_options = CURSOR_OPT_FAST_PLAN | $2;
+ }
+ ;
+
+opt_scrollable :
+ {
+ $$ = 0;
+ }
+ | K_NO K_SCROLL
+ {
+ $$ = CURSOR_OPT_NO_SCROLL;
+ }
+ | K_SCROLL
+ {
+ $$ = CURSOR_OPT_SCROLL;
+ }
+ ;
+
+decl_cursor_query :
+ {
+ $$ = read_sql_stmt();
+ }
+ ;
+
+decl_cursor_args :
+ {
+ $$ = NULL;
+ }
+ | '(' decl_cursor_arglist ')'
+ {
+ PLpgSQL_row *new;
+ int i;
+ ListCell *l;
+
+ new = palloc0(sizeof(PLpgSQL_row));
+ new->dtype = PLPGSQL_DTYPE_ROW;
+ new->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
+ * should consume it.
+ */
+ $$ = read_datatype(yychar);
+ yyclearin;
+ }
+ ;
+
+decl_collate :
+ { $$ = InvalidOid; }
+ | K_COLLATE T_WORD
+ {
+ $$ = get_collation_oid(list_make1(makeString($2.ident)),
+ false);
+ }
+ | K_COLLATE unreserved_keyword
+ {
+ $$ = get_collation_oid(list_make1(makeString(pstrdup($2))),
+ false);
+ }
+ | K_COLLATE T_CWORD
+ {
+ $$ = get_collation_oid($2.idents, false);
+ }
+ ;
+
+decl_notnull :
+ { $$ = false; }
+ | K_NOT K_NULL
+ { $$ = true; }
+ ;
+
+decl_defval : ';'
+ { $$ = NULL; }
+ | decl_defkey
+ {
+ $$ = read_sql_expression(';', ";");
+ }
+ ;
+
+decl_defkey : assign_operator
+ | K_DEFAULT
+ ;
+
+/*
+ * 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, true,
+ &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, 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:
+ 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_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,
+ true,
+ &expr1loc,
+ &tok);
+
+ if (tok == DOT_DOT)
+ {
+ /* Saw "..", so it must be an integer loop */
+ PLpgSQL_expr *expr2;
+ PLpgSQL_expr *expr_by;
+ PLpgSQL_var *fvar;
+ PLpgSQL_stmt_fori *new;
+
+ /*
+ * 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, 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);
+ }
+ | K_INSERT
+ {
+ $$ = make_execsql_stmt(K_INSERT, @1);
+ }
+ | K_MERGE
+ {
+ $$ = make_execsql_stmt(K_MERGE, @1);
+ }
+ | 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);
+ }
+ | 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);
+ }
+ ;
+
+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, 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, 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;
+
+ $<exception_block>$ = new;
+ }
+ proc_exceptions
+ {
+ PLpgSQL_exception_block *new = $<exception_block>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_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, 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, 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, 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
+ * trim: trim trailing whitespace
+ * startloc: if not NULL, location of first token is stored at *startloc
+ * endtoken: if not NULL, ending token is stored at *endtoken
+ * (this is only interesting if until2 or until3 isn't zero)
+ */
+static PLpgSQL_expr *
+read_sql_construct(int until,
+ int until2,
+ int until3,
+ const char *expected,
+ RawParseMode parsemode,
+ bool isexpression,
+ bool valid_sql,
+ bool trim,
+ int *startloc,
+ int *endtoken)
+{
+ int tok;
+ StringInfoData ds;
+ IdentifierLookup save_IdentifierLookup;
+ int startlocation = -1;
+ int parenlevel = 0;
+ PLpgSQL_expr *expr;
+
+ initStringInfo(&ds);
+
+ /* special lookup mode for identifiers within the SQL text */
+ save_IdentifierLookup = plpgsql_IdentifierLookup;
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
+
+ for (;;)
+ {
+ tok = yylex();
+ if (startlocation < 0) /* remember loc of first token */
+ startlocation = yylloc;
+ if (tok == until && parenlevel == 0)
+ break;
+ if (tok == until2 && parenlevel == 0)
+ break;
+ if (tok == until3 && parenlevel == 0)
+ break;
+ if (tok == '(' || tok == '[')
+ parenlevel++;
+ else if (tok == ')' || tok == ']')
+ {
+ parenlevel--;
+ if (parenlevel < 0)
+ yyerror("mismatched parentheses");
+ }
+ /*
+ * End of function definition is an error, and we don't expect to
+ * hit a semicolon either (unless it's the until symbol, in which
+ * case we should have fallen out above).
+ */
+ if (tok == 0 || tok == ';')
+ {
+ if (parenlevel != 0)
+ yyerror("mismatched parentheses");
+ if (isexpression)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("missing \"%s\" at end of SQL expression",
+ expected),
+ parser_errposition(yylloc)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("missing \"%s\" at end of SQL statement",
+ expected),
+ parser_errposition(yylloc)));
+ }
+ }
+
+ plpgsql_IdentifierLookup = save_IdentifierLookup;
+
+ if (startloc)
+ *startloc = startlocation;
+ if (endtoken)
+ *endtoken = tok;
+
+ /* give helpful complaint about empty input */
+ if (startlocation >= yylloc)
+ {
+ if (isexpression)
+ yyerror("missing expression");
+ else
+ yyerror("missing SQL statement");
+ }
+
+ plpgsql_append_source_text(&ds, startlocation, yylloc);
+
+ /* trim any trailing whitespace, for neatness */
+ if (trim)
+ {
+ while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
+ ds.data[--ds.len] = '\0';
+ }
+
+ expr = palloc0(sizeof(PLpgSQL_expr));
+ expr->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;
+}
+
+static PLpgSQL_type *
+read_datatype(int tok)
+{
+ StringInfoData ds;
+ char *type_name;
+ int startlocation;
+ PLpgSQL_type *result;
+ int parenlevel = 0;
+
+ /* Should only be called while parsing DECLARE sections */
+ Assert(plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_DECLARE);
+
+ /* Often there will be a lookahead token, but if not, get one */
+ if (tok == YYEMPTY)
+ tok = yylex();
+
+ startlocation = yylloc;
+
+ /*
+ * If we have a simple or composite identifier, check for %TYPE
+ * and %ROWTYPE constructs.
+ */
+ if (tok == T_WORD)
+ {
+ char *dtname = yylval.word.ident;
+
+ tok = yylex();
+ if (tok == '%')
+ {
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_TYPE, "type"))
+ {
+ result = plpgsql_parse_wordtype(dtname);
+ if (result)
+ return result;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ROWTYPE, "rowtype"))
+ {
+ result = plpgsql_parse_wordrowtype(dtname);
+ if (result)
+ return result;
+ }
+ }
+ }
+ else if (plpgsql_token_is_unreserved_keyword(tok))
+ {
+ char *dtname = pstrdup(yylval.keyword);
+
+ tok = yylex();
+ if (tok == '%')
+ {
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_TYPE, "type"))
+ {
+ result = plpgsql_parse_wordtype(dtname);
+ if (result)
+ return result;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ROWTYPE, "rowtype"))
+ {
+ result = plpgsql_parse_wordrowtype(dtname);
+ if (result)
+ return result;
+ }
+ }
+ }
+ else if (tok == T_CWORD)
+ {
+ List *dtnames = yylval.cword.idents;
+
+ tok = yylex();
+ if (tok == '%')
+ {
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_TYPE, "type"))
+ {
+ result = plpgsql_parse_cwordtype(dtnames);
+ if (result)
+ return result;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ROWTYPE, "rowtype"))
+ {
+ result = plpgsql_parse_cwordrowtype(dtnames);
+ if (result)
+ return result;
+ }
+ }
+ }
+
+ while (tok != ';')
+ {
+ if (tok == 0)
+ {
+ if (parenlevel != 0)
+ yyerror("mismatched parentheses");
+ else
+ yyerror("incomplete data type declaration");
+ }
+ /* Possible followers for datatype in a declaration */
+ if (tok == K_COLLATE || tok == K_NOT ||
+ tok == '=' || tok == COLON_EQUALS || tok == K_DEFAULT)
+ break;
+ /* Possible followers for datatype in a cursor_arg list */
+ if ((tok == ',' || tok == ')') && parenlevel == 0)
+ break;
+ if (tok == '(')
+ parenlevel++;
+ else if (tok == ')')
+ parenlevel--;
+
+ tok = yylex();
+ }
+
+ /* set up ds to contain complete typename text */
+ initStringInfo(&ds);
+ plpgsql_append_source_text(&ds, startlocation, yylloc);
+ type_name = ds.data;
+
+ if (type_name[0] == '\0')
+ yyerror("missing data type declaration");
+
+ result = parse_datatype(type_name, startlocation);
+
+ pfree(ds.data);
+
+ plpgsql_push_back_token(tok);
+
+ return result;
+}
+
+static PLpgSQL_stmt *
+make_execsql_stmt(int firsttoken, int location)
+{
+ StringInfoData ds;
+ IdentifierLookup save_IdentifierLookup;
+ PLpgSQL_stmt_execsql *execsql;
+ PLpgSQL_expr *expr;
+ PLpgSQL_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;
+
+ initStringInfo(&ds);
+
+ /* 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().
+ *
+ * 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;
+ for (;;)
+ {
+ prev_tok = tok;
+ tok = yylex();
+ if (have_into && into_end_loc < 0)
+ into_end_loc = yylloc; /* token after the INTO part */
+ if (tok == ';')
+ break;
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+ if (tok == K_INTO)
+ {
+ 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);
+ 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. Trailing whitespace must not be trimmed, because
+ * otherwise input of the form (param -- comment\n, param) would be
+ * translated into a form where the second parameter is commented
+ * out.
+ */
+ item = read_sql_construct(',', ')', 0,
+ ",\" or \")",
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true,
+ false, /* do not trim */
+ 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;
+}