]> granicus.if.org Git - postgresql/commitdiff
Support plpgsql variable names that conflict with unreserved SQL keywords.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 4 Jan 2019 17:16:19 +0000 (12:16 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 4 Jan 2019 17:16:19 +0000 (12:16 -0500)
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

src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_scanner.c
src/pl/plpgsql/src/plpgsql.h
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

index 0e9ea105964b7dc15cc78e6c1c1d6867cfbef3b5..2454386af84500adf543250c09ed20b3cf3bc4be 100644 (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,
  * 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 not recognized, fill in *word and return false.
  * (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
-plpgsql_parse_word(char *word1, const char *yytxt,
+plpgsql_parse_word(char *word1, const char *yytxt, bool lookup,
                                   PLwdatum *wdatum, PLword *word)
 {
        PLpgSQL_nsitem *ns;
 
        /*
-        * We should do nothing in DECLARE sections.  In SQL expressions, there's
-        * no need to do anything either --- lookup will happen when the
-        * expression is compiled.
+        * We should not lookup variables in DECLARE sections.  In SQL
+        * expressions, there's no need to do so either --- lookup will happen
+        * 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
index 394d4658cd31ceb0787b1c0c9981fe12089bcfd4..8340628de3c8726c0d0e3e06ca8b4260d89b2814 100644 (file)
@@ -328,6 +328,7 @@ plpgsql_yylex(void)
                                push_back_token(tok2, &aux2);
                                if (plpgsql_parse_word(aux1.lval.str,
                                                                           core_yy.scanbuf + aux1.lloc,
+                                                                          true,
                                                                           &aux1.lval.wdatum,
                                                                           &aux1.lval.word))
                                        tok1 = T_DATUM;
@@ -349,53 +350,40 @@ plpgsql_yylex(void)
                        push_back_token(tok2, &aux2);
 
                        /*
-                        * If we are at start of statement, prefer unreserved keywords
-                        * over variable names, unless the next token is assignment or
-                        * '[', in which case prefer variable names.  (Note we need not
-                        * consider '.' as the next token; that case was handled above,
-                        * and we always prefer variable names in that case.)  If we are
-                        * not at start of statement, always prefer variable names over
-                        * unreserved keywords.
+                        * See if it matches a variable name, except in the context where
+                        * we are at start of statement and the next token isn't
+                        * assignment or '['.  In that case, it couldn't validly be a
+                        * variable name, and skipping the lookup allows variable names to
+                        * be used that would conflict with plpgsql or core keywords that
+                        * introduce statements (e.g., "comment").  Without this special
+                        * 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) &&
-                               !(tok2 == '=' || tok2 == COLON_EQUALS || tok2 == '['))
+                       if (plpgsql_parse_word(aux1.lval.str,
+                                                                  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 */
-                               if (core_yy.scanbuf[aux1.lloc] != '"' &&
-                                       (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;
+                               aux1.lval.keyword = kw->name;
+                               tok1 = kw->value;
                        }
                        else
-                       {
-                               /* 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;
-                       }
+                               tok1 = T_WORD;
                }
        }
        else
index 2dca49334af5ff4312a566a6f1c4afe4fb92bf4e..a7118b8386101ece1177248bf122b63b2e4f877d 100644 (file)
@@ -1175,7 +1175,7 @@ extern PLpgSQL_function *plpgsql_compile(FunctionCallInfo fcinfo,
 extern PLpgSQL_function *plpgsql_compile_inline(char *proc_source);
 extern void plpgsql_parser_setup(struct ParseState *pstate,
                                         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);
 extern bool plpgsql_parse_dblword(char *word1, char *word2,
                                          PLwdatum *wdatum, PLcword *cword);
index b9a987dbcfa983e0f75f1753c850433ae02be30a..d2f68e1e4cb1c59b6e30eacf972feb1332d14224 100644 (file)
@@ -4781,6 +4781,27 @@ select unreserved_test();
               43
 (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();
 --
 -- Test FOREACH over arrays
index 01239e26bed0b40fb50389f3f78706b696973ca2..9c8cf752adbeef9a37d51b4a7e4dfa10163e43c8 100644 (file)
@@ -3892,6 +3892,20 @@ $$ language plpgsql;
 
 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();
 
 --