postgresql/src/backend/parser/parse_relation.c

1960 lines
50 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* parse_relation.c
* parser support routines dealing with relations
*
2003-08-04 04:40:20 +02:00
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.94 2004/04/18 18:12:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <ctype.h>
#include "access/heapam.h"
1997-11-26 02:14:33 +01:00
#include "access/htup.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
1997-11-26 02:14:33 +01:00
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "parser/parsetree.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
1999-07-16 07:00:38 +02:00
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
1997-11-26 02:14:33 +01:00
#include "utils/lsyscache.h"
#include "utils/syscache.h"
/* GUC parameter */
2003-08-04 02:43:34 +02:00
bool add_missing_from;
static Node *scanNameSpaceForRefname(ParseState *pstate, Node *nsnode,
const char *refname);
static Node *scanNameSpaceForRelid(ParseState *pstate, Node *nsnode,
2002-09-04 22:31:48 +02:00
Oid relid);
static void scanNameSpaceForConflict(ParseState *pstate, Node *nsnode,
RangeTblEntry *rte1, const char *aliasname1);
static bool isForUpdate(ParseState *pstate, char *refname);
static bool get_rte_attribute_is_dropped(RangeTblEntry *rte,
2002-09-04 22:31:48 +02:00
AttrNumber attnum);
static int specialAttNum(const char *attname);
static void warnAutoRange(ParseState *pstate, RangeVar *relation);
/*
* refnameRangeTblEntry
* 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
* ereport().
*
* 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
2002-09-04 22:31:48 +02:00
* 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,
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;
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 */
if (IsA(nsnode, RangeTblEntry))
return (RangeTblEntry *) nsnode;
Assert(IsA(nsnode, JoinExpr));
return rt_fetch(((JoinExpr *) nsnode)->rtindex, pstate->p_rtable);
}
pstate = pstate->parentParseState;
if (sublevels_up)
(*sublevels_up)++;
else
break;
}
return NULL;
}
/*
* 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.
*/
static Node *
scanNameSpaceForRefname(ParseState *pstate, Node *nsnode,
const char *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);
if (strcmp(rte->eref->aliasname, refname) == 0)
result = (Node *) rte;
}
else if (IsA(nsnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) nsnode;
if (j->alias)
{
if (strcmp(j->alias->aliasname, refname) == 0)
2001-03-22 05:01:46 +01:00
return (Node *) j; /* matched a join 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 = scanNameSpaceForRefname(pstate, j->larg, refname);
newresult = scanNameSpaceForRefname(pstate, j->rarg, refname);
2001-03-22 05:01:46 +01:00
if (!result)
result = newresult;
else if (newresult)
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_ALIAS),
errmsg("table reference \"%s\" is ambiguous",
refname)));
}
else if (IsA(nsnode, List))
{
List *l;
foreach(l, (List *) nsnode)
{
newresult = scanNameSpaceForRefname(pstate, lfirst(l), refname);
if (!result)
result = newresult;
else if (newresult)
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_ALIAS),
errmsg("table reference \"%s\" is ambiguous",
refname)));
}
}
else
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(nsnode));
return result;
}
/*
* Recursively search a namespace for a relation RTE matching the
2002-09-04 22:31:48 +02:00
* 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)
{
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)
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_ALIAS),
errmsg("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)
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_ALIAS),
errmsg("table reference %u is ambiguous",
relid)));
}
}
else
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(nsnode));
return result;
}
/*
* Recursively check for name conflicts between two namespaces or
2001-03-22 05:01:46 +01:00
* namespace subtrees. Raise an error if any is found.
*
* 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,
Node *namespace2)
{
if (namespace1 == NULL)
return;
if (IsA(namespace1, RangeTblRef))
{
int varno = ((RangeTblRef *) namespace1)->rtindex;
RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
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))
{
JoinExpr *j = (JoinExpr *) namespace1;
if (j->alias)
{
scanNameSpaceForConflict(pstate, namespace2,
NULL, j->alias->aliasname);
2001-03-22 05:01:46 +01:00
/*
* 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;
}
checkNameSpaceConflicts(pstate, j->larg, namespace2);
checkNameSpaceConflicts(pstate, j->rarg, namespace2);
}
else if (IsA(namespace1, List))
{
List *l;
foreach(l, (List *) namespace1)
checkNameSpaceConflicts(pstate, lfirst(l), namespace2);
}
else
elog(ERROR, "unrecognized node type: %d", (int) 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 */
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_ALIAS),
errmsg("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)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_ALIAS),
2003-08-04 02:43:34 +02:00
errmsg("table name \"%s\" specified more than once",
aliasname1)));
2002-09-04 22:31:48 +02:00
/*
* 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, "unrecognized node type: %d", (int) 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.
* 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, "RTE not found (internal error)");
return 0; /* keep compiler quiet */
}
/*
* Given an RT index and nesting depth, find the corresponding RTE.
* This is the inverse of RTERangeTablePosn.
*/
RangeTblEntry *
GetRTEByRangeTablePosn(ParseState *pstate,
int varno,
int sublevels_up)
{
while (sublevels_up-- > 0)
{
pstate = pstate->parentParseState;
Assert(pstate != NULL);
}
Assert(varno > 0 && varno <= length(pstate->p_rtable));
return rt_fetch(varno, pstate->p_rtable);
}
/*
* 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().
*
* NOTE: if the RTE is for a join, marking it as requiring read access does
* nothing. It might seem that we need to propagate the mark to all the
* contained RTEs, but that is not necessary. This is so because a join
* expression can only appear in a FROM clause, and any table named in
* FROM will be marked as requiring read access from the beginning.
*/
Node *
scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname)
{
Node *result = NULL;
int attnum = 0;
List *c;
/*
2001-03-22 05:01:46 +01:00
* Scan the user column names (or aliases) for a match. Complain if
* multiple matches.
*
2002-09-04 22:31:48 +02:00
* Note: because eref->colnames may include names of dropped columns, we
* need to check for non-droppedness before accepting a match. This
* takes an extra cache lookup, but we can skip the lookup most of the
* time by exploiting the knowledge that dropped columns are assigned
* dummy names starting with '.', which is an unusual choice for
* actual column names.
*
2002-09-04 22:31:48 +02:00
* Should the user try to fool us by altering pg_attribute.attname for a
* dropped column, we'll still catch it by virtue of the checks in
* get_rte_attribute_type(), which is called by make_var(). That
* routine has to do a cache lookup anyway, so the check there is
* cheap.
*/
foreach(c, rte->eref->colnames)
{
attnum++;
if (strcmp(strVal(lfirst(c)), colname) == 0)
{
2002-09-04 22:31:48 +02:00
if (colname[0] == '.' && /* see note above */
get_rte_attribute_is_dropped(rte, attnum))
continue;
if (result)
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_COLUMN),
errmsg("column reference \"%s\" is ambiguous",
colname)));
result = (Node *) make_var(pstate, rte, attnum);
/* Require read access */
rte->requiredPerms |= ACL_SELECT;
}
}
/*
* 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 real table, consider system column names.
*/
if (rte->rtekind == RTE_RELATION)
{
/* quick check to see if name could be a system column */
attnum = specialAttNum(colname);
if (attnum != InvalidAttrNumber)
{
/* now check to see if column actually is defined */
if (SearchSysCacheExists(ATTNUM,
ObjectIdGetDatum(rte->relid),
Int16GetDatum(attnum),
0, 0))
{
result = (Node *) make_var(pstate, rte, attnum);
/* Require read access */
rte->requiredPerms |= ACL_SELECT;
}
}
}
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.
* If localonly is true, only names in the innermost query are considered.
*/
Node *
colNameToVar(ParseState *pstate, char *colname, bool localonly)
{
Node *result = NULL;
ParseState *orig_pstate = pstate;
int levels_up = 0;
while (pstate != NULL)
{
List *ns;
/*
* We need to look only at top-level namespace items, and even for
2001-03-22 05:01:46 +01:00
* those, ignore RTEs that are marked as not inFromCl and not the
* query's target relation.
*/
foreach(ns, pstate->p_namespace)
{
2001-03-22 05:01:46 +01:00
Node *nsnode = (Node *) lfirst(ns);
Node *newresult = NULL;
if (IsA(nsnode, RangeTblRef))
{
int varno = ((RangeTblRef *) nsnode)->rtindex;
RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
2001-03-22 05:01:46 +01:00
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(nsnode, JoinExpr))
{
int varno = ((JoinExpr *) nsnode)->rtindex;
RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
/* joins are always inFromCl, so no need to check */
/* use orig_pstate here to get the right sublevels_up */
newresult = scanRTEForColumn(orig_pstate, rte, colname);
}
else
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(nsnode));
if (newresult)
{
if (result)
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_COLUMN),
2003-08-04 02:43:34 +02:00
errmsg("column reference \"%s\" is ambiguous",
colname)));
result = newresult;
}
}
if (result != NULL || localonly)
break; /* found, or don't want to look at parent */
pstate = pstate->parentParseState;
levels_up++;
}
return result;
}
/*
* qualifiedNameToVar
* 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 *schemaname,
char *refname,
char *colname,
bool implicitRTEOK)
{
RangeTblEntry *rte;
int sublevels_up;
rte = refnameRangeTblEntry(pstate, schemaname, refname, &sublevels_up);
if (rte == NULL)
{
2001-03-22 05:01:46 +01:00
if (!implicitRTEOK)
return NULL;
rte = addImplicitRTE(pstate, makeRangeVar(schemaname, refname));
}
return scanRTEForColumn(pstate, rte, colname);
}
/*
* Add an entry for a relation to the pstate's range table (p_rtable).
*
* If pstate is NULL, we just build an RTE and return it without adding it
* to an rtable list.
*
* Note: formerly this checked for refname conflicts, but that's wrong.
* Caller is responsible for checking for conflicts in the appropriate scope.
*/
RangeTblEntry *
addRangeTableEntry(ParseState *pstate,
RangeVar *relation,
Alias *alias,
bool inh,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
char *refname = alias ? alias->aliasname : relation->relname;
LOCKMODE lockmode;
Relation rel;
Alias *eref;
int maxattrs;
int numaliases;
int varattno;
rte->rtekind = RTE_RELATION;
rte->alias = alias;
/*
* Get the rel's OID. This access also ensures that we have an
2001-03-22 05:01:46 +01:00
* 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, refname) ? RowShareLock : AccessShareLock;
rel = heap_openrv(relation, lockmode);
rte->relid = RelationGetRelid(rel);
eref = alias ? (Alias *) copyObject(alias) : makeAlias(refname, NIL);
numaliases = length(eref->colnames);
/*
2001-03-22 05:01:46 +01:00
* 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)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("table \"%s\" has %d columns available but %d columns specified",
2003-08-04 02:43:34 +02:00
RelationGetRelationName(rel), maxattrs, numaliases)));
/* fill in any unspecified alias columns using actual column names */
for (varattno = numaliases; varattno < maxattrs; varattno++)
{
char *attrname;
attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
eref->colnames = lappend(eref->colnames, 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 appropriate 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->requiredPerms = ACL_SELECT;
rte->checkAsUser = 0; /* not set-uid by default, either */
/*
* Add completed RTE to pstate's range table list, but not to join
* list nor namespace --- caller must do that if appropriate.
*/
if (pstate != NULL)
pstate->p_rtable = lappend(pstate->p_rtable, rte);
return rte;
}
/*
* Add an entry for a relation to the pstate's range table (p_rtable).
*
* This is just like addRangeTableEntry() except that it makes an RTE
* given a relation OID instead of a RangeVar reference.
*
* Note that an alias clause *must* be supplied.
*/
RangeTblEntry *
addRangeTableEntryForRelation(ParseState *pstate,
Oid relid,
Alias *alias,
bool inh,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
char *refname = alias->aliasname;
LOCKMODE lockmode;
Relation rel;
Alias *eref;
int maxattrs;
int numaliases;
int varattno;
rte->rtekind = RTE_RELATION;
rte->alias = alias;
/*
* Get the rel's relcache entry. This access 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, refname) ? RowShareLock : AccessShareLock;
rel = heap_open(relid, lockmode);
rte->relid = relid;
eref = (Alias *) copyObject(alias);
numaliases = length(eref->colnames);
/*
* 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)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("table \"%s\" has %d columns available but %d columns specified",
2003-08-04 02:43:34 +02:00
RelationGetRelationName(rel), maxattrs, numaliases)));
/* fill in any unspecified alias columns using actual column names */
for (varattno = numaliases; varattno < maxattrs; varattno++)
{
char *attrname;
attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
eref->colnames = lappend(eref->colnames, makeString(attrname));
}
rte->eref = eref;
/*
2001-03-22 05:01:46 +01:00
* 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 appropriate 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->requiredPerms = ACL_SELECT;
rte->checkAsUser = 0; /* not set-uid by default, either */
/*
2001-03-22 05:01:46 +01:00
* Add completed RTE to pstate's range table list, but not to join
* list nor namespace --- caller must do that if appropriate.
*/
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,
Alias *alias,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
char *refname = alias->aliasname;
Alias *eref;
int numaliases;
int varattno;
List *tlistitem;
rte->rtekind = RTE_SUBQUERY;
rte->relid = InvalidOid;
rte->subquery = subquery;
rte->alias = alias;
eref = copyObject(alias);
numaliases = length(eref->colnames);
/* 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->colnames = lappend(eref->colnames, makeString(attrname));
}
}
if (varattno < numaliases)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("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 appropriate access rights.
*
* Subqueries are never checked for access rights.
*----------
*/
rte->inh = false; /* never true for subqueries */
rte->inFromCl = inFromCl;
rte->requiredPerms = 0;
rte->checkAsUser = 0;
/*
2001-03-22 05:01:46 +01:00
* Add completed RTE to pstate's range table list, but not to join
* list nor namespace --- caller must do that if appropriate.
*/
if (pstate != NULL)
pstate->p_rtable = lappend(pstate->p_rtable, rte);
return rte;
}
/*
* Add an entry for a function to the pstate's range table (p_rtable).
*
* This is just like addRangeTableEntry() except that it makes a function RTE.
*/
RangeTblEntry *
addRangeTableEntryForFunction(ParseState *pstate,
char *funcname,
Node *funcexpr,
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
RangeFunction *rangefunc,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
Oid funcrettype = exprType(funcexpr);
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
char functyptype;
Alias *alias = rangefunc->alias;
List *coldeflist = rangefunc->coldeflist;
Alias *eref;
int numaliases;
int varattno;
rte->rtekind = RTE_FUNCTION;
rte->relid = InvalidOid;
rte->subquery = NULL;
rte->funcexpr = funcexpr;
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
rte->coldeflist = coldeflist;
rte->alias = alias;
eref = alias ? (Alias *) copyObject(alias) : makeAlias(funcname, NIL);
rte->eref = eref;
numaliases = length(eref->colnames);
/*
* Now determine if the function returns a simple or composite type,
* and check/add column aliases.
*/
if (coldeflist != NIL)
{
/*
2002-09-04 22:31:48 +02:00
* we *only* allow a coldeflist for functions returning a RECORD
* pseudo-type
*/
if (funcrettype != RECORDOID)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("a column definition list is only allowed for functions returning \"record\"")));
}
else
{
/*
* ... and a coldeflist is *required* for functions returning a
* RECORD pseudo-type
*/
if (funcrettype == RECORDOID)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("a column definition list is required for functions returning \"record\"")));
}
functyptype = get_typtype(funcrettype);
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
if (functyptype == 'c')
{
/*
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
* Named composite data type, i.e. a table's row type
*/
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
Oid funcrelid = typeidTypeRelid(funcrettype);
Relation rel;
int maxattrs;
2003-08-04 02:43:34 +02:00
if (!OidIsValid(funcrelid)) /* shouldn't happen if typtype is
* 'c' */
elog(ERROR, "invalid typrelid for complex type %u", funcrettype);
/*
2002-09-04 22:31:48 +02:00
* Get the rel's relcache entry. This access ensures that we have
* an up-to-date relcache entry for the rel.
*/
rel = relation_open(funcrelid, AccessShareLock);
/*
2002-09-04 22:31:48 +02:00
* Since the rel is open anyway, let's check that the number of
* column aliases is reasonable.
*/
maxattrs = RelationGetNumberOfAttributes(rel);
if (maxattrs < numaliases)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("table \"%s\" has %d columns available but %d columns specified",
RelationGetRelationName(rel),
maxattrs, numaliases)));
/* fill in alias columns using actual column names */
for (varattno = numaliases; varattno < maxattrs; varattno++)
{
char *attrname;
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
eref->colnames = lappend(eref->colnames, makeString(attrname));
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
}
/*
* Drop the rel refcount, but keep the access lock till end of
2002-09-04 22:31:48 +02:00
* transaction so that the table can't be deleted or have its
* schema modified underneath us.
*/
relation_close(rel, NoLock);
}
else if (functyptype == 'b' || functyptype == 'd')
{
/*
2002-09-04 22:31:48 +02:00
* Must be a base data type, i.e. scalar. Just add one alias
* column named for the function.
*/
if (numaliases > 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
2003-08-04 02:43:34 +02:00
errmsg("too many column aliases specified for function %s",
funcname)));
if (numaliases == 0)
eref->colnames = makeList1(makeString(eref->aliasname));
}
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
else if (functyptype == 'p' && funcrettype == RECORDOID)
{
List *col;
/* Use the column definition list to form the alias list */
eref->colnames = NIL;
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
foreach(col, coldeflist)
{
ColumnDef *n = lfirst(col);
char *attrname;
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
attrname = pstrdup(n->colname);
eref->colnames = lappend(eref->colnames, makeString(attrname));
}
}
else
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
2003-08-04 02:43:34 +02:00
errmsg("function \"%s\" in FROM has unsupported return type",
funcname)));
/*----------
* Flags:
* - this RTE should be expanded to include descendant tables,
* - this RTE is in the FROM clause,
* - this RTE should be checked for appropriate access rights.
*
* Functions are never checked for access rights (at least, not by
* the RTE permissions mechanism).
*----------
*/
rte->inh = false; /* never true for functions */
rte->inFromCl = inFromCl;
rte->requiredPerms = 0;
rte->checkAsUser = 0;
/*
* Add completed RTE to pstate's range table list, but not to join
* list nor namespace --- caller must do that if appropriate.
*/
if (pstate != NULL)
pstate->p_rtable = lappend(pstate->p_rtable, rte);
return rte;
}
/*
* Add an entry for a join to the pstate's range table (p_rtable).
*
* This is much like addRangeTableEntry() except that it makes a join RTE.
*/
RangeTblEntry *
addRangeTableEntryForJoin(ParseState *pstate,
List *colnames,
JoinType jointype,
List *aliasvars,
Alias *alias,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
Alias *eref;
int numaliases;
rte->rtekind = RTE_JOIN;
rte->relid = InvalidOid;
rte->subquery = NULL;
rte->jointype = jointype;
rte->joinaliasvars = aliasvars;
rte->alias = alias;
eref = alias ? (Alias *) copyObject(alias) : makeAlias("unnamed_join", NIL);
numaliases = length(eref->colnames);
/* fill in any unspecified alias columns */
if (numaliases < length(colnames))
{
while (numaliases-- > 0)
colnames = lnext(colnames);
eref->colnames = nconc(eref->colnames, colnames);
}
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 appropriate access rights.
*
* Joins are never checked for access rights.
*----------
*/
rte->inh = false; /* never true for joins */
rte->inFromCl = inFromCl;
rte->requiredPerms = 0;
rte->checkAsUser = 0;
/*
* Add completed RTE to pstate's range table list, but not to join
* list nor namespace --- caller must do that if appropriate.
*/
if (pstate != NULL)
pstate->p_rtable = lappend(pstate->p_rtable, rte);
return rte;
}
/*
* Has the specified refname been selected FOR UPDATE?
*/
static bool
isForUpdate(ParseState *pstate, char *refname)
{
/* 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 */
2001-03-22 05:01:46 +01:00
List *l;
foreach(l, pstate->p_forUpdate)
{
char *rname = strVal(lfirst(l));
if (strcmp(refname, rname) == 0)
return true;
}
}
}
pstate = pstate->parentParseState;
}
return false;
}
/*
* Add the given RTE as a top-level entry in the pstate's join list
2001-03-22 05:01:46 +01:00
* and/or name space list. (We assume caller has checked for any
* namespace conflict.)
*/
void
addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
bool addToJoinList, bool addToNameSpace)
{
int rtindex = RTERangeTablePosn(pstate, rte, NULL);
RangeTblRef *rtr = makeNode(RangeTblRef);
rtr->rtindex = rtindex;
if (addToJoinList)
pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
if (addToNameSpace)
pstate->p_namespace = lappend(pstate->p_namespace, rtr);
}
/*
* Add a POSTQUEL-style implicit RTE.
*
* We assume caller has already checked that there is no RTE or join with
* a conflicting name.
*/
RangeTblEntry *
addImplicitRTE(ParseState *pstate, RangeVar *relation)
{
RangeTblEntry *rte;
rte = addRangeTableEntry(pstate, relation, NULL, false, false);
addRTEtoQuery(pstate, rte, true, true);
warnAutoRange(pstate, relation);
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);
switch (rte->rtekind)
{
case RTE_RELATION:
{
/* Ordinary relation RTE */
Relation rel;
int maxattrs;
int numaliases;
rel = heap_open(rte->relid, AccessShareLock);
maxattrs = RelationGetNumberOfAttributes(rel);
numaliases = length(rte->eref->colnames);
for (varattno = 0; varattno < maxattrs; varattno++)
{
Form_pg_attribute attr = rel->rd_att->attrs[varattno];
if (attr->attisdropped)
continue;
if (colnames)
{
char *label;
if (varattno < numaliases)
label = strVal(nth(varattno, rte->eref->colnames));
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);
}
break;
case RTE_SUBQUERY:
{
/* Subquery RTE */
List *aliasp = rte->eref->colnames;
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);
}
}
}
break;
case RTE_FUNCTION:
{
/* Function RTE */
2002-09-04 22:31:48 +02:00
Oid funcrettype = exprType(rte->funcexpr);
char functyptype = get_typtype(funcrettype);
List *coldeflist = rte->coldeflist;
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
if (functyptype == 'c')
{
/*
2002-09-04 22:31:48 +02:00
* Composite data type, i.e. a table's row type Same
* as ordinary relation RTE
*/
2002-09-04 22:31:48 +02:00
Oid funcrelid = typeidTypeRelid(funcrettype);
Relation rel;
int maxattrs;
int numaliases;
2003-08-04 02:43:34 +02:00
if (!OidIsValid(funcrelid)) /* shouldn't happen */
elog(ERROR, "invalid typrelid for complex type %u",
funcrettype);
rel = relation_open(funcrelid, AccessShareLock);
maxattrs = RelationGetNumberOfAttributes(rel);
numaliases = length(rte->eref->colnames);
for (varattno = 0; varattno < maxattrs; varattno++)
{
Form_pg_attribute attr = rel->rd_att->attrs[varattno];
if (attr->attisdropped)
continue;
if (colnames)
{
char *label;
if (varattno < numaliases)
label = strVal(nth(varattno, rte->eref->colnames));
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);
}
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
}
relation_close(rel, AccessShareLock);
}
else if (functyptype == 'b' || functyptype == 'd')
{
/*
* Must be a base data type, i.e. scalar
*/
if (colnames)
*colnames = lappend(*colnames,
lfirst(rte->eref->colnames));
if (colvars)
{
Var *varnode;
varnode = makeVar(rtindex, 1,
funcrettype, -1,
sublevels_up);
*colvars = lappend(*colvars, varnode);
}
}
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
else if (functyptype == 'p' && funcrettype == RECORDOID)
{
List *col;
int attnum = 0;
foreach(col, coldeflist)
{
ColumnDef *colDef = lfirst(col);
attnum++;
if (colnames)
{
char *attrname;
attrname = pstrdup(colDef->colname);
*colnames = lappend(*colnames, makeString(attrname));
}
if (colvars)
{
Var *varnode;
Oid atttypid;
atttypid = typenameTypeId(colDef->typename);
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
varnode = makeVar(rtindex,
2002-09-04 22:31:48 +02:00
attnum,
atttypid,
-1,
sublevels_up);
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
*colvars = lappend(*colvars, varnode);
}
}
}
else
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("function in FROM has unsupported return type")));
}
break;
case RTE_JOIN:
{
/* Join RTE */
List *aliasp = rte->eref->colnames;
List *aliasvars = rte->joinaliasvars;
varattno = 0;
while (aliasp)
{
Assert(aliasvars);
varattno++;
if (colnames)
{
char *label = strVal(lfirst(aliasp));
*colnames = lappend(*colnames, makeString(pstrdup(label)));
}
1998-01-20 06:05:08 +01:00
if (colvars)
{
Node *aliasvar = (Node *) lfirst(aliasvars);
Var *varnode;
varnode = makeVar(rtindex, varattno,
exprType(aliasvar),
exprTypmod(aliasvar),
sublevels_up);
2001-03-22 05:01:46 +01:00
*colvars = lappend(*colvars, varnode);
}
aliasp = lnext(aliasp);
aliasvars = lnext(aliasvars);
}
Assert(aliasvars == NIL);
}
break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
}
}
/*
* expandRelAttrs -
* Workhorse for "*" expansion: produce a list of targetentries
* for the attributes of the rte
*/
List *
expandRelAttrs(ParseState *pstate, RangeTblEntry *rte)
{
List *names,
*vars;
List *te_list = NIL;
expandRTE(pstate, rte, &names, &vars);
while (names)
{
char *label = strVal(lfirst(names));
Node *varnode = (Node *) lfirst(vars);
TargetEntry *te = makeNode(TargetEntry);
te->resdom = makeResdom((AttrNumber) pstate->p_next_resno++,
exprType(varnode),
exprTypmod(varnode),
label,
1999-05-17 19:03:51 +02:00
false);
te->expr = (Expr *) varnode;
te_list = lappend(te_list, te);
names = lnext(names);
vars = lnext(vars);
}
Assert(vars == NIL); /* lists not same length? */
1998-01-20 06:05:08 +01:00
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 or join, whereas
* get_attname() only works on real relations.
*
* "*" is returned if the given attnum is InvalidAttrNumber --- this case
* occurs when a Var represents a whole tuple of a relation.
*/
char *
get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
{
if (attnum == InvalidAttrNumber)
return "*";
/*
* If there is a user-written column alias, use it.
*/
if (rte->alias &&
attnum > 0 && attnum <= length(rte->alias->colnames))
return strVal(nth(attnum - 1, rte->alias->colnames));
2001-03-22 05:01:46 +01:00
/*
* If the RTE is a relation, go to the system catalogs not the
2002-09-04 22:31:48 +02:00
* eref->colnames list. This is a little slower but it will give the
* right answer if the column has been renamed since the eref list was
* built (which can easily happen for rules).
*/
if (rte->rtekind == RTE_RELATION)
return get_relid_attribute_name(rte->relid, attnum);
2001-03-22 05:01:46 +01:00
/*
2002-09-04 22:31:48 +02:00
* Otherwise use the column name from eref. There should always be
* one.
*/
if (attnum > 0 && attnum <= length(rte->eref->colnames))
return strVal(nth(attnum - 1, rte->eref->colnames));
/* else caller gave us a bogus attnum */
elog(ERROR, "invalid attnum %d for rangetable entry %s",
attnum, rte->eref->aliasname);
return NULL; /* keep compiler quiet */
}
/*
* get_rte_attribute_type
* Get attribute type information from a RangeTblEntry
*/
void
get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
Oid *vartype, int32 *vartypmod)
{
switch (rte->rtekind)
{
case RTE_RELATION:
{
/* Plain relation RTE --- get the attribute's type info */
HeapTuple tp;
Form_pg_attribute att_tup;
tp = SearchSysCache(ATTNUM,
ObjectIdGetDatum(rte->relid),
Int16GetDatum(attnum),
0, 0);
2003-08-04 02:43:34 +02:00
if (!HeapTupleIsValid(tp)) /* shouldn't happen */
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
attnum, rte->relid);
att_tup = (Form_pg_attribute) GETSTRUCT(tp);
2002-09-04 22:31:48 +02:00
/*
* If dropped column, pretend it ain't there. See notes
* in scanRTEForColumn.
*/
if (att_tup->attisdropped)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
NameStr(att_tup->attname),
get_rel_name(rte->relid))));
*vartype = att_tup->atttypid;
*vartypmod = att_tup->atttypmod;
ReleaseSysCache(tp);
}
break;
case RTE_SUBQUERY:
{
/* Subselect RTE --- get type info from subselect's tlist */
TargetEntry *te = get_tle_by_resno(rte->subquery->targetList,
attnum);
if (te == NULL || te->resdom->resjunk)
elog(ERROR, "subquery %s does not have attribute %d",
rte->eref->aliasname, attnum);
*vartype = te->resdom->restype;
*vartypmod = te->resdom->restypmod;
}
break;
case RTE_FUNCTION:
{
/* Function RTE */
2002-09-04 22:31:48 +02:00
Oid funcrettype = exprType(rte->funcexpr);
char functyptype = get_typtype(funcrettype);
List *coldeflist = rte->coldeflist;
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
if (functyptype == 'c')
{
/*
2002-09-04 22:31:48 +02:00
* Composite data type, i.e. a table's row type Same
* as ordinary relation RTE
*/
2002-09-04 22:31:48 +02:00
Oid funcrelid = typeidTypeRelid(funcrettype);
HeapTuple tp;
Form_pg_attribute att_tup;
2003-08-04 02:43:34 +02:00
if (!OidIsValid(funcrelid)) /* shouldn't happen */
elog(ERROR, "invalid typrelid for complex type %u",
funcrettype);
tp = SearchSysCache(ATTNUM,
ObjectIdGetDatum(funcrelid),
Int16GetDatum(attnum),
0, 0);
2003-08-04 02:43:34 +02:00
if (!HeapTupleIsValid(tp)) /* shouldn't happen */
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
attnum, funcrelid);
att_tup = (Form_pg_attribute) GETSTRUCT(tp);
2002-09-04 22:31:48 +02:00
/*
2002-09-04 22:31:48 +02:00
* If dropped column, pretend it ain't there. See
* notes in scanRTEForColumn.
*/
if (att_tup->attisdropped)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
NameStr(att_tup->attname),
get_rel_name(funcrelid))));
*vartype = att_tup->atttypid;
*vartypmod = att_tup->atttypmod;
ReleaseSysCache(tp);
}
else if (functyptype == 'b' || functyptype == 'd')
{
/*
* Must be a base data type, i.e. scalar
*/
*vartype = funcrettype;
*vartypmod = -1;
}
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
else if (functyptype == 'p' && funcrettype == RECORDOID)
{
ColumnDef *colDef = nth(attnum - 1, coldeflist);
*vartype = typenameTypeId(colDef->typename);
Attached are two patches to implement and document anonymous composite types for Table Functions, as previously proposed on HACKERS. Here is a brief explanation: 1. Creates a new pg_type typtype: 'p' for pseudo type (currently either 'b' for base or 'c' for catalog, i.e. a class). 2. Creates new builtin type of typtype='p' named RECORD. This is the first of potentially several pseudo types. 3. Modify FROM clause grammer to accept: SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...) where m is the table alias, colname1, etc are the column names, and type1, etc are the column types. 4. When typtype == 'p' and the function return type is RECORD, a list of column defs is required, and when typtype != 'p', it is disallowed. 5. A check was added to ensure that the tupdesc provide via the parser and the actual return tupdesc match in number and type of attributes. When creating a function you can do: CREATE FUNCTION foo(text) RETURNS setof RECORD ... When using it you can do: SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp) or SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp) Included in the patches are adjustments to the regression test sql and expected files, and documentation. p.s. This potentially solves (or at least improves) the issue of builtin Table Functions. They can be bootstrapped as returning RECORD, and we can wrap system views around them with properly specified column defs. For example: CREATE VIEW pg_settings AS SELECT s.name, s.setting FROM show_all_settings()AS s(name text, setting text); Then we can also add the UPDATE RULE that I previously posted to pg_settings, and have pg_settings act like a virtual table, allowing settings to be queried and set. Joe Conway
2002-08-04 21:48:11 +02:00
*vartypmod = -1;
}
else
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("function in FROM has unsupported return type")));
}
break;
case RTE_JOIN:
{
2002-09-04 22:31:48 +02:00
/*
* Join RTE --- get type info from join RTE's alias
* variable
*/
Node *aliasvar;
Assert(attnum > 0 && attnum <= length(rte->joinaliasvars));
2002-09-04 22:31:48 +02:00
aliasvar = (Node *) nth(attnum - 1, rte->joinaliasvars);
*vartype = exprType(aliasvar);
*vartypmod = exprTypmod(aliasvar);
}
break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
}
}
/*
* get_rte_attribute_is_dropped
* Check whether attempted attribute ref is to a dropped column
*/
static bool
get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
{
2002-09-04 22:31:48 +02:00
bool result;
switch (rte->rtekind)
{
case RTE_RELATION:
{
/* Plain relation RTE --- get the attribute's type info */
HeapTuple tp;
Form_pg_attribute att_tup;
tp = SearchSysCache(ATTNUM,
ObjectIdGetDatum(rte->relid),
Int16GetDatum(attnum),
0, 0);
2003-08-04 02:43:34 +02:00
if (!HeapTupleIsValid(tp)) /* shouldn't happen */
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
attnum, rte->relid);
att_tup = (Form_pg_attribute) GETSTRUCT(tp);
result = att_tup->attisdropped;
ReleaseSysCache(tp);
}
break;
case RTE_SUBQUERY:
case RTE_JOIN:
/* Subselect and join RTEs never have dropped columns */
result = false;
break;
case RTE_FUNCTION:
{
/* Function RTE */
Oid funcrettype = exprType(rte->funcexpr);
Oid funcrelid = typeidTypeRelid(funcrettype);
if (OidIsValid(funcrelid))
{
/*
2002-09-04 22:31:48 +02:00
* Composite data type, i.e. a table's row type Same
* as ordinary relation RTE
*/
2002-09-04 22:31:48 +02:00
HeapTuple tp;
Form_pg_attribute att_tup;
tp = SearchSysCache(ATTNUM,
ObjectIdGetDatum(funcrelid),
Int16GetDatum(attnum),
0, 0);
2003-08-04 02:43:34 +02:00
if (!HeapTupleIsValid(tp)) /* shouldn't happen */
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
attnum, funcrelid);
att_tup = (Form_pg_attribute) GETSTRUCT(tp);
result = att_tup->attisdropped;
ReleaseSysCache(tp);
}
else
{
/*
* Must be a base data type, i.e. scalar
*/
result = false;
}
}
break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
result = false; /* keep compiler quiet */
}
return result;
}
/*
* Given a targetlist and a resno, return the matching TargetEntry
*
* Returns NULL if resno is not present in list.
*
* Note: we need to search, rather than just indexing with nth(), because
* not all tlists are sorted by resno.
*/
TargetEntry *
get_tle_by_resno(List *tlist, AttrNumber resno)
{
List *i;
foreach(i, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(i);
if (tle->resdom->resno == resno)
return tle;
}
return NULL;
}
1998-01-20 06:05:08 +01:00
/*
* 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, const char *attname, bool sysColOK)
{
int i;
for (i = 0; i < rd->rd_rel->relnatts; i++)
{
2002-09-04 22:31:48 +02:00
Form_pg_attribute att = rd->rd_att->attrs[i];
if (namestrcmp(&(att->attname), attname) == 0 && !att->attisdropped)
1998-09-01 05:29:17 +02:00
return i + 1;
}
if (sysColOK)
{
if ((i = specialAttNum(attname)) != InvalidAttrNumber)
{
if (i != ObjectIdAttributeNumber || rd->rd_rel->relhasoids)
return i;
}
}
/* on failure */
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
attname, RelationGetRelationName(rd))));
return InvalidAttrNumber; /* keep compiler quiet */
}
/* specialAttNum()
*
* Check attribute name to see if it is "special", e.g. "oid".
* - thomas 2000-02-07
*
* Note: this only discovers whether the name could be a system attribute.
* Caller needs to verify that it really is an attribute of the rel,
* at least in the case of "oid", which is now optional.
*/
static int
specialAttNum(const char *attname)
{
Form_pg_attribute sysatt;
sysatt = SystemAttributeByName(attname,
true /* "oid" will be accepted */ );
if (sysatt != NULL)
return sysatt->attnum;
return InvalidAttrNumber;
}
2001-10-23 19:39:03 +02:00
/*
* given attribute id, return name 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.
*/
Name
attnumAttName(Relation rd, int attid)
{
if (attid <= 0)
{
Form_pg_attribute sysatt;
sysatt = SystemAttributeDefinition(attid, rd->rd_rel->relhasoids);
return &sysatt->attname;
}
if (attid > rd->rd_att->natts)
elog(ERROR, "invalid attribute number %d", attid);
2001-10-23 19:39:03 +02:00
return &rd->rd_att->attrs[attid - 1]->attname;
}
1998-01-20 06:05:08 +01:00
/*
* given attribute id, return type of that attribute
*
1998-01-20 06:05:08 +01:00
* 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)
{
Form_pg_attribute sysatt;
sysatt = SystemAttributeDefinition(attid, rd->rd_rel->relhasoids);
return sysatt->atttypid;
}
2001-10-23 19:39:03 +02:00
if (attid > rd->rd_att->natts)
elog(ERROR, "invalid attribute number %d", attid);
1998-09-01 05:29:17 +02:00
return rd->rd_att->attrs[attid - 1]->atttypid;
}
/*
* Generate a warning or error about an implicit RTE, if appropriate.
*
* If ADD_MISSING_FROM is not enabled, raise an error.
*
* Our current theory on warnings is that we should allow "SELECT foo.*"
* but warn about a mixture of explicit and implicit RTEs.
*/
static void
warnAutoRange(ParseState *pstate, RangeVar *relation)
{
bool foundInFromCl = false;
List *temp;
if (!add_missing_from)
{
if (pstate->parentParseState != NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("missing FROM-clause entry in subquery for table \"%s\"",
relation->relname)));
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("missing FROM-clause entry for table \"%s\"",
relation->relname)));
}
foreach(temp, pstate->p_rtable)
{
RangeTblEntry *rte = lfirst(temp);
if (rte->inFromCl)
{
foundInFromCl = true;
break;
}
}
if (foundInFromCl)
{
if (pstate->parentParseState != NULL)
ereport(NOTICE,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("adding missing FROM-clause entry in subquery for table \"%s\"",
relation->relname)));
else
ereport(NOTICE,
(errcode(ERRCODE_UNDEFINED_TABLE),
2003-08-04 02:43:34 +02:00
errmsg("adding missing FROM-clause entry for table \"%s\"",
relation->relname)));
}
}