Fix plpgsql's handling of -- comments following expressions.
Up to now, read_sql_construct() has collected all the source text from the statement or expression's initial token up to the character just before the "until" token. It normally tries to strip trailing whitespace from that, largely for neatness. If there was a "-- text" comment after the expression, this resulted in removing the newline that terminates the comment, which creates a hazard if we try to paste the collected text into a larger SQL construct without inserting a newline after it. In particular this caused our handling of CASE constructs to fail if there's a comment after a WHEN expression. Commit 4adead1d2 noticed a similar problem with cursor arguments, and worked around it through the rather crude hack of suppressing the whitespace-trimming behavior for those. Rather than do that and leave the hazard open for future hackers to trip over, let's fix it properly. pl_scanner.c already has enough infrastructure to report the end location of the expression's last token, so we can copy up to that location and never collect any trailing whitespace or comment to begin with. Erik Wienhold and Tom Lane, per report from Michal Bartak. Back-patch to all supported branches. Discussion: https://postgr.es/m/CAAVzF_FjRoi8fOVuLCZhQJx6HATQ7MKm=aFOHWZODFnLmjX-xA@mail.gmail.com
This commit is contained in:
parent
2e56ad6a31
commit
dc5824a06e
@ -681,3 +681,20 @@ select case_test(13);
|
|||||||
other
|
other
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
-- test line comment between WHEN and THEN
|
||||||
|
create or replace function case_comment(int) returns text as $$
|
||||||
|
begin
|
||||||
|
case $1
|
||||||
|
when 1 -- comment before THEN
|
||||||
|
then return 'one';
|
||||||
|
else
|
||||||
|
return 'other';
|
||||||
|
end case;
|
||||||
|
end;
|
||||||
|
$$ language plpgsql immutable;
|
||||||
|
select case_comment(1);
|
||||||
|
case_comment
|
||||||
|
--------------
|
||||||
|
one
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
@ -69,7 +69,6 @@ static PLpgSQL_expr *read_sql_construct(int until,
|
|||||||
RawParseMode parsemode,
|
RawParseMode parsemode,
|
||||||
bool isexpression,
|
bool isexpression,
|
||||||
bool valid_sql,
|
bool valid_sql,
|
||||||
bool trim,
|
|
||||||
int *startloc,
|
int *startloc,
|
||||||
int *endtoken);
|
int *endtoken);
|
||||||
static PLpgSQL_expr *read_sql_expression(int until,
|
static PLpgSQL_expr *read_sql_expression(int until,
|
||||||
@ -921,7 +920,7 @@ stmt_perform : K_PERFORM
|
|||||||
*/
|
*/
|
||||||
new->expr = read_sql_construct(';', 0, 0, ";",
|
new->expr = read_sql_construct(';', 0, 0, ";",
|
||||||
RAW_PARSE_DEFAULT,
|
RAW_PARSE_DEFAULT,
|
||||||
false, false, true,
|
false, false,
|
||||||
&startloc, NULL);
|
&startloc, NULL);
|
||||||
/* overwrite "perform" ... */
|
/* overwrite "perform" ... */
|
||||||
memcpy(new->expr->query, " SELECT", 7);
|
memcpy(new->expr->query, " SELECT", 7);
|
||||||
@ -1007,7 +1006,7 @@ stmt_assign : T_DATUM
|
|||||||
plpgsql_push_back_token(T_DATUM);
|
plpgsql_push_back_token(T_DATUM);
|
||||||
new->expr = read_sql_construct(';', 0, 0, ";",
|
new->expr = read_sql_construct(';', 0, 0, ";",
|
||||||
pmode,
|
pmode,
|
||||||
false, true, true,
|
false, true,
|
||||||
NULL, NULL);
|
NULL, NULL);
|
||||||
|
|
||||||
$$ = (PLpgSQL_stmt *)new;
|
$$ = (PLpgSQL_stmt *)new;
|
||||||
@ -1496,7 +1495,6 @@ for_control : for_variable K_IN
|
|||||||
RAW_PARSE_DEFAULT,
|
RAW_PARSE_DEFAULT,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true,
|
|
||||||
&expr1loc,
|
&expr1loc,
|
||||||
&tok);
|
&tok);
|
||||||
|
|
||||||
@ -1901,7 +1899,7 @@ stmt_raise : K_RAISE
|
|||||||
expr = read_sql_construct(',', ';', K_USING,
|
expr = read_sql_construct(',', ';', K_USING,
|
||||||
", or ; or USING",
|
", or ; or USING",
|
||||||
RAW_PARSE_PLPGSQL_EXPR,
|
RAW_PARSE_PLPGSQL_EXPR,
|
||||||
true, true, true,
|
true, true,
|
||||||
NULL, &tok);
|
NULL, &tok);
|
||||||
new->params = lappend(new->params, expr);
|
new->params = lappend(new->params, expr);
|
||||||
}
|
}
|
||||||
@ -2034,7 +2032,7 @@ stmt_dynexecute : K_EXECUTE
|
|||||||
expr = read_sql_construct(K_INTO, K_USING, ';',
|
expr = read_sql_construct(K_INTO, K_USING, ';',
|
||||||
"INTO or USING or ;",
|
"INTO or USING or ;",
|
||||||
RAW_PARSE_PLPGSQL_EXPR,
|
RAW_PARSE_PLPGSQL_EXPR,
|
||||||
true, true, true,
|
true, true,
|
||||||
NULL, &endtoken);
|
NULL, &endtoken);
|
||||||
|
|
||||||
new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
|
new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
|
||||||
@ -2073,7 +2071,7 @@ stmt_dynexecute : K_EXECUTE
|
|||||||
expr = read_sql_construct(',', ';', K_INTO,
|
expr = read_sql_construct(',', ';', K_INTO,
|
||||||
", or ; or INTO",
|
", or ; or INTO",
|
||||||
RAW_PARSE_PLPGSQL_EXPR,
|
RAW_PARSE_PLPGSQL_EXPR,
|
||||||
true, true, true,
|
true, true,
|
||||||
NULL, &endtoken);
|
NULL, &endtoken);
|
||||||
new->params = lappend(new->params, expr);
|
new->params = lappend(new->params, expr);
|
||||||
} while (endtoken == ',');
|
} while (endtoken == ',');
|
||||||
@ -2656,7 +2654,7 @@ read_sql_expression(int until, const char *expected)
|
|||||||
{
|
{
|
||||||
return read_sql_construct(until, 0, 0, expected,
|
return read_sql_construct(until, 0, 0, expected,
|
||||||
RAW_PARSE_PLPGSQL_EXPR,
|
RAW_PARSE_PLPGSQL_EXPR,
|
||||||
true, true, true, NULL, NULL);
|
true, true, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Convenience routine to read an expression with two possible terminators */
|
/* Convenience routine to read an expression with two possible terminators */
|
||||||
@ -2666,7 +2664,7 @@ read_sql_expression2(int until, int until2, const char *expected,
|
|||||||
{
|
{
|
||||||
return read_sql_construct(until, until2, 0, expected,
|
return read_sql_construct(until, until2, 0, expected,
|
||||||
RAW_PARSE_PLPGSQL_EXPR,
|
RAW_PARSE_PLPGSQL_EXPR,
|
||||||
true, true, true, NULL, endtoken);
|
true, true, NULL, endtoken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Convenience routine to read a SQL statement that must end with ';' */
|
/* Convenience routine to read a SQL statement that must end with ';' */
|
||||||
@ -2675,7 +2673,7 @@ read_sql_stmt(void)
|
|||||||
{
|
{
|
||||||
return read_sql_construct(';', 0, 0, ";",
|
return read_sql_construct(';', 0, 0, ";",
|
||||||
RAW_PARSE_DEFAULT,
|
RAW_PARSE_DEFAULT,
|
||||||
false, true, true, NULL, NULL);
|
false, true, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2688,7 +2686,6 @@ read_sql_stmt(void)
|
|||||||
* parsemode: raw_parser() mode to use
|
* parsemode: raw_parser() mode to use
|
||||||
* isexpression: whether to say we're reading an "expression" or a "statement"
|
* isexpression: whether to say we're reading an "expression" or a "statement"
|
||||||
* valid_sql: whether to check the syntax of the expr
|
* valid_sql: whether to check the syntax of the expr
|
||||||
* trim: trim trailing whitespace
|
|
||||||
* startloc: if not NULL, location of first token is stored at *startloc
|
* startloc: if not NULL, location of first token is stored at *startloc
|
||||||
* endtoken: if not NULL, ending token is stored at *endtoken
|
* endtoken: if not NULL, ending token is stored at *endtoken
|
||||||
* (this is only interesting if until2 or until3 isn't zero)
|
* (this is only interesting if until2 or until3 isn't zero)
|
||||||
@ -2701,7 +2698,6 @@ read_sql_construct(int until,
|
|||||||
RawParseMode parsemode,
|
RawParseMode parsemode,
|
||||||
bool isexpression,
|
bool isexpression,
|
||||||
bool valid_sql,
|
bool valid_sql,
|
||||||
bool trim,
|
|
||||||
int *startloc,
|
int *startloc,
|
||||||
int *endtoken)
|
int *endtoken)
|
||||||
{
|
{
|
||||||
@ -2709,6 +2705,7 @@ read_sql_construct(int until,
|
|||||||
StringInfoData ds;
|
StringInfoData ds;
|
||||||
IdentifierLookup save_IdentifierLookup;
|
IdentifierLookup save_IdentifierLookup;
|
||||||
int startlocation = -1;
|
int startlocation = -1;
|
||||||
|
int endlocation = -1;
|
||||||
int parenlevel = 0;
|
int parenlevel = 0;
|
||||||
PLpgSQL_expr *expr;
|
PLpgSQL_expr *expr;
|
||||||
|
|
||||||
@ -2759,6 +2756,8 @@ read_sql_construct(int until,
|
|||||||
expected),
|
expected),
|
||||||
parser_errposition(yylloc)));
|
parser_errposition(yylloc)));
|
||||||
}
|
}
|
||||||
|
/* Remember end+1 location of last accepted token */
|
||||||
|
endlocation = yylloc + plpgsql_token_length();
|
||||||
}
|
}
|
||||||
|
|
||||||
plpgsql_IdentifierLookup = save_IdentifierLookup;
|
plpgsql_IdentifierLookup = save_IdentifierLookup;
|
||||||
@ -2769,7 +2768,7 @@ read_sql_construct(int until,
|
|||||||
*endtoken = tok;
|
*endtoken = tok;
|
||||||
|
|
||||||
/* give helpful complaint about empty input */
|
/* give helpful complaint about empty input */
|
||||||
if (startlocation >= yylloc)
|
if (startlocation >= endlocation)
|
||||||
{
|
{
|
||||||
if (isexpression)
|
if (isexpression)
|
||||||
yyerror("missing expression");
|
yyerror("missing expression");
|
||||||
@ -2777,14 +2776,14 @@ read_sql_construct(int until,
|
|||||||
yyerror("missing SQL statement");
|
yyerror("missing SQL statement");
|
||||||
}
|
}
|
||||||
|
|
||||||
plpgsql_append_source_text(&ds, startlocation, yylloc);
|
/*
|
||||||
|
* We save only the text from startlocation to endlocation-1. This
|
||||||
/* trim any trailing whitespace, for neatness */
|
* suppresses the "until" token as well as any whitespace or comments
|
||||||
if (trim)
|
* following the last accepted token. (We used to strip such trailing
|
||||||
{
|
* whitespace by hand, but that causes problems if there's a "-- comment"
|
||||||
while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
|
* in front of said whitespace.)
|
||||||
ds.data[--ds.len] = '\0';
|
*/
|
||||||
}
|
plpgsql_append_source_text(&ds, startlocation, endlocation);
|
||||||
|
|
||||||
expr = palloc0(sizeof(PLpgSQL_expr));
|
expr = palloc0(sizeof(PLpgSQL_expr));
|
||||||
expr->query = pstrdup(ds.data);
|
expr->query = pstrdup(ds.data);
|
||||||
@ -3923,16 +3922,12 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
|
|||||||
* Read the value expression. To provide the user with meaningful
|
* Read the value expression. To provide the user with meaningful
|
||||||
* parse error positions, we check the syntax immediately, instead of
|
* parse error positions, we check the syntax immediately, instead of
|
||||||
* checking the final expression that may have the arguments
|
* checking the final expression that may have the arguments
|
||||||
* reordered. Trailing whitespace must not be trimmed, because
|
* reordered.
|
||||||
* otherwise input of the form (param -- comment\n, param) would be
|
|
||||||
* translated into a form where the second parameter is commented
|
|
||||||
* out.
|
|
||||||
*/
|
*/
|
||||||
item = read_sql_construct(',', ')', 0,
|
item = read_sql_construct(',', ')', 0,
|
||||||
",\" or \")",
|
",\" or \")",
|
||||||
RAW_PARSE_PLPGSQL_EXPR,
|
RAW_PARSE_PLPGSQL_EXPR,
|
||||||
true, true,
|
true, true,
|
||||||
false, /* do not trim */
|
|
||||||
NULL, &endtoken);
|
NULL, &endtoken);
|
||||||
|
|
||||||
argv[argpos] = item->query;
|
argv[argpos] = item->query;
|
||||||
|
@ -184,6 +184,8 @@ plpgsql_yylex(void)
|
|||||||
tok1 = T_DATUM;
|
tok1 = T_DATUM;
|
||||||
else
|
else
|
||||||
tok1 = T_CWORD;
|
tok1 = T_CWORD;
|
||||||
|
/* Adjust token length to include A.B.C */
|
||||||
|
aux1.leng = aux5.lloc - aux1.lloc + aux5.leng;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -197,6 +199,8 @@ plpgsql_yylex(void)
|
|||||||
tok1 = T_DATUM;
|
tok1 = T_DATUM;
|
||||||
else
|
else
|
||||||
tok1 = T_CWORD;
|
tok1 = T_CWORD;
|
||||||
|
/* Adjust token length to include A.B */
|
||||||
|
aux1.leng = aux3.lloc - aux1.lloc + aux3.leng;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -210,6 +214,8 @@ plpgsql_yylex(void)
|
|||||||
tok1 = T_DATUM;
|
tok1 = T_DATUM;
|
||||||
else
|
else
|
||||||
tok1 = T_CWORD;
|
tok1 = T_CWORD;
|
||||||
|
/* Adjust token length to include A.B */
|
||||||
|
aux1.leng = aux3.lloc - aux1.lloc + aux3.leng;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -298,6 +304,17 @@ plpgsql_yylex(void)
|
|||||||
return tok1;
|
return tok1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the length of the token last returned by plpgsql_yylex().
|
||||||
|
*
|
||||||
|
* In the case of compound tokens, the length includes all the parts.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
plpgsql_token_length(void)
|
||||||
|
{
|
||||||
|
return plpgsql_yyleng;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Internal yylex function. This wraps the core lexer and adds one feature:
|
* Internal yylex function. This wraps the core lexer and adds one feature:
|
||||||
* a token pushback stack. We also make a couple of trivial single-token
|
* a token pushback stack. We also make a couple of trivial single-token
|
||||||
|
@ -1302,6 +1302,7 @@ extern void plpgsql_dumptree(PLpgSQL_function *func);
|
|||||||
*/
|
*/
|
||||||
extern int plpgsql_base_yylex(void);
|
extern int plpgsql_base_yylex(void);
|
||||||
extern int plpgsql_yylex(void);
|
extern int plpgsql_yylex(void);
|
||||||
|
extern int plpgsql_token_length(void);
|
||||||
extern void plpgsql_push_back_token(int token);
|
extern void plpgsql_push_back_token(int token);
|
||||||
extern bool plpgsql_token_is_unreserved_keyword(int token);
|
extern bool plpgsql_token_is_unreserved_keyword(int token);
|
||||||
extern void plpgsql_append_source_text(StringInfo buf,
|
extern void plpgsql_append_source_text(StringInfo buf,
|
||||||
|
@ -486,3 +486,17 @@ select case_test(1);
|
|||||||
select case_test(2);
|
select case_test(2);
|
||||||
select case_test(12);
|
select case_test(12);
|
||||||
select case_test(13);
|
select case_test(13);
|
||||||
|
|
||||||
|
-- test line comment between WHEN and THEN
|
||||||
|
create or replace function case_comment(int) returns text as $$
|
||||||
|
begin
|
||||||
|
case $1
|
||||||
|
when 1 -- comment before THEN
|
||||||
|
then return 'one';
|
||||||
|
else
|
||||||
|
return 'other';
|
||||||
|
end case;
|
||||||
|
end;
|
||||||
|
$$ language plpgsql immutable;
|
||||||
|
|
||||||
|
select case_comment(1);
|
||||||
|
@ -2363,11 +2363,9 @@ select namedparmcursor_test7();
|
|||||||
ERROR: division by zero
|
ERROR: division by zero
|
||||||
CONTEXT: SQL expression "42/0 AS p1, 77 AS p2"
|
CONTEXT: SQL expression "42/0 AS p1, 77 AS p2"
|
||||||
PL/pgSQL function namedparmcursor_test7() line 6 at OPEN
|
PL/pgSQL function namedparmcursor_test7() line 6 at OPEN
|
||||||
-- check that line comments work correctly within the argument list (there
|
-- check that line comments work correctly within the argument list
|
||||||
-- is some special handling of this case in the code: the newline after the
|
-- (this used to require a special hack in the code; it no longer does,
|
||||||
-- comment must be preserved when the argument-evaluating query is
|
-- but let's keep the test anyway)
|
||||||
-- constructed, otherwise the comment effectively comments out the next
|
|
||||||
-- argument, too)
|
|
||||||
create function namedparmcursor_test8() returns int4 as $$
|
create function namedparmcursor_test8() returns int4 as $$
|
||||||
declare
|
declare
|
||||||
c1 cursor (p1 int, p2 int) for
|
c1 cursor (p1 int, p2 int) for
|
||||||
|
@ -2023,11 +2023,9 @@ begin
|
|||||||
end $$ language plpgsql;
|
end $$ language plpgsql;
|
||||||
select namedparmcursor_test7();
|
select namedparmcursor_test7();
|
||||||
|
|
||||||
-- check that line comments work correctly within the argument list (there
|
-- check that line comments work correctly within the argument list
|
||||||
-- is some special handling of this case in the code: the newline after the
|
-- (this used to require a special hack in the code; it no longer does,
|
||||||
-- comment must be preserved when the argument-evaluating query is
|
-- but let's keep the test anyway)
|
||||||
-- constructed, otherwise the comment effectively comments out the next
|
|
||||||
-- argument, too)
|
|
||||||
create function namedparmcursor_test8() returns int4 as $$
|
create function namedparmcursor_test8() returns int4 as $$
|
||||||
declare
|
declare
|
||||||
c1 cursor (p1 int, p2 int) for
|
c1 cursor (p1 int, p2 int) for
|
||||||
|
Loading…
x
Reference in New Issue
Block a user