mirror of
https://github.com/postgres/postgres.git
synced 2025-07-12 21:01:52 +03:00
(WAL logging for this is not done yet, however.) Clean up a number of really crufty things that are no longer needed now that DROP behaves nicely. Make temp table mapper do the right things when drop or rename affecting a temp table is rolled back. Also, remove "relation modified while in use" error check, in favor of locking tables at first reference and holding that lock throughout the statement.
1092 lines
25 KiB
C
1092 lines
25 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* parse_relation.c
|
|
* parser support routines dealing with relations
|
|
*
|
|
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.50 2000/11/08 22:09:58 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include <ctype.h>
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/heapam.h"
|
|
#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/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 bool isForUpdate(ParseState *pstate, char *relname);
|
|
static List *expandNamesVars(ParseState *pstate, List *names, List *vars);
|
|
static void warnAutoRange(ParseState *pstate, char *refname);
|
|
|
|
|
|
/*
|
|
* Information defining the "system" attributes of every relation.
|
|
*/
|
|
static struct
|
|
{
|
|
char *attrname; /* name of system attribute */
|
|
int attrnum; /* its attribute number (always < 0) */
|
|
Oid attrtype; /* its type id */
|
|
} special_attr[] =
|
|
|
|
{
|
|
{
|
|
"ctid", SelfItemPointerAttributeNumber, TIDOID
|
|
},
|
|
{
|
|
"oid", ObjectIdAttributeNumber, OIDOID
|
|
},
|
|
{
|
|
"xmin", MinTransactionIdAttributeNumber, XIDOID
|
|
},
|
|
{
|
|
"cmin", MinCommandIdAttributeNumber, CIDOID
|
|
},
|
|
{
|
|
"xmax", MaxTransactionIdAttributeNumber, XIDOID
|
|
},
|
|
{
|
|
"cmax", MaxCommandIdAttributeNumber, CIDOID
|
|
},
|
|
{
|
|
"tableoid", TableOidAttributeNumber, OIDOID
|
|
}
|
|
};
|
|
|
|
#define SPECIALS ((int) (sizeof(special_attr)/sizeof(special_attr[0])))
|
|
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
Node *
|
|
refnameRangeOrJoinEntry(ParseState *pstate,
|
|
char *refname,
|
|
int *sublevels_up)
|
|
{
|
|
if (sublevels_up)
|
|
*sublevels_up = 0;
|
|
|
|
while (pstate != NULL)
|
|
{
|
|
List *temp;
|
|
JoinExpr *join;
|
|
|
|
/*
|
|
* Check the rangetable for RTEs; if no match, recursively scan
|
|
* the joinlist 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)
|
|
return (Node *) rte;
|
|
}
|
|
|
|
join = scanJoinListForRefname((Node *) pstate->p_joinlist, refname);
|
|
if (join)
|
|
return (Node *) join;
|
|
|
|
pstate = pstate->parentParseState;
|
|
if (sublevels_up)
|
|
(*sublevels_up)++;
|
|
else
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Recursively search a joinlist for a joinexpr with given refname
|
|
*
|
|
* Note that during parse analysis, we don't expect to find a FromExpr node
|
|
* in p_joinlist; its top level is just a bare List.
|
|
*/
|
|
JoinExpr *
|
|
scanJoinListForRefname(Node *jtnode, char *refname)
|
|
{
|
|
JoinExpr *result = NULL;
|
|
|
|
if (jtnode == NULL)
|
|
return NULL;
|
|
if (IsA(jtnode, List))
|
|
{
|
|
List *l;
|
|
|
|
foreach(l, (List *) jtnode)
|
|
{
|
|
result = scanJoinListForRefname(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 = scanJoinListForRefname(j->larg, refname);
|
|
if (! result)
|
|
result = scanJoinListForRefname(j->rarg, refname);
|
|
}
|
|
else
|
|
elog(ERROR, "scanJoinListForRefname: 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)
|
|
{
|
|
List *temp;
|
|
|
|
while (pstate != NULL)
|
|
{
|
|
foreach(temp, pstate->p_rtable)
|
|
{
|
|
RangeTblEntry *rte = lfirst(temp);
|
|
|
|
if (strcmp(rte->eref->relname, refname) == 0)
|
|
return rte;
|
|
}
|
|
pstate = pstate->parentParseState;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
int index;
|
|
List *temp;
|
|
|
|
if (sublevels_up)
|
|
*sublevels_up = 0;
|
|
|
|
while (pstate != NULL)
|
|
{
|
|
index = 1;
|
|
foreach(temp, pstate->p_rtable)
|
|
{
|
|
RangeTblEntry *rte = lfirst(temp);
|
|
|
|
if (strcmp(rte->eref->relname, refname) == 0)
|
|
return index;
|
|
index++;
|
|
}
|
|
pstate = pstate->parentParseState;
|
|
if (sublevels_up)
|
|
(*sublevels_up)++;
|
|
else
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
int
|
|
RTERangeTablePosn(ParseState *pstate, RangeTblEntry *rte, int *sublevels_up)
|
|
{
|
|
int index;
|
|
List *temp;
|
|
|
|
if (sublevels_up)
|
|
*sublevels_up = 0;
|
|
|
|
while (pstate != NULL)
|
|
{
|
|
index = 1;
|
|
foreach(temp, pstate->p_rtable)
|
|
{
|
|
if (rte == (RangeTblEntry *) lfirst(temp))
|
|
return index;
|
|
index++;
|
|
}
|
|
pstate = pstate->parentParseState;
|
|
if (sublevels_up)
|
|
(*sublevels_up)++;
|
|
else
|
|
break;
|
|
}
|
|
elog(ERROR, "RTERangeTablePosn: RTE not found (internal error)");
|
|
return 0; /* keep compiler quiet */
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* Side effect: if we find a match, mark the RTE as requiring read access.
|
|
* See comments in setTargetTable().
|
|
*/
|
|
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);
|
|
rte->checkForRead = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
rte->checkForRead = true;
|
|
}
|
|
}
|
|
|
|
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.
|
|
*
|
|
* NOTE: unlike scanRTEForColumn, there's no need to worry about forcing
|
|
* checkForRead true for the referenced tables. This is so because a join
|
|
* expression can only appear in a FROM clause, and any table named in
|
|
* FROM will be marked checkForRead from the beginning.
|
|
*/
|
|
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_joinlist)
|
|
{
|
|
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 for a relation to the pstate's range table (p_rtable).
|
|
*
|
|
* If the specified refname is already present, 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 *alias,
|
|
bool inh,
|
|
bool inFromCl)
|
|
{
|
|
char *refname = alias ? alias->relname : relname;
|
|
LOCKMODE lockmode;
|
|
Relation rel;
|
|
RangeTblEntry *rte;
|
|
Attr *eref;
|
|
int maxattrs;
|
|
int numaliases;
|
|
int varattno;
|
|
|
|
/* Check for conflicting RTE or jointable alias (at level 0 only) */
|
|
if (pstate != NULL)
|
|
{
|
|
Node *rteorjoin = refnameRangeOrJoinEntry(pstate, refname, NULL);
|
|
|
|
if (rteorjoin)
|
|
elog(ERROR, "Table name \"%s\" specified more than once",
|
|
refname);
|
|
}
|
|
|
|
rte = makeNode(RangeTblEntry);
|
|
|
|
rte->relname = relname;
|
|
rte->alias = alias;
|
|
rte->subquery = NULL;
|
|
|
|
/*
|
|
* Get the rel's OID. This access also ensures that we have an
|
|
* up-to-date relcache entry for the rel. Since this is typically
|
|
* the first access to a rel in a statement, be careful to get the
|
|
* right access level depending on whether we're doing SELECT FOR UPDATE.
|
|
*/
|
|
lockmode = isForUpdate(pstate, relname) ? RowShareLock : AccessShareLock;
|
|
rel = heap_openr(relname, lockmode);
|
|
rte->relid = RelationGetRelid(rel);
|
|
|
|
eref = alias ? (Attr *) copyObject(alias) : makeAttr(refname, NULL);
|
|
numaliases = length(eref->attrs);
|
|
|
|
/*
|
|
* Since the rel is open anyway, let's check that the
|
|
* number of column aliases is reasonable. - Thomas 2000-02-04
|
|
*/
|
|
maxattrs = RelationGetNumberOfAttributes(rel);
|
|
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 = numaliases; varattno < maxattrs; varattno++)
|
|
{
|
|
char *attrname;
|
|
|
|
attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
|
|
eref->attrs = lappend(eref->attrs, makeString(attrname));
|
|
}
|
|
rte->eref = eref;
|
|
|
|
/*
|
|
* Drop the rel refcount, but keep the access lock till end of transaction
|
|
* so that the table can't be deleted or have its schema modified
|
|
* underneath us.
|
|
*/
|
|
heap_close(rel, NoLock);
|
|
|
|
/*----------
|
|
* Flags:
|
|
* - this RTE should be expanded to include descendant tables,
|
|
* - this RTE is in the FROM clause,
|
|
* - this RTE should be checked for read/write access rights.
|
|
*
|
|
* The initial default on access checks is always check-for-READ-access,
|
|
* which is the right thing for all except target tables.
|
|
*----------
|
|
*/
|
|
rte->inh = inh;
|
|
rte->inFromCl = inFromCl;
|
|
rte->checkForRead = true;
|
|
rte->checkForWrite = false;
|
|
|
|
rte->checkAsUser = InvalidOid; /* not set-uid by default, either */
|
|
|
|
/*
|
|
* Add completed RTE to range table list.
|
|
*/
|
|
if (pstate != NULL)
|
|
pstate->p_rtable = lappend(pstate->p_rtable, rte);
|
|
|
|
return rte;
|
|
}
|
|
|
|
/*
|
|
* Add an entry for a subquery to the pstate's range table (p_rtable).
|
|
*
|
|
* This is just like addRangeTableEntry() except that it makes a subquery RTE.
|
|
* Note that an alias clause *must* be supplied.
|
|
*/
|
|
RangeTblEntry *
|
|
addRangeTableEntryForSubquery(ParseState *pstate,
|
|
Query *subquery,
|
|
Attr *alias,
|
|
bool inFromCl)
|
|
{
|
|
char *refname = alias->relname;
|
|
RangeTblEntry *rte;
|
|
Attr *eref;
|
|
int numaliases;
|
|
int varattno;
|
|
List *tlistitem;
|
|
|
|
/* Check for conflicting RTE or jointable alias (at level 0 only) */
|
|
if (pstate != NULL)
|
|
{
|
|
Node *rteorjoin = refnameRangeOrJoinEntry(pstate, refname, NULL);
|
|
|
|
if (rteorjoin)
|
|
elog(ERROR, "Table name \"%s\" specified more than once",
|
|
refname);
|
|
}
|
|
|
|
rte = makeNode(RangeTblEntry);
|
|
|
|
rte->relname = NULL;
|
|
rte->relid = InvalidOid;
|
|
rte->subquery = subquery;
|
|
rte->alias = alias;
|
|
|
|
eref = copyObject(alias);
|
|
numaliases = length(eref->attrs);
|
|
|
|
/* fill in any unspecified alias columns */
|
|
varattno = 0;
|
|
foreach(tlistitem, subquery->targetList)
|
|
{
|
|
TargetEntry *te = (TargetEntry *) lfirst(tlistitem);
|
|
|
|
if (te->resdom->resjunk)
|
|
continue;
|
|
varattno++;
|
|
Assert(varattno == te->resdom->resno);
|
|
if (varattno > numaliases)
|
|
{
|
|
char *attrname;
|
|
|
|
attrname = pstrdup(te->resdom->resname);
|
|
eref->attrs = lappend(eref->attrs, makeString(attrname));
|
|
}
|
|
}
|
|
if (varattno < numaliases)
|
|
elog(ERROR, "Table \"%s\" has %d columns available but %d columns specified",
|
|
refname, varattno, numaliases);
|
|
|
|
rte->eref = eref;
|
|
|
|
/*----------
|
|
* Flags:
|
|
* - this RTE should be expanded to include descendant tables,
|
|
* - this RTE is in the FROM clause,
|
|
* - this RTE should be checked for read/write access rights.
|
|
*
|
|
* Subqueries are never checked for access rights.
|
|
*----------
|
|
*/
|
|
rte->inh = false; /* never true for subqueries */
|
|
rte->inFromCl = inFromCl;
|
|
rte->checkForRead = false;
|
|
rte->checkForWrite = false;
|
|
|
|
rte->checkAsUser = InvalidOid;
|
|
|
|
/*
|
|
* Add completed RTE to range table list.
|
|
*/
|
|
if (pstate != NULL)
|
|
pstate->p_rtable = lappend(pstate->p_rtable, rte);
|
|
|
|
return rte;
|
|
}
|
|
|
|
/*
|
|
* Has the specified relname been selected FOR UPDATE?
|
|
*/
|
|
static bool
|
|
isForUpdate(ParseState *pstate, char *relname)
|
|
{
|
|
/* Outer loop to check parent query levels as well as this one */
|
|
while (pstate != NULL)
|
|
{
|
|
if (pstate->p_forUpdate != NIL)
|
|
{
|
|
if (lfirst(pstate->p_forUpdate) == NULL)
|
|
{
|
|
/* all tables used in query */
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
/* just the named tables */
|
|
List *l;
|
|
|
|
foreach(l, pstate->p_forUpdate)
|
|
{
|
|
char *rname = lfirst(l);
|
|
|
|
if (strcmp(relname, rname) == 0)
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
pstate = pstate->parentParseState;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Add the given RTE as a top-level entry in the pstate's join list,
|
|
* unless there already is an entry for it.
|
|
*/
|
|
void
|
|
addRTEtoJoinList(ParseState *pstate, RangeTblEntry *rte)
|
|
{
|
|
int rtindex = RTERangeTablePosn(pstate, rte, NULL);
|
|
List *jt;
|
|
RangeTblRef *rtr;
|
|
|
|
foreach(jt, pstate->p_joinlist)
|
|
{
|
|
Node *n = (Node *) lfirst(jt);
|
|
|
|
if (IsA(n, RangeTblRef))
|
|
{
|
|
if (rtindex == ((RangeTblRef *) n)->rtindex)
|
|
return; /* it's already being joined to */
|
|
}
|
|
}
|
|
|
|
/* Not present, so add it */
|
|
rtr = makeNode(RangeTblRef);
|
|
rtr->rtindex = rtindex;
|
|
pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
|
|
}
|
|
|
|
/*
|
|
* Add a POSTQUEL-style implicit RTE.
|
|
*
|
|
* We assume caller has already checked that there is no such RTE now.
|
|
*/
|
|
RangeTblEntry *
|
|
addImplicitRTE(ParseState *pstate, char *relname)
|
|
{
|
|
RangeTblEntry *rte;
|
|
|
|
rte = addRangeTableEntry(pstate, relname, NULL, false, false);
|
|
addRTEtoJoinList(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)
|
|
{
|
|
int rtindex,
|
|
sublevels_up,
|
|
varattno;
|
|
|
|
if (colnames)
|
|
*colnames = NIL;
|
|
if (colvars)
|
|
*colvars = NIL;
|
|
|
|
/* Need the RT index of the entry for creating Vars */
|
|
rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up);
|
|
|
|
if (rte->relname)
|
|
{
|
|
/* Ordinary relation RTE */
|
|
Relation rel;
|
|
int maxattrs;
|
|
|
|
rel = heap_openr(rte->relname, AccessShareLock);
|
|
|
|
maxattrs = RelationGetNumberOfAttributes(rel);
|
|
|
|
for (varattno = 0; varattno < maxattrs; varattno++)
|
|
{
|
|
Form_pg_attribute attr = rel->rd_att->attrs[varattno];
|
|
|
|
#ifdef _DROP_COLUMN_HACK__
|
|
if (COLUMN_IS_DROPPED(attr))
|
|
continue;
|
|
#endif /* _DROP_COLUMN_HACK__ */
|
|
|
|
if (colnames)
|
|
{
|
|
char *label;
|
|
|
|
if (varattno < length(rte->eref->attrs))
|
|
label = strVal(nth(varattno, rte->eref->attrs));
|
|
else
|
|
label = NameStr(attr->attname);
|
|
*colnames = lappend(*colnames, makeString(pstrdup(label)));
|
|
}
|
|
|
|
if (colvars)
|
|
{
|
|
Var *varnode;
|
|
|
|
varnode = makeVar(rtindex, attr->attnum,
|
|
attr->atttypid, attr->atttypmod,
|
|
sublevels_up);
|
|
|
|
*colvars = lappend(*colvars, varnode);
|
|
}
|
|
}
|
|
|
|
heap_close(rel, AccessShareLock);
|
|
}
|
|
else
|
|
{
|
|
/* Subquery RTE */
|
|
List *aliasp = rte->eref->attrs;
|
|
List *tlistitem;
|
|
|
|
varattno = 0;
|
|
foreach(tlistitem, rte->subquery->targetList)
|
|
{
|
|
TargetEntry *te = (TargetEntry *) lfirst(tlistitem);
|
|
|
|
if (te->resdom->resjunk)
|
|
continue;
|
|
varattno++;
|
|
Assert(varattno == te->resdom->resno);
|
|
|
|
if (colnames)
|
|
{
|
|
/* Assume there is one alias per target item */
|
|
char *label = strVal(lfirst(aliasp));
|
|
|
|
*colnames = lappend(*colnames, makeString(pstrdup(label)));
|
|
aliasp = lnext(aliasp);
|
|
}
|
|
|
|
if (colvars)
|
|
{
|
|
Var *varnode;
|
|
|
|
varnode = makeVar(rtindex, varattno,
|
|
te->resdom->restype,
|
|
te->resdom->restypmod,
|
|
sublevels_up);
|
|
|
|
*colvars = lappend(*colvars, varnode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/* ----------
|
|
* get_rte_attribute_name
|
|
* Get an attribute name from a RangeTblEntry
|
|
*
|
|
* This is unlike get_attname() because we use aliases if available.
|
|
* In particular, it will work on an RTE for a subselect, whereas
|
|
* get_attname() only works on real relations.
|
|
* ----------
|
|
*/
|
|
char *
|
|
get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
|
|
{
|
|
char *attname;
|
|
|
|
/*
|
|
* If there is an alias, use it
|
|
*/
|
|
if (attnum > 0 && attnum <= length(rte->eref->attrs))
|
|
return strVal(nth(attnum-1, rte->eref->attrs));
|
|
/*
|
|
* Can get here for a system attribute (which never has an alias),
|
|
* or if alias name list is too short (which probably can't happen
|
|
* anymore). Neither of these cases is valid for a subselect RTE.
|
|
*/
|
|
if (rte->relid == InvalidOid)
|
|
elog(ERROR, "Invalid attnum %d for rangetable entry %s",
|
|
attnum, rte->eref->relname);
|
|
/*
|
|
* Use the real name of the table's column
|
|
*/
|
|
attname = get_attname(rte->relid, attnum);
|
|
if (attname == NULL)
|
|
elog(ERROR, "cache lookup of attribute %d in relation %u failed",
|
|
attnum, rte->relid);
|
|
return attname;
|
|
}
|
|
|
|
/*
|
|
* given relation and att name, return id of variable
|
|
*
|
|
* This should only be used if the relation is already
|
|
* heap_open()'ed. Use the cache version get_attnum()
|
|
* for access to non-opened relations.
|
|
*/
|
|
int
|
|
attnameAttNum(Relation rd, char *a)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < rd->rd_rel->relnatts; i++)
|
|
if (!namestrcmp(&(rd->rd_att->attrs[i]->attname), a))
|
|
return i + 1;
|
|
|
|
if ((i = specialAttNum(a)) != InvalidAttrNumber)
|
|
return i;
|
|
|
|
/* on failure */
|
|
elog(ERROR, "Relation '%s' does not have attribute '%s'",
|
|
RelationGetRelationName(rd), a);
|
|
return InvalidAttrNumber; /* lint */
|
|
}
|
|
|
|
/* specialAttNum()
|
|
* Check attribute name to see if it is "special", e.g. "oid".
|
|
* - thomas 2000-02-07
|
|
*/
|
|
int
|
|
specialAttNum(char *a)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < SPECIALS; i++)
|
|
if (strcmp(special_attr[i].attrname, a) == 0)
|
|
return special_attr[i].attrnum;
|
|
|
|
return InvalidAttrNumber;
|
|
}
|
|
|
|
|
|
#ifdef NOT_USED
|
|
/*
|
|
* Given range variable, return whether attribute of this name
|
|
* is a set.
|
|
* NOTE the ASSUMPTION here that no system attributes are, or ever
|
|
* will be, sets.
|
|
*
|
|
* This should only be used if the relation is already
|
|
* heap_open()'ed. Use the cache version get_attisset()
|
|
* for access to non-opened relations.
|
|
*/
|
|
bool
|
|
attnameIsSet(Relation rd, char *name)
|
|
{
|
|
int i;
|
|
|
|
/* First check if this is a system attribute */
|
|
for (i = 0; i < SPECIALS; i++)
|
|
{
|
|
if (strcmp(special_attr[i].attrname, name) == 0)
|
|
return false; /* no sys attr is a set */
|
|
}
|
|
return get_attisset(RelationGetRelid(rd), name);
|
|
}
|
|
#endif
|
|
|
|
#ifdef NOT_USED
|
|
/*
|
|
* This should only be used if the relation is already
|
|
* heap_open()'ed. Use the cache version
|
|
* for access to non-opened relations.
|
|
*/
|
|
int
|
|
attnumAttNelems(Relation rd, int attid)
|
|
{
|
|
return rd->rd_att->attrs[attid - 1]->attnelems;
|
|
}
|
|
#endif
|
|
|
|
/* given attribute id, return type of that attribute */
|
|
/*
|
|
* This should only be used if the relation is already
|
|
* heap_open()'ed. Use the cache version get_atttype()
|
|
* for access to non-opened relations.
|
|
*/
|
|
Oid
|
|
attnumTypeId(Relation rd, int attid)
|
|
{
|
|
if (attid < 0)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < SPECIALS; i++)
|
|
{
|
|
if (special_attr[i].attrnum == attid)
|
|
return special_attr[i].attrtype;
|
|
}
|
|
/* negative but not a valid system attr? */
|
|
elog(ERROR, "attnumTypeId: bogus attribute number %d", attid);
|
|
}
|
|
|
|
/*
|
|
* -1 because attid is 1-based
|
|
*/
|
|
return rd->rd_att->attrs[attid - 1]->atttypid;
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
bool foundInFromCl = false;
|
|
List *temp;
|
|
|
|
foreach(temp, pstate->p_rtable)
|
|
{
|
|
RangeTblEntry *rte = lfirst(temp);
|
|
|
|
if (rte->inFromCl)
|
|
{
|
|
foundInFromCl = true;
|
|
break;
|
|
}
|
|
}
|
|
if (foundInFromCl)
|
|
elog(NOTICE, "Adding missing FROM-clause entry%s for table \"%s\"",
|
|
pstate->parentParseState != NULL ? " in subquery" : "",
|
|
refname);
|
|
}
|
|
|