postgresql/src/backend/executor/execSRF.c

982 lines
28 KiB
C

/*-------------------------------------------------------------------------
*
* execSRF.c
* Routines implementing the API for set-returning functions
*
* This file serves nodeFunctionscan.c and nodeProjectSet.c, providing
* common code for calling set-returning functions according to the
* ReturnSetInfo API.
*
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/executor/execSRF.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/htup_details.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_proc.h"
#include "executor/execdebug.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
#include "pgstat.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/typcache.h"
/* static function decls */
static void init_sexpr(Oid foid, Oid input_collation, Expr *node,
SetExprState *sexpr, PlanState *parent,
MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF);
static void ShutdownSetExpr(Datum arg);
static void ExecEvalFuncArgs(FunctionCallInfo fcinfo,
List *argList, ExprContext *econtext);
static void ExecPrepareTuplestoreResult(SetExprState *sexpr,
ExprContext *econtext,
Tuplestorestate *resultStore,
TupleDesc resultDesc);
static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
/*
* Prepare function call in FROM (ROWS FROM) for execution.
*
* This is used by nodeFunctionscan.c.
*/
SetExprState *
ExecInitTableFunctionResult(Expr *expr,
ExprContext *econtext, PlanState *parent)
{
SetExprState *state = makeNode(SetExprState);
state->funcReturnsSet = false;
state->expr = expr;
state->func.fn_oid = InvalidOid;
/*
* Normally the passed expression tree will be a FuncExpr, since the
* grammar only allows a function call at the top level of a table
* function reference. However, if the function doesn't return set then
* the planner might have replaced the function call via constant-folding
* or inlining. So if we see any other kind of expression node, execute
* it via the general ExecEvalExpr() code. That code path will not
* support set-returning functions buried in the expression, though.
*/
if (IsA(expr, FuncExpr))
{
FuncExpr *func = (FuncExpr *) expr;
state->funcReturnsSet = func->funcretset;
state->args = ExecInitExprList(func->args, parent);
init_sexpr(func->funcid, func->inputcollid, expr, state, parent,
econtext->ecxt_per_query_memory, func->funcretset, false);
}
else
{
state->elidedFuncState = ExecInitExpr(expr, parent);
}
return state;
}
/*
* ExecMakeTableFunctionResult
*
* Evaluate a table function, producing a materialized result in a Tuplestore
* object.
*
* This is used by nodeFunctionscan.c.
*/
Tuplestorestate *
ExecMakeTableFunctionResult(SetExprState *setexpr,
ExprContext *econtext,
MemoryContext argContext,
TupleDesc expectedDesc,
bool randomAccess)
{
Tuplestorestate *tupstore = NULL;
TupleDesc tupdesc = NULL;
Oid funcrettype;
bool returnsTuple;
bool returnsSet = false;
FunctionCallInfo fcinfo;
PgStat_FunctionCallUsage fcusage;
ReturnSetInfo rsinfo;
HeapTupleData tmptup;
MemoryContext callerContext;
bool first_time = true;
/*
* Execute per-tablefunc actions in appropriate context.
*
* The FunctionCallInfo needs to live across all the calls to a
* ValuePerCall function, so it can't be allocated in the per-tuple
* context. Similarly, the function arguments need to be evaluated in a
* context that is longer lived than the per-tuple context: The argument
* values would otherwise disappear when we reset that context in the
* inner loop. As the caller's CurrentMemoryContext is typically a
* query-lifespan context, we don't want to leak memory there. We require
* the caller to pass a separate memory context that can be used for this,
* and can be reset each time through to avoid bloat.
*/
MemoryContextReset(argContext);
callerContext = MemoryContextSwitchTo(argContext);
funcrettype = exprType((Node *) setexpr->expr);
returnsTuple = type_is_rowtype(funcrettype);
/*
* Prepare a resultinfo node for communication. We always do this even if
* not expecting a set result, so that we can pass expectedDesc. In the
* generic-expression case, the expression doesn't actually get to see the
* resultinfo, but set it up anyway because we use some of the fields as
* our own state variables.
*/
rsinfo.type = T_ReturnSetInfo;
rsinfo.econtext = econtext;
rsinfo.expectedDesc = expectedDesc;
rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred);
if (randomAccess)
rsinfo.allowedModes |= (int) SFRM_Materialize_Random;
rsinfo.returnMode = SFRM_ValuePerCall;
/* isDone is filled below */
rsinfo.setResult = NULL;
rsinfo.setDesc = NULL;
fcinfo = palloc(SizeForFunctionCallInfo(list_length(setexpr->args)));
/*
* Normally the passed expression tree will be a SetExprState, since the
* grammar only allows a function call at the top level of a table
* function reference. However, if the function doesn't return set then
* the planner might have replaced the function call via constant-folding
* or inlining. So if we see any other kind of expression node, execute
* it via the general ExecEvalExpr() code; the only difference is that we
* don't get a chance to pass a special ReturnSetInfo to any functions
* buried in the expression.
*/
if (!setexpr->elidedFuncState)
{
/*
* This path is similar to ExecMakeFunctionResultSet.
*/
returnsSet = setexpr->funcReturnsSet;
InitFunctionCallInfoData(*fcinfo, &(setexpr->func),
list_length(setexpr->args),
setexpr->fcinfo->fncollation,
NULL, (Node *) &rsinfo);
/* evaluate the function's argument list */
Assert(CurrentMemoryContext == argContext);
ExecEvalFuncArgs(fcinfo, setexpr->args, econtext);
/*
* If function is strict, and there are any NULL arguments, skip
* calling the function and act like it returned NULL (or an empty
* set, in the returns-set case).
*/
if (setexpr->func.fn_strict)
{
int i;
for (i = 0; i < fcinfo->nargs; i++)
{
if (fcinfo->args[i].isnull)
goto no_function_result;
}
}
}
else
{
/* Treat setexpr as a generic expression */
InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
}
/*
* Switch to short-lived context for calling the function or expression.
*/
MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
/*
* Loop to handle the ValuePerCall protocol (which is also the same
* behavior needed in the generic ExecEvalExpr path).
*/
for (;;)
{
Datum result;
CHECK_FOR_INTERRUPTS();
/*
* Reset per-tuple memory context before each call of the function or
* expression. This cleans up any local memory the function may leak
* when called.
*/
ResetExprContext(econtext);
/* Call the function or expression one time */
if (!setexpr->elidedFuncState)
{
pgstat_init_function_usage(fcinfo, &fcusage);
fcinfo->isnull = false;
rsinfo.isDone = ExprSingleResult;
result = FunctionCallInvoke(fcinfo);
pgstat_end_function_usage(&fcusage,
rsinfo.isDone != ExprMultipleResult);
}
else
{
result =
ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo->isnull);
rsinfo.isDone = ExprSingleResult;
}
/* Which protocol does function want to use? */
if (rsinfo.returnMode == SFRM_ValuePerCall)
{
/*
* Check for end of result set.
*/
if (rsinfo.isDone == ExprEndResult)
break;
/*
* If first time through, build tuplestore for result. For a
* scalar function result type, also make a suitable tupdesc.
*/
if (first_time)
{
MemoryContext oldcontext =
MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
rsinfo.setResult = tupstore;
if (!returnsTuple)
{
tupdesc = CreateTemplateTupleDesc(1);
TupleDescInitEntry(tupdesc,
(AttrNumber) 1,
"column",
funcrettype,
-1,
0);
rsinfo.setDesc = tupdesc;
}
MemoryContextSwitchTo(oldcontext);
}
/*
* Store current resultset item.
*/
if (returnsTuple)
{
if (!fcinfo->isnull)
{
HeapTupleHeader td = DatumGetHeapTupleHeader(result);
if (tupdesc == NULL)
{
MemoryContext oldcontext =
MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
/*
* This is the first non-NULL result from the
* function. Use the type info embedded in the
* rowtype Datum to look up the needed tupdesc. Make
* a copy for the query.
*/
tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
HeapTupleHeaderGetTypMod(td));
rsinfo.setDesc = tupdesc;
MemoryContextSwitchTo(oldcontext);
}
else
{
/*
* Verify all later returned rows have same subtype;
* necessary in case the type is RECORD.
*/
if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid ||
HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("rows returned by function are not all of the same row type")));
}
/*
* tuplestore_puttuple needs a HeapTuple not a bare
* HeapTupleHeader, but it doesn't need all the fields.
*/
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
tmptup.t_data = td;
tuplestore_puttuple(tupstore, &tmptup);
}
else
{
/*
* NULL result from a tuple-returning function; expand it
* to a row of all nulls. We rely on the expectedDesc to
* form such rows. (Note: this would be problematic if
* tuplestore_putvalues saved the tdtypeid/tdtypmod from
* the provided descriptor, since that might not match
* what we get from the function itself. But it doesn't.)
*/
int natts = expectedDesc->natts;
bool *nullflags;
nullflags = (bool *) palloc(natts * sizeof(bool));
memset(nullflags, true, natts * sizeof(bool));
tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
}
}
else
{
/* Scalar-type case: just store the function result */
tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo->isnull);
}
/*
* Are we done?
*/
if (rsinfo.isDone != ExprMultipleResult)
break;
/*
* Check that set-returning functions were properly declared.
* (Note: for historical reasons, we don't complain if a non-SRF
* returns ExprEndResult; that's treated as returning NULL.)
*/
if (!returnsSet)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
errmsg("table-function protocol for value-per-call mode was not followed")));
}
else if (rsinfo.returnMode == SFRM_Materialize)
{
/* check we're on the same page as the function author */
if (!first_time || rsinfo.isDone != ExprSingleResult || !returnsSet)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
errmsg("table-function protocol for materialize mode was not followed")));
/* Done evaluating the set result */
break;
}
else
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
errmsg("unrecognized table-function returnMode: %d",
(int) rsinfo.returnMode)));
first_time = false;
}
no_function_result:
/*
* If we got nothing from the function (ie, an empty-set or NULL result),
* we have to create the tuplestore to return, and if it's a
* non-set-returning function then insert a single all-nulls row. As
* above, we depend on the expectedDesc to manufacture the dummy row.
*/
if (rsinfo.setResult == NULL)
{
MemoryContext oldcontext =
MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
rsinfo.setResult = tupstore;
MemoryContextSwitchTo(oldcontext);
if (!returnsSet)
{
int natts = expectedDesc->natts;
bool *nullflags;
nullflags = (bool *) palloc(natts * sizeof(bool));
memset(nullflags, true, natts * sizeof(bool));
tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
}
}
/*
* If function provided a tupdesc, cross-check it. We only really need to
* do this for functions returning RECORD, but might as well do it always.
*/
if (rsinfo.setDesc)
{
tupledesc_match(expectedDesc, rsinfo.setDesc);
/*
* If it is a dynamically-allocated TupleDesc, free it: it is
* typically allocated in a per-query context, so we must avoid
* leaking it across multiple usages.
*/
if (rsinfo.setDesc->tdrefcount == -1)
FreeTupleDesc(rsinfo.setDesc);
}
MemoryContextSwitchTo(callerContext);
/* All done, pass back the tuplestore */
return rsinfo.setResult;
}
/*
* Prepare targetlist SRF function call for execution.
*
* This is used by nodeProjectSet.c.
*/
SetExprState *
ExecInitFunctionResultSet(Expr *expr,
ExprContext *econtext, PlanState *parent)
{
SetExprState *state = makeNode(SetExprState);
state->funcReturnsSet = true;
state->expr = expr;
state->func.fn_oid = InvalidOid;
/*
* Initialize metadata. The expression node could be either a FuncExpr or
* an OpExpr.
*/
if (IsA(expr, FuncExpr))
{
FuncExpr *func = (FuncExpr *) expr;
state->args = ExecInitExprList(func->args, parent);
init_sexpr(func->funcid, func->inputcollid, expr, state, parent,
econtext->ecxt_per_query_memory, true, true);
}
else if (IsA(expr, OpExpr))
{
OpExpr *op = (OpExpr *) expr;
state->args = ExecInitExprList(op->args, parent);
init_sexpr(op->opfuncid, op->inputcollid, expr, state, parent,
econtext->ecxt_per_query_memory, true, true);
}
else
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(expr));
/* shouldn't get here unless the selected function returns set */
Assert(state->func.fn_retset);
return state;
}
/*
* ExecMakeFunctionResultSet
*
* Evaluate the arguments to a set-returning function and then call the
* function itself. The argument expressions may not contain set-returning
* functions (the planner is supposed to have separated evaluation for those).
*
* This should be called in a short-lived (per-tuple) context, argContext
* needs to live until all rows have been returned (i.e. *isDone set to
* ExprEndResult or ExprSingleResult).
*
* This is used by nodeProjectSet.c.
*/
Datum
ExecMakeFunctionResultSet(SetExprState *fcache,
ExprContext *econtext,
MemoryContext argContext,
bool *isNull,
ExprDoneCond *isDone)
{
List *arguments;
Datum result;
FunctionCallInfo fcinfo;
PgStat_FunctionCallUsage fcusage;
ReturnSetInfo rsinfo;
bool callit;
int i;
restart:
/* Guard against stack overflow due to overly complex expressions */
check_stack_depth();
/*
* If a previous call of the function returned a set result in the form of
* a tuplestore, continue reading rows from the tuplestore until it's
* empty.
*/
if (fcache->funcResultStore)
{
TupleTableSlot *slot = fcache->funcResultSlot;
MemoryContext oldContext;
bool foundTup;
/*
* Have to make sure tuple in slot lives long enough, otherwise
* clearing the slot could end up trying to free something already
* freed.
*/
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
foundTup = tuplestore_gettupleslot(fcache->funcResultStore, true, false,
fcache->funcResultSlot);
MemoryContextSwitchTo(oldContext);
if (foundTup)
{
*isDone = ExprMultipleResult;
if (fcache->funcReturnsTuple)
{
/* We must return the whole tuple as a Datum. */
*isNull = false;
return ExecFetchSlotHeapTupleDatum(fcache->funcResultSlot);
}
else
{
/* Extract the first column and return it as a scalar. */
return slot_getattr(fcache->funcResultSlot, 1, isNull);
}
}
/* Exhausted the tuplestore, so clean up */
tuplestore_end(fcache->funcResultStore);
fcache->funcResultStore = NULL;
*isDone = ExprEndResult;
*isNull = true;
return (Datum) 0;
}
/*
* arguments is a list of expressions to evaluate before passing to the
* function manager. We skip the evaluation if it was already done in the
* previous call (ie, we are continuing the evaluation of a set-valued
* function). Otherwise, collect the current argument values into fcinfo.
*
* The arguments have to live in a context that lives at least until all
* rows from this SRF have been returned, otherwise ValuePerCall SRFs
* would reference freed memory after the first returned row.
*/
fcinfo = fcache->fcinfo;
arguments = fcache->args;
if (!fcache->setArgsValid)
{
MemoryContext oldContext = MemoryContextSwitchTo(argContext);
ExecEvalFuncArgs(fcinfo, arguments, econtext);
MemoryContextSwitchTo(oldContext);
}
else
{
/* Reset flag (we may set it again below) */
fcache->setArgsValid = false;
}
/*
* Now call the function, passing the evaluated parameter values.
*/
/* Prepare a resultinfo node for communication. */
fcinfo->resultinfo = (Node *) &rsinfo;
rsinfo.type = T_ReturnSetInfo;
rsinfo.econtext = econtext;
rsinfo.expectedDesc = fcache->funcResultDesc;
rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
/* note we do not set SFRM_Materialize_Random or _Preferred */
rsinfo.returnMode = SFRM_ValuePerCall;
/* isDone is filled below */
rsinfo.setResult = NULL;
rsinfo.setDesc = NULL;
/*
* If function is strict, and there are any NULL arguments, skip calling
* the function.
*/
callit = true;
if (fcache->func.fn_strict)
{
for (i = 0; i < fcinfo->nargs; i++)
{
if (fcinfo->args[i].isnull)
{
callit = false;
break;
}
}
}
if (callit)
{
pgstat_init_function_usage(fcinfo, &fcusage);
fcinfo->isnull = false;
rsinfo.isDone = ExprSingleResult;
result = FunctionCallInvoke(fcinfo);
*isNull = fcinfo->isnull;
*isDone = rsinfo.isDone;
pgstat_end_function_usage(&fcusage,
rsinfo.isDone != ExprMultipleResult);
}
else
{
/* for a strict SRF, result for NULL is an empty set */
result = (Datum) 0;
*isNull = true;
*isDone = ExprEndResult;
}
/* Which protocol does function want to use? */
if (rsinfo.returnMode == SFRM_ValuePerCall)
{
if (*isDone != ExprEndResult)
{
/*
* Save the current argument values to re-use on the next call.
*/
if (*isDone == ExprMultipleResult)
{
fcache->setArgsValid = true;
/* Register cleanup callback if we didn't already */
if (!fcache->shutdown_reg)
{
RegisterExprContextCallback(econtext,
ShutdownSetExpr,
PointerGetDatum(fcache));
fcache->shutdown_reg = true;
}
}
}
}
else if (rsinfo.returnMode == SFRM_Materialize)
{
/* check we're on the same page as the function author */
if (rsinfo.isDone != ExprSingleResult)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
errmsg("table-function protocol for materialize mode was not followed")));
if (rsinfo.setResult != NULL)
{
/* prepare to return values from the tuplestore */
ExecPrepareTuplestoreResult(fcache, econtext,
rsinfo.setResult,
rsinfo.setDesc);
/* loop back to top to start returning from tuplestore */
goto restart;
}
/* if setResult was left null, treat it as empty set */
*isDone = ExprEndResult;
*isNull = true;
result = (Datum) 0;
}
else
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
errmsg("unrecognized table-function returnMode: %d",
(int) rsinfo.returnMode)));
return result;
}
/*
* init_sexpr - initialize a SetExprState node during first use
*/
static void
init_sexpr(Oid foid, Oid input_collation, Expr *node,
SetExprState *sexpr, PlanState *parent,
MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF)
{
AclResult aclresult;
size_t numargs = list_length(sexpr->args);
/* Check permission to call function */
aclresult = object_aclcheck(ProcedureRelationId, foid, GetUserId(), ACL_EXECUTE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(foid));
InvokeFunctionExecuteHook(foid);
/*
* Safety check on nargs. Under normal circumstances this should never
* fail, as parser should check sooner. But possibly it might fail if
* server has been compiled with FUNC_MAX_ARGS smaller than some functions
* declared in pg_proc?
*/
if (list_length(sexpr->args) > FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg_plural("cannot pass more than %d argument to a function",
"cannot pass more than %d arguments to a function",
FUNC_MAX_ARGS,
FUNC_MAX_ARGS)));
/* Set up the primary fmgr lookup information */
fmgr_info_cxt(foid, &(sexpr->func), sexprCxt);
fmgr_info_set_expr((Node *) sexpr->expr, &(sexpr->func));
/* Initialize the function call parameter struct as well */
sexpr->fcinfo =
(FunctionCallInfo) palloc(SizeForFunctionCallInfo(numargs));
InitFunctionCallInfoData(*sexpr->fcinfo, &(sexpr->func),
numargs,
input_collation, NULL, NULL);
/* If function returns set, check if that's allowed by caller */
if (sexpr->func.fn_retset && !allowSRF)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set"),
parent ? executor_errposition(parent->state,
exprLocation((Node *) node)) : 0));
/* Otherwise, caller should have marked the sexpr correctly */
Assert(sexpr->func.fn_retset == sexpr->funcReturnsSet);
/* If function returns set, prepare expected tuple descriptor */
if (sexpr->func.fn_retset && needDescForSRF)
{
TypeFuncClass functypclass;
Oid funcrettype;
TupleDesc tupdesc;
MemoryContext oldcontext;
functypclass = get_expr_result_type(sexpr->func.fn_expr,
&funcrettype,
&tupdesc);
/* Must save tupdesc in sexpr's context */
oldcontext = MemoryContextSwitchTo(sexprCxt);
if (functypclass == TYPEFUNC_COMPOSITE ||
functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
{
/* Composite data type, e.g. a table's row type */
Assert(tupdesc);
/* Must copy it out of typcache for safety */
sexpr->funcResultDesc = CreateTupleDescCopy(tupdesc);
sexpr->funcReturnsTuple = true;
}
else if (functypclass == TYPEFUNC_SCALAR)
{
/* Base data type, i.e. scalar */
tupdesc = CreateTemplateTupleDesc(1);
TupleDescInitEntry(tupdesc,
(AttrNumber) 1,
NULL,
funcrettype,
-1,
0);
sexpr->funcResultDesc = tupdesc;
sexpr->funcReturnsTuple = false;
}
else if (functypclass == TYPEFUNC_RECORD)
{
/* This will work if function doesn't need an expectedDesc */
sexpr->funcResultDesc = NULL;
sexpr->funcReturnsTuple = true;
}
else
{
/* Else, we will fail if function needs an expectedDesc */
sexpr->funcResultDesc = NULL;
}
MemoryContextSwitchTo(oldcontext);
}
else
sexpr->funcResultDesc = NULL;
/* Initialize additional state */
sexpr->funcResultStore = NULL;
sexpr->funcResultSlot = NULL;
sexpr->shutdown_reg = false;
}
/*
* callback function in case a SetExprState needs to be shut down before it
* has been run to completion
*/
static void
ShutdownSetExpr(Datum arg)
{
SetExprState *sexpr = castNode(SetExprState, DatumGetPointer(arg));
/* If we have a slot, make sure it's let go of any tuplestore pointer */
if (sexpr->funcResultSlot)
ExecClearTuple(sexpr->funcResultSlot);
/* Release any open tuplestore */
if (sexpr->funcResultStore)
tuplestore_end(sexpr->funcResultStore);
sexpr->funcResultStore = NULL;
/* Clear any active set-argument state */
sexpr->setArgsValid = false;
/* execUtils will deregister the callback... */
sexpr->shutdown_reg = false;
}
/*
* Evaluate arguments for a function.
*/
static void
ExecEvalFuncArgs(FunctionCallInfo fcinfo,
List *argList,
ExprContext *econtext)
{
int i;
ListCell *arg;
i = 0;
foreach(arg, argList)
{
ExprState *argstate = (ExprState *) lfirst(arg);
fcinfo->args[i].value = ExecEvalExpr(argstate,
econtext,
&fcinfo->args[i].isnull);
i++;
}
Assert(i == fcinfo->nargs);
}
/*
* ExecPrepareTuplestoreResult
*
* Subroutine for ExecMakeFunctionResultSet: prepare to extract rows from a
* tuplestore function result. We must set up a funcResultSlot (unless
* already done in a previous call cycle) and verify that the function
* returned the expected tuple descriptor.
*/
static void
ExecPrepareTuplestoreResult(SetExprState *sexpr,
ExprContext *econtext,
Tuplestorestate *resultStore,
TupleDesc resultDesc)
{
sexpr->funcResultStore = resultStore;
if (sexpr->funcResultSlot == NULL)
{
/* Create a slot so we can read data out of the tuplestore */
TupleDesc slotDesc;
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(sexpr->func.fn_mcxt);
/*
* If we were not able to determine the result rowtype from context,
* and the function didn't return a tupdesc, we have to fail.
*/
if (sexpr->funcResultDesc)
slotDesc = sexpr->funcResultDesc;
else if (resultDesc)
{
/* don't assume resultDesc is long-lived */
slotDesc = CreateTupleDescCopy(resultDesc);
}
else
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning setof record called in "
"context that cannot accept type record")));
slotDesc = NULL; /* keep compiler quiet */
}
sexpr->funcResultSlot = MakeSingleTupleTableSlot(slotDesc,
&TTSOpsMinimalTuple);
MemoryContextSwitchTo(oldcontext);
}
/*
* If function provided a tupdesc, cross-check it. We only really need to
* do this for functions returning RECORD, but might as well do it always.
*/
if (resultDesc)
{
if (sexpr->funcResultDesc)
tupledesc_match(sexpr->funcResultDesc, resultDesc);
/*
* If it is a dynamically-allocated TupleDesc, free it: it is
* typically allocated in a per-query context, so we must avoid
* leaking it across multiple usages.
*/
if (resultDesc->tdrefcount == -1)
FreeTupleDesc(resultDesc);
}
/* Register cleanup callback if we didn't already */
if (!sexpr->shutdown_reg)
{
RegisterExprContextCallback(econtext,
ShutdownSetExpr,
PointerGetDatum(sexpr));
sexpr->shutdown_reg = true;
}
}
/*
* Check that function result tuple type (src_tupdesc) matches or can
* be considered to match what the query expects (dst_tupdesc). If
* they don't match, ereport.
*
* We really only care about number of attributes and data type.
* Also, we can ignore type mismatch on columns that are dropped in the
* destination type, so long as the physical storage matches. This is
* helpful in some cases involving out-of-date cached plans.
*/
static void
tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
{
int i;
if (dst_tupdesc->natts != src_tupdesc->natts)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("function return row and query-specified return row do not match"),
errdetail_plural("Returned row contains %d attribute, but query expects %d.",
"Returned row contains %d attributes, but query expects %d.",
src_tupdesc->natts,
src_tupdesc->natts, dst_tupdesc->natts)));
for (i = 0; i < dst_tupdesc->natts; i++)
{
Form_pg_attribute dattr = TupleDescAttr(dst_tupdesc, i);
Form_pg_attribute sattr = TupleDescAttr(src_tupdesc, i);
if (IsBinaryCoercible(sattr->atttypid, dattr->atttypid))
continue; /* no worries */
if (!dattr->attisdropped)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("function return row and query-specified return row do not match"),
errdetail("Returned type %s at ordinal position %d, but query expects %s.",
format_type_be(sattr->atttypid),
i + 1,
format_type_be(dattr->atttypid))));
if (dattr->attlen != sattr->attlen ||
dattr->attalign != sattr->attalign)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("function return row and query-specified return row do not match"),
errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
i + 1)));
}
}