OSDN Git Service

Cause schema-qualified FROM items and schema-qualified variable references
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 8 Aug 2002 01:44:31 +0000 (01:44 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
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 bc20980..4999ea4 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 3cd09a9..85cdc43 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 677acf9..edd0e81 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",
@@ -1226,6 +1232,21 @@ ParseComplexProjection(ParseState *pstate,
 }
 
 /*
+ * 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 8a15c0f..229eab8 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,7 +313,9 @@ checkNameSpaceConflicts(ParseState *pstate, Node *namespace1,
                List       *l;
 
                foreach(l, (List *) namespace1)
+               {
                        checkNameSpaceConflicts(pstate, lfirst(l), namespace2);
+               }
        }
        else
                elog(ERROR, "checkNameSpaceConflicts: unexpected node type %d",
@@ -210,6 +323,61 @@ checkNameSpaceConflicts(ParseState *pstate, Node *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
  * is NULL, only consider rels at the current nesting level.
@@ -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 1e51f23..6f5b5ba 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 5999ad9..98bbba5 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 f7e4ec3..dd6c786 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 38729a8..5d40590 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,