]> granicus.if.org Git - postgresql/blobdiff - src/pl/plpgsql/src/gram.y
Make all our flex and bison files use %option prefix or %name-prefix
[postgresql] / src / pl / plpgsql / src / gram.y
index 30bc55f69e7f419cd7af2df6d1975b090178a8c4..c92438d9b46c7fe4ee8f8cce0706198d66b1f4eb 100644 (file)
 %{
 /**********************************************************************
- * gram.y              - Parser for the PL/pgSQL
- *                       procedural language
+ * gram.y                              - Parser for the PL/pgSQL
+ *                                               procedural language
  *
  * IDENTIFICATION
- *    $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.1 1998/08/24 19:14:47 momjian Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.86 2006/03/07 01:03:12 tgl Exp $
  *
- *    This software is copyrighted by Jan Wieck - Hamburg.
+ *       This software is copyrighted by Jan Wieck - Hamburg.
  *
- *    The author hereby grants permission  to  use,  copy,  modify,
- *    distribute,  and  license this software and its documentation
- *    for any purpose, provided that existing copyright notices are
- *    retained  in  all  copies  and  that  this notice is included
- *    verbatim in any distributions. No written agreement, license,
- *    or  royalty  fee  is required for any of the authorized uses.
- *    Modifications to this software may be  copyrighted  by  their
- *    author  and  need  not  follow  the licensing terms described
- *    here, provided that the new terms are  clearly  indicated  on
- *    the first page of each file where they apply.
+ *       The author hereby grants permission  to  use,  copy,  modify,
+ *       distribute,  and      license this software and its documentation
+ *       for any purpose, provided that existing copyright notices are
+ *       retained      in      all  copies  and  that  this notice is included
+ *       verbatim in any distributions. No written agreement, license,
+ *       or  royalty  fee      is required for any of the authorized uses.
+ *       Modifications to this software may be  copyrighted  by  their
+ *       author  and  need  not  follow  the licensing terms described
+ *       here, provided that the new terms are  clearly  indicated  on
+ *       the first page of each file where they apply.
  *
- *    IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY
- *    PARTY  FOR  DIRECT,   INDIRECT,   SPECIAL,   INCIDENTAL,   OR
- *    CONSEQUENTIAL   DAMAGES  ARISING  OUT  OF  THE  USE  OF  THIS
- *    SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
- *    IF  THE  AUTHOR  HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
- *    DAMAGE.
+ *       IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY
+ *       PARTY  FOR  DIRECT,   INDIRECT,       SPECIAL,   INCIDENTAL,   OR
+ *       CONSEQUENTIAL   DAMAGES  ARISING      OUT  OF  THE  USE  OF  THIS
+ *       SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
+ *       IF  THE  AUTHOR  HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ *       DAMAGE.
  *
- *    THE  AUTHOR  AND  DISTRIBUTORS  SPECIFICALLY   DISCLAIM   ANY
- *    WARRANTIES,  INCLUDING,  BUT  NOT  LIMITED  TO,  THE  IMPLIED
- *    WARRANTIES  OF  MERCHANTABILITY,  FITNESS  FOR  A  PARTICULAR
- *    PURPOSE,  AND NON-INFRINGEMENT.  THIS SOFTWARE IS PROVIDED ON
- *    AN "AS IS" BASIS, AND THE AUTHOR  AND  DISTRIBUTORS  HAVE  NO
- *    OBLIGATION   TO   PROVIDE   MAINTENANCE,   SUPPORT,  UPDATES,
- *    ENHANCEMENTS, OR MODIFICATIONS.
+ *       THE  AUTHOR  AND      DISTRIBUTORS  SPECIFICALLY       DISCLAIM       ANY
+ *       WARRANTIES,  INCLUDING,  BUT  NOT  LIMITED  TO,  THE  IMPLIED
+ *       WARRANTIES  OF  MERCHANTABILITY,      FITNESS  FOR  A  PARTICULAR
+ *       PURPOSE,      AND NON-INFRINGEMENT.  THIS SOFTWARE IS PROVIDED ON
+ *       AN "AS IS" BASIS, AND THE AUTHOR      AND  DISTRIBUTORS  HAVE  NO
+ *       OBLIGATION   TO       PROVIDE   MAINTENANCE,   SUPPORT,  UPDATES,
+ *       ENHANCEMENTS, OR MODIFICATIONS.
  *
  **********************************************************************/
 
-#include "stdio.h"
-#include "string.h"
 #include "plpgsql.h"
 
-extern int     yylineno;
-extern char    yytext[];
-
-static PLpgSQL_expr    *read_sqlstmt(int until, char *s, char *sqlstart);
+#include "parser/parser.h"
+
+static PLpgSQL_expr            *read_sql_construct(int until,
+                                                                                       int until2,
+                                                                                       const char *expected,
+                                                                                       const char *sqlstart,
+                                                                                       bool isexpression,
+                                                                                       bool valid_sql,
+                                                                                       int *endtoken);
+static PLpgSQL_expr    *read_sql_stmt(const char *sqlstart);
+static PLpgSQL_type    *read_datatype(int tok);
 static PLpgSQL_stmt    *make_select_stmt(void);
-static PLpgSQL_expr    *make_tupret_expr(PLpgSQL_row *row);
+static PLpgSQL_stmt    *make_fetch_stmt(void);
+static void                     check_assignable(PLpgSQL_datum *datum);
+static PLpgSQL_row             *read_into_scalar_list(const char *initial_name,
+                                                                                          PLpgSQL_datum *initial_datum);
+static PLpgSQL_row             *make_scalar_list1(const char *initial_name,
+                                                                                  PLpgSQL_datum *initial_datum,
+                                                                                  int lineno);
+static void                     check_sql_expr(const char *stmt);
+static void                     plpgsql_sql_error_callback(void *arg);
+static void                     check_labels(const char *start_label,
+                                                                         const char *end_label);
 
 %}
 
+%name-prefix="plpgsql_yy"
+
 %union {
-       int32                   ival;
-       char                    *str;
-       struct {
-           char *name;
-           int  lineno;
-       }                       varname;
-       struct {
-           int  nalloc;
-           int  nused;
-           int  *dtnums;
-       }                       dtlist;
-       struct {
-           int  reverse;
-           PLpgSQL_expr *expr;
-       }                       forilow;
-       struct {
-           char *label;
-           int  n_initvars;
-           int  *initvarnos;
-       }                       declhdr;
-       PLpgSQL_type            *dtype;
-       PLpgSQL_var             *var;
-       PLpgSQL_row             *row;
-       PLpgSQL_rec             *rec;
-       PLpgSQL_recfield        *recfield;
-       PLpgSQL_trigarg         *trigarg;
-       PLpgSQL_expr            *expr;
-       PLpgSQL_stmt            *stmt;
-       PLpgSQL_stmts           *stmts;
-       PLpgSQL_stmt_block      *program;
-       PLpgSQL_nsitem          *nsitem;
+               int32                                   ival;
+               bool                                    boolean;
+               char                                    *str;
+               struct
+               {
+                       char *name;
+                       int  lineno;
+               }                                               varname;
+               struct
+               {
+                       char *name;
+                       int  lineno;
+                       PLpgSQL_datum   *scalar;
+                       PLpgSQL_rec     *rec;
+                       PLpgSQL_row     *row;
+               }                                               forvariable;
+               struct
+               {
+                       char *label;
+                       int  n_initvars;
+                       int  *initvarnos;
+               }                                               declhdr;
+               struct
+               {
+                       char *end_label;
+                       List *stmts;
+               }                                               loop_body;
+               List                                    *list;
+               PLpgSQL_type                    *dtype;
+               PLpgSQL_datum                   *scalar;        /* a VAR, RECFIELD, or TRIGARG */
+               PLpgSQL_variable                *variable;      /* a VAR, REC, or ROW */
+               PLpgSQL_var                             *var;
+               PLpgSQL_row                             *row;
+               PLpgSQL_rec                             *rec;
+               PLpgSQL_expr                    *expr;
+               PLpgSQL_stmt                    *stmt;
+               PLpgSQL_stmt_block              *program;
+               PLpgSQL_condition               *condition;
+               PLpgSQL_exception               *exception;
+               PLpgSQL_exception_block *exception_block;
+               PLpgSQL_nsitem                  *nsitem;
+               PLpgSQL_diag_item               *diagitem;
 }
 
-%type <declhdr>        decl_sect
-%type <varname>        decl_varname
-%type <str>    decl_renname
-%type <ival>   decl_const, decl_notnull, decl_atttypmod, decl_atttypmodval
-%type <expr>   decl_defval
-%type <dtype>  decl_datatype, decl_dtypename
-%type <row>    decl_rowtype
+%type <declhdr> decl_sect
+%type <varname> decl_varname
+%type <str>            decl_renname
+%type <boolean>        decl_const decl_notnull exit_type
+%type <expr>   decl_defval decl_cursor_query
+%type <dtype>  decl_datatype
+%type <row>            decl_cursor_args
+%type <list>   decl_cursor_arglist
 %type <nsitem> decl_aliasitem
-%type <str>    decl_stmts, decl_stmt
+%type <str>            decl_stmts decl_stmt
 
-%type <expr>   expr_until_semi, expr_until_then, expr_until_loop
+%type <expr>   expr_until_semi expr_until_rightbracket
+%type <expr>   expr_until_then expr_until_loop
 %type <expr>   opt_exitcond
 
-%type <ival>   assign_var
-%type <var>    fori_var
-%type <varname>        fori_varname
-%type <forilow>        fori_lower
-%type <rec>    fors_target
+%type <ival>   assign_var cursor_variable
+%type <var>            cursor_varptr
+%type <variable>       decl_cursor_arg
+%type <forvariable>    for_variable
+%type <stmt>   for_control
+
+%type <str>            opt_lblname opt_block_label opt_label
+%type <str>            execsql_start
+
+%type <list>   proc_sect proc_stmts 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_return_next stmt_raise stmt_execsql
+%type <stmt>   stmt_for stmt_select stmt_perform
+%type <stmt>   stmt_dynexecute stmt_getdiag
+%type <stmt>   stmt_open stmt_fetch stmt_close stmt_null
+
+%type <list>   proc_exceptions
+%type <exception_block> exception_sect
+%type <exception>      proc_exception
+%type <condition>      proc_conditions
 
-%type <str>    opt_lblname, opt_label
-%type <str>    opt_exitlabel
-%type <str>    execsql_start
 
-%type <stmts>  proc_sect, proc_stmts, stmt_else, 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_execsql, stmt_fori
-%type <stmt>   stmt_fors, stmt_select, stmt_perform
+%type <ival>   raise_level
+%type <str>            raise_msg
 
-%type <dtlist> raise_params
-%type <ival>   raise_level, raise_param
-%type <str>    raise_msg
+%type <list>   getdiag_list
+%type <diagitem> getdiag_list_item
+%type <ival>   getdiag_kind getdiag_target
 
 %type <ival>   lno
 
-       /*
-        * Keyword tokens
-        */
+               /*
+                * Keyword tokens
+                */
 %token K_ALIAS
 %token K_ASSIGN
 %token K_BEGIN
+%token K_CLOSE
 %token K_CONSTANT
+%token K_CONTINUE
+%token K_CURSOR
 %token K_DEBUG
 %token K_DECLARE
 %token K_DEFAULT
+%token K_DIAGNOSTICS
 %token K_DOTDOT
 %token K_ELSE
+%token K_ELSIF
 %token K_END
 %token K_EXCEPTION
+%token K_EXECUTE
 %token K_EXIT
 %token K_FOR
+%token K_FETCH
 %token K_FROM
+%token K_GET
 %token K_IF
 %token K_IN
+%token K_INFO
 %token K_INTO
+%token K_IS
+%token K_LOG
 %token K_LOOP
+%token K_NEXT
 %token K_NOT
 %token K_NOTICE
 %token K_NULL
+%token K_OPEN
+%token K_OR
 %token K_PERFORM
+%token K_ROW_COUNT
 %token K_RAISE
-%token K_RECORD
 %token K_RENAME
+%token K_RESULT_OID
 %token K_RETURN
+%token K_RETURN_NEXT
 %token K_REVERSE
 %token K_SELECT
 %token K_THEN
 %token K_TO
 %token K_TYPE
+%token K_WARNING
 %token K_WHEN
 %token K_WHILE
 
-       /*
-        * Other tokens
-        */
+               /*
+                * Other tokens
+                */
 %token T_FUNCTION
 %token T_TRIGGER
-%token T_CHAR
-%token T_BPCHAR
-%token T_VARCHAR
-%token T_LABEL
 %token T_STRING
-%token T_VARIABLE
+%token T_NUMBER
+%token T_SCALAR                                /* a VAR, RECFIELD, or TRIGARG */
 %token T_ROW
-%token T_ROWTYPE
 %token T_RECORD
-%token T_RECFIELD
-%token T_TGARGV
 %token T_DTYPE
+%token T_LABEL
 %token T_WORD
-%token T_NUMBER
 %token T_ERROR
 
 %token O_OPTION
@@ -181,1280 +233,2139 @@ static        PLpgSQL_expr    *make_tupret_expr(PLpgSQL_row *row);
 
 %%
 
-pl_function    : T_FUNCTION comp_optsect pl_block
-                   {
-                       yylval.program = (PLpgSQL_stmt_block *)$3;
-                   }
-               | T_TRIGGER comp_optsect pl_block
-                   {
-                       yylval.program = (PLpgSQL_stmt_block *)$3;
-                   }
-               ;
+pl_function            : T_FUNCTION comp_optsect pl_block opt_semi
+                                       {
+                                               yylval.program = (PLpgSQL_stmt_block *)$3;
+                                       }
+                               | T_TRIGGER comp_optsect pl_block opt_semi
+                                       {
+                                               yylval.program = (PLpgSQL_stmt_block *)$3;
+                                       }
+                               ;
 
 comp_optsect   :
-               | comp_options
-               ;
+                               | comp_options
+                               ;
 
 comp_options   : comp_options comp_option
-               | comp_option
-               ;
-
-comp_option    : O_OPTION O_DUMP
-                   {
-                       plpgsql_DumpExecTree = 1;
-                   }
-               ;
-
-pl_block       : decl_sect K_BEGIN lno proc_sect K_END ';'
-                   {
-                       PLpgSQL_stmt_block *new;
-
-                       new = malloc(sizeof(PLpgSQL_stmt_block));
-                       memset(new, 0, sizeof(PLpgSQL_stmt_block));
-
-                       new->cmd_type   = PLPGSQL_STMT_BLOCK;
-                       new->lineno     = $3;
-                       new->label      = $1.label;
-                       new->n_initvars = $1.n_initvars;
-                       new->initvarnos = $1.initvarnos;
-                       new->body       = $4;
-
-                       plpgsql_ns_pop();
-
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               ;
-
-
-decl_sect      : opt_label
-                   {
-                       plpgsql_ns_setlocal(false);
-                       $$.label      = $1;
-                       $$.n_initvars = 0;
-                       $$.initvarnos = NULL;
-                       plpgsql_add_initdatums(NULL);
-                   }
-               | opt_label decl_start
-                   {
-                       plpgsql_ns_setlocal(false);
-                       $$.label      = $1;
-                       $$.n_initvars = 0;
-                       $$.initvarnos = NULL;
-                       plpgsql_add_initdatums(NULL);
-                   }
-               | opt_label decl_start decl_stmts
-                   {
-                       plpgsql_ns_setlocal(false);
-                       if ($3 != NULL) {
-                           $$.label = $3;
-                       } else {
-                           $$.label = $1;
-                       }
-                       $$.n_initvars = plpgsql_add_initdatums(&($$.initvarnos));
-                   }
-               ;
-
-decl_start     : K_DECLARE
-                   {
-                       plpgsql_ns_setlocal(true);
-                   }
-               ;
-
-decl_stmts     : decl_stmts decl_stmt
-                   {
-                       $$ = $2;
-                   }
-               | decl_stmt
-                   {
-                       $$ = $1;
-                   }
-               ;
-
-decl_stmt      : '<' '<' opt_lblname '>' '>'
-                   {
-                       $$ = $3;
-                   }
-               | K_DECLARE
-                   {
-                       $$ = NULL;
-                   }
-               | decl_statement
-                   {
-                       $$ = NULL;
-                   }
-               ;
+                               | comp_option
+                               ;
+
+comp_option            : O_OPTION O_DUMP
+                                       {
+                                               plpgsql_DumpExecTree = true;
+                                       }
+                               ;
+
+opt_semi               :
+                               | ';'
+                               ;
+
+pl_block               : decl_sect K_BEGIN lno 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             = $3;
+                                               new->label              = $1.label;
+                                               new->n_initvars = $1.n_initvars;
+                                               new->initvarnos = $1.initvarnos;
+                                               new->body               = $4;
+                                               new->exceptions = $5;
+
+                                               check_labels($1.label, $7);
+                                               plpgsql_ns_pop();
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+
+decl_sect              : opt_block_label
+                                       {
+                                               plpgsql_ns_setlocal(false);
+                                               $$.label          = $1;
+                                               $$.n_initvars = 0;
+                                               $$.initvarnos = NULL;
+                                               plpgsql_add_initdatums(NULL);
+                                       }
+                               | opt_block_label decl_start
+                                       {
+                                               plpgsql_ns_setlocal(false);
+                                               $$.label          = $1;
+                                               $$.n_initvars = 0;
+                                               $$.initvarnos = NULL;
+                                               plpgsql_add_initdatums(NULL);
+                                       }
+                               | opt_block_label decl_start decl_stmts
+                                       {
+                                               plpgsql_ns_setlocal(false);
+                                               if ($3 != NULL)
+                                                       $$.label = $3;
+                                               else
+                                                       $$.label = $1;
+                                               $$.n_initvars = plpgsql_add_initdatums(&($$.initvarnos));
+                                       }
+                               ;
+
+decl_start             : K_DECLARE
+                                       {
+                                               plpgsql_ns_setlocal(true);
+                                       }
+                               ;
+
+decl_stmts             : decl_stmts decl_stmt
+                                       {       $$ = $2;        }
+                               | decl_stmt
+                                       {       $$ = $1;        }
+                               ;
+
+decl_stmt              : '<' '<' opt_lblname '>' '>'
+                                       {       $$ = $3;        }
+                               | K_DECLARE
+                                       {       $$ = NULL;      }
+                               | decl_statement
+                                       {       $$ = NULL;      }
+                               ;
 
 decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval
-                   {
-                       PLpgSQL_var     *new;
-
-                       new = malloc(sizeof(PLpgSQL_var));
-
-                       new->dtype      = PLPGSQL_DTYPE_VAR;
-                       new->refname    = $1.name;
-                       new->lineno     = $1.lineno;
-
-                       new->datatype   = $3;
-                       new->isconst    = $2;
-                       new->notnull    = $4;
-                       new->default_val = $5;
-
-                       plpgsql_adddatum((PLpgSQL_datum *)new);
-                       plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, new->varno,
-                                               $1.name);
-                   }
-               | decl_varname K_RECORD ';'
-                   {
-                       PLpgSQL_rec     *new;
-
-                       new = malloc(sizeof(PLpgSQL_var));
-
-                       new->dtype      = PLPGSQL_DTYPE_REC;
-                       new->refname    = $1.name;
-                       new->lineno     = $1.lineno;
-
-                       plpgsql_adddatum((PLpgSQL_datum *)new);
-                       plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, new->recno,
-                                               $1.name);
-                   }
-               | decl_varname decl_rowtype ';'
-                   {
-                       $2->dtype       = PLPGSQL_DTYPE_ROW;
-                       $2->refname     = $1.name;
-                       $2->lineno      = $1.lineno;
-
-                       plpgsql_adddatum((PLpgSQL_datum *)$2);
-                       plpgsql_ns_additem(PLPGSQL_NSTYPE_ROW, $2->rowno,
-                                               $1.name);
-                   }
-               | decl_varname K_ALIAS K_FOR decl_aliasitem ';'
-                   {
-                       plpgsql_ns_additem($4->itemtype,
-                                       $4->itemno, $1.name);
-                   }
-               | K_RENAME decl_renname K_TO decl_renname ';'
-                   {
-                       plpgsql_ns_rename($2, $4);
-                   }
-               ;
+                                       {
+                                               PLpgSQL_variable        *var;
+
+                                               var = plpgsql_build_variable($1.name, $1.lineno,
+                                                                                                        $3, true);
+                                               if ($2)
+                                               {
+                                                       if (var->dtype == PLPGSQL_DTYPE_VAR)
+                                                               ((PLpgSQL_var *) var)->isconst = $2;
+                                                       else
+                                                               ereport(ERROR,
+                                                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                                                errmsg("row or record variable cannot be CONSTANT")));
+                                               }
+                                               if ($4)
+                                               {
+                                                       if (var->dtype == PLPGSQL_DTYPE_VAR)
+                                                               ((PLpgSQL_var *) var)->notnull = $4;
+                                                       else
+                                                               ereport(ERROR,
+                                                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                                                errmsg("row or record variable cannot be NOT NULL")));
+                                               }
+                                               if ($5 != NULL)
+                                               {
+                                                       if (var->dtype == PLPGSQL_DTYPE_VAR)
+                                                               ((PLpgSQL_var *) var)->default_val = $5;
+                                                       else
+                                                               ereport(ERROR,
+                                                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                                                errmsg("default value for row or record variable is not supported")));
+                                               }
+                                       }
+                               | decl_varname K_ALIAS K_FOR decl_aliasitem ';'
+                                       {
+                                               plpgsql_ns_additem($4->itemtype,
+                                                                                  $4->itemno, $1.name);
+                                       }
+                               | K_RENAME decl_renname K_TO decl_renname ';'
+                                       {
+                                               plpgsql_ns_rename($2, $4);
+                                       }
+                               | decl_varname K_CURSOR
+                                       { plpgsql_ns_push(NULL); }
+                                 decl_cursor_args decl_is_from decl_cursor_query
+                                       {
+                                               PLpgSQL_var *new;
+                                               PLpgSQL_expr *curname_def;
+                                               char            buf[1024];
+                                               char            *cp1;
+                                               char            *cp2;
+
+                                               /* pop local namespace for cursor args */
+                                               plpgsql_ns_pop();
+
+                                               new = (PLpgSQL_var *)
+                                                       plpgsql_build_variable($1.name, $1.lineno,
+                                                                                                  plpgsql_build_datatype(REFCURSOROID,
+                                                                                                                                                 -1),
+                                                                                                  true);
+
+                                               curname_def = palloc0(sizeof(PLpgSQL_expr));
+
+                                               curname_def->dtype = PLPGSQL_DTYPE_EXPR;
+                                               strcpy(buf, "SELECT ");
+                                               cp1 = new->refname;
+                                               cp2 = buf + strlen(buf);
+                                               if (strchr(cp1, '\\') != NULL)
+                                                       *cp2++ = ESCAPE_STRING_SYNTAX;
+                                               *cp2++ = '\'';
+                                               while (*cp1)
+                                               {
+                                                       if (SQL_STR_DOUBLE(*cp1))
+                                                               *cp2++ = *cp1;
+                                                       *cp2++ = *cp1++;
+                                               }
+                                               strcpy(cp2, "'::refcursor");
+                                               curname_def->query = pstrdup(buf);
+                                               new->default_val = curname_def;
+
+                                               new->cursor_explicit_expr = $6;
+                                               if ($4 == NULL)
+                                                       new->cursor_explicit_argrow = -1;
+                                               else
+                                                       new->cursor_explicit_argrow = $4->rowno;
+                                       }
+                               ;
+
+decl_cursor_query :
+                                       {
+                                               PLpgSQL_expr *query;
+
+                                               plpgsql_ns_setlocal(false);
+                                               query = read_sql_stmt("");
+                                               plpgsql_ns_setlocal(true);
+
+                                               $$ = query;
+                                       }
+                               ;
+
+decl_cursor_args :
+                                       {
+                                               $$ = NULL;
+                                       }
+                               | '(' decl_cursor_arglist ')'
+                                       {
+                                               PLpgSQL_row *new;
+                                               int i;
+                                               ListCell *l;
+
+                                               new = palloc0(sizeof(PLpgSQL_row));
+                                               new->dtype = PLPGSQL_DTYPE_ROW;
+                                               new->lineno = plpgsql_scanner_lineno();
+                                               new->rowtupdesc = NULL;
+                                               new->nfields = list_length($2);
+                                               new->fieldnames = palloc(new->nfields * sizeof(char *));
+                                               new->varnos = palloc(new->nfields * sizeof(int));
+
+                                               i = 0;
+                                               foreach (l, $2)
+                                               {
+                                                       PLpgSQL_variable *arg = (PLpgSQL_variable *) lfirst(l);
+                                                       new->fieldnames[i] = arg->refname;
+                                                       new->varnos[i] = arg->dno;
+                                                       i++;
+                                               }
+                                               list_free($2);
+
+                                               plpgsql_adddatum((PLpgSQL_datum *) new);
+                                               $$ = 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_build_variable($1.name, $1.lineno,
+                                                                                                       $2, true);
+                                       }
+                               ;
+
+decl_is_from   :       K_IS |          /* Oracle */
+                                       K_FOR;          /* ANSI */
 
 decl_aliasitem : T_WORD
-                   {
-                       PLpgSQL_nsitem *nsi;
-                       char    *name;
-
-                       plpgsql_ns_setlocal(false);
-                       name = plpgsql_tolower(pstrdup(yytext));
-                       if (name[0] != '$') {
-                           elog(ERROR, "can only alias positional parameters");
-                       }
-                       nsi = plpgsql_ns_lookup(name, NULL);
-                       if (nsi == NULL) {
-                           elog(ERROR, "function has no parameter %s", name);
-                       }
+                                       {
+                                               char    *name;
+                                               PLpgSQL_nsitem *nsi;
+
+                                               plpgsql_convert_ident(yytext, &name, 1);
+                                               if (name[0] != '$')
+                                                       yyerror("only positional parameters may be aliased");
+
+                                               plpgsql_ns_setlocal(false);
+                                               nsi = plpgsql_ns_lookup(name, NULL);
+                                               if (nsi == NULL)
+                                               {
+                                                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_UNDEFINED_PARAMETER),
+                                                                        errmsg("function has no parameter \"%s\"",
+                                                                                       name)));
+                                               }
 
-                       plpgsql_ns_setlocal(true);
+                                               plpgsql_ns_setlocal(true);
 
-                       $$ = nsi;
-                   }
-               ;
+                                               pfree(name);
 
-decl_rowtype   : T_ROW
-                   {
-                       $$ = yylval.row;
-                   }
-               ;
+                                               $$ = nsi;
+                                       }
+                               ;
 
 decl_varname   : T_WORD
-                   {
-                       $$.name = strdup(yytext);
-                       $$.lineno  = yylineno;
-                   }
-               ;
+                                       {
+                                               char    *name;
+
+                                               plpgsql_convert_ident(yytext, &name, 1);
+                                               $$.name = name;
+                                               $$.lineno  = plpgsql_scanner_lineno();
+                                       }
+                               ;
 
 decl_renname   : T_WORD
-                   {
-                       $$ = plpgsql_tolower(pstrdup(yytext));
-                   }
-               ;
-
-decl_const     :
-                   { $$ = 0; }
-               | K_CONSTANT
-                   { $$ = 1; }
-               ;
-
-decl_datatype  : decl_dtypename
-                   {
-                       $$ = $1;
-                   }
-               ;
-
-decl_dtypename : T_DTYPE
-                   {
-                       $$ = yylval.dtype;
-                   }
-               | T_CHAR decl_atttypmod
-                   {
-                       if ($2 < 0) {
-                           plpgsql_parse_word("char");
-                           $$ = yylval.dtype;
-                       } else {
-                           plpgsql_parse_word("bpchar");
-                           $$ = yylval.dtype;
-                           $$->atttypmod = $2;
-                       }
-                   }
-               | T_VARCHAR decl_atttypmod
-                   {
-                       plpgsql_parse_word("varchar");
-                       $$ = yylval.dtype;
-                       $$->atttypmod = $2;
-                   }
-               | T_BPCHAR '(' decl_atttypmodval ')'
-                   {
-                       plpgsql_parse_word("bpchar");
-                       $$ = yylval.dtype;
-                       $$->atttypmod = $3;
-                   }
-               ;
-
-decl_atttypmod :
-                   {
-                       $$ = -1;
-                   }
-               | '(' decl_atttypmodval ')'
-                   {
-                       $$ = $2;
-                   }
-               ;
-
-decl_atttypmodval      : T_NUMBER
-                   {
-                       $$ = int2in(yytext) + VARHDRSZ;
-                   }
-               ;
+                                       {
+                                               char    *name;
+
+                                               plpgsql_convert_ident(yytext, &name, 1);
+                                               /* the result must be palloc'd, see plpgsql_ns_rename */
+                                               $$ = name;
+                                       }
+                               ;
+
+decl_const             :
+                                       { $$ = false; }
+                               | K_CONSTANT
+                                       { $$ = true; }
+                               ;
+
+decl_datatype  :
+                                       {
+                                               /*
+                                                * If there's a lookahead token, read_datatype
+                                                * should consume it.
+                                                */
+                                               $$ = read_datatype(yychar);
+                                               yyclearin;
+                                       }
+                               ;
 
 decl_notnull   :
-                   { $$ = 0; }
-               | K_NOT K_NULL
-                   { $$ = 1; }
-               ;
-
-decl_defval    : ';'
-                   { $$ = NULL; }
-               | decl_defkey
-                   {
-                       int             tok;
-                       int             lno;
-                       PLpgSQL_dstring ds;
-                       PLpgSQL_expr    *expr;
-
-                       lno = yylineno;
-                       expr = malloc(sizeof(PLpgSQL_expr));
-                       plpgsql_dstring_init(&ds);
-                       plpgsql_dstring_append(&ds, "SELECT ");
-
-                       expr->dtype   = PLPGSQL_DTYPE_EXPR;
-                       expr->plan    = NULL;
-                       expr->nparams = 0;
+                                       { $$ = false; }
+                               | K_NOT K_NULL
+                                       { $$ = true; }
+                               ;
+
+decl_defval            : ';'
+                                       { $$ = NULL; }
+                               | decl_defkey
+                                       {
+                                               plpgsql_ns_setlocal(false);
+                                               $$ = plpgsql_read_expression(';', ";");
+                                               plpgsql_ns_setlocal(true);
+                                       }
+                               ;
+
+decl_defkey            : K_ASSIGN
+                               | K_DEFAULT
+                               ;
+
+proc_sect              :
+                                       {
+                                               $$ = NIL;
+                                       }
+                               | proc_stmts
+                                       { $$ = $1; }
+                               ;
+
+proc_stmts             : proc_stmts proc_stmt
+                                               {
+                                                       if ($2 == NULL)
+                                                               $$ = $1;
+                                                       else
+                                                               $$ = lappend($1, $2);
+                                               }
+                               | proc_stmt
+                                               {
+                                                       if ($1 == NULL)
+                                                               $$ = NULL;
+                                                       else
+                                                               $$ = list_make1($1);
+                                               }
+                               ;
+
+proc_stmt              : pl_block ';'
+                                               { $$ = $1; }
+                               | stmt_assign
+                                               { $$ = $1; }
+                               | stmt_if
+                                               { $$ = $1; }
+                               | stmt_loop
+                                               { $$ = $1; }
+                               | stmt_while
+                                               { $$ = $1; }
+                               | stmt_for
+                                               { $$ = $1; }
+                               | stmt_select
+                                               { $$ = $1; }
+                               | stmt_exit
+                                               { $$ = $1; }
+                               | stmt_return
+                                               { $$ = $1; }
+                               | stmt_return_next
+                                               { $$ = $1; }
+                               | stmt_raise
+                                               { $$ = $1; }
+                               | stmt_execsql
+                                               { $$ = $1; }
+                               | stmt_dynexecute
+                                               { $$ = $1; }
+                               | stmt_perform
+                                               { $$ = $1; }
+                               | stmt_getdiag
+                                               { $$ = $1; }
+                               | stmt_open
+                                               { $$ = $1; }
+                               | stmt_fetch
+                                               { $$ = $1; }
+                               | stmt_close
+                                               { $$ = $1; }
+                               | stmt_null
+                                               { $$ = $1; }
+                               ;
 
-                       tok = yylex();
-                       switch (tok) {
-                           case 0:
-                               plpgsql_error_lineno = lno;
-                               plpgsql_comperrinfo();
-                               elog(ERROR, "unexpected end of file");
-                           case K_NULL:
-                               if (yylex() != ';') {
-                                   plpgsql_error_lineno = lno;
-                                   plpgsql_comperrinfo();
-                                   elog(ERROR, "expectec ; after NULL");
-                               }
-                               free(expr);
-                               plpgsql_dstring_free(&ds);
-
-                               $$ = NULL;
+stmt_perform   : K_PERFORM lno expr_until_semi
+                                       {
+                                               PLpgSQL_stmt_perform *new;
+
+                                               new = palloc0(sizeof(PLpgSQL_stmt_perform));
+                                               new->cmd_type = PLPGSQL_STMT_PERFORM;
+                                               new->lineno   = $2;
+                                               new->expr  = $3;
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+stmt_assign            : assign_var lno K_ASSIGN expr_until_semi
+                                       {
+                                               PLpgSQL_stmt_assign *new;
+
+                                               new = palloc0(sizeof(PLpgSQL_stmt_assign));
+                                               new->cmd_type = PLPGSQL_STMT_ASSIGN;
+                                               new->lineno   = $2;
+                                               new->varno = $1;
+                                               new->expr  = $4;
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+stmt_getdiag   : K_GET K_DIAGNOSTICS lno getdiag_list ';'
+                                       {
+                                               PLpgSQL_stmt_getdiag     *new;
+
+                                               new = palloc0(sizeof(PLpgSQL_stmt_getdiag));
+                                               new->cmd_type = PLPGSQL_STMT_GETDIAG;
+                                               new->lineno   = $3;
+                                               new->diag_items  = $4;
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+getdiag_list : getdiag_list ',' getdiag_list_item
+                                       {
+                                               $$ = lappend($1, $3);
+                                       }
+                               | getdiag_list_item
+                                       {
+                                               $$ = list_make1($1);
+                                       }
+                               ;
+
+getdiag_list_item : getdiag_target K_ASSIGN getdiag_kind
+                                       {
+                                               PLpgSQL_diag_item *new;
+
+                                               new = palloc(sizeof(PLpgSQL_diag_item));
+                                               new->target = $1;
+                                               new->kind = $3;
+
+                                               $$ = new;
+                                       }
+                               ;
+
+getdiag_kind : K_ROW_COUNT
+                                       {
+                                               $$ = PLPGSQL_GETDIAG_ROW_COUNT;
+                                       }
+                               | K_RESULT_OID
+                                       {
+                                               $$ = PLPGSQL_GETDIAG_RESULT_OID;
+                                       }
+                               ;
+
+getdiag_target : T_SCALAR
+                                       {
+                                               check_assignable(yylval.scalar);
+                                               $$ = yylval.scalar->dno;
+                                       }
+                               ;
+
+
+assign_var             : T_SCALAR
+                                       {
+                                               check_assignable(yylval.scalar);
+                                               $$ = yylval.scalar->dno;
+                                       }
+                               | T_ROW
+                                       {
+                                               check_assignable((PLpgSQL_datum *) yylval.row);
+                                               $$ = yylval.row->rowno;
+                                       }
+                               | T_RECORD
+                                       {
+                                               check_assignable((PLpgSQL_datum *) yylval.rec);
+                                               $$ = yylval.rec->recno;
+                                       }
+                               | assign_var '[' expr_until_rightbracket
+                                       {
+                                               PLpgSQL_arrayelem       *new;
+
+                                               new = palloc0(sizeof(PLpgSQL_arrayelem));
+                                               new->dtype              = PLPGSQL_DTYPE_ARRAYELEM;
+                                               new->subscript  = $3;
+                                               new->arrayparentno = $1;
+
+                                               plpgsql_adddatum((PLpgSQL_datum *)new);
+
+                                               $$ = new->dno;
+                                       }
+                               ;
+
+stmt_if                        : K_IF lno expr_until_then proc_sect stmt_else K_END K_IF ';'
+                                       {
+                                               PLpgSQL_stmt_if *new;
+
+                                               new = palloc0(sizeof(PLpgSQL_stmt_if));
+                                               new->cmd_type   = PLPGSQL_STMT_IF;
+                                               new->lineno             = $2;
+                                               new->cond               = $3;
+                                               new->true_body  = $4;
+                                               new->false_body = $5;
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+stmt_else              :
+                                       {
+                                               $$ = NIL;
+                                       }
+                               | K_ELSIF lno expr_until_then proc_sect stmt_else
+                                       {
+                                               /*
+                                                * Translate the structure:        into:
+                                                *
+                                                * IF c1 THEN                              IF c1 THEN
+                                                *       ...                                               ...
+                                                * ELSIF c2 THEN                           ELSE
+                                                *                                                                 IF c2 THEN
+                                                *       ...                                                       ...
+                                                * ELSE                                                    ELSE
+                                                *       ...                                                       ...
+                                                * END IF                                                  END IF
+                                                *                                                         END IF
+                                                */
+                                               PLpgSQL_stmt_if *new_if;
+
+                                               /* first create a new if-statement */
+                                               new_if = palloc0(sizeof(PLpgSQL_stmt_if));
+                                               new_if->cmd_type        = PLPGSQL_STMT_IF;
+                                               new_if->lineno          = $2;
+                                               new_if->cond            = $3;
+                                               new_if->true_body       = $4;
+                                               new_if->false_body      = $5;
+
+                                               /* wrap the if-statement in a "container" list */
+                                               $$ = list_make1(new_if);
+                                       }
+
+                               | K_ELSE proc_sect
+                                       {
+                                               $$ = $2;
+                                       }
+                               ;
+
+stmt_loop              : opt_block_label K_LOOP lno loop_body
+                                       {
+                                               PLpgSQL_stmt_loop *new;
+
+                                               new = palloc0(sizeof(PLpgSQL_stmt_loop));
+                                               new->cmd_type = PLPGSQL_STMT_LOOP;
+                                               new->lineno   = $3;
+                                               new->label        = $1;
+                                               new->body         = $4.stmts;
+
+                                               check_labels($1, $4.end_label);
+                                               plpgsql_ns_pop();
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+stmt_while             : opt_block_label K_WHILE lno expr_until_loop loop_body
+                                       {
+                                               PLpgSQL_stmt_while *new;
+
+                                               new = palloc0(sizeof(PLpgSQL_stmt_while));
+                                               new->cmd_type = PLPGSQL_STMT_WHILE;
+                                               new->lineno   = $3;
+                                               new->label        = $1;
+                                               new->cond         = $4;
+                                               new->body         = $5.stmts;
+
+                                               check_labels($1, $5.end_label);
+                                               plpgsql_ns_pop();
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+stmt_for               : opt_block_label K_FOR for_control loop_body
+                                       {
+                                               /* This runs after we've scanned the loop body */
+                                               if ($3->cmd_type == PLPGSQL_STMT_FORI)
+                                               {
+                                                       PLpgSQL_stmt_fori               *new;
+
+                                                       new = (PLpgSQL_stmt_fori *) $3;
+                                                       new->label        = $1;
+                                                       new->body         = $4.stmts;
+                                                       $$ = (PLpgSQL_stmt *) new;
+                                               }
+                                               else if ($3->cmd_type == PLPGSQL_STMT_FORS)
+                                               {
+                                                       PLpgSQL_stmt_fors               *new;
+
+                                                       new = (PLpgSQL_stmt_fors *) $3;
+                                                       new->label        = $1;
+                                                       new->body         = $4.stmts;
+                                                       $$ = (PLpgSQL_stmt *) new;
+                                               }
+                                               else
+                                               {
+                                                       PLpgSQL_stmt_dynfors    *new;
+
+                                                       Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS);
+                                                       new = (PLpgSQL_stmt_dynfors *) $3;
+                                                       new->label        = $1;
+                                                       new->body         = $4.stmts;
+                                                       $$ = (PLpgSQL_stmt *) new;
+                                               }
+
+                                               check_labels($1, $4.end_label);
+                                               /* close namespace started in opt_label */
+                                               plpgsql_ns_pop();
+                                       }
+                               ;
+
+for_control            :
+                               lno for_variable K_IN
+                                       {
+                                               int                     tok = yylex();
+
+                                               /* Simple case: EXECUTE is a dynamic FOR loop */
+                                               if (tok == K_EXECUTE)
+                                               {
+                                                       PLpgSQL_stmt_dynfors    *new;
+                                                       PLpgSQL_expr                    *expr;
+
+                                                       expr = plpgsql_read_expression(K_LOOP, "LOOP");
+
+                                                       new = palloc0(sizeof(PLpgSQL_stmt_dynfors));
+                                                       new->cmd_type = PLPGSQL_STMT_DYNFORS;
+                                                       new->lineno   = $1;
+                                                       if ($2.rec)
+                                                       {
+                                                               new->rec = $2.rec;
+                                                               check_assignable((PLpgSQL_datum *) new->rec);
+                                                       }
+                                                       else if ($2.row)
+                                                       {
+                                                               new->row = $2.row;
+                                                               check_assignable((PLpgSQL_datum *) new->row);
+                                                       }
+                                                       else if ($2.scalar)
+                                                       {
+                                                               /* convert single scalar to list */
+                                                               new->row = make_scalar_list1($2.name, $2.scalar, $2.lineno);
+                                                               /* no need for check_assignable */
+                                                       }
+                                                       else
+                                                       {
+                                                               plpgsql_error_lineno = $2.lineno;
+                                                               yyerror("loop variable of loop over rows must be a record or row variable or list of scalar variables");
+                                                       }
+                                                       new->query = expr;
+
+                                                       $$ = (PLpgSQL_stmt *) new;
+                                               }
+                                               else
+                                               {
+                                                       PLpgSQL_expr    *expr1;
+                                                       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 == K_REVERSE)
+                                                               reverse = true;
+                                                       else
+                                                               plpgsql_push_back_token(tok);
+
+                                                       /*
+                                                        * Read tokens until we see either a ".."
+                                                        * or a LOOP. The text we read may not
+                                                        * necessarily be a well-formed SQL
+                                                        * statement, so we need to invoke
+                                                        * read_sql_construct directly.
+                                                        */
+                                                       expr1 = read_sql_construct(K_DOTDOT,
+                                                                                                          K_LOOP,
+                                                                                                          "LOOP",
+                                                                                                          "SELECT ",
+                                                                                                          true,
+                                                                                                          false,
+                                                                                                          &tok);
+
+                                                       if (tok == K_DOTDOT)
+                                                       {
+                                                               /* Saw "..", so it must be an integer loop */
+                                                               PLpgSQL_expr            *expr2;
+                                                               PLpgSQL_var                     *fvar;
+                                                               PLpgSQL_stmt_fori       *new;
+                                                               char                            *varname;
+
+                                                               /* First expression is well-formed */
+                                                               check_sql_expr(expr1->query);
+
+                                                               expr2 = plpgsql_read_expression(K_LOOP, "LOOP");
+
+                                                               /* should have had a single variable name */
+                                                               plpgsql_error_lineno = $2.lineno;
+                                                               if ($2.scalar && $2.row)
+                                                                       ereport(ERROR,
+                                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                                        errmsg("integer FOR loop must have just one target variable")));
+
+                                                               /* create loop's private variable */
+                                                               plpgsql_convert_ident($2.name, &varname, 1);
+                                                               fvar = (PLpgSQL_var *)
+                                                                       plpgsql_build_variable(varname,
+                                                                                                                  $2.lineno,
+                                                                                                                  plpgsql_build_datatype(INT4OID,
+                                                                                                                                                                 -1),
+                                                                                                                  true);
+
+                                                               /* put the for-variable into the local block */
+                                                               plpgsql_add_initdatums(NULL);
+
+                                                               new = palloc0(sizeof(PLpgSQL_stmt_fori));
+                                                               new->cmd_type = PLPGSQL_STMT_FORI;
+                                                               new->lineno   = $1;
+                                                               new->var          = fvar;
+                                                               new->reverse  = reverse;
+                                                               new->lower        = expr1;
+                                                               new->upper        = expr2;
+
+                                                               $$ = (PLpgSQL_stmt *) new;
+                                                       }
+                                                       else
+                                                       {
+                                                               /*
+                                                                * No "..", so it must be a query loop. We've prefixed an
+                                                                * extra SELECT to the query text, so we need to remove that
+                                                                * before performing syntax checking.
+                                                                */
+                                                               char                            *tmp_query;
+                                                               PLpgSQL_stmt_fors       *new;
+
+                                                               if (reverse)
+                                                                       yyerror("cannot specify REVERSE in query FOR loop");
+
+                                                               Assert(strncmp(expr1->query, "SELECT ", 7) == 0);
+                                                               tmp_query = pstrdup(expr1->query + 7);
+                                                               pfree(expr1->query);
+                                                               expr1->query = tmp_query;
+
+                                                               check_sql_expr(expr1->query);
+
+                                                               new = palloc0(sizeof(PLpgSQL_stmt_fors));
+                                                               new->cmd_type = PLPGSQL_STMT_FORS;
+                                                               new->lineno   = $1;
+                                                               if ($2.rec)
+                                                               {
+                                                                       new->rec = $2.rec;
+                                                                       check_assignable((PLpgSQL_datum *) new->rec);
+                                                               }
+                                                               else if ($2.row)
+                                                               {
+                                                                       new->row = $2.row;
+                                                                       check_assignable((PLpgSQL_datum *) new->row);
+                                                               }
+                                                               else if ($2.scalar)
+                                                               {
+                                                                       /* convert single scalar to list */
+                                                                       new->row = make_scalar_list1($2.name, $2.scalar, $2.lineno);
+                                                                       /* no need for check_assignable */
+                                                               }
+                                                               else
+                                                               {
+                                                                       plpgsql_error_lineno = $2.lineno;
+                                                                       yyerror("loop variable of loop over rows must be a record or row variable or list of scalar variables");
+                                                               }
+
+                                                               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_SCALAR
+                                       {
+                                               int                     tok;
+
+                                               $$.name = pstrdup(yytext);
+                                               $$.lineno  = plpgsql_scanner_lineno();
+                                               $$.scalar = yylval.scalar;
+                                               $$.rec = NULL;
+                                               $$.row = NULL;
+                                               /* check for comma-separated list */
+                                               tok = yylex();
+                                               plpgsql_push_back_token(tok);
+                                               if (tok == ',')
+                                                       $$.row = read_into_scalar_list($$.name, $$.scalar);
+                                       }
+                               | T_WORD
+                                       {
+                                               int                     tok;
+
+                                               $$.name = pstrdup(yytext);
+                                               $$.lineno  = plpgsql_scanner_lineno();
+                                               $$.scalar = NULL;
+                                               $$.rec = NULL;
+                                               $$.row = NULL;
+                                               /* check for comma-separated list */
+                                               tok = yylex();
+                                               plpgsql_push_back_token(tok);
+                                               if (tok == ',')
+                                               {
+                                                       plpgsql_error_lineno = $$.lineno;
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                        errmsg("\"%s\" is not a scalar variable",
+                                                                                       $$.name)));
+                                               }
+                                       }
+                               | T_RECORD
+                                       {
+                                               $$.name = pstrdup(yytext);
+                                               $$.lineno  = plpgsql_scanner_lineno();
+                                               $$.scalar = NULL;
+                                               $$.rec = yylval.rec;
+                                               $$.row = NULL;
+                                       }
+                               | T_ROW
+                                       {
+                                               $$.name = pstrdup(yytext);
+                                               $$.lineno  = plpgsql_scanner_lineno();
+                                               $$.scalar = NULL;
+                                               $$.row = yylval.row;
+                                               $$.rec = NULL;
+                                       }
+                               ;
+
+stmt_select            : K_SELECT lno
+                                       {
+                                               $$ = make_select_stmt();
+                                               $$->lineno = $2;
+                                       }
+                               ;
+
+stmt_exit              : exit_type lno opt_label opt_exitcond
+                                       {
+                                               PLpgSQL_stmt_exit *new;
+
+                                               new = palloc0(sizeof(PLpgSQL_stmt_exit));
+                                               new->cmd_type = PLPGSQL_STMT_EXIT;
+                                               new->is_exit  = $1;
+                                               new->lineno       = $2;
+                                               new->label        = $3;
+                                               new->cond         = $4;
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+exit_type              : K_EXIT
+                                       {
+                                               $$ = true;
+                                       }
+                               | K_CONTINUE
+                                       {
+                                               $$ = false;
+                                       }
+                               ;
+
+stmt_return            : K_RETURN lno
+                                       {
+                                               PLpgSQL_stmt_return *new;
+
+                                               new = palloc0(sizeof(PLpgSQL_stmt_return));
+                                               new->cmd_type = PLPGSQL_STMT_RETURN;
+                                               new->lineno   = $2;
+                                               new->expr         = NULL;
+                                               new->retvarno = -1;
+
+                                               if (plpgsql_curr_compile->fn_retset)
+                                               {
+                                                       if (yylex() != ';')
+                                                               yyerror("RETURN cannot have a parameter in function returning set; use RETURN NEXT");
+                                               }
+                                               else if (plpgsql_curr_compile->out_param_varno >= 0)
+                                               {
+                                                       if (yylex() != ';')
+                                                               yyerror("RETURN cannot have a parameter in function with OUT parameters");
+                                                       new->retvarno = plpgsql_curr_compile->out_param_varno;
+                                               }
+                                               else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
+                                               {
+                                                       if (yylex() != ';')
+                                                               yyerror("RETURN cannot have a parameter in function returning void");
+                                               }
+                                               else if (plpgsql_curr_compile->fn_retistuple)
+                                               {
+                                                       switch (yylex())
+                                                       {
+                                                               case K_NULL:
+                                                                       /* we allow this to support RETURN NULL in triggers */
+                                                                       break;
+
+                                                               case T_ROW:
+                                                                       new->retvarno = yylval.row->rowno;
+                                                                       break;
+
+                                                               case T_RECORD:
+                                                                       new->retvarno = yylval.rec->recno;
+                                                                       break;
+
+                                                               default:
+                                                                       yyerror("RETURN must specify a record or row variable in function returning tuple");
+                                                                       break;
+                                                       }
+                                                       if (yylex() != ';')
+                                                               yyerror("RETURN must specify a record or row variable in function returning tuple");
+                                               }
+                                               else
+                                               {
+                                                       /*
+                                                        * Note that a well-formed expression is
+                                                        * _required_ here; anything else is a
+                                                        * compile-time error.
+                                                        */
+                                                       new->expr = plpgsql_read_expression(';', ";");
+                                               }
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+stmt_return_next: K_RETURN_NEXT lno
+                                       {
+                                               PLpgSQL_stmt_return_next *new;
+
+                                               if (!plpgsql_curr_compile->fn_retset)
+                                                       yyerror("cannot use RETURN NEXT in a non-SETOF function");
+
+                                               new = palloc0(sizeof(PLpgSQL_stmt_return_next));
+                                               new->cmd_type   = PLPGSQL_STMT_RETURN_NEXT;
+                                               new->lineno             = $2;
+                                               new->expr = NULL;
+                                               new->retvarno   = -1;
+
+                                               if (plpgsql_curr_compile->out_param_varno >= 0)
+                                               {
+                                                       if (yylex() != ';')
+                                                               yyerror("RETURN NEXT cannot have a parameter in function with OUT parameters");
+                                                       new->retvarno = plpgsql_curr_compile->out_param_varno;
+                                               }
+                                               else if (plpgsql_curr_compile->fn_retistuple)
+                                               {
+                                                       switch (yylex())
+                                                       {
+                                                               case T_ROW:
+                                                                       new->retvarno = yylval.row->rowno;
+                                                                       break;
+
+                                                               case T_RECORD:
+                                                                       new->retvarno = yylval.rec->recno;
+                                                                       break;
+
+                                                               default:
+                                                                       yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
+                                                                       break;
+                                                       }
+                                                       if (yylex() != ';')
+                                                               yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
+                                               }
+                                               else
+                                                       new->expr = plpgsql_read_expression(';', ";");
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+stmt_raise             : K_RAISE lno raise_level raise_msg
+                                       {
+                                               PLpgSQL_stmt_raise              *new;
+                                               int     tok;
+
+                                               new = palloc(sizeof(PLpgSQL_stmt_raise));
+
+                                               new->cmd_type   = PLPGSQL_STMT_RAISE;
+                                               new->lineno             = $2;
+                                               new->elog_level = $3;
+                                               new->message    = $4;
+                                               new->params             = NIL;
+
+                                               tok = yylex();
+
+                                               /*
+                                                * We expect either a semi-colon, which
+                                                * indicates no parameters, or a comma that
+                                                * begins the list of parameter expressions
+                                                */
+                                               if (tok != ',' && tok != ';')
+                                                       yyerror("syntax error");
+
+                                               if (tok == ',')
+                                               {
+                                                       PLpgSQL_expr *expr;
+                                                       int term;
+
+                                                       for (;;)
+                                                       {
+                                                               expr = read_sql_construct(',', ';', ", or ;",
+                                                                                                                 "SELECT ",
+                                                                                                                 true, true, &term);
+                                                               new->params = lappend(new->params, expr);
+                                                               if (term == ';')
+                                                                       break;
+                                                       }
+                                               }
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+raise_msg              : T_STRING
+                                       {
+                                               $$ = plpgsql_get_string_value();
+                                       }
+                               ;
+
+raise_level            : K_EXCEPTION
+                                       {
+                                               $$ = ERROR;
+                                       }
+                               | K_WARNING
+                                       {
+                                               $$ = WARNING;
+                                       }
+                               | K_NOTICE
+                                       {
+                                               $$ = NOTICE;
+                                       }
+                               | K_INFO
+                                       {
+                                               $$ = INFO;
+                                       }
+                               | K_LOG
+                                       {
+                                               $$ = LOG;
+                                       }
+                               | K_DEBUG
+                                       {
+                                               $$ = DEBUG1;
+                                       }
+                               ;
+
+loop_body              : proc_sect K_END K_LOOP opt_label ';'
+                                       {
+                                               $$.stmts = $1;
+                                               $$.end_label = $4;
+                                       }
+                               ;
+
+stmt_execsql   : execsql_start lno
+                                       {
+                                               PLpgSQL_stmt_execsql    *new;
+
+                                               new = palloc(sizeof(PLpgSQL_stmt_execsql));
+                                               new->cmd_type = PLPGSQL_STMT_EXECSQL;
+                                               new->lineno   = $2;
+                                               new->sqlstmt  = read_sql_stmt($1);
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+stmt_dynexecute : K_EXECUTE lno
+                                       {
+                                               PLpgSQL_stmt_dynexecute *new;
+                                               PLpgSQL_expr *expr;
+                                               int endtoken;
+
+                                               expr = read_sql_construct(K_INTO, ';', "INTO|;", "SELECT ",
+                                                                                                 true, true, &endtoken);
+
+                                               new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
+                                               new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
+                                               new->lineno   = $2;
+                                               new->query    = expr;
+                                               new->rec = NULL;
+                                               new->row = NULL;
+
+                                               /*
+                                                * If we saw "INTO", look for a following row
+                                                * var, record var, or list of scalars.
+                                                */
+                                               if (endtoken == K_INTO)
+                                               {
+                                                       switch (yylex())
+                                                       {
+                                                               case T_ROW:
+                                                                       new->row = yylval.row;
+                                                                       check_assignable((PLpgSQL_datum *) new->row);
+                                                                       break;
+
+                                                               case T_RECORD:
+                                                                       new->rec = yylval.rec;
+                                                                       check_assignable((PLpgSQL_datum *) new->rec);
+                                                                       break;
+
+                                                               case T_SCALAR:
+                                                                       new->row = read_into_scalar_list(yytext, yylval.scalar);
+                                                                       break;
+
+                                                               default:
+                                                                       plpgsql_error_lineno = $2;
+                                                                       ereport(ERROR,
+                                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                                        errmsg("syntax error at \"%s\"", yytext),
+                                                                                        errdetail("Expected record variable, row variable, "
+                                                                                                          "or list of scalar variables.")));
+                                                       }
+                                                       if (yylex() != ';')
+                                                               yyerror("syntax error");
+                                               }
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+
+stmt_open              : K_OPEN lno cursor_varptr
+                                       {
+                                               PLpgSQL_stmt_open *new;
+                                               int                               tok;
+
+                                               new = palloc0(sizeof(PLpgSQL_stmt_open));
+                                               new->cmd_type = PLPGSQL_STMT_OPEN;
+                                               new->lineno = $2;
+                                               new->curvar = $3->varno;
+
+                                               if ($3->cursor_explicit_expr == NULL)
+                                               {
+                                                   tok = yylex();
+                                                       if (tok != K_FOR)
+                                                       {
+                                                               plpgsql_error_lineno = $2;
+                                                               ereport(ERROR,
+                                                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                                errmsg("syntax error at \"%s\"",
+                                                                                               yytext),
+                                                                                errdetail("Expected FOR to open a reference cursor.")));
+                                                       }
+
+                                                       tok = yylex();
+                                                       if (tok == K_EXECUTE)
+                                                       {
+                                                               new->dynquery = read_sql_stmt("SELECT ");
+                                                       }
+                                                       else
+                                                       {
+                                                               plpgsql_push_back_token(tok);
+                                                               new->query = read_sql_stmt("");
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       if ($3->cursor_explicit_argrow >= 0)
+                                                       {
+                                                               char   *cp;
+
+                                                               tok = yylex();
+                                                               if (tok != '(')
+                                                               {
+                                                                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                                                                       ereport(ERROR,
+                                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                                        errmsg("cursor \"%s\" has arguments",
+                                                                                                       $3->refname)));
+                                                               }
+
+                                                               /*
+                                                                * Push back the '(', else read_sql_stmt
+                                                                * will complain about unbalanced parens.
+                                                                */
+                                                               plpgsql_push_back_token(tok);
+
+                                                               new->argquery = read_sql_stmt("SELECT ");
+
+                                                               /*
+                                                                * Now remove the leading and trailing parens,
+                                                                * because we want "select 1, 2", not
+                                                                * "select (1, 2)".
+                                                                */
+                                                               cp = new->argquery->query;
+
+                                                               if (strncmp(cp, "SELECT", 6) != 0)
+                                                               {
+                                                                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                                                                       /* internal error */
+                                                                       elog(ERROR, "expected \"SELECT (\", got \"%s\"",
+                                                                                new->argquery->query);
+                                                               }
+                                                               cp += 6;
+                                                               while (*cp == ' ') /* could be more than 1 space here */
+                                                                       cp++;
+                                                               if (*cp != '(')
+                                                               {
+                                                                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                                                                       /* internal error */
+                                                                       elog(ERROR, "expected \"SELECT (\", got \"%s\"",
+                                                                                new->argquery->query);
+                                                               }
+                                                               *cp = ' ';
+
+                                                               cp += strlen(cp) - 1;
+
+                                                               if (*cp != ')')
+                                                                       yyerror("expected \")\"");
+                                                               *cp = '\0';
+                                                       }
+                                                       else
+                                                       {
+                                                               tok = yylex();
+                                                               if (tok == '(')
+                                                               {
+                                                                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                                                                       ereport(ERROR,
+                                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                                        errmsg("cursor \"%s\" has no arguments",
+                                                                                                       $3->refname)));
+                                                               }
+
+                                                               if (tok != ';')
+                                                               {
+                                                                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                                                                       ereport(ERROR,
+                                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                                        errmsg("syntax error at \"%s\"",
+                                                                                                       yytext)));
+                                                               }
+                                                       }
+                                               }
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+stmt_fetch             : K_FETCH lno cursor_variable K_INTO
+                                       {
+                                               PLpgSQL_stmt_fetch *new;
+
+                                               new = (PLpgSQL_stmt_fetch *)make_fetch_stmt();
+                                               new->curvar = $3;
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                               $$->lineno = $2;
+                                       }
+                               ;
+
+stmt_close             : K_CLOSE lno cursor_variable ';'
+                                       {
+                                               PLpgSQL_stmt_close *new;
+
+                                               new = palloc(sizeof(PLpgSQL_stmt_close));
+                                               new->cmd_type = PLPGSQL_STMT_CLOSE;
+                                               new->lineno = $2;
+                                               new->curvar = $3;
+
+                                               $$ = (PLpgSQL_stmt *)new;
+                                       }
+                               ;
+
+stmt_null              : K_NULL ';'
+                                       {
+                                               /* We do not bother building a node for NULL */
+                                               $$ = NULL;
+                                       }
+                               ;
+
+cursor_varptr  : T_SCALAR
+                                       {
+                                               if (yylval.scalar->dtype != PLPGSQL_DTYPE_VAR)
+                                                       yyerror("cursor variable must be a simple variable");
+
+                                               if (((PLpgSQL_var *) yylval.scalar)->datatype->typoid != REFCURSOROID)
+                                               {
+                                                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                                        errmsg("\"%s\" must be of type cursor or refcursor",
+                                                                                       ((PLpgSQL_var *) yylval.scalar)->refname)));
+                                               }
+                                               $$ = (PLpgSQL_var *) yylval.scalar;
+                                       }
+                               ;
+
+cursor_variable        : T_SCALAR
+                                       {
+                                               if (yylval.scalar->dtype != PLPGSQL_DTYPE_VAR)
+                                                       yyerror("cursor variable must be a simple variable");
+
+                                               if (((PLpgSQL_var *) yylval.scalar)->datatype->typoid != REFCURSOROID)
+                                               {
+                                                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                                        errmsg("\"%s\" must be of type refcursor",
+                                                                                       ((PLpgSQL_var *) yylval.scalar)->refname)));
+                                               }
+                                               $$ = yylval.scalar->dno;
+                                       }
+                               ;
+
+execsql_start  : T_WORD
+                                       { $$ = pstrdup(yytext); }
+                               | T_ERROR
+                                       { $$ = pstrdup(yytext); }
+                               ;
+
+exception_sect :
+                                       { $$ = NULL; }
+                               | K_EXCEPTION lno
+                                       {
+                                               /*
+                                                * We use a mid-rule action to add these
+                                                * special variables to the namespace before
+                                                * parsing the WHEN clauses themselves.
+                                                */
+                                               PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block));
+                                               PLpgSQL_variable *var;
+
+                                               var = plpgsql_build_variable("sqlstate", $2,
+                                                                                                        plpgsql_build_datatype(TEXTOID, -1),
+                                                                                                        true);
+                                               ((PLpgSQL_var *) var)->isconst = true;
+                                               new->sqlstate_varno = var->dno;
+
+                                               var = plpgsql_build_variable("sqlerrm", $2,
+                                                                                                        plpgsql_build_datatype(TEXTOID, -1),
+                                                                                                        true);
+                                               ((PLpgSQL_var *) var)->isconst = true;
+                                               new->sqlerrm_varno = var->dno;
+
+                                               $<exception_block>$ = new;
+                                       }
+                                       proc_exceptions
+                                       {
+                                               PLpgSQL_exception_block *new = $<exception_block>3;
+                                               new->exc_list = $4;
+
+                                               $$ = new;
+                                       }
+                               ;
+
+proc_exceptions        : proc_exceptions proc_exception
+                                               {
+                                                       $$ = lappend($1, $2);
+                                               }
+                               | proc_exception
+                                               {
+                                                       $$ = list_make1($1);
+                                               }
+                               ;
+
+proc_exception : K_WHEN lno proc_conditions K_THEN proc_sect
+                                       {
+                                               PLpgSQL_exception *new;
+
+                                               new = palloc0(sizeof(PLpgSQL_exception));
+                                               new->lineno     = $2;
+                                               new->conditions = $3;
+                                               new->action         = $5;
+
+                                               $$ = new;
+                                       }
+                               ;
+
+proc_conditions        : proc_conditions K_OR opt_lblname
+                                               {
+                                                       PLpgSQL_condition       *old;
+
+                                                       for (old = $1; old->next != NULL; old = old->next)
+                                                               /* skip */ ;
+                                                       old->next = plpgsql_parse_err_condition($3);
+
+                                                       $$ = $1;
+                                               }
+                               | opt_lblname
+                                               {
+                                                       $$ = plpgsql_parse_err_condition($1);
+                                               }
+                               ;
+
+expr_until_semi :
+                                       { $$ = plpgsql_read_expression(';', ";"); }
+                               ;
+
+expr_until_rightbracket :
+                                       { $$ = plpgsql_read_expression(']', "]"); }
+                               ;
+
+expr_until_then :
+                                       { $$ = plpgsql_read_expression(K_THEN, "THEN"); }
+                               ;
+
+expr_until_loop :
+                                       { $$ = plpgsql_read_expression(K_LOOP, "LOOP"); }
+                               ;
+
+opt_block_label        :
+                                       {
+                                               plpgsql_ns_push(NULL);
+                                               $$ = NULL;
+                                       }
+                               | '<' '<' opt_lblname '>' '>'
+                                       {
+                                               plpgsql_ns_push($3);
+                                               $$ = $3;
+                                       }
+                               ;
+
+opt_label      :
+                                       {
+                                               $$ = NULL;
+                                       }
+                               | T_LABEL
+                                       {
+                                               char *label_name;
+                                               plpgsql_convert_ident(yytext, &label_name, 1);
+                                               $$ = label_name;
+                                       }
+                               | T_WORD
+                                       {
+                                               /* just to give a better error than "syntax error" */
+                                               yyerror("no such label");
+                                       }
+                               ;
+
+opt_exitcond   : ';'
+                                       { $$ = NULL; }
+                               | K_WHEN expr_until_semi
+                                       { $$ = $2; }
+                               ;
+
+opt_lblname            : T_WORD
+                                       {
+                                               char    *name;
+
+                                               plpgsql_convert_ident(yytext, &name, 1);
+                                               $$ = name;
+                                       }
+                               ;
+
+lno                            :
+                                       {
+                                               $$ = plpgsql_error_lineno = plpgsql_scanner_lineno();
+                                       }
+                               ;
+
+%%
+
+
+PLpgSQL_expr *
+plpgsql_read_expression(int until, const char *expected)
+{
+       return read_sql_construct(until, 0, expected, "SELECT ", true, true, NULL);
+}
+
+static PLpgSQL_expr *
+read_sql_stmt(const char *sqlstart)
+{
+       return read_sql_construct(';', 0, ";", sqlstart, false, true, NULL);
+}
+
+/*
+ * Read a SQL construct and build a PLpgSQL_expr for it.
+ *
+ * until:              token code for expected terminator
+ * until2:             token code for alternate terminator (pass 0 if none)
+ * expected:   text to use in complaining that terminator was not found
+ * sqlstart:   text to prefix to the accumulated SQL text
+ * isexpression: whether to say we're reading an "expression" or a "statement"
+ * valid_sql:   whether to check the syntax of the expr (prefixed with sqlstart)
+ * endtoken:   if not NULL, ending token is stored at *endtoken
+ *                             (this is only interesting if until2 isn't zero)
+ */
+static PLpgSQL_expr *
+read_sql_construct(int until,
+                                  int until2,
+                                  const char *expected,
+                                  const char *sqlstart,
+                                  bool isexpression,
+                                  bool valid_sql,
+                                  int *endtoken)
+{
+       int                                     tok;
+       int                                     lno;
+       PLpgSQL_dstring         ds;
+       int                                     parenlevel = 0;
+       int                                     nparams = 0;
+       int                                     params[1024];
+       char                            buf[32];
+       PLpgSQL_expr            *expr;
+
+       lno = plpgsql_scanner_lineno();
+       plpgsql_dstring_init(&ds);
+       plpgsql_dstring_append(&ds, sqlstart);
+
+       for (;;)
+       {
+               tok = yylex();
+               if (tok == until && parenlevel == 0)
+                       break;
+               if (tok == until2 && parenlevel == 0)
+                       break;
+               if (tok == '(' || tok == '[')
+                       parenlevel++;
+               else if (tok == ')' || tok == ']')
+               {
+                       parenlevel--;
+                       if (parenlevel < 0)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("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 == ';')
+               {
+                       plpgsql_error_lineno = lno;
+                       if (parenlevel != 0)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("mismatched parentheses")));
+                       if (isexpression)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("missing \"%s\" at end of SQL expression",
+                                                               expected)));
+                       else
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("missing \"%s\" at end of SQL statement",
+                                                               expected)));
+               }
+
+               if (plpgsql_SpaceScanned)
+                       plpgsql_dstring_append(&ds, " ");
+
+               /* Check for array overflow */
+               if (nparams >= 1024)
+               {
+                       plpgsql_error_lineno = lno;
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                                        errmsg("too many variables specified in SQL statement")));
+               }
+
+               switch (tok)
+               {
+                       case T_SCALAR:
+                               params[nparams] = yylval.scalar->dno;
+                               snprintf(buf, sizeof(buf), " $%d ", ++nparams);
+                               plpgsql_dstring_append(&ds, buf);
+                               break;
+
+                       case T_ROW:
+                               params[nparams] = yylval.row->rowno;
+                               snprintf(buf, sizeof(buf), " $%d ", ++nparams);
+                               plpgsql_dstring_append(&ds, buf);
+                               break;
+
+                       case T_RECORD:
+                               params[nparams] = yylval.rec->recno;
+                               snprintf(buf, sizeof(buf), " $%d ", ++nparams);
+                               plpgsql_dstring_append(&ds, buf);
                                break;
 
-                           default:
+                       default:
                                plpgsql_dstring_append(&ds, yytext);
-                               while ((tok = yylex()) != ';') {
-                                   if (tok == 0) {
-                                       plpgsql_error_lineno = lno;
-                                       plpgsql_comperrinfo();
-                                       elog(ERROR, "unterminated default value");
-                                   }
-                                   if (plpgsql_SpaceScanned) {
-                                       plpgsql_dstring_append(&ds, " ");
-                                   }
-                                   plpgsql_dstring_append(&ds, yytext);
-                               }
-                               expr->query = strdup(plpgsql_dstring_get(&ds));
-                               plpgsql_dstring_free(&ds);
-
-                               $$ = expr;
                                break;
-                       }
-                   }
-               ;
+               }
+       }
 
-decl_defkey    : K_ASSIGN
-               | K_DEFAULT
+       if (endtoken)
+               *endtoken = tok;
 
-proc_sect      :
-                       {
-                               PLpgSQL_stmts   *new;
+       expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
+       expr->dtype                     = PLPGSQL_DTYPE_EXPR;
+       expr->query                     = pstrdup(plpgsql_dstring_get(&ds));
+       expr->plan                      = NULL;
+       expr->nparams           = nparams;
+       while(nparams-- > 0)
+               expr->params[nparams] = params[nparams];
+       plpgsql_dstring_free(&ds);
 
-                               new = malloc(sizeof(PLpgSQL_stmts));
-                               memset(new, 0, sizeof(PLpgSQL_stmts));
-                               $$ = new;
-                       }
-               | proc_stmts
-                       {
-                               $$ = $1;
-                       }
-               ;
+       if (valid_sql)
+               check_sql_expr(expr->query);
 
-proc_stmts     : proc_stmts proc_stmt
-                       {
-                               if ($1->stmts_used == $1->stmts_alloc) {
-                                   $1->stmts_alloc *= 2;
-                                   $1->stmts = realloc($1->stmts, sizeof(PLpgSQL_stmt *) * $1->stmts_alloc);
-                               }
-                               $1->stmts[$1->stmts_used++] = (struct PLpgSQL_stmt *)$2;
+       return expr;
+}
 
-                               $$ = $1;
-                       }
-               | proc_stmt
-                       {
-                               PLpgSQL_stmts   *new;
+static PLpgSQL_type *
+read_datatype(int tok)
+{
+       int                                     lno;
+       PLpgSQL_dstring         ds;
+       PLpgSQL_type            *result;
+       bool                            needspace = false;
+       int                                     parenlevel = 0;
+
+       lno = plpgsql_scanner_lineno();
+
+       /* Often there will be a lookahead token, but if not, get one */
+       if (tok == YYEMPTY)
+               tok = yylex();
+
+       if (tok == T_DTYPE)
+       {
+               /* lexer found word%TYPE and did its thing already */
+               return yylval.dtype;
+       }
 
-                               new = malloc(sizeof(PLpgSQL_stmts));
-                               memset(new, 0, sizeof(PLpgSQL_stmts));
+       plpgsql_dstring_init(&ds);
+
+       while (tok != ';')
+       {
+               if (tok == 0)
+               {
+                       plpgsql_error_lineno = lno;
+                       if (parenlevel != 0)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("mismatched parentheses")));
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("incomplete datatype declaration")));
+               }
+               /* Possible followers for datatype in a declaration */
+               if (tok == K_NOT || tok == K_ASSIGN || 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--;
+               if (needspace)
+                       plpgsql_dstring_append(&ds, " ");
+               needspace = true;
+               plpgsql_dstring_append(&ds, yytext);
 
-                               new->stmts_alloc = 64;
-                               new->stmts_used  = 1;
-                               new->stmts = malloc(sizeof(PLpgSQL_stmt *) * new->stmts_alloc);
-                               new->stmts[0] = (struct PLpgSQL_stmt *)$1;
+               tok = yylex();
+       }
 
-                               $$ = new;
-                       }
-               ;
-
-proc_stmt      : pl_block
-                       { $$ = $1; }
-               | stmt_assign
-                       { $$ = $1; }
-               | stmt_if
-                       { $$ = $1; }
-               | stmt_loop
-                       { $$ = $1; }
-               | stmt_while
-                       { $$ = $1; }
-               | stmt_fori
-                       { $$ = $1; }
-               | stmt_fors
-                       { $$ = $1; }
-               | stmt_select
-                       { $$ = $1; }
-               | stmt_exit
-                       { $$ = $1; }
-               | stmt_return
-                       { $$ = $1; }
-               | stmt_raise
-                       { $$ = $1; }
-               | stmt_execsql
-                       { $$ = $1; }
-               | stmt_perform
-                       { $$ = $1; }
-               ;
+       plpgsql_push_back_token(tok);
 
-stmt_perform   : K_PERFORM lno expr_until_semi
-                   {
-                       PLpgSQL_stmt_assign *new;
-
-                       new = malloc(sizeof(PLpgSQL_stmt_assign));
-                       memset(new, 0, sizeof(PLpgSQL_stmt_assign));
-
-                       new->cmd_type = PLPGSQL_STMT_ASSIGN;
-                       new->lineno   = $2;
-                       new->varno = -1;
-                       new->expr  = $3;
-
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               ;
-
-stmt_assign    : assign_var lno K_ASSIGN expr_until_semi
-                   {
-                       PLpgSQL_stmt_assign *new;
-
-                       new = malloc(sizeof(PLpgSQL_stmt_assign));
-                       memset(new, 0, sizeof(PLpgSQL_stmt_assign));
-
-                       new->cmd_type = PLPGSQL_STMT_ASSIGN;
-                       new->lineno   = $2;
-                       new->varno = $1;
-                       new->expr  = $4;
-
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               ;
-
-assign_var     : T_VARIABLE
-                   {
-                       if (yylval.var->isconst) {
-                           plpgsql_comperrinfo();
-                           elog(ERROR, "%s is declared CONSTANT", yylval.var->refname);
-                       }
-                       $$ = yylval.var->varno;
-                   }
-               | T_RECFIELD
-                   {
-                       $$ = yylval.recfield->rfno;
-                   }
-               ;
-
-stmt_if                : K_IF lno expr_until_then proc_sect stmt_else K_END K_IF ';'
-                   {
-                       PLpgSQL_stmt_if *new;
-
-                       new = malloc(sizeof(PLpgSQL_stmt_if));
-                       memset(new, 0, sizeof(PLpgSQL_stmt_if));
-
-                       new->cmd_type   = PLPGSQL_STMT_IF;
-                       new->lineno     = $2;
-                       new->cond       = $3;
-                       new->true_body  = $4;
-                       new->false_body = $5;
-
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               ;
-
-stmt_else      :
-                       {
-                               PLpgSQL_stmts   *new;
+       plpgsql_error_lineno = lno;     /* in case of error in parse_datatype */
 
-                               new = malloc(sizeof(PLpgSQL_stmts));
-                               memset(new, 0, sizeof(PLpgSQL_stmts));
-                               $$ = new;
-                       }
-               | K_ELSE proc_sect
-                       { $$ = $2; }
-               ;
-
-stmt_loop      : opt_label K_LOOP lno loop_body
-                   {
-                       PLpgSQL_stmt_loop *new;
-
-                       new = malloc(sizeof(PLpgSQL_stmt_loop));
-                       memset(new, 0, sizeof(PLpgSQL_stmt_loop));
-
-                       new->cmd_type = PLPGSQL_STMT_LOOP;
-                       new->lineno   = $3;
-                       new->label    = $1;
-                       new->body     = $4;
-
-                       plpgsql_ns_pop();
-
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               ;
-
-stmt_while     : opt_label K_WHILE lno expr_until_loop loop_body
-                   {
-                       PLpgSQL_stmt_while *new;
-
-                       new = malloc(sizeof(PLpgSQL_stmt_while));
-                       memset(new, 0, sizeof(PLpgSQL_stmt_while));
-
-                       new->cmd_type = PLPGSQL_STMT_WHILE;
-                       new->lineno   = $3;
-                       new->label    = $1;
-                       new->cond     = $4;
-                       new->body     = $5;
-
-                       plpgsql_ns_pop();
-
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               ;
-
-stmt_fori      : opt_label K_FOR lno fori_var K_IN fori_lower expr_until_loop loop_body
-                   {
-                       PLpgSQL_stmt_fori       *new;
-
-                       new = malloc(sizeof(PLpgSQL_stmt_fori));
-                       memset(new, 0, sizeof(PLpgSQL_stmt_fori));
-
-                       new->cmd_type = PLPGSQL_STMT_FORI;
-                       new->lineno   = $3;
-                       new->label    = $1;
-                       new->var      = $4;
-                       new->reverse  = $6.reverse;
-                       new->lower    = $6.expr;
-                       new->upper    = $7;
-                       new->body     = $8;
-
-                       plpgsql_ns_pop();
-
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               ;
-
-fori_var       : fori_varname
-                   {
-                       PLpgSQL_var     *new;
-
-                       new = malloc(sizeof(PLpgSQL_var));
-
-                       new->dtype      = PLPGSQL_DTYPE_VAR;
-                       new->refname    = $1.name;
-                       new->lineno     = $1.lineno;
-
-                       plpgsql_parse_word("integer");
-
-                       new->datatype   = yylval.dtype;
-                       new->isconst    = false;
-                       new->notnull    = false;
-                       new->default_val = NULL;
-
-                       plpgsql_adddatum((PLpgSQL_datum *)new);
-                       plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, new->varno,
-                                               $1.name);
-
-                       plpgsql_add_initdatums(NULL);
-
-                       $$ = new;
-                   }
-               ;
-
-fori_varname   : T_VARIABLE
-                   {
-                       $$.name = strdup(yytext);
-                       $$.lineno = yylineno;
-                   }
-               | T_WORD
-                   {
-                       $$.name = strdup(yytext);
-                       $$.lineno = yylineno;
-                   }
-               ;
-
-fori_lower     :
-                   {
-                       int                     tok;
-                       int                     lno;
-                       PLpgSQL_dstring ds;
-                       int                     nparams = 0;
-                       int                     params[1024];
-                       char            buf[32];
-                       PLpgSQL_expr    *expr;
-                       int                     firsttok = 1;
-
-                       lno = yylineno;
-                       plpgsql_dstring_init(&ds);
-                       plpgsql_dstring_append(&ds, "SELECT ");
-
-                       $$.reverse = 0;
-                       while((tok = yylex()) != K_DOTDOT) {
-                           if (firsttok) {
-                               firsttok = 0;
-                               if (tok == K_REVERSE) {
-                                   $$.reverse = 1;
-                                   continue;
-                               }
-                           }
-                           if (tok == ';') break;
-                           if (plpgsql_SpaceScanned) {
-                               plpgsql_dstring_append(&ds, " ");
-                           }
-                           switch (tok) {
-                               case T_VARIABLE:
-                                   params[nparams] = yylval.var->varno;
-                                   sprintf(buf, "$%d", ++nparams);
-                                   plpgsql_dstring_append(&ds, buf);
-                                   break;
-                                   
-                               case T_RECFIELD:
-                                   params[nparams] = yylval.recfield->rfno;
-                                   sprintf(buf, "$%d", ++nparams);
-                                   plpgsql_dstring_append(&ds, buf);
-                                   break;
-                                   
-                               case T_TGARGV:
-                                   params[nparams] = yylval.trigarg->dno;
-                                   sprintf(buf, "$%d", ++nparams);
-                                   plpgsql_dstring_append(&ds, buf);
-                                   break;
-                                   
-                               default:
-                                   if (tok == 0) {
-                                       plpgsql_error_lineno = lno;
-                                       plpgsql_comperrinfo();
-                                       elog(ERROR, "missing .. to terminate lower bound of for loop");
-                                   }
-                                   plpgsql_dstring_append(&ds, yytext);
-                                   break;
-                           }
-                       }
+       result = plpgsql_parse_datatype(plpgsql_dstring_get(&ds));
 
-                       expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - 1);
-                       expr->dtype             = PLPGSQL_DTYPE_EXPR;
-                       expr->query             = strdup(plpgsql_dstring_get(&ds));
-                       expr->plan              = NULL;
-                       expr->nparams   = nparams;
-                       while(nparams-- > 0) {
-                           expr->params[nparams] = params[nparams];
-                       }
-                       plpgsql_dstring_free(&ds);
-                       $$.expr = expr;
-                   }
-
-stmt_fors      : opt_label K_FOR lno fors_target K_IN K_SELECT expr_until_loop loop_body
-                   {
-                       PLpgSQL_stmt_fors       *new;
-
-                       new = malloc(sizeof(PLpgSQL_stmt_fors));
-                       memset(new, 0, sizeof(PLpgSQL_stmt_fors));
-
-                       new->cmd_type = PLPGSQL_STMT_FORS;
-                       new->lineno   = $3;
-                       new->label    = $1;
-                       switch ($4->dtype) {
-                           case PLPGSQL_DTYPE_REC:
-                               new->rec = $4;
-                               break;
-                           case PLPGSQL_DTYPE_ROW:
-                               new->row = (PLpgSQL_row *)$4;
-                               break;
-                           default:
-                               plpgsql_comperrinfo();
-                               elog(ERROR, "unknown dtype %d in stmt_fors", $4->dtype);
+       plpgsql_dstring_free(&ds);
+
+       return result;
+}
+
+static PLpgSQL_stmt *
+make_select_stmt(void)
+{
+       PLpgSQL_dstring         ds;
+       int                                     nparams = 0;
+       int                                     params[1024];
+       char                            buf[32];
+       PLpgSQL_expr            *expr;
+       PLpgSQL_row                     *row = NULL;
+       PLpgSQL_rec                     *rec = NULL;
+       int                                     tok;
+       bool                            have_into = false;
+
+       plpgsql_dstring_init(&ds);
+       plpgsql_dstring_append(&ds, "SELECT ");
+
+       while (1)
+       {
+               tok = yylex();
+
+               if (tok == ';')
+                       break;
+               if (tok == 0)
+               {
+                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("unexpected end of function definition")));
+               }
+               if (tok == K_INTO)
+               {
+                       if (have_into)
+                       {
+                               plpgsql_error_lineno = plpgsql_scanner_lineno();
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("INTO specified more than once")));
                        }
-                       new->query = $7;
-                       new->body  = $8;
-
-                       plpgsql_ns_pop();
-
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-
-fors_target    : T_RECORD
-                   {
-                       $$ = yylval.rec;
-                   }
-               | T_ROW
-                   {
-                       $$ = (PLpgSQL_rec *)(yylval.row);
-                   }
-               ;
-
-stmt_select    : K_SELECT lno
-                   {
-                       $$ = make_select_stmt();
-                       $$->lineno = $2;
-                   }
-               ;
-
-stmt_exit      : K_EXIT lno opt_exitlabel opt_exitcond
-                   {
-                       PLpgSQL_stmt_exit *new;
-
-                       new = malloc(sizeof(PLpgSQL_stmt_exit));
-                       memset(new, 0, sizeof(PLpgSQL_stmt_exit));
-
-                       new->cmd_type = PLPGSQL_STMT_EXIT;
-                       new->lineno   = $2;
-                       new->label    = $3;
-                       new->cond     = $4;
-
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               ;
-
-stmt_return    : K_RETURN lno
-                   {
-                       PLpgSQL_stmt_return *new;
-                       PLpgSQL_expr    *expr = NULL;
-                       int             tok;
-
-                       new = malloc(sizeof(PLpgSQL_stmt_return));
-                       memset(new, 0, sizeof(PLpgSQL_stmt_return));
-
-                       if (plpgsql_curr_compile->fn_retistuple) {
-                           new->retistuple = true;
-                           new->retrecno   = -1;
-                           switch (tok = yylex()) {
-                               case K_NULL:
-                                   expr = NULL;
-                                   break;
-
-                               case T_ROW:
-                                   expr = make_tupret_expr(yylval.row);
-                                   break;
+                       tok = yylex();
+                       switch (tok)
+                       {
+                               case T_ROW:
+                                       row = yylval.row;
+                                       check_assignable((PLpgSQL_datum *) row);
+                                       have_into = true;
+                                       break;
 
                                case T_RECORD:
-                                   new->retrecno = yylval.rec->recno;
-                                   expr = NULL;
-                                   break;
+                                       rec = yylval.rec;
+                                       check_assignable((PLpgSQL_datum *) rec);
+                                       have_into = true;
+                                       break;
+
+                               case T_SCALAR:
+                                       row = read_into_scalar_list(yytext, yylval.scalar);
+                                       have_into = true;
+                                       break;
 
                                default:
-                                   yyerror("return type mismatch in function returning table row");
-                                   break;
-                           }
-                           if (yylex() != ';') {
-                               yyerror("expected ';'");
-                           }
-                       } else {
-                           new->retistuple = false;
-                           expr = plpgsql_read_expression(';', ";");
+                                       /* Treat the INTO as non-special */
+                                       plpgsql_dstring_append(&ds, " INTO ");
+                                       plpgsql_push_back_token(tok);
+                                       break;
                        }
+                       continue;
+               }
 
-                       new->cmd_type = PLPGSQL_STMT_RETURN;
-                       new->lineno   = $2;
-                       new->expr     = expr;
-
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               ;
-
-stmt_raise     : K_RAISE lno raise_level raise_msg raise_params ';'
-                   {
-                       PLpgSQL_stmt_raise      *new;
-
-                       new = malloc(sizeof(PLpgSQL_stmt_raise));
-
-                       new->cmd_type   = PLPGSQL_STMT_RAISE;
-                       new->lineno     = $2;
-                       new->elog_level = $3;
-                       new->message    = $4;
-                       new->nparams    = $5.nused;
-                       new->params     = malloc(sizeof(int) * $5.nused);
-                       memcpy(new->params, $5.dtnums, sizeof(int) * $5.nused);
-
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               | K_RAISE lno raise_level raise_msg ';'
-                   {
-                       PLpgSQL_stmt_raise      *new;
-
-                       new = malloc(sizeof(PLpgSQL_stmt_raise));
-
-                       new->cmd_type   = PLPGSQL_STMT_RAISE;
-                       new->lineno     = $2;
-                       new->elog_level = $3;
-                       new->message    = $4;
-                       new->nparams    = 0;
-                       new->params     = NULL;
-
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               ;
-
-raise_msg      : T_STRING
-                   {
-                       $$ = strdup(yytext);
-                   }
-               ;
-
-raise_level    : K_EXCEPTION
-                   {
-                       $$ = ERROR;
-                   }
-               | K_NOTICE
-                   {
-                       $$ = NOTICE;
-                   }
-               | K_DEBUG
-                   {
-                       $$ = DEBUG;
-                   }
-               ;
-
-raise_params   : raise_params raise_param
-                   {
-                       if ($1.nused == $1.nalloc) {
-                           $1.nalloc *= 2;
-                           $1.dtnums = repalloc($1.dtnums, sizeof(int) * $1.nalloc);
-                       }
-                       $1.dtnums[$1.nused++] = $2;
-
-                       $$.nalloc = $1.nalloc;
-                       $$.nused  = $1.nused;
-                       $$.dtnums = $1.dtnums;
-                   }
-               | raise_param
-                   {
-                       $$.nalloc = 1;
-                       $$.nused  = 1;
-                       $$.dtnums = palloc(sizeof(int) * $$.nalloc);
-                       $$.dtnums[0] = $1;
-                   }
-               ;
-
-raise_param    : ',' T_VARIABLE
-                   {
-                       $$ = yylval.var->varno;
-                   }
-               | ',' T_RECFIELD
-                   {
-                       $$ = yylval.recfield->rfno;
-                   }
-               | ',' T_TGARGV
-                   {
-                       $$ = yylval.trigarg->dno;
-                   }
-               ;
-
-loop_body      : proc_sect K_END K_LOOP ';'
-                   { $$ = $1; }
-               ;
+               if (plpgsql_SpaceScanned)
+                       plpgsql_dstring_append(&ds, " ");
 
-stmt_execsql   : execsql_start lno
-                   {
-                       PLpgSQL_stmt_execsql    *new;
+               /* Check for array overflow */
+               if (nparams >= 1024)
+               {
+                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                                        errmsg("too many parameters specified in SQL statement")));
+               }
 
-                       new = malloc(sizeof(PLpgSQL_stmt_execsql));
-                       new->cmd_type = PLPGSQL_STMT_EXECSQL;
-                       new->lineno   = $2;
-                       new->sqlstmt  = read_sqlstmt(';', ";", $1);
+               switch (tok)
+               {
+                       case T_SCALAR:
+                               params[nparams] = yylval.scalar->dno;
+                               snprintf(buf, sizeof(buf), " $%d ", ++nparams);
+                               plpgsql_dstring_append(&ds, buf);
+                               break;
 
-                       $$ = (PLpgSQL_stmt *)new;
-                   }
-               ;
+                       case T_ROW:
+                               params[nparams] = yylval.row->rowno;
+                               snprintf(buf, sizeof(buf), " $%d ", ++nparams);
+                               plpgsql_dstring_append(&ds, buf);
+                               break;
 
-execsql_start  : T_WORD
-                   { $$ = strdup(yytext); }
-               | T_ERROR
-                   { $$ = strdup(yytext); }
-               ;
+                       case T_RECORD:
+                               params[nparams] = yylval.rec->recno;
+                               snprintf(buf, sizeof(buf), " $%d ", ++nparams);
+                               plpgsql_dstring_append(&ds, buf);
+                               break;
 
-expr_until_semi        :
-                   { $$ = plpgsql_read_expression(';', ";"); }
-               ;
+                       default:
+                               plpgsql_dstring_append(&ds, yytext);
+                               break;
+               }
+       }
 
-expr_until_then        :
-                   { $$ = plpgsql_read_expression(K_THEN, "THEN"); }
-               ;
+       expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
+       expr->dtype                     = PLPGSQL_DTYPE_EXPR;
+       expr->query                     = pstrdup(plpgsql_dstring_get(&ds));
+       expr->plan                      = NULL;
+       expr->nparams           = nparams;
+       while(nparams-- > 0)
+               expr->params[nparams] = params[nparams];
+       plpgsql_dstring_free(&ds);
 
-expr_until_loop        :
-                   { $$ = plpgsql_read_expression(K_LOOP, "LOOP"); }
-               ;
+       check_sql_expr(expr->query);
 
-opt_label      :
-                   {
-                       plpgsql_ns_push(NULL);
-                       $$ = NULL;
-                   }
-               | '<' '<' opt_lblname '>' '>'
-                   {
-                       plpgsql_ns_push($3);
-                       $$ = $3;
-                   }
-               ;
-
-opt_exitlabel  :
-                   { $$ = NULL; }
-               | T_LABEL
-                   { $$ = strdup(yytext); }
-               ;
+       if (have_into)
+       {
+               PLpgSQL_stmt_select *select;
 
-opt_exitcond   : ';'
-                   { $$ = NULL; }
-               | K_WHEN expr_until_semi
-                   { $$ = $2; }
-               ;
-
-opt_lblname    : T_WORD
-                   { $$ = strdup(yytext); }
-               ;
-
-lno            :
-                   {
-                       plpgsql_error_lineno = yylineno;
-                       $$ = yylineno;
-                   }
-               ;
+               select = palloc0(sizeof(PLpgSQL_stmt_select));
+               select->cmd_type = PLPGSQL_STMT_SELECT;
+               select->rec              = rec;
+               select->row              = row;
+               select->query    = expr;
 
-%%
-
-PLpgSQL_expr *
-plpgsql_read_expression (int until, char *s)
-{
-    return read_sqlstmt(until, s, "SELECT ");
-}
+               return (PLpgSQL_stmt *)select;
+       }
+       else
+       {
+               PLpgSQL_stmt_execsql *execsql;
 
+               execsql = palloc(sizeof(PLpgSQL_stmt_execsql));
+               execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
+               execsql->sqlstmt  = expr;
 
-static PLpgSQL_expr *
-read_sqlstmt (int until, char *s, char *sqlstart)
-{
-    int                        tok;
-    int                        lno;
-    PLpgSQL_dstring    ds;
-    int                        nparams = 0;
-    int                        params[1024];
-    char               buf[32];
-    PLpgSQL_expr       *expr;
-
-    lno = yylineno;
-    plpgsql_dstring_init(&ds);
-    plpgsql_dstring_append(&ds, sqlstart);
-
-    while((tok = yylex()) != until) {
-       if (tok == ';') break;
-       if (plpgsql_SpaceScanned) {
-           plpgsql_dstring_append(&ds, " ");
+               return (PLpgSQL_stmt *)execsql;
        }
-        switch (tok) {
-           case T_VARIABLE:
-               params[nparams] = yylval.var->varno;
-               sprintf(buf, "$%d", ++nparams);
-               plpgsql_dstring_append(&ds, buf);
-               break;
-               
-           case T_RECFIELD:
-               params[nparams] = yylval.recfield->rfno;
-               sprintf(buf, "$%d", ++nparams);
-               plpgsql_dstring_append(&ds, buf);
-               break;
-               
-           case T_TGARGV:
-               params[nparams] = yylval.trigarg->dno;
-               sprintf(buf, "$%d", ++nparams);
-               plpgsql_dstring_append(&ds, buf);
-               break;
-               
-           default:
-               if (tok == 0) {
-                   plpgsql_error_lineno = lno;
-                   plpgsql_comperrinfo();
-                   elog(ERROR, "missing %s at end of SQL statement", s);
-               }
-               plpgsql_dstring_append(&ds, yytext);
-               break;
-        }
-    }
-
-    expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - 1);
-    expr->dtype                = PLPGSQL_DTYPE_EXPR;
-    expr->query                = strdup(plpgsql_dstring_get(&ds));
-    expr->plan         = NULL;
-    expr->nparams      = nparams;
-    while(nparams-- > 0) {
-        expr->params[nparams] = params[nparams];
-    }
-    plpgsql_dstring_free(&ds);
-    
-    return expr;
 }
 
 
 static PLpgSQL_stmt *
-make_select_stmt()
+make_fetch_stmt(void)
 {
-    int                        tok;
-    int                        lno;
-    PLpgSQL_dstring    ds;
-    int                        nparams = 0;
-    int                        params[1024];
-    char               buf[32];
-    PLpgSQL_expr       *expr;
-    PLpgSQL_row                *row = NULL;
-    PLpgSQL_rec                *rec = NULL;
-    PLpgSQL_stmt_select        *select;
-    int                        have_nexttok = 0;
-
-    lno = yylineno;
-    plpgsql_dstring_init(&ds);
-    plpgsql_dstring_append(&ds, "SELECT ");
-
-    while((tok = yylex()) != K_INTO) {
-       if (tok == ';') {
-           PLpgSQL_stmt_execsql        *execsql;
-
-           expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - 1);
-           expr->dtype         = PLPGSQL_DTYPE_EXPR;
-           expr->query         = strdup(plpgsql_dstring_get(&ds));
-           expr->plan          = NULL;
-           expr->nparams       = nparams;
-           while(nparams-- > 0) {
-               expr->params[nparams] = params[nparams];
-           }
-           plpgsql_dstring_free(&ds);
+       int                                     tok;
+       PLpgSQL_row                *row = NULL;
+       PLpgSQL_rec                *rec = NULL;
+       PLpgSQL_stmt_fetch *fetch;
+
+       /* We have already parsed everything through the INTO keyword */
+
+       tok = yylex();
+       switch (tok)
+       {
+               case T_ROW:
+                       row = yylval.row;
+                       check_assignable((PLpgSQL_datum *) row);
+                       break;
 
-           execsql = malloc(sizeof(PLpgSQL_stmt_execsql));
-           execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
-           execsql->sqlstmt  = expr;
+               case T_RECORD:
+                       rec = yylval.rec;
+                       check_assignable((PLpgSQL_datum *) rec);
+                       break;
 
-           return (PLpgSQL_stmt *)execsql;
-       }
+               case T_SCALAR:
+                       row = read_into_scalar_list(yytext, yylval.scalar);
+                       break;
 
-       if (plpgsql_SpaceScanned) {
-           plpgsql_dstring_append(&ds, " ");
+               default:
+                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("syntax error at \"%s\"", yytext),
+                                        errdetail("Expected record variable, row variable, "
+                                                          "or list of scalar variables.")));
        }
-        switch (tok) {
-           case T_VARIABLE:
-               params[nparams] = yylval.var->varno;
-               sprintf(buf, "$%d", ++nparams);
-               plpgsql_dstring_append(&ds, buf);
-               break;
-               
-           case T_RECFIELD:
-               params[nparams] = yylval.recfield->rfno;
-               sprintf(buf, "$%d", ++nparams);
-               plpgsql_dstring_append(&ds, buf);
-               break;
-               
-           case T_TGARGV:
-               params[nparams] = yylval.trigarg->dno;
-               sprintf(buf, "$%d", ++nparams);
-               plpgsql_dstring_append(&ds, buf);
-               break;
-               
-           default:
-               if (tok == 0) {
-                   plpgsql_error_lineno = yylineno;
-                   plpgsql_comperrinfo();
-                   elog(ERROR, "unexpected end of file");
-               }
-               plpgsql_dstring_append(&ds, yytext);
-               break;
-        }
-    }
-
-    tok = yylex();
-    switch (tok) {
-        case T_ROW:
-           row = yylval.row;
-           break;
-
-        case T_RECORD:
-           rec = yylval.rec;
-           break;
-
-       case T_VARIABLE:
-       case T_RECFIELD:
-           {
-               PLpgSQL_var     *var;
-               PLpgSQL_recfield *recfield;
-               int             nfields = 1;
-               char            *fieldnames[1024];
-               int             varnos[1024];
-
-               switch (tok) {
-                   case T_VARIABLE:
-                       var = yylval.var;
-                       fieldnames[0] = strdup(yytext);
-                       varnos[0]     = var->varno;
+
+       tok = yylex();
+       if (tok != ';')
+               yyerror("syntax error");
+
+       fetch = palloc0(sizeof(PLpgSQL_stmt_select));
+       fetch->cmd_type = PLPGSQL_STMT_FETCH;
+       fetch->rec               = rec;
+       fetch->row               = row;
+
+       return (PLpgSQL_stmt *)fetch;
+}
+
+
+static void
+check_assignable(PLpgSQL_datum *datum)
+{
+       switch (datum->dtype)
+       {
+               case PLPGSQL_DTYPE_VAR:
+                       if (((PLpgSQL_var *) datum)->isconst)
+                       {
+                               plpgsql_error_lineno = plpgsql_scanner_lineno();
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
+                                                errmsg("\"%s\" is declared CONSTANT",
+                                                               ((PLpgSQL_var *) datum)->refname)));
+                       }
                        break;
-                   
-                   case T_RECFIELD:
-                       recfield = yylval.recfield;
-                       fieldnames[0] = strdup(yytext);
-                       varnos[0]     = recfield->rfno;
+               case PLPGSQL_DTYPE_ROW:
+                       /* always assignable? */
                        break;
+               case PLPGSQL_DTYPE_REC:
+                       /* always assignable?  What about NEW/OLD? */
+                       break;
+               case PLPGSQL_DTYPE_RECFIELD:
+                       /* always assignable? */
+                       break;
+               case PLPGSQL_DTYPE_ARRAYELEM:
+                       /* always assignable? */
+                       break;
+               case PLPGSQL_DTYPE_TRIGARG:
+                       yyerror("cannot assign to tg_argv");
+                       break;
+               default:
+                       elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+                       break;
+       }
+}
+
+/*
+ * 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(const char *initial_name,
+                                         PLpgSQL_datum *initial_datum)
+{
+       int                              nfields;
+       char                    *fieldnames[1024];
+       int                              varnos[1024];
+       PLpgSQL_row             *row;
+       int                              tok;
+
+       check_assignable(initial_datum);
+       fieldnames[0] = pstrdup(initial_name);
+       varnos[0]         = initial_datum->dno;
+       nfields           = 1;
+
+       while ((tok = yylex()) == ',')
+       {
+               /* Check for array overflow */
+               if (nfields >= 1024)
+               {
+                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                                        errmsg("too many INTO variables specified")));
                }
 
-               while ((tok = yylex()) == ',') {
-                   tok = yylex();
-                   switch(tok) {
-                       case T_VARIABLE:
-                           var = yylval.var;
-                           fieldnames[nfields] = strdup(yytext);
-                           varnos[nfields++]   = var->varno;
-                           break;
-
-                       case T_RECFIELD:
-                           recfield = yylval.recfield;
-                           fieldnames[0] = strdup(yytext);
-                           varnos[0]     = recfield->rfno;
-                           break;
+               tok = yylex();
+               switch(tok)
+               {
+                       case T_SCALAR:
+                               check_assignable(yylval.scalar);
+                               fieldnames[nfields] = pstrdup(yytext);
+                               varnos[nfields++]       = yylval.scalar->dno;
+                               break;
 
                        default:
-                           elog(ERROR, "plpgsql: %s is not a variable or record field", yytext);
-                   }
-               }
-               row = malloc(sizeof(PLpgSQL_row));
-               row->dtype = PLPGSQL_DTYPE_ROW;
-               row->refname = strdup("*internal*");
-               row->lineno = yylineno;
-               row->rowtypeclass = InvalidOid;
-               row->nfields = nfields;
-               row->fieldnames = malloc(sizeof(char *) * nfields);
-               row->varnos = malloc(sizeof(int) * nfields);
-               while (--nfields >= 0) {
-                   row->fieldnames[nfields] = fieldnames[nfields];
-                   row->varnos[nfields] = varnos[nfields];
+                               plpgsql_error_lineno = plpgsql_scanner_lineno();
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("\"%s\" is not a scalar variable",
+                                                               yytext)));
                }
+       }
 
-               plpgsql_adddatum((PLpgSQL_datum *)row);
+       /*
+        * We read an extra, non-comma token from yylex(), so push it
+        * back onto the input stream
+        */
+       plpgsql_push_back_token(tok);
+
+       row = palloc(sizeof(PLpgSQL_row));
+       row->dtype = PLPGSQL_DTYPE_ROW;
+       row->refname = pstrdup("*internal*");
+       row->lineno = plpgsql_scanner_lineno();
+       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];
+       }
 
-               have_nexttok = 1;
-           }
-           break;
+       plpgsql_adddatum((PLpgSQL_datum *)row);
 
-        default:
-           {
-               if (plpgsql_SpaceScanned) {
-                   plpgsql_dstring_append(&ds, " ");
-               }
-               plpgsql_dstring_append(&ds, yytext);
+       return row;
+}
 
-               while(1) {
-                   tok = yylex();
-                   if (tok == ';') {
-                       PLpgSQL_stmt_execsql    *execsql;
-
-                       expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - 1);
-                       expr->dtype             = PLPGSQL_DTYPE_EXPR;
-                       expr->query             = strdup(plpgsql_dstring_get(&ds));
-                       expr->plan              = NULL;
-                       expr->nparams   = nparams;
-                       while(nparams-- > 0) {
-                           expr->params[nparams] = params[nparams];
-                       }
-                       plpgsql_dstring_free(&ds);
+/*
+ * Convert a single scalar into a "row" list.  This is exactly
+ * like read_into_scalar_list except we never consume any input.
+ * In fact, since this can be invoked long after the source
+ * input was actually read, the lineno has to be passed in.
+ */
+static PLpgSQL_row *
+make_scalar_list1(const char *initial_name,
+                                 PLpgSQL_datum *initial_datum,
+                                 int lineno)
+{
+       PLpgSQL_row             *row;
 
-                       execsql = malloc(sizeof(PLpgSQL_stmt_execsql));
-                       execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
-                       execsql->sqlstmt  = expr;
+       check_assignable(initial_datum);
 
-                       return (PLpgSQL_stmt *)execsql;
-                   }
+       row = palloc(sizeof(PLpgSQL_row));
+       row->dtype = PLPGSQL_DTYPE_ROW;
+       row->refname = pstrdup("*internal*");
+       row->lineno = lineno;
+       row->rowtupdesc = NULL;
+       row->nfields = 1;
+       row->fieldnames = palloc(sizeof(char *));
+       row->varnos = palloc(sizeof(int));
+       row->fieldnames[0] = pstrdup(initial_name);
+       row->varnos[0] = initial_datum->dno;
 
-                   if (plpgsql_SpaceScanned) {
-                       plpgsql_dstring_append(&ds, " ");
-                   }
-                   switch (tok) {
-                       case T_VARIABLE:
-                           params[nparams] = yylval.var->varno;
-                           sprintf(buf, "$%d", ++nparams);
-                           plpgsql_dstring_append(&ds, buf);
-                           break;
-                           
-                       case T_RECFIELD:
-                           params[nparams] = yylval.recfield->rfno;
-                           sprintf(buf, "$%d", ++nparams);
-                           plpgsql_dstring_append(&ds, buf);
-                           break;
-                           
-                       case T_TGARGV:
-                           params[nparams] = yylval.trigarg->dno;
-                           sprintf(buf, "$%d", ++nparams);
-                           plpgsql_dstring_append(&ds, buf);
-                           break;
-                           
-                       default:
-                           if (tok == 0) {
-                               plpgsql_error_lineno = yylineno;
-                               plpgsql_comperrinfo();
-                               elog(ERROR, "unexpected end of file");
-                           }
-                           plpgsql_dstring_append(&ds, yytext);
-                           break;
-                   }
-               }
-           }
-    }
-
-    /************************************************************
-     * Eat up the rest of the statement after the target fields
-     ************************************************************/
-    while(1) {
-       if (!have_nexttok) {
-           tok = yylex();
-       }
-       have_nexttok = 0;
-       if (tok == ';') {
-           break;
-       }
+       plpgsql_adddatum((PLpgSQL_datum *)row);
 
-       if (plpgsql_SpaceScanned) {
-           plpgsql_dstring_append(&ds, " ");
-       }
-       switch (tok) {
-           case T_VARIABLE:
-               params[nparams] = yylval.var->varno;
-               sprintf(buf, "$%d", ++nparams);
-               plpgsql_dstring_append(&ds, buf);
-               break;
-               
-           case T_RECFIELD:
-               params[nparams] = yylval.recfield->rfno;
-               sprintf(buf, "$%d", ++nparams);
-               plpgsql_dstring_append(&ds, buf);
-               break;
-               
-           case T_TGARGV:
-               params[nparams] = yylval.trigarg->dno;
-               sprintf(buf, "$%d", ++nparams);
-               plpgsql_dstring_append(&ds, buf);
-               break;
-               
-           default:
-               if (tok == 0) {
-                   plpgsql_error_lineno = yylineno;
-                   plpgsql_comperrinfo();
-                   elog(ERROR, "unexpected end of file");
-               }
-               plpgsql_dstring_append(&ds, yytext);
-               break;
-       }
-    }
-
-    expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * (nparams - 1));
-    expr->dtype                = PLPGSQL_DTYPE_EXPR;
-    expr->query                = strdup(plpgsql_dstring_get(&ds));
-    expr->plan         = NULL;
-    expr->nparams      = nparams;
-    while(nparams-- > 0) {
-        expr->params[nparams] = params[nparams];
-    }
-    plpgsql_dstring_free(&ds);
-
-    select = malloc(sizeof(PLpgSQL_stmt_select));
-    memset(select, 0, sizeof(PLpgSQL_stmt_select));
-    select->cmd_type = PLPGSQL_STMT_SELECT;
-    select->rec      = rec;
-    select->row      = row;
-    select->query    = expr;
-    
-    return (PLpgSQL_stmt *)select;
+       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.
+ */
+static void
+check_sql_expr(const char *stmt)
+{
+       ErrorContextCallback  syntax_errcontext;
+       ErrorContextCallback *previous_errcontext;
+       MemoryContext oldCxt;
 
-static PLpgSQL_expr *
-make_tupret_expr(PLpgSQL_row *row)
+       if (!plpgsql_check_syntax)
+               return;
+
+       /*
+        * Setup error traceback support for ereport(). The previous
+        * ereport callback is installed by pl_comp.c, but we don't want
+        * that to be invoked (since it will try to transpose the syntax
+        * error to be relative to the CREATE FUNCTION), so temporarily
+        * remove it from the list of callbacks.
+        */
+       Assert(error_context_stack->callback == plpgsql_compile_error_callback);
+
+       previous_errcontext = error_context_stack;
+       syntax_errcontext.callback = plpgsql_sql_error_callback;
+       syntax_errcontext.arg = (char *) stmt;
+       syntax_errcontext.previous = error_context_stack->previous;
+       error_context_stack = &syntax_errcontext;
+
+       oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
+       (void) raw_parser(stmt);
+       MemoryContextSwitchTo(oldCxt);
+
+       /* Restore former ereport callback */
+       error_context_stack = previous_errcontext;
+}
+
+static void
+plpgsql_sql_error_callback(void *arg)
 {
-    PLpgSQL_dstring    ds;
-    PLpgSQL_expr       *expr;
-    int                        i;
-    char               buf[16];
-
-    expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * (row->nfields - 1));
-    expr->dtype                = PLPGSQL_DTYPE_EXPR;
-
-    plpgsql_dstring_init(&ds);
-    plpgsql_dstring_append(&ds, "SELECT ");
-
-    for (i = 0; i < row->nfields; i++) {
-        sprintf(buf, "%s$%d", (i > 0) ? "," : "", i + 1);
-       plpgsql_dstring_append(&ds, buf);
-       expr->params[i] = row->varnos[i];
-    }
-
-    expr->query         = strdup(plpgsql_dstring_get(&ds));
-    expr->plan          = NULL;
-    expr->plan_argtypes = NULL;
-    expr->nparams       = row->nfields;
-
-    plpgsql_dstring_free(&ds);
-    return expr;
+       char *sql_stmt = (char *) arg;
+
+       Assert(plpgsql_error_funcname);
+
+       errcontext("SQL statement in PL/PgSQL function \"%s\" near line %d",
+                          plpgsql_error_funcname, plpgsql_error_lineno);
+       internalerrquery(sql_stmt);
+       internalerrposition(geterrposition());
+       errposition(0);
 }
 
+static void
+check_labels(const char *start_label, const char *end_label)
+{
+       if (end_label)
+       {
+               if (!start_label)
+               {
+                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("end label \"%s\" specified for unlabelled block",
+                                                       end_label)));
+               }
+
+               if (strcmp(start_label, end_label) != 0)
+               {
+                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("end label \"%s\" differs from block's label \"%s\"",
+                                                       end_label, start_label)));
+               }
+       }
+}
 
+/* Needed to avoid conflict between different prefix settings: */
+#undef yylex
 
 #include "pl_scan.c"