1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-02 09:02:37 +03:00

First phase of OUT-parameters project. We can now define and use SQL

functions with OUT parameters.  The various PLs still need work, as does
pg_dump.  Rudimentary docs and regression tests included.
This commit is contained in:
Tom Lane
2005-03-31 22:46:33 +00:00
parent fb13881f42
commit 47888fe842
23 changed files with 1500 additions and 529 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.94 2005/03/29 00:16:59 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.95 2005/03/31 22:46:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -20,6 +20,7 @@
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_type.h"
@ -277,8 +278,8 @@ init_sql_fcache(FmgrInfo *finfo)
* form.
*/
if (haspolyarg || fcache->returnsTuple)
fcache->returnsTuple = check_sql_fn_retval(rettype,
get_typtype(rettype),
fcache->returnsTuple = check_sql_fn_retval(foid,
rettype,
queryTree_list,
&fcache->junkFilter);
@ -858,7 +859,7 @@ ShutdownSQLFunction(Datum arg)
* tuple result), *junkFilter is set to NULL.
*/
bool
check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
JunkFilter **junkFilter)
{
Query *parse;
@ -866,12 +867,8 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
List *tlist;
ListCell *tlistitem;
int tlistlen;
Oid typerelid;
char fn_typtype;
Oid restype;
Relation reln;
int relnatts; /* physical number of columns in rel */
int rellogcols; /* # of nondeleted columns in rel */
int colindex; /* physical column index */
if (junkFilter)
*junkFilter = NULL; /* default result */
@ -922,13 +919,10 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
*/
tlistlen = ExecCleanTargetListLength(tlist);
typerelid = typeidTypeRelid(rettype);
fn_typtype = get_typtype(rettype);
if (fn_typtype == 'b' || fn_typtype == 'd')
{
/* Shouldn't have a typerelid */
Assert(typerelid == InvalidOid);
/*
* For base-type returns, the target list should have exactly one
* entry, and its type should agree with what the user declared.
@ -950,10 +944,13 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
errdetail("Actual return type is %s.",
format_type_be(restype))));
}
else if (fn_typtype == 'c')
else if (fn_typtype == 'c' || rettype == RECORDOID)
{
/* Must have a typerelid */
Assert(typerelid != InvalidOid);
/* Returns a rowtype */
TupleDesc tupdesc;
int tupnatts; /* physical number of columns in tuple */
int tuplogcols; /* # of nondeleted columns in tuple */
int colindex; /* physical column index */
/*
* If the target list is of length 1, and the type of the varnode
@ -969,16 +966,27 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
return false; /* NOT returning whole tuple */
}
/* Is the rowtype fixed, or determined only at runtime? */
if (get_func_result_type(func_id, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
{
/*
* Assume we are returning the whole tuple.
* Crosschecking against what the caller expects will happen at
* runtime.
*/
if (junkFilter)
*junkFilter = ExecInitJunkFilter(tlist, false, NULL);
return true;
}
Assert(tupdesc);
/*
* Otherwise verify that the targetlist matches the return tuple
* type. 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.
* Verify that the targetlist matches the return tuple type.
* We scan the non-deleted attributes to ensure that they match the
* datatypes of the non-resjunk columns.
*/
reln = relation_open(typerelid, AccessShareLock);
relnatts = reln->rd_rel->relnatts;
rellogcols = 0; /* we'll count nondeleted cols as we go */
tupnatts = tupdesc->natts;
tuplogcols = 0; /* we'll count nondeleted cols as we go */
colindex = 0;
foreach(tlistitem, tlist)
@ -994,15 +1002,15 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
do
{
colindex++;
if (colindex > relnatts)
if (colindex > tupnatts)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Final SELECT returns too many columns.")));
attr = reln->rd_att->attrs[colindex - 1];
attr = tupdesc->attrs[colindex - 1];
} while (attr->attisdropped);
rellogcols++;
tuplogcols++;
tletype = exprType((Node *) tle->expr);
atttype = attr->atttypid;
@ -1014,19 +1022,19 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
errdetail("Final SELECT returns %s instead of %s at column %d.",
format_type_be(tletype),
format_type_be(atttype),
rellogcols)));
tuplogcols)));
}
for (;;)
{
colindex++;
if (colindex > relnatts)
if (colindex > tupnatts)
break;
if (!reln->rd_att->attrs[colindex - 1]->attisdropped)
rellogcols++;
if (!tupdesc->attrs[colindex - 1]->attisdropped)
tuplogcols++;
}
if (tlistlen != rellogcols)
if (tlistlen != tuplogcols)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
@ -1036,40 +1044,12 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
/* Set up junk filter if needed */
if (junkFilter)
*junkFilter = ExecInitJunkFilterConversion(tlist,
CreateTupleDescCopy(reln->rd_att),
CreateTupleDescCopy(tupdesc),
NULL);
relation_close(reln, AccessShareLock);
/* Report that we are returning entire tuple result */
return true;
}
else if (rettype == RECORDOID)
{
/*
* 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 *) linitial(tlist))->resdom->restype;
if (IsBinaryCoercible(restype, rettype))
return false; /* NOT returning whole tuple */
}
/*
* Otherwise assume we are returning the whole tuple.
* Crosschecking against what the caller expects will happen at
* runtime.
*/
if (junkFilter)
*junkFilter = ExecInitJunkFilter(tlist, false, NULL);
return true;
}
else if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID)
{
/* This should already have been caught ... */

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.31 2005/03/16 21:38:07 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.32 2005/03/31 22:46:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -22,18 +22,10 @@
*/
#include "postgres.h"
#include "access/heapam.h"
#include "catalog/pg_type.h"
#include "executor/execdebug.h"
#include "executor/execdefs.h"
#include "executor/execdesc.h"
#include "executor/nodeFunctionscan.h"
#include "funcapi.h"
#include "parser/parsetree.h"
#include "parser/parse_expr.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
static TupleTableSlot *FunctionNext(FunctionScanState *node);
@ -180,18 +172,21 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate)
*/
rte = rt_fetch(node->scan.scanrelid, estate->es_range_table);
Assert(rte->rtekind == RTE_FUNCTION);
funcrettype = exprType(rte->funcexpr);
/*
* Now determine if the function returns a simple or composite type,
* and build an appropriate tupdesc.
*/
functypclass = get_type_func_class(funcrettype);
functypclass = get_expr_result_type(rte->funcexpr,
&funcrettype,
&tupdesc);
if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(funcrettype, -1));
Assert(tupdesc);
/* Must copy it out of typcache for safety */
tupdesc = CreateTupleDescCopy(tupdesc);
}
else if (functypclass == TYPEFUNC_SCALAR)
{
@ -216,14 +211,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate)
elog(ERROR, "function in FROM has unsupported return type");
}
/*
* For RECORD results, make sure a typmod has been assigned. (The
* function should do this for itself, but let's cover things in case
* it doesn't.)
*/
if (tupdesc->tdtypeid == RECORDOID && tupdesc->tdtypmod < 0)
assign_record_type_typmod(tupdesc);
scanstate->tupdesc = tupdesc;
ExecSetSlotDescriptor(scanstate->ss.ss_ScanTupleSlot,
tupdesc, false);