Fix the implicit-RTE code to be able to handle implicit RTEs for CTEs, as
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 6 Oct 2008 02:12:56 +0000 (02:12 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 6 Oct 2008 02:12:56 +0000 (02:12 +0000)
well as regular tables.  Per discussion, this seems necessary to meet the
principle of least astonishment.

In passing, simplify the error messages in warnAutoRange().  Now that we
have parser error position info for these errors, it doesn't seem very
useful to word the error message differently depending on whether we are
inside a sub-select or not.

src/backend/parser/parse_clause.c
src/backend/parser/parse_relation.c
src/include/parser/parse_relation.h

index a1a79bf6c81fc588ee5371fd6ca37b3fdb9832eb..58cef1cfc95dfeee559deeba9693849b67715efd 100644 (file)
@@ -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 */
index 16a7cb74203a3653cec7e228d6186387e21fa727..c1b55e7d62118005a263918e7b88d028d52435ee 100644 (file)
@@ -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) :
index 34b99f3388a30767ba3505d0c23b7d9da008fadd..119b1a09728945ed64f1319bb056cb0441e4436f 100644 (file)
@@ -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,