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

First cut at full support for OUTER JOINs. There are still a few loose

ends to clean up (see my message of same date to pghackers), but mostly
it works.  INITDB REQUIRED!
This commit is contained in:
Tom Lane
2000-09-12 21:07:18 +00:00
parent b5c0ab278b
commit ed5003c584
93 changed files with 6386 additions and 4262 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.46 2000/08/08 15:42:04 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.47 2000/09/12 21:07:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -20,14 +20,25 @@
#include "access/htup.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "parser/parsetree.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
#include "rewrite/rewriteManip.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
static Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte,
char *colname);
static Node *scanJoinForColumn(JoinExpr *join, char *colname,
int sublevels_up);
static List *expandNamesVars(ParseState *pstate, List *names, List *vars);
static void warnAutoRange(ParseState *pstate, char *refname);
/*
* Information defining the "system" attributes of every relation.
*/
@ -65,40 +76,96 @@ static struct
#define SPECIALS ((int) (sizeof(special_attr)/sizeof(special_attr[0])))
#ifdef NOT_USED
/* refnameRangeTableEntries()
* Given refname, return a list of range table entries
* This is possible with JOIN syntax, where tables in a join
* acquire the same reference name.
* - thomas 2000-01-20
* But at the moment we aren't carrying along a full list of
* table/column aliases, so we don't have the full mechanism
* to support outer joins in place yet.
* - thomas 2000-03-04
/*
* refnameRangeOrJoinEntry
* Given a refname, look to see if it matches any RTE or join table.
* If so, return a pointer to the RangeTblEntry or JoinExpr.
* Optionally get its nesting depth (0 = current). If sublevels_up
* is NULL, only consider items at the current nesting level.
*/
static List *
refnameRangeTableEntries(ParseState *pstate, char *refname)
Node *
refnameRangeOrJoinEntry(ParseState *pstate,
char *refname,
int *sublevels_up)
{
List *rteList = NULL;
List *temp;
if (sublevels_up)
*sublevels_up = 0;
while (pstate != NULL)
{
List *temp;
JoinExpr *join;
/*
* Check the rangetable for RTEs; if no match, recursively scan
* the jointree for join tables. We assume that no duplicate
* entries have been made in any one nesting level.
*/
foreach(temp, pstate->p_rtable)
{
RangeTblEntry *rte = lfirst(temp);
if (strcmp(rte->eref->relname, refname) == 0)
rteList = lappend(rteList, rte);
return (Node *) rte;
}
pstate = pstate->parentParseState;
}
return rteList;
}
#endif
/* given refname, return a pointer to the range table entry */
join = scanJoinTreeForRefname((Node *) pstate->p_jointree, refname);
if (join)
return (Node *) join;
pstate = pstate->parentParseState;
if (sublevels_up)
(*sublevels_up)++;
else
break;
}
return NULL;
}
/* Recursively search a jointree for a joinexpr with given refname */
JoinExpr *
scanJoinTreeForRefname(Node *jtnode, char *refname)
{
JoinExpr *result = NULL;
if (jtnode == NULL)
return NULL;
if (IsA(jtnode, List))
{
List *l;
foreach(l, (List *) jtnode)
{
result = scanJoinTreeForRefname(lfirst(l), refname);
if (result)
break;
}
}
else if (IsA(jtnode, RangeTblRef))
{
/* ignore ... */
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
if (j->alias && strcmp(j->alias->relname, refname) == 0)
return j;
result = scanJoinTreeForRefname(j->larg, refname);
if (! result)
result = scanJoinTreeForRefname(j->rarg, refname);
}
else
elog(ERROR, "scanJoinTreeForRefname: unexpected node type %d",
nodeTag(jtnode));
return result;
}
/*
* given refname, return a pointer to the range table entry.
*
* NOTE that this routine will ONLY find RTEs, not join tables.
*/
RangeTblEntry *
refnameRangeTableEntry(ParseState *pstate, char *refname)
{
@ -118,9 +185,13 @@ refnameRangeTableEntry(ParseState *pstate, char *refname)
return NULL;
}
/* given refname, return RT index (starting with 1) of the relation,
/*
* given refname, return RT index (starting with 1) of the relation,
* and optionally get its nesting depth (0 = current). If sublevels_up
* is NULL, only consider rels at the current nesting level.
* A zero result means name not found.
*
* NOTE that this routine will ONLY find RTEs, not join tables.
*/
int
refnameRangeTablePosn(ParseState *pstate, char *refname, int *sublevels_up)
@ -152,114 +223,264 @@ refnameRangeTablePosn(ParseState *pstate, char *refname, int *sublevels_up)
}
/*
* returns range entry if found, else NULL
* 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.
* Raises error if RTE not found.
*/
RangeTblEntry *
colnameRangeTableEntry(ParseState *pstate, char *colname)
int
RTERangeTablePosn(ParseState *pstate, RangeTblEntry *rte, int *sublevels_up)
{
List *et;
List *rtable;
RangeTblEntry *rte_result = NULL;
int index;
List *temp;
if (sublevels_up)
*sublevels_up = 0;
while (pstate != NULL)
{
if (pstate->p_is_rule)
rtable = lnext(lnext(pstate->p_rtable));
else
rtable = pstate->p_rtable;
foreach(et, rtable)
index = 1;
foreach(temp, pstate->p_rtable)
{
RangeTblEntry *rte_candidate = NULL;
RangeTblEntry *rte = lfirst(et);
/* only consider RTEs mentioned in FROM or UPDATE/DELETE */
if (!rte->inFromCl && rte != pstate->p_target_rangetblentry)
continue;
if (rte->eref->attrs != NULL)
{
List *c;
foreach(c, rte->ref->attrs)
{
if (strcmp(strVal(lfirst(c)), colname) == 0)
{
if (rte_candidate != NULL)
elog(ERROR, "Column '%s' is ambiguous"
" (internal error)", colname);
rte_candidate = rte;
}
}
}
/*
* Even if we have an attribute list in the RTE, look for the
* column here anyway. This is the only way we will find
* implicit columns like "oid". - thomas 2000-02-07
*/
if ((rte_candidate == NULL)
&& (get_attnum(rte->relid, colname) != InvalidAttrNumber))
rte_candidate = rte;
if (rte_candidate == NULL)
continue;
if (rte_result != NULL)
{
if (!pstate->p_is_insert ||
rte != pstate->p_target_rangetblentry)
elog(ERROR, "Column '%s' is ambiguous", colname);
}
else
rte_result = rte;
if (rte == (RangeTblEntry *) lfirst(temp))
return index;
index++;
}
if (rte_result != NULL)
break; /* found */
pstate = pstate->parentParseState;
if (sublevels_up)
(*sublevels_up)++;
else
break;
}
return rte_result;
elog(ERROR, "RTERangeTablePosn: RTE not found (internal error)");
return 0; /* keep compiler quiet */
}
/*
* put new entry in pstate p_rtable structure, or return pointer
* if pstate null
* scanRTEForColumn
* Search the column names of a single RTE for the given name.
* If found, return an appropriate Var node, else return NULL.
* If the name proves ambiguous within this RTE, raise error.
*/
static Node *
scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname)
{
Node *result = NULL;
int attnum = 0;
List *c;
/*
* Scan the user column names (or aliases) for a match.
* Complain if multiple matches.
*/
foreach(c, rte->eref->attrs)
{
attnum++;
if (strcmp(strVal(lfirst(c)), colname) == 0)
{
if (result)
elog(ERROR, "Column reference \"%s\" is ambiguous", colname);
result = (Node *) make_var(pstate, rte, attnum);
}
}
/*
* If we have a unique match, return it. Note that this allows a user
* alias to override a system column name (such as OID) without error.
*/
if (result)
return result;
/*
* If the RTE represents a table (not a sub-select), consider system
* column names.
*/
if (rte->relid != InvalidOid)
{
attnum = specialAttNum(colname);
if (attnum != InvalidAttrNumber)
result = (Node *) make_var(pstate, rte, attnum);
}
return result;
}
/*
* scanJoinForColumn
* Search the column names of a single join table for the given name.
* If found, return an appropriate Var node or expression, else return NULL.
* If the name proves ambiguous within this jointable, raise error.
*/
static Node *
scanJoinForColumn(JoinExpr *join, char *colname, int sublevels_up)
{
Node *result = NULL;
int attnum = 0;
List *c;
foreach(c, join->colnames)
{
attnum++;
if (strcmp(strVal(lfirst(c)), colname) == 0)
{
if (result)
elog(ERROR, "Column reference \"%s\" is ambiguous", colname);
result = copyObject(nth(attnum-1, join->colvars));
/*
* If referencing an uplevel join item, we must adjust
* sublevels settings in the copied expression.
*/
if (sublevels_up > 0)
IncrementVarSublevelsUp(result, sublevels_up, 0);
}
}
return result;
}
/*
* colnameToVar
* Search for an unqualified column name.
* If found, return the appropriate Var node (or expression).
* If not found, return NULL. If the name proves ambiguous, raise error.
*/
Node *
colnameToVar(ParseState *pstate, char *colname)
{
Node *result = NULL;
ParseState *orig_pstate = pstate;
int levels_up = 0;
while (pstate != NULL)
{
List *jt;
/*
* We want to look only at top-level jointree items, and even for
* those, ignore RTEs that are marked as not inFromCl and not
* the query's target relation.
*/
foreach(jt, pstate->p_jointree)
{
Node *jtnode = (Node *) lfirst(jt);
Node *newresult = NULL;
if (IsA(jtnode, RangeTblRef))
{
int varno = ((RangeTblRef *) jtnode)->rtindex;
RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
if (! rte->inFromCl &&
rte != pstate->p_target_rangetblentry)
continue;
/* use orig_pstate here to get the right sublevels_up */
newresult = scanRTEForColumn(orig_pstate, rte, colname);
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
newresult = scanJoinForColumn(j, colname, levels_up);
}
else
elog(ERROR, "colnameToVar: unexpected node type %d",
nodeTag(jtnode));
if (newresult)
{
if (result)
elog(ERROR, "Column reference \"%s\" is ambiguous",
colname);
result = newresult;
}
}
if (result != NULL)
break; /* found */
pstate = pstate->parentParseState;
levels_up++;
}
return result;
}
/*
* qualifiedNameToVar
* Search for a qualified column name (refname + column name).
* If found, return the appropriate Var node (or expression).
* If not found, return NULL. If the name proves ambiguous, raise error.
*/
Node *
qualifiedNameToVar(ParseState *pstate, char *refname, char *colname,
bool implicitRTEOK)
{
Node *result;
Node *rteorjoin;
int sublevels_up;
rteorjoin = refnameRangeOrJoinEntry(pstate, refname, &sublevels_up);
if (rteorjoin == NULL)
{
if (! implicitRTEOK)
return NULL;
rteorjoin = (Node *) addImplicitRTE(pstate, refname);
sublevels_up = 0;
}
if (IsA(rteorjoin, RangeTblEntry))
result = scanRTEForColumn(pstate, (RangeTblEntry *) rteorjoin,
colname);
else if (IsA(rteorjoin, JoinExpr))
result = scanJoinForColumn((JoinExpr *) rteorjoin,
colname, sublevels_up);
else
{
elog(ERROR, "qualifiedNameToVar: unexpected node type %d",
nodeTag(rteorjoin));
result = NULL; /* keep compiler quiet */
}
return result;
}
/*
* Add an entry to the pstate's range table (p_rtable), unless the
* specified refname is already present, in which case raise error.
*
* If pstate is NULL, we just build an RTE and return it without worrying
* about membership in an rtable list.
*/
RangeTblEntry *
addRangeTableEntry(ParseState *pstate,
char *relname,
Attr *ref,
Attr *alias,
bool inh,
bool inFromCl,
bool inJoinSet)
bool inFromCl)
{
char *refname = alias ? alias->relname : relname;
Relation rel;
RangeTblEntry *rte;
Attr *eref;
int maxattrs;
int sublevels_up;
int numaliases;
int varattno;
/* Look for an existing rte, if available... */
/* Check for conflicting RTE or jointable alias (at level 0 only) */
if (pstate != NULL)
{
int rt_index = refnameRangeTablePosn(pstate, ref->relname,
&sublevels_up);
Node *rteorjoin = refnameRangeOrJoinEntry(pstate, refname, NULL);
if (rt_index != 0 && (!inFromCl || sublevels_up == 0))
{
if (!strcmp(ref->relname, "*OLD*") || !strcmp(ref->relname, "*NEW*"))
return (RangeTblEntry *) nth(rt_index - 1, pstate->p_rtable);
elog(ERROR, "Table name '%s' specified more than once", ref->relname);
}
if (rteorjoin)
elog(ERROR, "Table name \"%s\" specified more than once",
refname);
}
rte = makeNode(RangeTblEntry);
rte->relname = relname;
rte->ref = ref;
rte->alias = alias;
/*
* Get the rel's OID. This access also ensures that we have an
@ -271,30 +492,34 @@ addRangeTableEntry(ParseState *pstate,
rte->relid = RelationGetRelid(rel);
maxattrs = RelationGetNumberOfAttributes(rel);
eref = copyObject(ref);
if (maxattrs < length(eref->attrs))
elog(ERROR, "Table '%s' has %d columns available but %d columns specified",
relname, maxattrs, length(eref->attrs));
eref = alias ? copyObject(alias) : makeAttr(refname, NULL);
numaliases = length(eref->attrs);
if (maxattrs < numaliases)
elog(ERROR, "Table \"%s\" has %d columns available but %d columns specified",
refname, maxattrs, numaliases);
/* fill in any unspecified alias columns */
for (varattno = length(eref->attrs); varattno < maxattrs; varattno++)
for (varattno = numaliases; varattno < maxattrs; varattno++)
{
char *attrname;
attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
eref->attrs = lappend(eref->attrs, makeString(attrname));
}
heap_close(rel, AccessShareLock);
rte->eref = eref;
/*
* Flags: - this RTE should be expanded to include descendant tables,
* - this RTE is in the FROM clause, - this RTE should be included in
* the planner's final join.
heap_close(rel, AccessShareLock);
/*----------
* Flags:
* - this RTE should be expanded to include descendant tables,
* - this RTE is in the FROM clause,
* - this RTE should not be checked for access rights.
*----------
*/
rte->inh = inh;
rte->inFromCl = inFromCl;
rte->inJoinSet = inJoinSet;
rte->skipAcl = false; /* always starts out false */
/*
@ -306,75 +531,78 @@ addRangeTableEntry(ParseState *pstate,
return rte;
}
/* expandTable()
* Populates an Attr with table name and column names
* This is similar to expandAll(), but does not create an RTE
* if it does not already exist.
* - thomas 2000-01-19
/*
* Add the given RTE as a top-level entry in the pstate's join tree,
* unless there already is an entry for it.
*/
Attr *
expandTable(ParseState *pstate, char *refname, bool getaliases)
void
addRTEtoJoinTree(ParseState *pstate, RangeTblEntry *rte)
{
Attr *attr;
RangeTblEntry *rte;
Relation rel;
int varattno,
maxattrs;
int rtindex = RTERangeTablePosn(pstate, rte, NULL);
List *jt;
RangeTblRef *rtr;
rte = refnameRangeTableEntry(pstate, refname);
if (getaliases && (rte != NULL))
return rte->eref;
if (rte != NULL)
rel = heap_open(rte->relid, AccessShareLock);
else
rel = heap_openr(refname, AccessShareLock);
if (rel == NULL)
elog(ERROR, "Relation '%s' not found", refname);
maxattrs = RelationGetNumberOfAttributes(rel);
attr = makeAttr(refname, NULL);
for (varattno = 0; varattno < maxattrs; varattno++)
foreach(jt, pstate->p_jointree)
{
char *attrname;
Node *n = (Node *) lfirst(jt);
#ifdef _DROP_COLUMN_HACK__
if (COLUMN_IS_DROPPED(rel->rd_att->attrs[varattno]))
continue;
#endif /* _DROP_COLUMN_HACK__ */
attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
attr->attrs = lappend(attr->attrs, makeString(attrname));
if (IsA(n, RangeTblRef))
{
if (rtindex == ((RangeTblRef *) n)->rtindex)
return; /* it's already being joined to */
}
}
heap_close(rel, AccessShareLock);
return attr;
/* Not present, so add it */
rtr = makeNode(RangeTblRef);
rtr->rtindex = rtindex;
pstate->p_jointree = lappend(pstate->p_jointree, rtr);
}
/*
* expandAll -
* makes a list of attributes
* Add a POSTQUEL-style implicit RTE.
*
* We assume caller has already checked that there is no such RTE now.
*/
List *
expandAll(ParseState *pstate, char *relname, Attr *ref, int *this_resno)
RangeTblEntry *
addImplicitRTE(ParseState *pstate, char *relname)
{
List *te_list = NIL;
RangeTblEntry *rte;
rte = addRangeTableEntry(pstate, relname, NULL, false, false);
addRTEtoJoinTree(pstate, rte);
warnAutoRange(pstate, relname);
return rte;
}
/* expandRTE()
*
* Given a rangetable entry, create lists of its column names (aliases if
* provided, else real names) and Vars for each column. Only user columns
* are considered, since this is primarily used to expand '*' and determine
* the contents of JOIN tables.
*
* If only one of the two kinds of output list is needed, pass NULL for the
* output pointer for the unwanted one.
*/
void
expandRTE(ParseState *pstate, RangeTblEntry *rte,
List **colnames, List **colvars)
{
Relation rel;
int varattno,
maxattrs;
maxattrs,
rtindex,
sublevels_up;
rte = refnameRangeTableEntry(pstate, ref->relname);
if (rte == NULL)
{
rte = addRangeTableEntry(pstate, relname, ref,
FALSE, FALSE, TRUE);
warnAutoRange(pstate, ref->relname);
}
if (colnames)
*colnames = NIL;
if (colvars)
*colvars = NIL;
/* Need the RT index of the entry for creating Vars */
rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up);
rel = heap_open(rte->relid, AccessShareLock);
@ -382,42 +610,105 @@ expandAll(ParseState *pstate, char *relname, Attr *ref, int *this_resno)
for (varattno = 0; varattno < maxattrs; varattno++)
{
char *attrname;
char *label;
Var *varnode;
TargetEntry *te = makeNode(TargetEntry);
Form_pg_attribute attr = rel->rd_att->attrs[varattno];
#ifdef _DROP_COLUMN_HACK__
if (COLUMN_IS_DROPPED(rel->rd_att->attrs[varattno]))
if (COLUMN_IS_DROPPED(attr))
continue;
#endif /* _DROP_COLUMN_HACK__ */
attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
/*
* varattno is zero-based, so check that length() is always
* greater
*/
if (length(rte->eref->attrs) > varattno)
label = pstrdup(strVal(nth(varattno, rte->eref->attrs)));
else
label = attrname;
varnode = make_var(pstate, rte->relid, relname, attrname);
if (colnames)
{
char *label;
/*
* Even if the elements making up a set are complex, the set
* itself is not.
*/
if (varattno < length(rte->eref->attrs))
label = strVal(nth(varattno, rte->eref->attrs));
else
label = NameStr(attr->attname);
*colnames = lappend(*colnames, makeString(pstrdup(label)));
}
te->resdom = makeResdom((AttrNumber) (*this_resno)++,
varnode->vartype,
varnode->vartypmod,
label,
false);
te->expr = (Node *) varnode;
te_list = lappend(te_list, te);
if (colvars)
{
Var *varnode;
varnode = makeVar(rtindex, attr->attnum,
attr->atttypid, attr->atttypmod,
sublevels_up);
*colvars = lappend(*colvars, varnode);
}
}
heap_close(rel, AccessShareLock);
}
/*
* expandRelAttrs -
* makes a list of TargetEntry nodes for the attributes of the rel
*/
List *
expandRelAttrs(ParseState *pstate, RangeTblEntry *rte)
{
List *name_list,
*var_list;
expandRTE(pstate, rte, &name_list, &var_list);
return expandNamesVars(pstate, name_list, var_list);
}
/*
* expandJoinAttrs -
* makes a list of TargetEntry nodes for the attributes of the join
*/
List *
expandJoinAttrs(ParseState *pstate, JoinExpr *join, int sublevels_up)
{
List *vars;
vars = copyObject(join->colvars);
/*
* If referencing an uplevel join item, we must adjust
* sublevels settings in the copied expression.
*/
if (sublevels_up > 0)
IncrementVarSublevelsUp((Node *) vars, sublevels_up, 0);
return expandNamesVars(pstate,
copyObject(join->colnames),
vars);
}
/*
* expandNamesVars -
* Workhorse for "*" expansion: produce a list of targetentries
* given lists of column names (as String nodes) and var references.
*/
static List *
expandNamesVars(ParseState *pstate, List *names, List *vars)
{
List *te_list = NIL;
while (names)
{
char *label = strVal(lfirst(names));
Node *varnode = (Node *) lfirst(vars);
TargetEntry *te = makeNode(TargetEntry);
te->resdom = makeResdom((AttrNumber) (pstate->p_last_resno)++,
exprType(varnode),
exprTypmod(varnode),
label,
false);
te->expr = varnode;
te_list = lappend(te_list, te);
names = lnext(names);
vars = lnext(vars);
}
Assert(vars == NIL); /* lists not same length? */
return te_list;
}
@ -531,11 +822,17 @@ attnumTypeId(Relation rd, int attid)
return rd->rd_att->attrs[attid - 1]->atttypid;
}
void
/*
* Generate a warning about an implicit RTE, if appropriate.
*
* Our current theory on this is that we should allow "SELECT foo.*"
* but warn about a mixture of explicit and implicit RTEs.
*/
static void
warnAutoRange(ParseState *pstate, char *refname)
{
List *temp;
bool foundInFromCl = false;
List *temp;
foreach(temp, pstate->p_rtable)
{
@ -548,8 +845,8 @@ warnAutoRange(ParseState *pstate, char *refname)
}
}
if (foundInFromCl)
elog(NOTICE, "Adding missing FROM-clause entry%s for table %s",
pstate->parentParseState != NULL ? " in subquery" : "",
refname);
elog(NOTICE, "Adding missing FROM-clause entry%s for table \"%s\"",
pstate->parentParseState != NULL ? " in subquery" : "",
refname);
}