mirror of
https://github.com/postgres/postgres.git
synced 2025-05-15 19:15:29 +03:00
Don't move parenthesized lines to the left, even if that means they flow past the right margin. By default, BSD indent lines up statement continuation lines that are within parentheses so that they start just to the right of the preceding left parenthesis. However, traditionally, if that resulted in the continuation line extending to the right of the desired right margin, then indent would push it left just far enough to not overrun the margin, if it could do so without making the continuation line start to the left of the current statement indent. That makes for a weird mix of indentations unless one has been completely rigid about never violating the 80-column limit. This behavior has been pretty universally panned by Postgres developers. Hence, disable it with indent's new -lpl switch, so that parenthesized lines are always lined up with the preceding left paren. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
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 = dst_tupdesc->attrs[i];
|
|
Form_pg_attribute sattr = src_tupdesc->attrs[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)));
|
|
}
|
|
}
|