Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Postgres/src/pl/plpgsql/src/   (Postgres Database Version 18.4©)  Datei vom 11.4.2026 mit Größe 119 kB image not shown  

Quelle  pl_gram.y   Sprache: unbekannt

 
Spracherkennung für: .y vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

%{
/*-------------------------------------------------------------------------
 *
 * pl_gram.y   - Parser for the PL/pgSQL procedural language
 *
 * Portions Copyright (c) 1996-2025, 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"

#include "pl_gram.h"

/* Location tracking support --- simpler than bison's default */
#define YYLLOC_DEFAULT(Current, Rhs, N) \
 do { \
  if (N) \
   (Current) = (Rhs)[1]; \
  else \
   (Current) = (Rhs)[0]; \
 } while (0)

/*
 * Bison doesn't allocate anything that needs to live across parser calls,
 * so we can easily have it use palloc instead of malloc.  This prevents
 * memory leaks if we error out during parsing.
 */
#define YYMALLOC palloc
#define YYFREE   pfree


typedef struct
{
 int   location;
 yyscan_t yyscanner;
} sql_error_callback_arg;

#define parser_errposition(pos)  plpgsql_scanner_errposition(pos, yyscanner)

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, yyscan_t yyscanner);
static void   cword_is_not_variable(PLcword *cword, int location, yyscan_t yyscanner);
static void   current_token_is_not_variable(int tok, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static PLpgSQL_expr *make_plpgsql_expr(const char *query,
             RawParseMode parsemode);
static void   mark_expr_as_assignment_source(PLpgSQL_expr *expr,
                PLpgSQL_datum *target);
static PLpgSQL_expr *read_sql_construct(int until,
           int until2,
           int until3,
           const char *expected,
           RawParseMode parsemode,
           bool isexpression,
           bool valid_sql,
           int *startloc,
           int *endtoken,
           YYSTYPE *yylvalp, YYLTYPE *yyllocp,
           yyscan_t yyscanner);
static PLpgSQL_expr *read_sql_expression(int until, const char *expected,
            YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static PLpgSQL_expr *read_sql_expression2(int until, int until2,
             const char *expected, int *endtoken,
             YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static PLpgSQL_expr *read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static PLpgSQL_type *read_datatype(int tok, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location,
             PLword *word, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static PLpgSQL_stmt_fetch *read_fetch_direction(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static void    complete_direction(PLpgSQL_stmt_fetch *fetch,
           bool *check_FROM, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static PLpgSQL_stmt *make_return_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static PLpgSQL_stmt *make_return_next_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static PLpgSQL_stmt *make_return_query_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static  PLpgSQL_stmt *make_case(int location, PLpgSQL_expr *t_expr,
           List *case_when_list, List *else_stmts, yyscan_t yyscanner);
static char   *NameOfDatum(PLwdatum *wdatum);
static void    check_assignable(PLpgSQL_datum *datum, int location, yyscan_t yyscanner);
static void    read_into_target(PLpgSQL_variable **target, bool *strict,
            YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static PLpgSQL_row  *read_into_scalar_list(char *initial_name,
              PLpgSQL_datum *initial_datum,
              int initial_location,
              YYSTYPE *yylvalp, YYLTYPE *yyllocp,
              yyscan_t yyscanner);
static PLpgSQL_row  *make_scalar_list1(char *initial_name,
             PLpgSQL_datum *initial_datum,
             int lineno, int location, yyscan_t yyscanner);
static void    check_sql_expr(const char *stmt,
          RawParseMode parseMode, int location, yyscan_t yyscanner);
static void    plpgsql_sql_error_callback(void *arg);
static PLpgSQL_type *parse_datatype(const char *string, int location, yyscan_t yyscanner);
static void    check_labels(const char *start_label,
           const char *end_label,
           int end_location,
           yyscan_t yyscanner);
static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor, int until,
            YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static List   *read_raise_options(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static void   check_raise_parameters(PLpgSQL_stmt_raise *stmt);

%}

%parse-param {PLpgSQL_stmt_block **plpgsql_parse_result_p}
%parse-param {yyscan_t yyscanner}
%lex-param   {yyscan_t yyscanner}
%pure-parser
%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_PG_ROUTINE_OID
%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_p = (PLpgSQL_stmt_block *) $2;
      (void) yynerrs;  /* suppress compiler warning */
     }
    ;

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, yyscanner);
      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, yyscanner);
      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)));

      if (var->default_val != NULL)
       mark_expr_as_assignment_source(var->default_val,
                 (PLpgSQL_datum *) var);
     }
    | decl_varname K_ALIAS K_FOR decl_aliasitem ';'
     {
      plpgsql_ns_additem($4->itemtype,
             $4->itemno, $1.name);
     }
    | decl_varname opt_scrollable K_CURSOR
     { plpgsql_ns_push($1.name, PLPGSQL_LABEL_OTHER); }
      decl_cursor_args decl_is_for decl_cursor_query
     {
      PLpgSQL_var *new;

      /* pop local namespace for cursor args */
      plpgsql_ns_pop();

      new = (PLpgSQL_var *)
       plpgsql_build_variable($1.name, $1.lineno,
               plpgsql_build_datatype(REFCURSOROID,
                    -1,
                    InvalidOid,
                    NULL),
               true);

      new->cursor_explicit_expr = $7;
      if ($5 == NULL)
       new->cursor_explicit_argrow = -1;
      else
       new->cursor_explicit_argrow = $5->dno;
      new->cursor_options = CURSOR_OPT_FAST_PLAN | $2;
     }
    ;

opt_scrollable :
     {
      $$ = 0;
     }
    | K_NO K_SCROLL
     {
      $$ = CURSOR_OPT_NO_SCROLL;
     }
    | K_SCROLL
     {
      $$ = CURSOR_OPT_SCROLL;
     }
    ;

decl_cursor_query :
     {
      $$ = read_sql_stmt(&yylval, &yylloc, yyscanner);
     }
    ;

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, yyscanner);
      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, yyscanner);
      /*
       * 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(&yylloc, NULL, yyscanner, "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, yyscanner);
      /*
       * 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(&yylloc, NULL, yyscanner, "duplicate declaration");

      if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_SHADOWVAR ||
       plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR)
      {
       PLpgSQL_nsitem *nsi;
       nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
             $1, NULL, NULL, NULL);
       if (nsi != NULL)
        ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR ? ERROR : WARNING,
          (errcode(ERRCODE_DUPLICATE_ALIAS),
           errmsg("variable \"%s\" shadows a previously defined variable",
            $1),
           parser_errposition(@1)));
      }

     }
    ;

decl_const  :
     { $$ = false; }
    | K_CONSTANT
     { $$ = true; }
    ;

decl_datatype :
     {
      /*
       * If there's a lookahead token, read_datatype() will
       * consume it, and then we must tell bison to forget
       * it.
       */
      $$ = read_datatype(yychar, &yylval, &yylloc, yyscanner);
      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(';', ";", &yylval, &yylloc, yyscanner);
     }
    ;

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, yyscanner);
      new->stmtid = ++plpgsql_curr_compile->nstatements;
      plpgsql_push_back_token(K_PERFORM, &yylval, &yylloc, yyscanner);

      /*
       * 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(';', 00, ";",
                RAW_PARSE_DEFAULT,
                false, false,
                &startloc, NULL,
                &yylval, &yylloc, yyscanner);
      /* 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, yyscanner);

      $$ = (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, yyscanner);
      new->stmtid = ++plpgsql_curr_compile->nstatements;
      plpgsql_push_back_token(K_CALL, &yylval, &yylloc, yyscanner);
      new->expr = read_sql_stmt(&yylval, &yylloc, yyscanner);
      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, yyscanner);
      new->stmtid = ++plpgsql_curr_compile->nstatements;
      plpgsql_push_back_token(K_DO, &yylval, &yylloc, yyscanner);
      new->expr = read_sql_stmt(&yylval, &yylloc, yyscanner);
      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, yyscanner);
      new = palloc0(sizeof(PLpgSQL_stmt_assign));
      new->cmd_type = PLPGSQL_STMT_ASSIGN;
      new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
      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, &yylval, &yylloc, yyscanner);
      new->expr = read_sql_construct(';', 00, ";",
                pmode,
                false, true,
                NULL, NULL,
                &yylval, &yylloc, yyscanner);
      mark_expr_as_assignment_source(new->expr, $1.datum);

      $$ = (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, yyscanner);
      new->stmtid = ++plpgsql_curr_compile->nstatements;
      new->is_stacked = $2;
      new->diag_items = $4;

      /*
       * Check information items are valid for area option.
       */
      foreach(lc, new->diag_items)
      {
       PLpgSQL_diag_item *ditem = (PLpgSQL_diag_item *) lfirst(lc);

       switch (ditem->kind)
       {
        /* these fields are disallowed in stacked case */
        case PLPGSQL_GETDIAG_ROW_COUNT:
        case PLPGSQL_GETDIAG_ROUTINE_OID:
         if (new->is_stacked)
          ereport(ERROR,
            (errcode(ERRCODE_SYNTAX_ERROR),
             errmsg("diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS",
              plpgsql_getdiag_kindname(ditem->kind)),
             parser_errposition(@1)));
         break;
        /* these fields are disallowed in current case */
        case PLPGSQL_GETDIAG_ERROR_CONTEXT:
        case PLPGSQL_GETDIAG_ERROR_DETAIL:
        case PLPGSQL_GETDIAG_ERROR_HINT:
        case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
        case PLPGSQL_GETDIAG_COLUMN_NAME:
        case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
        case PLPGSQL_GETDIAG_DATATYPE_NAME:
        case PLPGSQL_GETDIAG_MESSAGE_TEXT:
        case PLPGSQL_GETDIAG_TABLE_NAME:
        case PLPGSQL_GETDIAG_SCHEMA_NAME:
         if (!new->is_stacked)
          ereport(ERROR,
            (errcode(ERRCODE_SYNTAX_ERROR),
             errmsg("diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS",
              plpgsql_getdiag_kindname(ditem->kind)),
             parser_errposition(@1)));
         break;
        /* these fields are allowed in either case */
        case PLPGSQL_GETDIAG_CONTEXT:
         break;
        default:
         elog(ERROR, "unrecognized diagnostic item kind: %d",
           ditem->kind);
         break;
       }
      }

      $$ = (PLpgSQL_stmt *) new;
     }
    ;

getdiag_area_opt :
     {
      $$ = false;
     }
    | K_CURRENT
     {
      $$ = false;
     }
    | K_STACKED
     {
      $$ = true;
     }
    ;

getdiag_list : getdiag_list ',' getdiag_list_item
     {
      $$ = lappend($1, $3);
     }
    | getdiag_list_item
     {
      $$ = list_make1($1);
     }
    ;

getdiag_list_item : getdiag_target assign_operator getdiag_item
     {
      PLpgSQL_diag_item *new;

      new = palloc(sizeof(PLpgSQL_diag_item));
      new->target = $1->dno;
      new->kind = $3;

      $$ = new;
     }
    ;

getdiag_item :
     {
      int   tok = yylex(&yylval, &yylloc, yyscanner);

      if (tok_is_keyword(tok, &yylval,
             K_ROW_COUNT, "row_count"))
       $$ = PLPGSQL_GETDIAG_ROW_COUNT;
      else if (tok_is_keyword(tok, &yylval,
            K_PG_ROUTINE_OID, "pg_routine_oid"))
       $$ = PLPGSQL_GETDIAG_ROUTINE_OID;
      else if (tok_is_keyword(tok, &yylval,
            K_PG_CONTEXT, "pg_context"))
       $$ = PLPGSQL_GETDIAG_CONTEXT;
      else if (tok_is_keyword(tok, &yylval,
            K_PG_EXCEPTION_DETAIL, "pg_exception_detail"))
       $$ = PLPGSQL_GETDIAG_ERROR_DETAIL;
      else if (tok_is_keyword(tok, &yylval,
            K_PG_EXCEPTION_HINT, "pg_exception_hint"))
       $$ = PLPGSQL_GETDIAG_ERROR_HINT;
      else if (tok_is_keyword(tok, &yylval,
            K_PG_EXCEPTION_CONTEXT, "pg_exception_context"))
       $$ = PLPGSQL_GETDIAG_ERROR_CONTEXT;
      else if (tok_is_keyword(tok, &yylval,
            K_COLUMN_NAME, "column_name"))
       $$ = PLPGSQL_GETDIAG_COLUMN_NAME;
      else if (tok_is_keyword(tok, &yylval,
            K_CONSTRAINT_NAME, "constraint_name"))
       $$ = PLPGSQL_GETDIAG_CONSTRAINT_NAME;
      else if (tok_is_keyword(tok, &yylval,
            K_PG_DATATYPE_NAME, "pg_datatype_name"))
       $$ = PLPGSQL_GETDIAG_DATATYPE_NAME;
      else if (tok_is_keyword(tok, &yylval,
            K_MESSAGE_TEXT, "message_text"))
       $$ = PLPGSQL_GETDIAG_MESSAGE_TEXT;
      else if (tok_is_keyword(tok, &yylval,
            K_TABLE_NAME, "table_name"))
       $$ = PLPGSQL_GETDIAG_TABLE_NAME;
      else if (tok_is_keyword(tok, &yylval,
            K_SCHEMA_NAME, "schema_name"))
       $$ = PLPGSQL_GETDIAG_SCHEMA_NAME;
      else if (tok_is_keyword(tok, &yylval,
            K_RETURNED_SQLSTATE, "returned_sqlstate"))
       $$ = PLPGSQL_GETDIAG_RETURNED_SQLSTATE;
      else
       yyerror(&yylloc, NULL, yyscanner, "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(yyscanner) == '[')
       ereport(ERROR,
         (errcode(ERRCODE_SYNTAX_ERROR),
          errmsg("\"%s\" is not a scalar variable",
           NameOfDatum(&($1))),
          parser_errposition(@1)));
      check_assignable($1.datum, @1, yyscanner);
      $$ = $1.datum;
     }
    | T_WORD
     {
      /* just to give a better message than "syntax error" */
      word_is_not_variable(&($1), @1, yyscanner);
     }
    | T_CWORD
     {
      /* just to give a better message than "syntax error" */
      cword_is_not_variable(&($1), @1, yyscanner);
     }
    ;

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, yyscanner);
      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, yyscanner);
      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, yyscanner);
     }
    ;

opt_expr_until_when :
     {
      PLpgSQL_expr *expr = NULL;
      int   tok = yylex(&yylval, &yylloc, yyscanner);

      if (tok != K_WHEN)
      {
       plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
       expr = read_sql_expression(K_WHEN, "WHEN", &yylval, &yylloc, yyscanner);
      }
      plpgsql_push_back_token(K_WHEN, &yylval, &yylloc, yyscanner);
      $$ = 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, yyscanner);
      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, yyscanner);
      new->stmtid = ++plpgsql_curr_compile->nstatements;
      new->label = $1;
      new->body = $3.stmts;

      check_labels($1, $3.end_label, $3.end_label_location, yyscanner);
      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, yyscanner);
      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, yyscanner);
      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, yyscanner);
       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, yyscanner);
       new->label = $1;
       new->body = $4.stmts;
       $$ = (PLpgSQL_stmt *) new;
      }

      check_labels($1, $4.end_label, $4.end_label_location, yyscanner);
      /* close namespace started in opt_loop_label */
      plpgsql_ns_pop();
     }
    ;

for_control  : for_variable K_IN
     {
      int   tok = yylex(&yylval, &yylloc, yyscanner);
      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, &yylval, &yylloc, yyscanner);

       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, yyscanner);
       }
       else if ($1.scalar)
       {
        /* convert single scalar to list */
        new->var = (PLpgSQL_variable *)
         make_scalar_list1($1.name, $1.scalar,
               $1.lineno, @1, yyscanner);
        /* 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, &yylval, &yylloc, yyscanner);
         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, &yylval, &yylloc, yyscanner);

       /* 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, &yylval, &yylloc, yyscanner);

       /*
        * Read tokens until we see either a ".."
        * or a LOOP.  The text we read may be either
        * an expression or a whole SQL statement, so
        * we need to invoke read_sql_construct directly,
        * and tell it not to check syntax yet.
        */
       expr1 = read_sql_construct(DOT_DOT,
                K_LOOP,
                0,
                "LOOP",
                RAW_PARSE_DEFAULT,
                true,
                false,
                &expr1loc,
                &tok,
                &yylval, &yylloc, yyscanner);

       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, yyscanner);

        /* Read and check the second one */
        expr2 = read_sql_expression2(K_LOOP, K_BY,
                "LOOP",
                &tok, &yylval, &yylloc, yyscanner);

        /* Get the BY clause if any */
        if (tok == K_BY)
         expr_by = read_sql_expression(K_LOOP,
                  "LOOP", &yylval, &yylloc, yyscanner);
        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, yyscanner);

        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, yyscanner);
        }
        else if ($1.scalar)
        {
         /* convert single scalar to list */
         new->var = (PLpgSQL_variable *)
          make_scalar_list1($1.name, $1.scalar,
                $1.lineno, @1, yyscanner);
         /* 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, yyscanner);
      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(&yylval, &yylloc, yyscanner);
       plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
       if (tok == ',')
        $$.row = (PLpgSQL_datum *)
         read_into_scalar_list($$.name,
                $$.scalar,
                @1,
                &yylval, &yylloc,
                yyscanner);
      }
     }
    | T_WORD
     {
      int   tok;

      $$.name = $1.ident;
      $$.lineno = plpgsql_location_to_lineno(@1, yyscanner);
      $$.scalar = NULL;
      $$.row = NULL;
      /* check for comma-separated list */
      tok = yylex(&yylval, &yylloc, yyscanner);
      plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
      if (tok == ',')
       word_is_not_variable(&($1), @1, yyscanner);
     }
    | T_CWORD
     {
      /* just to give a better message than "syntax error" */
      cword_is_not_variable(&($1), @1, yyscanner);
     }
    ;

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, yyscanner);
      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, yyscanner);
      }
      else if ($3.scalar)
      {
       new->varno = $3.scalar->dno;
       check_assignable($3.scalar, @3, yyscanner);
      }
      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, yyscanner);
      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, yyscanner);
      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(&yylval, &yylloc, yyscanner);
      if (tok == 0)
       yyerror(&yylloc, NULL, yyscanner, "unexpected end of function definition");

      if (tok_is_keyword(tok, &yylval,
             K_NEXT, "next"))
      {
       $$ = make_return_next_stmt(@1, &yylval, &yylloc, yyscanner);
      }
      else if (tok_is_keyword(tok, &yylval,
            K_QUERY, "query"))
      {
       $$ = make_return_query_stmt(@1, &yylval, &yylloc, yyscanner);
      }
      else
      {
       plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
       $$ = make_return_stmt(@1, &yylval, &yylloc, yyscanner);
      }
     }
    ;

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, yyscanner);
      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(&yylval, &yylloc, yyscanner);
      if (tok == 0)
       yyerror(&yylloc, NULL, yyscanner, "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(&yylval, &yylloc, yyscanner);
       }
       else if (tok_is_keyword(tok, &yylval,
             K_WARNING, "warning"))
       {
        new->elog_level = WARNING;
        tok = yylex(&yylval, &yylloc, yyscanner);
       }
       else if (tok_is_keyword(tok, &yylval,
             K_NOTICE, "notice"))
       {
        new->elog_level = NOTICE;
        tok = yylex(&yylval, &yylloc, yyscanner);
       }
       else if (tok_is_keyword(tok, &yylval,
             K_INFO, "info"))
       {
        new->elog_level = INFO;
        tok = yylex(&yylval, &yylloc, yyscanner);
       }
       else if (tok_is_keyword(tok, &yylval,
             K_LOG, "log"))
       {
        new->elog_level = LOG;
        tok = yylex(&yylval, &yylloc, yyscanner);
       }
       else if (tok_is_keyword(tok, &yylval,
             K_DEBUG, "debug"))
       {
        new->elog_level = DEBUG1;
        tok = yylex(&yylval, &yylloc, yyscanner);
       }
       if (tok == 0)
        yyerror(&yylloc, NULL, yyscanner, "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(&yylval, &yylloc, yyscanner);
        if (tok != ',' && tok != ';' && tok != K_USING)
         yyerror(&yylloc, NULL, yyscanner, "syntax error");

        while (tok == ',')
        {
         PLpgSQL_expr *expr;

         expr = read_sql_construct(',', ';', K_USING,
                 ", or ; or USING",
                 RAW_PARSE_PLPGSQL_EXPR,
                 true, true,
                 NULL, &tok,
                 &yylval, &yylloc, yyscanner);
         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(&yylval, &yylloc, yyscanner) != SCONST)
          yyerror(&yylloc, NULL, yyscanner, "syntax error");
         sqlstatestr = yylval.str;

         if (strlen(sqlstatestr) != 5)
          yyerror(&yylloc, NULL, yyscanner, "invalid SQLSTATE code");
         if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
          yyerror(&yylloc, NULL, yyscanner, "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(&yylloc, NULL, yyscanner, "syntax error");
         plpgsql_recognize_err_condition(new->condname,
                 false);
        }
        tok = yylex(&yylval, &yylloc, yyscanner);
        if (tok != ';' && tok != K_USING)
         yyerror(&yylloc, NULL, yyscanner, "syntax error");
       }

       if (tok == K_USING)
        new->options = read_raise_options(&yylval, &yylloc, yyscanner);
      }

      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, yyscanner);
      new->stmtid = ++plpgsql_curr_compile->nstatements;

      new->cond = read_sql_expression2(',', ';',
               ", or ;",
               &tok, &yylval, &yylloc, yyscanner);

      if (tok == ',')
       new->message = read_sql_expression(';', ";", &yylval, &yylloc, yyscanner);
      else
       new->message = NULL;

      $$ = (PLpgSQL_stmt *) new;
     }
    ;

loop_body  : proc_sect K_END K_LOOP opt_label ';'
     {
      $$.stmts = $1;
      $$.end_label = $4;
      $$.end_label_location = @4;
     }
    ;

/*
 * T_WORD+T_CWORD match any initial identifier that is not a known plpgsql
 * variable.  (The composite case is probably a syntax error, but we'll let
 * the core parser decide that.)  Normally, we should assume that such a
 * word is a SQL statement keyword that isn't also a plpgsql keyword.
 * However, if the next token is assignment or '[' or '.', it can't be a valid
 * SQL statement, and what we're probably looking at is an intended variable
 * assignment.  Give an appropriate complaint for that, instead of letting
 * the core parser throw an unhelpful "syntax error".
 */
stmt_execsql : K_IMPORT
     {
      $$ = make_execsql_stmt(K_IMPORT, @1, NULL, &yylval, &yylloc, yyscanner);
     }
    | K_INSERT
     {
      $$ = make_execsql_stmt(K_INSERT, @1, NULL, &yylval, &yylloc, yyscanner);
     }
    | K_MERGE
     {
      $$ = make_execsql_stmt(K_MERGE, @1, NULL, &yylval, &yylloc, yyscanner);
     }
    | T_WORD
     {
      int   tok;

      tok = yylex(&yylval, &yylloc, yyscanner);
      plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
      if (tok == '=' || tok == COLON_EQUALS ||
       tok == '[' || tok == '.')
       word_is_not_variable(&($1), @1, yyscanner);
      $$ = make_execsql_stmt(T_WORD, @1, &($1), &yylval, &yylloc, yyscanner);
     }
    | T_CWORD
     {
      int   tok;

      tok = yylex(&yylval, &yylloc, yyscanner);
      plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
      if (tok == '=' || tok == COLON_EQUALS ||
       tok == '[' || tok == '.')
       cword_is_not_variable(&($1), @1, yyscanner);
      $$ = make_execsql_stmt(T_CWORD, @1, NULL, &yylval, &yylloc, yyscanner);
     }
    ;

stmt_dynexecute : K_EXECUTE
     {
      PLpgSQL_stmt_dynexecute *new;
      PLpgSQL_expr *expr;
      int   endtoken;

      expr = read_sql_construct(K_INTO, K_USING, ';',
              "INTO or USING or ;",
              RAW_PARSE_PLPGSQL_EXPR,
              true, true,
              NULL, &endtoken,
              &yylval, &yylloc, yyscanner);

      new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
      new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
      new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
      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(&yylloc, NULL, yyscanner, "syntax error");
        new->into = true;
        read_into_target(&new->target, &new->strict, &yylval, &yylloc, yyscanner);
        endtoken = yylex(&yylval, &yylloc, yyscanner);
       }
       else if (endtoken == K_USING)
       {
        if (new->params)  /* multiple USING */
         yyerror(&yylloc, NULL, yyscanner, "syntax error");
        do
        {
         expr = read_sql_construct(',', ';', K_INTO,
                 ", or ; or INTO",
                 RAW_PARSE_PLPGSQL_EXPR,
                 true, true,
                 NULL, &endtoken,
                 &yylval, &yylloc, yyscanner);
         new->params = lappend(new->params, expr);
        } while (endtoken == ',');
       }
       else if (endtoken == ';')
        break;
       else
        yyerror(&yylloc, NULL, yyscanner, "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, yyscanner);
      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(&yylval, &yylloc, yyscanner);
       if (tok_is_keyword(tok, &yylval,
              K_NO, "no"))
       {
        tok = yylex(&yylval, &yylloc, yyscanner);
        if (tok_is_keyword(tok, &yylval,
               K_SCROLL, "scroll"))
        {
         new->cursor_options |= CURSOR_OPT_NO_SCROLL;
         tok = yylex(&yylval, &yylloc, yyscanner);
        }
       }
       else if (tok_is_keyword(tok, &yylval,
             K_SCROLL, "scroll"))
       {
        new->cursor_options |= CURSOR_OPT_SCROLL;
        tok = yylex(&yylval, &yylloc, yyscanner);
       }

       if (tok != K_FOR)
        yyerror(&yylloc, NULL, yyscanner, "syntax error, expected \"FOR\"");

       tok = yylex(&yylval, &yylloc, yyscanner);
       if (tok == K_EXECUTE)
       {
        int   endtoken;

        new->dynquery =
         read_sql_expression2(K_USING, ';',
               "USING or ;",
               &endtoken, &yylval, &yylloc, yyscanner);

        /* If we found "USING", collect argument(s) */
        if (endtoken == K_USING)
        {
         PLpgSQL_expr *expr;

         do
         {
          expr = read_sql_expression2(',', ';',
                 ", or ;",
                 &endtoken, &yylval, &yylloc, yyscanner);
          new->params = lappend(new->params,
                 expr);
         } while (endtoken == ',');
        }
       }
       else
       {
        plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
        new->query = read_sql_stmt(&yylval, &yylloc, yyscanner);
       }
      }
      else
      {
       /* predefined cursor query, so read args */
       new->argquery = read_cursor_args($2, ';', &yylval, &yylloc, yyscanner);
      }

      $$ = (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, &yylval, &yylloc, yyscanner);

      if (yylex(&yylval, &yylloc, yyscanner) != ';')
       yyerror(&yylloc, NULL, yyscanner, "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, yyscanner);
      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, yyscanner);
      fetch->curvar = $3->dno;
      fetch->is_move = true;

      $$ = (PLpgSQL_stmt *) fetch;
     }
    ;

opt_fetch_direction :
     {
      $$ = read_fetch_direction(&yylval, &yylloc, yyscanner);
     }
    ;

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, yyscanner);
      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, yyscanner);
      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, yyscanner);
      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(yyscanner) == '[')
       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, yyscanner);
     }
    | T_CWORD
     {
      /* just to give a better message than "syntax error" */
      cword_is_not_variable(&($1), @1, yyscanner);
     }
    ;

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, yyscanner);
      PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block));
      PLpgSQL_variable *var;

      plpgsql_curr_compile->has_exception_block = true;

      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, yyscanner);
      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(&yylval, &yylloc, yyscanner) != SCONST)
         yyerror(&yylloc, NULL, yyscanner, "syntax error");
        sqlstatestr = yylval.str;

        if (strlen(sqlstatestr) != 5)
         yyerror(&yylloc, NULL, yyscanner, "invalid SQLSTATE code");
        if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
         yyerror(&yylloc, NULL, yyscanner, "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(';', ";", &yylval, &yylloc, yyscanner); }
    ;

expr_until_then :
     { $$ = read_sql_expression(K_THEN, "THEN", &yylval, &yylloc, yyscanner); }
    ;

expr_until_loop :
     { $$ = read_sql_expression(K_LOOP, "LOOP", &yylval, &yylloc, yyscanner); }
    ;

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(&yylloc, NULL, yyscanner, "syntax error");
      $$ = $1.ident;
     }
    ;

unreserved_keyword :
    K_ABSOLUTE
    | K_ALIAS
    | K_AND
    | K_ARRAY
    | K_ASSERT
    | K_BACKWARD
    | K_CALL
    | K_CHAIN
    | K_CLOSE
    | K_COLLATE
    | K_COLUMN
    | K_COLUMN_NAME
    | K_COMMIT
    | K_CONSTANT
    | K_CONSTRAINT
    | K_CONSTRAINT_NAME
    | K_CONTINUE
    | K_CURRENT
    | K_CURSOR
    | K_DATATYPE
    | K_DEBUG
    | K_DEFAULT
    | K_DETAIL
    | K_DIAGNOSTICS
    | K_DO
    | K_DUMP
    | K_ELSIF
    | K_ERRCODE
    | K_ERROR
    | K_EXCEPTION
    | K_EXIT
    | K_FETCH
    | K_FIRST
    | K_FORWARD
    | K_GET
    | K_HINT
    | K_IMPORT
    | K_INFO
    | K_INSERT
    | K_IS
    | K_LAST
    | K_LOG
    | K_MERGE
    | K_MESSAGE
    | K_MESSAGE_TEXT
    | K_MOVE
    | K_NEXT
    | K_NO
    | K_NOTICE
    | K_OPEN
    | K_OPTION
    | K_PERFORM
    | K_PG_CONTEXT
    | K_PG_DATATYPE_NAME
    | K_PG_EXCEPTION_CONTEXT
    | K_PG_EXCEPTION_DETAIL
    | K_PG_EXCEPTION_HINT
    | K_PG_ROUTINE_OID
    | K_PRINT_STRICT_PARAMS
    | K_PRIOR
    | K_QUERY
    | K_RAISE
    | K_RELATIVE
    | K_RETURN
    | K_RETURNED_SQLSTATE
    | K_REVERSE
    | K_ROLLBACK
    | K_ROW_COUNT
    | K_ROWTYPE
    | K_SCHEMA
    | K_SCHEMA_NAME
    | K_SCROLL
    | K_SLICE
    | K_SQLSTATE
    | K_STACKED
    | K_TABLE
    | K_TABLE_NAME
    | K_TYPE
    | K_USE_COLUMN
    | K_USE_VARIABLE
    | K_VARIABLE_CONFLICT
    | K_WARNING
    ;

%%

/*
 * Check whether a token represents an "unreserved keyword".
 * We have various places where we want to recognize a keyword in preference
 * to a variable name, but not reserve that keyword in other contexts.
 * Hence, this kluge.
 */
static bool
tok_is_keyword(int token, union YYSTYPE *lval,
      int kw_token, const char *kw_str)
{
 if (token == kw_token)
 {
  /* Normal case, was recognized by scanner (no conflicting variable) */
  return true;
 }
 else if (token == T_DATUM)
 {
  /*
   * It's a variable, so recheck the string name.  Note we will not
   * match composite names (hence an unreserved word followed by "."
   * will not be recognized).
   */
  if (!lval->wdatum.quoted && lval->wdatum.ident != NULL &&
   strcmp(lval->wdatum.ident, kw_str) == 0)
   return true;
 }
 return false;    /* not the keyword */
}

/*
 * Convenience routine to complain when we expected T_DATUM and got T_WORD,
 * ie, unrecognized variable.
 */
static void
word_is_not_variable(PLword *word, int location, yyscan_t yyscanner)
{
 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, yyscan_t yyscanner)
{
 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, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 if (tok == T_WORD)
  word_is_not_variable(&(yylvalp->word), *yyllocp, yyscanner);
 else if (tok == T_CWORD)
  cword_is_not_variable(&(yylvalp->cword), *yyllocp, yyscanner);
 else
  yyerror(yyllocp, NULL, yyscanner, "syntax error");
}

/* Convenience routine to construct a PLpgSQL_expr struct */
static PLpgSQL_expr *
make_plpgsql_expr(const char *query,
      RawParseMode parsemode)
{
 PLpgSQL_expr *expr = palloc0(sizeof(PLpgSQL_expr));

 expr->query = pstrdup(query);
 expr->parseMode = parsemode;
 expr->func = plpgsql_curr_compile;
 expr->ns = plpgsql_ns_top();
 /* might get changed later during parsing: */
 expr->target_param = -1;
 expr->target_is_local = false;
 /* other fields are left as zeroes until first execution */
 return expr;
}

/* Mark a PLpgSQL_expr as being the source of an assignment to target */
static void
mark_expr_as_assignment_source(PLpgSQL_expr *expr, PLpgSQL_datum *target)
{
 /*
  * Mark the expression as being an assignment source, if target is a
  * simple variable.  We don't currently support optimized assignments to
  * other DTYPEs, so no need to mark in other cases.
  */
 if (target->dtype == PLPGSQL_DTYPE_VAR)
 {
  expr->target_param = target->dno;

  /*
   * For now, assume the target is local to the nearest enclosing
   * exception block.  That's correct if the function contains no
   * exception blocks; otherwise we'll update this later.
   */
  expr->target_is_local = true;
 }
 else
 {
  expr->target_param = -1; /* should be that already */
  expr->target_is_local = false; /* ditto */
 }
}

/* Convenience routine to read an expression with one possible terminator */
static PLpgSQL_expr *
read_sql_expression(int until, const char *expected, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 return read_sql_construct(until, 00, expected,
         RAW_PARSE_PLPGSQL_EXPR,
         true, true, NULL, NULL,
         yylvalp, yyllocp, yyscanner);
}

/* 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, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 return read_sql_construct(until, until2, 0, expected,
         RAW_PARSE_PLPGSQL_EXPR,
         true, true, NULL, endtoken,
         yylvalp, yyllocp, yyscanner);
}

/* Convenience routine to read a SQL statement that must end with ';' */
static PLpgSQL_expr *
read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 return read_sql_construct(';', 00, ";",
         RAW_PARSE_DEFAULT,
         false, true, NULL, NULL,
         yylvalp, yyllocp, yyscanner);
}

/*
 * Read a SQL construct and build a PLpgSQL_expr for it.
 *
 * until:  token code for expected terminator
 * until2:  token code for alternate terminator (pass 0 if none)
 * until3:  token code for another alternate terminator (pass 0 if none)
 * expected: text to use in complaining that terminator was not found
 * parsemode: raw_parser() mode to use
 * isexpression: whether to say we're reading an "expression" or a "statement"
 * valid_sql:   whether to check the syntax of the expr
 * startloc: if not NULL, location of first token is stored at *startloc
 * endtoken: if not NULL, ending token is stored at *endtoken
 *    (this is only interesting if until2 or until3 isn't zero)
 */
static PLpgSQL_expr *
read_sql_construct(int until,
       int until2,
       int until3,
       const char *expected,
       RawParseMode parsemode,
       bool isexpression,
       bool valid_sql,
       int *startloc,
       int *endtoken,
       YYSTYPE *yylvalp, YYLTYPE *yyllocp,
       yyscan_t yyscanner)
{
 int   tok;
 StringInfoData ds;
 IdentifierLookup save_IdentifierLookup;
 int   startlocation = -1;
 int   endlocation = -1;
 int   parenlevel = 0;
 PLpgSQL_expr *expr;

 initStringInfo(&ds);

 /* special lookup mode for identifiers within the SQL text */
 save_IdentifierLookup = plpgsql_IdentifierLookup;
 plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;

 for (;;)
 {
  tok = yylex(yylvalp, yyllocp, yyscanner);
  if (startlocation < 0) /* remember loc of first token */
   startlocation = *yyllocp;
  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(yyllocp, NULL, yyscanner, "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(yyllocp, NULL, yyscanner, "mismatched parentheses");
   if (isexpression)
    ereport(ERROR,
      (errcode(ERRCODE_SYNTAX_ERROR),
       errmsg("missing \"%s\" at end of SQL expression",
        expected),
       parser_errposition(*yyllocp)));
   else
    ereport(ERROR,
      (errcode(ERRCODE_SYNTAX_ERROR),
       errmsg("missing \"%s\" at end of SQL statement",
        expected),
       parser_errposition(*yyllocp)));
  }
  /* Remember end+1 location of last accepted token */
  endlocation = *yyllocp + plpgsql_token_length(yyscanner);
 }

 plpgsql_IdentifierLookup = save_IdentifierLookup;

 if (startloc)
  *startloc = startlocation;
 if (endtoken)
  *endtoken = tok;

 /* give helpful complaint about empty input */
 if (startlocation >= endlocation)
 {
  if (isexpression)
   yyerror(yyllocp, NULL, yyscanner, "missing expression");
  else
   yyerror(yyllocp, NULL, yyscanner, "missing SQL statement");
 }

 /*
  * We save only the text from startlocation to endlocation-1.  This
  * suppresses the "until" token as well as any whitespace or comments
  * following the last accepted token.  (We used to strip such trailing
  * whitespace by hand, but that causes problems if there's a "-- comment"
  * in front of said whitespace.)
  */
 plpgsql_append_source_text(&ds, startlocation, endlocation, yyscanner);

 expr = make_plpgsql_expr(ds.data, parsemode);
 pfree(ds.data);

 if (valid_sql)
  check_sql_expr(expr->query, expr->parseMode, startlocation, yyscanner);

 return expr;
}

/*
 * Read a datatype declaration, consuming the current lookahead token if any.
 * Returns a PLpgSQL_type struct.
 */
static PLpgSQL_type *
read_datatype(int tok, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 StringInfoData ds;
 char    *type_name;
 int   startlocation;
 PLpgSQL_type *result = NULL;
 int   parenlevel = 0;

 /* Should only be called while parsing DECLARE sections */
 Assert(plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_DECLARE);

 /* Often there will be a lookahead token, but if not, get one */
 if (tok == YYEMPTY)
  tok = yylex(yylvalp, yyllocp, yyscanner);

 /* The current token is the start of what we'll pass to parse_datatype */
 startlocation = *yyllocp;

 /*
  * If we have a simple or composite identifier, check for %TYPE and
  * %ROWTYPE constructs.
  */
 if (tok == T_WORD)
 {
  char    *dtname = yylvalp->word.ident;

  tok = yylex(yylvalp, yyllocp, yyscanner);
  if (tok == '%')
  {
   tok = yylex(yylvalp, yyllocp, yyscanner);
   if (tok_is_keyword(tok, yylvalp,
          K_TYPE, "type"))
    result = plpgsql_parse_wordtype(dtname);
   else if (tok_is_keyword(tok, yylvalp,
         K_ROWTYPE, "rowtype"))
    result = plpgsql_parse_wordrowtype(dtname);
  }
 }
 else if (plpgsql_token_is_unreserved_keyword(tok))
 {
  char    *dtname = pstrdup(yylvalp->keyword);

  tok = yylex(yylvalp, yyllocp, yyscanner);
  if (tok == '%')
  {
   tok = yylex(yylvalp, yyllocp, yyscanner);
   if (tok_is_keyword(tok, yylvalp,
          K_TYPE, "type"))
    result = plpgsql_parse_wordtype(dtname);
   else if (tok_is_keyword(tok, yylvalp,
         K_ROWTYPE, "rowtype"))
    result = plpgsql_parse_wordrowtype(dtname);
  }
 }
 else if (tok == T_CWORD)
 {
  List    *dtnames = yylvalp->cword.idents;

  tok = yylex(yylvalp, yyllocp, yyscanner);
  if (tok == '%')
  {
   tok = yylex(yylvalp, yyllocp, yyscanner);
   if (tok_is_keyword(tok, yylvalp,
          K_TYPE, "type"))
    result = plpgsql_parse_cwordtype(dtnames);
   else if (tok_is_keyword(tok, yylvalp,
         K_ROWTYPE, "rowtype"))
    result = plpgsql_parse_cwordrowtype(dtnames);
  }
 }

 /*
  * If we recognized a %TYPE or %ROWTYPE construct, see if it is followed
  * by array decoration: [ ARRAY ] [ '[' [ iconst ] ']' [ ... ] ]
  *
  * Like the core parser, we ignore the specific numbers and sizes of
  * dimensions; arrays of different dimensionality are still the same type
  * in Postgres.
  */
 if (result)
 {
  bool  is_array = false;

  tok = yylex(yylvalp, yyllocp, yyscanner);
  if (tok_is_keyword(tok, yylvalp,
         K_ARRAY, "array"))
  {
   is_array = true;
   tok = yylex(yylvalp, yyllocp, yyscanner);
  }
  while (tok == '[')
  {
   is_array = true;
   tok = yylex(yylvalp, yyllocp, yyscanner);
   if (tok == ICONST)
    tok = yylex(yylvalp, yyllocp, yyscanner);
   if (tok != ']')
    yyerror(yyllocp, NULL, yyscanner, "syntax error, expected \"]\"");
   tok = yylex(yylvalp, yyllocp, yyscanner);
  }
  plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);

  if (is_array)
   result = plpgsql_build_datatype_arrayof(result);

  return result;
 }

 /*
  * Not %TYPE or %ROWTYPE, so scan to the end of the datatype declaration,
  * which could include typmod or array decoration.  We are not very picky
  * here, instead relying on parse_datatype to complain about garbage.  But
  * we must count parens to handle typmods within cursor_arg correctly.
  */
 while (tok != ';')
 {
  if (tok == 0)
  {
   if (parenlevel != 0)
    yyerror(yyllocp, NULL, yyscanner, "mismatched parentheses");
   else
    yyerror(yyllocp, NULL, yyscanner, "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(yylvalp, yyllocp, yyscanner);
 }

 /* set up ds to contain complete typename text */
 initStringInfo(&ds);
 plpgsql_append_source_text(&ds, startlocation, *yyllocp, yyscanner);
 type_name = ds.data;

 if (type_name[0] == '\0')
  yyerror(yyllocp, NULL, yyscanner, "missing data type declaration");

 result = parse_datatype(type_name, startlocation, yyscanner);

 pfree(ds.data);

 plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);

 return result;
}

/*
 * Read a generic SQL statement.  We have already read its first token;
 * firsttoken is that token's code and location its starting location.
 * If firsttoken == T_WORD, pass its yylval value as "word", else pass NULL.
 */
static PLpgSQL_stmt *
make_execsql_stmt(int firsttoken, int location, PLword *word, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 StringInfoData ds;
 IdentifierLookup save_IdentifierLookup;
 PLpgSQL_stmt_execsql *execsql;
 PLpgSQL_expr *expr;
 PLpgSQL_variable *target = NULL;
 int   tok;
 int   prev_tok;
 bool  have_into = false;
 bool  have_strict = false;
 int   into_start_loc = -1;
 int   into_end_loc = -1;
 int   paren_depth = 0;
 int   begin_depth = 0;
 bool  in_routine_definition = false;
 int   token_count = 0;
 char  tokens[4];  /* records the first few tokens */

 initStringInfo(&ds);

 memset(tokens, 0, sizeof(tokens));

 /* special lookup mode for identifiers within the SQL text */
 save_IdentifierLookup = plpgsql_IdentifierLookup;
 plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;

 /*
  * Scan to the end of the SQL command.  Identify any INTO-variables clause
  * lurking within it, and parse that via read_into_target().
  *
  * The end of the statement is defined by a semicolon ... except that
  * semicolons within parentheses or BEGIN/END blocks don't terminate a
  * statement.  We follow psql's lead in not recognizing BEGIN/END except
  * after CREATE [OR REPLACE] {FUNCTION|PROCEDURE}.  END can also appear
  * within a CASE construct, so we treat CASE/END like BEGIN/END.
  *
  * Because INTO is sometimes used in the main SQL grammar, we have to be
  * careful not to take any such usage of INTO as a PL/pgSQL INTO clause.
  * There are currently three such cases:
  *
  * 1. SELECT ... INTO.  We don't care, we just override that with the
  * PL/pgSQL definition.
  *
  * 2. INSERT INTO.  This is relatively easy to recognize since the words
  * must appear adjacently; but we can't assume INSERT starts the command,
  * because it can appear in CREATE RULE or WITH.  Unfortunately, INSERT is
  * *not* fully reserved, so that means there is a chance of a false match;
  * but it's not very likely.
  *
  * 3. IMPORT FOREIGN SCHEMA ... INTO.  This is not allowed in CREATE RULE
  * or WITH, so we just check for IMPORT as the command's first token. (If
  * IMPORT FOREIGN SCHEMA returned data someone might wish to capture with
  * an INTO-variables clause, we'd have to work much harder here.)
  *
  * Fortunately, INTO is a fully reserved word in the main grammar, so at
  * least we need not worry about it appearing as an identifier.
  *
  * Any future additional uses of INTO in the main grammar will doubtless
  * break this logic again ... beware!
  */
 tok = firsttoken;
 if (tok == T_WORD && strcmp(word->ident, "create") == 0)
  tokens[token_count] = 'c';
 token_count++;

 for (;;)
 {
  prev_tok = tok;
  tok = yylex(yylvalp, yyllocp, yyscanner);
  if (have_into && into_end_loc < 0)
   into_end_loc = *yyllocp; /* token after the INTO part */
  /* Detect CREATE [OR REPLACE] {FUNCTION|PROCEDURE} */
  if (tokens[0] == 'c' && token_count < sizeof(tokens))
  {
   if (tok == K_OR)
    tokens[token_count] = 'o';
   else if (tok == T_WORD &&
      strcmp(yylvalp->word.ident, "replace") == 0)
    tokens[token_count] = 'r';
   else if (tok == T_WORD &&
      strcmp(yylvalp->word.ident, "function") == 0)
    tokens[token_count] = 'f';
   else if (tok == T_WORD &&
      strcmp(yylvalp->word.ident, "procedure") == 0)
    tokens[token_count] = 'f'; /* treat same as "function" */
   if (tokens[1] == 'f' ||
    (tokens[1] == 'o' && tokens[2] == 'r' && tokens[3] == 'f'))
    in_routine_definition = true;
   token_count++;
  }
  /* Track paren nesting (needed for CREATE RULE syntax) */
  if (tok == '(')
   paren_depth++;
  else if (tok == ')' && paren_depth > 0)
   paren_depth--;
  /* We need track BEGIN/END nesting only in a routine definition */
  if (in_routine_definition && paren_depth == 0)
  {
   if (tok == K_BEGIN || tok == K_CASE)
    begin_depth++;
   else if (tok == K_END && begin_depth > 0)
    begin_depth--;
  }
  /* Command-ending semicolon? */
  if (tok == ';' && paren_depth == 0 && begin_depth == 0)
   break;
  if (tok == 0)
   yyerror(yyllocp, NULL, yyscanner, "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(yyllocp, NULL, yyscanner, "INTO specified more than once");
   have_into = true;
   into_start_loc = *yyllocp;
   plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
   read_into_target(&target, &have_strict, yylvalp, yyllocp, yyscanner);
   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, yyscanner);
  appendStringInfoSpaces(&ds, into_end_loc - into_start_loc);
  plpgsql_append_source_text(&ds, into_end_loc, *yyllocp, yyscanner);
 }
 else
  plpgsql_append_source_text(&ds, location, *yyllocp, yyscanner);

 /* trim any trailing whitespace, for neatness */
 while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
  ds.data[--ds.len] = '\0';

 expr = make_plpgsql_expr(ds.data, RAW_PARSE_DEFAULT);
 pfree(ds.data);

 check_sql_expr(expr->query, expr->parseMode, location, yyscanner);

 execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
 execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
 execsql->lineno = plpgsql_location_to_lineno(location, yyscanner);
 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(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 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(yylvalp, yyllocp, yyscanner);
 if (tok == 0)
  yyerror(yyllocp, NULL, yyscanner, "unexpected end of function definition");

 if (tok_is_keyword(tok, yylvalp,
        K_NEXT, "next"))
 {
  /* use defaults */
 }
 else if (tok_is_keyword(tok, yylvalp,
       K_PRIOR, "prior"))
 {
  fetch->direction = FETCH_BACKWARD;
 }
 else if (tok_is_keyword(tok, yylvalp,
       K_FIRST, "first"))
 {
  fetch->direction = FETCH_ABSOLUTE;
 }
 else if (tok_is_keyword(tok, yylvalp,
       K_LAST, "last"))
 {
  fetch->direction = FETCH_ABSOLUTE;
  fetch->how_many = -1;
 }
 else if (tok_is_keyword(tok, yylvalp,
       K_ABSOLUTE, "absolute"))
 {
  fetch->direction = FETCH_ABSOLUTE;
  fetch->expr = read_sql_expression2(K_FROM, K_IN,
             "FROM or IN",
             NULL, yylvalp, yyllocp, yyscanner);
  check_FROM = false;
 }
 else if (tok_is_keyword(tok, yylvalp,
       K_RELATIVE, "relative"))
 {
  fetch->direction = FETCH_RELATIVE;
  fetch->expr = read_sql_expression2(K_FROM, K_IN,
             "FROM or IN",
             NULL, yylvalp, yyllocp, yyscanner);
  check_FROM = false;
 }
 else if (tok_is_keyword(tok, yylvalp,
       K_ALL, "all"))
 {
  fetch->how_many = FETCH_ALL;
  fetch->returns_multiple_rows = true;
 }
 else if (tok_is_keyword(tok, yylvalp,
       K_FORWARD, "forward"))
 {
  complete_direction(fetch, &check_FROM, yylvalp, yyllocp, yyscanner);
 }
 else if (tok_is_keyword(tok, yylvalp,
       K_BACKWARD, "backward"))
 {
  fetch->direction = FETCH_BACKWARD;
  complete_direction(fetch, &check_FROM, yylvalp, yyllocp, yyscanner);
 }
 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, yylvalp, yyllocp, yyscanner);
  check_FROM = false;
 }
 else
 {
  /*
   * Assume it's a count expression with no preceding keyword. Note: we
   * allow this syntax because core SQL does, but it's ambiguous with
   * the case of an omitted direction clause; for instance, "MOVE n IN
   * c" will fail if n is a variable, because the preceding else-arm
   * will trigger.  Perhaps this can be improved someday, but it hardly
   * seems worth a lot of work.
   */
  plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
  fetch->expr = read_sql_expression2(K_FROM, K_IN,
             "FROM or IN",
             NULL, yylvalp, yyllocp, yyscanner);
  fetch->returns_multiple_rows = true;
  check_FROM = false;
 }

 /* check FROM or IN keyword after direction's specification */
 if (check_FROM)
 {
  tok = yylex(yylvalp, yyllocp, yyscanner);
  if (tok != K_FROM && tok != K_IN)
   yyerror(yyllocp, NULL, yyscanner, "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, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 int   tok;

 tok = yylex(yylvalp, yyllocp, yyscanner);
 if (tok == 0)
  yyerror(yyllocp, NULL, yyscanner, "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, yylvalp, yyllocp, yyscanner);
 fetch->expr = read_sql_expression2(K_FROM, K_IN,
            "FROM or IN",
            NULL, yylvalp, yyllocp, yyscanner);
 fetch->returns_multiple_rows = true;
 *check_FROM = false;
}


static PLpgSQL_stmt *
make_return_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 PLpgSQL_stmt_return *new;

 new = palloc0(sizeof(PLpgSQL_stmt_return));
 new->cmd_type = PLPGSQL_STMT_RETURN;
 new->lineno = plpgsql_location_to_lineno(location, yyscanner);
 new->stmtid = ++plpgsql_curr_compile->nstatements;
 new->expr = NULL;
 new->retvarno = -1;

 if (plpgsql_curr_compile->fn_retset)
 {
  if (yylex(yylvalp, yyllocp, yyscanner) != ';')
   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(*yyllocp)));
 }
 else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
 {
  if (yylex(yylvalp, yyllocp, yyscanner) != ';')
  {
   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(*yyllocp)));
   else
    ereport(ERROR,
      (errcode(ERRCODE_DATATYPE_MISMATCH),
       errmsg("RETURN cannot have a parameter in function returning void"),
       parser_errposition(*yyllocp)));
  }
 }
 else if (plpgsql_curr_compile->out_param_varno >= 0)
 {
  if (yylex(yylvalp, yyllocp, yyscanner) != ';')
   ereport(ERROR,
     (errcode(ERRCODE_DATATYPE_MISMATCH),
      errmsg("RETURN cannot have a parameter in function with OUT parameters"),
      parser_errposition(*yyllocp)));
  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(yylvalp, yyllocp, yyscanner);

  if (tok == T_DATUM && plpgsql_peek(yyscanner) == ';' &&
   (yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_VAR ||
    yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE ||
    yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
    yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
  {
   new->retvarno = yylvalp->wdatum.datum->dno;
   /* eat the semicolon token that we only peeked at above */
   tok = yylex(yylvalp, yyllocp, yyscanner);
   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, yylvalp, yyllocp, yyscanner);
   new->expr = read_sql_expression(';', ";", yylvalp, yyllocp, yyscanner);
  }
 }

 return (PLpgSQL_stmt *) new;
}


static PLpgSQL_stmt *
make_return_next_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 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, yyscanner);
 new->stmtid = ++plpgsql_curr_compile->nstatements;
 new->expr = NULL;
 new->retvarno = -1;

 if (plpgsql_curr_compile->out_param_varno >= 0)
 {
  if (yylex(yylvalp, yyllocp, yyscanner) != ';')
   ereport(ERROR,
     (errcode(ERRCODE_DATATYPE_MISMATCH),
      errmsg("RETURN NEXT cannot have a parameter in function with OUT parameters"),
      parser_errposition(*yyllocp)));
  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(yylvalp, yyllocp, yyscanner);

  if (tok == T_DATUM && plpgsql_peek(yyscanner) == ';' &&
   (yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_VAR ||
    yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE ||
    yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
    yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
  {
   new->retvarno = yylvalp->wdatum.datum->dno;
   /* eat the semicolon token that we only peeked at above */
   tok = yylex(yylvalp, yyllocp, yyscanner);
   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, yylvalp, yyllocp, yyscanner);
   new->expr = read_sql_expression(';', ";", yylvalp, yyllocp, yyscanner);
  }
 }

 return (PLpgSQL_stmt *) new;
}


static PLpgSQL_stmt *
make_return_query_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 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, yyscanner);
 new->stmtid = ++plpgsql_curr_compile->nstatements;

 /* check for RETURN QUERY EXECUTE */
 if ((tok = yylex(yylvalp, yyllocp, yyscanner)) != K_EXECUTE)
 {
  /* ordinary static query */
  plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
  new->query = read_sql_stmt(yylvalp, yyllocp, yyscanner);
 }
 else
 {
  /* dynamic SQL */
  int   term;

  new->dynquery = read_sql_expression2(';', K_USING, "; or USING",
            &term, yylvalp, yyllocp, yyscanner);
  if (term == K_USING)
  {
   do
   {
    PLpgSQL_expr *expr;

    expr = read_sql_expression2(',', ';', ", or ;", &term, yylvalp, yyllocp, yyscanner);
    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, yyscan_t yyscanner)
{
 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, yyscanner);
   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, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 int   tok;

 /* Set default results */
 *target = NULL;
 if (strict)
  *strict = false;

 tok = yylex(yylvalp, yyllocp, yyscanner);
 if (strict && tok == K_STRICT)
 {
  *strict = true;
  tok = yylex(yylvalp, yyllocp, yyscanner);
 }

 /*
  * 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 (yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
    yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
   {
    check_assignable(yylvalp->wdatum.datum, *yyllocp, yyscanner);
    *target = (PLpgSQL_variable *) yylvalp->wdatum.datum;

    if ((tok = yylex(yylvalp, yyllocp, yyscanner)) == ',')
     ereport(ERROR,
       (errcode(ERRCODE_SYNTAX_ERROR),
        errmsg("record variable cannot be part of multiple-item INTO list"),
        parser_errposition(*yyllocp)));
    plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
   }
   else
   {
    *target = (PLpgSQL_variable *)
     read_into_scalar_list(NameOfDatum(&(yylvalp->wdatum)),
            yylvalp->wdatum.datum, *yyllocp, yylvalp, yyllocp, yyscanner);
   }
   break;

  default:
   /* just to give a better message than "syntax error" */
   current_token_is_not_variable(tok, yylvalp, yyllocp, yyscanner);
 }
}

/*
 * 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,
       YYSTYPE *yylvalp, YYLTYPE *yyllocp,
       yyscan_t yyscanner)
{
 int   nfields;
 char    *fieldnames[1024];
 int   varnos[1024];
 PLpgSQL_row *row;
 int   tok;

 check_assignable(initial_datum, initial_location, yyscanner);
 fieldnames[0] = initial_name;
 varnos[0] = initial_datum->dno;
 nfields = 1;

 while ((tok = yylex(yylvalp, yyllocp, yyscanner)) == ',')
 {
  /* Check for array overflow */
  if (nfields >= 1024)
   ereport(ERROR,
     (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
      errmsg("too many INTO variables specified"),
      parser_errposition(*yyllocp)));

  tok = yylex(yylvalp, yyllocp, yyscanner);
  switch (tok)
  {
   case T_DATUM:
    check_assignable(yylvalp->wdatum.datum, *yyllocp, yyscanner);
    if (yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
     yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
     ereport(ERROR,
       (errcode(ERRCODE_SYNTAX_ERROR),
        errmsg("\"%s\" is not a scalar variable",
         NameOfDatum(&(yylvalp->wdatum))),
        parser_errposition(*yyllocp)));
    fieldnames[nfields] = NameOfDatum(&(yylvalp->wdatum));
    varnos[nfields++] = yylvalp->wdatum.datum->dno;
    break;

   default:
    /* just to give a better message than "syntax error" */
    current_token_is_not_variable(tok, yylvalp, yyllocp, yyscanner);
  }
 }

 /*
  * We read an extra, non-comma token from yylex(), so push it back onto
  * the input stream
  */
 plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);

 row = palloc0(sizeof(PLpgSQL_row));
 row->dtype = PLPGSQL_DTYPE_ROW;
 row->refname = "(unnamed row)";
 row->lineno = plpgsql_location_to_lineno(initial_location, yyscanner);
 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, yyscan_t yyscanner)
{
 PLpgSQL_row *row;

 check_assignable(initial_datum, location, yyscanner);

 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, yyscan_t yyscanner)
{
 sql_error_callback_arg cbarg;
 ErrorContextCallback syntax_errcontext;
 MemoryContext oldCxt;

 if (!plpgsql_check_syntax)
  return;

 cbarg.location = location;
 cbarg.yyscanner = yyscanner;

 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;
 yyscan_t yyscanner = cbarg->yyscanner;
 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, yyscan_t yyscanner)
{
 TypeName   *typeName;
 Oid   type_id;
 int32  typmod;
 sql_error_callback_arg cbarg;
 ErrorContextCallback syntax_errcontext;

 cbarg.location = location;
 cbarg.yyscanner = yyscanner;

 syntax_errcontext.callback = plpgsql_sql_error_callback;
 syntax_errcontext.arg = &cbarg;
 syntax_errcontext.previous = error_context_stack;
 error_context_stack = &syntax_errcontext;

 /* Let the main parser try to parse it under standard SQL rules */
 typeName = typeStringToTypeName(string, NULL);
 typenameTypeIdAndMod(NULL, typeName, &type_id, &typmod);

 /* Restore former ereport callback */
 error_context_stack = syntax_errcontext.previous;

 /* Okay, build a PLpgSQL_type data structure for it */
 return plpgsql_build_datatype(type_id, typmod,
          plpgsql_curr_compile->fn_input_collation,
          typeName);
}

/*
 * Check block starting and ending labels match.
 */
static void
check_labels(const char *start_label, const char *end_label, int end_location, yyscan_t yyscanner)
{
 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, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 PLpgSQL_expr *expr;
 PLpgSQL_row *row;
 int   tok;
 int   argc;
 char   **argv;
 StringInfoData ds;
 bool  any_named = false;

 tok = yylex(yylvalp, yyllocp, yyscanner);
 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(*yyllocp)));

  if (tok != until)
   yyerror(yyllocp, NULL, yyscanner, "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(*yyllocp)));

 /*
  * 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"
   * or "param => value"
   */
  plpgsql_peek2(&tok1, &tok2, &arglocation, NULL, yyscanner);
  if (tok1 == IDENT && (tok2 == COLON_EQUALS || tok2 == EQUALS_GREATER))
  {
   char    *argname;
   IdentifierLookup save_IdentifierLookup;

   /* Read the argument name, ignoring any matching variable */
   save_IdentifierLookup = plpgsql_IdentifierLookup;
   plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
   yylex(yylvalp, yyllocp, yyscanner);
   argname = yylvalp->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(*yyllocp)));

   /*
    * Eat the ":=" or "=>".  We already peeked, so the error should
    * never happen.
    */
   tok2 = yylex(yylvalp, yyllocp, yyscanner);
   if (tok2 != COLON_EQUALS && tok2 != EQUALS_GREATER)
    yyerror(yyllocp, NULL, yyscanner, "syntax error");

   any_named = true;
  }
  else
   argpos = argc;

  if (argv[argpos] != NULL)
   ereport(ERROR,
     (errcode(ERRCODE_SYNTAX_ERROR),
      errmsg("value for parameter \"%s\" of cursor \"%s\" specified more than once",
       row->fieldnames[argpos], cursor->refname),
      parser_errposition(arglocation)));

  /*
   * Read the value expression. To provide the user with meaningful
   * parse error positions, we check the syntax immediately, instead of
   * checking the final expression that may have the arguments
   * reordered.
   */
  item = read_sql_construct(',', ')', 0,
          ",\" or \")",
          RAW_PARSE_PLPGSQL_EXPR,
          true, true,
          NULL, &endtoken,
          yylvalp, yyllocp, yyscanner);

  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(*yyllocp)));

  if (endtoken == ',' && (argc == row->nfields - 1))
   ereport(ERROR,
     (errcode(ERRCODE_SYNTAX_ERROR),
      errmsg("too many arguments for cursor \"%s\"",
       cursor->refname),
      parser_errposition(*yyllocp)));
 }

 /* 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 = make_plpgsql_expr(ds.data, RAW_PARSE_PLPGSQL_EXPR);
 pfree(ds.data);

 /* Next we'd better find the until token */
 tok = yylex(yylvalp, yyllocp, yyscanner);
 if (tok != until)
  yyerror(yyllocp, NULL, yyscanner, "syntax error");

 return expr;
}

/*
 * Parse RAISE ... USING options
 */
static List *
read_raise_options(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
 List    *result = NIL;

 for (;;)
 {
  PLpgSQL_raise_option *opt;
  int   tok;

  if ((tok = yylex(yylvalp, yyllocp, yyscanner)) == 0)
   yyerror(yyllocp, NULL, yyscanner, "unexpected end of function definition");

  opt = (PLpgSQL_raise_option *) palloc(sizeof(PLpgSQL_raise_option));

  if (tok_is_keyword(tok, yylvalp,
         K_ERRCODE, "errcode"))
   opt->opt_type = PLPGSQL_RAISEOPTION_ERRCODE;
  else if (tok_is_keyword(tok, yylvalp,
        K_MESSAGE, "message"))
   opt->opt_type = PLPGSQL_RAISEOPTION_MESSAGE;
  else if (tok_is_keyword(tok, yylvalp,
        K_DETAIL, "detail"))
   opt->opt_type = PLPGSQL_RAISEOPTION_DETAIL;
  else if (tok_is_keyword(tok, yylvalp,
        K_HINT, "hint"))
   opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
  else if (tok_is_keyword(tok, yylvalp,
        K_COLUMN, "column"))
   opt->opt_type = PLPGSQL_RAISEOPTION_COLUMN;
  else if (tok_is_keyword(tok, yylvalp,
        K_CONSTRAINT, "constraint"))
   opt->opt_type = PLPGSQL_RAISEOPTION_CONSTRAINT;
  else if (tok_is_keyword(tok, yylvalp,
        K_DATATYPE, "datatype"))
   opt->opt_type = PLPGSQL_RAISEOPTION_DATATYPE;
  else if (tok_is_keyword(tok, yylvalp,
        K_TABLE, "table"))
   opt->opt_type = PLPGSQL_RAISEOPTION_TABLE;
  else if (tok_is_keyword(tok, yylvalp,
        K_SCHEMA, "schema"))
   opt->opt_type = PLPGSQL_RAISEOPTION_SCHEMA;
  else
   yyerror(yyllocp, NULL, yyscanner, "unrecognized RAISE statement option");

  tok = yylex(yylvalp, yyllocp, yyscanner);
  if (tok != '=' && tok != COLON_EQUALS)
   yyerror(yyllocp, NULL, yyscanner, "syntax error, expected \"=\"");

  opt->expr = read_sql_expression2(',', ';', ", or ;", &tok, yylvalp, yyllocp, yyscanner);

  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, yyscan_t yyscanner)
{
 PLpgSQL_stmt_case *new;

 new = palloc(sizeof(PLpgSQL_stmt_case));
 new->cmd_type = PLPGSQL_STMT_CASE;
 new->lineno = plpgsql_location_to_lineno(location, yyscanner);
 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;
}

[Verzeichnis aufwärts0.67unsichere VerbindungÜbersetzung europäischer Sprachen durch Browser2026-08-08]