
This is a mechanical change in preparation for a later commit that will change the layout of TupleDesc. Introducing a macro to abstract the details of where attributes are stored will allow us to change that in separate step and revise it in future. Author: Thomas Munro, editorialized by Andres Freund Reviewed-By: Andres Freund Discussion: https://postgr.es/m/CAEepm=0ZtQ-SpsgCyzzYpsXS6e=kZWqk3g5Ygn3MDV7A8dabUA@mail.gmail.com
929 lines
26 KiB
C
929 lines
26 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-2017, 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 "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;
|
|
FunctionCallInfoData fcinfo;
|
|
PgStat_FunctionCallUsage fcusage;
|
|
ReturnSetInfo rsinfo;
|
|
HeapTupleData tmptup;
|
|
MemoryContext callerContext;
|
|
MemoryContext oldcontext;
|
|
bool first_time = true;
|
|
|
|
callerContext = CurrentMemoryContext;
|
|
|
|
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;
|
|
|
|
/*
|
|
* 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_data.fncollation,
|
|
NULL, (Node *) &rsinfo);
|
|
|
|
/*
|
|
* Evaluate the function's argument list.
|
|
*
|
|
* We can't do this in the per-tuple context: the argument values
|
|
* would disappear when we reset that context in the inner loop. And
|
|
* the caller's CurrentMemoryContext is typically a query-lifespan
|
|
* context, so 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);
|
|
oldcontext = MemoryContextSwitchTo(argContext);
|
|
ExecEvalFuncArgs(&fcinfo, setexpr->args, econtext);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
/*
|
|
* 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.argnull[i])
|
|
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)
|
|
{
|
|
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
|
|
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
|
|
rsinfo.setResult = tupstore;
|
|
if (!returnsTuple)
|
|
{
|
|
tupdesc = CreateTemplateTupleDesc(1, false);
|
|
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)
|
|
{
|
|
/*
|
|
* 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.
|
|
*/
|
|
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
|
|
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;
|
|
}
|
|
else if (rsinfo.returnMode == SFRM_Materialize)
|
|
{
|
|
/* check we're on the same page as the function author */
|
|
if (!first_time || rsinfo.isDone != ExprSingleResult)
|
|
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)
|
|
{
|
|
MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
|
|
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
|
|
rsinfo.setResult = tupstore;
|
|
if (!returnsSet)
|
|
{
|
|
int natts = expectedDesc->natts;
|
|
bool *nullflags;
|
|
|
|
MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
|
|
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 is used by nodeProjectSet.c.
|
|
*/
|
|
Datum
|
|
ExecMakeFunctionResultSet(SetExprState *fcache,
|
|
ExprContext *econtext,
|
|
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)
|
|
{
|
|
if (tuplestore_gettupleslot(fcache->funcResultStore, true, false,
|
|
fcache->funcResultSlot))
|
|
{
|
|
*isDone = ExprMultipleResult;
|
|
if (fcache->funcReturnsTuple)
|
|
{
|
|
/* We must return the whole tuple as a Datum. */
|
|
*isNull = false;
|
|
return ExecFetchSlotTupleDatum(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.
|
|
*/
|
|
fcinfo = &fcache->fcinfo_data;
|
|
arguments = fcache->args;
|
|
if (!fcache->setArgsValid)
|
|
ExecEvalFuncArgs(fcinfo, arguments, econtext);
|
|
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->argnull[i])
|
|
{
|
|
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;
|
|
|
|
/* Check permission to call function */
|
|
aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_PROC, 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 */
|
|
InitFunctionCallInfoData(sexpr->fcinfo_data, &(sexpr->func),
|
|
list_length(sexpr->args),
|
|
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)
|
|
{
|
|
/* 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, false);
|
|
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->arg[i] = ExecEvalExpr(argstate,
|
|
econtext,
|
|
&fcinfo->argnull[i]);
|
|
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);
|
|
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)));
|
|
}
|
|
}
|