From: Tom Lane Date: Mon, 9 Nov 2009 00:26:55 +0000 (+0000) Subject: Modernize plpgsql's handling of parse locations, making it look a lot more X-Git-Tag: REL9_0_0~1456 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=39bd3fd1db6f3aa3764d4a1bebcd71c4e9c00281;p=pg-rex%2Fsyncrep.git Modernize plpgsql's handling of parse locations, making it look a lot more 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. --- diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index bda7bb2b58..fd4d3e6d78 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 for_control %type any_identifier any_name opt_block_label opt_label -%type execsql_start %type proc_sect proc_stmts stmt_else %type loop_body @@ -170,8 +202,6 @@ static List *read_raise_options(void); %type opt_scrollable %type opt_fetch_direction -%type 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 = $3; - new->exc_list = $4; + PLpgSQL_exception_block *new = $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 4c232673b3..a394eace60 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 0c0f077845..117da74eb0 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 386d89a365..1d41ee74c6 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 2c5645ce63..e77508900b 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; } {xcstart} { @@ -281,14 +273,14 @@ dump { return O_DUMP; } <> { 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); } {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; } {xqdouble} { @@ -317,7 +309,7 @@ dump { return O_DUMP; } <> { 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]; } {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 5846246b7c..534a60057d 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 $$ <> @@ -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$ <>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.