Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 5e9d8be

Browse files
committed
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 4adead1 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
1 parent bfed705 commit 5e9d8be

File tree

7 files changed

+74
-34
lines changed

7 files changed

+74
-34
lines changed

src/pl/plpgsql/src/expected/plpgsql_control.out

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,3 +681,20 @@ select case_test(13);
681681
other
682682
(1 row)
683683

684+
-- test line comment between WHEN and THEN
685+
create or replace function case_comment(int) returns text as $$
686+
begin
687+
case $1
688+
when 1 -- comment before THEN
689+
then return 'one';
690+
else
691+
return 'other';
692+
end case;
693+
end;
694+
$$ language plpgsql immutable;
695+
select case_comment(1);
696+
case_comment
697+
--------------
698+
one
699+
(1 row)
700+

src/pl/plpgsql/src/pl_gram.y

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ static PLpgSQL_expr *read_sql_construct(int until,
7070
const char *sqlstart,
7171
bool isexpression,
7272
bool valid_sql,
73-
bool trim,
7473
int *startloc,
7574
int *endtoken);
7675
static PLpgSQL_expr *read_sql_expression(int until,
@@ -1463,7 +1462,6 @@ for_control : for_variable K_IN
14631462
"SELECT ",
14641463
true,
14651464
false,
1466-
true,
14671465
&expr1loc,
14681466
&tok);
14691467

@@ -1870,7 +1868,7 @@ stmt_raise : K_RAISE
18701868
expr = read_sql_construct(',', ';', K_USING,
18711869
", or ; or USING",
18721870
"SELECT ",
1873-
true, true, true,
1871+
true, true,
18741872
NULL, &tok);
18751873
new->params = lappend(new->params, expr);
18761874
}
@@ -2001,7 +1999,7 @@ stmt_dynexecute : K_EXECUTE
20011999
expr = read_sql_construct(K_INTO, K_USING, ';',
20022000
"INTO or USING or ;",
20032001
"SELECT ",
2004-
true, true, true,
2002+
true, true,
20052003
NULL, &endtoken);
20062004

20072005
new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
@@ -2040,7 +2038,7 @@ stmt_dynexecute : K_EXECUTE
20402038
expr = read_sql_construct(',', ';', K_INTO,
20412039
", or ; or INTO",
20422040
"SELECT ",
2043-
true, true, true,
2041+
true, true,
20442042
NULL, &endtoken);
20452043
new->params = lappend(new->params, expr);
20462044
} while (endtoken == ',');
@@ -2655,7 +2653,7 @@ static PLpgSQL_expr *
26552653
read_sql_expression(int until, const char *expected)
26562654
{
26572655
return read_sql_construct(until, 0, 0, expected,
2658-
"SELECT ", true, true, true, NULL, NULL);
2656+
"SELECT ", true, true, NULL, NULL);
26592657
}
26602658

26612659
/* Convenience routine to read an expression with two possible terminators */
@@ -2664,15 +2662,15 @@ read_sql_expression2(int until, int until2, const char *expected,
26642662
int *endtoken)
26652663
{
26662664
return read_sql_construct(until, until2, 0, expected,
2667-
"SELECT ", true, true, true, NULL, endtoken);
2665+
"SELECT ", true, true, NULL, endtoken);
26682666
}
26692667

26702668
/* Convenience routine to read a SQL statement that must end with ';' */
26712669
static PLpgSQL_expr *
26722670
read_sql_stmt(const char *sqlstart)
26732671
{
26742672
return read_sql_construct(';', 0, 0, ";",
2675-
sqlstart, false, true, true, NULL, NULL);
2673+
sqlstart, false, true, NULL, NULL);
26762674
}
26772675

26782676
/*
@@ -2685,7 +2683,6 @@ read_sql_stmt(const char *sqlstart)
26852683
* sqlstart: text to prefix to the accumulated SQL text
26862684
* isexpression: whether to say we're reading an "expression" or a "statement"
26872685
* valid_sql: whether to check the syntax of the expr (prefixed with sqlstart)
2688-
* trim: trim trailing whitespace
26892686
* startloc: if not NULL, location of first token is stored at *startloc
26902687
* endtoken: if not NULL, ending token is stored at *endtoken
26912688
* (this is only interesting if until2 or until3 isn't zero)
@@ -2698,14 +2695,14 @@ read_sql_construct(int until,
26982695
const char *sqlstart,
26992696
bool isexpression,
27002697
bool valid_sql,
2701-
bool trim,
27022698
int *startloc,
27032699
int *endtoken)
27042700
{
27052701
int tok;
27062702
StringInfoData ds;
27072703
IdentifierLookup save_IdentifierLookup;
27082704
int startlocation = -1;
2705+
int endlocation = -1;
27092706
int parenlevel = 0;
27102707
PLpgSQL_expr *expr;
27112708

@@ -2757,6 +2754,8 @@ read_sql_construct(int until,
27572754
expected),
27582755
parser_errposition(yylloc)));
27592756
}
2757+
/* Remember end+1 location of last accepted token */
2758+
endlocation = yylloc + plpgsql_token_length();
27602759
}
27612760

27622761
plpgsql_IdentifierLookup = save_IdentifierLookup;
@@ -2767,22 +2766,22 @@ read_sql_construct(int until,
27672766
*endtoken = tok;
27682767

27692768
/* give helpful complaint about empty input */
2770-
if (startlocation >= yylloc)
2769+
if (startlocation >= endlocation)
27712770
{
27722771
if (isexpression)
27732772
yyerror("missing expression");
27742773
else
27752774
yyerror("missing SQL statement");
27762775
}
27772776

2778-
plpgsql_append_source_text(&ds, startlocation, yylloc);
2779-
2780-
/* trim any trailing whitespace, for neatness */
2781-
if (trim)
2782-
{
2783-
while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
2784-
ds.data[--ds.len] = '\0';
2785-
}
2777+
/*
2778+
* We save only the text from startlocation to endlocation-1. This
2779+
* suppresses the "until" token as well as any whitespace or comments
2780+
* following the last accepted token. (We used to strip such trailing
2781+
* whitespace by hand, but that causes problems if there's a "-- comment"
2782+
* in front of said whitespace.)
2783+
*/
2784+
plpgsql_append_source_text(&ds, startlocation, endlocation);
27862785

27872786
expr = palloc0(sizeof(PLpgSQL_expr));
27882787
expr->query = pstrdup(ds.data);
@@ -3873,16 +3872,12 @@ read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
38733872
* Read the value expression. To provide the user with meaningful
38743873
* parse error positions, we check the syntax immediately, instead of
38753874
* checking the final expression that may have the arguments
3876-
* reordered. Trailing whitespace must not be trimmed, because
3877-
* otherwise input of the form (param -- comment\n, param) would be
3878-
* translated into a form where the second parameter is commented
3879-
* out.
3875+
* reordered.
38803876
*/
38813877
item = read_sql_construct(',', ')', 0,
38823878
",\" or \")",
38833879
sqlstart,
38843880
true, true,
3885-
false, /* do not trim */
38863881
NULL, &endtoken);
38873882

38883883
argv[argpos] = item->query + strlen(sqlstart);

src/pl/plpgsql/src/pl_scanner.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ plpgsql_yylex(void)
184184
tok1 = T_DATUM;
185185
else
186186
tok1 = T_CWORD;
187+
/* Adjust token length to include A.B.C */
188+
aux1.leng = aux5.lloc - aux1.lloc + aux5.leng;
187189
}
188190
else
189191
{
@@ -197,6 +199,8 @@ plpgsql_yylex(void)
197199
tok1 = T_DATUM;
198200
else
199201
tok1 = T_CWORD;
202+
/* Adjust token length to include A.B */
203+
aux1.leng = aux3.lloc - aux1.lloc + aux3.leng;
200204
}
201205
}
202206
else
@@ -210,6 +214,8 @@ plpgsql_yylex(void)
210214
tok1 = T_DATUM;
211215
else
212216
tok1 = T_CWORD;
217+
/* Adjust token length to include A.B */
218+
aux1.leng = aux3.lloc - aux1.lloc + aux3.leng;
213219
}
214220
}
215221
else
@@ -298,6 +304,17 @@ plpgsql_yylex(void)
298304
return tok1;
299305
}
300306

307+
/*
308+
* Return the length of the token last returned by plpgsql_yylex().
309+
*
310+
* In the case of compound tokens, the length includes all the parts.
311+
*/
312+
int
313+
plpgsql_token_length(void)
314+
{
315+
return plpgsql_yyleng;
316+
}
317+
301318
/*
302319
* Internal yylex function. This wraps the core lexer and adds one feature:
303320
* a token pushback stack. We also make a couple of trivial single-token

src/pl/plpgsql/src/plpgsql.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,7 @@ extern void plpgsql_dumptree(PLpgSQL_function *func);
13111311
*/
13121312
extern int plpgsql_base_yylex(void);
13131313
extern int plpgsql_yylex(void);
1314+
extern int plpgsql_token_length(void);
13141315
extern void plpgsql_push_back_token(int token);
13151316
extern bool plpgsql_token_is_unreserved_keyword(int token);
13161317
extern void plpgsql_append_source_text(StringInfo buf,

src/pl/plpgsql/src/sql/plpgsql_control.sql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,17 @@ select case_test(1);
486486
select case_test(2);
487487
select case_test(12);
488488
select case_test(13);
489+
490+
-- test line comment between WHEN and THEN
491+
create or replace function case_comment(int) returns text as $$
492+
begin
493+
case $1
494+
when 1 -- comment before THEN
495+
then return 'one';
496+
else
497+
return 'other';
498+
end case;
499+
end;
500+
$$ language plpgsql immutable;
501+
502+
select case_comment(1);

src/test/regress/expected/plpgsql.out

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,11 +2185,9 @@ select namedparmcursor_test7();
21852185
ERROR: division by zero
21862186
CONTEXT: SQL statement "SELECT 42/0 AS p1, 77 AS p2;"
21872187
PL/pgSQL function namedparmcursor_test7() line 6 at OPEN
2188-
-- check that line comments work correctly within the argument list (there
2189-
-- is some special handling of this case in the code: the newline after the
2190-
-- comment must be preserved when the argument-evaluating query is
2191-
-- constructed, otherwise the comment effectively comments out the next
2192-
-- argument, too)
2188+
-- check that line comments work correctly within the argument list
2189+
-- (this used to require a special hack in the code; it no longer does,
2190+
-- but let's keep the test anyway)
21932191
create function namedparmcursor_test8() returns int4 as $$
21942192
declare
21952193
c1 cursor (p1 int, p2 int) for

src/test/regress/sql/plpgsql.sql

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1895,11 +1895,9 @@ begin
18951895
end $$ language plpgsql;
18961896
select namedparmcursor_test7();
18971897

1898-
-- check that line comments work correctly within the argument list (there
1899-
-- is some special handling of this case in the code: the newline after the
1900-
-- comment must be preserved when the argument-evaluating query is
1901-
-- constructed, otherwise the comment effectively comments out the next
1902-
-- argument, too)
1898+
-- check that line comments work correctly within the argument list
1899+
-- (this used to require a special hack in the code; it no longer does,
1900+
-- but let's keep the test anyway)
19031901
create function namedparmcursor_test8() returns int4 as $$
19041902
declare
19051903
c1 cursor (p1 int, p2 int) for

0 commit comments

Comments
 (0)