/* *SpecializedDestReceiverforcollectingqueryoutputinaSQLfunction
*/ typedefstruct
{
DestReceiver pub; /* publicly-known function pointers */
Tuplestorestate *tstore; /* where to put result tuples, or NULL */
JunkFilter *filter; /* filter to convert tuple type */
} DR_sqlfunction;
typedefstruct execution_state
{ struct execution_state *next;
ExecStatus status; bool setsResult; /* true if this query produces func's result */ bool lazyEval; /* true if should fetch one row at a time */
PlannedStmt *stmt; /* plan for this query */
QueryDesc *qd; /* null unless status == RUN */
} execution_state;
char *fname; /* function name (for error msgs) */ char *src; /* function body text (for error msgs) */
SQLFunctionParseInfoPtr pinfo; /* data for parser callback hooks */
int16 *argtyplen; /* lengths of the input argument types */
Oid rettype; /* actual return type */
int16 typlen; /* length of the return type */ bool typbyval; /* true if return type is pass by value */ bool returnsSet; /* true if returning multiple rows */ bool returnsTuple; /* true if returning whole tuple result */ bool readonly_func; /* true to run in "read only" mode */ char prokind; /* prokind from pg_proc row */
TupleDesc rettupdesc; /* result tuple descriptor */
List *source_list; /* RawStmts or Queries read from pg_proc */ int num_queries; /* original length of source_list */ bool raw_source; /* true if source_list contains RawStmts */
List *plansource_list; /* CachedPlanSources for fn's queries */
bool active; /* are we executing this cache entry? */ bool lazyEvalOK; /* true if lazyEval is safe */ bool shutdown_reg; /* true if registered shutdown callback */ bool lazyEval; /* true if using lazyEval for result query */ bool randomAccess; /* true if tstore needs random access */ bool ownSubcontext; /* is subcontext really a separate context? */
ParamListInfo paramLI; /* Param list representing current args */
Tuplestorestate *tstore; /* where we accumulate result for a SRF */
MemoryContext tscontext; /* memory context that tstore should be in */
JunkFilter *junkFilter; /* will be NULL if function returns VOID */ int jf_generation; /* tracks whether junkFilter is up-to-date */
/* *Whileexecutingaparticularquerywithinthefunction,cplanisthe *CachedPlanwe'veobtainedforthatquery,andeslistisachainof *execution_staterecordsfortheindividualplanswithintheCachedPlan. *IfeslistisnotNULLatentrytofmgr_sql,thenweareresuming *executionofalazyEval-modeset-returningfunction. * *next_query_indexisthe0-basedindexofthenextCachedPlanSourceto *getaCachedPlanfrom.
*/
CachedPlan *cplan; /* Plan for current query, if any */
ResourceOwner cowner; /* CachedPlan is registered with this owner */ int next_query_index; /* index of next CachedPlanSource to run */
execution_state *eslist; /* chain of execution_state records */
execution_state *esarray; /* storage for eslist */ int esarray_len; /* allocated length of esarray[] */
/* if positive, this is the 1-based index of the query we're processing */ int error_query_index;
MemoryContext fcontext; /* memory context holding this struct and all
* subsidiary data */
MemoryContext jfcontext; /* subsidiary memory context holding
* junkFilter, result slot, and related data */
MemoryContext subcontext; /* subsidiary memory context for sub-executor */
/* Callback to release our use-count on the SQLFunctionHashEntry */
MemoryContextCallback mcb;
} SQLFunctionCache;
for (argnum = 0; argnum < nargs; argnum++)
{
Oid argtype = argOidVect[argnum];
if (IsPolymorphicType(argtype))
{
argtype = get_call_expr_argtype(call_expr, argnum); if (argtype == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("could not determine actual type of argument declared %s",
format_type_be(argOidVect[argnum]))));
argOidVect[argnum] = argtype;
}
}
pinfo->argtypes = argOidVect;
}
/* *Collectnamesofarguments,too,ifany
*/ if (nargs > 0)
{
Datum proargnames;
Datum proargmodes; int n_arg_names; bool isNull;
proargnames = SysCacheGetAttr(PROCNAMEARGSNSP, procedureTuple,
Anum_pg_proc_proargnames,
&isNull); if (isNull)
proargnames = PointerGetDatum(NULL); /* just to be sure */
proargmodes = SysCacheGetAttr(PROCNAMEARGSNSP, procedureTuple,
Anum_pg_proc_proargmodes,
&isNull); if (isNull)
proargmodes = PointerGetDatum(NULL); /* just to be sure */
if (param)
{ /* Yes, so this is a parameter reference, no subfield */
subfield = NULL;
} else
{ /* No, so try to match as parameter name and subfield */
param = sql_fn_resolve_param_name(pinfo, name1, cref->location);
}
} else
{ /* Single name, or parameter name followed by subfield */
param = sql_fn_resolve_param_name(pinfo, name1, cref->location);
}
/* *Searchforafunctionparameterofthegivenname;ifthereisone, *constructandreturnaParamnodeforit.Ifnot,returnNULL. *Helperfunctionforsql_fn_post_column_ref.
*/ static Node *
sql_fn_resolve_param_name(SQLFunctionParseInfoPtr pinfo, constchar *paramname, int location)
{ int i;
if (pinfo->argnames == NULL) return NULL;
for (i = 0; i < pinfo->nargs; i++)
{ if (pinfo->argnames[i] && strcmp(pinfo->argnames[i], paramname) == 0) return sql_fn_make_param(pinfo, i + 1, location);
}
/* *Precheckallcommandsforvalidityinafunction.Thisshould *generallymatchtherestrictionsspi.capplies.
*/ if (stmt->commandType == CMD_UTILITY)
{ if (IsA(stmt->utilityStmt, CopyStmt) &&
((CopyStmt *) stmt->utilityStmt)->filename == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot COPY to/from client in an SQL function")));
if (IsA(stmt->utilityStmt, TransactionStmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is a SQL statement name */
errmsg("%s is not allowed in an SQL function",
CreateCommandName(stmt->utilityStmt))));
}
if (fcache->func->readonly_func && !CommandIsReadOnly(stmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is a SQL statement name */
errmsg("%s is not allowed in a non-volatile function",
CreateCommandName((Node *) stmt))));
/* OK, build the execution_state for this query */
newes = &fcache->esarray[foreach_current_index(lc)]; if (preves)
preves->next = newes; else
fcache->eslist = newes;
/* Make sure output rowtype is properly blessed */ if (fcache->func->returnsTuple)
BlessTupleDesc(fcache->junkFilter->jf_resultSlot->tts_tupleDescriptor);
/* Mark the JunkFilter as up-to-date */
fcache->jf_generation = fcache->cplan->generation;
/* If we have prosqlbody, pay attention to that not prosrc. */
tmp = SysCacheGetAttr(PROCOID,
procedureTuple,
Anum_pg_proc_prosqlbody,
&isNull); if (!isNull)
{ /* Source queries are already parse-analyzed */
Node *n;
n = stringToNode(TextDatumGetCString(tmp)); if (IsA(n, List))
source_list = linitial_node(List, castNode(List, n)); else
source_list = list_make1(n);
func->raw_source = false;
} else
{ /* Source queries are raw parsetrees */
source_list = pg_parse_query(func->src);
func->raw_source = true;
}
/* *Edgecase:emptyfunctionbodyisOKonlyifitreturnsVOID.Normally *wevalidatethatthelaststatementreturnstherightthingin *check_sql_stmt_retval,butwe'llneverreachthatifthere'snolast *statement.
*/ if (func->num_queries == 0 && rettype != VOIDOID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Function's final statement must be SELECT or INSERT/UPDATE/DELETE/MERGE RETURNING.")));
/* Save the source trees in pcontext for now. */
MemoryContextSwitchTo(pcontext);
func->source_list = copyObject(source_list);
MemoryContextSwitchTo(oldcontext);
returnsTuple = check_sql_stmt_retval(querytree_list,
func->rettype,
func->rettupdesc,
func->prokind, false); if (returnsTuple != func->returnsTuple)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cached plan must not change result type")));
}
}
/* Start up execution of one execution_state node */ staticvoid
postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
{
DestReceiver *dest;
MemoryContext oldcontext = CurrentMemoryContext;
Assert(es->qd == NULL);
/* Caller should have ensured a suitable snapshot is active */
Assert(ActiveSnapshotSet());
/* Switch into the selected subcontext (might be a no-op) */
MemoryContextSwitchTo(fcache->subcontext);
/* *Ifthisqueryproducesthefunctionresult,collectitsoutputusing *ourcustomDestReceiver;elsediscardanyoutput.
*/ if (es->setsResult)
{
DR_sqlfunction *myState;
dest = CreateDestReceiver(DestSQLFunction); /* pass down the needed info to the dest receiver routines */
myState = (DR_sqlfunction *) dest;
Assert(myState->pub.mydest == DestSQLFunction);
myState->tstore = fcache->tstore; /* might be NULL */
myState->filter = fcache->junkFilter;
/* Make very sure the junkfilter's result slot is empty */
ExecClearTuple(fcache->junkFilter->jf_resultSlot);
} else
dest = None_Receiver;
/* Run one execution_state; either to completion or to first result row */ /* Returns true if we ran to completion */ staticbool
postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
{ bool result;
MemoryContext oldcontext;
/* Run the sub-executor in subcontext */
oldcontext = MemoryContextSwitchTo(fcache->subcontext);
if (es->qd->operation == CMD_UTILITY)
{
ProcessUtility(es->qd->plannedstmt,
fcache->func->src, true, /* protect function cache's parsetree */
PROCESS_UTILITY_QUERY,
es->qd->params,
es->qd->queryEnv,
es->qd->dest,
NULL);
result = true; /* never stops early */
} else
{ /* Run regular commands to completion unless lazyEval */
uint64 count = (es->lazyEval) ? 1 : 0;
/* Shut down execution of one execution_state node */ staticvoid
postquel_end(execution_state *es, SQLFunctionCachePtr fcache)
{
MemoryContext oldcontext;
/* Run the sub-executor in subcontext */
oldcontext = MemoryContextSwitchTo(fcache->subcontext);
/* mark status done to ensure we don't do ExecutorEnd twice */
es->status = F_EXEC_DONE;
/* Utility commands don't need Executor. */ if (es->qd->operation != CMD_UTILITY)
{
ExecutorFinish(es->qd);
ExecutorEnd(es->qd);
}
es->qd->dest->rDestroy(es->qd->dest);
FreeQueryDesc(es->qd);
es->qd = NULL;
MemoryContextSwitchTo(oldcontext);
/* Delete the subcontext, if it's actually a separate context */ if (fcache->ownSubcontext)
MemoryContextDelete(fcache->subcontext);
fcache->subcontext = NULL;
}
/* Build ParamListInfo array representing current arguments */ staticvoid
postquel_sub_params(SQLFunctionCachePtr fcache,
FunctionCallInfo fcinfo)
{ int nargs = fcinfo->nargs;
if (nargs > 0)
{
ParamListInfo paramLI;
Oid *argtypes = fcache->func->pinfo->argtypes;
int16 *argtyplen = fcache->func->argtyplen;
if (fcache->paramLI == NULL)
{ /* First time through: build a persistent ParamListInfo struct */
MemoryContext oldcontext;
for (int i = 0; i < nargs; i++)
{
ParamExternData *prm = ¶mLI->params[i];
/* *IfanincomingparametervalueisaR/Wexpandeddatum,we *forceittoR/O.We'dbeperfectlyentitledtoscribbleonit, *buttheproblemisthatiftheparameterisreferencedmore *thanonceinthefunction,earlierreferencesmightmutatethe *valueseenbylaterreferences,whichwon'tdoatall.We *coulddobetterifwecouldbesureofthenumberofParam *nodesinthefunction'splans;butwemightnothaveplanned *allthestatementsyet,nordowehaveplantreewalker *infrastructure.(Examiningtheparsetreesisnotgoodenough, *becauseofpossiblefunctioninliningduringplanning.)
*/
prm->isnull = fcinfo->args[i].isnull;
prm->value = MakeExpandedObjectReadOnly(fcinfo->args[i].value,
prm->isnull,
argtyplen[i]); /* Allow the value to be substituted into custom plans */
prm->pflags = PARAM_FLAG_CONST;
prm->ptype = argtypes[i];
}
} else
fcache->paramLI = NULL;
}
/* *ExtracttheSQLfunction'svaluefromasingleresultrow.Thisisused *bothforscalar(non-set)functionsandforeachrowofalazy-evalset *result.Weexpectthecurrentmemorycontexttobethatofthecaller *offmgr_sql.
*/ static Datum
postquel_get_single_result(TupleTableSlot *slot,
FunctionCallInfo fcinfo,
SQLFunctionCachePtr fcache)
{
Datum value;
/* *Setuptoreturnthefunctionvalue.Forpass-by-referencedatatypes, *besuretocopytheresultintothecurrentcontext.Wecan'tleave *thedataintheTupleTableSlotbecausewemustcleartheslotbefore *returning.
*/ if (fcache->func->returnsTuple)
{ /* We must return the whole tuple as a Datum. */
fcinfo->isnull = false;
value = ExecFetchSlotHeapTupleDatum(slot);
} else
{ /* *Returningascalar,whichwehavetoextractfromthefirstcolumn *oftheSELECTresult,andthencopyintocurrentcontextifneeded.
*/
value = slot_getattr(slot, 1, &(fcinfo->isnull));
if (!fcinfo->isnull)
value = datumCopy(value, fcache->func->typbyval, fcache->func->typlen);
}
/* Clear the slot for next time */
ExecClearTuple(slot);
/* *Findfirstunfinishedexecution_state.Ifnone,advancetothenext *queryinfunction.
*/ do
{
es = fcache->eslist; while (es && es->status == F_EXEC_DONE)
es = es->next; if (es) break;
} while (init_execution_state(fcache));
if (es)
{ /* *Ifwestoppedshortofbeingdone,wemusthavealazy-eval *row.
*/
Assert(es->lazyEval); /* The junkfilter's result slot contains the query result tuple */
Assert(fcache->junkFilter);
slot = fcache->junkFilter->jf_resultSlot;
Assert(!TTS_EMPTY(slot)); /* Extract the result as a datum, and copy out from the slot */
result = postquel_get_single_result(slot, fcinfo, fcache);
/* Deregister shutdown callback, if we made one */ if (fcache->shutdown_reg)
{
UnregisterExprContextCallback(rsi->econtext,
ShutdownSQLFunction,
PointerGetDatum(fcache));
fcache->shutdown_reg = false;
}
} else
{ /* *Wearedonewithanon-lazyevaluation.Returnwhateverisin *thetuplestore.(Itisnowcaller'sresponsibilitytofreethe *tuplestorewhendone.) * *Noteanedgecase:wecouldgetherewithouthavingmadea *tuplestoreifthefunctionisdeclaredtoreturnSETOFVOID. *ExecMakeTableFunctionResultwillcopewithnullsetResult.
*/
Assert(fcache->tstore || fcache->func->rettype == VOIDOID);
rsi->returnMode = SFRM_Materialize;
rsi->setResult = fcache->tstore;
fcache->tstore = NULL; /* must copy desc because execSRF.c will free it */ if (fcache->junkFilter)
rsi->setDesc = CreateTupleDescCopy(fcache->junkFilter->jf_cleanTupType);
fcinfo->isnull = true;
result = (Datum) 0;
/* Deregister shutdown callback, if we made one */ if (fcache->shutdown_reg)
{
UnregisterExprContextCallback(rsi->econtext,
ShutdownSQLFunction,
PointerGetDatum(fcache));
fcache->shutdown_reg = false;
}
}
} else
{ /* *Non-setfunction.Ifwegotarow,returnit;elsereturnNULL.
*/ if (fcache->junkFilter)
{ /* The junkfilter's result slot contains the query result tuple */
slot = fcache->junkFilter->jf_resultSlot; if (!TTS_EMPTY(slot))
result = postquel_get_single_result(slot, fcinfo, fcache); else
{
fcinfo->isnull = true;
result = (Datum) 0;
}
} else
{ /* Should only get here for VOID functions and procedures */
Assert(fcache->func->rettype == VOIDOID);
fcinfo->isnull = true;
result = (Datum) 0;
}
}
/* Pop snapshot if we have pushed one */ if (pushed_snapshot)
PopActiveSnapshot();
es = fcache->eslist; while (es)
{ /* Shut down anything still running */ if (es->status == F_EXEC_RUN)
{ /* Re-establish active snapshot for any called functions */ if (!fcache->func->readonly_func)
PushActiveSnapshot(es->qd->snapshot);
postquel_end(es, fcache);
if (!fcache->func->readonly_func)
PopActiveSnapshot();
}
es = es->next;
}
fcache->eslist = NULL;
/* Release tuplestore if we have one */ if (fcache->tstore)
tuplestore_end(fcache->tstore);
fcache->tstore = NULL;
/* Release CachedPlan if we have one */ if (fcache->cplan)
ReleaseCachedPlan(fcache->cplan, fcache->cowner);
fcache->cplan = NULL;
/* execUtils will deregister the callback... */
fcache->shutdown_reg = false;
}
/* Release reference count on SQLFunctionHashEntry */ if (fcache->func != NULL)
{
Assert(fcache->func->cfunc.use_count > 0);
fcache->func->cfunc.use_count--; /* This should be unnecessary, but let's just be sure: */
fcache->func = NULL;
}
}
if (stmt->outargs != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("calling procedures with output arguments is not supported in SQL functions")));
}
}
}
if (tlistlen != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Final statement must return exactly one column.")));
/* We assume here that non-junk TLEs must come first in tlists */
tle = (TargetEntry *) linitial(tlist);
Assert(!tle->resjunk);
if (!coerce_fn_result_column(tle, rettype, -1,
tlist_is_modifiable,
&upper_tlist,
&upper_tlist_nontrivial))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Actual return type is %s.",
format_type_be(exprType((Node *) tle->expr)))));
} elseif (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
{ /* *Returnsarowtype. * *Notethatwewillnotconsideradomainovercompositetobea *"rowtype"returntype;itgoesthroughthescalarcaseabove.This *isbecauseweonlyprovidecolumn-by-columnimplicitcasting,and *willnotcastthecompleterecordresult.Sotheonlywayto *produceadomain-over-compositeresultistocomputeitasan *explicitsingle-columnresult.Thesingle-composite-columncode *pathjustbelowcouldhandlesuchcases,butitwon'tbereached.
*/ int tupnatts; /* physical number of columns in tuple */ int tuplogcols; /* # of nondeleted columns in tuple */ int colindex; /* physical column index */
/* resjunk columns can simply be ignored */ if (tle->resjunk) continue;
do
{
colindex++; 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 statement returns too many columns.")));
attr = TupleDescAttr(rettupdesc, colindex - 1); if (attr->attisdropped && insertDroppedCols)
{
Expr *null_expr;
/* The type of the null we insert isn't important */
null_expr = (Expr *) makeConst(INT4OID,
-1,
InvalidOid, sizeof(int32),
(Datum) 0, true, /* isnull */ true/* byval */ );
upper_tlist = lappend(upper_tlist,
makeTargetEntry(null_expr,
list_length(upper_tlist) + 1,
NULL, false));
upper_tlist_nontrivial = true;
}
} while (attr->attisdropped);
tuplogcols++;
if (!coerce_fn_result_column(tle,
attr->atttypid, attr->atttypmod,
tlist_is_modifiable,
&upper_tlist,
&upper_tlist_nontrivial))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Final statement returns %s instead of %s at column %d.",
format_type_be(exprType((Node *) tle->expr)),
format_type_be(attr->atttypid),
tuplogcols)));
}
/* remaining columns in rettupdesc had better all be dropped */ for (colindex++; colindex <= tupnatts; colindex++)
{ if (!TupleDescCompactAttr(rettupdesc, colindex - 1)->attisdropped)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Final statement returns too few columns."))); if (insertDroppedCols)
{
Expr *null_expr;
/* The type of the null we insert isn't important */
null_expr = (Expr *) makeConst(INT4OID,
-1,
InvalidOid, sizeof(int32),
(Datum) 0, true, /* isnull */ true/* byval */ );
upper_tlist = lappend(upper_tlist,
makeTargetEntry(null_expr,
list_length(upper_tlist) + 1,
NULL, false));
upper_tlist_nontrivial = true;
}
}
/* Report that we are returning entire tuple result */
is_tuple_result = true;
} else
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type %s is not supported for SQL functions",
format_type_be(rettype))));
tlist_coercion_finished:
/* *Ifnecessary,modifythefinalQuerybyinjectinganextraQuerylevel *thatjustperformsaprojection.(It'dbedubioustodothistoa *non-SELECTquery,butweneverhaveto;RETURNINGlistscanalwaysbe *modifiedin-place.)
*/ if (upper_tlist_nontrivial)
{
Query *newquery;
List *colnames;
RangeTblEntry *rte;
RangeTblRef *rtr;
Assert(parse->commandType == CMD_SELECT);
/* Most of the upper Query struct can be left as zeroes/nulls */
newquery = makeNode(Query);
newquery->commandType = CMD_SELECT;
newquery->querySource = parse->querySource;
newquery->canSetTag = true;
newquery->targetList = upper_tlist;
/* We need a moderately realistic colnames list for the subquery RTE */
colnames = NIL;
foreach(lc, parse->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(lc);
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.122Bemerkung:
(vorverarbeitet am 2026-08-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.