OSDN Git Service

Modernize plpgsql's handling of parse locations, making it look a lot more
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 9 Nov 2009 00:26:55 +0000 (00:26 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 9 Nov 2009 00:26:55 +0000 (00:26 +0000)
like the core parser's code.  In particular, track locations at the character
rather than line level during parsing, allowing many more parse-time error
conditions to be reported with precise error pointers rather than just
"near line N".

Also, exploit the fact that we no longer need to substitute $N for variable
references by making extracted SQL queries and expressions be exact copies
of subranges of the function text, rather than having random whitespace
changes within them.  This makes it possible to directly map parse error
positions from the core parser onto positions in the function text, which
lets us report them without the previous kluge of showing the intermediate
internal-query form.  (Later it might be good to do that for core
parse-analysis errors too, but this patch is just touching plpgsql's
lexer/parser, not what happens at runtime.)

In passing, make plpgsql's lexer use palloc not malloc.

These changes make plpgsql's parse-time error reports noticeably nicer
(as illustrated by the regression test changes), and will also simplify
the planned removal of plpgsql's separate lexer by reducing the impedance
mismatch between what it does and what the core lexer does.

src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/plpgsql.h
src/pl/plpgsql/src/scan.l
src/test/regress/expected/plpgsql.out

index bda7bb2..fd4d3e6 100644 (file)
@@ -1,15 +1,14 @@
 %{
 /*-------------------------------------------------------------------------
  *
- * gram.y                              - Parser for the PL/pgSQL
- *                                               procedural language
+ * gram.y                              - Parser for the PL/pgSQL procedural language
  *
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.132 2009/11/07 00:52:26 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.133 2009/11/09 00:26:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "plpgsql.h"
 
 #include "catalog/pg_type.h"
-#include "lib/stringinfo.h"
 #include "parser/parser.h"
+#include "parser/parse_type.h"
+#include "parser/scansup.h"
 
 
 /*
+ * We track token locations in terms of byte offsets from the start of the
+ * source string, not the column number/line number representation that
+ * bison uses by default.  Also, to minimize overhead we track only one
+ * location (usually the first token location) for each construct, not
+ * the beginning and ending locations as bison does by default.  It's
+ * therefore sufficient to make YYLTYPE an int.
+ */
+#define YYLTYPE  int
+
+/* Location tracking support --- simpler than bison's default */
+#define YYLLOC_DEFAULT(Current, Rhs, N) \
+       do { \
+               if (N) \
+                       (Current) = (Rhs)[1]; \
+               else \
+                       (Current) = (Rhs)[0]; \
+       } while (0)
+
+/*
  * Bison doesn't allocate anything that needs to live across parser calls,
  * so we can easily have it use palloc instead of malloc.  This prevents
  * memory leaks if we error out during parsing.  Note this only works with
 #define YYFREE   pfree
 
 
+typedef struct
+{
+       int                     location;
+       int                     leaderlen;
+} sql_error_callback_arg;
+
+#define parser_errposition(pos)  plpgsql_scanner_errposition(pos)
+
 static PLpgSQL_expr            *read_sql_construct(int until,
                                                                                        int until2,
                                                                                        int until3,
@@ -40,36 +67,40 @@ static PLpgSQL_expr         *read_sql_construct(int until,
                                                                                        const char *sqlstart,
                                                                                        bool isexpression,
                                                                                        bool valid_sql,
+                                                                                       int *startloc,
                                                                                        int *endtoken);
 static PLpgSQL_expr            *read_sql_expression2(int until, int until2,
                                                                                          const char *expected,
                                                                                          int *endtoken);
 static PLpgSQL_expr    *read_sql_stmt(const char *sqlstart);
 static PLpgSQL_type    *read_datatype(int tok);
-static PLpgSQL_stmt    *make_execsql_stmt(const char *sqlstart, int lineno);
+static PLpgSQL_stmt    *make_execsql_stmt(int firsttoken, int location);
 static PLpgSQL_stmt_fetch *read_fetch_direction(void);
 static void                     complete_direction(PLpgSQL_stmt_fetch *fetch,
                                                                                        bool *check_FROM);
-static PLpgSQL_stmt    *make_return_stmt(int lineno);
-static PLpgSQL_stmt    *make_return_next_stmt(int lineno);
-static PLpgSQL_stmt    *make_return_query_stmt(int lineno);
-static  PLpgSQL_stmt   *make_case(int lineno, PLpgSQL_expr *t_expr,
+static PLpgSQL_stmt    *make_return_stmt(int location);
+static PLpgSQL_stmt    *make_return_next_stmt(int location);
+static PLpgSQL_stmt    *make_return_query_stmt(int location);
+static  PLpgSQL_stmt   *make_case(int location, PLpgSQL_expr *t_expr,
                                                                   List *case_when_list, List *else_stmts);
-static void                     check_assignable(PLpgSQL_datum *datum);
+static void                     check_assignable(PLpgSQL_datum *datum, int location);
 static void                     read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row,
                                                                                  bool *strict);
 static PLpgSQL_row             *read_into_scalar_list(const char *initial_name,
-                                                                                          PLpgSQL_datum *initial_datum);
+                                                                                          PLpgSQL_datum *initial_datum,
+                                                                                          int initial_location);
 static PLpgSQL_row             *make_scalar_list1(const char *initial_name,
                                                                                   PLpgSQL_datum *initial_datum,
-                                                                                  int lineno);
-static void                     check_sql_expr(const char *stmt);
+                                                                                  int lineno, int location);
+static void                     check_sql_expr(const char *stmt, int location,
+                                                                               int leaderlen);
 static void                     plpgsql_sql_error_callback(void *arg);
-static char                    *parse_string_token(const char *token);
-static void                     plpgsql_string_error_callback(void *arg);
+static PLpgSQL_type            *parse_datatype(const char *string, int location);
+static char                    *parse_string_token(const char *token, int location);
 static char                    *check_label(const char *yytxt);
 static void                     check_labels(const char *start_label,
-                                                                         const char *end_label);
+                                                                         const char *end_label,
+                                                                         int end_location);
 static PLpgSQL_expr    *read_cursor_args(PLpgSQL_var *cursor,
                                                                                  int until, const char *expected);
 static List                            *read_raise_options(void);
@@ -78,6 +109,7 @@ static List                          *read_raise_options(void);
 
 %expect 0
 %name-prefix="plpgsql_yy"
+%locations
 
 %union {
                int32                                   ival;
@@ -104,8 +136,9 @@ static List                         *read_raise_options(void);
                }                                               declhdr;
                struct
                {
-                       char *end_label;
                        List *stmts;
+                       char *end_label;
+                       int   end_label_location;
                }                                               loop_body;
                List                                    *list;
                PLpgSQL_type                    *dtype;
@@ -144,7 +177,6 @@ static List                         *read_raise_options(void);
 %type <stmt>   for_control
 
 %type <str>            any_identifier any_name opt_block_label opt_label
-%type <str>            execsql_start
 
 %type <list>   proc_sect proc_stmts stmt_else
 %type <loop_body>      loop_body
@@ -170,8 +202,6 @@ static List                         *read_raise_options(void);
 %type <ival>   opt_scrollable
 %type <fetch>   opt_fetch_direction
 
-%type <ival>   lno
-
                /*
                 * Keyword tokens
                 */
@@ -265,21 +295,21 @@ opt_semi          :
                                | ';'
                                ;
 
-pl_block               : decl_sect K_BEGIN lno proc_sect exception_sect K_END opt_label
+pl_block               : decl_sect K_BEGIN proc_sect exception_sect K_END opt_label
                                        {
                                                PLpgSQL_stmt_block *new;
 
                                                new = palloc0(sizeof(PLpgSQL_stmt_block));
 
                                                new->cmd_type   = PLPGSQL_STMT_BLOCK;
-                                               new->lineno             = $3;
+                                               new->lineno             = plpgsql_location_to_lineno(@2);
                                                new->label              = $1.label;
                                                new->n_initvars = $1.n_initvars;
                                                new->initvarnos = $1.initvarnos;
-                                               new->body               = $4;
-                                               new->exceptions = $5;
+                                               new->body               = $3;
+                                               new->exceptions = $4;
 
-                                               check_labels($1.label, $7);
+                                               check_labels($1.label, $6, @6);
                                                plpgsql_ns_pop();
 
                                                $$ = (PLpgSQL_stmt *)new;
@@ -353,7 +383,8 @@ decl_statement      : decl_varname decl_const decl_datatype decl_notnull decl_defval
                                                        else
                                                                ereport(ERROR,
                                                                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                                                errmsg("row or record variable cannot be CONSTANT")));
+                                                                                errmsg("row or record variable cannot be CONSTANT"),
+                                                                                parser_errposition(@2)));
                                                }
                                                if ($4)
                                                {
@@ -362,7 +393,9 @@ decl_statement      : decl_varname decl_const decl_datatype decl_notnull decl_defval
                                                        else
                                                                ereport(ERROR,
                                                                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                                                errmsg("row or record variable cannot be NOT NULL")));
+                                                                                errmsg("row or record variable cannot be NOT NULL"),
+                                                                                parser_errposition(@4)));
+
                                                }
                                                if ($5 != NULL)
                                                {
@@ -371,7 +404,8 @@ decl_statement      : decl_varname decl_const decl_datatype decl_notnull decl_defval
                                                        else
                                                                ereport(ERROR,
                                                                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                                                errmsg("default value for row or record variable is not supported")));
+                                                                                errmsg("default value for row or record variable is not supported"),
+                                                                                parser_errposition(@5)));
                                                }
                                        }
                                | decl_varname K_ALIAS K_FOR decl_aliasitem ';'
@@ -417,7 +451,7 @@ decl_statement      : decl_varname decl_const decl_datatype decl_notnull decl_defval
                                                                *cp2++ = *cp1;
                                                        *cp2++ = *cp1++;
                                                }
-                                               strcpy(cp2, "'::refcursor");
+                                               strcpy(cp2, "'::pg_catalog.refcursor");
                                                curname_def->query = pstrdup(buf);
                                                new->default_val = curname_def;
 
@@ -462,7 +496,7 @@ decl_cursor_args :
 
                                                new = palloc0(sizeof(PLpgSQL_row));
                                                new->dtype = PLPGSQL_DTYPE_ROW;
-                                               new->lineno = plpgsql_scanner_lineno();
+                                               new->lineno = plpgsql_location_to_lineno(@1);
                                                new->rowtupdesc = NULL;
                                                new->nfields = list_length($2);
                                                new->fieldnames = palloc(new->nfields * sizeof(char *));
@@ -515,13 +549,11 @@ decl_aliasitem    : T_WORD
                                                                                                name[0], NULL, NULL,
                                                                                                NULL);
                                                if (nsi == NULL)
-                                               {
-                                                       plpgsql_error_lineno = plpgsql_scanner_lineno();
                                                        ereport(ERROR,
                                                                        (errcode(ERRCODE_UNDEFINED_OBJECT),
                                                                         errmsg("variable \"%s\" does not exist",
-                                                                                       name[0])));
-                                               }
+                                                                                       name[0]),
+                                                                        parser_errposition(@1)));
 
                                                pfree(name[0]);
 
@@ -538,13 +570,11 @@ decl_aliasitem    : T_WORD
                                                                                                name[0], name[1], NULL,
                                                                                                NULL);
                                                if (nsi == NULL)
-                                               {
-                                                       plpgsql_error_lineno = plpgsql_scanner_lineno();
                                                        ereport(ERROR,
                                                                        (errcode(ERRCODE_UNDEFINED_OBJECT),
                                                                         errmsg("variable \"%s.%s\" does not exist",
-                                                                                       name[0], name[1])));
-                                               }
+                                                                                       name[0], name[1]),
+                                                                        parser_errposition(@1)));
 
                                                pfree(name[0]);
                                                pfree(name[1]);
@@ -559,7 +589,7 @@ decl_varname        : T_WORD
 
                                                plpgsql_convert_ident(yytext, &name, 1);
                                                $$.name = name;
-                                               $$.lineno  = plpgsql_scanner_lineno();
+                                               $$.lineno = plpgsql_location_to_lineno(@1);
                                                /*
                                                 * Check to make sure name isn't already declared
                                                 * in the current block.
@@ -668,41 +698,41 @@ proc_stmt         : pl_block ';'
                                                { $$ = $1; }
                                ;
 
-stmt_perform   : K_PERFORM lno expr_until_semi
+stmt_perform   : K_PERFORM expr_until_semi
                                        {
                                                PLpgSQL_stmt_perform *new;
 
                                                new = palloc0(sizeof(PLpgSQL_stmt_perform));
                                                new->cmd_type = PLPGSQL_STMT_PERFORM;
-                                               new->lineno   = $2;
-                                               new->expr  = $3;
+                                               new->lineno   = plpgsql_location_to_lineno(@1);
+                                               new->expr  = $2;
 
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
                                ;
 
-stmt_assign            : assign_var lno K_ASSIGN expr_until_semi
+stmt_assign            : assign_var 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->lineno   = plpgsql_location_to_lineno(@1);
                                                new->varno = $1;
-                                               new->expr  = $4;
+                                               new->expr  = $3;
 
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
                                ;
 
-stmt_getdiag   : K_GET K_DIAGNOSTICS lno getdiag_list ';'
+stmt_getdiag   : K_GET K_DIAGNOSTICS 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;
+                                               new->lineno   = plpgsql_location_to_lineno(@1);
+                                               new->diag_items  = $3;
 
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
@@ -742,13 +772,14 @@ getdiag_kind : K_ROW_COUNT
 
 getdiag_target : T_DATUM
                                        {
-                                               check_assignable(yylval.datum);
+                                               check_assignable(yylval.datum, @1);
                                                if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW ||
                                                        yylval.datum->dtype == PLPGSQL_DTYPE_REC)
                                                        ereport(ERROR,
                                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                                         errmsg("\"%s\" is not a scalar variable",
-                                                                                       yytext)));
+                                                                                       yytext),
+                                                                        parser_errposition(@1)));
                                                $$ = yylval.datum->dno;
                                        }
                                | T_WORD
@@ -757,14 +788,15 @@ getdiag_target    : T_DATUM
                                                ereport(ERROR,
                                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                                 errmsg("\"%s\" is not a known variable",
-                                                                               yytext)));
+                                                                               yytext),
+                                                                parser_errposition(@1)));
                                        }
                                ;
 
 
 assign_var             : T_DATUM
                                        {
-                                               check_assignable(yylval.datum);
+                                               check_assignable(yylval.datum, @1);
                                                $$ = yylval.datum->dno;
                                        }
                                | assign_var '[' expr_until_rightbracket
@@ -782,16 +814,16 @@ assign_var                : T_DATUM
                                        }
                                ;
 
-stmt_if                        : K_IF lno expr_until_then proc_sect stmt_else K_END K_IF ';'
+stmt_if                        : K_IF 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;
+                                               new->lineno             = plpgsql_location_to_lineno(@1);
+                                               new->cond               = $2;
+                                               new->true_body  = $3;
+                                               new->false_body = $4;
 
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
@@ -801,9 +833,9 @@ stmt_else           :
                                        {
                                                $$ = NIL;
                                        }
-                               | K_ELSIF lno expr_until_then proc_sect stmt_else
+                               | K_ELSIF expr_until_then proc_sect stmt_else
                                        {
-                                               /*
+                                               /*----------
                                                 * Translate the structure:        into:
                                                 *
                                                 * IF c1 THEN                              IF c1 THEN
@@ -815,16 +847,17 @@ stmt_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;
+                                               new_if->lineno          = plpgsql_location_to_lineno(@1);
+                                               new_if->cond            = $2;
+                                               new_if->true_body       = $3;
+                                               new_if->false_body      = $4;
 
                                                /* wrap the if-statement in a "container" list */
                                                $$ = list_make1(new_if);
@@ -836,9 +869,9 @@ stmt_else           :
                                        }
                                ;
 
-stmt_case              : K_CASE lno opt_expr_until_when case_when_list opt_case_else K_END K_CASE ';'
+stmt_case              : K_CASE opt_expr_until_when case_when_list opt_case_else K_END K_CASE ';'
                                        {
-                                               $$ = make_case($2, $3, $4, $5);
+                                               $$ = make_case(@1, $2, $3, $4);
                                        }
                                ;
 
@@ -867,13 +900,13 @@ case_when_list    : case_when_list case_when
                                        }
                                ;
 
-case_when              : K_WHEN lno expr_until_then proc_sect
+case_when              : K_WHEN expr_until_then proc_sect
                                        {
                                                PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
 
-                                               new->lineno     = $2;
-                                               new->expr       = $3;
-                                               new->stmts      = $4;
+                                               new->lineno     = plpgsql_location_to_lineno(@1);
+                                               new->expr       = $2;
+                                               new->stmts      = $3;
                                                $$ = new;
                                        }
                                ;
@@ -897,35 +930,35 @@ opt_case_else     :
                                        }
                                ;
 
-stmt_loop              : opt_block_label K_LOOP lno loop_body
+stmt_loop              : opt_block_label K_LOOP loop_body
                                        {
                                                PLpgSQL_stmt_loop *new;
 
                                                new = palloc0(sizeof(PLpgSQL_stmt_loop));
                                                new->cmd_type = PLPGSQL_STMT_LOOP;
-                                               new->lineno   = $3;
+                                               new->lineno   = plpgsql_location_to_lineno(@2);
                                                new->label        = $1;
-                                               new->body         = $4.stmts;
+                                               new->body         = $3.stmts;
 
-                                               check_labels($1, $4.end_label);
+                                               check_labels($1, $3.end_label, $3.end_label_location);
                                                plpgsql_ns_pop();
 
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
                                ;
 
-stmt_while             : opt_block_label K_WHILE lno expr_until_loop loop_body
+stmt_while             : opt_block_label K_WHILE expr_until_loop loop_body
                                        {
                                                PLpgSQL_stmt_while *new;
 
                                                new = palloc0(sizeof(PLpgSQL_stmt_while));
                                                new->cmd_type = PLPGSQL_STMT_WHILE;
-                                               new->lineno   = $3;
+                                               new->lineno   = plpgsql_location_to_lineno(@2);
                                                new->label        = $1;
-                                               new->cond         = $4;
-                                               new->body         = $5.stmts;
+                                               new->cond         = $3;
+                                               new->body         = $4.stmts;
 
-                                               check_labels($1, $5.end_label);
+                                               check_labels($1, $4.end_label, $4.end_label_location);
                                                plpgsql_ns_pop();
 
                                                $$ = (PLpgSQL_stmt *)new;
@@ -940,6 +973,7 @@ stmt_for            : opt_block_label K_FOR for_control loop_body
                                                        PLpgSQL_stmt_fori               *new;
 
                                                        new = (PLpgSQL_stmt_fori *) $3;
+                                                       new->lineno   = plpgsql_location_to_lineno(@2);
                                                        new->label        = $1;
                                                        new->body         = $4.stmts;
                                                        $$ = (PLpgSQL_stmt *) new;
@@ -953,21 +987,22 @@ stmt_for          : opt_block_label K_FOR for_control loop_body
                                                                   $3->cmd_type == PLPGSQL_STMT_DYNFORS);
                                                        /* forq is the common supertype of all three */
                                                        new = (PLpgSQL_stmt_forq *) $3;
+                                                       new->lineno   = plpgsql_location_to_lineno(@2);
                                                        new->label        = $1;
                                                        new->body         = $4.stmts;
                                                        $$ = (PLpgSQL_stmt *) new;
                                                }
 
-                                               check_labels($1, $4.end_label);
+                                               check_labels($1, $4.end_label, $4.end_label_location);
                                                /* close namespace started in opt_block_label */
                                                plpgsql_ns_pop();
                                        }
                                ;
 
-for_control            :
-                               lno for_variable K_IN
+for_control            : for_variable K_IN
                                        {
                                                int                     tok = yylex();
+                                               int                     tokloc = yylloc;
 
                                                if (tok == K_EXECUTE)
                                                {
@@ -982,27 +1017,29 @@ for_control              :
 
                                                        new = palloc0(sizeof(PLpgSQL_stmt_dynfors));
                                                        new->cmd_type = PLPGSQL_STMT_DYNFORS;
-                                                       new->lineno   = $1;
-                                                       if ($2.rec)
+                                                       if ($1.rec)
                                                        {
-                                                               new->rec = $2.rec;
-                                                               check_assignable((PLpgSQL_datum *) new->rec);
+                                                               new->rec = $1.rec;
+                                                               check_assignable((PLpgSQL_datum *) new->rec, @1);
                                                        }
-                                                       else if ($2.row)
+                                                       else if ($1.row)
                                                        {
-                                                               new->row = $2.row;
-                                                               check_assignable((PLpgSQL_datum *) new->row);
+                                                               new->row = $1.row;
+                                                               check_assignable((PLpgSQL_datum *) new->row, @1);
                                                        }
-                                                       else if ($2.scalar)
+                                                       else if ($1.scalar)
                                                        {
                                                                /* convert single scalar to list */
-                                                               new->row = make_scalar_list1($2.name, $2.scalar, $2.lineno);
+                                                               new->row = make_scalar_list1($1.name, $1.scalar,
+                                                                                                                        $1.lineno, @1);
                                                                /* 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");
+                                                               ereport(ERROR,
+                                                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                                                errmsg("loop variable of loop over rows must be a record or row variable or list of scalar variables"),
+                                                                                parser_errposition(@1)));
                                                        }
                                                        new->query = expr;
 
@@ -1030,22 +1067,21 @@ for_control             :
 
                                                        new = (PLpgSQL_stmt_forc *) palloc0(sizeof(PLpgSQL_stmt_forc));
                                                        new->cmd_type = PLPGSQL_STMT_FORC;
-                                                       new->lineno = $1;
-
                                                        new->curvar = cursor->dno;
 
                                                        /* Should have had a single variable name */
-                                                       plpgsql_error_lineno = $2.lineno;
-                                                       if ($2.scalar && $2.row)
+                                                       if ($1.scalar && $1.row)
                                                                ereport(ERROR,
                                                                                (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                                errmsg("cursor FOR loop must have only one target variable")));
+                                                                                errmsg("cursor FOR loop must have only one target variable"),
+                                                                                parser_errposition(@1)));
 
                                                        /* can't use an unbound cursor this way */
                                                        if (cursor->cursor_explicit_expr == NULL)
                                                                ereport(ERROR,
                                                                                (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                                errmsg("cursor FOR loop must use a bound cursor variable")));
+                                                                                errmsg("cursor FOR loop must use a bound cursor variable"),
+                                                                                parser_errposition(tokloc)));
 
                                                        /* collect cursor's parameters if any */
                                                        new->argquery = read_cursor_args(cursor,
@@ -1053,9 +1089,9 @@ for_control               :
                                                                                                                         "LOOP");
 
                                                        /* create loop's private RECORD variable */
-                                                       plpgsql_convert_ident($2.name, &varname, 1);
+                                                       plpgsql_convert_ident($1.name, &varname, 1);
                                                        new->rec = plpgsql_build_record(varname,
-                                                                                                                       $2.lineno,
+                                                                                                                       $1.lineno,
                                                                                                                        true);
 
                                                        $$ = (PLpgSQL_stmt *) new;
@@ -1063,7 +1099,8 @@ for_control               :
                                                else
                                                {
                                                        PLpgSQL_expr    *expr1;
-                                                       bool                     reverse = false;
+                                                       int                             expr1loc;
+                                                       bool                    reverse = false;
 
                                                        /*
                                                         * We have to distinguish between two
@@ -1096,6 +1133,7 @@ for_control               :
                                                                                                           "SELECT ",
                                                                                                           true,
                                                                                                           false,
+                                                                                                          &expr1loc,
                                                                                                           &tok);
 
                                                        if (tok == K_DOTDOT)
@@ -1108,7 +1146,7 @@ for_control               :
                                                                char                            *varname;
 
                                                                /* Check first expression is well-formed */
-                                                               check_sql_expr(expr1->query);
+                                                               check_sql_expr(expr1->query, expr1loc, 7);
 
                                                                /* Read and check the second one */
                                                                expr2 = read_sql_expression2(K_LOOP, K_BY,
@@ -1123,24 +1161,23 @@ for_control             :
                                                                        expr_by = NULL;
 
                                                                /* Should have had a single variable name */
-                                                               plpgsql_error_lineno = $2.lineno;
-                                                               if ($2.scalar && $2.row)
+                                                               if ($1.scalar && $1.row)
                                                                        ereport(ERROR,
                                                                                        (errcode(ERRCODE_SYNTAX_ERROR),
-                                                                                        errmsg("integer FOR loop must have only one target variable")));
+                                                                                        errmsg("integer FOR loop must have only one target variable"),
+                                                                                        parser_errposition(@1)));
 
                                                                /* create loop's private variable */
-                                                               plpgsql_convert_ident($2.name, &varname, 1);
+                                                               plpgsql_convert_ident($1.name, &varname, 1);
                                                                fvar = (PLpgSQL_var *)
                                                                        plpgsql_build_variable(varname,
-                                                                                                                  $2.lineno,
+                                                                                                                  $1.lineno,
                                                                                                                   plpgsql_build_datatype(INT4OID,
                                                                                                                                                                  -1),
                                                                                                                   true);
 
                                                                new = palloc0(sizeof(PLpgSQL_stmt_fori));
                                                                new->cmd_type = PLPGSQL_STMT_FORI;
-                                                               new->lineno   = $1;
                                                                new->var          = fvar;
                                                                new->reverse  = reverse;
                                                                new->lower        = expr1;
@@ -1161,38 +1198,43 @@ for_control             :
                                                                PLpgSQL_stmt_fors       *new;
 
                                                                if (reverse)
-                                                                       yyerror("cannot specify REVERSE in query FOR loop");
+                                                                       ereport(ERROR,
+                                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                                        errmsg("cannot specify REVERSE in query FOR loop"),
+                                                                                        parser_errposition(tokloc)));
 
                                                                Assert(strncmp(expr1->query, "SELECT ", 7) == 0);
                                                                tmp_query = pstrdup(expr1->query + 7);
                                                                pfree(expr1->query);
                                                                expr1->query = tmp_query;
 
-                                                               check_sql_expr(expr1->query);
+                                                               check_sql_expr(expr1->query, expr1loc, 0);
 
                                                                new = palloc0(sizeof(PLpgSQL_stmt_fors));
                                                                new->cmd_type = PLPGSQL_STMT_FORS;
-                                                               new->lineno   = $1;
-                                                               if ($2.rec)
+                                                               if ($1.rec)
                                                                {
-                                                                       new->rec = $2.rec;
-                                                                       check_assignable((PLpgSQL_datum *) new->rec);
+                                                                       new->rec = $1.rec;
+                                                                       check_assignable((PLpgSQL_datum *) new->rec, @1);
                                                                }
-                                                               else if ($2.row)
+                                                               else if ($1.row)
                                                                {
-                                                                       new->row = $2.row;
-                                                                       check_assignable((PLpgSQL_datum *) new->row);
+                                                                       new->row = $1.row;
+                                                                       check_assignable((PLpgSQL_datum *) new->row, @1);
                                                                }
-                                                               else if ($2.scalar)
+                                                               else if ($1.scalar)
                                                                {
                                                                        /* convert single scalar to list */
-                                                                       new->row = make_scalar_list1($2.name, $2.scalar, $2.lineno);
+                                                                       new->row = make_scalar_list1($1.name, $1.scalar,
+                                                                                                                                $1.lineno, @1);
                                                                        /* 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");
+                                                                       ereport(ERROR,
+                                                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                                                        errmsg("loop variable of loop over rows must be a record or row variable or list of scalar variables"),
+                                                                                        parser_errposition(@1)));
                                                                }
 
                                                                new->query = expr1;
@@ -1223,7 +1265,7 @@ for_control               :
 for_variable   : T_DATUM
                                        {
                                                $$.name = pstrdup(yytext);
-                                               $$.lineno  = plpgsql_scanner_lineno();
+                                               $$.lineno = plpgsql_location_to_lineno(@1);
                                                if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW)
                                                {
                                                        $$.scalar = NULL;
@@ -1248,7 +1290,8 @@ for_variable      : T_DATUM
                                                        plpgsql_push_back_token(tok);
                                                        if (tok == ',')
                                                                $$.row = read_into_scalar_list($$.name,
-                                                                                                                          $$.scalar);
+                                                                                                                          $$.scalar,
+                                                                                                                          @1);
                                                }
                                        }
                                | T_WORD
@@ -1256,7 +1299,7 @@ for_variable      : T_DATUM
                                                int                     tok;
 
                                                $$.name = pstrdup(yytext);
-                                               $$.lineno  = plpgsql_scanner_lineno();
+                                               $$.lineno = plpgsql_location_to_lineno(@1);
                                                $$.scalar = NULL;
                                                $$.rec = NULL;
                                                $$.row = NULL;
@@ -1264,26 +1307,24 @@ for_variable    : T_DATUM
                                                tok = yylex();
                                                plpgsql_push_back_token(tok);
                                                if (tok == ',')
-                                               {
-                                                       plpgsql_error_lineno = $$.lineno;
                                                        ereport(ERROR,
                                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                                         errmsg("\"%s\" is not a known variable",
-                                                                                       $$.name)));
-                                               }
+                                                                                       $$.name),
+                                                                        parser_errposition(@1)));
                                        }
                                ;
 
-stmt_exit              : exit_type lno opt_label opt_exitcond
+stmt_exit              : exit_type opt_label opt_exitcond
                                        {
                                                PLpgSQL_stmt_exit *new;
 
                                                new = palloc0(sizeof(PLpgSQL_stmt_exit));
                                                new->cmd_type = PLPGSQL_STMT_EXIT;
                                                new->is_exit  = $1;
-                                               new->lineno       = $2;
-                                               new->label        = $3;
-                                               new->cond         = $4;
+                                               new->lineno       = plpgsql_location_to_lineno(@1);
+                                               new->label        = $2;
+                                               new->cond         = $3;
 
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
@@ -1299,7 +1340,7 @@ exit_type         : K_EXIT
                                        }
                                ;
 
-stmt_return            : K_RETURN lno
+stmt_return            : K_RETURN
                                        {
                                                int     tok;
 
@@ -1314,21 +1355,21 @@ stmt_return             : K_RETURN lno
                                                 */
                                                if (pg_strcasecmp(yytext, "next") == 0)
                                                {
-                                                       $$ = make_return_next_stmt($2);
+                                                       $$ = make_return_next_stmt(@1);
                                                }
                                                else if (pg_strcasecmp(yytext, "query") == 0)
                                                {
-                                                       $$ = make_return_query_stmt($2);
+                                                       $$ = make_return_query_stmt(@1);
                                                }
                                                else
                                                {
                                                        plpgsql_push_back_token(tok);
-                                                       $$ = make_return_stmt($2);
+                                                       $$ = make_return_stmt(@1);
                                                }
                                        }
                                ;
 
-stmt_raise             : K_RAISE lno
+stmt_raise             : K_RAISE
                                        {
                                                PLpgSQL_stmt_raise              *new;
                                                int     tok;
@@ -1336,7 +1377,7 @@ stmt_raise                : K_RAISE lno
                                                new = palloc(sizeof(PLpgSQL_stmt_raise));
 
                                                new->cmd_type   = PLPGSQL_STMT_RAISE;
-                                               new->lineno             = $2;
+                                               new->lineno             = plpgsql_location_to_lineno(@1);
                                                new->elog_level = ERROR;        /* default */
                                                new->condname   = NULL;
                                                new->message    = NULL;
@@ -1400,7 +1441,7 @@ stmt_raise                : K_RAISE lno
                                                        if (tok == T_STRING)
                                                        {
                                                                /* old style message and parameters */
-                                                               new->message = parse_string_token(yytext);
+                                                               new->message = parse_string_token(yytext, yylloc);
                                                                /*
                                                                 * We expect either a semi-colon, which
                                                                 * indicates no parameters, or a comma that
@@ -1418,7 +1459,8 @@ stmt_raise                : K_RAISE lno
                                                                        expr = read_sql_construct(',', ';', K_USING,
                                                                                                                          ", or ; or USING",
                                                                                                                          "SELECT ",
-                                                                                                                         true, true, &tok);
+                                                                                                                         true, true,
+                                                                                                                         NULL, &tok);
                                                                        new->params = lappend(new->params, expr);
                                                                }
                                                        }
@@ -1432,7 +1474,7 @@ stmt_raise                : K_RAISE lno
 
                                                                        if (yylex() != T_STRING)
                                                                                yyerror("syntax error");
-                                                                       sqlstatestr = parse_string_token(yytext);
+                                                                       sqlstatestr = parse_string_token(yytext, yylloc);
 
                                                                        if (strlen(sqlstatestr) != 5)
                                                                                yyerror("invalid SQLSTATE code");
@@ -1468,12 +1510,7 @@ loop_body                : proc_sect K_END K_LOOP opt_label ';'
                                        {
                                                $$.stmts = $1;
                                                $$.end_label = $4;
-                                       }
-                               ;
-
-stmt_execsql   : execsql_start lno
-                                       {
-                                               $$ = make_execsql_stmt($1, $2);
+                                               $$.end_label_location = @4;
                                        }
                                ;
 
@@ -1482,17 +1519,17 @@ stmt_execsql    : execsql_start lno
  * known plpgsql variable.  The latter two cases are probably syntax errors,
  * but we'll let the core parser decide that.
  */
-execsql_start  : K_INSERT
-                                       { $$ = pstrdup(yytext); }
+stmt_execsql   : K_INSERT
+                                       { $$ = make_execsql_stmt(K_INSERT, @1); }
                                | T_WORD
-                                       { $$ = pstrdup(yytext); }
+                                       { $$ = make_execsql_stmt(T_WORD, @1); }
                                | T_DBLWORD
-                                       { $$ = pstrdup(yytext); }
+                                       { $$ = make_execsql_stmt(T_DBLWORD, @1); }
                                | T_TRIPWORD
-                                       { $$ = pstrdup(yytext); }
+                                       { $$ = make_execsql_stmt(T_TRIPWORD, @1); }
                                ;
 
-stmt_dynexecute : K_EXECUTE lno
+stmt_dynexecute : K_EXECUTE
                                        {
                                                PLpgSQL_stmt_dynexecute *new;
                                                PLpgSQL_expr *expr;
@@ -1501,11 +1538,12 @@ stmt_dynexecute : K_EXECUTE lno
                                                expr = read_sql_construct(K_INTO, K_USING, ';',
                                                                                                  "INTO or USING or ;",
                                                                                                  "SELECT ",
-                                                                                                 true, true, &endtoken);
+                                                                                                 true, true,
+                                                                                                 NULL, &endtoken);
 
                                                new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
                                                new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
-                                               new->lineno = $2;
+                                               new->lineno = plpgsql_location_to_lineno(@1);
                                                new->query = expr;
                                                new->into = false;
                                                new->strict = false;
@@ -1540,18 +1578,18 @@ stmt_dynexecute : K_EXECUTE lno
                                ;
 
 
-stmt_open              : K_OPEN lno cursor_variable
+stmt_open              : K_OPEN cursor_variable
                                        {
                                                PLpgSQL_stmt_open *new;
                                                int                               tok;
 
                                                new = palloc0(sizeof(PLpgSQL_stmt_open));
                                                new->cmd_type = PLPGSQL_STMT_OPEN;
-                                               new->lineno = $2;
-                                               new->curvar = $3->dno;
+                                               new->lineno = plpgsql_location_to_lineno(@1);
+                                               new->curvar = $2->dno;
                                                new->cursor_options = CURSOR_OPT_FAST_PLAN;
 
-                                               if ($3->cursor_explicit_expr == NULL)
+                                               if ($2->cursor_explicit_expr == NULL)
                                                {
                                                        /* be nice if we could use opt_scrollable here */
                                                    tok = yylex();
@@ -1567,14 +1605,12 @@ stmt_open               : K_OPEN lno cursor_variable
                                                        }
 
                                                        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 cursor for an unbound cursor variable.")));
-                                                       }
+                                                                                errdetail("Expected \"FOR\", to open a cursor for an unbound cursor variable."),
+                                                                                parser_errposition(yylloc)));
 
                                                        tok = yylex();
                                                        if (tok == K_EXECUTE)
@@ -1590,16 +1626,16 @@ stmt_open               : K_OPEN lno cursor_variable
                                                else
                                                {
                                                        /* predefined cursor query, so read args */
-                                                       new->argquery = read_cursor_args($3, ';', ";");
+                                                       new->argquery = read_cursor_args($2, ';', ";");
                                                }
 
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
                                ;
 
-stmt_fetch             : K_FETCH lno opt_fetch_direction cursor_variable K_INTO
+stmt_fetch             : K_FETCH opt_fetch_direction cursor_variable K_INTO
                                        {
-                                               PLpgSQL_stmt_fetch *fetch = $3;
+                                               PLpgSQL_stmt_fetch *fetch = $2;
                                                PLpgSQL_rec        *rec;
                                                PLpgSQL_row        *row;
 
@@ -1616,24 +1652,25 @@ stmt_fetch              : K_FETCH lno opt_fetch_direction cursor_variable K_INTO
                                                if (fetch->returns_multiple_rows)
                                                        ereport(ERROR,
                                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                                        errmsg("FETCH statement cannot return multiple rows")));
+                                                                        errmsg("FETCH statement cannot return multiple rows"),
+                                                                        parser_errposition(@1)));
 
-                                               fetch->lineno = $2;
+                                               fetch->lineno = plpgsql_location_to_lineno(@1);
                                                fetch->rec              = rec;
                                                fetch->row              = row;
-                                               fetch->curvar   = $4->dno;
+                                               fetch->curvar   = $3->dno;
                                                fetch->is_move  = false;
 
                                                $$ = (PLpgSQL_stmt *)fetch;
                                        }
                                ;
 
-stmt_move              : K_MOVE lno opt_fetch_direction cursor_variable ';'
+stmt_move              : K_MOVE opt_fetch_direction cursor_variable ';'
                                        {
-                                               PLpgSQL_stmt_fetch *fetch = $3;
+                                               PLpgSQL_stmt_fetch *fetch = $2;
 
-                                               fetch->lineno = $2;
-                                               fetch->curvar   = $4->dno;
+                                               fetch->lineno = plpgsql_location_to_lineno(@1);
+                                               fetch->curvar   = $3->dno;
                                                fetch->is_move  = true;
 
                                                $$ = (PLpgSQL_stmt *)fetch;
@@ -1646,14 +1683,14 @@ opt_fetch_direction     :
                                        }
                                ;
 
-stmt_close             : K_CLOSE lno cursor_variable ';'
+stmt_close             : K_CLOSE cursor_variable ';'
                                        {
                                                PLpgSQL_stmt_close *new;
 
                                                new = palloc(sizeof(PLpgSQL_stmt_close));
                                                new->cmd_type = PLPGSQL_STMT_CLOSE;
-                                               new->lineno = $2;
-                                               new->curvar = $3->dno;
+                                               new->lineno = plpgsql_location_to_lineno(@1);
+                                               new->curvar = $2->dno;
 
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
@@ -1669,16 +1706,17 @@ stmt_null               : K_NULL ';'
 cursor_variable        : T_DATUM
                                        {
                                                if (yylval.datum->dtype != PLPGSQL_DTYPE_VAR)
-                                                       yyerror("cursor variable must be a simple variable");
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                                        errmsg("cursor variable must be a simple variable"),
+                                                                        parser_errposition(@1)));
 
                                                if (((PLpgSQL_var *) yylval.datum)->datatype->typoid != REFCURSOROID)
-                                               {
-                                                       plpgsql_error_lineno = plpgsql_scanner_lineno();
                                                        ereport(ERROR,
                                                                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                                                                         errmsg("variable \"%s\" must be of type cursor or refcursor",
-                                                                                       ((PLpgSQL_var *) yylval.datum)->refname)));
-                                               }
+                                                                                       ((PLpgSQL_var *) yylval.datum)->refname),
+                                                                        parser_errposition(@1)));
                                                $$ = (PLpgSQL_var *) yylval.datum;
                                        }
                                | T_WORD
@@ -1687,13 +1725,14 @@ cursor_variable : T_DATUM
                                                ereport(ERROR,
                                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                                 errmsg("\"%s\" is not a known variable",
-                                                                               yytext)));
+                                                                               yytext),
+                                                                parser_errposition(@1)));
                                        }
                                ;
 
 exception_sect :
                                        { $$ = NULL; }
-                               | K_EXCEPTION lno
+                               | K_EXCEPTION
                                        {
                                                /*
                                                 * We use a mid-rule action to add these
@@ -1702,16 +1741,17 @@ exception_sect  :
                                                 * scope of the names extends to the end of the
                                                 * current block.
                                                 */
+                                               int                     lineno = plpgsql_location_to_lineno(@1);
                                                PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block));
                                                PLpgSQL_variable *var;
 
-                                               var = plpgsql_build_variable("sqlstate", $2,
+                                               var = plpgsql_build_variable("sqlstate", lineno,
                                                                                                         plpgsql_build_datatype(TEXTOID, -1),
                                                                                                         true);
                                                ((PLpgSQL_var *) var)->isconst = true;
                                                new->sqlstate_varno = var->dno;
 
-                                               var = plpgsql_build_variable("sqlerrm", $2,
+                                               var = plpgsql_build_variable("sqlerrm", lineno,
                                                                                                         plpgsql_build_datatype(TEXTOID, -1),
                                                                                                         true);
                                                ((PLpgSQL_var *) var)->isconst = true;
@@ -1721,8 +1761,8 @@ exception_sect    :
                                        }
                                        proc_exceptions
                                        {
-                                               PLpgSQL_exception_block *new = $<exception_block>3;
-                                               new->exc_list = $4;
+                                               PLpgSQL_exception_block *new = $<exception_block>2;
+                                               new->exc_list = $3;
 
                                                $$ = new;
                                        }
@@ -1738,14 +1778,14 @@ proc_exceptions : proc_exceptions proc_exception
                                                }
                                ;
 
-proc_exception : K_WHEN lno proc_conditions K_THEN proc_sect
+proc_exception : K_WHEN proc_conditions K_THEN proc_sect
                                        {
                                                PLpgSQL_exception *new;
 
                                                new = palloc0(sizeof(PLpgSQL_exception));
-                                               new->lineno     = $2;
-                                               new->conditions = $3;
-                                               new->action         = $5;
+                                               new->lineno     = plpgsql_location_to_lineno(@1);
+                                               new->conditions = $2;
+                                               new->action         = $4;
 
                                                $$ = new;
                                        }
@@ -1780,7 +1820,7 @@ proc_condition    : any_name
                                                                /* next token should be a string literal */
                                                                if (yylex() != T_STRING)
                                                                        yyerror("syntax error");
-                                                               sqlstatestr = parse_string_token(yytext);
+                                                               sqlstatestr = parse_string_token(yytext, yylloc);
 
                                                                if (strlen(sqlstatestr) != 5)
                                                                        yyerror("invalid SQLSTATE code");
@@ -1868,12 +1908,6 @@ any_name         : any_identifier
                                        }
                                ;
 
-lno                            :
-                                       {
-                                               $$ = plpgsql_error_lineno = plpgsql_scanner_lineno();
-                                       }
-                               ;
-
 %%
 
 
@@ -1882,7 +1916,7 @@ PLpgSQL_expr *
 plpgsql_read_expression(int until, const char *expected)
 {
        return read_sql_construct(until, 0, 0, expected,
-                                                         "SELECT ", true, true, NULL);
+                                                         "SELECT ", true, true, NULL, NULL);
 }
 
 /* Convenience routine to read an expression with two possible terminators */
@@ -1891,7 +1925,7 @@ read_sql_expression2(int until, int until2, const char *expected,
                                         int *endtoken)
 {
        return read_sql_construct(until, until2, 0, expected,
-                                                         "SELECT ", true, true, endtoken);
+                                                         "SELECT ", true, true, NULL, endtoken);
 }
 
 /* Convenience routine to read a SQL statement that must end with ';' */
@@ -1899,7 +1933,7 @@ static PLpgSQL_expr *
 read_sql_stmt(const char *sqlstart)
 {
        return read_sql_construct(';', 0, 0, ";",
-                                                         sqlstart, false, true, NULL);
+                                                         sqlstart, false, true, NULL, NULL);
 }
 
 /*
@@ -1912,6 +1946,7 @@ read_sql_stmt(const char *sqlstart)
  * 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)
+ * startloc:   if not NULL, location of first token is stored at *startloc
  * endtoken:   if not NULL, ending token is stored at *endtoken
  *                             (this is only interesting if until2 or until3 isn't zero)
  */
@@ -1923,16 +1958,16 @@ read_sql_construct(int until,
                                   const char *sqlstart,
                                   bool isexpression,
                                   bool valid_sql,
+                                  int *startloc,
                                   int *endtoken)
 {
        int                                     tok;
-       int                                     lno;
        StringInfoData          ds;
        bool                            save_LookupIdentifiers;
+       int                                     startlocation = -1;
        int                                     parenlevel = 0;
        PLpgSQL_expr            *expr;
 
-       lno = plpgsql_scanner_lineno();
        initStringInfo(&ds);
        appendStringInfoString(&ds, sqlstart);
 
@@ -1943,6 +1978,8 @@ read_sql_construct(int until,
        for (;;)
        {
                tok = yylex();
+               if (startlocation < 0)                  /* remember loc of first token */
+                       startlocation = yylloc;
                if (tok == until && parenlevel == 0)
                        break;
                if (tok == until2 && parenlevel == 0)
@@ -1966,29 +2003,43 @@ read_sql_construct(int until,
                {
                        if (parenlevel != 0)
                                yyerror("mismatched parentheses");
-                       plpgsql_error_lineno = lno;
                        if (isexpression)
                                ereport(ERROR,
                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                 errmsg("missing \"%s\" at end of SQL expression",
-                                                               expected)));
+                                                               expected),
+                                                parser_errposition(yylloc)));
                        else
                                ereport(ERROR,
                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                 errmsg("missing \"%s\" at end of SQL statement",
-                                                               expected)));
+                                                               expected),
+                                                parser_errposition(yylloc)));
                }
-
-               if (plpgsql_SpaceScanned)
-                       appendStringInfoChar(&ds, ' ');
-               appendStringInfoString(&ds, yytext);
        }
 
        plpgsql_LookupIdentifiers = save_LookupIdentifiers;
 
+       if (startloc)
+               *startloc = startlocation;
        if (endtoken)
                *endtoken = tok;
 
+       /* give helpful complaint about empty input */
+       if (startlocation >= yylloc)
+       {
+               if (isexpression)
+                       yyerror("missing expression");
+               else
+                       yyerror("missing SQL statement");
+       }
+
+       plpgsql_append_source_text(&ds, startlocation, yylloc);
+
+       /* trim any trailing whitespace, for neatness */
+       while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
+               ds.data[--ds.len] = '\0';
+
        expr = palloc0(sizeof(PLpgSQL_expr));
        expr->dtype                     = PLPGSQL_DTYPE_EXPR;
        expr->query                     = pstrdup(ds.data);
@@ -1998,7 +2049,7 @@ read_sql_construct(int until,
        pfree(ds.data);
 
        if (valid_sql)
-               check_sql_expr(expr->query);
+               check_sql_expr(expr->query, startlocation, strlen(sqlstart));
 
        return expr;
 }
@@ -2006,24 +2057,23 @@ read_sql_construct(int until,
 static PLpgSQL_type *
 read_datatype(int tok)
 {
-       int                                     lno;
        StringInfoData          ds;
        char                       *type_name;
+       int                                     startlocation;
        PLpgSQL_type            *result;
-       bool                            needspace = false;
        int                                     parenlevel = 0;
 
        /* Should always be called with LookupIdentifiers off */
        Assert(!plpgsql_LookupIdentifiers);
 
-       lno = plpgsql_scanner_lineno();
-
        initStringInfo(&ds);
 
        /* Often there will be a lookahead token, but if not, get one */
        if (tok == YYEMPTY)
                tok = yylex();
 
+       startlocation = yylloc;
+
        /*
         * If we have a single, double, or triple identifier, check for %TYPE
         * and %ROWTYPE constructs.
@@ -2053,9 +2103,7 @@ read_datatype(int tok)
                                        return result;
                                }
                        }
-                       appendStringInfoString(&ds, " %");
                }
-               needspace = true;
        }
        else if (tok == T_DBLWORD)
        {
@@ -2082,9 +2130,7 @@ read_datatype(int tok)
                                        return result;
                                }
                        }
-                       appendStringInfoString(&ds, " %");
                }
-               needspace = true;
        }
        else if (tok == T_TRIPWORD)
        {
@@ -2103,11 +2149,12 @@ read_datatype(int tok)
                                }
                        }
                        /* there's no tripword rowtype construct */
-                       appendStringInfoString(&ds, " %");
                }
-               needspace = true;
        }
 
+       /* flush temporary usage of ds for rowtype checks */
+       resetStringInfo(&ds);
+
        while (tok != ';')
        {
                if (tok == 0)
@@ -2127,32 +2174,28 @@ read_datatype(int tok)
                        parenlevel++;
                else if (tok == ')')
                        parenlevel--;
-               if (needspace)
-                       appendStringInfoChar(&ds, ' ');
-               needspace = true;
-               appendStringInfoString(&ds, yytext);
 
                tok = yylex();
        }
 
-       plpgsql_push_back_token(tok);
-
+       /* set up ds to contain complete typename text */
+       plpgsql_append_source_text(&ds, startlocation, yylloc);
        type_name = ds.data;
 
        if (type_name[0] == '\0')
                yyerror("missing data type declaration");
 
-       plpgsql_error_lineno = lno;     /* in case of error in parse_datatype */
-
-       result = plpgsql_parse_datatype(type_name);
+       result = parse_datatype(type_name, startlocation);
 
        pfree(ds.data);
 
+       plpgsql_push_back_token(tok);
+
        return result;
 }
 
 static PLpgSQL_stmt *
-make_execsql_stmt(const char *sqlstart, int lineno)
+make_execsql_stmt(int firsttoken, int location)
 {
        StringInfoData          ds;
        bool                            save_LookupIdentifiers;
@@ -2164,9 +2207,10 @@ make_execsql_stmt(const char *sqlstart, int lineno)
        int                                     prev_tok;
        bool                            have_into = false;
        bool                            have_strict = false;
+       int                                     into_start_loc = -1;
+       int                                     into_end_loc = -1;
 
        initStringInfo(&ds);
-       appendStringInfoString(&ds, sqlstart);
 
        /* no need to lookup identifiers within the SQL text */
        save_LookupIdentifiers = plpgsql_LookupIdentifiers;
@@ -2180,15 +2224,13 @@ make_execsql_stmt(const char *sqlstart, int lineno)
         * anywhere in the string, not only at the start; consider CREATE RULE
         * containing an INSERT statement.
         */
-       if (pg_strcasecmp(sqlstart, "insert") == 0)
-               tok = K_INSERT;
-       else
-               tok = 0;
-
+       tok = firsttoken;
        for (;;)
        {
                prev_tok = tok;
                tok = yylex();
+               if (have_into && into_end_loc < 0)
+                       into_end_loc = yylloc;          /* token after the INTO part */
                if (tok == ';')
                        break;
                if (tok == 0)
@@ -2199,19 +2241,33 @@ make_execsql_stmt(const char *sqlstart, int lineno)
                        if (have_into)
                                yyerror("INTO specified more than once");
                        have_into = true;
+                       into_start_loc = yylloc;
                        plpgsql_LookupIdentifiers = true;
                        read_into_target(&rec, &row, &have_strict);
                        plpgsql_LookupIdentifiers = false;
-                       continue;
                }
-
-               if (plpgsql_SpaceScanned)
-                       appendStringInfoChar(&ds, ' ');
-               appendStringInfoString(&ds, yytext);
        }
 
        plpgsql_LookupIdentifiers = save_LookupIdentifiers;
 
+       if (have_into)
+       {
+               /*
+                * Insert an appropriate number of spaces corresponding to the
+                * INTO text, so that locations within the redacted SQL statement
+                * still line up with those in the original source text.
+                */
+               plpgsql_append_source_text(&ds, location, into_start_loc);
+               appendStringInfoSpaces(&ds, into_end_loc - into_start_loc);
+               plpgsql_append_source_text(&ds, into_end_loc, yylloc);
+       }
+       else
+               plpgsql_append_source_text(&ds, location, yylloc);
+
+       /* trim any trailing whitespace, for neatness */
+       while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
+               ds.data[--ds.len] = '\0';
+
        expr = palloc0(sizeof(PLpgSQL_expr));
        expr->dtype                     = PLPGSQL_DTYPE_EXPR;
        expr->query                     = pstrdup(ds.data);
@@ -2220,11 +2276,11 @@ make_execsql_stmt(const char *sqlstart, int lineno)
        expr->ns            = plpgsql_ns_top();
        pfree(ds.data);
 
-       check_sql_expr(expr->query);
+       check_sql_expr(expr->query, location, 0);
 
        execsql = palloc(sizeof(PLpgSQL_stmt_execsql));
        execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
-       execsql->lineno  = lineno;
+       execsql->lineno  = plpgsql_location_to_lineno(location);
        execsql->sqlstmt = expr;
        execsql->into    = have_into;
        execsql->strict  = have_strict;
@@ -2391,32 +2447,41 @@ complete_direction(PLpgSQL_stmt_fetch *fetch,  bool *check_FROM)
 
 
 static PLpgSQL_stmt *
-make_return_stmt(int lineno)
+make_return_stmt(int location)
 {
        PLpgSQL_stmt_return *new;
 
        new = palloc0(sizeof(PLpgSQL_stmt_return));
        new->cmd_type = PLPGSQL_STMT_RETURN;
-       new->lineno   = lineno;
+       new->lineno   = plpgsql_location_to_lineno(location);
        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 or RETURN QUERY");
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("RETURN cannot have a parameter in function returning set"),
+                                        errhint("Use RETURN NEXT or RETURN QUERY."),
+                                        parser_errposition(yylloc)));
        }
        else if (plpgsql_curr_compile->out_param_varno >= 0)
        {
                if (yylex() != ';')
-                       yyerror("RETURN cannot have a parameter in function with OUT parameters");
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("RETURN cannot have a parameter in function with OUT parameters"),
+                                        parser_errposition(yylloc)));
                new->retvarno = plpgsql_curr_compile->out_param_varno;
        }
        else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
        {
                if (yylex() != ';')
-                       yyerror("RETURN cannot have a parameter in function returning void");
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("RETURN cannot have a parameter in function returning void"),
+                                        parser_errposition(yylloc)));
        }
        else if (plpgsql_curr_compile->fn_retistuple)
        {
@@ -2431,22 +2496,27 @@ make_return_stmt(int lineno)
                                        yylval.datum->dtype == PLPGSQL_DTYPE_REC)
                                        new->retvarno = yylval.datum->dno;
                                else
-                                       yyerror("RETURN must specify a record or row variable in function returning row");
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                        errmsg("RETURN must specify a record or row variable in function returning row"),
+                                                        parser_errposition(yylloc)));
                                break;
 
                        default:
-                               yyerror("RETURN must specify a record or row variable in function returning row");
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                errmsg("RETURN must specify a record or row variable in function returning row"),
+                                                parser_errposition(yylloc)));
                                break;
                }
                if (yylex() != ';')
-                       yyerror("RETURN must specify a record or row variable in function returning row");
+                       yyerror("syntax error");
        }
        else
        {
                /*
-                * Note that a well-formed expression is
-                * _required_ here; anything else is a
-                * compile-time error.
+                * Note that a well-formed expression is _required_ here;
+                * anything else is a compile-time error.
                 */
                new->expr = plpgsql_read_expression(';', ";");
        }
@@ -2456,23 +2526,29 @@ make_return_stmt(int lineno)
 
 
 static PLpgSQL_stmt *
-make_return_next_stmt(int lineno)
+make_return_next_stmt(int location)
 {
        PLpgSQL_stmt_return_next *new;
 
        if (!plpgsql_curr_compile->fn_retset)
-               yyerror("cannot use RETURN NEXT in a non-SETOF function");
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("cannot use RETURN NEXT in a non-SETOF function"),
+                                parser_errposition(location)));
 
        new = palloc0(sizeof(PLpgSQL_stmt_return_next));
        new->cmd_type   = PLPGSQL_STMT_RETURN_NEXT;
-       new->lineno             = lineno;
+       new->lineno             = plpgsql_location_to_lineno(location);
        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");
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("RETURN NEXT cannot have a parameter in function with OUT parameters"),
+                                        parser_errposition(yylloc)));
                new->retvarno = plpgsql_curr_compile->out_param_varno;
        }
        else if (plpgsql_curr_compile->fn_retistuple)
@@ -2484,15 +2560,21 @@ make_return_next_stmt(int lineno)
                                        yylval.datum->dtype == PLPGSQL_DTYPE_REC)
                                        new->retvarno = yylval.datum->dno;
                                else
-                                       yyerror("RETURN NEXT must specify a record or row variable in function returning row");
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                        errmsg("RETURN NEXT must specify a record or row variable in function returning row"),
+                                                        parser_errposition(yylloc)));
                                break;
 
                        default:
-                               yyerror("RETURN NEXT must specify a record or row variable in function returning row");
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                errmsg("RETURN NEXT must specify a record or row variable in function returning row"),
+                                                parser_errposition(yylloc)));
                                break;
                }
                if (yylex() != ';')
-                       yyerror("RETURN NEXT must specify a record or row variable in function returning row");
+                       yyerror("syntax error");
        }
        else
                new->expr = plpgsql_read_expression(';', ";");
@@ -2502,17 +2584,20 @@ make_return_next_stmt(int lineno)
 
 
 static PLpgSQL_stmt *
-make_return_query_stmt(int lineno)
+make_return_query_stmt(int location)
 {
        PLpgSQL_stmt_return_query *new;
        int                     tok;
 
        if (!plpgsql_curr_compile->fn_retset)
-               yyerror("cannot use RETURN QUERY in a non-SETOF function");
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("cannot use RETURN QUERY in a non-SETOF function"),
+                                parser_errposition(location)));
 
        new = palloc0(sizeof(PLpgSQL_stmt_return_query));
        new->cmd_type = PLPGSQL_STMT_RETURN_QUERY;
-       new->lineno = lineno;
+       new->lineno = plpgsql_location_to_lineno(location);
 
        /* check for RETURN QUERY EXECUTE */
        if ((tok = yylex()) != K_EXECUTE)
@@ -2545,19 +2630,17 @@ make_return_query_stmt(int lineno)
 
 
 static void
-check_assignable(PLpgSQL_datum *datum)
+check_assignable(PLpgSQL_datum *datum, int location)
 {
        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)));
-                       }
+                                                               ((PLpgSQL_var *) datum)->refname),
+                                                parser_errposition(location)));
                        break;
                case PLPGSQL_DTYPE_ROW:
                        /* always assignable? */
@@ -2604,27 +2687,27 @@ read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict)
                case T_DATUM:
                        if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW)
                        {
-                               check_assignable(yylval.datum);
+                               check_assignable(yylval.datum, yylloc);
                                *row = (PLpgSQL_row *) yylval.datum;
                        }
                        else if (yylval.datum->dtype == PLPGSQL_DTYPE_REC)
                        {
-                               check_assignable(yylval.datum);
+                               check_assignable(yylval.datum, yylloc);
                                *rec = (PLpgSQL_rec *) yylval.datum;
                        }
                        else
                        {
-                               *row = read_into_scalar_list(yytext, yylval.datum);
+                               *row = read_into_scalar_list(yytext, yylval.datum, yylloc);
                        }
                        break;
 
                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 following INTO.")));
+                                                          "or list of scalar variables following INTO."),
+                                        parser_errposition(yylloc)));
        }
 }
 
@@ -2636,7 +2719,8 @@ read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict)
  */
 static PLpgSQL_row *
 read_into_scalar_list(const char *initial_name,
-                                         PLpgSQL_datum *initial_datum)
+                                         PLpgSQL_datum *initial_datum,
+                                         int initial_location)
 {
        int                              nfields;
        char                    *fieldnames[1024];
@@ -2644,7 +2728,7 @@ read_into_scalar_list(const char *initial_name,
        PLpgSQL_row             *row;
        int                              tok;
 
-       check_assignable(initial_datum);
+       check_assignable(initial_datum, initial_location);
        fieldnames[0] = pstrdup(initial_name);
        varnos[0]         = initial_datum->dno;
        nfields           = 1;
@@ -2653,34 +2737,33 @@ read_into_scalar_list(const char *initial_name,
        {
                /* 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")));
-               }
+                                        errmsg("too many INTO variables specified"),
+                                        parser_errposition(yylloc)));
 
                tok = yylex();
-               switch(tok)
+               switch (tok)
                {
                        case T_DATUM:
-                               check_assignable(yylval.datum);
+                               check_assignable(yylval.datum, yylloc);
                                if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW ||
                                        yylval.datum->dtype == PLPGSQL_DTYPE_REC)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("\"%s\" is not a scalar variable",
-                                                                       yytext)));
+                                                                       yytext),
+                                                        parser_errposition(yylloc)));
                                fieldnames[nfields] = pstrdup(yytext);
                                varnos[nfields++]       = yylval.datum->dno;
                                break;
 
                        default:
-                               plpgsql_error_lineno = plpgsql_scanner_lineno();
                                ereport(ERROR,
                                                (errcode(ERRCODE_SYNTAX_ERROR),
                                                 errmsg("\"%s\" is not a known variable",
-                                                               yytext)));
+                                                               yytext),
+                                                parser_errposition(yylloc)));
                }
        }
 
@@ -2693,7 +2776,7 @@ read_into_scalar_list(const char *initial_name,
        row = palloc(sizeof(PLpgSQL_row));
        row->dtype = PLPGSQL_DTYPE_ROW;
        row->refname = pstrdup("*internal*");
-       row->lineno = plpgsql_scanner_lineno();
+       row->lineno = plpgsql_location_to_lineno(initial_location);
        row->rowtupdesc = NULL;
        row->nfields = nfields;
        row->fieldnames = palloc(sizeof(char *) * nfields);
@@ -2712,17 +2795,18 @@ read_into_scalar_list(const char *initial_name,
 /*
  * 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.
+ *
+ * Note: lineno could be computed from location, but since callers
+ * have it at hand already, we may as well pass it in.
  */
 static PLpgSQL_row *
 make_scalar_list1(const char *initial_name,
                                  PLpgSQL_datum *initial_datum,
-                                 int lineno)
+                                 int lineno, int location)
 {
        PLpgSQL_row             *row;
 
-       check_assignable(initial_datum);
+       check_assignable(initial_datum, location);
 
        row = palloc(sizeof(PLpgSQL_row));
        row->dtype = PLPGSQL_DTYPE_ROW;
@@ -2756,30 +2840,29 @@ make_scalar_list1(const char *initial_name,
  * than after parsing has finished, because a malformed SQL statement
  * may cause the PL/PgSQL parser to become confused about statement
  * borders. So it is best to bail out as early as we can.
+ *
+ * It is assumed that "stmt" represents a copy of the function source text
+ * beginning at offset "location", with leader text of length "leaderlen"
+ * (typically "SELECT ") prefixed to the source text.  We use this assumption
+ * to transpose any error cursor position back to the function source text.
+ * If no error cursor is provided, we'll just point at "location".
  */
 static void
-check_sql_expr(const char *stmt)
+check_sql_expr(const char *stmt, int location, int leaderlen)
 {
+       sql_error_callback_arg cbarg;
        ErrorContextCallback  syntax_errcontext;
-       ErrorContextCallback *previous_errcontext;
        MemoryContext oldCxt;
 
        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);
+       cbarg.location = location;
+       cbarg.leaderlen = leaderlen;
 
-       previous_errcontext = error_context_stack;
        syntax_errcontext.callback = plpgsql_sql_error_callback;
-       syntax_errcontext.arg = (char *) stmt;
-       syntax_errcontext.previous = error_context_stack->previous;
+       syntax_errcontext.arg = &cbarg;
+       syntax_errcontext.previous = error_context_stack;
        error_context_stack = &syntax_errcontext;
 
        oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
@@ -2787,65 +2870,104 @@ check_sql_expr(const char *stmt)
        MemoryContextSwitchTo(oldCxt);
 
        /* Restore former ereport callback */
-       error_context_stack = previous_errcontext;
+       error_context_stack = syntax_errcontext.previous;
 }
 
 static void
 plpgsql_sql_error_callback(void *arg)
 {
-       char *sql_stmt = (char *) arg;
+       sql_error_callback_arg *cbarg = (sql_error_callback_arg *) arg;
+       int                     errpos;
 
-       Assert(plpgsql_error_funcname);
+       /*
+        * First, set up internalerrposition to point to the start of the
+        * statement text within the function text.  Note this converts
+        * location (a byte offset) to a character number.
+        */
+       parser_errposition(cbarg->location);
 
-       errcontext("SQL statement in PL/PgSQL function \"%s\" near line %d",
-                          plpgsql_error_funcname, plpgsql_error_lineno);
-       internalerrquery(sql_stmt);
-       internalerrposition(geterrposition());
+       /*
+        * If the core parser provided an error position, transpose it.
+        * Note we are dealing with 1-based character numbers at this point.
+        */
+       errpos = geterrposition();
+       if (errpos > cbarg->leaderlen)
+       {
+               int             myerrpos = getinternalerrposition();
+
+               if (myerrpos > 0)               /* safety check */
+                       internalerrposition(myerrpos + errpos - cbarg->leaderlen - 1);
+       }
+
+       /* In any case, flush errposition --- we want internalerrpos only */
        errposition(0);
 }
 
 /*
+ * Parse a SQL datatype name and produce a PLpgSQL_type structure.
+ *
+ * The heavy lifting is done elsewhere.  Here we are only concerned
+ * with setting up an errcontext link that will let us give an error
+ * cursor pointing into the plpgsql function source, if necessary.
+ * This is handled the same as in check_sql_expr(), and we likewise
+ * expect that the given string is a copy from the source text.
+ */
+static PLpgSQL_type *
+parse_datatype(const char *string, int location)
+{
+       Oid                     type_id;
+       int32           typmod;
+       sql_error_callback_arg cbarg;
+       ErrorContextCallback  syntax_errcontext;
+
+       cbarg.location = location;
+       cbarg.leaderlen = 0;
+
+       syntax_errcontext.callback = plpgsql_sql_error_callback;
+       syntax_errcontext.arg = &cbarg;
+       syntax_errcontext.previous = error_context_stack;
+       error_context_stack = &syntax_errcontext;
+
+       /* Let the main parser try to parse it under standard SQL rules */
+       parseTypeString(string, &type_id, &typmod);
+
+       /* Restore former ereport callback */
+       error_context_stack = syntax_errcontext.previous;
+
+       /* Okay, build a PLpgSQL_type data structure for it */
+       return plpgsql_build_datatype(type_id, typmod);
+}
+
+/*
  * Convert a string-literal token to the represented string value.
  *
  * To do this, we need to invoke the core lexer.  Here we are only concerned
- * with setting up the right errcontext state, which is handled the same as
+ * with setting up an errcontext link, which is handled the same as
  * in check_sql_expr().
  */
 static char *
-parse_string_token(const char *token)
+parse_string_token(const char *token, int location)
 {
        char       *result;
+       sql_error_callback_arg cbarg;
        ErrorContextCallback  syntax_errcontext;
-       ErrorContextCallback *previous_errcontext;
 
-       /* See comments in check_sql_expr() */
-       Assert(error_context_stack->callback == plpgsql_compile_error_callback);
+       cbarg.location = location;
+       cbarg.leaderlen = 0;
 
-       previous_errcontext = error_context_stack;
-       syntax_errcontext.callback = plpgsql_string_error_callback;
-       syntax_errcontext.arg = (char *) token;
-       syntax_errcontext.previous = error_context_stack->previous;
+       syntax_errcontext.callback = plpgsql_sql_error_callback;
+       syntax_errcontext.arg = &cbarg;
+       syntax_errcontext.previous = error_context_stack;
        error_context_stack = &syntax_errcontext;
 
        result = pg_parse_string_token(token);
 
        /* Restore former ereport callback */
-       error_context_stack = previous_errcontext;
+       error_context_stack = syntax_errcontext.previous;
 
        return result;
 }
 
-static void
-plpgsql_string_error_callback(void *arg)
-{
-       Assert(plpgsql_error_funcname);
-
-       errcontext("string literal in PL/PgSQL function \"%s\" near line %d",
-                          plpgsql_error_funcname, plpgsql_error_lineno);
-       /* representing the string literal as internalquery seems overkill */
-       errposition(0);
-}
-
 static char *
 check_label(const char *yytxt)
 {
@@ -2858,27 +2980,23 @@ check_label(const char *yytxt)
 }
 
 static void
-check_labels(const char *start_label, const char *end_label)
+check_labels(const char *start_label, const char *end_label, int end_location)
 {
        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)));
-               }
+                                                       end_label),
+                                        parser_errposition(end_location)));
 
                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)));
-               }
+                                                       end_label, start_label),
+                                        parser_errposition(end_location)));
        }
 }
 
@@ -2895,79 +3013,41 @@ read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
 {
        PLpgSQL_expr *expr;
        int                     tok;
-       char       *cp;
 
        tok = yylex();
        if (cursor->cursor_explicit_argrow < 0)
        {
                /* No arguments expected */
                if (tok == '(')
-               {
-                       plpgsql_error_lineno = plpgsql_scanner_lineno();
                        ereport(ERROR,
                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                         errmsg("cursor \"%s\" has no arguments",
-                                                       cursor->refname)));
-               }
+                                                       cursor->refname),
+                                        parser_errposition(yylloc)));
 
                if (tok != until)
-               {
-                       plpgsql_error_lineno = plpgsql_scanner_lineno();
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                                        errmsg("syntax error at \"%s\"",
-                                                       yytext)));
-               }
+                       yyerror("syntax error");
 
                return NULL;
        }
 
        /* Else better provide arguments */
        if (tok != '(')
-       {
-               plpgsql_error_lineno = plpgsql_scanner_lineno();
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
                                 errmsg("cursor \"%s\" has arguments",
-                                               cursor->refname)));
-       }
-
-       /*
-        * Push back the '(', else plpgsql_read_expression
-        * will complain about unbalanced parens.
-        */
-       plpgsql_push_back_token(tok);
-
-       expr = plpgsql_read_expression(until, expected);
+                                               cursor->refname),
+                                parser_errposition(yylloc)));
 
        /*
-        * Now remove the leading and trailing parens,
-        * because we want "SELECT 1, 2", not "SELECT (1, 2)".
+        * Read expressions until the matching ')'.
         */
-       cp = expr->query;
+       expr = plpgsql_read_expression(')', ")");
 
-       if (strncmp(cp, "SELECT", 6) != 0)
-       {
-               plpgsql_error_lineno = plpgsql_scanner_lineno();
-               /* internal error */
-               elog(ERROR, "expected \"SELECT (\", got \"%s\"", expr->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\"", expr->query);
-       }
-       *cp = ' ';
-
-       cp += strlen(cp) - 1;
-
-       if (*cp != ')')
-               yyerror("expected \")\"");
-       *cp = '\0';
+       /* Next we'd better find the until token */
+       tok = yylex();
+       if (tok != until)
+               yyerror("syntax error");
 
        return expr;
 }
@@ -2999,13 +3079,11 @@ read_raise_options(void)
                else if (pg_strcasecmp(yytext, "hint") == 0)
                        opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
                else
-               {
-                       plpgsql_error_lineno = plpgsql_scanner_lineno();
                        ereport(ERROR,
                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                         errmsg("unrecognized RAISE statement option \"%s\"",
-                                                       yytext)));
-               }
+                                                       yytext),
+                                        parser_errposition(yylloc)));
 
                if (yylex() != K_ASSIGN)
                        yyerror("syntax error, expected \"=\"");
@@ -3025,14 +3103,14 @@ read_raise_options(void)
  * Fix up CASE statement
  */
 static PLpgSQL_stmt *
-make_case(int lineno, PLpgSQL_expr *t_expr,
+make_case(int location, PLpgSQL_expr *t_expr,
                  List *case_when_list, List *else_stmts)
 {
        PLpgSQL_stmt_case       *new;
 
        new = palloc(sizeof(PLpgSQL_stmt_case));
        new->cmd_type = PLPGSQL_STMT_CASE;
-       new->lineno = lineno;
+       new->lineno = plpgsql_location_to_lineno(location);
        new->t_expr = t_expr;
        new->t_varno = 0;
        new->case_when_list = case_when_list;
@@ -3066,7 +3144,7 @@ make_case(int lineno, PLpgSQL_expr *t_expr,
                 * variable as if it were INT4; we'll fix this at runtime if needed.
                 */
                t_var = (PLpgSQL_var *)
-                       plpgsql_build_variable(varname, lineno,
+                       plpgsql_build_variable(varname, new->lineno,
                                                                   plpgsql_build_datatype(INT4OID, -1),
                                                                   true);
                new->t_varno = t_var->dno;
index 4c23267..a394eac 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.142 2009/11/07 00:52:26 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.143 2009/11/09 00:26:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -46,7 +46,6 @@ int                   plpgsql_nDatums;
 PLpgSQL_datum **plpgsql_Datums;
 static int     datums_last = 0;
 
-int                    plpgsql_error_lineno;
 char      *plpgsql_error_funcname;
 bool           plpgsql_DumpExecTree = false;
 bool           plpgsql_check_syntax = false;
@@ -95,6 +94,7 @@ static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
                   PLpgSQL_function *function,
                   PLpgSQL_func_hashkey *hashkey,
                   bool forValidator);
+static void plpgsql_compile_error_callback(void *arg);
 static void add_dummy_return(PLpgSQL_function *function);
 static Node *plpgsql_pre_column_ref(ParseState *pstate, ColumnRef *cref);
 static Node *plpgsql_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var);
@@ -301,7 +301,6 @@ do_compile(FunctionCallInfo fcinfo,
        plpgsql_scanner_init(proc_source);
 
        plpgsql_error_funcname = pstrdup(NameStr(procStruct->proname));
-       plpgsql_error_lineno = 0;
 
        /*
         * Setup error traceback support for ereport()
@@ -713,7 +712,6 @@ do_compile(FunctionCallInfo fcinfo,
         */
        error_context_stack = plerrcontext.previous;
        plpgsql_error_funcname = NULL;
-       plpgsql_error_lineno = 0;
 
        plpgsql_check_syntax = false;
 
@@ -752,7 +750,6 @@ plpgsql_compile_inline(char *proc_source)
        plpgsql_scanner_init(proc_source);
 
        plpgsql_error_funcname = func_name;
-       plpgsql_error_lineno = 0;
 
        /*
         * Setup error traceback support for ereport()
@@ -851,7 +848,6 @@ plpgsql_compile_inline(char *proc_source)
         */
        error_context_stack = plerrcontext.previous;
        plpgsql_error_funcname = NULL;
-       plpgsql_error_lineno = 0;
 
        plpgsql_check_syntax = false;
 
@@ -865,10 +861,8 @@ plpgsql_compile_inline(char *proc_source)
  * error context callback to let us supply a call-stack traceback.
  * If we are validating or executing an anonymous code block, the function
  * source text is passed as an argument.
- *
- * This function is public only for the sake of an assertion in gram.y
  */
-void
+static void
 plpgsql_compile_error_callback(void *arg)
 {
        if (arg)
@@ -888,7 +882,7 @@ plpgsql_compile_error_callback(void *arg)
 
        if (plpgsql_error_funcname)
                errcontext("compilation of PL/pgSQL function \"%s\" near line %d",
-                                  plpgsql_error_funcname, plpgsql_error_lineno);
+                                  plpgsql_error_funcname, plpgsql_latest_lineno());
 }
 
 
@@ -2065,25 +2059,6 @@ build_row_from_vars(PLpgSQL_variable **vars, int numvars)
        return row;
 }
 
-
-/* ----------
- * plpgsql_parse_datatype                      Scanner found something that should
- *                                     be a datatype name.
- * ----------
- */
-PLpgSQL_type *
-plpgsql_parse_datatype(const char *string)
-{
-       Oid                     type_id;
-       int32           typmod;
-
-       /* Let the main parser try to parse it under standard SQL rules */
-       parseTypeString(string, &type_id, &typmod);
-
-       /* Okay, build a PLpgSQL_type data structure for it */
-       return plpgsql_build_datatype(type_id, typmod);
-}
-
 /*
  * plpgsql_build_datatype
  *             Build PLpgSQL_type struct given type OID and typmod.
index 0c0f077..117da74 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.250 2009/11/06 18:37:54 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.251 2009/11/09 00:26:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -23,7 +23,6 @@
 #include "catalog/pg_type.h"
 #include "executor/spi_priv.h"
 #include "funcapi.h"
-#include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/scansup.h"
index 386d89a..1d41ee7 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.121 2009/11/07 00:52:26 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.122 2009/11/09 00:26:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,6 +22,7 @@
 #include "fmgr.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
+#include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "utils/tuplestore.h"
 
@@ -774,11 +775,9 @@ typedef struct
 
 extern bool plpgsql_DumpExecTree;
 extern bool plpgsql_LookupIdentifiers;
-extern bool plpgsql_SpaceScanned;
 extern int     plpgsql_nDatums;
 extern PLpgSQL_datum **plpgsql_Datums;
 
-extern int     plpgsql_error_lineno;
 extern char *plpgsql_error_funcname;
 
 /* linkage to the real yytext variable */
@@ -813,7 +812,6 @@ extern PLpgSQL_type *plpgsql_parse_dblwordtype(const char *word);
 extern PLpgSQL_type *plpgsql_parse_tripwordtype(const char *word);
 extern PLpgSQL_type *plpgsql_parse_wordrowtype(const char *word);
 extern PLpgSQL_type *plpgsql_parse_dblwordrowtype(const char *word);
-extern PLpgSQL_type *plpgsql_parse_datatype(const char *string);
 extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod);
 extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
                                           PLpgSQL_type *dtype,
@@ -826,7 +824,6 @@ extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname);
 extern void plpgsql_adddatum(PLpgSQL_datum *new);
 extern int     plpgsql_add_initdatums(int **varnos);
 extern void plpgsql_HashTableInit(void);
-extern void plpgsql_compile_error_callback(void *arg);
 
 /* ----------
  * Functions in pl_handler.c
@@ -885,8 +882,12 @@ extern int plpgsql_yyparse(void);
 extern int     plpgsql_base_yylex(void);
 extern int     plpgsql_yylex(void);
 extern void plpgsql_push_back_token(int token);
+extern void plpgsql_append_source_text(StringInfo buf,
+                                                                          int startlocation, int endlocation);
+extern int     plpgsql_scanner_errposition(int location);
 extern void plpgsql_yyerror(const char *message);
-extern int     plpgsql_scanner_lineno(void);
+extern int     plpgsql_location_to_lineno(int location);
+extern int     plpgsql_latest_lineno(void);
 extern void plpgsql_scanner_init(const char *str);
 extern void plpgsql_scanner_finish(void);
 
index 2c5645c..e775089 100644 (file)
@@ -1,15 +1,14 @@
 %{
 /*-------------------------------------------------------------------------
  *
- * scan.l              - Scanner for the PL/pgSQL
- *                       procedural language
+ * scan.l              - Scanner for the PL/pgSQL procedural language
  *
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.74 2009/11/07 00:52:26 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.75 2009/11/09 00:26:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #define fprintf(file, fmt, msg)  ereport(ERROR, (errmsg_internal("%s", msg)))
 
 /*
+ * Each call to yylex must set yylloc to the location of the found token
+ * (expressed as a byte offset from the start of the input text).
  * When we parse a token that requires multiple lexer rules to process,
- * remember the token's starting position this way.
+ * this should be done in the first such rule, else yylloc will point
+ * into the middle of the token.
  */
-#define SAVE_TOKEN_START()  \
-       ( start_lineno = plpgsql_scanner_lineno(), start_charpos = yytext )
+#define SET_YYLLOC()  (yylloc = yytext - scanbuf)
 
 /* Handles to the buffer that the lexer uses internally */
 static YY_BUFFER_STATE scanbufhandle;
 static char *scanbuf;
 
-static const char *scanstr;            /* original input string */
+static const char *scanorig;           /* original input string */
 
 static int     pushback_token;
 static bool have_pushback_token;
 static const char *cur_line_start;
+static const char *cur_line_end;
 static int     cur_line_num;
 static int             xcdepth = 0;    /* depth of nesting in slash-star comments */
 static char    *dolqstart;      /* current $foo$ quote start string */
 
 bool plpgsql_LookupIdentifiers = true;
-bool plpgsql_SpaceScanned = false;
+
+static void location_lineno_init(void);
+
 %}
 
 %option 8bit
@@ -53,6 +57,10 @@ bool plpgsql_SpaceScanned = false;
 %option noinput
 %option nounput
 %option noyywrap
+%option noyyalloc
+%option noyyrealloc
+%option noyyfree
+%option warn
 %option prefix="plpgsql_base_yy"
 
 %option case-insensitive
@@ -126,133 +134,117 @@ param                   \${digit}+
 
 %%
     /* ----------
-     * Local variables in scanner to remember where
-     * a string or comment started
-     * ----------
-     */
-    int        start_lineno = 0;
-       char *start_charpos = NULL;
-
-    /* ----------
-     * Reset the state when entering the scanner
+     * Reset the state when entering yylex()
      * ----------
      */
     BEGIN(INITIAL);
-    plpgsql_SpaceScanned = false;
 
     /* ----------
      * The keyword rules
      * ----------
      */
-:=                             { return K_ASSIGN;                      }
-=                              { return K_ASSIGN;                      }
-\.\.                   { return K_DOTDOT;                      }
-alias                  { return K_ALIAS;                       }
-all                            { return K_ALL;                         }
-begin                  { return K_BEGIN;                       }
-by                             { return K_BY;                          }
-case                   { return K_CASE;                        }
-close                  { return K_CLOSE;                       }
-constant               { return K_CONSTANT;            }
-continue               { return K_CONTINUE;            }
-cursor                 { return K_CURSOR;                      }
-declare                        { return K_DECLARE;                     }
-default                        { return K_DEFAULT;                     }
-diagnostics            { return K_DIAGNOSTICS;         }
-else                   { return K_ELSE;                        }
-elseif                 { return K_ELSIF;                       }
-elsif                  { return K_ELSIF;                       }
-end                            { return K_END;                         }
-exception              { return K_EXCEPTION;           }
-execute                        { return K_EXECUTE;                     }
-exit                   { return K_EXIT;                        }
-fetch                  { return K_FETCH;                       }
-for                            { return K_FOR;                         }
-from                   { return K_FROM;                        }
-get                            { return K_GET;                         }
-if                             { return K_IF;                          }
-in                             { return K_IN;                          }
-insert                 { return K_INSERT;                      }
-into                   { return K_INTO;                        }
-is                             { return K_IS;                          }
-loop                   { return K_LOOP;                        }
-move                   { return K_MOVE;                        }
-no{space}+scroll { return K_NOSCROLL;          }
-not                            { return K_NOT;                         }
-null                   { return K_NULL;                        }
-open                   { return K_OPEN;                        }
-or                             { return K_OR;                          }
-perform                        { return K_PERFORM;                     }
-raise                  { return K_RAISE;                       }
-result_oid             { return K_RESULT_OID;          }
-return                 { return K_RETURN;                      }
-reverse                        { return K_REVERSE;                     }
-row_count              { return K_ROW_COUNT;           }
-scroll                 { return K_SCROLL;                      }
-strict                 { return K_STRICT;              }
-then                   { return K_THEN;                        }
-to                             { return K_TO;                          }
-type                   { return K_TYPE;                        }
-using                  { return K_USING;                       }
-when                   { return K_WHEN;                        }
-while                  { return K_WHILE;                       }
-
-^#option               { return O_OPTION;                      }
-dump                   { return O_DUMP;                        }
+:=                             { SET_YYLLOC(); return K_ASSIGN;                }
+=                              { SET_YYLLOC(); return K_ASSIGN;                }
+\.\.                   { SET_YYLLOC(); return K_DOTDOT;                }
+alias                  { SET_YYLLOC(); return K_ALIAS;                 }
+all                            { SET_YYLLOC(); return K_ALL;                   }
+begin                  { SET_YYLLOC(); return K_BEGIN;                 }
+by                             { SET_YYLLOC(); return K_BY;                    }
+case                   { SET_YYLLOC(); return K_CASE;                  }
+close                  { SET_YYLLOC(); return K_CLOSE;                 }
+constant               { SET_YYLLOC(); return K_CONSTANT;              }
+continue               { SET_YYLLOC(); return K_CONTINUE;              }
+cursor                 { SET_YYLLOC(); return K_CURSOR;                }
+declare                        { SET_YYLLOC(); return K_DECLARE;               }
+default                        { SET_YYLLOC(); return K_DEFAULT;               }
+diagnostics            { SET_YYLLOC(); return K_DIAGNOSTICS;   }
+else                   { SET_YYLLOC(); return K_ELSE;                  }
+elseif                 { SET_YYLLOC(); return K_ELSIF;                 }
+elsif                  { SET_YYLLOC(); return K_ELSIF;                 }
+end                            { SET_YYLLOC(); return K_END;                   }
+exception              { SET_YYLLOC(); return K_EXCEPTION;             }
+execute                        { SET_YYLLOC(); return K_EXECUTE;               }
+exit                   { SET_YYLLOC(); return K_EXIT;                  }
+fetch                  { SET_YYLLOC(); return K_FETCH;                 }
+for                            { SET_YYLLOC(); return K_FOR;                   }
+from                   { SET_YYLLOC(); return K_FROM;                  }
+get                            { SET_YYLLOC(); return K_GET;                   }
+if                             { SET_YYLLOC(); return K_IF;                    }
+in                             { SET_YYLLOC(); return K_IN;                    }
+insert                 { SET_YYLLOC(); return K_INSERT;                }
+into                   { SET_YYLLOC(); return K_INTO;                  }
+is                             { SET_YYLLOC(); return K_IS;                    }
+loop                   { SET_YYLLOC(); return K_LOOP;                  }
+move                   { SET_YYLLOC(); return K_MOVE;                  }
+no{space}+scroll { SET_YYLLOC(); return K_NOSCROLL;            }
+not                            { SET_YYLLOC(); return K_NOT;                   }
+null                   { SET_YYLLOC(); return K_NULL;                  }
+open                   { SET_YYLLOC(); return K_OPEN;                  }
+or                             { SET_YYLLOC(); return K_OR;                    }
+perform                        { SET_YYLLOC(); return K_PERFORM;               }
+raise                  { SET_YYLLOC(); return K_RAISE;                 }
+result_oid             { SET_YYLLOC(); return K_RESULT_OID;    }
+return                 { SET_YYLLOC(); return K_RETURN;                }
+reverse                        { SET_YYLLOC(); return K_REVERSE;               }
+row_count              { SET_YYLLOC(); return K_ROW_COUNT;             }
+scroll                 { SET_YYLLOC(); return K_SCROLL;                }
+strict                 { SET_YYLLOC(); return K_STRICT;            }
+then                   { SET_YYLLOC(); return K_THEN;                  }
+to                             { SET_YYLLOC(); return K_TO;                    }
+type                   { SET_YYLLOC(); return K_TYPE;                  }
+using                  { SET_YYLLOC(); return K_USING;                 }
+when                   { SET_YYLLOC(); return K_WHEN;                  }
+while                  { SET_YYLLOC(); return K_WHILE;                 }
+
+^#option               { SET_YYLLOC(); return O_OPTION;                }
+dump                   { SET_YYLLOC(); return O_DUMP;                  }
 
 
     /* ----------
      * Special word rules
-        *
-        * We set plpgsql_error_lineno in each rule so that errors reported
-        * in the pl_comp.c subroutines will point to the right place.
      * ----------
      */
 {identifier}                                   {
-       plpgsql_error_lineno = plpgsql_scanner_lineno();
+       SET_YYLLOC();
        if (!plpgsql_LookupIdentifiers) return T_WORD;
        return plpgsql_parse_word(yytext); }
 {identifier}{space}*\.{space}*{identifier}     {
-       plpgsql_error_lineno = plpgsql_scanner_lineno();
+       SET_YYLLOC();
        if (!plpgsql_LookupIdentifiers) return T_DBLWORD;
        return plpgsql_parse_dblword(yytext); }
 {identifier}{space}*\.{space}*{identifier}{space}*\.{space}*{identifier}       {
-       plpgsql_error_lineno = plpgsql_scanner_lineno();
+       SET_YYLLOC();
        if (!plpgsql_LookupIdentifiers) return T_TRIPWORD;
        return plpgsql_parse_tripword(yytext); }
 {param}                                                        {
-       plpgsql_error_lineno = plpgsql_scanner_lineno();
+       SET_YYLLOC();
        if (!plpgsql_LookupIdentifiers) return T_WORD;
        return plpgsql_parse_word(yytext); }
 {param}{space}*\.{space}*{identifier}  {
-       plpgsql_error_lineno = plpgsql_scanner_lineno();
+       SET_YYLLOC();
        if (!plpgsql_LookupIdentifiers) return T_DBLWORD;
        return plpgsql_parse_dblword(yytext); }
 {param}{space}*\.{space}*{identifier}{space}*\.{space}*{identifier}    {
-       plpgsql_error_lineno = plpgsql_scanner_lineno();
+       SET_YYLLOC();
        if (!plpgsql_LookupIdentifiers) return T_TRIPWORD;
        return plpgsql_parse_tripword(yytext); }
 
-{digit}+               { return T_NUMBER;                      }
+{digit}+               { SET_YYLLOC(); return T_NUMBER;                }
 
-\".                            { yyerror("unterminated quoted identifier"); }
-
-    /* ----------
-     * Ignore whitespace (including comments) but remember this happened
-     * ----------
-     */
-{whitespace}   { plpgsql_SpaceScanned = true; }
+\".                            { SET_YYLLOC(); yyerror("unterminated quoted identifier"); }
 
     /* ----------
      * Comment and literal handling is mostly copied from the core lexer
      * ----------
      */
+{whitespace}   {
+                                       /* ignore */
+                               }
+
 {xcstart}              {
-                                       /* Set location in case of syntax error in comment */
-                                       SAVE_TOKEN_START();
+                                       SET_YYLLOC();
                                        xcdepth = 0;
                                        BEGIN(xc);
-                                       plpgsql_SpaceScanned = true;
                                }
 
 <xc>{xcstart}  {
@@ -281,14 +273,14 @@ dump                      { return O_DUMP;                        }
 <xc><<EOF>>            { yyerror("unterminated /* comment"); }
 
 {xqstart}              {
-                                       SAVE_TOKEN_START();
+                                       SET_YYLLOC();
                                        if (standard_conforming_strings)
                                                BEGIN(xq);
                                        else
                                                BEGIN(xe);
                                }
 {xestart}              {
-                                       SAVE_TOKEN_START();
+                                       SET_YYLLOC();
                                        BEGIN(xe);
                                }
 <xq,xe>{quotestop}     |
@@ -296,8 +288,8 @@ dump                        { return O_DUMP;                        }
                                        yyless(1);
                                        BEGIN(INITIAL);
                                        /* adjust yytext/yyleng to describe whole string token */
-                                       yyleng += (yytext - start_charpos);
-                                       yytext = start_charpos;
+                                       yyleng += (yytext - (scanbuf + yylloc));
+                                       yytext = scanbuf + yylloc;
                                        return T_STRING;
                                }
 <xq,xe>{xqdouble} {
@@ -317,7 +309,7 @@ dump                        { return O_DUMP;                        }
 <xq,xe><<EOF>>         { yyerror("unterminated quoted string"); }
 
 {dolqdelim}            {
-                                       SAVE_TOKEN_START();
+                                       SET_YYLLOC();
                                        dolqstart = pstrdup(yytext);
                                        BEGIN(xdolq);
                                }
@@ -325,7 +317,7 @@ dump                        { return O_DUMP;                        }
                                        /* throw back all but the initial "$" */
                                        yyless(1);
                                        /* and treat it as {other} */
-                                       return yytext[0];
+                                       SET_YYLLOC(); return yytext[0];
                                }
 <xdolq>{dolqdelim} {
                                        if (strcmp(yytext, dolqstart) == 0)
@@ -333,8 +325,8 @@ dump                        { return O_DUMP;                        }
                                                pfree(dolqstart);
                                                BEGIN(INITIAL);
                                                /* adjust yytext/yyleng to describe whole string */
-                                               yyleng += (yytext - start_charpos);
-                                               yytext = start_charpos;
+                                               yyleng += (yytext - (scanbuf + yylloc));
+                                               yytext = scanbuf + yylloc;
                                                return T_STRING;
                                        }
                                        else
@@ -361,7 +353,7 @@ dump                        { return O_DUMP;                        }
      * ----------
      */
 .                              {
-                                       return yytext[0];
+                                       SET_YYLLOC(); return yytext[0];
                                }
 
 %%
@@ -369,9 +361,10 @@ dump                       { return O_DUMP;                        }
 
 /*
  * This is the yylex routine called from outside. It exists to provide
- * a one-token pushback facility.  Beware of trying to make it do more:
- * for the most part, plpgsql's gram.y assumes that yytext is in step
- * with the "current token".
+ * a one-token pushback facility.  Beware of trying to push back more;
+ * for the most part, plpgsql's gram.y assumes that yytext and yylloc
+ * are in step with the "current token".  In particular it is assumed that
+ * those are in step with the result immediately after any yylex() call.
  */
 int
 plpgsql_yylex(void)
@@ -387,7 +380,8 @@ plpgsql_yylex(void)
 /*
  * Push back a single token to be re-read by next plpgsql_yylex() call.
  *
- * NOTE: this does not cause yytext to "back up".
+ * NOTE: this does not cause yytext or yylloc to "back up".  Also, it
+ * is not a good idea to push back a token other than what you read.
  */
 void
 plpgsql_push_back_token(int token)
@@ -399,18 +393,61 @@ plpgsql_push_back_token(int token)
 }
 
 /*
- * Report a syntax error.
+ * Append the function text starting at startlocation and extending to
+ * (not including) endlocation onto the existing contents of "buf".
  */
 void
-plpgsql_yyerror(const char *message)
+plpgsql_append_source_text(StringInfo buf,
+                                                  int startlocation, int endlocation)
 {
-       const char *loc = yytext;
-       int                     cursorpos;
+       Assert(startlocation <= endlocation);
+       appendBinaryStringInfo(buf, scanorig + startlocation,
+                                                  endlocation - startlocation);
+}
 
-       plpgsql_error_lineno = plpgsql_scanner_lineno();
+/*
+ * plpgsql_scanner_errposition
+ *             Report an error cursor position, if possible.
+ *
+ * This is expected to be used within an ereport() call.  The return value
+ * is a dummy (always 0, in fact).
+ *
+ * Note that this can only be used for messages emitted during initial
+ * parsing of a plpgsql function, since it requires the scanorig string
+ * to still be available.
+ */
+int
+plpgsql_scanner_errposition(int location)
+{
+       int             pos;
 
-       /* in multibyte encodings, return index in characters not bytes */
-       cursorpos = pg_mbstrlen_with_len(scanbuf, loc - scanbuf) + 1;
+       if (location < 0 || scanorig == NULL)
+               return 0;                               /* no-op if location is unknown */
+
+       /* Convert byte offset to character number */
+       pos = pg_mbstrlen_with_len(scanorig, location) + 1;
+       /* And pass it to the ereport mechanism */
+       (void) internalerrposition(pos);
+       /* Also pass the function body string */
+       return internalerrquery(scanorig);
+}
+
+/*
+ * plpgsql_yyerror
+ *             Report a lexer or grammar error.
+ *
+ * The message's cursor position is whatever YYLLOC was last set to,
+ * ie, the start of the current token if called within yylex(), or the
+ * most recently lexed token if called from the grammar.
+ * This is OK for syntax error messages from the Bison parser, because Bison
+ * parsers report error as soon as the first unparsable token is reached.
+ * Beware of using yyerror for other purposes, as the cursor position might
+ * be misleading!
+ */
+void
+plpgsql_yyerror(const char *message)
+{
+       const char *loc = scanbuf + yylloc;
 
        if (*loc == YY_END_OF_BUFFER_CHAR)
        {
@@ -418,8 +455,7 @@ plpgsql_yyerror(const char *message)
                                (errcode(ERRCODE_SYNTAX_ERROR),
                                 /* translator: %s is typically the translation of "syntax error" */
                                 errmsg("%s at end of input", _(message)),
-                                internalerrposition(cursorpos),
-                                internalerrquery(scanstr)));
+                                plpgsql_scanner_errposition(yylloc)));
        }
        else
        {
@@ -427,33 +463,72 @@ plpgsql_yyerror(const char *message)
                                (errcode(ERRCODE_SYNTAX_ERROR),
                                 /* translator: first %s is typically the translation of "syntax error" */
                                 errmsg("%s at or near \"%s\"", _(message), loc),
-                                internalerrposition(cursorpos),
-                                internalerrquery(scanstr)));
+                                plpgsql_scanner_errposition(yylloc)));
        }
 }
 
 /*
- * Get the line number at which the current token ends.  This substitutes
- * for flex's very poorly implemented yylineno facility.
+ * Given a location (a byte offset in the function source text),
+ * return a line number.
  *
- * We assume that flex has written a '\0' over the character following the
- * current token in scanbuf.  So, we just have to count the '\n' characters
- * before that.  We optimize this a little by keeping track of the last
- * '\n' seen so far.
+ * We expect that this is typically called for a sequence of increasing
+ * location values, so optimize accordingly by tracking the endpoints
+ * of the "current" line.
  */
 int
-plpgsql_scanner_lineno(void)
+plpgsql_location_to_lineno(int location)
 {
-       const char *c;
+       const char *loc;
+
+       if (location < 0 || scanorig == NULL)
+               return 0;                               /* garbage in, garbage out */
+       loc = scanorig + location;
 
-       while ((c = strchr(cur_line_start, '\n')) != NULL)
+       /* be correct, but not fast, if input location goes backwards */
+       if (loc < cur_line_start)
+               location_lineno_init();
+
+       while (cur_line_end != NULL && loc > cur_line_end)
        {
-               cur_line_start = c + 1;
+               cur_line_start = cur_line_end + 1;
                cur_line_num++;
+               cur_line_end = strchr(cur_line_start, '\n');
        }
+
+       return cur_line_num;
+}
+
+/* initialize or reset the state for plpgsql_location_to_lineno */
+static void
+location_lineno_init(void)
+{
+       cur_line_start = scanorig;
+       cur_line_num = 1;
+
+       /*----------
+        * Hack: skip any initial newline, so that in the common coding layout
+        *              CREATE FUNCTION ... AS $$
+        *                      code body
+        *              $$ LANGUAGE plpgsql;
+        * we will think "line 1" is what the programmer thinks of as line 1.
+        *----------
+        */
+    if (*cur_line_start == '\r')
+        cur_line_start++;
+    if (*cur_line_start == '\n')
+        cur_line_start++;
+
+       cur_line_end = strchr(cur_line_start, '\n');
+}
+
+/* return the most recently computed lineno */
+int
+plpgsql_latest_lineno(void)
+{
        return cur_line_num;
 }
 
+
 /*
  * Called before any actual parsing is done
  *
@@ -464,48 +539,37 @@ plpgsql_scanner_lineno(void)
 void
 plpgsql_scanner_init(const char *str)
 {
-       Size    slen;
-
-       slen = strlen(str);
+       Size    slen = strlen(str);
 
        /*
-        * Might be left over after ereport()
+        * Reset flex internal state.  Whatever data it might think it has
+        * has long since been pfree'd.
         */
-       if (YY_CURRENT_BUFFER)
-               yy_delete_buffer(YY_CURRENT_BUFFER);
+       yy_init_globals();
 
        /*
         * Make a scan buffer with special termination needed by flex.
         */
-       scanbuf = palloc(slen + 2);
+       scanbuf = (char *) palloc(slen + 2);
        memcpy(scanbuf, str, slen);
        scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR;
        scanbufhandle = yy_scan_buffer(scanbuf, slen + 2);
 
-       /* Other setup */
-       scanstr = str;
+       /*
+        * scanorig points to the original string, which unlike scanbuf won't
+        * be modified on-the-fly by flex.  Notice that although yytext points
+        * into scanbuf, we rely on being able to apply locations (offsets from
+        * string start) to scanorig as well.
+        */
+       scanorig = str;
 
+       /* Other setup */
        have_pushback_token = false;
 
-       cur_line_start = scanbuf;
-       cur_line_num = 1;
-
-       /*----------
-        * Hack: skip any initial newline, so that in the common coding layout
-        *              CREATE FUNCTION ... AS '
-        *                      code body
-        *              ' LANGUAGE plpgsql;
-        * we will think "line 1" is what the programmer thinks of as line 1.
-        *----------
-        */
-    if (*cur_line_start == '\r')
-        cur_line_start++;
-    if (*cur_line_start == '\n')
-        cur_line_start++;
+       location_lineno_init();
 
        BEGIN(INITIAL);
        plpgsql_LookupIdentifiers = true;
-       plpgsql_SpaceScanned = false;
 }
 
 /*
@@ -514,6 +578,38 @@ plpgsql_scanner_init(const char *str)
 void
 plpgsql_scanner_finish(void)
 {
+       /* release storage */
        yy_delete_buffer(scanbufhandle);
        pfree(scanbuf);
+       /* avoid leaving any dangling pointers */
+       scanbufhandle = NULL;
+       scanbuf = NULL;
+       scanorig = NULL;
+}
+
+/*
+ * Interface functions to make flex use palloc() instead of malloc().
+ * It'd be better to make these static, but flex insists otherwise.
+ */
+
+void *
+plpgsql_base_yyalloc(yy_size_t bytes)
+{
+       return palloc(bytes);
+}
+
+void *
+plpgsql_base_yyrealloc(void *ptr, yy_size_t bytes)
+{
+       if (ptr)
+               return repalloc(ptr, bytes);
+       else
+               return palloc(bytes);
+}
+
+void
+plpgsql_base_yyfree(void *ptr)
+{
+       if (ptr)
+               pfree(ptr);
 }
index 5846246..534a600 100644 (file)
@@ -1747,7 +1747,7 @@ create function f1(in i int, out j int) returns int as $$
 begin
   return i+1;
 end$$ language plpgsql;
-ERROR:  RETURN cannot have a parameter in function with OUT parameters at or near "i"
+ERROR:  RETURN cannot have a parameter in function with OUT parameters
 LINE 3:   return i+1;
                  ^
 create function f1(in i int, out j int) as $$
@@ -2066,13 +2066,13 @@ begin
 end$$ language plpgsql;
 select test_variable_storage();
 NOTICE:  should see this
-CONTEXT:  SQL statement "SELECT  trap_zero_divide(-100)"
+CONTEXT:  SQL statement "SELECT trap_zero_divide(-100)"
 PL/pgSQL function "test_variable_storage" line 7 at PERFORM
 NOTICE:  should see this only if -100 <> 0
-CONTEXT:  SQL statement "SELECT  trap_zero_divide(-100)"
+CONTEXT:  SQL statement "SELECT trap_zero_divide(-100)"
 PL/pgSQL function "test_variable_storage" line 7 at PERFORM
 NOTICE:  should see this only if -100 fits in smallint
-CONTEXT:  SQL statement "SELECT  trap_zero_divide(-100)"
+CONTEXT:  SQL statement "SELECT trap_zero_divide(-100)"
 PL/pgSQL function "test_variable_storage" line 7 at PERFORM
  test_variable_storage 
 -----------------------
@@ -2325,10 +2325,8 @@ begin
     return a;
 end$$ language plpgsql;
 ERROR:  syntax error at or near "Johnny"
-LINE 1: Johnny Yuma
-        ^
-QUERY:  Johnny Yuma
-CONTEXT:  SQL statement in PL/PgSQL function "bad_sql1" near line 4
+LINE 5:     Johnny Yuma;
+            ^
 create function bad_sql2() returns int as $$
 declare r record;
 begin
@@ -2338,26 +2336,22 @@ begin
     return 5;
 end;$$ language plpgsql;
 ERROR:  syntax error at or near "the"
-LINE 1:  select I fought the law, the law won
-                         ^
-QUERY:   select I fought the law, the law won
-CONTEXT:  SQL statement in PL/PgSQL function "bad_sql2" near line 3
+LINE 4:     for r in select I fought the law, the law won LOOP
+                                     ^
 -- a RETURN expression is mandatory, except for void-returning
 -- functions, where it is not allowed
 create function missing_return_expr() returns int as $$
 begin
     return ;
 end;$$ language plpgsql;
-ERROR:  syntax error at end of input
-LINE 1: SELECT 
-               ^
-QUERY:  SELECT 
-CONTEXT:  SQL statement in PL/PgSQL function "missing_return_expr" near line 2
+ERROR:  missing expression at or near ";"
+LINE 3:     return ;
+                   ^
 create function void_return_expr() returns void as $$
 begin
     return 5;
 end;$$ language plpgsql;
-ERROR:  RETURN cannot have a parameter in function returning void at or near "5"
+ERROR:  RETURN cannot have a parameter in function returning void
 LINE 3:     return 5;
                    ^
 -- VOID functions are allowed to omit RETURN
@@ -2427,9 +2421,9 @@ end; $$ language plpgsql;
 -- blocks
 select excpt_test1();
 ERROR:  column "sqlstate" does not exist
-LINE 1: SELECT  sqlstate
-                ^
-QUERY:  SELECT  sqlstate
+LINE 1: SELECT sqlstate
+               ^
+QUERY:  SELECT sqlstate
 CONTEXT:  PL/pgSQL function "excpt_test1" line 2 at RAISE
 create function excpt_test2() returns void as $$
 begin
@@ -2442,9 +2436,9 @@ end; $$ language plpgsql;
 -- should fail
 select excpt_test2();
 ERROR:  column "sqlstate" does not exist
-LINE 1: SELECT  sqlstate
-                ^
-QUERY:  SELECT  sqlstate
+LINE 1: SELECT sqlstate
+               ^
+QUERY:  SELECT sqlstate
 CONTEXT:  PL/pgSQL function "excpt_test2" line 4 at RAISE
 create function excpt_test3() returns void as $$
 begin
@@ -2714,7 +2708,8 @@ begin
 end;
 $$ language plpgsql;
 ERROR:  end label "outer_label" differs from block's label "inner_label"
-CONTEXT:  compilation of PL/pgSQL function "end_label3" near line 6
+LINE 7:   end loop outer_label;
+                   ^
 -- should fail: end label on a block without a start label
 create function end_label4() returns void as $$
 <<outer_label>>
@@ -2725,7 +2720,8 @@ begin
 end;
 $$ language plpgsql;
 ERROR:  end label "outer_label" specified for unlabelled block
-CONTEXT:  compilation of PL/pgSQL function "end_label4" near line 5
+LINE 6:   end loop outer_label;
+                   ^
 -- using list of scalars in fori and fore stmts
 create function for_vect() returns void as $proc$
 <<lbl>>declare a integer; b varchar; c varchar; r record;
@@ -3308,7 +3304,8 @@ begin
 end;
 $$ language plpgsql;
 ERROR:  cursor FOR loop must use a bound cursor variable
-CONTEXT:  compilation of PL/pgSQL function "forc_bad" near line 4
+LINE 5:   for r in c loop
+                   ^
 -- test RETURN QUERY EXECUTE
 create or replace function return_dquery()
 returns setof int as $$
@@ -3839,21 +3836,20 @@ begin
 end
 $$ language plpgsql;
 WARNING:  nonstandard use of \\ in a string literal
+LINE 3:   raise notice 'foo\\bar\041baz';
+                       ^
 HINT:  Use the escape string syntax for backslashes, e.g., E'\\'.
-CONTEXT:  string literal in PL/PgSQL function "strtest" near line 2
 WARNING:  nonstandard use of \\ in a string literal
-LINE 1: SELECT  'foo\\bar\041baz'
-                ^
+LINE 4:   return 'foo\\bar\041baz';
+                 ^
 HINT:  Use the escape string syntax for backslashes, e.g., E'\\'.
-QUERY:  SELECT  'foo\\bar\041baz'
-CONTEXT:  SQL statement in PL/PgSQL function "strtest" near line 3
 select strtest();
 NOTICE:  foo\bar!baz
 WARNING:  nonstandard use of \\ in a string literal
-LINE 1: SELECT  'foo\\bar\041baz'
-                ^
+LINE 1: SELECT 'foo\\bar\041baz'
+               ^
 HINT:  Use the escape string syntax for backslashes, e.g., E'\\'.
-QUERY:  SELECT  'foo\\bar\041baz'
+QUERY:  SELECT 'foo\\bar\041baz'
 CONTEXT:  PL/pgSQL function "strtest" line 3 at RETURN
    strtest   
 -------------
@@ -3922,7 +3918,7 @@ NOTICE:  105, Office
 NOTICE:  106, Office
 -- these are to check syntax error reporting
 DO LANGUAGE plpgsql $$begin return 1; end$$;
-ERROR:  RETURN cannot have a parameter in function returning void at or near "1"
+ERROR:  RETURN cannot have a parameter in function returning void
 LINE 1: DO LANGUAGE plpgsql $$begin return 1; end$$;
                                            ^
 DO LANGUAGE plpgsql $$
@@ -3934,9 +3930,9 @@ BEGIN
     END LOOP;
 END$$;
 ERROR:  column "foo" does not exist
-LINE 1:  SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY room...
-                                         ^
-QUERY:   SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno
+LINE 1: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomn...
+                                        ^
+QUERY:  SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno
 CONTEXT:  PL/pgSQL function "inline_code_block" line 3 at FOR over SELECT rows
 -- Check variable scoping -- a var is not available in its own or prior
 -- default expressions.