Support plpgsql variable names that conflict with unreserved SQL keywords.

A variable name matching a statement-introducing keyword, such as
"comment" or "update", caused parse failures if one tried to write
a statement using that keyword.  Commit bb1b8f69 already addressed
this scenario for the case of variable names matching unreserved
plpgsql keywords, but we didn't think about unreserved core-grammar
keywords.  The same heuristic (viz, it can't be a variable name
unless the next token is assignment or '[') should work fine for
that case too, and as a bonus the code gets shorter and less
duplicative.

Per bug #15555 from Feike Steenbergen.  Since this hasn't been
complained of before, and is easily worked around anyway,
I won't risk a back-patch.

Discussion: https://postgr.es/m/15555-149bbd70ddc7b4b6@postgresql.org
This commit is contained in:
Tom Lane 2019-01-04 12:16:19 -05:00
parent cb719fa02d
commit 4879a5172a
5 changed files with 75 additions and 49 deletions

View file

@ -1353,6 +1353,9 @@ make_datum_param(PLpgSQL_expr *expr, int dno, int location)
* yytxt is the original token text; we need this to check for quoting, * yytxt is the original token text; we need this to check for quoting,
* so that later checks for unreserved keywords work properly. * so that later checks for unreserved keywords work properly.
* *
* We attempt to recognize the token as a variable only if lookup is true
* and the plpgsql_IdentifierLookup context permits it.
*
* If recognized as a variable, fill in *wdatum and return true; * If recognized as a variable, fill in *wdatum and return true;
* if not recognized, fill in *word and return false. * if not recognized, fill in *word and return false.
* (Note: those two pointers actually point to members of the same union, * (Note: those two pointers actually point to members of the same union,
@ -1360,17 +1363,17 @@ make_datum_param(PLpgSQL_expr *expr, int dno, int location)
* ---------- * ----------
*/ */
bool bool
plpgsql_parse_word(char *word1, const char *yytxt, plpgsql_parse_word(char *word1, const char *yytxt, bool lookup,
PLwdatum *wdatum, PLword *word) PLwdatum *wdatum, PLword *word)
{ {
PLpgSQL_nsitem *ns; PLpgSQL_nsitem *ns;
/* /*
* We should do nothing in DECLARE sections. In SQL expressions, there's * We should not lookup variables in DECLARE sections. In SQL
* no need to do anything either --- lookup will happen when the * expressions, there's no need to do so either --- lookup will happen
* expression is compiled. * when the expression is compiled.
*/ */
if (plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_NORMAL) if (lookup && plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_NORMAL)
{ {
/* /*
* Do a lookup in the current namespace stack * Do a lookup in the current namespace stack

View file

@ -328,6 +328,7 @@ plpgsql_yylex(void)
push_back_token(tok2, &aux2); push_back_token(tok2, &aux2);
if (plpgsql_parse_word(aux1.lval.str, if (plpgsql_parse_word(aux1.lval.str,
core_yy.scanbuf + aux1.lloc, core_yy.scanbuf + aux1.lloc,
true,
&aux1.lval.wdatum, &aux1.lval.wdatum,
&aux1.lval.word)) &aux1.lval.word))
tok1 = T_DATUM; tok1 = T_DATUM;
@ -349,53 +350,40 @@ plpgsql_yylex(void)
push_back_token(tok2, &aux2); push_back_token(tok2, &aux2);
/* /*
* If we are at start of statement, prefer unreserved keywords * See if it matches a variable name, except in the context where
* over variable names, unless the next token is assignment or * we are at start of statement and the next token isn't
* '[', in which case prefer variable names. (Note we need not * assignment or '['. In that case, it couldn't validly be a
* consider '.' as the next token; that case was handled above, * variable name, and skipping the lookup allows variable names to
* and we always prefer variable names in that case.) If we are * be used that would conflict with plpgsql or core keywords that
* not at start of statement, always prefer variable names over * introduce statements (e.g., "comment"). Without this special
* unreserved keywords. * logic, every statement-introducing keyword would effectively be
* reserved in PL/pgSQL, which would be unpleasant.
*
* If it isn't a variable name, try to match against unreserved
* plpgsql keywords. If not one of those either, it's T_WORD.
*
* Note: we must call plpgsql_parse_word even if we don't want to
* do variable lookup, because it sets up aux1.lval.word for the
* non-variable cases.
*/ */
if (AT_STMT_START(plpgsql_yytoken) && if (plpgsql_parse_word(aux1.lval.str,
!(tok2 == '=' || tok2 == COLON_EQUALS || tok2 == '[')) core_yy.scanbuf + aux1.lloc,
(!AT_STMT_START(plpgsql_yytoken) ||
(tok2 == '=' || tok2 == COLON_EQUALS ||
tok2 == '[')),
&aux1.lval.wdatum,
&aux1.lval.word))
tok1 = T_DATUM;
else if (!aux1.lval.word.quoted &&
(kw = ScanKeywordLookup(aux1.lval.word.ident,
unreserved_keywords,
num_unreserved_keywords)))
{ {
/* try for unreserved keyword, then for variable name */ aux1.lval.keyword = kw->name;
if (core_yy.scanbuf[aux1.lloc] != '"' && tok1 = kw->value;
(kw = ScanKeywordLookup(aux1.lval.str,
unreserved_keywords,
num_unreserved_keywords)))
{
aux1.lval.keyword = kw->name;
tok1 = kw->value;
}
else if (plpgsql_parse_word(aux1.lval.str,
core_yy.scanbuf + aux1.lloc,
&aux1.lval.wdatum,
&aux1.lval.word))
tok1 = T_DATUM;
else
tok1 = T_WORD;
} }
else else
{ tok1 = T_WORD;
/* try for variable name, then for unreserved keyword */
if (plpgsql_parse_word(aux1.lval.str,
core_yy.scanbuf + aux1.lloc,
&aux1.lval.wdatum,
&aux1.lval.word))
tok1 = T_DATUM;
else if (!aux1.lval.word.quoted &&
(kw = ScanKeywordLookup(aux1.lval.word.ident,
unreserved_keywords,
num_unreserved_keywords)))
{
aux1.lval.keyword = kw->name;
tok1 = kw->value;
}
else
tok1 = T_WORD;
}
} }
} }
else else

View file

@ -1175,7 +1175,7 @@ extern PLpgSQL_function *plpgsql_compile(FunctionCallInfo fcinfo,
extern PLpgSQL_function *plpgsql_compile_inline(char *proc_source); extern PLpgSQL_function *plpgsql_compile_inline(char *proc_source);
extern void plpgsql_parser_setup(struct ParseState *pstate, extern void plpgsql_parser_setup(struct ParseState *pstate,
PLpgSQL_expr *expr); PLpgSQL_expr *expr);
extern bool plpgsql_parse_word(char *word1, const char *yytxt, extern bool plpgsql_parse_word(char *word1, const char *yytxt, bool lookup,
PLwdatum *wdatum, PLword *word); PLwdatum *wdatum, PLword *word);
extern bool plpgsql_parse_dblword(char *word1, char *word2, extern bool plpgsql_parse_dblword(char *word1, char *word2,
PLwdatum *wdatum, PLcword *cword); PLwdatum *wdatum, PLcword *cword);

View file

@ -4781,6 +4781,27 @@ select unreserved_test();
43 43
(1 row) (1 row)
create or replace function unreserved_test() returns int as $$
declare
comment int := 21;
begin
comment := comment * 2;
comment on function unreserved_test() is 'this is a test';
return comment;
end
$$ language plpgsql;
select unreserved_test();
unreserved_test
-----------------
42
(1 row)
select obj_description('unreserved_test()'::regprocedure, 'pg_proc');
obj_description
-----------------
this is a test
(1 row)
drop function unreserved_test(); drop function unreserved_test();
-- --
-- Test FOREACH over arrays -- Test FOREACH over arrays

View file

@ -3892,6 +3892,20 @@ $$ language plpgsql;
select unreserved_test(); select unreserved_test();
create or replace function unreserved_test() returns int as $$
declare
comment int := 21;
begin
comment := comment * 2;
comment on function unreserved_test() is 'this is a test';
return comment;
end
$$ language plpgsql;
select unreserved_test();
select obj_description('unreserved_test()'::regprocedure, 'pg_proc');
drop function unreserved_test(); drop function unreserved_test();
-- --