More incremental refactoring in plpgsql: get rid of gram.y dependencies on
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 10 Nov 2009 02:13:13 +0000 (02:13 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 10 Nov 2009 02:13:13 +0000 (02:13 +0000)
yytext.  This is a necessary change if we're going to have a lexer interface
layer that does lookahead, since yytext won't necessarily be in step with
what the grammar thinks is the current token.  yylval and yylloc should
be the only side-variables that we need to manage when doing lookahead.

src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/plpgsql.h
src/pl/plpgsql/src/scan.l

index fd4d3e6d784f6e5e60c047f5f9bea8451b3cb40c..ec269a88c55f62d51916c9d5a87d3b79645df681 100644 (file)
@@ -8,13 +8,14 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.133 2009/11/09 00:26:55 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.134 2009/11/10 02:13:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "plpgsql.h"
 
+#include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "parser/parser.h"
 #include "parser/parse_type.h"
@@ -60,7 +61,12 @@ typedef struct
 
 #define parser_errposition(pos)  plpgsql_scanner_errposition(pos)
 
-static PLpgSQL_expr        *read_sql_construct(int until,
+union YYSTYPE;                 /* need forward reference for tok_is_keyword */
+
+static bool            tok_is_keyword(int token, union YYSTYPE *lval,
+                                      const char *keyword);
+static void            token_is_not_variable(int tok);
+static PLpgSQL_expr    *read_sql_construct(int until,
                                            int until2,
                                            int until3,
                                            const char *expected,
@@ -69,7 +75,7 @@ static PLpgSQL_expr       *read_sql_construct(int until,
                                            bool valid_sql,
                                            int *startloc,
                                            int *endtoken);
-static PLpgSQL_expr        *read_sql_expression2(int until, int until2,
+static PLpgSQL_expr    *read_sql_expression2(int until, int until2,
                                              const char *expected,
                                              int *endtoken);
 static PLpgSQL_expr    *read_sql_stmt(const char *sqlstart);
@@ -83,27 +89,27 @@ static  PLpgSQL_stmt    *make_return_next_stmt(int location);
 static PLpgSQL_stmt    *make_return_query_stmt(int location);
 static  PLpgSQL_stmt   *make_case(int location, PLpgSQL_expr *t_expr,
                                   List *case_when_list, List *else_stmts);
+static char            *NameOfDatum(PLwdatum *wdatum);
 static void             check_assignable(PLpgSQL_datum *datum, int location);
 static void             read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row,
                                          bool *strict);
-static PLpgSQL_row     *read_into_scalar_list(const char *initial_name,
+static PLpgSQL_row     *read_into_scalar_list(char *initial_name,
                                               PLpgSQL_datum *initial_datum,
                                               int initial_location);
-static PLpgSQL_row     *make_scalar_list1(const char *initial_name,
+static PLpgSQL_row     *make_scalar_list1(char *initial_name,
                                           PLpgSQL_datum *initial_datum,
                                           int lineno, int location);
 static void             check_sql_expr(const char *stmt, int location,
                                        int leaderlen);
 static void             plpgsql_sql_error_callback(void *arg);
-static PLpgSQL_type        *parse_datatype(const char *string, int location);
+static PLpgSQL_type    *parse_datatype(const char *string, int location);
 static char            *parse_string_token(const char *token, int location);
-static char            *check_label(const char *yytxt);
 static void             check_labels(const char *start_label,
                                      const char *end_label,
                                      int end_location);
-static PLpgSQL_expr    *read_cursor_args(PLpgSQL_var *cursor,
+static PLpgSQL_expr    *read_cursor_args(PLpgSQL_var *cursor,
                                          int until, const char *expected);
-static List                *read_raise_options(void);
+static List            *read_raise_options(void);
 
 %}
 
@@ -112,9 +118,15 @@ static List                *read_raise_options(void);
 %locations
 
 %union {
-       int32                   ival;
-       bool                    boolean;
+       /* these fields must match core_YYSTYPE: */
+       int                     ival;
        char                    *str;
+       const char              *keyword;
+
+       PLword                  word;
+       PLcword                 cword;
+       PLwdatum                wdatum;
+       bool                    boolean;
        struct
        {
            char *name;
@@ -176,7 +188,7 @@ static List             *read_raise_options(void);
 %type <forvariable>    for_variable
 %type <stmt>   for_control
 
-%type <str>        any_identifier any_name opt_block_label opt_label
+%type <str>        any_identifier opt_block_label opt_label
 
 %type <list>   proc_sect proc_stmts stmt_else
 %type <loop_body>  loop_body
@@ -197,14 +209,38 @@ static List               *read_raise_options(void);
 
 %type <list>   getdiag_list
 %type <diagitem> getdiag_list_item
-%type <ival>   getdiag_kind getdiag_target
+%type <ival>   getdiag_item getdiag_target
 
 %type <ival>   opt_scrollable
 %type <fetch>   opt_fetch_direction
 
-       /*
-        * Keyword tokens
-        */
+/*
+ * Basic non-keyword token types.  These are hard-wired into the core lexer.
+ * They must be listed first so that their numeric codes do not depend on
+ * the set of keywords.  Keep this list in sync with backend/parser/gram.y!
+ *
+ * Some of these are not directly referenced in this file, but they must be
+ * here anyway.
+ */
+%token <str>   IDENT FCONST SCONST BCONST XCONST Op
+%token <ival>  ICONST PARAM
+%token         TYPECAST DOT_DOT COLON_EQUALS
+
+/*
+ * Other tokens recognized by plpgsql's lexer interface layer.
+ */
+%token             T_STRING
+%token             T_NUMBER
+%token <word>      T_WORD      /* unrecognized simple identifier */
+%token <cword>     T_CWORD     /* unrecognized composite identifier */
+%token <wdatum>        T_DATUM     /* a VAR, ROW, REC, or RECFIELD variable */
+
+%token O_OPTION
+%token O_DUMP
+
+/*
+ * Keyword tokens
+ */
 %token K_ALIAS
 %token K_ALL
 %token K_ASSIGN
@@ -242,33 +278,16 @@ static List               *read_raise_options(void);
 %token K_OPEN
 %token K_OR
 %token K_PERFORM
-%token K_ROW_COUNT
 %token K_RAISE
-%token K_RESULT_OID
 %token K_RETURN
-%token K_REVERSE
 %token K_SCROLL
 %token K_STRICT
 %token K_THEN
 %token K_TO
-%token K_TYPE
 %token K_USING
 %token K_WHEN
 %token K_WHILE
 
-       /*
-        * Other tokens
-        */
-%token T_STRING
-%token T_NUMBER
-%token T_DATUM                 /* a VAR, ROW, REC, or RECFIELD variable */
-%token T_WORD                  /* unrecognized simple identifier */
-%token T_DBLWORD               /* unrecognized ident.ident */
-%token T_TRIPWORD              /* unrecognized ident.ident.ident */
-
-%token O_OPTION
-%token O_DUMP
-
 %%
 
 pl_function        : comp_optsect pl_block opt_semi
@@ -362,7 +381,7 @@ decl_stmts      : decl_stmts decl_stmt
                    {   $$ = $1;    }
                ;
 
-decl_stmt      : '<' '<' any_name '>' '>'
+decl_stmt      : '<' '<' any_identifier '>' '>'
                    {   $$ = $3;    }
                | K_DECLARE
                    {   $$ = NULL;  }
@@ -540,62 +559,57 @@ decl_is_for       :   K_IS |      /* Oracle */
 
 decl_aliasitem : T_WORD
                    {
-                       char    *name[1];
                        PLpgSQL_nsitem *nsi;
 
-                       plpgsql_convert_ident(yytext, name, 1);
-
                        nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
-                                               name[0], NULL, NULL,
+                                               $1.ident, NULL, NULL,
                                                NULL);
                        if (nsi == NULL)
                            ereport(ERROR,
                                    (errcode(ERRCODE_UNDEFINED_OBJECT),
                                     errmsg("variable \"%s\" does not exist",
-                                           name[0]),
+                                           $1.ident),
                                     parser_errposition(@1)));
-
-                       pfree(name[0]);
-
                        $$ = nsi;
                    }
-               | T_DBLWORD
+               | T_CWORD
                    {
-                       char    *name[2];
                        PLpgSQL_nsitem *nsi;
 
-                       plpgsql_convert_ident(yytext, name, 2);
-
-                       nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
-                                               name[0], name[1], NULL,
-                                               NULL);
+                       if (list_length($1.idents) == 2)
+                           nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+                                                   strVal(linitial($1.idents)),
+                                                   strVal(lsecond($1.idents)),
+                                                   NULL,
+                                                   NULL);
+                       else if (list_length($1.idents) == 3)
+                           nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+                                                   strVal(linitial($1.idents)),
+                                                   strVal(lsecond($1.idents)),
+                                                   strVal(lthird($1.idents)),
+                                                   NULL);
+                       else
+                           nsi = NULL;
                        if (nsi == NULL)
                            ereport(ERROR,
                                    (errcode(ERRCODE_UNDEFINED_OBJECT),
-                                    errmsg("variable \"%s.%s\" does not exist",
-                                           name[0], name[1]),
+                                    errmsg("variable \"%s\" does not exist",
+                                           NameListToString($1.idents)),
                                     parser_errposition(@1)));
-
-                       pfree(name[0]);
-                       pfree(name[1]);
-
                        $$ = nsi;
                    }
                ;
 
 decl_varname   : T_WORD
                    {
-                       char    *name;
-
-                       plpgsql_convert_ident(yytext, &name, 1);
-                       $$.name = name;
+                       $$.name = $1.ident;
                        $$.lineno = plpgsql_location_to_lineno(@1);
                        /*
                         * Check to make sure name isn't already declared
                         * in the current block.
                         */
                        if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
-                                             name, NULL, NULL,
+                                             $1.ident, NULL, NULL,
                                              NULL) != NULL)
                            yyerror("duplicate declaration");
                    }
@@ -748,7 +762,7 @@ getdiag_list : getdiag_list ',' getdiag_list_item
                    }
                ;
 
-getdiag_list_item : getdiag_target K_ASSIGN getdiag_kind
+getdiag_list_item : getdiag_target K_ASSIGN getdiag_item
                    {
                        PLpgSQL_diag_item *new;
 
@@ -760,44 +774,48 @@ getdiag_list_item : getdiag_target K_ASSIGN getdiag_kind
                    }
                ;
 
-getdiag_kind : K_ROW_COUNT
+getdiag_item :
                    {
-                       $$ = PLPGSQL_GETDIAG_ROW_COUNT;
-                   }
-               | K_RESULT_OID
-                   {
-                       $$ = PLPGSQL_GETDIAG_RESULT_OID;
+                       int tok = yylex();
+
+                       if (tok_is_keyword(tok, &yylval, "row_count"))
+                           $$ = PLPGSQL_GETDIAG_ROW_COUNT;
+                       else if (tok_is_keyword(tok, &yylval, "result_oid"))
+                           $$ = PLPGSQL_GETDIAG_RESULT_OID;
+                       else
+                           yyerror("unrecognized GET DIAGNOSTICS item");
                    }
                ;
 
 getdiag_target : T_DATUM
                    {
-                       check_assignable(yylval.datum, @1);
-                       if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW ||
-                           yylval.datum->dtype == PLPGSQL_DTYPE_REC)
+                       check_assignable($1.datum, @1);
+                       if ($1.datum->dtype == PLPGSQL_DTYPE_ROW ||
+                           $1.datum->dtype == PLPGSQL_DTYPE_REC)
                            ereport(ERROR,
                                    (errcode(ERRCODE_SYNTAX_ERROR),
                                     errmsg("\"%s\" is not a scalar variable",
-                                           yytext),
+                                           NameOfDatum(&($1))),
                                     parser_errposition(@1)));
-                       $$ = yylval.datum->dno;
+                       $$ = $1.datum->dno;
                    }
                | T_WORD
                    {
                        /* just to give a better message than "syntax error" */
-                       ereport(ERROR,
-                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                errmsg("\"%s\" is not a known variable",
-                                       yytext),
-                                parser_errposition(@1)));
+                       token_is_not_variable(T_WORD);
+                   }
+               | T_CWORD
+                   {
+                       /* just to give a better message than "syntax error" */
+                       token_is_not_variable(T_CWORD);
                    }
                ;
 
 
 assign_var     : T_DATUM
                    {
-                       check_assignable(yylval.datum, @1);
-                       $$ = yylval.datum->dno;
+                       check_assignable($1.datum, @1);
+                       $$ = $1.datum->dno;
                    }
                | assign_var '[' expr_until_rightbracket
                    {
@@ -1057,13 +1075,12 @@ for_control     : for_variable K_IN
                            $$ = (PLpgSQL_stmt *) new;
                        }
                        else if (tok == T_DATUM &&
-                                yylval.datum->dtype == PLPGSQL_DTYPE_VAR &&
-                                ((PLpgSQL_var *) yylval.datum)->datatype->typoid == REFCURSOROID)
+                                yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR &&
+                                ((PLpgSQL_var *) yylval.wdatum.datum)->datatype->typoid == REFCURSOROID)
                        {
                            /* It's FOR var IN cursor */
                            PLpgSQL_stmt_forc   *new;
-                           PLpgSQL_var         *cursor = (PLpgSQL_var *) yylval.datum;
-                           char                *varname;
+                           PLpgSQL_var         *cursor = (PLpgSQL_var *) yylval.wdatum.datum;
 
                            new = (PLpgSQL_stmt_forc *) palloc0(sizeof(PLpgSQL_stmt_forc));
                            new->cmd_type = PLPGSQL_STMT_FORC;
@@ -1089,8 +1106,7 @@ for_control       : for_variable K_IN
                                                             "LOOP");
 
                            /* create loop's private RECORD variable */
-                           plpgsql_convert_ident($1.name, &varname, 1);
-                           new->rec = plpgsql_build_record(varname,
+                           new->rec = plpgsql_build_record($1.name,
                                                            $1.lineno,
                                                            true);
 
@@ -1114,7 +1130,7 @@ for_control       : for_variable K_IN
                             * keyword, which means it must be an
                             * integer loop.
                             */
-                           if (tok == K_REVERSE)
+                           if (tok_is_keyword(tok, &yylval, "reverse"))
                                reverse = true;
                            else
                                plpgsql_push_back_token(tok);
@@ -1143,7 +1159,6 @@ for_control       : for_variable K_IN
                                PLpgSQL_expr        *expr_by;
                                PLpgSQL_var         *fvar;
                                PLpgSQL_stmt_fori   *new;
-                               char                *varname;
 
                                /* Check first expression is well-formed */
                                check_sql_expr(expr1->query, expr1loc, 7);
@@ -1168,9 +1183,8 @@ for_control       : for_variable K_IN
                                             parser_errposition(@1)));
 
                                /* create loop's private variable */
-                               plpgsql_convert_ident($1.name, &varname, 1);
                                fvar = (PLpgSQL_var *)
-                                   plpgsql_build_variable(varname,
+                                   plpgsql_build_variable($1.name,
                                                           $1.lineno,
                                                           plpgsql_build_datatype(INT4OID,
                                                                                  -1),
@@ -1264,25 +1278,25 @@ for_control     : for_variable K_IN
  */
 for_variable   : T_DATUM
                    {
-                       $$.name = pstrdup(yytext);
+                       $$.name = NameOfDatum(&($1));
                        $$.lineno = plpgsql_location_to_lineno(@1);
-                       if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW)
+                       if ($1.datum->dtype == PLPGSQL_DTYPE_ROW)
                        {
                            $$.scalar = NULL;
                            $$.rec = NULL;
-                           $$.row = (PLpgSQL_row *) yylval.datum;
+                           $$.row = (PLpgSQL_row *) $1.datum;
                        }
-                       else if (yylval.datum->dtype == PLPGSQL_DTYPE_REC)
+                       else if ($1.datum->dtype == PLPGSQL_DTYPE_REC)
                        {
                            $$.scalar = NULL;
-                           $$.rec = (PLpgSQL_rec *) yylval.datum;
+                           $$.rec = (PLpgSQL_rec *) $1.datum;
                            $$.row = NULL;
                        }
                        else
                        {
                            int         tok;
 
-                           $$.scalar = yylval.datum;
+                           $$.scalar = $1.datum;
                            $$.rec = NULL;
                            $$.row = NULL;
                            /* check for comma-separated list */
@@ -1298,7 +1312,7 @@ for_variable  : T_DATUM
                    {
                        int         tok;
 
-                       $$.name = pstrdup(yytext);
+                       $$.name = $1.ident;
                        $$.lineno = plpgsql_location_to_lineno(@1);
                        $$.scalar = NULL;
                        $$.rec = NULL;
@@ -1307,11 +1321,19 @@ for_variable    : T_DATUM
                        tok = yylex();
                        plpgsql_push_back_token(tok);
                        if (tok == ',')
+                       {
+                           /* can't use token_is_not_variable here */
                            ereport(ERROR,
                                    (errcode(ERRCODE_SYNTAX_ERROR),
                                     errmsg("\"%s\" is not a known variable",
-                                           $$.name),
+                                           $1.ident),
                                     parser_errposition(@1)));
+                       }
+                   }
+               | T_CWORD
+                   {
+                       /* just to give a better message than "syntax error" */
+                       token_is_not_variable(T_CWORD);
                    }
                ;
 
@@ -1348,16 +1370,11 @@ stmt_return     : K_RETURN
                        if (tok == 0)
                            yyerror("unexpected end of function definition");
 
-                       /*
-                        * To avoid making NEXT and QUERY effectively be
-                        * reserved words within plpgsql, recognize them
-                        * via yytext.
-                        */
-                       if (pg_strcasecmp(yytext, "next") == 0)
+                       if (tok_is_keyword(tok, &yylval, "next"))
                        {
                            $$ = make_return_next_stmt(@1);
                        }
-                       else if (pg_strcasecmp(yytext, "query") == 0)
+                       else if (tok_is_keyword(tok, &yylval, "query"))
                        {
                            $$ = make_return_query_stmt(@1);
                        }
@@ -1396,35 +1413,33 @@ stmt_raise      : K_RAISE
                        {
                            /*
                             * First is an optional elog severity level.
-                            * Most of these are not plpgsql keywords,
-                            * so we rely on examining yytext.
                             */
-                           if (pg_strcasecmp(yytext, "exception") == 0)
+                           if (tok == K_EXCEPTION)
                            {
                                new->elog_level = ERROR;
                                tok = yylex();
                            }
-                           else if (pg_strcasecmp(yytext, "warning") == 0)
+                           else if (tok_is_keyword(tok, &yylval, "warning"))
                            {
                                new->elog_level = WARNING;
                                tok = yylex();
                            }
-                           else if (pg_strcasecmp(yytext, "notice") == 0)
+                           else if (tok_is_keyword(tok, &yylval, "notice"))
                            {
                                new->elog_level = NOTICE;
                                tok = yylex();
                            }
-                           else if (pg_strcasecmp(yytext, "info") == 0)
+                           else if (tok_is_keyword(tok, &yylval, "info"))
                            {
                                new->elog_level = INFO;
                                tok = yylex();
                            }
-                           else if (pg_strcasecmp(yytext, "log") == 0)
+                           else if (tok_is_keyword(tok, &yylval, "log"))
                            {
                                new->elog_level = LOG;
                                tok = yylex();
                            }
-                           else if (pg_strcasecmp(yytext, "debug") == 0)
+                           else if (tok_is_keyword(tok, &yylval, "debug"))
                            {
                                new->elog_level = DEBUG1;
                                tok = yylex();
@@ -1467,7 +1482,7 @@ stmt_raise        : K_RAISE
                            else if (tok != K_USING)
                            {
                                /* must be condition name or SQLSTATE */
-                               if (pg_strcasecmp(yytext, "sqlstate") == 0)
+                               if (tok_is_keyword(tok, &yylval, "sqlstate"))
                                {
                                    /* next token should be a string literal */
                                    char   *sqlstatestr;
@@ -1484,14 +1499,11 @@ stmt_raise      : K_RAISE
                                }
                                else
                                {
-                                   char   *cname;
-
                                    if (tok != T_WORD)
                                        yyerror("syntax error");
-                                   plpgsql_convert_ident(yytext, &cname, 1);
-                                   plpgsql_recognize_err_condition(cname,
+                                   new->condname = yylval.word.ident;
+                                   plpgsql_recognize_err_condition(new->condname,
                                                                    false);
-                                   new->condname = cname;
                                }
                                tok = yylex();
                                if (tok != ';' && tok != K_USING)
@@ -1515,18 +1527,16 @@ loop_body       : proc_sect K_END K_LOOP opt_label ';'
                ;
 
 /*
- * T_WORD+T_DBLWORD+T_TRIPWORD match any initial identifier that is not a
- * known plpgsql variable.  The latter two cases are probably syntax errors,
- * but we'll let the core parser decide that.
+ * T_WORD+T_CWORD match any initial identifier that is not a known plpgsql
+ * variable.  The composite case is probably a syntax error, but we'll let
+ * the core parser decide that.
  */
 stmt_execsql   : K_INSERT
                    { $$ = make_execsql_stmt(K_INSERT, @1); }
                | T_WORD
                    { $$ = make_execsql_stmt(T_WORD, @1); }
-               | T_DBLWORD
-                   { $$ = make_execsql_stmt(T_DBLWORD, @1); }
-               | T_TRIPWORD
-                   { $$ = make_execsql_stmt(T_TRIPWORD, @1); }
+               | T_CWORD
+                   { $$ = make_execsql_stmt(T_CWORD, @1); }
                ;
 
 stmt_dynexecute : K_EXECUTE
@@ -1605,12 +1615,7 @@ stmt_open        : K_OPEN cursor_variable
                            }
 
                            if (tok != K_FOR)
-                               ereport(ERROR,
-                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                                        errmsg("syntax error at \"%s\"",
-                                               yytext),
-                                        errdetail("Expected \"FOR\", to open a cursor for an unbound cursor variable."),
-                                        parser_errposition(yylloc)));
+                               yyerror("syntax error, expected \"FOR\"");
 
                            tok = yylex();
                            if (tok == K_EXECUTE)
@@ -1705,28 +1710,29 @@ stmt_null       : K_NULL ';'
 
 cursor_variable    : T_DATUM
                    {
-                       if (yylval.datum->dtype != PLPGSQL_DTYPE_VAR)
+                       if ($1.datum->dtype != PLPGSQL_DTYPE_VAR)
                            ereport(ERROR,
                                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                                     errmsg("cursor variable must be a simple variable"),
                                     parser_errposition(@1)));
 
-                       if (((PLpgSQL_var *) yylval.datum)->datatype->typoid != REFCURSOROID)
+                       if (((PLpgSQL_var *) $1.datum)->datatype->typoid != REFCURSOROID)
                            ereport(ERROR,
                                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                                     errmsg("variable \"%s\" must be of type cursor or refcursor",
-                                           ((PLpgSQL_var *) yylval.datum)->refname),
+                                           ((PLpgSQL_var *) $1.datum)->refname),
                                     parser_errposition(@1)));
-                       $$ = (PLpgSQL_var *) yylval.datum;
+                       $$ = (PLpgSQL_var *) $1.datum;
                    }
                | T_WORD
                    {
                        /* just to give a better message than "syntax error" */
-                       ereport(ERROR,
-                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                errmsg("\"%s\" is not a known variable",
-                                       yytext),
-                                parser_errposition(@1)));
+                       token_is_not_variable(T_WORD);
+                   }
+               | T_CWORD
+                   {
+                       /* just to give a better message than "syntax error" */
+                       token_is_not_variable(T_CWORD);
                    }
                ;
 
@@ -1806,7 +1812,7 @@ proc_conditions   : proc_conditions K_OR proc_condition
                        }
                ;
 
-proc_condition : any_name
+proc_condition : any_identifier
                        {
                            if (strcmp($1, "sqlstate") != 0)
                            {
@@ -1863,7 +1869,7 @@ opt_block_label   :
                        plpgsql_ns_push(NULL);
                        $$ = NULL;
                    }
-               | '<' '<' any_name '>' '>'
+               | '<' '<' any_identifier '>' '>'
                    {
                        plpgsql_ns_push($3);
                        $$ = $3;
@@ -1876,7 +1882,9 @@ opt_label :
                    }
                | any_identifier
                    {
-                       $$ = check_label($1);
+                       if (plpgsql_ns_lookup_label(plpgsql_ns_top(), $1) == NULL)
+                           yyerror("label does not exist");
+                       $$ = $1;
                    }
                ;
 
@@ -1891,25 +1899,68 @@ opt_exitcond    : ';'
  */
 any_identifier : T_WORD
                    {
-                       $$ = yytext;
+                       $$ = $1.ident;
                    }
                | T_DATUM
                    {
-                       $$ = yytext;
-                   }
-               ;
-
-any_name       : any_identifier
-                   {
-                       char    *name;
-
-                       plpgsql_convert_ident($1, &name, 1);
-                       $$ = name;
+                       if ($1.ident == NULL) /* composite name not OK */
+                           yyerror("syntax error");
+                       $$ = $1.ident;
                    }
                ;
 
 %%
 
+/*
+ * Check whether a token represents an "unreserved keyword".
+ * We have various places where we want to recognize a keyword in preference
+ * to a variable name, but not reserve that keyword in other contexts.
+ * Hence, this kluge.  CAUTION: don't use this for reserved keywords;
+ * it won't recognize them.
+ */
+static bool
+tok_is_keyword(int token, union YYSTYPE *lval, const char *keyword)
+{
+   if (token == T_WORD)
+   {
+       /* must be unquoted and match the downcased string */
+       if (!lval->word.quoted && strcmp(lval->word.ident, keyword) == 0)
+           return true;
+   }
+   else if (token == T_DATUM)
+   {
+       /* like the T_WORD case, but also reject composite identifiers */
+       /* (hence an unreserved word followed by "." will not be recognized) */
+       if (!lval->word.quoted && lval->word.ident != NULL &&
+           strcmp(lval->word.ident, keyword) == 0)
+           return true;
+   }
+   return false;               /* not the keyword */
+}
+
+/*
+ * Convenience routine to complain when we expected T_DATUM and got
+ * something else.  "tok" must be the current token, since we also
+ * look at yylval and yylloc.
+ */
+static void
+token_is_not_variable(int tok)
+{
+   if (tok == T_WORD)
+       ereport(ERROR,
+               (errcode(ERRCODE_SYNTAX_ERROR),
+                errmsg("\"%s\" is not a known variable",
+                       yylval.word.ident),
+                parser_errposition(yylloc)));
+   else if (tok == T_CWORD)
+       ereport(ERROR,
+               (errcode(ERRCODE_SYNTAX_ERROR),
+                errmsg("\"%s\" is not a known variable",
+                       NameListToString(yylval.cword.idents)),
+                parser_errposition(yylloc)));
+   else
+       yyerror("syntax error");
+}
 
 /* Convenience routine to read an expression with one possible terminator */
 PLpgSQL_expr *
@@ -2066,8 +2117,6 @@ read_datatype(int tok)
    /* Should always be called with LookupIdentifiers off */
    Assert(!plpgsql_LookupIdentifiers);
 
-   initStringInfo(&ds);
-
    /* Often there will be a lookahead token, but if not, get one */
    if (tok == YYEMPTY)
        tok = yylex();
@@ -2075,86 +2124,54 @@ read_datatype(int tok)
    startlocation = yylloc;
 
    /*
-    * If we have a single, double, or triple identifier, check for %TYPE
+    * If we have a simple or composite identifier, check for %TYPE
     * and %ROWTYPE constructs.
     */
    if (tok == T_WORD)
    {
-       appendStringInfoString(&ds, yytext);
+       char   *dtname = yylval.word.ident;
+
        tok = yylex();
        if (tok == '%')
        {
            tok = yylex();
-           if (pg_strcasecmp(yytext, "type") == 0)
+           if (tok_is_keyword(tok, &yylval, "type"))
            {
-               result = plpgsql_parse_wordtype(ds.data);
+               result = plpgsql_parse_wordtype(dtname);
                if (result)
-               {
-                   pfree(ds.data);
                    return result;
-               }
            }
-           else if (pg_strcasecmp(yytext, "rowtype") == 0)
+           else if (tok_is_keyword(tok, &yylval, "rowtype"))
            {
-               result = plpgsql_parse_wordrowtype(ds.data);
+               result = plpgsql_parse_wordrowtype(dtname);
                if (result)
-               {
-                   pfree(ds.data);
                    return result;
-               }
            }
        }
    }
-   else if (tok == T_DBLWORD)
+   else if (tok == T_CWORD)
    {
-       appendStringInfoString(&ds, yytext);
+       List   *dtnames = yylval.cword.idents;
+
        tok = yylex();
        if (tok == '%')
        {
            tok = yylex();
-           if (pg_strcasecmp(yytext, "type") == 0)
-           {
-               result = plpgsql_parse_dblwordtype(ds.data);
-               if (result)
-               {
-                   pfree(ds.data);
-                   return result;
-               }
-           }
-           else if (pg_strcasecmp(yytext, "rowtype") == 0)
+           if (tok_is_keyword(tok, &yylval, "type"))
            {
-               result = plpgsql_parse_dblwordrowtype(ds.data);
+               result = plpgsql_parse_cwordtype(dtnames);
                if (result)
-               {
-                   pfree(ds.data);
                    return result;
-               }
            }
-       }
-   }
-   else if (tok == T_TRIPWORD)
-   {
-       appendStringInfoString(&ds, yytext);
-       tok = yylex();
-       if (tok == '%')
-       {
-           tok = yylex();
-           if (pg_strcasecmp(yytext, "type") == 0)
+           else if (tok_is_keyword(tok, &yylval, "rowtype"))
            {
-               result = plpgsql_parse_tripwordtype(ds.data);
+               result = plpgsql_parse_cwordrowtype(dtnames);
                if (result)
-               {
-                   pfree(ds.data);
                    return result;
-               }
            }
-           /* there's no tripword rowtype construct */
        }
    }
 
-   /* flush temporary usage of ds for rowtype checks */
-   resetStringInfo(&ds);
-
    while (tok != ';')
    {
        if (tok == 0)
@@ -2179,6 +2196,7 @@ read_datatype(int tok)
    }
 
    /* set up ds to contain complete typename text */
+   initStringInfo(&ds);
    plpgsql_append_source_text(&ds, startlocation, yylloc);
    type_name = ds.data;
 
@@ -2313,32 +2331,28 @@ read_fetch_direction(void)
    fetch->expr      = NULL;
    fetch->returns_multiple_rows = false;
 
-   /*
-    * Most of the direction keywords are not plpgsql keywords, so we
-    * rely on examining yytext ...
-    */
    tok = yylex();
    if (tok == 0)
        yyerror("unexpected end of function definition");
 
-   if (pg_strcasecmp(yytext, "next") == 0)
+   if (tok_is_keyword(tok, &yylval, "next"))
    {
        /* use defaults */
    }
-   else if (pg_strcasecmp(yytext, "prior") == 0)
+   else if (tok_is_keyword(tok, &yylval, "prior"))
    {
        fetch->direction = FETCH_BACKWARD;
    }
-   else if (pg_strcasecmp(yytext, "first") == 0)
+   else if (tok_is_keyword(tok, &yylval, "first"))
    {
        fetch->direction = FETCH_ABSOLUTE;
    }
-   else if (pg_strcasecmp(yytext, "last") == 0)
+   else if (tok_is_keyword(tok, &yylval, "last"))
    {
        fetch->direction = FETCH_ABSOLUTE;
        fetch->how_many  = -1;
    }
-   else if (pg_strcasecmp(yytext, "absolute") == 0)
+   else if (tok_is_keyword(tok, &yylval, "absolute"))
    {
        fetch->direction = FETCH_ABSOLUTE;
        fetch->expr = read_sql_expression2(K_FROM, K_IN,
@@ -2346,7 +2360,7 @@ read_fetch_direction(void)
                                           NULL);
        check_FROM = false;
    }
-   else if (pg_strcasecmp(yytext, "relative") == 0)
+   else if (tok_is_keyword(tok, &yylval, "relative"))
    {
        fetch->direction = FETCH_RELATIVE;
        fetch->expr = read_sql_expression2(K_FROM, K_IN,
@@ -2354,16 +2368,16 @@ read_fetch_direction(void)
                                           NULL);
        check_FROM = false;
    }
-   else if (pg_strcasecmp(yytext, "all") == 0)
+   else if (tok == K_ALL)
    {
        fetch->how_many = FETCH_ALL;
        fetch->returns_multiple_rows = true;
    }
-   else if (pg_strcasecmp(yytext, "forward") == 0)
+   else if (tok_is_keyword(tok, &yylval, "forward"))
    {
        complete_direction(fetch, &check_FROM);
    }
-   else if (pg_strcasecmp(yytext, "backward") == 0)
+   else if (tok_is_keyword(tok, &yylval, "backward"))
    {
        fetch->direction = FETCH_BACKWARD;
        complete_direction(fetch, &check_FROM);
@@ -2492,9 +2506,9 @@ make_return_stmt(int location)
                break;
 
            case T_DATUM:
-               if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW ||
-                   yylval.datum->dtype == PLPGSQL_DTYPE_REC)
-                   new->retvarno = yylval.datum->dno;
+               if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
+                   yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
+                   new->retvarno = yylval.wdatum.datum->dno;
                else
                    ereport(ERROR,
                            (errcode(ERRCODE_DATATYPE_MISMATCH),
@@ -2556,9 +2570,9 @@ make_return_next_stmt(int location)
        switch (yylex())
        {
            case T_DATUM:
-               if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW ||
-                   yylval.datum->dtype == PLPGSQL_DTYPE_REC)
-                   new->retvarno = yylval.datum->dno;
+               if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
+                   yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
+                   new->retvarno = yylval.wdatum.datum->dno;
                else
                    ereport(ERROR,
                            (errcode(ERRCODE_DATATYPE_MISMATCH),
@@ -2629,6 +2643,16 @@ make_return_query_stmt(int location)
 }
 
 
+/* convenience routine to fetch the name of a T_DATUM */
+static char *
+NameOfDatum(PLwdatum *wdatum)
+{
+   if (wdatum->ident)
+       return wdatum->ident;
+   Assert(wdatum->idents != NIL);
+   return NameListToString(wdatum->idents);
+}
+
 static void
 check_assignable(PLpgSQL_datum *datum, int location)
 {
@@ -2685,29 +2709,26 @@ read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict)
    switch (tok)
    {
        case T_DATUM:
-           if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW)
+           if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW)
            {
-               check_assignable(yylval.datum, yylloc);
-               *row = (PLpgSQL_row *) yylval.datum;
+               check_assignable(yylval.wdatum.datum, yylloc);
+               *row = (PLpgSQL_row *) yylval.wdatum.datum;
            }
-           else if (yylval.datum->dtype == PLPGSQL_DTYPE_REC)
+           else if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
            {
-               check_assignable(yylval.datum, yylloc);
-               *rec = (PLpgSQL_rec *) yylval.datum;
+               check_assignable(yylval.wdatum.datum, yylloc);
+               *rec = (PLpgSQL_rec *) yylval.wdatum.datum;
            }
            else
            {
-               *row = read_into_scalar_list(yytext, yylval.datum, yylloc);
+               *row = read_into_scalar_list(NameOfDatum(&(yylval.wdatum)),
+                                            yylval.wdatum.datum, yylloc);
            }
            break;
 
        default:
-           ereport(ERROR,
-                   (errcode(ERRCODE_SYNTAX_ERROR),
-                    errmsg("syntax error at \"%s\"", yytext),
-                    errdetail("Expected record variable, row variable, "
-                              "or list of scalar variables following INTO."),
-                    parser_errposition(yylloc)));
+           /* just to give a better message than "syntax error" */
+           token_is_not_variable(tok);
    }
 }
 
@@ -2718,7 +2739,7 @@ read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict)
  * scalars.
  */
 static PLpgSQL_row *
-read_into_scalar_list(const char *initial_name,
+read_into_scalar_list(char *initial_name,
                      PLpgSQL_datum *initial_datum,
                      int initial_location)
 {
@@ -2729,7 +2750,7 @@ read_into_scalar_list(const char *initial_name,
    int              tok;
 
    check_assignable(initial_datum, initial_location);
-   fieldnames[0] = pstrdup(initial_name);
+   fieldnames[0] = initial_name;
    varnos[0]     = initial_datum->dno;
    nfields       = 1;
 
@@ -2746,24 +2767,21 @@ read_into_scalar_list(const char *initial_name,
        switch (tok)
        {
            case T_DATUM:
-               check_assignable(yylval.datum, yylloc);
-               if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW ||
-                   yylval.datum->dtype == PLPGSQL_DTYPE_REC)
+               check_assignable(yylval.wdatum.datum, yylloc);
+               if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
+                   yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
                    ereport(ERROR,
                            (errcode(ERRCODE_SYNTAX_ERROR),
                             errmsg("\"%s\" is not a scalar variable",
-                                   yytext),
+                                   NameOfDatum(&(yylval.wdatum))),
                             parser_errposition(yylloc)));
-               fieldnames[nfields] = pstrdup(yytext);
-               varnos[nfields++]   = yylval.datum->dno;
+               fieldnames[nfields] = NameOfDatum(&(yylval.wdatum));
+               varnos[nfields++]   = yylval.wdatum.datum->dno;
                break;
 
            default:
-               ereport(ERROR,
-                       (errcode(ERRCODE_SYNTAX_ERROR),
-                        errmsg("\"%s\" is not a known variable",
-                               yytext),
-                        parser_errposition(yylloc)));
+               /* just to give a better message than "syntax error" */
+               token_is_not_variable(tok);
        }
    }
 
@@ -2800,7 +2818,7 @@ read_into_scalar_list(const char *initial_name,
  * have it at hand already, we may as well pass it in.
  */
 static PLpgSQL_row *
-make_scalar_list1(const char *initial_name,
+make_scalar_list1(char *initial_name,
                  PLpgSQL_datum *initial_datum,
                  int lineno, int location)
 {
@@ -2816,7 +2834,7 @@ make_scalar_list1(const char *initial_name,
    row->nfields = 1;
    row->fieldnames = palloc(sizeof(char *));
    row->varnos = palloc(sizeof(int));
-   row->fieldnames[0] = pstrdup(initial_name);
+   row->fieldnames[0] = initial_name;
    row->varnos[0] = initial_datum->dno;
 
    plpgsql_adddatum((PLpgSQL_datum *)row);
@@ -2968,17 +2986,9 @@ parse_string_token(const char *token, int location)
    return result;
 }
 
-static char *
-check_label(const char *yytxt)
-{
-   char       *label_name;
-
-   plpgsql_convert_ident(yytxt, &label_name, 1);
-   if (plpgsql_ns_lookup_label(plpgsql_ns_top(), label_name) == NULL)
-       yyerror("label does not exist");
-   return label_name;
-}
-
+/*
+ * Check block starting and ending labels match.
+ */
 static void
 check_labels(const char *start_label, const char *end_label, int end_location)
 {
@@ -3070,20 +3080,16 @@ read_raise_options(void)
 
        opt = (PLpgSQL_raise_option *) palloc(sizeof(PLpgSQL_raise_option));
 
-       if (pg_strcasecmp(yytext, "errcode") == 0)
+       if (tok_is_keyword(tok, &yylval, "errcode"))
            opt->opt_type = PLPGSQL_RAISEOPTION_ERRCODE;
-       else if (pg_strcasecmp(yytext, "message") == 0)
+       else if (tok_is_keyword(tok, &yylval, "message"))
            opt->opt_type = PLPGSQL_RAISEOPTION_MESSAGE;
-       else if (pg_strcasecmp(yytext, "detail") == 0)
+       else if (tok_is_keyword(tok, &yylval, "detail"))
            opt->opt_type = PLPGSQL_RAISEOPTION_DETAIL;
-       else if (pg_strcasecmp(yytext, "hint") == 0)
+       else if (tok_is_keyword(tok, &yylval, "hint"))
            opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
        else
-           ereport(ERROR,
-                   (errcode(ERRCODE_SYNTAX_ERROR),
-                    errmsg("unrecognized RAISE statement option \"%s\"",
-                           yytext),
-                    parser_errposition(yylloc)));
+           yyerror("unrecognized RAISE statement option");
 
        if (yylex() != K_ASSIGN)
            yyerror("syntax error, expected \"=\"");
index a394eace606644f17fdbb05465b2b341cf180df2..2f69a647f8efc400373947f529ac11d480dd0bd6 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.143 2009/11/09 00:26:55 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.144 2009/11/10 02:13:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1250,26 +1250,33 @@ plpgsql_parse_word(const char *word)
    /* Do case conversion and word separation */
    plpgsql_convert_ident(word, cp, 1);
 
-   /*
-    * Do a lookup in the current namespace stack
-    */
-   nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
-                           cp[0], NULL, NULL,
-                           NULL);
-   pfree(cp[0]);
-
-   if (nse != NULL)
+   /* No lookup if disabled */
+   if (plpgsql_LookupIdentifiers)
    {
-       switch (nse->itemtype)
+       /*
+        * Do a lookup in the current namespace stack
+        */
+       nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+                               cp[0], NULL, NULL,
+                               NULL);
+
+       if (nse != NULL)
        {
-           case PLPGSQL_NSTYPE_VAR:
-           case PLPGSQL_NSTYPE_ROW:
-           case PLPGSQL_NSTYPE_REC:
-               plpgsql_yylval.datum = plpgsql_Datums[nse->itemno];
-               return T_DATUM;
+           switch (nse->itemtype)
+           {
+               case PLPGSQL_NSTYPE_VAR:
+               case PLPGSQL_NSTYPE_ROW:
+               case PLPGSQL_NSTYPE_REC:
+                   plpgsql_yylval.wdatum.datum = plpgsql_Datums[nse->itemno];
+                   plpgsql_yylval.wdatum.ident = cp[0];
+                   plpgsql_yylval.wdatum.quoted = (word[0] == '"');
+                   plpgsql_yylval.wdatum.idents = NIL;
+                   return T_DATUM;
 
-           default:
-               elog(ERROR, "unrecognized plpgsql itemtype: %d", nse->itemtype);
+               default:
+                   elog(ERROR, "unrecognized plpgsql itemtype: %d",
+                        nse->itemtype);
+           }
        }
    }
 
@@ -1277,6 +1284,8 @@ plpgsql_parse_word(const char *word)
     * Nothing found - up to now it's a word without any special meaning for
     * us.
     */
+   plpgsql_yylval.word.ident = cp[0];
+   plpgsql_yylval.word.quoted = (word[0] == '"');
    return T_WORD;
 }
 
@@ -1291,107 +1300,111 @@ plpgsql_parse_dblword(const char *word)
 {
    PLpgSQL_nsitem *ns;
    char       *cp[2];
+   List       *idents;
    int         nnames;
 
    /* Do case conversion and word separation */
    plpgsql_convert_ident(word, cp, 2);
 
-   /*
-    * Do a lookup in the current namespace stack
-    */
-   ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
-                          cp[0], cp[1], NULL,
-                          &nnames);
-   if (ns == NULL)
-   {
-       pfree(cp[0]);
-       pfree(cp[1]);
-       return T_DBLWORD;
-   }
+   idents = list_make2(makeString(cp[0]),
+                       makeString(cp[1]));
 
-   switch (ns->itemtype)
+   /* No lookup if disabled */
+   if (plpgsql_LookupIdentifiers)
    {
-       case PLPGSQL_NSTYPE_VAR:
-           /* Block-qualified reference to scalar variable. */
-           plpgsql_yylval.datum = plpgsql_Datums[ns->itemno];
-           pfree(cp[0]);
-           pfree(cp[1]);
-           return T_DATUM;
-
-       case PLPGSQL_NSTYPE_REC:
-           if (nnames == 1)
+       /*
+        * Do a lookup in the current namespace stack
+        */
+       ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+                              cp[0], cp[1], NULL,
+                              &nnames);
+       if (ns != NULL)
+       {
+           switch (ns->itemtype)
            {
-               /*
-                * First word is a record name, so second word must be a field
-                * in this record.
-                */
-               PLpgSQL_recfield *new;
-
-               new = palloc(sizeof(PLpgSQL_recfield));
-               new->dtype = PLPGSQL_DTYPE_RECFIELD;
-               new->fieldname = pstrdup(cp[1]);
-               new->recparentno = ns->itemno;
-
-               plpgsql_adddatum((PLpgSQL_datum *) new);
+               case PLPGSQL_NSTYPE_VAR:
+                   /* Block-qualified reference to scalar variable. */
+                   plpgsql_yylval.wdatum.datum = plpgsql_Datums[ns->itemno];
+                   plpgsql_yylval.wdatum.ident = NULL;
+                   plpgsql_yylval.wdatum.quoted = false; /* not used */
+                   plpgsql_yylval.wdatum.idents = idents;
+                   return T_DATUM;
+
+               case PLPGSQL_NSTYPE_REC:
+                   if (nnames == 1)
+                   {
+                       /*
+                        * First word is a record name, so second word must be
+                        * a field in this record.
+                        */
+                       PLpgSQL_recfield *new;
 
-               plpgsql_yylval.datum = (PLpgSQL_datum *) new;
+                       new = palloc(sizeof(PLpgSQL_recfield));
+                       new->dtype = PLPGSQL_DTYPE_RECFIELD;
+                       new->fieldname = pstrdup(cp[1]);
+                       new->recparentno = ns->itemno;
 
-               pfree(cp[0]);
-               pfree(cp[1]);
-               return T_DATUM;
-           }
-           else
-           {
-               /* Block-qualified reference to record variable. */
-               plpgsql_yylval.datum = plpgsql_Datums[ns->itemno];
-               pfree(cp[0]);
-               pfree(cp[1]);
-               return T_DATUM;
-           }
+                       plpgsql_adddatum((PLpgSQL_datum *) new);
 
-       case PLPGSQL_NSTYPE_ROW:
-           if (nnames == 1)
-           {
-               /*
-                * First word is a row name, so second word must be a field in
-                * this row.
-                */
-               PLpgSQL_row *row;
-               int         i;
+                       plpgsql_yylval.wdatum.datum = (PLpgSQL_datum *) new;
+                   }
+                   else
+                   {
+                       /* Block-qualified reference to record variable. */
+                       plpgsql_yylval.wdatum.datum = plpgsql_Datums[ns->itemno];
+                   }
+                   plpgsql_yylval.wdatum.ident = NULL;
+                   plpgsql_yylval.wdatum.quoted = false; /* not used */
+                   plpgsql_yylval.wdatum.idents = idents;
+                   return T_DATUM;
 
-               row = (PLpgSQL_row *) (plpgsql_Datums[ns->itemno]);
-               for (i = 0; i < row->nfields; i++)
-               {
-                   if (row->fieldnames[i] &&
-                       strcmp(row->fieldnames[i], cp[1]) == 0)
+               case PLPGSQL_NSTYPE_ROW:
+                   if (nnames == 1)
+                   {
+                       /*
+                        * First word is a row name, so second word must be a
+                        * field in this row.
+                        */
+                       PLpgSQL_row *row;
+                       int         i;
+
+                       row = (PLpgSQL_row *) (plpgsql_Datums[ns->itemno]);
+                       for (i = 0; i < row->nfields; i++)
+                       {
+                           if (row->fieldnames[i] &&
+                               strcmp(row->fieldnames[i], cp[1]) == 0)
+                           {
+                               plpgsql_yylval.wdatum.datum = plpgsql_Datums[row->varnos[i]];
+                               plpgsql_yylval.wdatum.ident = NULL;
+                               plpgsql_yylval.wdatum.quoted = false; /* not used */
+                               plpgsql_yylval.wdatum.idents = idents;
+                               return T_DATUM;
+                           }
+                       }
+                       ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                errmsg("row \"%s\" has no field \"%s\"",
+                                       cp[0], cp[1])));
+                   }
+                   else
                    {
-                       plpgsql_yylval.datum = plpgsql_Datums[row->varnos[i]];
-                       pfree(cp[0]);
-                       pfree(cp[1]);
+                       /* Block-qualified reference to row variable. */
+                       plpgsql_yylval.wdatum.datum = plpgsql_Datums[ns->itemno];
+                       plpgsql_yylval.wdatum.ident = NULL;
+                       plpgsql_yylval.wdatum.quoted = false; /* not used */
+                       plpgsql_yylval.wdatum.idents = idents;
                        return T_DATUM;
                    }
-               }
-               ereport(ERROR,
-                       (errcode(ERRCODE_UNDEFINED_COLUMN),
-                        errmsg("row \"%s\" has no field \"%s\"",
-                               cp[0], cp[1])));
-           }
-           else
-           {
-               /* Block-qualified reference to row variable. */
-               plpgsql_yylval.datum = plpgsql_Datums[ns->itemno];
-               pfree(cp[0]);
-               pfree(cp[1]);
-               return T_DATUM;
-           }
 
-       default:
-           break;
+               default:
+                   break;
+           }
+       }
    }
 
-   pfree(cp[0]);
-   pfree(cp[1]);
-   return T_DBLWORD;
+   /* Nothing found */
+   plpgsql_yylval.cword.idents = idents;
+   return T_CWORD;
 }
 
 
@@ -1405,90 +1418,89 @@ plpgsql_parse_tripword(const char *word)
 {
    PLpgSQL_nsitem *ns;
    char       *cp[3];
+   List       *idents;
    int         nnames;
 
    /* Do case conversion and word separation */
    plpgsql_convert_ident(word, cp, 3);
 
-   /*
-    * Do a lookup in the current namespace stack. Must find a qualified
-    * reference.
-    */
-   ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
-                          cp[0], cp[1], cp[2],
-                          &nnames);
-   if (ns == NULL || nnames != 2)
-   {
-       pfree(cp[0]);
-       pfree(cp[1]);
-       pfree(cp[2]);
-       return T_TRIPWORD;
-   }
+   idents = list_make3(makeString(cp[0]),
+                       makeString(cp[1]),
+                       makeString(cp[2]));
 
-   switch (ns->itemtype)
+   /* No lookup if disabled */
+   if (plpgsql_LookupIdentifiers)
    {
-       case PLPGSQL_NSTYPE_REC:
-           {
-               /*
-                * words 1/2 are a record name, so third word must be a field
-                * in this record.
-                */
-               PLpgSQL_recfield *new;
-
-               new = palloc(sizeof(PLpgSQL_recfield));
-               new->dtype = PLPGSQL_DTYPE_RECFIELD;
-               new->fieldname = pstrdup(cp[2]);
-               new->recparentno = ns->itemno;
-
-               plpgsql_adddatum((PLpgSQL_datum *) new);
-
-               plpgsql_yylval.datum = (PLpgSQL_datum *) new;
-
-               pfree(cp[0]);
-               pfree(cp[1]);
-               pfree(cp[2]);
-
-               return T_DATUM;
-           }
-
-       case PLPGSQL_NSTYPE_ROW:
+       /*
+        * Do a lookup in the current namespace stack. Must find a qualified
+        * reference, else ignore.
+        */
+       ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+                              cp[0], cp[1], cp[2],
+                              &nnames);
+       if (ns != NULL && nnames == 2)
+       {
+           switch (ns->itemtype)
            {
-               /*
-                * words 1/2 are a row name, so third word must be a field in
-                * this row.
-                */
-               PLpgSQL_row *row;
-               int         i;
+               case PLPGSQL_NSTYPE_REC:
+               {
+                   /*
+                    * words 1/2 are a record name, so third word must be a
+                    * field in this record.
+                    */
+                   PLpgSQL_recfield *new;
+
+                   new = palloc(sizeof(PLpgSQL_recfield));
+                   new->dtype = PLPGSQL_DTYPE_RECFIELD;
+                   new->fieldname = pstrdup(cp[2]);
+                   new->recparentno = ns->itemno;
+
+                   plpgsql_adddatum((PLpgSQL_datum *) new);
+
+                   plpgsql_yylval.wdatum.datum = (PLpgSQL_datum *) new;
+                   plpgsql_yylval.wdatum.ident = NULL;
+                   plpgsql_yylval.wdatum.quoted = false; /* not used */
+                   plpgsql_yylval.wdatum.idents = idents;
+                   return T_DATUM;
+               }
 
-               row = (PLpgSQL_row *) (plpgsql_Datums[ns->itemno]);
-               for (i = 0; i < row->nfields; i++)
+               case PLPGSQL_NSTYPE_ROW:
                {
-                   if (row->fieldnames[i] &&
-                       strcmp(row->fieldnames[i], cp[2]) == 0)
+                   /*
+                    * words 1/2 are a row name, so third word must be a field
+                    * in this row.
+                    */
+                   PLpgSQL_row *row;
+                   int         i;
+
+                   row = (PLpgSQL_row *) (plpgsql_Datums[ns->itemno]);
+                   for (i = 0; i < row->nfields; i++)
                    {
-                       plpgsql_yylval.datum = plpgsql_Datums[row->varnos[i]];
-
-                       pfree(cp[0]);
-                       pfree(cp[1]);
-                       pfree(cp[2]);
-
-                       return T_DATUM;
+                       if (row->fieldnames[i] &&
+                           strcmp(row->fieldnames[i], cp[2]) == 0)
+                       {
+                           plpgsql_yylval.wdatum.datum = plpgsql_Datums[row->varnos[i]];
+                           plpgsql_yylval.wdatum.ident = NULL;
+                           plpgsql_yylval.wdatum.quoted = false; /* not used */
+                           plpgsql_yylval.wdatum.idents = idents;
+                           return T_DATUM;
+                       }
                    }
+                   ereport(ERROR,
+                           (errcode(ERRCODE_UNDEFINED_COLUMN),
+                            errmsg("row \"%s.%s\" has no field \"%s\"",
+                                   cp[0], cp[1], cp[2])));
                }
-               ereport(ERROR,
-                       (errcode(ERRCODE_UNDEFINED_COLUMN),
-                        errmsg("row \"%s.%s\" has no field \"%s\"",
-                               cp[0], cp[1], cp[2])));
-           }
 
-       default:
-           break;
+               default:
+                   break;
+           }
+       }
    }
 
-   pfree(cp[0]);
-   pfree(cp[1]);
-   pfree(cp[2]);
-   return T_TRIPWORD;
+   /* Nothing found */
+   plpgsql_yylval.cword.idents = idents;
+   return T_CWORD;
 }
 
 
@@ -1500,26 +1512,21 @@ plpgsql_parse_tripword(const char *word)
  * ----------
  */
 PLpgSQL_type *
-plpgsql_parse_wordtype(const char *word)
+plpgsql_parse_wordtype(char *ident)
 {
    PLpgSQL_type *dtype;
    PLpgSQL_nsitem *nse;
    HeapTuple   typeTup;
-   char       *cp[1];
-
-   /* Do case conversion and word separation */
-   plpgsql_convert_ident(word, cp, 1);
 
    /*
     * Do a lookup in the current namespace stack
     */
    nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
-                           cp[0], NULL, NULL,
+                           ident, NULL, NULL,
                            NULL);
 
    if (nse != NULL)
    {
-       pfree(cp[0]);
        switch (nse->itemtype)
        {
            case PLPGSQL_NSTYPE_VAR:
@@ -1536,7 +1543,7 @@ plpgsql_parse_wordtype(const char *word)
     * Word wasn't found in the namespace stack. Try to find a data type
     * with that name, but ignore shell types and complex types.
     */
-   typeTup = LookupTypeName(NULL, makeTypeName(cp[0]), NULL);
+   typeTup = LookupTypeName(NULL, makeTypeName(ident), NULL);
    if (typeTup)
    {
        Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
@@ -1545,14 +1552,12 @@ plpgsql_parse_wordtype(const char *word)
            typeStruct->typrelid != InvalidOid)
        {
            ReleaseSysCache(typeTup);
-           pfree(cp[0]);
            return NULL;
        }
 
        dtype = build_datatype(typeTup, -1);
 
        ReleaseSysCache(typeTup);
-       pfree(cp[0]);
        return dtype;
    }
 
@@ -1560,134 +1565,71 @@ plpgsql_parse_wordtype(const char *word)
     * Nothing found - up to now it's a word without any special meaning for
     * us.
     */
-   pfree(cp[0]);
    return NULL;
 }
 
 
 /* ----------
- * plpgsql_parse_dblwordtype       Same lookup for word.word%TYPE
+ * plpgsql_parse_cwordtype     Same lookup for compositeword%TYPE
  * ----------
  */
 PLpgSQL_type *
-plpgsql_parse_dblwordtype(const char *word)
+plpgsql_parse_cwordtype(List *idents)
 {
    PLpgSQL_type *dtype = NULL;
    PLpgSQL_nsitem *nse;
+   const char *fldname;
    Oid         classOid;
    HeapTuple   classtup = NULL;
    HeapTuple   attrtup = NULL;
    HeapTuple   typetup = NULL;
    Form_pg_class classStruct;
    Form_pg_attribute attrStruct;
-   char       *cp[2];
    MemoryContext oldCxt;
 
    /* Avoid memory leaks in the long-term function context */
    oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
 
-   /* Do case conversion and word separation */
-   plpgsql_convert_ident(word, cp, 2);
+   if (list_length(idents) == 2)
+   {
+       /*
+        * Do a lookup in the current namespace stack.
+        * We don't need to check number of names matched, because we will
+        * only consider scalar variables.
+        */
+       nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+                               strVal(linitial(idents)),
+                               strVal(lsecond(idents)),
+                               NULL,
+                               NULL);
 
-   /*
-    * Do a lookup in the current namespace stack.
-    * We don't need to check number of names matched, because we will only
-    * consider scalar variables.
-    */
-   nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
-                           cp[0], cp[1], NULL,
-                           NULL);
+       if (nse != NULL && nse->itemtype == PLPGSQL_NSTYPE_VAR)
+       {
+           dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype;
+           goto done;
+       }
 
-   if (nse != NULL && nse->itemtype == PLPGSQL_NSTYPE_VAR)
+       /*
+        * First word could also be a table name
+        */
+       classOid = RelnameGetRelid(strVal(linitial(idents)));
+       if (!OidIsValid(classOid))
+           goto done;
+       fldname = strVal(lsecond(idents));
+   }
+   else if (list_length(idents) == 3)
    {
-       dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype;
-       goto done;
+       RangeVar   *relvar;
+
+       relvar = makeRangeVar(strVal(linitial(idents)),
+                             strVal(lsecond(idents)),
+                             -1);
+       classOid = RangeVarGetRelid(relvar, true);
+       if (!OidIsValid(classOid))
+           goto done;
+       fldname = strVal(lthird(idents));
    }
-
-   /*
-    * First word could also be a table name
-    */
-   classOid = RelnameGetRelid(cp[0]);
-   if (!OidIsValid(classOid))
-       goto done;
-
-   classtup = SearchSysCache(RELOID,
-                             ObjectIdGetDatum(classOid),
-                             0, 0, 0);
-   if (!HeapTupleIsValid(classtup))
-       goto done;
-   classStruct = (Form_pg_class) GETSTRUCT(classtup);
-
-   /*
-    * It must be a relation, sequence, view, or type
-    */
-   if (classStruct->relkind != RELKIND_RELATION &&
-       classStruct->relkind != RELKIND_SEQUENCE &&
-       classStruct->relkind != RELKIND_VIEW &&
-       classStruct->relkind != RELKIND_COMPOSITE_TYPE)
-       goto done;
-
-   /*
-    * Fetch the named table field and its type
-    */
-   attrtup = SearchSysCacheAttName(classOid, cp[1]);
-   if (!HeapTupleIsValid(attrtup))
-       goto done;
-   attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup);
-
-   typetup = SearchSysCache(TYPEOID,
-                            ObjectIdGetDatum(attrStruct->atttypid),
-                            0, 0, 0);
-   if (!HeapTupleIsValid(typetup))
-       elog(ERROR, "cache lookup failed for type %u", attrStruct->atttypid);
-
-   /*
-    * Found that - build a compiler type struct in the caller's cxt and
-    * return it
-    */
-   MemoryContextSwitchTo(oldCxt);
-   dtype = build_datatype(typetup, attrStruct->atttypmod);
-   MemoryContextSwitchTo(compile_tmp_cxt);
-
-done:
-   if (HeapTupleIsValid(classtup))
-       ReleaseSysCache(classtup);
-   if (HeapTupleIsValid(attrtup))
-       ReleaseSysCache(attrtup);
-   if (HeapTupleIsValid(typetup))
-       ReleaseSysCache(typetup);
-
-   MemoryContextSwitchTo(oldCxt);
-   return dtype;
-}
-
-/* ----------
- * plpgsql_parse_tripwordtype      Same lookup for word.word.word%TYPE
- * ----------
- */
-PLpgSQL_type *
-plpgsql_parse_tripwordtype(const char *word)
-{
-   PLpgSQL_type *dtype = NULL;
-   Oid         classOid;
-   HeapTuple   classtup = NULL;
-   HeapTuple   attrtup = NULL;
-   HeapTuple   typetup = NULL;
-   Form_pg_class classStruct;
-   Form_pg_attribute attrStruct;
-   char       *cp[3];
-   RangeVar   *relvar;
-   MemoryContext oldCxt;
-
-   /* Avoid memory leaks in the long-term function context */
-   oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
-
-   /* Do case conversion and word separation */
-   plpgsql_convert_ident(word, cp, 3);
-
-   relvar = makeRangeVar(cp[0], cp[1], -1);
-   classOid = RangeVarGetRelid(relvar, true);
-   if (!OidIsValid(classOid))
+   else
        goto done;
 
    classtup = SearchSysCache(RELOID,
@@ -1709,7 +1651,7 @@ plpgsql_parse_tripwordtype(const char *word)
    /*
     * Fetch the named table field and its type
     */
-   attrtup = SearchSysCacheAttName(classOid, cp[2]);
+   attrtup = SearchSysCacheAttName(classOid, fldname);
    if (!HeapTupleIsValid(attrtup))
        goto done;
    attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup);
@@ -1746,64 +1688,54 @@ done:
  * ----------
  */
 PLpgSQL_type *
-plpgsql_parse_wordrowtype(const char *word)
+plpgsql_parse_wordrowtype(char *ident)
 {
-   PLpgSQL_type *dtype;
    Oid         classOid;
-   char       *cp[1];
-
-   /* Do case conversion and word separation */
-   plpgsql_convert_ident(word, cp, 1);
 
    /* Lookup the relation */
-   classOid = RelnameGetRelid(cp[0]);
+   classOid = RelnameGetRelid(ident);
    if (!OidIsValid(classOid))
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_TABLE),
-                errmsg("relation \"%s\" does not exist", cp[0])));
+                errmsg("relation \"%s\" does not exist", ident)));
 
    /* Build and return the row type struct */
-   dtype = plpgsql_build_datatype(get_rel_type_id(classOid), -1);
-
-   pfree(cp[0]);
-
-   return dtype;
+   return plpgsql_build_datatype(get_rel_type_id(classOid), -1);
 }
 
 /* ----------
- * plpgsql_parse_dblwordrowtype        Scanner found word.word%ROWTYPE.
+ * plpgsql_parse_cwordrowtype      Scanner found compositeword%ROWTYPE.
  *         So word must be a namespace qualified table name.
  * ----------
  */
 PLpgSQL_type *
-plpgsql_parse_dblwordrowtype(const char *word)
+plpgsql_parse_cwordrowtype(List *idents)
 {
-   PLpgSQL_type *dtype;
    Oid         classOid;
-   char       *cp[2];
    RangeVar   *relvar;
    MemoryContext oldCxt;
 
+   if (list_length(idents) != 2)
+       return NULL;
+
    /* Avoid memory leaks in long-term function context */
    oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
 
-   /* Do case conversion and word separation */
-   plpgsql_convert_ident(word, cp, 2);
-
    /* Lookup the relation */
-   relvar = makeRangeVar(cp[0], cp[1], -1);
+   relvar = makeRangeVar(strVal(linitial(idents)),
+                         strVal(lsecond(idents)),
+                         -1);
    classOid = RangeVarGetRelid(relvar, true);
    if (!OidIsValid(classOid))
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_TABLE),
-                errmsg("relation \"%s.%s\" does not exist", cp[0], cp[1])));
+                errmsg("relation \"%s.%s\" does not exist",
+                       strVal(linitial(idents)), strVal(lsecond(idents)))));
 
    MemoryContextSwitchTo(oldCxt);
 
    /* Build and return the row type struct */
-   dtype = plpgsql_build_datatype(get_rel_type_id(classOid), -1);
-
-   return dtype;
+   return plpgsql_build_datatype(get_rel_type_id(classOid), -1);
 }
 
 /*
index 1d41ee74c684b86ec363196d0570cb57f397850d..6b6669973d7dc80a00a4946830c4aa8fbf322a9b 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.122 2009/11/09 00:26:55 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.123 2009/11/10 02:13:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -769,6 +769,27 @@ typedef struct
 } PLpgSQL_plugin;
 
 
+/* Struct types used during parsing */
+
+typedef struct
+{
+   char       *ident;          /* palloc'd converted identifier */
+   bool        quoted;         /* Was it double-quoted? */
+} PLword;
+
+typedef struct
+{
+   List       *idents;         /* composite identifiers (list of String) */
+} PLcword;
+
+typedef struct
+{
+   PLpgSQL_datum *datum;       /* referenced variable */
+   char       *ident;          /* valid if simple name */
+   bool        quoted;
+   List       *idents;         /* valid if composite name */
+} PLwdatum;
+
 /**********************************************************************
  * Global variable declarations
  **********************************************************************/
@@ -807,11 +828,10 @@ extern void plpgsql_parser_setup(struct ParseState *pstate,
 extern int plpgsql_parse_word(const char *word);
 extern int plpgsql_parse_dblword(const char *word);
 extern int plpgsql_parse_tripword(const char *word);
-extern PLpgSQL_type *plpgsql_parse_wordtype(const char *word);
-extern PLpgSQL_type *plpgsql_parse_dblwordtype(const char *word);
-extern PLpgSQL_type *plpgsql_parse_tripwordtype(const char *word);
-extern PLpgSQL_type *plpgsql_parse_wordrowtype(const char *word);
-extern PLpgSQL_type *plpgsql_parse_dblwordrowtype(const char *word);
+extern PLpgSQL_type *plpgsql_parse_wordtype(char *ident);
+extern PLpgSQL_type *plpgsql_parse_cwordtype(List *idents);
+extern PLpgSQL_type *plpgsql_parse_wordrowtype(char *ident);
+extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents);
 extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod);
 extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
                       PLpgSQL_type *dtype,
index e77508900b57effed627f9f55ff2da4cea564652..101559f3efcde374044db29daba7b4341e69c624 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.75 2009/11/09 00:26:55 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.76 2009/11/10 02:13:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -183,15 +183,11 @@ open          { SET_YYLLOC(); return K_OPEN;          }
 or             { SET_YYLLOC(); return K_OR;            }
 perform            { SET_YYLLOC(); return K_PERFORM;       }
 raise          { SET_YYLLOC(); return K_RAISE;         }
-result_oid     { SET_YYLLOC(); return K_RESULT_OID;    }
 return         { SET_YYLLOC(); return K_RETURN;        }
-reverse            { SET_YYLLOC(); return K_REVERSE;       }
-row_count      { SET_YYLLOC(); return K_ROW_COUNT;     }
 scroll         { SET_YYLLOC(); return K_SCROLL;        }
 strict         { SET_YYLLOC(); return K_STRICT;        }
 then           { SET_YYLLOC(); return K_THEN;          }
 to             { SET_YYLLOC(); return K_TO;            }
-type           { SET_YYLLOC(); return K_TYPE;          }
 using          { SET_YYLLOC(); return K_USING;         }
 when           { SET_YYLLOC(); return K_WHEN;          }
 while          { SET_YYLLOC(); return K_WHILE;         }
@@ -206,27 +202,21 @@ dump          { SET_YYLLOC(); return O_DUMP;          }
      */
 {identifier}                   {
    SET_YYLLOC();
-   if (!plpgsql_LookupIdentifiers) return T_WORD;
    return plpgsql_parse_word(yytext); }
 {identifier}{space}*\.{space}*{identifier} {
    SET_YYLLOC();
-   if (!plpgsql_LookupIdentifiers) return T_DBLWORD;
    return plpgsql_parse_dblword(yytext); }
 {identifier}{space}*\.{space}*{identifier}{space}*\.{space}*{identifier}   {
    SET_YYLLOC();
-   if (!plpgsql_LookupIdentifiers) return T_TRIPWORD;
    return plpgsql_parse_tripword(yytext); }
 {param}                            {
    SET_YYLLOC();
-   if (!plpgsql_LookupIdentifiers) return T_WORD;
    return plpgsql_parse_word(yytext); }
 {param}{space}*\.{space}*{identifier}  {
    SET_YYLLOC();
-   if (!plpgsql_LookupIdentifiers) return T_DBLWORD;
    return plpgsql_parse_dblword(yytext); }
 {param}{space}*\.{space}*{identifier}{space}*\.{space}*{identifier}    {
    SET_YYLLOC();
-   if (!plpgsql_LookupIdentifiers) return T_TRIPWORD;
    return plpgsql_parse_tripword(yytext); }
 
 {digit}+       { SET_YYLLOC(); return T_NUMBER;        }