aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/pl/plpgsql/src/gram.y874
-rw-r--r--src/pl/plpgsql/src/pl_comp.c33
-rw-r--r--src/pl/plpgsql/src/pl_exec.c3
-rw-r--r--src/pl/plpgsql/src/plpgsql.h13
-rw-r--r--src/pl/plpgsql/src/scan.l400
-rw-r--r--src/test/regress/expected/plpgsql.out74
6 files changed, 771 insertions, 626 deletions
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index bda7bb2b588..fd4d3e6d784 100644
--- a/src/pl/plpgsql/src/gram.y
+++ b/src/pl/plpgsql/src/gram.y
@@ -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 $
*
*-------------------------------------------------------------------------
*/
@@ -17,11 +16,31 @@
#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
@@ -33,6 +52,14 @@
#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;
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 4c232673b37..a394eace606 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -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.
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 0c0f077845d..117da74eb0f 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -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"
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 386d89a3659..1d41ee74c68 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -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);
diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l
index 2c5645ce63a..e77508900b5 100644
--- a/src/pl/plpgsql/src/scan.l
+++ b/src/pl/plpgsql/src/scan.l
@@ -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 $
*
*-------------------------------------------------------------------------
*/
@@ -24,27 +23,32 @@
#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);
}
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 5846246b7c2..534a60057dc 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -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.