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:
parent
cb719fa02d
commit
4879a5172a
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|
Loading…
Reference in a new issue