diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index ffb16bea1c..4541c9839d 100644 --- a/src/pl/plpgsql/src/gram.y +++ b/src/pl/plpgsql/src/gram.y @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.106 2007/11/09 23:58:32 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.107 2007/11/27 19:58:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -43,6 +43,7 @@ static PLpgSQL_row *make_scalar_list1(const char *initial_name, int lineno); static void check_sql_expr(const char *stmt); static void plpgsql_sql_error_callback(void *arg); +static char *check_label(const char *yytxt); static void check_labels(const char *start_label, const char *end_label); @@ -214,7 +215,6 @@ static void check_labels(const char *start_label, %token T_ROW %token T_RECORD %token T_DTYPE -%token T_LABEL %token T_WORD %token T_ERROR @@ -505,7 +505,8 @@ decl_aliasitem : T_WORD yyerror("only positional parameters can be aliased"); plpgsql_ns_setlocal(false); - nsi = plpgsql_ns_lookup(name, NULL); + + nsi = plpgsql_ns_lookup(name, NULL, NULL, NULL); if (nsi == NULL) { plpgsql_error_lineno = plpgsql_scanner_lineno(); @@ -1642,20 +1643,28 @@ opt_block_label : } ; +/* + * need all the options because scanner will have tried to resolve as variable + */ opt_label : { $$ = NULL; } - | T_LABEL - { - char *label_name; - plpgsql_convert_ident(yytext, &label_name, 1); - $$ = label_name; - } | T_WORD { - /* just to give a better error than "syntax error" */ - yyerror("no such label"); + $$ = check_label(yytext); + } + | T_SCALAR + { + $$ = check_label(yytext); + } + | T_RECORD + { + $$ = check_label(yytext); + } + | T_ROW + { + $$ = check_label(yytext); } ; @@ -2484,6 +2493,17 @@ plpgsql_sql_error_callback(void *arg) errposition(0); } +static char * +check_label(const char *yytxt) +{ + char *label_name; + + plpgsql_convert_ident(yytxt, &label_name, 1); + if (plpgsql_ns_lookup_label(label_name) == NULL) + yyerror("no such label"); + return label_name; +} + static void check_labels(const char *start_label, const char *end_label) { diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index edb423e521..2e76b5e187 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.119 2007/11/15 21:14:46 momjian Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.120 2007/11/27 19:58:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -787,7 +787,7 @@ plpgsql_compile_error_callback(void *arg) * ---------- */ int -plpgsql_parse_word(char *word) +plpgsql_parse_word(const char *word) { PLpgSQL_nsitem *nse; char *cp[1]; @@ -797,6 +797,7 @@ plpgsql_parse_word(char *word) /* * Recognize tg_argv when compiling triggers + * (XXX this sucks, it should be a regular variable in the namestack) */ if (plpgsql_curr_compile->fn_functype == T_TRIGGER) { @@ -825,15 +826,13 @@ plpgsql_parse_word(char *word) /* * Do a lookup on the compiler's namestack */ - nse = plpgsql_ns_lookup(cp[0], NULL); + nse = plpgsql_ns_lookup(cp[0], NULL, NULL, NULL); + pfree(cp[0]); + if (nse != NULL) { - pfree(cp[0]); switch (nse->itemtype) { - case PLPGSQL_NSTYPE_LABEL: - return T_LABEL; - case PLPGSQL_NSTYPE_VAR: plpgsql_yylval.scalar = plpgsql_Datums[nse->itemno]; return T_SCALAR; @@ -855,7 +854,6 @@ plpgsql_parse_word(char *word) * Nothing found - up to now it's a word without any special meaning for * us. */ - pfree(cp[0]); return T_WORD; } @@ -866,18 +864,19 @@ plpgsql_parse_word(char *word) * ---------- */ int -plpgsql_parse_dblword(char *word) +plpgsql_parse_dblword(const char *word) { PLpgSQL_nsitem *ns; char *cp[2]; + int nnames; /* Do case conversion and word separation */ plpgsql_convert_ident(word, cp, 2); /* - * Lookup the first word + * Do a lookup on the compiler's namestack */ - ns = plpgsql_ns_lookup(cp[0], NULL); + ns = plpgsql_ns_lookup(cp[0], cp[1], NULL, &nnames); if (ns == NULL) { pfree(cp[0]); @@ -887,39 +886,15 @@ plpgsql_parse_dblword(char *word) switch (ns->itemtype) { - case PLPGSQL_NSTYPE_LABEL: - - /* - * First word is a label, so second word could be a variable, - * record or row in that bodies namestack. Anything else could - * only be something in a query given to the SPI manager and - * T_ERROR will get eaten up by the collector routines. - */ - ns = plpgsql_ns_lookup(cp[1], cp[0]); + case PLPGSQL_NSTYPE_VAR: + /* Block-qualified reference to scalar variable. */ + plpgsql_yylval.scalar = plpgsql_Datums[ns->itemno]; pfree(cp[0]); pfree(cp[1]); - if (ns == NULL) - return T_ERROR; - switch (ns->itemtype) - { - case PLPGSQL_NSTYPE_VAR: - plpgsql_yylval.scalar = plpgsql_Datums[ns->itemno]; - return T_SCALAR; - - case PLPGSQL_NSTYPE_REC: - plpgsql_yylval.rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]); - return T_RECORD; - - case PLPGSQL_NSTYPE_ROW: - plpgsql_yylval.row = (PLpgSQL_row *) (plpgsql_Datums[ns->itemno]); - return T_ROW; - - default: - return T_ERROR; - } - break; + return T_SCALAR; case PLPGSQL_NSTYPE_REC: + if (nnames == 1) { /* * First word is a record name, so second word must be a field @@ -940,8 +915,17 @@ plpgsql_parse_dblword(char *word) pfree(cp[1]); return T_SCALAR; } + else + { + /* Block-qualified reference to record variable. */ + plpgsql_yylval.rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]); + pfree(cp[0]); + pfree(cp[1]); + return T_RECORD; + } case PLPGSQL_NSTYPE_ROW: + if (nnames == 1) { /* * First word is a row name, so second word must be a field in @@ -967,6 +951,14 @@ plpgsql_parse_dblword(char *word) errmsg("row \"%s\" has no field \"%s\"", cp[0], cp[1]))); } + else + { + /* Block-qualified reference to row variable. */ + plpgsql_yylval.row = (PLpgSQL_row *) (plpgsql_Datums[ns->itemno]); + pfree(cp[0]); + pfree(cp[1]); + return T_ROW; + } default: break; @@ -984,38 +976,21 @@ plpgsql_parse_dblword(char *word) * ---------- */ int -plpgsql_parse_tripword(char *word) +plpgsql_parse_tripword(const char *word) { PLpgSQL_nsitem *ns; char *cp[3]; + int nnames; /* Do case conversion and word separation */ plpgsql_convert_ident(word, cp, 3); /* - * Lookup the first word - it must be a label + * Do a lookup on the compiler's namestack. + * Must find a qualified reference. */ - ns = plpgsql_ns_lookup(cp[0], NULL); - if (ns == NULL) - { - pfree(cp[0]); - pfree(cp[1]); - pfree(cp[2]); - return T_ERROR; - } - if (ns->itemtype != PLPGSQL_NSTYPE_LABEL) - { - pfree(cp[0]); - pfree(cp[1]); - pfree(cp[2]); - return T_ERROR; - } - - /* - * First word is a label, so second word could be a record or row - */ - ns = plpgsql_ns_lookup(cp[1], cp[0]); - if (ns == NULL) + ns = plpgsql_ns_lookup(cp[0], cp[1], cp[2], &nnames); + if (ns == NULL || nnames != 2) { pfree(cp[0]); pfree(cp[1]); @@ -1028,7 +1003,7 @@ plpgsql_parse_tripword(char *word) case PLPGSQL_NSTYPE_REC: { /* - * This word is a record name, so third word must be a field + * words 1/2 are a record name, so third word must be a field * in this record. */ PLpgSQL_recfield *new; @@ -1052,7 +1027,7 @@ plpgsql_parse_tripword(char *word) case PLPGSQL_NSTYPE_ROW: { /* - * This word is a row name, so third word must be a field in + * words 1/2 are a row name, so third word must be a field in * this row. */ PLpgSQL_row *row; @@ -1114,11 +1089,10 @@ plpgsql_parse_wordtype(char *word) pfree(cp[1]); /* - * Do a lookup on the compiler's namestack. But ensure it moves up to the - * toplevel. + * Do a lookup on the compiler's namestack. Ensure we scan all levels. */ old_nsstate = plpgsql_ns_setlocal(false); - nse = plpgsql_ns_lookup(cp[0], NULL); + nse = plpgsql_ns_lookup(cp[0], NULL, NULL, NULL); plpgsql_ns_setlocal(old_nsstate); if (nse != NULL) @@ -1200,32 +1174,21 @@ plpgsql_parse_dblwordtype(char *word) word[i] = '.'; plpgsql_convert_ident(word, cp, 3); word[i] = '%'; + pfree(cp[2]); /* - * Lookup the first word + * Do a lookup on the compiler's namestack. Ensure we scan all levels. + * We don't need to check number of names matched, because we will only + * consider scalar variables. */ - nse = plpgsql_ns_lookup(cp[0], NULL); + old_nsstate = plpgsql_ns_setlocal(false); + nse = plpgsql_ns_lookup(cp[0], cp[1], NULL, NULL); + plpgsql_ns_setlocal(old_nsstate); - /* - * If this is a label lookup the second word in that label's namestack - * level - */ - if (nse != NULL) + if (nse != NULL && nse->itemtype == PLPGSQL_NSTYPE_VAR) { - if (nse->itemtype == PLPGSQL_NSTYPE_LABEL) - { - old_nsstate = plpgsql_ns_setlocal(false); - nse = plpgsql_ns_lookup(cp[1], cp[0]); - plpgsql_ns_setlocal(old_nsstate); - - if (nse != NULL && nse->itemtype == PLPGSQL_NSTYPE_VAR) - { - plpgsql_yylval.dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype; - result = T_DTYPE; - } - } - - /* Return T_ERROR if not found, otherwise T_DTYPE */ + plpgsql_yylval.dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype; + result = T_DTYPE; goto done; } @@ -1291,8 +1254,6 @@ done: * plpgsql_parse_tripwordtype Same lookup for word.word.word%TYPE * ---------- */ -#define TYPE_JUNK_LEN 5 - int plpgsql_parse_tripwordtype(char *word) { @@ -1302,10 +1263,7 @@ plpgsql_parse_tripwordtype(char *word) HeapTuple typetup = NULL; Form_pg_class classStruct; Form_pg_attribute attrStruct; - char *cp[2]; - char *colname[1]; - int qualified_att_len; - int numdots = 0; + char *cp[4]; int i; RangeVar *relvar; MemoryContext oldCxt; @@ -1315,27 +1273,15 @@ plpgsql_parse_tripwordtype(char *word) oldCxt = MemoryContextSwitchTo(compile_tmp_cxt); /* Do case conversion and word separation */ - qualified_att_len = strlen(word) - TYPE_JUNK_LEN; - Assert(word[qualified_att_len] == '%'); + /* We convert %type to .type momentarily to keep converter happy */ + i = strlen(word) - 5; + Assert(word[i] == '%'); + word[i] = '.'; + plpgsql_convert_ident(word, cp, 4); + word[i] = '%'; + pfree(cp[3]); - for (i = 0; i < qualified_att_len; i++) - { - if (word[i] == '.' && ++numdots == 2) - break; - } - - cp[0] = (char *) palloc((i + 1) * sizeof(char)); - memcpy(cp[0], word, i * sizeof(char)); - cp[0][i] = '\0'; - - /* - * qualified_att_len - one based position + 1 (null terminator) - */ - cp[1] = (char *) palloc((qualified_att_len - i) * sizeof(char)); - memcpy(cp[1], &word[i + 1], (qualified_att_len - i - 1) * sizeof(char)); - cp[1][qualified_att_len - i - 1] = '\0'; - - relvar = makeRangeVarFromNameList(stringToQualifiedNameList(cp[0])); + relvar = makeRangeVar(cp[0], cp[1]); classOid = RangeVarGetRelid(relvar, true); if (!OidIsValid(classOid)) goto done; @@ -1359,8 +1305,7 @@ plpgsql_parse_tripwordtype(char *word) /* * Fetch the named table field and its type */ - plpgsql_convert_ident(cp[1], colname, 1); - attrtup = SearchSysCacheAttName(classOid, colname[0]); + attrtup = SearchSysCacheAttName(classOid, cp[2]); if (!HeapTupleIsValid(attrtup)) goto done; attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup); @@ -1436,13 +1381,11 @@ plpgsql_parse_wordrowtype(char *word) * So word must be a namespace qualified table name. * ---------- */ -#define ROWTYPE_JUNK_LEN 8 - int plpgsql_parse_dblwordrowtype(char *word) { Oid classOid; - char *cp; + char *cp[3]; int i; RangeVar *relvar; MemoryContext oldCxt; @@ -1452,19 +1395,19 @@ plpgsql_parse_dblwordrowtype(char *word) /* Do case conversion and word separation */ /* We convert %rowtype to .rowtype momentarily to keep converter happy */ - i = strlen(word) - ROWTYPE_JUNK_LEN; + i = strlen(word) - 8; Assert(word[i] == '%'); - word[i] = '\0'; - cp = pstrdup(word); + word[i] = '.'; + plpgsql_convert_ident(word, cp, 3); word[i] = '%'; /* Lookup the relation */ - relvar = makeRangeVarFromNameList(stringToQualifiedNameList(cp)); + relvar = makeRangeVar(cp[0], cp[1]); classOid = RangeVarGetRelid(relvar, true); if (!OidIsValid(classOid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("relation \"%s\" does not exist", cp))); + errmsg("relation \"%s.%s\" does not exist", cp[0], cp[1]))); /* Build and return the row type struct */ plpgsql_yylval.dtype = plpgsql_build_datatype(get_rel_type_id(classOid), diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index 573bfd4e0c..66e0f47d8c 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.65 2007/11/15 22:25:17 momjian Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.66 2007/11/27 19:58:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -126,9 +126,15 @@ plpgsql_ns_init(void) /* ---------- - * plpgsql_ns_setlocal Tell plpgsql_ns_lookup to or to - * not look into the current level - * only. + * plpgsql_ns_setlocal Tell plpgsql_ns_lookup whether to + * look into the current level only. + * + * This is a crock, but in the current design we need it because scan.l + * initiates name lookup, and the scanner does not know whether we are + * examining a name being declared in a DECLARE section. For that case + * we only want to know if there is a conflicting name earlier in the + * same DECLARE section. So the grammar must temporarily set local mode + * before scanning decl_varnames. * ---------- */ bool @@ -219,59 +225,98 @@ plpgsql_ns_additem(int itemtype, int itemno, const char *name) /* ---------- - * plpgsql_ns_lookup Lookup for a word in the namestack + * plpgsql_ns_lookup Lookup an identifier in the namestack + * + * Note that this only searches for variables, not labels. + * + * name1 must be non-NULL. Pass NULL for name2 and/or name3 if parsing a name + * with fewer than three components. + * + * If names_used isn't NULL, *names_used receives the number of names + * matched: 0 if no match, 1 if name1 matched an unqualified variable name, + * 2 if name1 and name2 matched a block label + variable name. + * + * Note that name3 is never directly matched to anything. However, if it + * isn't NULL, we will disregard qualified matches to scalar variables. + * Similarly, if name2 isn't NULL, we disregard unqualified matches to + * scalar variables. * ---------- */ PLpgSQL_nsitem * -plpgsql_ns_lookup(const char *name, const char *label) +plpgsql_ns_lookup(const char *name1, const char *name2, const char *name3, + int *names_used) { PLpgSQL_ns *ns; int i; - /* - * If a label is specified, lookup only in that - */ - if (label != NULL) + /* Scan each level of the namestack */ + for (ns = ns_current; ns != NULL; ns = ns->upper) { - for (ns = ns_current; ns != NULL; ns = ns->upper) + /* Check for unqualified match to variable name */ + for (i = 1; i < ns->items_used; i++) { - if (strcmp(ns->items[0]->name, label) == 0) + PLpgSQL_nsitem *nsitem = ns->items[i]; + + if (strcmp(nsitem->name, name1) == 0) { - for (i = 1; i < ns->items_used; i++) + if (name2 == NULL || + nsitem->itemtype != PLPGSQL_NSTYPE_VAR) { - if (strcmp(ns->items[i]->name, name) == 0) - return ns->items[i]; + if (names_used) + *names_used = 1; + return nsitem; } - return NULL; /* name not found in specified label */ } } - return NULL; /* label not found */ + + /* Check for qualified match to variable name */ + if (name2 != NULL && + strcmp(ns->items[0]->name, name1) == 0) + { + for (i = 1; i < ns->items_used; i++) + { + PLpgSQL_nsitem *nsitem = ns->items[i]; + + if (strcmp(nsitem->name, name2) == 0) + { + if (name3 == NULL || + nsitem->itemtype != PLPGSQL_NSTYPE_VAR) + { + if (names_used) + *names_used = 2; + return nsitem; + } + } + } + } + + if (ns_localmode) + break; /* do not look into upper levels */ } - /* - * No label given, lookup for visible labels ignoring localmode - */ + /* This is just to suppress possibly-uninitialized-variable warnings */ + if (names_used) + *names_used = 0; + return NULL; /* No match found */ +} + + +/* ---------- + * plpgsql_ns_lookup_label Lookup a label in the namestack + * ---------- + */ +PLpgSQL_nsitem * +plpgsql_ns_lookup_label(const char *name) +{ + PLpgSQL_ns *ns; + for (ns = ns_current; ns != NULL; ns = ns->upper) { if (strcmp(ns->items[0]->name, name) == 0) return ns->items[0]; } - /* - * Finally lookup name in the namestack - */ - for (ns = ns_current; ns != NULL; ns = ns->upper) - { - for (i = 1; i < ns->items_used; i++) - { - if (strcmp(ns->items[i]->name, name) == 0) - return ns->items[i]; - } - if (ns_localmode) - return NULL; /* name not found in current namespace */ - } - - return NULL; + return NULL; /* label not found */ } @@ -846,8 +891,9 @@ static void dump_exit(PLpgSQL_stmt_exit *stmt) { dump_ind(); - printf("%s label='%s'", - stmt->is_exit ? "EXIT" : "CONTINUE", stmt->label); + printf("%s", stmt->is_exit ? "EXIT" : "CONTINUE"); + if (stmt->label != NULL) + printf(" label='%s'", stmt->label); if (stmt->cond != NULL) { printf(" WHEN "); diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 6ba0126b49..7bbed551ab 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.93 2007/11/15 22:25:17 momjian Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.94 2007/11/27 19:58:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -473,7 +473,7 @@ typedef struct int cmd_type; int lineno; bool is_exit; /* Is this an exit or a continue? */ - char *label; + char *label; /* NULL if it's an unlabelled EXIT/CONTINUE */ PLpgSQL_expr *cond; } PLpgSQL_stmt_exit; @@ -723,9 +723,9 @@ extern PLpgSQL_plugin **plugin_ptr; */ extern PLpgSQL_function *plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator); -extern int plpgsql_parse_word(char *word); -extern int plpgsql_parse_dblword(char *word); -extern int plpgsql_parse_tripword(char *word); +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 int plpgsql_parse_wordtype(char *word); extern int plpgsql_parse_dblwordtype(char *word); extern int plpgsql_parse_tripwordtype(char *word); @@ -773,7 +773,7 @@ extern void plpgsql_dstring_append_char(PLpgSQL_dstring *ds, char c); extern char *plpgsql_dstring_get(PLpgSQL_dstring *ds); /* ---------- - * Functions for the namestack handling in pl_funcs.c + * Functions for namestack handling in pl_funcs.c * ---------- */ extern void plpgsql_ns_init(void); @@ -781,7 +781,9 @@ extern bool plpgsql_ns_setlocal(bool flag); extern void plpgsql_ns_push(const char *label); extern void plpgsql_ns_pop(void); extern void plpgsql_ns_additem(int itemtype, int itemno, const char *name); -extern PLpgSQL_nsitem *plpgsql_ns_lookup(const char *name, const char *nsname); +extern PLpgSQL_nsitem *plpgsql_ns_lookup(const char *name1, const char *name2, + const char *name3, int *names_used); +extern PLpgSQL_nsitem *plpgsql_ns_lookup_label(const char *name); extern void plpgsql_ns_rename(char *oldname, char *newname); /* ----------