mirror of
https://github.com/postgres/postgres.git
synced 2025-11-12 05:01:15 +03:00
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
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/access/common/tupdesc.c,v 1.83 2002/08/02 18:15:04 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/access/common/tupdesc.c,v 1.84 2002/08/04 19:48:09 momjian Exp $
|
||||
*
|
||||
* NOTES
|
||||
* some of the executor utility code such as "ExecTypeFromTL" should be
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "catalog/namespace.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "nodes/parsenodes.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "parser/parse_type.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/syscache.h"
|
||||
@@ -600,46 +601,53 @@ RelationNameGetTupleDesc(char *relname)
|
||||
TupleDesc
|
||||
TypeGetTupleDesc(Oid typeoid, List *colaliases)
|
||||
{
|
||||
Oid relid = typeidTypeRelid(typeoid);
|
||||
TupleDesc tupdesc;
|
||||
char functyptype = typeid_get_typtype(typeoid);
|
||||
TupleDesc tupdesc = NULL;
|
||||
|
||||
/*
|
||||
* Build a suitable tupledesc representing the output rows
|
||||
*/
|
||||
if (OidIsValid(relid))
|
||||
if (functyptype == 'c')
|
||||
{
|
||||
/* Composite data type, i.e. a table's row type */
|
||||
Relation rel;
|
||||
int natts;
|
||||
Oid relid = typeidTypeRelid(typeoid);
|
||||
|
||||
rel = relation_open(relid, AccessShareLock);
|
||||
tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
|
||||
natts = tupdesc->natts;
|
||||
relation_close(rel, AccessShareLock);
|
||||
|
||||
/* check to see if we've given column aliases */
|
||||
if(colaliases != NIL)
|
||||
if (OidIsValid(relid))
|
||||
{
|
||||
char *label;
|
||||
int varattno;
|
||||
Relation rel;
|
||||
int natts;
|
||||
|
||||
/* does the List length match the number of attributes */
|
||||
if (length(colaliases) != natts)
|
||||
elog(ERROR, "TypeGetTupleDesc: number of aliases does not match number of attributes");
|
||||
rel = relation_open(relid, AccessShareLock);
|
||||
tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
|
||||
natts = tupdesc->natts;
|
||||
relation_close(rel, AccessShareLock);
|
||||
|
||||
/* OK, use the aliases instead */
|
||||
for (varattno = 0; varattno < natts; varattno++)
|
||||
/* check to see if we've given column aliases */
|
||||
if(colaliases != NIL)
|
||||
{
|
||||
label = strVal(nth(varattno, colaliases));
|
||||
char *label;
|
||||
int varattno;
|
||||
|
||||
if (label != NULL)
|
||||
namestrcpy(&(tupdesc->attrs[varattno]->attname), label);
|
||||
else
|
||||
MemSet(NameStr(tupdesc->attrs[varattno]->attname), 0, NAMEDATALEN);
|
||||
/* does the List length match the number of attributes */
|
||||
if (length(colaliases) != natts)
|
||||
elog(ERROR, "TypeGetTupleDesc: number of aliases does not match number of attributes");
|
||||
|
||||
/* OK, use the aliases instead */
|
||||
for (varattno = 0; varattno < natts; varattno++)
|
||||
{
|
||||
label = strVal(nth(varattno, colaliases));
|
||||
|
||||
if (label != NULL)
|
||||
namestrcpy(&(tupdesc->attrs[varattno]->attname), label);
|
||||
else
|
||||
MemSet(NameStr(tupdesc->attrs[varattno]->attname), 0, NAMEDATALEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
elog(ERROR, "Invalid return relation specified for function");
|
||||
}
|
||||
else
|
||||
else if (functyptype == 'b')
|
||||
{
|
||||
/* Must be a base data type, i.e. scalar */
|
||||
char *attname;
|
||||
@@ -664,6 +672,11 @@ TypeGetTupleDesc(Oid typeoid, List *colaliases)
|
||||
0,
|
||||
false);
|
||||
}
|
||||
else if (functyptype == 'p' && typeoid == RECORDOID)
|
||||
elog(ERROR, "Unable to determine tuple description for function"
|
||||
" returning \"record\"");
|
||||
else
|
||||
elog(ERROR, "Unknown kind of return type specified for function");
|
||||
|
||||
return tupdesc;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.82 2002/08/02 18:15:05 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.83 2002/08/04 19:48:09 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "miscadmin.h"
|
||||
#include "parser/parse_coerce.h"
|
||||
#include "parser/parse_expr.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "parser/parse_type.h"
|
||||
#include "tcop/tcopprot.h"
|
||||
#include "utils/builtins.h"
|
||||
@@ -33,7 +34,7 @@
|
||||
#include "utils/syscache.h"
|
||||
|
||||
|
||||
static void checkretval(Oid rettype, List *queryTreeList);
|
||||
static void checkretval(Oid rettype, char fn_typtype, List *queryTreeList);
|
||||
Datum fmgr_internal_validator(PG_FUNCTION_ARGS);
|
||||
Datum fmgr_c_validator(PG_FUNCTION_ARGS);
|
||||
Datum fmgr_sql_validator(PG_FUNCTION_ARGS);
|
||||
@@ -367,94 +368,113 @@ checkretval(Oid rettype, List *queryTreeList)
|
||||
*/
|
||||
tlistlen = ExecCleanTargetListLength(tlist);
|
||||
|
||||
/*
|
||||
* For base-type returns, the target list should have exactly one
|
||||
* entry, and its type should agree with what the user declared. (As
|
||||
* of Postgres 7.2, we accept binary-compatible types too.)
|
||||
*/
|
||||
typerelid = typeidTypeRelid(rettype);
|
||||
if (typerelid == InvalidOid)
|
||||
{
|
||||
if (tlistlen != 1)
|
||||
elog(ERROR, "function declared to return %s returns multiple columns in final SELECT",
|
||||
format_type_be(rettype));
|
||||
|
||||
restype = ((TargetEntry *) lfirst(tlist))->resdom->restype;
|
||||
if (!IsBinaryCompatible(restype, rettype))
|
||||
elog(ERROR, "return type mismatch in function: declared to return %s, returns %s",
|
||||
format_type_be(rettype), format_type_be(restype));
|
||||
if (fn_typtype == 'b')
|
||||
{
|
||||
/*
|
||||
* For base-type returns, the target list should have exactly one
|
||||
* entry, and its type should agree with what the user declared. (As
|
||||
* of Postgres 7.2, we accept binary-compatible types too.)
|
||||
*/
|
||||
|
||||
if (typerelid == InvalidOid)
|
||||
{
|
||||
if (tlistlen != 1)
|
||||
elog(ERROR, "function declared to return %s returns multiple columns in final SELECT",
|
||||
format_type_be(rettype));
|
||||
|
||||
restype = ((TargetEntry *) lfirst(tlist))->resdom->restype;
|
||||
if (!IsBinaryCompatible(restype, rettype))
|
||||
elog(ERROR, "return type mismatch in function: declared to return %s, returns %s",
|
||||
format_type_be(rettype), format_type_be(restype));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the target list is of length 1, and the type of the varnode in
|
||||
* the target list matches the declared return type, this is okay.
|
||||
* This can happen, for example, where the body of the function is
|
||||
* 'SELECT func2()', where func2 has the same return type as the
|
||||
* function that's calling it.
|
||||
*/
|
||||
if (tlistlen == 1)
|
||||
{
|
||||
restype = ((TargetEntry *) lfirst(tlist))->resdom->restype;
|
||||
if (IsBinaryCompatible(restype, rettype))
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (fn_typtype == 'c')
|
||||
{
|
||||
/*
|
||||
* By here, the procedure returns a tuple or set of tuples. This part
|
||||
* of the typechecking is a hack. We look up the relation that is the
|
||||
* declared return type, and scan the non-deleted attributes to ensure
|
||||
* that they match the datatypes of the non-resjunk columns.
|
||||
*/
|
||||
reln = heap_open(typerelid, AccessShareLock);
|
||||
relnatts = reln->rd_rel->relnatts;
|
||||
rellogcols = 0; /* we'll count nondeleted cols as we go */
|
||||
colindex = 0;
|
||||
|
||||
foreach(tlistitem, tlist)
|
||||
{
|
||||
TargetEntry *tle = (TargetEntry *) lfirst(tlistitem);
|
||||
Form_pg_attribute attr;
|
||||
Oid tletype;
|
||||
Oid atttype;
|
||||
|
||||
if (tle->resdom->resjunk)
|
||||
continue;
|
||||
|
||||
do {
|
||||
colindex++;
|
||||
if (colindex > relnatts)
|
||||
elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)",
|
||||
format_type_be(rettype), rellogcols);
|
||||
attr = reln->rd_att->attrs[colindex - 1];
|
||||
} while (attr->attisdropped);
|
||||
rellogcols++;
|
||||
|
||||
tletype = exprType(tle->expr);
|
||||
atttype = attr->atttypid;
|
||||
if (!IsBinaryCompatible(tletype, atttype))
|
||||
elog(ERROR, "function declared to return %s returns %s instead of %s at column %d",
|
||||
format_type_be(rettype),
|
||||
format_type_be(tletype),
|
||||
format_type_be(atttype),
|
||||
rellogcols);
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
colindex++;
|
||||
if (colindex > relnatts)
|
||||
break;
|
||||
if (!reln->rd_att->attrs[colindex - 1]->attisdropped)
|
||||
rellogcols++;
|
||||
}
|
||||
|
||||
if (tlistlen != rellogcols)
|
||||
elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)",
|
||||
format_type_be(rettype), rellogcols);
|
||||
|
||||
heap_close(reln, AccessShareLock);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the target list is of length 1, and the type of the varnode in
|
||||
* the target list matches the declared return type, this is okay.
|
||||
* This can happen, for example, where the body of the function is
|
||||
* 'SELECT func2()', where func2 has the same return type as the
|
||||
* function that's calling it.
|
||||
*/
|
||||
if (tlistlen == 1)
|
||||
else if (fn_typtype == 'p' && rettype == RECORDOID)
|
||||
{
|
||||
restype = ((TargetEntry *) lfirst(tlist))->resdom->restype;
|
||||
if (IsBinaryCompatible(restype, rettype))
|
||||
return;
|
||||
/*
|
||||
* For RECORD return type, defer this check until we get the
|
||||
* first tuple.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* By here, the procedure returns a tuple or set of tuples. This part
|
||||
* of the typechecking is a hack. We look up the relation that is the
|
||||
* declared return type, and scan the non-deleted attributes to ensure
|
||||
* that they match the datatypes of the non-resjunk columns.
|
||||
*/
|
||||
reln = heap_open(typerelid, AccessShareLock);
|
||||
relnatts = reln->rd_rel->relnatts;
|
||||
rellogcols = 0; /* we'll count nondeleted cols as we go */
|
||||
colindex = 0;
|
||||
|
||||
foreach(tlistitem, tlist)
|
||||
{
|
||||
TargetEntry *tle = (TargetEntry *) lfirst(tlistitem);
|
||||
Form_pg_attribute attr;
|
||||
Oid tletype;
|
||||
Oid atttype;
|
||||
|
||||
if (tle->resdom->resjunk)
|
||||
continue;
|
||||
|
||||
do {
|
||||
colindex++;
|
||||
if (colindex > relnatts)
|
||||
elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)",
|
||||
format_type_be(rettype), rellogcols);
|
||||
attr = reln->rd_att->attrs[colindex - 1];
|
||||
} while (attr->attisdropped);
|
||||
rellogcols++;
|
||||
|
||||
tletype = exprType(tle->expr);
|
||||
atttype = attr->atttypid;
|
||||
if (!IsBinaryCompatible(tletype, atttype))
|
||||
elog(ERROR, "function declared to return %s returns %s instead of %s at column %d",
|
||||
format_type_be(rettype),
|
||||
format_type_be(tletype),
|
||||
format_type_be(atttype),
|
||||
rellogcols);
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
colindex++;
|
||||
if (colindex > relnatts)
|
||||
break;
|
||||
if (!reln->rd_att->attrs[colindex - 1]->attisdropped)
|
||||
rellogcols++;
|
||||
}
|
||||
|
||||
if (tlistlen != rellogcols)
|
||||
elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)",
|
||||
format_type_be(rettype), rellogcols);
|
||||
|
||||
heap_close(reln, AccessShareLock);
|
||||
else
|
||||
elog(ERROR, "Unknown kind of return type specified for function");
|
||||
}
|
||||
|
||||
|
||||
@@ -553,6 +573,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
|
||||
bool isnull;
|
||||
Datum tmp;
|
||||
char *prosrc;
|
||||
char functyptype;
|
||||
|
||||
tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
@@ -569,8 +590,11 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
|
||||
|
||||
prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
|
||||
|
||||
/* check typtype to see if we have a predetermined return type */
|
||||
functyptype = typeid_get_typtype(proc->prorettype);
|
||||
|
||||
querytree_list = pg_parse_and_rewrite(prosrc, proc->proargtypes, proc->pronargs);
|
||||
checkretval(proc->prorettype, querytree_list);
|
||||
checkretval(proc->prorettype, functyptype, querytree_list);
|
||||
|
||||
ReleaseSysCache(tuple);
|
||||
PG_RETURN_BOOL(true);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.52 2002/06/20 20:29:28 momjian Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.53 2002/08/04 19:48:09 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -194,7 +194,8 @@ init_sql_fcache(FmgrInfo *finfo)
|
||||
* get the type length and by-value flag from the type tuple
|
||||
*/
|
||||
fcache->typlen = typeStruct->typlen;
|
||||
if (typeStruct->typrelid == InvalidOid)
|
||||
|
||||
if (typeStruct->typtype == 'b')
|
||||
{
|
||||
/* The return type is not a relation, so just use byval */
|
||||
fcache->typbyval = typeStruct->typbyval;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.3 2002/07/20 05:16:58 momjian Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.4 2002/08/04 19:48:09 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "executor/nodeFunctionscan.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "parser/parse_expr.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "parser/parse_type.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "tcop/pquery.h"
|
||||
@@ -39,14 +40,11 @@
|
||||
#include "utils/tuplestore.h"
|
||||
|
||||
static TupleTableSlot *FunctionNext(FunctionScan *node);
|
||||
static TupleTableSlot *function_getonetuple(TupleTableSlot *slot,
|
||||
Node *expr,
|
||||
ExprContext *econtext,
|
||||
TupleDesc tupdesc,
|
||||
bool returnsTuple,
|
||||
static TupleTableSlot *function_getonetuple(FunctionScanState *scanstate,
|
||||
bool *isNull,
|
||||
ExprDoneCond *isDone);
|
||||
static FunctionMode get_functionmode(Node *expr);
|
||||
static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2);
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Scan Support
|
||||
@@ -62,9 +60,6 @@ static TupleTableSlot *
|
||||
FunctionNext(FunctionScan *node)
|
||||
{
|
||||
TupleTableSlot *slot;
|
||||
Node *expr;
|
||||
ExprContext *econtext;
|
||||
TupleDesc tupdesc;
|
||||
EState *estate;
|
||||
ScanDirection direction;
|
||||
Tuplestorestate *tuplestorestate;
|
||||
@@ -78,11 +73,8 @@ FunctionNext(FunctionScan *node)
|
||||
scanstate = (FunctionScanState *) node->scan.scanstate;
|
||||
estate = node->scan.plan.state;
|
||||
direction = estate->es_direction;
|
||||
econtext = scanstate->csstate.cstate.cs_ExprContext;
|
||||
|
||||
tuplestorestate = scanstate->tuplestorestate;
|
||||
tupdesc = scanstate->tupdesc;
|
||||
expr = scanstate->funcexpr;
|
||||
|
||||
/*
|
||||
* If first time through, read all tuples from function and pass them to
|
||||
@@ -108,10 +100,7 @@ FunctionNext(FunctionScan *node)
|
||||
|
||||
isNull = false;
|
||||
isDone = ExprSingleResult;
|
||||
slot = function_getonetuple(scanstate->csstate.css_ScanTupleSlot,
|
||||
expr, econtext, tupdesc,
|
||||
scanstate->returnsTuple,
|
||||
&isNull, &isDone);
|
||||
slot = function_getonetuple(scanstate, &isNull, &isDone);
|
||||
if (TupIsNull(slot))
|
||||
break;
|
||||
|
||||
@@ -169,7 +158,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
|
||||
RangeTblEntry *rte;
|
||||
Oid funcrettype;
|
||||
Oid funcrelid;
|
||||
TupleDesc tupdesc;
|
||||
char functyptype;
|
||||
TupleDesc tupdesc = NULL;
|
||||
|
||||
/*
|
||||
* FunctionScan should not have any children.
|
||||
@@ -209,25 +199,36 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
|
||||
rte = rt_fetch(node->scan.scanrelid, estate->es_range_table);
|
||||
Assert(rte->rtekind == RTE_FUNCTION);
|
||||
funcrettype = exprType(rte->funcexpr);
|
||||
funcrelid = typeidTypeRelid(funcrettype);
|
||||
|
||||
/*
|
||||
* Now determine if the function returns a simple or composite type,
|
||||
* and check/add column aliases.
|
||||
*/
|
||||
functyptype = typeid_get_typtype(funcrettype);
|
||||
|
||||
/*
|
||||
* Build a suitable tupledesc representing the output rows
|
||||
*/
|
||||
if (OidIsValid(funcrelid))
|
||||
if (functyptype == 'c')
|
||||
{
|
||||
/*
|
||||
* Composite data type, i.e. a table's row type
|
||||
* Same as ordinary relation RTE
|
||||
*/
|
||||
Relation rel;
|
||||
funcrelid = typeidTypeRelid(funcrettype);
|
||||
if (OidIsValid(funcrelid))
|
||||
{
|
||||
/*
|
||||
* Composite data type, i.e. a table's row type
|
||||
* Same as ordinary relation RTE
|
||||
*/
|
||||
Relation rel;
|
||||
|
||||
rel = relation_open(funcrelid, AccessShareLock);
|
||||
tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
|
||||
relation_close(rel, AccessShareLock);
|
||||
scanstate->returnsTuple = true;
|
||||
rel = relation_open(funcrelid, AccessShareLock);
|
||||
tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
|
||||
relation_close(rel, AccessShareLock);
|
||||
scanstate->returnsTuple = true;
|
||||
}
|
||||
else
|
||||
elog(ERROR, "Invalid return relation specified for function");
|
||||
}
|
||||
else
|
||||
else if (functyptype == 'b')
|
||||
{
|
||||
/*
|
||||
* Must be a base data type, i.e. scalar
|
||||
@@ -244,6 +245,21 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
|
||||
false);
|
||||
scanstate->returnsTuple = false;
|
||||
}
|
||||
else if (functyptype == 'p' && funcrettype == RECORDOID)
|
||||
{
|
||||
/*
|
||||
* Must be a pseudo type, i.e. record
|
||||
*/
|
||||
List *coldeflist = rte->coldeflist;
|
||||
|
||||
tupdesc = BuildDescForRelation(coldeflist);
|
||||
scanstate->returnsTuple = true;
|
||||
}
|
||||
else
|
||||
elog(ERROR, "Unknown kind of return type specified for function");
|
||||
|
||||
scanstate->fn_typeid = funcrettype;
|
||||
scanstate->fn_typtype = functyptype;
|
||||
scanstate->tupdesc = tupdesc;
|
||||
ExecSetSlotDescriptor(scanstate->csstate.css_ScanTupleSlot,
|
||||
tupdesc, false);
|
||||
@@ -404,17 +420,20 @@ ExecFunctionReScan(FunctionScan *node, ExprContext *exprCtxt, Plan *parent)
|
||||
* Run the underlying function to get the next tuple
|
||||
*/
|
||||
static TupleTableSlot *
|
||||
function_getonetuple(TupleTableSlot *slot,
|
||||
Node *expr,
|
||||
ExprContext *econtext,
|
||||
TupleDesc tupdesc,
|
||||
bool returnsTuple,
|
||||
function_getonetuple(FunctionScanState *scanstate,
|
||||
bool *isNull,
|
||||
ExprDoneCond *isDone)
|
||||
{
|
||||
HeapTuple tuple;
|
||||
Datum retDatum;
|
||||
char nullflag;
|
||||
HeapTuple tuple;
|
||||
Datum retDatum;
|
||||
char nullflag;
|
||||
TupleDesc tupdesc = scanstate->tupdesc;
|
||||
bool returnsTuple = scanstate->returnsTuple;
|
||||
Node *expr = scanstate->funcexpr;
|
||||
Oid fn_typeid = scanstate->fn_typeid;
|
||||
char fn_typtype = scanstate->fn_typtype;
|
||||
ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
|
||||
TupleTableSlot *slot = scanstate->csstate.css_ScanTupleSlot;
|
||||
|
||||
/*
|
||||
* get the next Datum from the function
|
||||
@@ -435,6 +454,16 @@ function_getonetuple(TupleTableSlot *slot,
|
||||
* function returns pointer to tts??
|
||||
*/
|
||||
slot = (TupleTableSlot *) retDatum;
|
||||
|
||||
/*
|
||||
* if function return type was RECORD, we need to check to be
|
||||
* sure the structure from the query matches the actual return
|
||||
* structure
|
||||
*/
|
||||
if (fn_typtype == 'p' && fn_typeid == RECORDOID)
|
||||
if (tupledesc_mismatch(tupdesc, slot->ttc_tupleDescriptor))
|
||||
elog(ERROR, "Query specified return tuple and actual"
|
||||
" function return tuple do not match");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -467,3 +496,26 @@ get_functionmode(Node *expr)
|
||||
*/
|
||||
return PM_REPEATEDCALL;
|
||||
}
|
||||
|
||||
static bool
|
||||
tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (tupdesc1->natts != tupdesc2->natts)
|
||||
return true;
|
||||
|
||||
for (i = 0; i < tupdesc1->natts; i++)
|
||||
{
|
||||
Form_pg_attribute attr1 = tupdesc1->attrs[i];
|
||||
Form_pg_attribute attr2 = tupdesc2->attrs[i];
|
||||
|
||||
/*
|
||||
* We really only care about number of attributes and data type
|
||||
*/
|
||||
if (attr1->atttypid != attr2->atttypid)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.199 2002/08/04 04:31:44 momjian Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.200 2002/08/04 19:48:09 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -1482,6 +1482,7 @@ _copyRangeTblEntry(RangeTblEntry *from)
|
||||
newnode->relid = from->relid;
|
||||
Node_Copy(from, newnode, subquery);
|
||||
Node_Copy(from, newnode, funcexpr);
|
||||
Node_Copy(from, newnode, coldeflist);
|
||||
newnode->jointype = from->jointype;
|
||||
Node_Copy(from, newnode, joinaliasvars);
|
||||
Node_Copy(from, newnode, alias);
|
||||
@@ -1707,6 +1708,7 @@ _copyRangeFunction(RangeFunction *from)
|
||||
|
||||
Node_Copy(from, newnode, funccallnode);
|
||||
Node_Copy(from, newnode, alias);
|
||||
Node_Copy(from, newnode, coldeflist);
|
||||
|
||||
return newnode;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.146 2002/08/04 04:31:44 momjian Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.147 2002/08/04 19:48:09 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -1607,6 +1607,8 @@ _equalRangeVar(RangeVar *a, RangeVar *b)
|
||||
return false;
|
||||
if (!equal(a->alias, b->alias))
|
||||
return false;
|
||||
if (!equal(a->coldeflist, b->coldeflist))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1742,6 +1744,8 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
|
||||
return false;
|
||||
if (!equal(a->funcexpr, b->funcexpr))
|
||||
return false;
|
||||
if (!equal(a->coldeflist, b->coldeflist))
|
||||
return false;
|
||||
if (a->jointype != b->jointype)
|
||||
return false;
|
||||
if (!equal(a->joinaliasvars, b->joinaliasvars))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.165 2002/07/18 17:14:19 momjian Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.166 2002/08/04 19:48:09 momjian Exp $
|
||||
*
|
||||
* NOTES
|
||||
* Every (plan) node in POSTGRES has an associated "out" routine which
|
||||
@@ -1004,6 +1004,8 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
|
||||
case RTE_FUNCTION:
|
||||
appendStringInfo(str, ":funcexpr ");
|
||||
_outNode(str, node->funcexpr);
|
||||
appendStringInfo(str, ":coldeflist ");
|
||||
_outNode(str, node->coldeflist);
|
||||
break;
|
||||
case RTE_JOIN:
|
||||
appendStringInfo(str, ":jointype %d :joinaliasvars ",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.126 2002/07/18 17:14:19 momjian Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.127 2002/08/04 19:48:09 momjian Exp $
|
||||
*
|
||||
* NOTES
|
||||
* Most of the read functions for plan nodes are tested. (In fact, they
|
||||
@@ -1545,6 +1545,10 @@ _readRangeTblEntry(void)
|
||||
case RTE_FUNCTION:
|
||||
token = pg_strtok(&length); /* eat :funcexpr */
|
||||
local_node->funcexpr = nodeRead(true); /* now read it */
|
||||
|
||||
token = pg_strtok(&length); /* eat :coldeflist */
|
||||
local_node->coldeflist = nodeRead(true); /* now read it */
|
||||
|
||||
break;
|
||||
|
||||
case RTE_JOIN:
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.354 2002/08/04 06:51:23 thomas Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.355 2002/08/04 19:48:09 momjian Exp $
|
||||
*
|
||||
* HISTORY
|
||||
* AUTHOR DATE MAJOR EVENT
|
||||
@@ -215,7 +215,8 @@ static void doNegateFloat(Value *v);
|
||||
target_list, update_target_list, insert_column_list,
|
||||
insert_target_list, def_list, opt_indirection,
|
||||
group_clause, TriggerFuncArgs, select_limit,
|
||||
opt_select_limit, opclass_item_list, trans_options
|
||||
opt_select_limit, opclass_item_list, trans_options,
|
||||
tableFuncElementList
|
||||
|
||||
%type <range> into_clause, OptTempTableName
|
||||
|
||||
@@ -256,8 +257,8 @@ static void doNegateFloat(Value *v);
|
||||
|
||||
%type <vsetstmt> set_rest
|
||||
|
||||
%type <node> OptTableElement, ConstraintElem
|
||||
%type <node> columnDef
|
||||
%type <node> OptTableElement, ConstraintElem, tableFuncElement
|
||||
%type <node> columnDef, tableFuncColumnDef
|
||||
%type <defelt> def_elem
|
||||
%type <node> def_arg, columnElem, where_clause, insert_column_item,
|
||||
a_expr, b_expr, c_expr, r_expr, AexprConst,
|
||||
@@ -4448,6 +4449,34 @@ table_ref: relation_expr
|
||||
{
|
||||
RangeFunction *n = makeNode(RangeFunction);
|
||||
n->funccallnode = $1;
|
||||
n->coldeflist = NIL;
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
| func_table AS '(' tableFuncElementList ')'
|
||||
{
|
||||
RangeFunction *n = makeNode(RangeFunction);
|
||||
n->funccallnode = $1;
|
||||
n->coldeflist = $4;
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
| func_table AS ColId '(' tableFuncElementList ')'
|
||||
{
|
||||
RangeFunction *n = makeNode(RangeFunction);
|
||||
Alias *a = makeNode(Alias);
|
||||
n->funccallnode = $1;
|
||||
a->aliasname = $3;
|
||||
n->alias = a;
|
||||
n->coldeflist = $5;
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
| func_table ColId '(' tableFuncElementList ')'
|
||||
{
|
||||
RangeFunction *n = makeNode(RangeFunction);
|
||||
Alias *a = makeNode(Alias);
|
||||
n->funccallnode = $1;
|
||||
a->aliasname = $2;
|
||||
n->alias = a;
|
||||
n->coldeflist = $4;
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
| func_table alias_clause
|
||||
@@ -4455,6 +4484,7 @@ table_ref: relation_expr
|
||||
RangeFunction *n = makeNode(RangeFunction);
|
||||
n->funccallnode = $1;
|
||||
n->alias = $2;
|
||||
n->coldeflist = NIL;
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
| select_with_parens
|
||||
@@ -4703,6 +4733,39 @@ where_clause:
|
||||
;
|
||||
|
||||
|
||||
tableFuncElementList:
|
||||
tableFuncElementList ',' tableFuncElement
|
||||
{
|
||||
if ($3 != NULL)
|
||||
$$ = lappend($1, $3);
|
||||
else
|
||||
$$ = $1;
|
||||
}
|
||||
| tableFuncElement
|
||||
{
|
||||
if ($1 != NULL)
|
||||
$$ = makeList1($1);
|
||||
else
|
||||
$$ = NIL;
|
||||
}
|
||||
| /*EMPTY*/ { $$ = NIL; }
|
||||
;
|
||||
|
||||
tableFuncElement:
|
||||
tableFuncColumnDef { $$ = $1; }
|
||||
;
|
||||
|
||||
tableFuncColumnDef: ColId Typename
|
||||
{
|
||||
ColumnDef *n = makeNode(ColumnDef);
|
||||
n->colname = $1;
|
||||
n->typename = $2;
|
||||
n->constraints = NIL;
|
||||
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
;
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Type syntax
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.94 2002/06/20 20:29:32 momjian Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.95 2002/08/04 19:48:10 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -515,7 +515,7 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
|
||||
* OK, build an RTE for the function.
|
||||
*/
|
||||
rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr,
|
||||
r->alias, true);
|
||||
r, true);
|
||||
|
||||
/*
|
||||
* We create a RangeTblRef, but we do not add it to the joinlist or
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.71 2002/08/02 18:15:07 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.72 2002/08/04 19:48:10 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -699,12 +699,14 @@ RangeTblEntry *
|
||||
addRangeTableEntryForFunction(ParseState *pstate,
|
||||
char *funcname,
|
||||
Node *funcexpr,
|
||||
Alias *alias,
|
||||
RangeFunction *rangefunc,
|
||||
bool inFromCl)
|
||||
{
|
||||
RangeTblEntry *rte = makeNode(RangeTblEntry);
|
||||
Oid funcrettype = exprType(funcexpr);
|
||||
Oid funcrelid;
|
||||
char functyptype;
|
||||
Alias *alias = rangefunc->alias;
|
||||
List *coldeflist = rangefunc->coldeflist;
|
||||
Alias *eref;
|
||||
int numaliases;
|
||||
int varattno;
|
||||
@@ -713,6 +715,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
||||
rte->relid = InvalidOid;
|
||||
rte->subquery = NULL;
|
||||
rte->funcexpr = funcexpr;
|
||||
rte->coldeflist = coldeflist;
|
||||
rte->alias = alias;
|
||||
|
||||
eref = alias ? (Alias *) copyObject(alias) : makeAlias(funcname, NIL);
|
||||
@@ -724,47 +727,56 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
||||
* Now determine if the function returns a simple or composite type,
|
||||
* and check/add column aliases.
|
||||
*/
|
||||
funcrelid = typeidTypeRelid(funcrettype);
|
||||
functyptype = typeid_get_typtype(funcrettype);
|
||||
|
||||
if (OidIsValid(funcrelid))
|
||||
if (functyptype == 'c')
|
||||
{
|
||||
/*
|
||||
* Composite data type, i.e. a table's row type
|
||||
*
|
||||
* Get the rel's relcache entry. This access ensures that we have an
|
||||
* up-to-date relcache entry for the rel.
|
||||
* Named composite data type, i.e. a table's row type
|
||||
*/
|
||||
Relation rel;
|
||||
int maxattrs;
|
||||
Oid funcrelid = typeidTypeRelid(funcrettype);
|
||||
|
||||
rel = heap_open(funcrelid, AccessShareLock);
|
||||
|
||||
/*
|
||||
* Since the rel is open anyway, let's check that the number of column
|
||||
* aliases is reasonable.
|
||||
*/
|
||||
maxattrs = RelationGetNumberOfAttributes(rel);
|
||||
if (maxattrs < numaliases)
|
||||
elog(ERROR, "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++)
|
||||
if (OidIsValid(funcrelid))
|
||||
{
|
||||
char *attrname;
|
||||
/*
|
||||
* Get the rel's relcache entry. This access ensures that we have an
|
||||
* up-to-date relcache entry for the rel.
|
||||
*/
|
||||
Relation rel;
|
||||
int maxattrs;
|
||||
|
||||
attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
|
||||
eref->colnames = lappend(eref->colnames, makeString(attrname));
|
||||
rel = heap_open(funcrelid, AccessShareLock);
|
||||
|
||||
/*
|
||||
* Since the rel is open anyway, let's check that the number of column
|
||||
* aliases is reasonable.
|
||||
*/
|
||||
maxattrs = RelationGetNumberOfAttributes(rel);
|
||||
if (maxattrs < numaliases)
|
||||
elog(ERROR, "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;
|
||||
|
||||
attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
|
||||
eref->colnames = lappend(eref->colnames, makeString(attrname));
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
else
|
||||
elog(ERROR, "Invalid return relation specified for function %s",
|
||||
funcname);
|
||||
}
|
||||
else
|
||||
else if (functyptype == 'b')
|
||||
{
|
||||
/*
|
||||
* Must be a base data type, i.e. scalar.
|
||||
@@ -776,6 +788,22 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
||||
if (numaliases == 0)
|
||||
eref->colnames = makeList1(makeString(funcname));
|
||||
}
|
||||
else if (functyptype == 'p' && funcrettype == RECORDOID)
|
||||
{
|
||||
List *col;
|
||||
|
||||
foreach(col, coldeflist)
|
||||
{
|
||||
char *attrname;
|
||||
ColumnDef *n = lfirst(col);
|
||||
|
||||
attrname = pstrdup(n->colname);
|
||||
eref->colnames = lappend(eref->colnames, makeString(attrname));
|
||||
}
|
||||
}
|
||||
else
|
||||
elog(ERROR, "Unknown kind of return type specified for function %s",
|
||||
funcname);
|
||||
|
||||
/*----------
|
||||
* Flags:
|
||||
@@ -1051,56 +1079,70 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte,
|
||||
case RTE_FUNCTION:
|
||||
{
|
||||
/* Function RTE */
|
||||
Oid funcrettype = exprType(rte->funcexpr);
|
||||
Oid funcrelid = typeidTypeRelid(funcrettype);
|
||||
Oid funcrettype = exprType(rte->funcexpr);
|
||||
char functyptype = typeid_get_typtype(funcrettype);
|
||||
List *coldeflist = rte->coldeflist;
|
||||
|
||||
if (OidIsValid(funcrelid))
|
||||
/*
|
||||
* Build a suitable tupledesc representing the output rows
|
||||
*/
|
||||
if (functyptype == 'c')
|
||||
{
|
||||
/*
|
||||
* Composite data type, i.e. a table's row type
|
||||
* Same as ordinary relation RTE
|
||||
*/
|
||||
Relation rel;
|
||||
int maxattrs;
|
||||
int numaliases;
|
||||
|
||||
rel = heap_open(funcrelid, AccessShareLock);
|
||||
maxattrs = RelationGetNumberOfAttributes(rel);
|
||||
numaliases = length(rte->eref->colnames);
|
||||
|
||||
for (varattno = 0; varattno < maxattrs; varattno++)
|
||||
Oid funcrelid = typeidTypeRelid(funcrettype);
|
||||
if (OidIsValid(funcrelid))
|
||||
{
|
||||
Form_pg_attribute attr = rel->rd_att->attrs[varattno];
|
||||
|
||||
if (attr->attisdropped)
|
||||
continue;
|
||||
/*
|
||||
* Composite data type, i.e. a table's row type
|
||||
* Same as ordinary relation RTE
|
||||
*/
|
||||
Relation rel;
|
||||
int maxattrs;
|
||||
int numaliases;
|
||||
|
||||
if (colnames)
|
||||
rel = heap_open(funcrelid, AccessShareLock);
|
||||
maxattrs = RelationGetNumberOfAttributes(rel);
|
||||
numaliases = length(rte->eref->colnames);
|
||||
|
||||
for (varattno = 0; varattno < maxattrs; varattno++)
|
||||
{
|
||||
char *label;
|
||||
Form_pg_attribute attr = rel->rd_att->attrs[varattno];
|
||||
|
||||
if (varattno < numaliases)
|
||||
label = strVal(nth(varattno, rte->eref->colnames));
|
||||
else
|
||||
label = NameStr(attr->attname);
|
||||
*colnames = lappend(*colnames, makeString(pstrdup(label)));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (colvars)
|
||||
{
|
||||
Var *varnode;
|
||||
|
||||
varnode = makeVar(rtindex, attr->attnum,
|
||||
attr->atttypid, attr->atttypmod,
|
||||
sublevels_up);
|
||||
|
||||
*colvars = lappend(*colvars, varnode);
|
||||
}
|
||||
heap_close(rel, AccessShareLock);
|
||||
}
|
||||
|
||||
heap_close(rel, AccessShareLock);
|
||||
else
|
||||
elog(ERROR, "Invalid return relation specified"
|
||||
" for function");
|
||||
}
|
||||
else
|
||||
else if (functyptype == 'b')
|
||||
{
|
||||
/*
|
||||
* Must be a base data type, i.e. scalar
|
||||
@@ -1120,6 +1162,47 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte,
|
||||
*colvars = lappend(*colvars, varnode);
|
||||
}
|
||||
}
|
||||
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;
|
||||
HeapTuple typeTuple;
|
||||
Oid atttypid;
|
||||
|
||||
typeTuple = typenameType(colDef->typename);
|
||||
atttypid = HeapTupleGetOid(typeTuple);
|
||||
ReleaseSysCache(typeTuple);
|
||||
|
||||
varnode = makeVar(rtindex,
|
||||
attnum,
|
||||
atttypid,
|
||||
-1,
|
||||
sublevels_up);
|
||||
|
||||
*colvars = lappend(*colvars, varnode);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
elog(ERROR, "Unknown kind of return type specified"
|
||||
" for function");
|
||||
}
|
||||
break;
|
||||
case RTE_JOIN:
|
||||
@@ -1308,40 +1391,52 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
||||
case RTE_FUNCTION:
|
||||
{
|
||||
/* Function RTE */
|
||||
Oid funcrettype = exprType(rte->funcexpr);
|
||||
Oid funcrelid = typeidTypeRelid(funcrettype);
|
||||
Oid funcrettype = exprType(rte->funcexpr);
|
||||
char functyptype = typeid_get_typtype(funcrettype);
|
||||
List *coldeflist = rte->coldeflist;
|
||||
|
||||
if (OidIsValid(funcrelid))
|
||||
/*
|
||||
* Build a suitable tupledesc representing the output rows
|
||||
*/
|
||||
if (functyptype == 'c')
|
||||
{
|
||||
/*
|
||||
* Composite data type, i.e. a table's row type
|
||||
* Same as ordinary relation RTE
|
||||
*/
|
||||
HeapTuple tp;
|
||||
Form_pg_attribute att_tup;
|
||||
Oid funcrelid = typeidTypeRelid(funcrettype);
|
||||
|
||||
tp = SearchSysCache(ATTNUM,
|
||||
ObjectIdGetDatum(funcrelid),
|
||||
Int16GetDatum(attnum),
|
||||
0, 0);
|
||||
/* this shouldn't happen... */
|
||||
if (!HeapTupleIsValid(tp))
|
||||
elog(ERROR, "Relation %s does not have attribute %d",
|
||||
get_rel_name(funcrelid), attnum);
|
||||
att_tup = (Form_pg_attribute) GETSTRUCT(tp);
|
||||
/*
|
||||
* If dropped column, pretend it ain't there. See notes
|
||||
* in scanRTEForColumn.
|
||||
*/
|
||||
if (att_tup->attisdropped)
|
||||
elog(ERROR, "Relation \"%s\" has no column \"%s\"",
|
||||
get_rel_name(funcrelid),
|
||||
NameStr(att_tup->attname));
|
||||
*vartype = att_tup->atttypid;
|
||||
*vartypmod = att_tup->atttypmod;
|
||||
ReleaseSysCache(tp);
|
||||
if (OidIsValid(funcrelid))
|
||||
{
|
||||
HeapTuple tp;
|
||||
Form_pg_attribute att_tup;
|
||||
|
||||
tp = SearchSysCache(ATTNUM,
|
||||
ObjectIdGetDatum(funcrelid),
|
||||
Int16GetDatum(attnum),
|
||||
0, 0);
|
||||
/* this shouldn't happen... */
|
||||
if (!HeapTupleIsValid(tp))
|
||||
elog(ERROR, "Relation %s does not have attribute %d",
|
||||
get_rel_name(funcrelid), attnum);
|
||||
att_tup = (Form_pg_attribute) GETSTRUCT(tp);
|
||||
/*
|
||||
* If dropped column, pretend it ain't there. See notes
|
||||
* in scanRTEForColumn.
|
||||
*/
|
||||
if (att_tup->attisdropped)
|
||||
elog(ERROR, "Relation \"%s\" has no column \"%s\"",
|
||||
get_rel_name(funcrelid),
|
||||
NameStr(att_tup->attname));
|
||||
*vartype = att_tup->atttypid;
|
||||
*vartypmod = att_tup->atttypmod;
|
||||
ReleaseSysCache(tp);
|
||||
}
|
||||
else
|
||||
elog(ERROR, "Invalid return relation specified"
|
||||
" for function");
|
||||
}
|
||||
else
|
||||
else if (functyptype == 'b')
|
||||
{
|
||||
/*
|
||||
* Must be a base data type, i.e. scalar
|
||||
@@ -1349,6 +1444,22 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
||||
*vartype = funcrettype;
|
||||
*vartypmod = -1;
|
||||
}
|
||||
else if (functyptype == 'p' && funcrettype == RECORDOID)
|
||||
{
|
||||
ColumnDef *colDef = nth(attnum - 1, coldeflist);
|
||||
HeapTuple typeTuple;
|
||||
Oid atttypid;
|
||||
|
||||
typeTuple = typenameType(colDef->typename);
|
||||
atttypid = HeapTupleGetOid(typeTuple);
|
||||
ReleaseSysCache(typeTuple);
|
||||
|
||||
*vartype = atttypid;
|
||||
*vartypmod = -1;
|
||||
}
|
||||
else
|
||||
elog(ERROR, "Unknown kind of return type specified"
|
||||
" for function");
|
||||
}
|
||||
break;
|
||||
case RTE_JOIN:
|
||||
@@ -1576,3 +1687,28 @@ warnAutoRange(ParseState *pstate, RangeVar *relation)
|
||||
pstate->parentParseState != NULL ? " in subquery" : "",
|
||||
relation->relname);
|
||||
}
|
||||
|
||||
char
|
||||
typeid_get_typtype(Oid typeid)
|
||||
{
|
||||
HeapTuple typeTuple;
|
||||
Form_pg_type typeStruct;
|
||||
char result;
|
||||
|
||||
/*
|
||||
* determine if the function returns a simple, named composite,
|
||||
* or anonymous composite type
|
||||
*/
|
||||
typeTuple = SearchSysCache(TYPEOID,
|
||||
ObjectIdGetDatum(typeid),
|
||||
0, 0, 0);
|
||||
if (!HeapTupleIsValid(typeTuple))
|
||||
elog(ERROR, "cache lookup for type %u failed", typeid);
|
||||
typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);
|
||||
|
||||
result = typeStruct->typtype;
|
||||
|
||||
ReleaseSysCache(typeTuple);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user