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,10 +16,30 @@ #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 @@ -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->lineno = plpgsql_location_to_lineno(@1); + new->expr = $2; + + $$ = (PLpgSQL_stmt *)new; + } + ; + +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 = plpgsql_location_to_lineno(@1); + new->varno = $1; new->expr = $3; $$ = (PLpgSQL_stmt *)new; } ; -stmt_assign : assign_var lno K_ASSIGN expr_until_semi - { - PLpgSQL_stmt_assign *new; - - new = palloc0(sizeof(PLpgSQL_stmt_assign)); - new->cmd_type = PLPGSQL_STMT_ASSIGN; - new->lineno = $2; - new->varno = $1; - new->expr = $4; - - $$ = (PLpgSQL_stmt *)new; - } - ; - -stmt_getdiag : K_GET K_DIAGNOSTICS lno getdiag_list ';' +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))); - } + cursor->refname), + parser_errposition(yylloc))); /* - * Push back the '(', else plpgsql_read_expression - * will complain about unbalanced parens. + * Read expressions until the matching ')'. */ - plpgsql_push_back_token(tok); + expr = plpgsql_read_expression(')', ")"); - expr = plpgsql_read_expression(until, expected); - - /* - * Now remove the leading and trailing parens, - * because we want "SELECT 1, 2", not "SELECT (1, 2)". - */ - cp = expr->query; - - 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; } +:= { 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 { return O_OPTION; } -dump { return O_DUMP; } +^#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_append_source_text(StringInfo buf, + int startlocation, int endlocation) +{ + Assert(startlocation <= endlocation); + appendBinaryStringInfo(buf, scanorig + startlocation, + endlocation - startlocation); +} + +/* + * 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; + + 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 = yytext; - int cursorpos; - - plpgsql_error_lineno = plpgsql_scanner_lineno(); - - /* in multibyte encodings, return index in characters not bytes */ - cursorpos = pg_mbstrlen_with_len(scanbuf, loc - scanbuf) + 1; + 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; - while ((c = strchr(cur_line_start, '\n')) != NULL) + if (location < 0 || scanorig == NULL) + return 0; /* garbage in, garbage out */ + loc = scanorig + location; + + /* 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 $$ <> @@ -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.