Cause schema-qualified FROM items and schema-qualified variable references
authorTom Lane
Thu, 8 Aug 2002 01:44:31 +0000 (01:44 +0000)
committerTom Lane
Thu, 8 Aug 2002 01:44:31 +0000 (01:44 +0000)
to behave according to SQL92 (or according to my current understanding
of same, anyway).  Per pghackers discussion way back in March 2002:
thread 'Do FROM items of different schemas conflict?'

src/backend/catalog/namespace.c
src/backend/parser/parse_expr.c
src/backend/parser/parse_func.c
src/backend/parser/parse_relation.c
src/backend/parser/parse_target.c
src/backend/utils/adt/ruleutils.c
src/include/catalog/namespace.h
src/include/parser/parse_relation.h

index bc2098034f3ca6f925852864da90b9509bb83094..4999ea4b640448e1bf28e595073ac258ff9686bd 100644 (file)
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.28 2002/08/06 05:40:44 ishii Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.29 2002/08/08 01:44:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1080,7 +1080,7 @@ DeconstructQualifiedName(List *names,
  * Returns the namespace OID.  Raises elog if any problem.
  */
 Oid
-LookupExplicitNamespace(char *nspname)
+LookupExplicitNamespace(const char *nspname)
 {
    Oid         namespaceId;
    AclResult   aclresult;
index 3cd09a9b43ca66e61e68afd7b7e05bd2a30adc50..85cdc431a0e5bda98e1bfe2f2164904d91001ff6 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.124 2002/08/04 06:46:12 thomas Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.125 2002/08/08 01:44:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -709,6 +709,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
            /* Try to identify as an unqualified column */
            node = colnameToVar(pstate, name);
+
            if (node == NULL)
            {
                /*
@@ -716,11 +717,15 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
                 * try to find the name as a relation ... but not if
                 * subscripts appear.  Note also that only relations
                 * already entered into the rangetable will be recognized.
+                *
+                * This is a hack for backwards compatibility with PostQUEL-
+                * inspired syntax.  The preferred form now is "rel.*".
                 */
                int     levels_up;
 
                if (cref->indirection == NIL &&
-                   refnameRangeTblEntry(pstate, name, &levels_up) != NULL)
+                   refnameRangeTblEntry(pstate, NULL, name,
+                                        &levels_up) != NULL)
                {
                    rv = makeNode(RangeVar);
                    rv->relname = name;
@@ -748,7 +753,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
            }
 
            /* Try to identify as a once-qualified column */
-           node = qualifiedNameToVar(pstate, name1, name2, true);
+           node = qualifiedNameToVar(pstate, NULL, name1, name2, true);
            if (node == NULL)
            {
                /*
@@ -784,8 +789,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
            }
 
            /* Try to identify as a twice-qualified column */
-           /* XXX do something with schema name here */
-           node = qualifiedNameToVar(pstate, name2, name3, true);
+           node = qualifiedNameToVar(pstate, name1, name2, name3, true);
            if (node == NULL)
            {
                /* Try it as a function call */
@@ -825,8 +829,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
            }
 
            /* Try to identify as a twice-qualified column */
-           /* XXX do something with schema name here */
-           node = qualifiedNameToVar(pstate, name3, name4, true);
+           node = qualifiedNameToVar(pstate, name2, name3, name4, true);
            if (node == NULL)
            {
                /* Try it as a function call */
index 677acf9d1a3d0975f7aacefaeb25c7ed67db87c4..edd0e8109579712a15a9d67c3c6070a094374ed7 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.133 2002/08/02 18:15:07 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.134 2002/08/08 01:44:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,6 +51,8 @@ static int match_argtypes(int nargs,
 static FieldSelect *setup_field_select(Node *input, char *attname, Oid relid);
 static FuncCandidateList func_select_candidate(int nargs, Oid *input_typeids,
                                  FuncCandidateList candidates);
+static void unknown_attribute(const char *schemaname, const char *relname,
+                             const char *attname);
 
 
 /*
@@ -80,7 +82,6 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
    Oid         funcid;
    List       *i;
    Node       *first_arg = NULL;
-   char       *refname;
    int         nargs = length(fargs);
    int         argn;
    Oid         oid_array[FUNC_MAX_ARGS];
@@ -121,10 +122,11 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
        if (IsA(first_arg, RangeVar))
        {
            /* First arg is a relation. This could be a projection. */
-           refname = ((RangeVar *) first_arg)->relname;
-
-           /* XXX WRONG: ignores possible qualification of argument */
-           retval = qualifiedNameToVar(pstate, refname, cname, true);
+           retval = qualifiedNameToVar(pstate,
+                                       ((RangeVar *) first_arg)->schemaname,
+                                       ((RangeVar *) first_arg)->relname,
+                                       cname,
+                                       true);
            if (retval)
                return retval;
        }
@@ -156,16 +158,19 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 
        if (IsA(arg, RangeVar))
        {
+           char       *schemaname;
+           char       *relname;
            RangeTblEntry *rte;
            int         vnum;
            int         sublevels_up;
 
            /*
-            * a relation
+            * a relation: look it up in the range table, or add if needed
             */
-           refname = ((RangeVar *) arg)->relname;
+           schemaname = ((RangeVar *) arg)->schemaname;
+           relname = ((RangeVar *) arg)->relname;
 
-           rte = refnameRangeTblEntry(pstate, refname,
+           rte = refnameRangeTblEntry(pstate, schemaname, relname,
                                       &sublevels_up);
 
            if (rte == NULL)
@@ -199,11 +204,11 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
                     * named tuple type
                     */
                    if (is_column)
-                       elog(ERROR, "No such attribute %s.%s",
-                            refname, strVal(lfirst(funcname)));
+                       unknown_attribute(schemaname, relname,
+                                         strVal(lfirst(funcname)));
                    else
                        elog(ERROR, "Cannot pass result of sub-select or join %s to a function",
-                            refname);
+                            relname);
                    toid = InvalidOid; /* keep compiler quiet */
                    break;
            }
@@ -268,8 +273,9 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 
            Assert(nargs == 1);
            if (IsA(first_arg, RangeVar))
-               elog(ERROR, "No such attribute %s.%s",
-                    ((RangeVar *) first_arg)->relname, colname);
+               unknown_attribute(((RangeVar *) first_arg)->schemaname,
+                                 ((RangeVar *) first_arg)->relname,
+                                 colname);
            relTypeId = exprType(first_arg);
            if (!ISCOMPLEX(relTypeId))
                elog(ERROR, "Attribute notation .%s applied to type %s, which is not a complex type",
@@ -1225,6 +1231,21 @@ ParseComplexProjection(ParseState *pstate,
    return (Node *) fselect;
 }
 
+/*
+ * Simple helper routine for delivering "No such attribute" error message
+ */
+static void
+unknown_attribute(const char *schemaname, const char *relname,
+                 const char *attname)
+{
+   if (schemaname)
+       elog(ERROR, "No such attribute %s.%s.%s",
+            schemaname, relname, attname);
+   else
+       elog(ERROR, "No such attribute %s.%s",
+            relname, attname);
+}
+
 /*
  * Error message when function lookup fails that gives details of the
  * argument types
index 8a15c0f7cbff4f52a9504a5b998e26f2d96875a4..229eab829a533a113562d0bd65bbcad06bac562e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.75 2002/08/06 05:34:10 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.76 2002/08/08 01:44:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,6 +19,7 @@
 #include "access/heapam.h"
 #include "access/htup.h"
 #include "catalog/heap.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "parser/parsetree.h"
 
 
 static Node *scanNameSpaceForRefname(ParseState *pstate, Node *nsnode,
-                       char *refname);
+                       const char *refname);
+static Node *scanNameSpaceForRelid(ParseState *pstate, Node *nsnode,
+                                  Oid relid);
+static void scanNameSpaceForConflict(ParseState *pstate, Node *nsnode,
+                        RangeTblEntry *rte1, const char *aliasname1);
 static Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte,
                 char *colname);
 static bool isForUpdate(ParseState *pstate, char *refname);
@@ -45,26 +50,58 @@ static void warnAutoRange(ParseState *pstate, RangeVar *relation);
 
 /*
  * refnameRangeTblEntry
- *   Given a refname, look to see if it matches any RTE.
- *   If so, return a pointer to the RangeTblEntry.
- *   Optionally get its nesting depth (0 = current).   If sublevels_up
- *   is NULL, only consider items at the current nesting level.
+ *   Given a possibly-qualified refname, look to see if it matches any RTE.
+ *   If so, return a pointer to the RangeTblEntry; else return NULL.
+ *
+ *   Optionally get RTE's nesting depth (0 = current) into *sublevels_up.
+ *   If sublevels_up is NULL, only consider items at the current nesting
+ *   level.
+ *
+ * An unqualified refname (schemaname == NULL) can match any RTE with matching
+ * alias, or matching unqualified relname in the case of alias-less relation
+ * RTEs.  It is possible that such a refname matches multiple RTEs in the
+ * nearest nesting level that has a match; if so, we report an error via elog.
+ *
+ * A qualified refname (schemaname != NULL) can only match a relation RTE
+ * that (a) has no alias and (b) is for the same relation identified by
+ * schemaname.refname.  In this case we convert schemaname.refname to a
+ * relation OID and search by relid, rather than by alias name.  This is
+ * peculiar, but it's what SQL92 says to do.
  */
 RangeTblEntry *
 refnameRangeTblEntry(ParseState *pstate,
-                    char *refname,
+                    const char *schemaname,
+                    const char *refname,
                     int *sublevels_up)
 {
+   Oid         relId = InvalidOid;
+
    if (sublevels_up)
        *sublevels_up = 0;
 
+   if (schemaname != NULL)
+   {
+       Oid         namespaceId;
+
+       namespaceId = LookupExplicitNamespace(schemaname);
+       relId = get_relname_relid(refname, namespaceId);
+       if (!OidIsValid(relId))
+           return NULL;
+   }
+
    while (pstate != NULL)
    {
        Node       *nsnode;
 
-       nsnode = scanNameSpaceForRefname(pstate,
-                                        (Node *) pstate->p_namespace,
-                                        refname);
+       if (OidIsValid(relId))
+           nsnode = scanNameSpaceForRelid(pstate,
+                                          (Node *) pstate->p_namespace,
+                                          relId);
+       else
+           nsnode = scanNameSpaceForRefname(pstate,
+                                            (Node *) pstate->p_namespace,
+                                            refname);
+
        if (nsnode)
        {
            /* should get an RTE or JoinExpr */
@@ -84,20 +121,19 @@ refnameRangeTblEntry(ParseState *pstate,
 }
 
 /*
- * Recursively search a namespace for an RTE or joinexpr with given refname.
+ * Recursively search a namespace for an RTE or joinexpr matching the
+ * given unqualified refname.  Return the node if a unique match, or NULL
+ * if no match.  Raise error if multiple matches.
  *
  * The top level of p_namespace is a list, and we recurse into any joins
- * that are not subqueries.  It is also possible to pass an individual
- * join subtree (useful when checking for name conflicts within a scope).
- *
- * Note: we do not worry about the possibility of multiple matches;
- * we assume the code that built the namespace checked for duplicates.
+ * that are not subqueries.
  */
 static Node *
 scanNameSpaceForRefname(ParseState *pstate, Node *nsnode,
-                       char *refname)
+                       const char *refname)
 {
    Node       *result = NULL;
+   Node       *newresult;
 
    if (nsnode == NULL)
        return NULL;
@@ -126,8 +162,11 @@ scanNameSpaceForRefname(ParseState *pstate, Node *nsnode,
            return NULL;
        }
        result = scanNameSpaceForRefname(pstate, j->larg, refname);
+       newresult = scanNameSpaceForRefname(pstate, j->rarg, refname);
        if (!result)
-           result = scanNameSpaceForRefname(pstate, j->rarg, refname);
+           result = newresult;
+       else if (newresult)
+           elog(ERROR, "Table reference \"%s\" is ambiguous", refname);
    }
    else if (IsA(nsnode, List))
    {
@@ -135,9 +174,11 @@ scanNameSpaceForRefname(ParseState *pstate, Node *nsnode,
 
        foreach(l, (List *) nsnode)
        {
-           result = scanNameSpaceForRefname(pstate, lfirst(l), refname);
-           if (result)
-               break;
+           newresult = scanNameSpaceForRefname(pstate, lfirst(l), refname);
+           if (!result)
+               result = newresult;
+           else if (newresult)
+               elog(ERROR, "Table reference \"%s\" is ambiguous", refname);
        }
    }
    else
@@ -146,25 +187,89 @@ scanNameSpaceForRefname(ParseState *pstate, Node *nsnode,
    return result;
 }
 
-/* Convenience subroutine for checkNameSpaceConflicts */
-static void
-scanNameSpaceForConflict(ParseState *pstate, Node *nsnode,
-                        char *refname)
+/*
+ * Recursively search a namespace for a relation RTE matching the
+ * given relation OID.  Return the node if a unique match, or NULL
+ * if no match.  Raise error if multiple matches (which shouldn't
+ * happen if the namespace was checked correctly when it was created).
+ *
+ * The top level of p_namespace is a list, and we recurse into any joins
+ * that are not subqueries.
+ *
+ * See the comments for refnameRangeTblEntry to understand why this
+ * acts the way it does.
+ */
+static Node *
+scanNameSpaceForRelid(ParseState *pstate, Node *nsnode, Oid relid)
 {
-   if (scanNameSpaceForRefname(pstate, nsnode, refname) != NULL)
-       elog(ERROR, "Table name \"%s\" specified more than once", refname);
+   Node       *result = NULL;
+   Node       *newresult;
+
+   if (nsnode == NULL)
+       return NULL;
+   if (IsA(nsnode, RangeTblRef))
+   {
+       int         varno = ((RangeTblRef *) nsnode)->rtindex;
+       RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
+
+       /* yes, the test for alias==NULL should be there... */
+       if (rte->rtekind == RTE_RELATION &&
+           rte->relid == relid &&
+           rte->alias == NULL)
+           result = (Node *) rte;
+   }
+   else if (IsA(nsnode, JoinExpr))
+   {
+       JoinExpr   *j = (JoinExpr *) nsnode;
+
+       if (j->alias)
+       {
+           /*
+            * Tables within an aliased join are invisible from outside
+            * the join, according to the scope rules of SQL92 (the join
+            * is considered a subquery).  So, stop here.
+            */
+           return NULL;
+       }
+       result = scanNameSpaceForRelid(pstate, j->larg, relid);
+       newresult = scanNameSpaceForRelid(pstate, j->rarg, relid);
+       if (!result)
+           result = newresult;
+       else if (newresult)
+           elog(ERROR, "Table reference %u is ambiguous", relid);
+   }
+   else if (IsA(nsnode, List))
+   {
+       List       *l;
+
+       foreach(l, (List *) nsnode)
+       {
+           newresult = scanNameSpaceForRelid(pstate, lfirst(l), relid);
+           if (!result)
+               result = newresult;
+           else if (newresult)
+               elog(ERROR, "Table reference %u is ambiguous", relid);
+       }
+   }
+   else
+       elog(ERROR, "scanNameSpaceForRelid: unexpected node type %d",
+            nodeTag(nsnode));
+   return result;
 }
 
 /*
- * Recursively check for refname conflicts between two namespaces or
+ * Recursively check for name conflicts between two namespaces or
  * namespace subtrees. Raise an error if any is found.
  *
- * Works by recursively scanning namespace1 in the same way that
- * scanNameSpaceForRefname does, and then looking in namespace2 for
- * a match to each refname found in namespace1.
+ * Works by recursively scanning namespace1 for RTEs and join nodes,
+ * and for each one recursively scanning namespace2 for a match.
  *
  * Note: we assume that each given argument does not contain conflicts
  * itself; we just want to know if the two can be merged together.
+ *
+ * Per SQL92, two alias-less plain relation RTEs do not conflict even if
+ * they have the same eref->aliasname (ie, same relation name), if they
+ * are for different relation OIDs (implying they are in different schemas).
  */
 void
 checkNameSpaceConflicts(ParseState *pstate, Node *namespace1,
@@ -177,7 +282,12 @@ checkNameSpaceConflicts(ParseState *pstate, Node *namespace1,
        int         varno = ((RangeTblRef *) namespace1)->rtindex;
        RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
 
-       scanNameSpaceForConflict(pstate, namespace2, rte->eref->aliasname);
+       if (rte->rtekind == RTE_RELATION && rte->alias == NULL)
+           scanNameSpaceForConflict(pstate, namespace2,
+                                    rte, rte->eref->aliasname);
+       else
+           scanNameSpaceForConflict(pstate, namespace2,
+                                    NULL, rte->eref->aliasname);
    }
    else if (IsA(namespace1, JoinExpr))
    {
@@ -185,7 +295,8 @@ checkNameSpaceConflicts(ParseState *pstate, Node *namespace1,
 
        if (j->alias)
        {
-           scanNameSpaceForConflict(pstate, namespace2, j->alias->aliasname);
+           scanNameSpaceForConflict(pstate, namespace2,
+                                    NULL, j->alias->aliasname);
 
            /*
             * Tables within an aliased join are invisible from outside
@@ -202,13 +313,70 @@ checkNameSpaceConflicts(ParseState *pstate, Node *namespace1,
        List       *l;
 
        foreach(l, (List *) namespace1)
+       {
            checkNameSpaceConflicts(pstate, lfirst(l), namespace2);
+       }
    }
    else
        elog(ERROR, "checkNameSpaceConflicts: unexpected node type %d",
             nodeTag(namespace1));
 }
 
+/*
+ * Subroutine for checkNameSpaceConflicts: scan namespace2
+ */
+static void
+scanNameSpaceForConflict(ParseState *pstate, Node *nsnode,
+                        RangeTblEntry *rte1, const char *aliasname1)
+{
+   if (nsnode == NULL)
+       return;
+   if (IsA(nsnode, RangeTblRef))
+   {
+       int         varno = ((RangeTblRef *) nsnode)->rtindex;
+       RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
+
+       if (strcmp(rte->eref->aliasname, aliasname1) != 0)
+           return;             /* definitely no conflict */
+       if (rte->rtekind == RTE_RELATION && rte->alias == NULL &&
+           rte1 != NULL && rte->relid != rte1->relid)
+           return;             /* no conflict per SQL92 rule */
+       elog(ERROR, "Table name \"%s\" specified more than once",
+            aliasname1);
+   }
+   else if (IsA(nsnode, JoinExpr))
+   {
+       JoinExpr   *j = (JoinExpr *) nsnode;
+
+       if (j->alias)
+       {
+           if (strcmp(j->alias->aliasname, aliasname1) == 0)
+               elog(ERROR, "Table name \"%s\" specified more than once",
+                    aliasname1);
+           /*
+            * Tables within an aliased join are invisible from outside
+            * the join, according to the scope rules of SQL92 (the join
+            * is considered a subquery).  So, stop here.
+            */
+           return;
+       }
+       scanNameSpaceForConflict(pstate, j->larg, rte1, aliasname1);
+       scanNameSpaceForConflict(pstate, j->rarg, rte1, aliasname1);
+   }
+   else if (IsA(nsnode, List))
+   {
+       List       *l;
+
+       foreach(l, (List *) nsnode)
+       {
+           scanNameSpaceForConflict(pstate, lfirst(l), rte1, aliasname1);
+       }
+   }
+   else
+       elog(ERROR, "scanNameSpaceForConflict: unexpected node type %d",
+            nodeTag(nsnode));
+}
+
 /*
  * given an RTE, return RT index (starting with 1) of the entry,
  * and optionally get its nesting depth (0 = current). If sublevels_up
@@ -403,24 +571,29 @@ colnameToVar(ParseState *pstate, char *colname)
 
 /*
  * qualifiedNameToVar
- *   Search for a qualified column name (refname + column name).
+ *   Search for a qualified column name: either refname.colname or
+ *   schemaname.relname.colname.
+ *
  *   If found, return the appropriate Var node.
  *   If not found, return NULL.  If the name proves ambiguous, raise error.
  */
 Node *
-qualifiedNameToVar(ParseState *pstate, char *refname, char *colname,
+qualifiedNameToVar(ParseState *pstate,
+                  char *schemaname,
+                  char *refname,
+                  char *colname,
                   bool implicitRTEOK)
 {
    RangeTblEntry *rte;
    int         sublevels_up;
 
-   rte = refnameRangeTblEntry(pstate, refname, &sublevels_up);
+   rte = refnameRangeTblEntry(pstate, schemaname, refname, &sublevels_up);
 
    if (rte == NULL)
    {
        if (!implicitRTEOK)
            return NULL;
-       rte = addImplicitRTE(pstate, makeRangeVar(NULL, refname));
+       rte = addImplicitRTE(pstate, makeRangeVar(schemaname, refname));
    }
 
    return scanRTEForColumn(pstate, rte, colname);
index 1e51f23d7049823760f019e477e0635c8bb1cce7..6f5b5bab90b8bd1e23244d260647c2bbc4d39e32 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.86 2002/08/02 18:15:07 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.87 2002/08/08 01:44:31 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -155,11 +155,11 @@ transformTargetList(ParseState *pstate, List *targetlist)
                        break;
                }
 
-               /* XXX do something with schema name */
-               rte = refnameRangeTblEntry(pstate, relname,
+               rte = refnameRangeTblEntry(pstate, schemaname, relname,
                                           &sublevels_up);
                if (rte == NULL)
-                   rte = addImplicitRTE(pstate, makeRangeVar(NULL, relname));
+                   rte = addImplicitRTE(pstate, makeRangeVar(schemaname,
+                                                             relname));
 
                p_target = nconc(p_target,
                                 expandRelAttrs(pstate, rte));
index 5999ad96285c2b0e5235e8f577015ef01048e7d9..98bbba534aa742a2e2d6c97b7a0737a99e945ce8 100644 (file)
@@ -3,7 +3,7 @@
  *             back to source text
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.112 2002/07/18 23:11:28 petere Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.113 2002/08/08 01:44:31 tgl Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -131,7 +131,9 @@ static Node *get_rule_sortgroupclause(SortClause *srt, List *tlist,
                         bool force_colno,
                         deparse_context *context);
 static void get_names_for_var(Var *var, deparse_context *context,
-                 char **refname, char **attname);
+                 char **schemaname, char **refname, char **attname);
+static RangeTblEntry *find_rte_by_refname(const char *refname,
+                                         deparse_context *context);
 static void get_rule_expr(Node *node, deparse_context *context);
 static void get_oper_expr(Expr *expr, deparse_context *context);
 static void get_func_expr(Expr *expr, deparse_context *context);
@@ -1204,10 +1206,11 @@ get_basic_select_query(Query *query, deparse_context *context)
        else
        {
            Var        *var = (Var *) (tle->expr);
+           char       *schemaname;
            char       *refname;
            char       *attname;
 
-           get_names_for_var(var, context, &refname, &attname);
+           get_names_for_var(var, context, &schemaname, &refname, &attname);
            tell_as = (attname == NULL ||
                       strcmp(attname, tle->resdom->resname) != 0);
        }
@@ -1513,17 +1516,22 @@ get_utility_query_def(Query *query, deparse_context *context)
 
 
 /*
- * Get the relation refname and attname for a (possibly nonlocal) Var.
+ * Get the schemaname, refname and attname for a (possibly nonlocal) Var.
+ *
+ * schemaname is usually returned as NULL.  It will be non-null only if
+ * use of the unqualified refname would find the wrong RTE.
  *
  * refname will be returned as NULL if the Var references an unnamed join.
  * In this case the Var *must* be displayed without any qualification.
  *
  * attname will be returned as NULL if the Var represents a whole tuple
- * of the relation.
+ * of the relation.  (Typically we'd want to display the Var as "foo.*",
+ * but it's convenient to return NULL to make it easier for callers to
+ * distinguish this case.)
  */
 static void
 get_names_for_var(Var *var, deparse_context *context,
-                 char **refname, char **attname)
+                 char **schemaname, char **refname, char **attname)
 {
    List       *nslist = context->namespaces;
    int         sup = var->varlevelsup;
@@ -1552,10 +1560,29 @@ get_names_for_var(Var *var, deparse_context *context,
             var->varno);
 
    /* Emit results */
-   if (rte->rtekind == RTE_JOIN && rte->alias == NULL)
-       *refname = NULL;
-   else
-       *refname = rte->eref->aliasname;
+   *schemaname = NULL;         /* default assumptions */
+   *refname = rte->eref->aliasname;
+
+   /* Exceptions occur only if the RTE is alias-less */
+   if (rte->alias == NULL)
+   {
+       if (rte->rtekind == RTE_RELATION)
+       {
+           /*
+            * It's possible that use of the bare refname would find another
+            * more-closely-nested RTE, or be ambiguous, in which case
+            * we need to specify the schemaname to avoid these errors.
+            */
+           if (find_rte_by_refname(rte->eref->aliasname, context) != rte)
+               *schemaname =
+                   get_namespace_name(get_rel_namespace(rte->relid));
+       }
+       else if (rte->rtekind == RTE_JOIN)
+       {
+           /* Unnamed join has neither schemaname nor refname */
+           *refname = NULL;
+       }
+   }
 
    if (var->varattno == InvalidAttrNumber)
        *attname = NULL;
@@ -1563,6 +1590,61 @@ get_names_for_var(Var *var, deparse_context *context,
        *attname = get_rte_attribute_name(rte, var->varattno);
 }
 
+/*
+ * find_rte_by_refname     - look up an RTE by refname in a deparse context
+ *
+ * Returns NULL if there is no matching RTE or the refname is ambiguous.
+ *
+ * NOTE: this code is not really correct since it does not take account of
+ * the fact that not all the RTEs in a rangetable may be visible from the
+ * point where a Var reference appears.  For the purposes we need, however,
+ * the only consequence of a false match is that we might stick a schema
+ * qualifier on a Var that doesn't really need it.  So it seems close
+ * enough.
+ */
+static RangeTblEntry *
+find_rte_by_refname(const char *refname, deparse_context *context)
+{
+   RangeTblEntry *result = NULL;
+   List       *nslist;
+
+   foreach(nslist, context->namespaces)
+   {
+       deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist);
+       List       *rtlist;
+
+       foreach(rtlist, dpns->rtable)
+       {
+           RangeTblEntry *rte = (RangeTblEntry *) lfirst(rtlist);
+
+           if (strcmp(rte->eref->aliasname, refname) == 0)
+           {
+               if (result)
+                   return NULL; /* it's ambiguous */
+               result = rte;
+           }
+       }
+       if (dpns->outer_rte &&
+           strcmp(dpns->outer_rte->eref->aliasname, refname) == 0)
+       {
+           if (result)
+               return NULL;    /* it's ambiguous */
+           result = dpns->outer_rte;
+       }
+       if (dpns->inner_rte &&
+           strcmp(dpns->inner_rte->eref->aliasname, refname) == 0)
+       {
+           if (result)
+               return NULL;    /* it's ambiguous */
+           result = dpns->inner_rte;
+       }
+       if (result)
+           break;
+   }
+   return result;
+}
+
+
 /* ----------
  * get_rule_expr           - Parse back an expression
  * ----------
@@ -1592,24 +1674,30 @@ get_rule_expr(Node *node, deparse_context *context)
        case T_Var:
            {
                Var        *var = (Var *) node;
+               char       *schemaname;
                char       *refname;
                char       *attname;
 
-               get_names_for_var(var, context, &refname, &attname);
+               get_names_for_var(var, context,
+                                 &schemaname, &refname, &attname);
                if (refname && (context->varprefix || attname == NULL))
                {
+                   if (schemaname)
+                       appendStringInfo(buf, "%s.",
+                                        quote_identifier(schemaname));
+
                    if (strcmp(refname, "*NEW*") == 0)
-                       appendStringInfo(buf, "new");
+                       appendStringInfo(buf, "new.");
                    else if (strcmp(refname, "*OLD*") == 0)
-                       appendStringInfo(buf, "old");
+                       appendStringInfo(buf, "old.");
                    else
-                       appendStringInfo(buf, "%s",
+                       appendStringInfo(buf, "%s.",
                                         quote_identifier(refname));
-                   if (attname)
-                       appendStringInfoChar(buf, '.');
                }
                if (attname)
                    appendStringInfo(buf, "%s", quote_identifier(attname));
+               else
+                   appendStringInfo(buf, "*");
            }
            break;
 
index f7e4ec32fdf030743577d0293ed46ffade116da4..dd6c786ef21d58b75e80f19340763e4f5e3f01f6 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: namespace.h,v 1.18 2002/08/06 05:40:45 ishii Exp $
+ * $Id: namespace.h,v 1.19 2002/08/08 01:44:31 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -69,7 +69,7 @@ extern bool OpclassIsVisible(Oid opcid);
 extern void DeconstructQualifiedName(List *names,
                                     char **nspname_p,
                                     char **objname_p);
-extern Oid LookupExplicitNamespace(char *nspname);
+extern Oid LookupExplicitNamespace(const char *nspname);
 
 extern Oid QualifiedNameGetCreationNamespace(List *names, char **objname_p);
 extern RangeVar *makeRangeVarFromNameList(List *names);
index 38729a81a64e7f3ef4bbe34e6bf6cea1117a9e38..5d405906178639776e2e0c39349ad9523ee04a63 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_relation.h,v 1.37 2002/08/05 02:30:50 tgl Exp $
+ * $Id: parse_relation.h,v 1.38 2002/08/08 01:44:31 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +17,8 @@
 #include "parser/parse_node.h"
 
 extern RangeTblEntry *refnameRangeTblEntry(ParseState *pstate,
-                       char *refname,
+                       const char *schemaname,
+                       const char *refname,
                        int *sublevels_up);
 extern void checkNameSpaceConflicts(ParseState *pstate, Node *namespace1,
                        Node *namespace2);
@@ -25,8 +26,11 @@ extern int RTERangeTablePosn(ParseState *pstate,
                  RangeTblEntry *rte,
                  int *sublevels_up);
 extern Node *colnameToVar(ParseState *pstate, char *colname);
-extern Node *qualifiedNameToVar(ParseState *pstate, char *refname,
-                  char *colname, bool implicitRTEOK);
+extern Node *qualifiedNameToVar(ParseState *pstate,
+                               char *schemaname,
+                               char *refname,
+                               char *colname,
+                               bool implicitRTEOK);
 extern RangeTblEntry *addRangeTableEntry(ParseState *pstate,
                   RangeVar *relation,
                   Alias *alias,