From 73a2f6c653cabe57f93e4a7f51dfea7b753076b4 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 10 Nov 2009 02:13:13 +0000 Subject: [PATCH] More incremental refactoring in plpgsql: get rid of gram.y dependencies on 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 | 544 +++++++++++++++++++++--------------------- src/pl/plpgsql/src/pl_comp.c | 546 +++++++++++++++++++------------------------ src/pl/plpgsql/src/plpgsql.h | 32 ++- src/pl/plpgsql/src/scan.l | 12 +- 4 files changed, 541 insertions(+), 593 deletions(-) diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index fd4d3e6d78..ec269a88c5 100644 --- a/src/pl/plpgsql/src/gram.y +++ b/src/pl/plpgsql/src/gram.y @@ -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 for_variable %type for_control -%type any_identifier any_name opt_block_label opt_label +%type any_identifier opt_block_label opt_label %type proc_sect proc_stmts stmt_else %type loop_body @@ -197,14 +209,38 @@ static List *read_raise_options(void); %type getdiag_list %type getdiag_list_item -%type getdiag_kind getdiag_target +%type getdiag_item getdiag_target %type opt_scrollable %type 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 IDENT FCONST SCONST BCONST XCONST Op +%token ICONST PARAM +%token TYPECAST DOT_DOT COLON_EQUALS + +/* + * Other tokens recognized by plpgsql's lexer interface layer. + */ +%token T_STRING +%token T_NUMBER +%token T_WORD /* unrecognized simple identifier */ +%token T_CWORD /* unrecognized composite identifier */ +%token 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 \"=\""); diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index a394eace60..2f69a647f8 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -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); } /* diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 1d41ee74c6..6b6669973d 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -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, diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l index e77508900b..101559f3ef 100644 --- a/src/pl/plpgsql/src/scan.l +++ b/src/pl/plpgsql/src/scan.l @@ -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; } -- 2.11.0