postgresql/src/backend/executor/execUtils.c
Tom Lane f9e4f611a1 First pass at set-returning-functions in FROM, by Joe Conway with
some kibitzing from Tom Lane.  Not everything works yet, and there's
no documentation or regression test, but let's commit this so Joe
doesn't need to cope with tracking changes in so many files ...
2002-05-12 20:10:05 +00:00

788 lines
21 KiB
C

/*-------------------------------------------------------------------------
*
* execUtils.c
* miscellaneous executor utility routines
*
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.81 2002/05/12 20:10:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* INTERFACE ROUTINES
* ExecAssignExprContext Common code for plan node init routines.
*
* ExecOpenIndices \
* ExecCloseIndices | referenced by InitPlan, EndPlan,
* ExecInsertIndexTuples / ExecAppend, ExecReplace
*
* RegisterExprContextCallback Register function shutdown callback
* UnregisterExprContextCallback Deregister function shutdown callback
*
* NOTES
* This file has traditionally been the place to stick misc.
* executor support stuff that doesn't really go anyplace else.
*
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/catname.h"
#include "catalog/index.h"
#include "catalog/catalog.h"
#include "catalog/pg_index.h"
#include "executor/execdebug.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/memutils.h"
#include "utils/relcache.h"
#include "utils/syscache.h"
/* ----------------------------------------------------------------
* global counters for number of tuples processed, retrieved,
* appended, replaced, deleted.
* ----------------------------------------------------------------
*/
int NTupleProcessed;
int NTupleRetrieved;
int NTupleReplaced;
int NTupleAppended;
int NTupleDeleted;
int NIndexTupleInserted;
extern int NIndexTupleProcessed; /* have to be defined in the
* access method level so that the
* cinterface.a will link ok. */
static void ShutdownExprContext(ExprContext *econtext);
/* ----------------------------------------------------------------
* statistic functions
* ----------------------------------------------------------------
*/
/* ----------------------------------------------------------------
* ResetTupleCount
* ----------------------------------------------------------------
*/
#ifdef NOT_USED
void
ResetTupleCount(void)
{
NTupleProcessed = 0;
NTupleRetrieved = 0;
NTupleAppended = 0;
NTupleDeleted = 0;
NTupleReplaced = 0;
NIndexTupleProcessed = 0;
}
#endif
/* ----------------------------------------------------------------
* PrintTupleCount
* ----------------------------------------------------------------
*/
#ifdef NOT_USED
void
DisplayTupleCount(FILE *statfp)
{
if (NTupleProcessed > 0)
fprintf(statfp, "!\t%d tuple%s processed, ", NTupleProcessed,
(NTupleProcessed == 1) ? "" : "s");
else
{
fprintf(statfp, "!\tno tuples processed.\n");
return;
}
if (NIndexTupleProcessed > 0)
fprintf(statfp, "%d indextuple%s processed, ", NIndexTupleProcessed,
(NIndexTupleProcessed == 1) ? "" : "s");
if (NIndexTupleInserted > 0)
fprintf(statfp, "%d indextuple%s inserted, ", NIndexTupleInserted,
(NIndexTupleInserted == 1) ? "" : "s");
if (NTupleRetrieved > 0)
fprintf(statfp, "%d tuple%s retrieved. ", NTupleRetrieved,
(NTupleRetrieved == 1) ? "" : "s");
if (NTupleAppended > 0)
fprintf(statfp, "%d tuple%s appended. ", NTupleAppended,
(NTupleAppended == 1) ? "" : "s");
if (NTupleDeleted > 0)
fprintf(statfp, "%d tuple%s deleted. ", NTupleDeleted,
(NTupleDeleted == 1) ? "" : "s");
if (NTupleReplaced > 0)
fprintf(statfp, "%d tuple%s replaced. ", NTupleReplaced,
(NTupleReplaced == 1) ? "" : "s");
fprintf(statfp, "\n");
}
#endif
/* ----------------------------------------------------------------
* miscellaneous node-init support functions
* ----------------------------------------------------------------
*/
/* ----------------
* ExecAssignExprContext
*
* This initializes the ExprContext field. It is only necessary
* to do this for nodes which use ExecQual or ExecProject
* because those routines depend on econtext. Other nodes that
* don't have to evaluate expressions don't need to do this.
*
* Note: we assume CurrentMemoryContext is the correct per-query context.
* This should be true during plan node initialization.
* ----------------
*/
void
ExecAssignExprContext(EState *estate, CommonState *commonstate)
{
ExprContext *econtext = makeNode(ExprContext);
econtext->ecxt_scantuple = NULL;
econtext->ecxt_innertuple = NULL;
econtext->ecxt_outertuple = NULL;
econtext->ecxt_per_query_memory = CurrentMemoryContext;
/*
* Create working memory for expression evaluation in this context.
*/
econtext->ecxt_per_tuple_memory =
AllocSetContextCreate(CurrentMemoryContext,
"PlanExprContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
econtext->ecxt_param_exec_vals = estate->es_param_exec_vals;
econtext->ecxt_param_list_info = estate->es_param_list_info;
econtext->ecxt_aggvalues = NULL;
econtext->ecxt_aggnulls = NULL;
econtext->ecxt_callbacks = NULL;
commonstate->cs_ExprContext = econtext;
}
/* ----------------
* MakeExprContext
*
* Build an expression context for use outside normal plan-node cases.
* A fake scan-tuple slot can be supplied (pass NULL if not needed).
* A memory context sufficiently long-lived to use as fcache context
* must be supplied as well.
* ----------------
*/
ExprContext *
MakeExprContext(TupleTableSlot *slot,
MemoryContext queryContext)
{
ExprContext *econtext = makeNode(ExprContext);
econtext->ecxt_scantuple = slot;
econtext->ecxt_innertuple = NULL;
econtext->ecxt_outertuple = NULL;
econtext->ecxt_per_query_memory = queryContext;
/*
* We make the temporary context a child of current working context,
* not of the specified queryContext. This seems reasonable but I'm
* not totally sure about it...
*
* Expression contexts made via this routine typically don't live long
* enough to get reset, so specify a minsize of 0. That avoids
* alloc'ing any memory in the common case where expr eval doesn't use
* any.
*/
econtext->ecxt_per_tuple_memory =
AllocSetContextCreate(CurrentMemoryContext,
"TempExprContext",
0,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
econtext->ecxt_param_exec_vals = NULL;
econtext->ecxt_param_list_info = NULL;
econtext->ecxt_aggvalues = NULL;
econtext->ecxt_aggnulls = NULL;
econtext->ecxt_callbacks = NULL;
return econtext;
}
/*
* Free an ExprContext made by MakeExprContext, including the temporary
* context used for expression evaluation. Note this will cause any
* pass-by-reference expression result to go away!
*/
void
FreeExprContext(ExprContext *econtext)
{
/* Call any registered callbacks */
ShutdownExprContext(econtext);
/* And clean up the memory used */
MemoryContextDelete(econtext->ecxt_per_tuple_memory);
pfree(econtext);
}
/*
* Build a per-output-tuple ExprContext for an EState.
*
* This is normally invoked via GetPerTupleExprContext() macro.
*/
ExprContext *
MakePerTupleExprContext(EState *estate)
{
if (estate->es_per_tuple_exprcontext == NULL)
{
MemoryContext oldContext;
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
estate->es_per_tuple_exprcontext =
MakeExprContext(NULL, estate->es_query_cxt);
MemoryContextSwitchTo(oldContext);
}
return estate->es_per_tuple_exprcontext;
}
/* ----------------------------------------------------------------
* Result slot tuple type and ProjectionInfo support
* ----------------------------------------------------------------
*/
/* ----------------
* ExecAssignResultType
* ----------------
*/
void
ExecAssignResultType(CommonState *commonstate,
TupleDesc tupDesc, bool shouldFree)
{
TupleTableSlot *slot = commonstate->cs_ResultTupleSlot;
ExecSetSlotDescriptor(slot, tupDesc, shouldFree);
}
/* ----------------
* ExecAssignResultTypeFromOuterPlan
* ----------------
*/
void
ExecAssignResultTypeFromOuterPlan(Plan *node, CommonState *commonstate)
{
Plan *outerPlan;
TupleDesc tupDesc;
outerPlan = outerPlan(node);
tupDesc = ExecGetTupType(outerPlan);
ExecAssignResultType(commonstate, tupDesc, false);
}
/* ----------------
* ExecAssignResultTypeFromTL
* ----------------
*/
void
ExecAssignResultTypeFromTL(Plan *node, CommonState *commonstate)
{
TupleDesc tupDesc;
tupDesc = ExecTypeFromTL(node->targetlist);
ExecAssignResultType(commonstate, tupDesc, true);
}
/* ----------------
* ExecGetResultType
* ----------------
*/
TupleDesc
ExecGetResultType(CommonState *commonstate)
{
TupleTableSlot *slot = commonstate->cs_ResultTupleSlot;
return slot->ttc_tupleDescriptor;
}
/* ----------------
* ExecAssignProjectionInfo
forms the projection information from the node's targetlist
* ----------------
*/
void
ExecAssignProjectionInfo(Plan *node, CommonState *commonstate)
{
ProjectionInfo *projInfo;
List *targetList;
int len;
targetList = node->targetlist;
len = ExecTargetListLength(targetList);
projInfo = makeNode(ProjectionInfo);
projInfo->pi_targetlist = targetList;
projInfo->pi_len = len;
projInfo->pi_tupValue = (len <= 0) ? NULL : (Datum *) palloc(sizeof(Datum) * len);
projInfo->pi_exprContext = commonstate->cs_ExprContext;
projInfo->pi_slot = commonstate->cs_ResultTupleSlot;
commonstate->cs_ProjInfo = projInfo;
}
/* ----------------
* ExecFreeProjectionInfo
* ----------------
*/
void
ExecFreeProjectionInfo(CommonState *commonstate)
{
ProjectionInfo *projInfo;
/*
* get projection info. if NULL then this node has none so we just
* return.
*/
projInfo = commonstate->cs_ProjInfo;
if (projInfo == NULL)
return;
/*
* clean up memory used.
*/
if (projInfo->pi_tupValue != NULL)
pfree(projInfo->pi_tupValue);
pfree(projInfo);
commonstate->cs_ProjInfo = NULL;
}
/* ----------------
* ExecFreeExprContext
* ----------------
*/
void
ExecFreeExprContext(CommonState *commonstate)
{
ExprContext *econtext;
/*
* get expression context. if NULL then this node has none so we just
* return.
*/
econtext = commonstate->cs_ExprContext;
if (econtext == NULL)
return;
/*
* clean up any registered callbacks
*/
ShutdownExprContext(econtext);
/*
* clean up memory used.
*/
MemoryContextDelete(econtext->ecxt_per_tuple_memory);
pfree(econtext);
commonstate->cs_ExprContext = NULL;
}
/* ----------------------------------------------------------------
* the following scan type support functions are for
* those nodes which are stubborn and return tuples in
* their Scan tuple slot instead of their Result tuple
* slot.. luck fur us, these nodes do not do projections
* so we don't have to worry about getting the ProjectionInfo
* right for them... -cim 6/3/91
* ----------------------------------------------------------------
*/
/* ----------------
* ExecGetScanType
* ----------------
*/
TupleDesc
ExecGetScanType(CommonScanState *csstate)
{
TupleTableSlot *slot = csstate->css_ScanTupleSlot;
return slot->ttc_tupleDescriptor;
}
/* ----------------
* ExecAssignScanType
* ----------------
*/
void
ExecAssignScanType(CommonScanState *csstate,
TupleDesc tupDesc, bool shouldFree)
{
TupleTableSlot *slot = csstate->css_ScanTupleSlot;
ExecSetSlotDescriptor(slot, tupDesc, shouldFree);
}
/* ----------------
* ExecAssignScanTypeFromOuterPlan
* ----------------
*/
void
ExecAssignScanTypeFromOuterPlan(Plan *node, CommonScanState *csstate)
{
Plan *outerPlan;
TupleDesc tupDesc;
outerPlan = outerPlan(node);
tupDesc = ExecGetTupType(outerPlan);
ExecAssignScanType(csstate, tupDesc, false);
}
/* ----------------------------------------------------------------
* ExecInsertIndexTuples support
* ----------------------------------------------------------------
*/
/* ----------------------------------------------------------------
* ExecOpenIndices
*
* Find the indices associated with a result relation, open them,
* and save information about them in the result ResultRelInfo.
*
* At entry, caller has already opened and locked
* resultRelInfo->ri_RelationDesc.
*
* This used to be horribly ugly code, and slow too because it
* did a sequential scan of pg_index. Now we rely on the relcache
* to cache a list of the OIDs of the indices associated with any
* specific relation, and we use the pg_index syscache to get the
* entries we need from pg_index.
* ----------------------------------------------------------------
*/
void
ExecOpenIndices(ResultRelInfo *resultRelInfo)
{
Relation resultRelation = resultRelInfo->ri_RelationDesc;
List *indexoidlist,
*indexoidscan;
int len,
i;
RelationPtr relationDescs;
IndexInfo **indexInfoArray;
resultRelInfo->ri_NumIndices = 0;
/* checks for disabled indexes */
if (!RelationGetForm(resultRelation)->relhasindex)
return;
if (IsIgnoringSystemIndexes() &&
IsSystemRelation(resultRelation))
return;
/*
* Get cached list of index OIDs
*/
indexoidlist = RelationGetIndexList(resultRelation);
len = length(indexoidlist);
if (len == 0)
return;
/*
* allocate space for result arrays
*/
relationDescs = (RelationPtr) palloc(len * sizeof(Relation));
indexInfoArray = (IndexInfo **) palloc(len * sizeof(IndexInfo *));
resultRelInfo->ri_NumIndices = len;
resultRelInfo->ri_IndexRelationDescs = relationDescs;
resultRelInfo->ri_IndexRelationInfo = indexInfoArray;
/*
* For each index, open the index relation and save pg_index info.
*/
i = 0;
foreach(indexoidscan, indexoidlist)
{
Oid indexOid = lfirsti(indexoidscan);
Relation indexDesc;
IndexInfo *ii;
/*
* Open (and lock, if necessary) the index relation
*
* If the index AM is not safe for concurrent updates, obtain an
* exclusive lock on the index to lock out other updaters as well
* as readers (index_beginscan places AccessShareLock). We will
* release this lock in ExecCloseIndices.
*
* If the index AM supports concurrent updates, we obtain no lock
* here at all, which is a tad weird, but safe since any critical
* operation on the index (like deleting it) will acquire
* exclusive lock on the parent table. Perhaps someday we should
* acquire RowExclusiveLock on the index here?
*
* If there are multiple not-concurrent-safe indexes, all backends
* must lock the indexes in the same order or we will get
* deadlocks here during concurrent updates. This is guaranteed
* by RelationGetIndexList(), which promises to return the index
* list in OID order.
*/
indexDesc = index_open(indexOid);
if (!indexDesc->rd_am->amconcurrent)
LockRelation(indexDesc, AccessExclusiveLock);
/*
* extract index key information from the index's pg_index tuple
*/
ii = BuildIndexInfo(indexDesc->rd_index);
relationDescs[i] = indexDesc;
indexInfoArray[i] = ii;
i++;
}
freeList(indexoidlist);
}
/* ----------------------------------------------------------------
* ExecCloseIndices
*
* Close the index relations stored in resultRelInfo
* ----------------------------------------------------------------
*/
void
ExecCloseIndices(ResultRelInfo *resultRelInfo)
{
int i;
int numIndices;
RelationPtr indexDescs;
numIndices = resultRelInfo->ri_NumIndices;
indexDescs = resultRelInfo->ri_IndexRelationDescs;
for (i = 0; i < numIndices; i++)
{
if (indexDescs[i] == NULL)
continue;
/* Drop lock, if one was acquired by ExecOpenIndices */
if (!indexDescs[i]->rd_am->amconcurrent)
UnlockRelation(indexDescs[i], AccessExclusiveLock);
index_close(indexDescs[i]);
}
/*
* XXX should free indexInfo array here too.
*/
}
/* ----------------------------------------------------------------
* ExecInsertIndexTuples
*
* This routine takes care of inserting index tuples
* into all the relations indexing the result relation
* when a heap tuple is inserted into the result relation.
* Much of this code should be moved into the genam
* stuff as it only exists here because the genam stuff
* doesn't provide the functionality needed by the
* executor.. -cim 9/27/89
* ----------------------------------------------------------------
*/
void
ExecInsertIndexTuples(TupleTableSlot *slot,
ItemPointer tupleid,
EState *estate,
bool is_update)
{
HeapTuple heapTuple;
ResultRelInfo *resultRelInfo;
int i;
int numIndices;
RelationPtr relationDescs;
Relation heapRelation;
TupleDesc heapDescriptor;
IndexInfo **indexInfoArray;
ExprContext *econtext;
Datum datum[INDEX_MAX_KEYS];
char nullv[INDEX_MAX_KEYS];
heapTuple = slot->val;
/*
* Get information from the result relation info structure.
*/
resultRelInfo = estate->es_result_relation_info;
numIndices = resultRelInfo->ri_NumIndices;
relationDescs = resultRelInfo->ri_IndexRelationDescs;
indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
heapRelation = resultRelInfo->ri_RelationDesc;
heapDescriptor = RelationGetDescr(heapRelation);
/*
* We will use the EState's per-tuple context for evaluating
* predicates and functional-index functions (creating it if it's not
* already there).
*/
econtext = GetPerTupleExprContext(estate);
/* Arrange for econtext's scan tuple to be the tuple under test */
econtext->ecxt_scantuple = slot;
/*
* for each index, form and insert the index tuple
*/
for (i = 0; i < numIndices; i++)
{
IndexInfo *indexInfo;
List *predicate;
InsertIndexResult result;
if (relationDescs[i] == NULL)
continue;
indexInfo = indexInfoArray[i];
predicate = indexInfo->ii_Predicate;
if (predicate != NIL)
{
/* Skip this index-update if the predicate isn't satisfied */
if (!ExecQual(predicate, econtext, false))
continue;
}
/*
* FormIndexDatum fills in its datum and null parameters with
* attribute information taken from the given heap tuple.
*/
FormIndexDatum(indexInfo,
heapTuple,
heapDescriptor,
econtext->ecxt_per_tuple_memory,
datum,
nullv);
result = index_insert(relationDescs[i], /* index relation */
datum, /* array of heaptuple Datums */
nullv, /* info on nulls */
&(heapTuple->t_self), /* tid of heap tuple */
heapRelation);
/*
* keep track of index inserts for debugging
*/
IncrIndexInserted();
if (result)
pfree(result);
}
}
void
SetChangedParamList(Plan *node, List *newchg)
{
List *nl;
foreach(nl, newchg)
{
int paramId = lfirsti(nl);
/* if this node doesn't depend on a param ... */
if (!intMember(paramId, node->extParam) &&
!intMember(paramId, node->locParam))
continue;
/* if this param is already in list of changed ones ... */
if (intMember(paramId, node->chgParam))
continue;
/* else - add this param to the list */
node->chgParam = lappendi(node->chgParam, paramId);
}
}
/*
* Register a shutdown callback in an ExprContext.
*
* Shutdown callbacks will be called (in reverse order of registration)
* when the ExprContext is deleted or rescanned. This provides a hook
* for functions called in the context to do any cleanup needed --- it's
* particularly useful for functions returning sets. Note that the
* callback will *not* be called in the event that execution is aborted
* by an error.
*/
void
RegisterExprContextCallback(ExprContext *econtext,
ExprContextCallbackFunction function,
Datum arg)
{
ExprContext_CB *ecxt_callback;
/* Save the info in appropriate memory context */
ecxt_callback = (ExprContext_CB *)
MemoryContextAlloc(econtext->ecxt_per_query_memory,
sizeof(ExprContext_CB));
ecxt_callback->function = function;
ecxt_callback->arg = arg;
/* link to front of list for appropriate execution order */
ecxt_callback->next = econtext->ecxt_callbacks;
econtext->ecxt_callbacks = ecxt_callback;
}
/*
* Deregister a shutdown callback in an ExprContext.
*
* Any list entries matching the function and arg will be removed.
* This can be used if it's no longer necessary to call the callback.
*/
void
UnregisterExprContextCallback(ExprContext *econtext,
ExprContextCallbackFunction function,
Datum arg)
{
ExprContext_CB **prev_callback;
ExprContext_CB *ecxt_callback;
prev_callback = &econtext->ecxt_callbacks;
while ((ecxt_callback = *prev_callback) != NULL)
{
if (ecxt_callback->function == function && ecxt_callback->arg == arg)
{
*prev_callback = ecxt_callback->next;
pfree(ecxt_callback);
}
else
{
prev_callback = &ecxt_callback->next;
}
}
}
/*
* Call all the shutdown callbacks registered in an ExprContext.
*
* The callback list is emptied (important in case this is only a rescan
* reset, and not deletion of the ExprContext).
*/
static void
ShutdownExprContext(ExprContext *econtext)
{
ExprContext_CB *ecxt_callback;
/*
* Call each callback function in reverse registration order.
*/
while ((ecxt_callback = econtext->ecxt_callbacks) != NULL)
{
econtext->ecxt_callbacks = ecxt_callback->next;
(*ecxt_callback->function) (ecxt_callback->arg);
pfree(ecxt_callback);
}
}