1
0
mirror of https://github.com/postgres/postgres.git synced 2025-08-22 21:53:06 +03:00

Cause schema-qualified FROM items and schema-qualified variable references

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?'
This commit is contained in:
Tom Lane
2002-08-08 01:44:31 +00:00
parent e42f8e32e9
commit b084cc3504
8 changed files with 375 additions and 86 deletions

View 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 */

View 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

View 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"
@@ -33,7 +34,11 @@
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);

View 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));