diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index dbf1775961..0e5fbfd28a 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.180 2008/10/04 21:56:54 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.181 2008/10/06 02:12:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -631,34 +631,15 @@ transformFromClauseItem(ParseState *pstate, Node *n, RangeTblEntry *rte = NULL; int rtindex; - /* - * If it is an unqualified name, it might be a reference to some - * CTE visible in this or a parent query. - */ + /* if it is an unqualified name, it might be a CTE reference */ if (!rv->schemaname) { - ParseState *ps; + CommonTableExpr *cte; Index levelsup; - for (ps = pstate, levelsup = 0; - ps != NULL; - ps = ps->parentParseState, levelsup++) - { - ListCell *lc; - - foreach(lc, ps->p_ctenamespace) - { - CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); - - if (strcmp(rv->relname, cte->ctename) == 0) - { - rte = transformCTEReference(pstate, rv, cte, levelsup); - break; - } - } - if (rte) - break; - } + cte = scanNameSpaceForCTE(pstate, rv->relname, &levelsup); + if (cte) + rte = transformCTEReference(pstate, rv, cte, levelsup); } /* if not found as a CTE, must be a table reference */ diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index fa2da35c6b..870911c062 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.136 2008/10/04 21:56:54 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.137 2008/10/06 02:12:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -182,6 +182,38 @@ scanNameSpaceForRelid(ParseState *pstate, Oid relid, int location) return result; } +/* + * Search the query's CTE namespace for a CTE matching the given unqualified + * refname. Return the CTE (and its levelsup count) if a match, or NULL + * if no match. We need not worry about multiple matches, since parse_cte.c + * rejects WITH lists containing duplicate CTE names. + */ +CommonTableExpr * +scanNameSpaceForCTE(ParseState *pstate, const char *refname, + Index *ctelevelsup) +{ + Index levelsup; + + for (levelsup = 0; + pstate != NULL; + pstate = pstate->parentParseState, levelsup++) + { + ListCell *lc; + + foreach(lc, pstate->p_ctenamespace) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); + + if (strcmp(cte->ctename, refname) == 0) + { + *ctelevelsup = levelsup; + return cte; + } + } + } + return NULL; +} + /* * searchRangeTable * See if any RangeTblEntry could possibly match the RangeVar. @@ -194,16 +226,32 @@ scanNameSpaceForRelid(ParseState *pstate, Oid relid, int location) * valid matches, but only one will be returned). This must be used ONLY * as a heuristic in giving suitable error messages. See warnAutoRange. * - * Notice that we consider both matches on actual relation name and matches - * on alias. + * Notice that we consider both matches on actual relation (or CTE) name + * and matches on alias. */ static RangeTblEntry * searchRangeTable(ParseState *pstate, RangeVar *relation) { - Oid relId = RangeVarGetRelid(relation, true); - char *refname = relation->relname; + const char *refname = relation->relname; + Oid relId = InvalidOid; + CommonTableExpr *cte = NULL; + Index ctelevelsup = 0; + Index levelsup; - while (pstate != NULL) + /* + * If it's an unqualified name, check for possible CTE matches. + * A CTE hides any real relation matches. If no CTE, look for + * a matching relation. + */ + if (!relation->schemaname) + cte = scanNameSpaceForCTE(pstate, refname, &ctelevelsup); + if (!cte) + relId = RangeVarGetRelid(relation, true); + + /* Now look for RTEs matching either the relation/CTE or the alias */ + for (levelsup = 0; + pstate != NULL; + pstate = pstate->parentParseState, levelsup++) { ListCell *l; @@ -211,15 +259,18 @@ searchRangeTable(ParseState *pstate, RangeVar *relation) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); - if (OidIsValid(relId) && - rte->rtekind == RTE_RELATION && + if (rte->rtekind == RTE_RELATION && + OidIsValid(relId) && rte->relid == relId) return rte; + if (rte->rtekind == RTE_CTE && + cte != NULL && + rte->ctelevelsup + levelsup == ctelevelsup && + strcmp(rte->ctename, refname) == 0) + return rte; if (strcmp(rte->eref->aliasname, refname) == 0) return rte; } - - pstate = pstate->parentParseState; } return NULL; } @@ -1293,18 +1344,27 @@ addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte, RangeTblEntry * addImplicitRTE(ParseState *pstate, RangeVar *relation) { + CommonTableExpr *cte = NULL; + Index levelsup = 0; RangeTblEntry *rte; /* issue warning or error as needed */ warnAutoRange(pstate, relation); + /* if it is an unqualified name, it might be a CTE reference */ + if (!relation->schemaname) + cte = scanNameSpaceForCTE(pstate, relation->relname, &levelsup); + /* * Note that we set inFromCl true, so that the RTE will be listed * explicitly if the parsetree is ever decompiled by ruleutils.c. This * provides a migration path for views/rules that were originally written * with implicit-RTE syntax. */ - rte = addRangeTableEntry(pstate, relation, NULL, false, true); + if (cte) + rte = addRangeTableEntryForCTE(pstate, cte, levelsup, NULL, true); + else + rte = addRangeTableEntry(pstate, relation, NULL, false, true); /* Add to joinlist and relnamespace, but not varnamespace */ addRTEtoQuery(pstate, rte, true, true, false); @@ -2194,8 +2254,8 @@ warnAutoRange(ParseState *pstate, RangeVar *relation) if (rte) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("invalid reference to FROM-clause entry for table \"%s\"", - relation->relname), + errmsg("invalid reference to FROM-clause entry for table \"%s\"", + relation->relname), (badAlias ? errhint("Perhaps you meant to reference the table alias \"%s\".", badAlias) : @@ -2205,11 +2265,8 @@ warnAutoRange(ParseState *pstate, RangeVar *relation) else ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), - (pstate->parentParseState ? - errmsg("missing FROM-clause entry in subquery for table \"%s\"", - relation->relname) : - errmsg("missing FROM-clause entry for table \"%s\"", - relation->relname)), + errmsg("missing FROM-clause entry for table \"%s\"", + relation->relname), parser_errposition(pstate, relation->location))); } else @@ -2217,11 +2274,8 @@ warnAutoRange(ParseState *pstate, RangeVar *relation) /* just issue a warning */ ereport(NOTICE, (errcode(ERRCODE_UNDEFINED_TABLE), - (pstate->parentParseState ? - errmsg("adding missing FROM-clause entry in subquery for table \"%s\"", - relation->relname) : - errmsg("adding missing FROM-clause entry for table \"%s\"", - relation->relname)), + errmsg("adding missing FROM-clause entry for table \"%s\"", + relation->relname), (badAlias ? errhint("Perhaps you meant to reference the table alias \"%s\".", badAlias) : diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index 9c81d4b50b..f5212c421b 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_relation.h,v 1.59 2008/10/04 21:56:55 tgl Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_relation.h,v 1.60 2008/10/06 02:12:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -23,6 +23,9 @@ extern RangeTblEntry *refnameRangeTblEntry(ParseState *pstate, const char *refname, int location, int *sublevels_up); +extern CommonTableExpr *scanNameSpaceForCTE(ParseState *pstate, + const char *refname, + Index *ctelevelsup); extern void checkNameSpaceConflicts(ParseState *pstate, List *namespace1, List *namespace2); extern int RTERangeTablePosn(ParseState *pstate,