postgresql/src/backend/executor/execTuples.c

984 lines
26 KiB
C

/*-------------------------------------------------------------------------
*
* execTuples.c
* Routines dealing with the executor tuple tables. These are used to
* ensure that the executor frees copies of tuples (made by
* ExecTargetList) properly.
*
* Routines dealing with the type information for tuples. Currently,
* the type information for a tuple is an array of FormData_pg_attribute.
* This information is needed by routines manipulating tuples
* (getattribute, formtuple, etc.).
*
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.38 2000/07/12 02:37:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* INTERFACE ROUTINES
*
* TABLE CREATE/DELETE
* ExecCreateTupleTable - create a new tuple table
* ExecDropTupleTable - destroy a table
*
* SLOT RESERVERATION
* ExecAllocTableSlot - find an available slot in the table
*
* SLOT ACCESSORS
* ExecStoreTuple - store a tuple in the table
* ExecFetchTuple - fetch a tuple from the table
* ExecClearTuple - clear contents of a table slot
* ExecSlotPolicy - return slot's tuple pfree policy
* ExecSetSlotPolicy - diddle the slot policy
* ExecSlotDescriptor - type of tuple in a slot
* ExecSetSlotDescriptor - set a slot's tuple descriptor
* ExecSetSlotDescriptorIsNew - diddle the slot-desc-is-new flag
* ExecSetNewSlotDescriptor - set a desc and the is-new-flag all at once
*
* SLOT STATUS PREDICATES
* TupIsNull - true when slot contains no tuple(Macro)
* ExecSlotDescriptorIsNew - true if we're now storing a different
* type of tuple in a slot
*
* CONVENIENCE INITIALIZATION ROUTINES
* ExecInitResultTupleSlot \ convience routines to initialize
* ExecInitScanTupleSlot \ the various tuple slots for nodes
* ExecInitMarkedTupleSlot / which store copies of tuples.
* ExecInitOuterTupleSlot /
* ExecInitHashTupleSlot /
*
* old routines:
* ExecGetTupType - get type of tuple returned by this node
* ExecTypeFromTL - form a TupleDesc from a target list
*
* EXAMPLE OF HOW TABLE ROUTINES WORK
* Suppose we have a query such as retrieve (EMP.name) and we have
* a single SeqScan node in the query plan.
*
* At ExecStart()
* ----------------
* - InitPlan() calls ExecCreateTupleTable() to create the tuple
* table which will hold tuples processed by the executor.
*
* - ExecInitSeqScan() calls ExecInitScanTupleSlot() and
* ExecInitResultTupleSlot() to reserve places in the tuple
* table for the tuples returned by the access methods and the
* tuples resulting from preforming target list projections.
*
* During ExecRun()
* ----------------
* - SeqNext() calls ExecStoreTuple() to place the tuple returned
* by the access methods into the scan tuple slot.
*
* - ExecSeqScan() calls ExecStoreTuple() to take the result
* tuple from ExecProject() and place it into the result tuple slot.
*
* - ExecutePlan() calls ExecRetrieve() which gets the tuple out of
* the slot passed to it by calling ExecFetchTuple(). this tuple
* is then returned.
*
* At ExecEnd()
* ----------------
* - EndPlan() calls ExecDropTupleTable() to clean up any remaining
* tuples left over from executing the query.
*
* The important thing to watch in the executor code is how pointers
* to the slots containing tuples are passed instead of the tuples
* themselves. This facilitates the communication of related information
* (such as whether or not a tuple should be pfreed, what buffer contains
* this tuple, the tuple's tuple descriptor, etc). Note that much of
* this information is also kept in the ExprContext of each node.
* Soon the executor will be redesigned and ExprContext's will contain
* only slot pointers. -cim 3/14/91
*
* NOTES
* The tuple table stuff is relatively new, put here to alleviate
* the process growth problems in the executor. The other routines
* are old (from the original lisp system) and may someday become
* obsolete. -cim 6/23/90
*
* In the implementation of nested-dot queries such as
* "retrieve (EMP.hobbies.all)", a single scan may return tuples
* of many types, so now we return pointers to tuple descriptors
* along with tuples returned via the tuple table. This means
* we now have a bunch of routines to diddle the slot descriptors
* too. -cim 1/18/90
*
* The tuple table stuff depends on the executor/tuptable.h macros,
* and the TupleTableSlot node in execnodes.h.
*
*/
#include "postgres.h"
#include "executor/executor.h"
#undef ExecStoreTuple
#include "catalog/pg_type.h"
#include "access/heapam.h"
static TupleTableSlot *NodeGetResultTupleSlot(Plan *node);
/* ----------------------------------------------------------------
* tuple table create/delete functions
* ----------------------------------------------------------------
*/
/* --------------------------------
* ExecCreateTupleTable
*
* This creates a new tuple table of the specified initial
* size. If the size is insufficient, ExecAllocTableSlot()
* will grow the table as necessary.
*
* This should be used by InitPlan() to allocate the table.
* The table's address will be stored in the EState structure.
* --------------------------------
*/
TupleTable /* return: address of table */
ExecCreateTupleTable(int initialSize) /* initial number of slots in
* table */
{
TupleTable newtable; /* newly allocated table */
TupleTableSlot *array; /* newly allocated slot array */
/* ----------------
* sanity checks
* ----------------
*/
Assert(initialSize >= 1);
/* ----------------
* Now allocate our new table along with space for the pointers
* to the tuples.
*/
newtable = (TupleTable) palloc(sizeof(TupleTableData));
array = (TupleTableSlot *) palloc(initialSize * sizeof(TupleTableSlot));
/* ----------------
* clean out the slots we just allocated
* ----------------
*/
MemSet(array, 0, initialSize * sizeof(TupleTableSlot));
/* ----------------
* initialize the new table and return it to the caller.
* ----------------
*/
newtable->size = initialSize;
newtable->next = 0;
newtable->array = array;
return newtable;
}
/* --------------------------------
* ExecDropTupleTable
*
* This frees the storage used by the tuple table itself
* and optionally frees the contents of the table also.
* It is expected that this routine be called by EndPlan().
* --------------------------------
*/
void
ExecDropTupleTable(TupleTable table, /* tuple table */
bool shouldFree) /* true if we should free slot
* contents */
{
int next; /* next available slot */
TupleTableSlot *array; /* start of table array */
int i; /* counter */
/* ----------------
* sanity checks
* ----------------
*/
Assert(table != NULL);
/* ----------------
* get information from the table
* ----------------
*/
array = table->array;
next = table->next;
/* ----------------
* first free all the valid pointers in the tuple array
* and drop refcounts of any referenced buffers,
* if that's what the caller wants. (There is probably
* no good reason for the caller ever not to want it!)
*
* Note: we do nothing about the Tuple Descriptor's
* we store in the slots. This may have to change (ex: we should
* probably worry about pfreeing tuple descs too) -cim 3/14/91
*
* Right now, the handling of tuple pointers and buffer refcounts
* is clean, but the handling of tuple descriptors is NOT; they
* are copied around with wild abandon. It would take some work
* to make tuple descs pfree'able. Fortunately, since they're
* normally only made once per scan, it's probably not worth
* worrying about... tgl 9/21/99
* ----------------
*/
if (shouldFree)
{
for (i = 0; i < next; i++)
ExecClearTuple(&array[i]);
}
/* ----------------
* finally free the tuple array and the table itself.
* ----------------
*/
pfree(array);
pfree(table);
}
/* ----------------------------------------------------------------
* tuple table slot reservation functions
* ----------------------------------------------------------------
*/
/* --------------------------------
* ExecAllocTableSlot
*
* This routine is used to reserve slots in the table for
* use by the various plan nodes. It is expected to be
* called by the node init routines (ex: ExecInitNestLoop)
* once per slot needed by the node. Not all nodes need
* slots (some just pass tuples around).
* --------------------------------
*/
TupleTableSlot *
ExecAllocTableSlot(TupleTable table)
{
int slotnum; /* new slot number */
TupleTableSlot *slot;
/* ----------------
* sanity checks
* ----------------
*/
Assert(table != NULL);
/* ----------------
* if our table is full we have to allocate a larger
* size table. Since ExecAllocTableSlot() is only called
* before the table is ever used to store tuples, we don't
* have to worry about the contents of the old table.
* If this changes, then we will have to preserve the contents.
* -cim 6/23/90
*
* Unfortunately, we *cannot* do this. All of the nodes in
* the plan that have already initialized their slots will have
* pointers into _freed_ memory. This leads to bad ends. We
* now count the number of slots we will need and create all the
* slots we will need ahead of time. The if below should never
* happen now. Fail if it does. -mer 4 Aug 1992
* ----------------
*/
if (table->next >= table->size)
elog(ERROR, "Plan requires more slots than are available"
"\n\tsend mail to your local executor guru to fix this");
/* ----------------
* at this point, space in the table is guaranteed so we
* reserve the next slot, initialize and return it.
* ----------------
*/
slotnum = table->next;
table->next++;
slot = &(table->array[slotnum]);
/* Make sure the allocated slot is valid (and empty) */
slot->type = T_TupleTableSlot;
slot->val = (HeapTuple) NULL;
slot->ttc_shouldFree = true;
slot->ttc_descIsNew = true;
slot->ttc_tupleDescriptor = (TupleDesc) NULL;
slot->ttc_buffer = InvalidBuffer;
slot->ttc_whichplan = -1;
return slot;
}
/* ----------------------------------------------------------------
* tuple table slot accessor functions
* ----------------------------------------------------------------
*/
/* --------------------------------
* ExecStoreTuple
*
* This function is used to store a tuple into a specified
* slot in the tuple table.
*
* tuple: tuple to store
* slot: slot to store it in
* buffer: disk buffer if tuple is in a disk page, else InvalidBuffer
* shouldFree: true if ExecClearTuple should pfree() the tuple
* when done with it
*
* If 'buffer' is not InvalidBuffer, the tuple table code acquires a pin
* on the buffer which is held until the slot is cleared, so that the tuple
* won't go away on us.
*
* shouldFree is normally set 'true' for tuples constructed on-the-fly.
* It must always be 'false' for tuples that are stored in disk pages,
* since we don't want to try to pfree those.
*
* Another case where it is 'false' is when the referenced tuple is held
* in a tuple table slot belonging to a lower-level executor Proc node.
* In this case the lower-level slot retains ownership and responsibility
* for eventually releasing the tuple. When this method is used, we must
* be certain that the upper-level Proc node will lose interest in the tuple
* sooner than the lower-level one does! If you're not certain, copy the
* lower-level tuple with heap_copytuple and let the upper-level table
* slot assume ownership of the copy!
*
* Return value is just the passed-in slot pointer.
* --------------------------------
*/
TupleTableSlot *
ExecStoreTuple(HeapTuple tuple,
TupleTableSlot *slot,
Buffer buffer,
bool shouldFree)
{
/* ----------------
* sanity checks
* ----------------
*/
Assert(slot != NULL);
/* passing shouldFree=true for a tuple on a disk page is not sane */
Assert(BufferIsValid(buffer) ? (!shouldFree) : true);
/* clear out any old contents of the slot */
ExecClearTuple(slot);
/* ----------------
* store the new tuple into the specified slot and
* return the slot into which we stored the tuple.
* ----------------
*/
slot->val = tuple;
slot->ttc_buffer = buffer;
slot->ttc_shouldFree = shouldFree;
/*
* If tuple is on a disk page, keep the page pinned as long as we hold
* a pointer into it.
*/
if (BufferIsValid(buffer))
IncrBufferRefCount(buffer);
return slot;
}
/* --------------------------------
* ExecClearTuple
*
* This function is used to clear out a slot in the tuple table.
* --------------------------------
*/
TupleTableSlot * /* return: slot passed */
ExecClearTuple(TupleTableSlot *slot) /* slot in which to store tuple */
{
HeapTuple oldtuple; /* prior contents of slot */
/* ----------------
* sanity checks
* ----------------
*/
Assert(slot != NULL);
/* ----------------
* get information from the tuple table
* ----------------
*/
oldtuple = slot->val;
/* ----------------
* free the old contents of the specified slot if necessary.
* ----------------
*/
if (slot->ttc_shouldFree && oldtuple != NULL)
heap_freetuple(oldtuple);
slot->val = (HeapTuple) NULL;
slot->ttc_shouldFree = true; /* probably useless code... */
/* ----------------
* Drop the pin on the referenced buffer, if there is one.
* ----------------
*/
if (BufferIsValid(slot->ttc_buffer))
ReleaseBuffer(slot->ttc_buffer);
slot->ttc_buffer = InvalidBuffer;
return slot;
}
/* --------------------------------
* ExecSlotPolicy
*
* This function is used to get the call/don't call pfree
* setting of a slot. Most executor routines don't need this.
* It's only when you do tricky things like marking tuples for
* merge joins that you need to diddle the slot policy.
* --------------------------------
*/
#ifdef NOT_USED
bool /* return: slot policy */
ExecSlotPolicy(TupleTableSlot *slot) /* slot to inspect */
{
return slot->ttc_shouldFree;
}
/* --------------------------------
* ExecSetSlotPolicy
*
* This function is used to change the call/don't call pfree
* setting of a slot. Most executor routines don't need this.
* It's only when you do tricky things like marking tuples for
* merge joins that you need to diddle the slot policy.
* --------------------------------
*/
bool /* return: old slot policy */
ExecSetSlotPolicy(TupleTableSlot *slot, /* slot to change */
bool shouldFree) /* true if we call pfree() when we
* gc. */
{
bool old_shouldFree = slot->ttc_shouldFree;
slot->ttc_shouldFree = shouldFree;
return old_shouldFree;
}
#endif
/* --------------------------------
* ExecSlotDescriptor
*
* This function is used to get the tuple descriptor associated
* with the slot's tuple.
*
* Now a macro in tuptable.h -mer 5 March 1992
* --------------------------------
*/
/* --------------------------------
* ExecSetSlotDescriptor
*
* This function is used to set the tuple descriptor associated
* with the slot's tuple.
* --------------------------------
*/
TupleDesc /* return: old slot tuple descriptor */
ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */
TupleDesc tupdesc) /* tuple descriptor */
{
TupleDesc old_tupdesc = slot->ttc_tupleDescriptor;
slot->ttc_tupleDescriptor = tupdesc;
return old_tupdesc;
}
/* --------------------------------
* ExecSetSlotDescriptorIsNew
*
* This function is used to change the setting of the "isNew" flag
* --------------------------------
*/
void
ExecSetSlotDescriptorIsNew(TupleTableSlot *slot, /* slot to change */
bool isNew) /* "isNew" setting */
{
slot->ttc_descIsNew = isNew;
}
/* --------------------------------
* ExecSetNewSlotDescriptor
*
* This function is used to set the tuple descriptor associated
* with the slot's tuple, and set the "isNew" flag at the same time.
* --------------------------------
*/
#ifdef NOT_USED
TupleDesc /* return: old slot tuple descriptor */
ExecSetNewSlotDescriptor(TupleTableSlot *slot, /* slot to change */
TupleDesc tupdesc) /* tuple descriptor */
{
TupleDesc old_tupdesc = slot->ttc_tupleDescriptor;
slot->ttc_tupleDescriptor = tupdesc;
slot->ttc_descIsNew = true;
return old_tupdesc;
}
#endif
/* ----------------------------------------------------------------
* tuple table slot status predicates
* ----------------------------------------------------------------
*/
/* --------------------------------
* ExecSlotDescriptorIsNew
*
* This function is used to check if the tuple descriptor
* associated with this slot has just changed. ie: we are
* now storing a new type of tuple in this slot
* --------------------------------
*/
#ifdef NOT_USED
bool /* return: descriptor "is new" */
ExecSlotDescriptorIsNew(TupleTableSlot *slot) /* slot to inspect */
{
/* bool isNew = SlotTupleDescriptorIsNew((TupleTableSlot*) slot);
return isNew; */
return slot->ttc_descIsNew;
}
#endif
/* ----------------------------------------------------------------
* convenience initialization routines
* ----------------------------------------------------------------
*/
/* --------------------------------
* ExecInit{Result,Scan,Raw,Marked,Outer,Hash}TupleSlot
*
* These are convenience routines to initialize the specfied slot
* in nodes inheriting the appropriate state.
* --------------------------------
*/
#define INIT_SLOT_DEFS \
TupleTable tupleTable; \
TupleTableSlot* slot
#define INIT_SLOT_ALLOC \
tupleTable = (TupleTable) estate->es_tupleTable; \
slot = ExecAllocTableSlot(tupleTable);
/* ----------------
* ExecInitResultTupleSlot
* ----------------
*/
void
ExecInitResultTupleSlot(EState *estate, CommonState *commonstate)
{
INIT_SLOT_DEFS;
INIT_SLOT_ALLOC;
commonstate->cs_ResultTupleSlot = (TupleTableSlot *) slot;
}
/* ----------------
* ExecInitScanTupleSlot
* ----------------
*/
void
ExecInitScanTupleSlot(EState *estate, CommonScanState *commonscanstate)
{
INIT_SLOT_DEFS;
INIT_SLOT_ALLOC;
commonscanstate->css_ScanTupleSlot = (TupleTableSlot *) slot;
}
#ifdef NOT_USED
/* ----------------
* ExecInitMarkedTupleSlot
* ----------------
*/
void
ExecInitMarkedTupleSlot(EState *estate, MergeJoinState *mergestate)
{
INIT_SLOT_DEFS;
INIT_SLOT_ALLOC;
mergestate->mj_MarkedTupleSlot = (TupleTableSlot *) slot;
}
#endif
/* ----------------
* ExecInitOuterTupleSlot
* ----------------
*/
void
ExecInitOuterTupleSlot(EState *estate, HashJoinState *hashstate)
{
INIT_SLOT_DEFS;
INIT_SLOT_ALLOC;
hashstate->hj_OuterTupleSlot = slot;
}
/* ----------------
* ExecInitHashTupleSlot
* ----------------
*/
#ifdef NOT_USED
void
ExecInitHashTupleSlot(EState *estate, HashJoinState *hashstate)
{
INIT_SLOT_DEFS;
INIT_SLOT_ALLOC;
hashstate->hj_HashTupleSlot = slot;
}
#endif
static TupleTableSlot *
NodeGetResultTupleSlot(Plan *node)
{
TupleTableSlot *slot;
switch (nodeTag(node))
{
case T_Result:
{
ResultState *resstate = ((Result *) node)->resstate;
slot = resstate->cstate.cs_ResultTupleSlot;
}
break;
case T_SeqScan:
{
CommonScanState *scanstate = ((SeqScan *) node)->scanstate;
slot = scanstate->cstate.cs_ResultTupleSlot;
}
break;
case T_NestLoop:
{
NestLoopState *nlstate = ((NestLoop *) node)->nlstate;
slot = nlstate->jstate.cs_ResultTupleSlot;
}
break;
case T_Append:
{
Append *n = (Append *) node;
AppendState *appendstate;
List *appendplans;
int whichplan;
Plan *subplan;
appendstate = n->appendstate;
appendplans = n->appendplans;
whichplan = appendstate->as_whichplan;
subplan = (Plan *) nth(whichplan, appendplans);
slot = NodeGetResultTupleSlot(subplan);
break;
}
case T_IndexScan:
{
CommonScanState *scanstate = ((IndexScan *) node)->scan.scanstate;
slot = scanstate->cstate.cs_ResultTupleSlot;
}
break;
case T_Material:
{
MaterialState *matstate = ((Material *) node)->matstate;
slot = matstate->csstate.css_ScanTupleSlot;
}
break;
case T_Sort:
{
SortState *sortstate = ((Sort *) node)->sortstate;
slot = sortstate->csstate.css_ScanTupleSlot;
}
break;
case T_Agg:
{
AggState *aggstate = ((Agg *) node)->aggstate;
slot = aggstate->csstate.cstate.cs_ResultTupleSlot;
}
break;
case T_Group:
{
GroupState *grpstate = ((Group *) node)->grpstate;
slot = grpstate->csstate.cstate.cs_ResultTupleSlot;
}
break;
case T_Hash:
{
HashState *hashstate = ((Hash *) node)->hashstate;
slot = hashstate->cstate.cs_ResultTupleSlot;
}
break;
case T_Unique:
{
UniqueState *uniquestate = ((Unique *) node)->uniquestate;
slot = uniquestate->cstate.cs_ResultTupleSlot;
}
break;
case T_MergeJoin:
{
MergeJoinState *mergestate = ((MergeJoin *) node)->mergestate;
slot = mergestate->jstate.cs_ResultTupleSlot;
}
break;
case T_HashJoin:
{
HashJoinState *hashjoinstate = ((HashJoin *) node)->hashjoinstate;
slot = hashjoinstate->jstate.cs_ResultTupleSlot;
}
break;
case T_TidScan:
{
CommonScanState *scanstate = ((IndexScan *) node)->scan.scanstate;
slot = scanstate->cstate.cs_ResultTupleSlot;
}
break;
default:
/* ----------------
* should never get here
* ----------------
*/
elog(ERROR, "NodeGetResultTupleSlot: node not yet supported: %d ",
nodeTag(node));
return NULL;
}
return slot;
}
/* ----------------------------------------------------------------
* ExecGetTupType
*
* this gives you the tuple descriptor for tuples returned
* by this node. I really wish I could ditch this routine,
* but since not all nodes store their type info in the same
* place, we have to do something special for each node type.
*
* Soon, the system will have to adapt to deal with changing
* tuple descriptors as we deal with dynamic tuple types
* being returned from procedure nodes. Perhaps then this
* routine can be retired. -cim 6/3/91
*
* old comments
* This routine just gets the type information out of the
* node's state. If you already have a node's state, you
* can get this information directly, but this is a useful
* routine if you want to get the type information from
* the node's inner or outer subplan easily without having
* to inspect the subplan.. -cim 10/16/89
*
* ----------------------------------------------------------------
*/
TupleDesc
ExecGetTupType(Plan *node)
{
TupleTableSlot *slot;
TupleDesc tupType;
if (node == NULL)
return NULL;
slot = NodeGetResultTupleSlot(node);
tupType = slot->ttc_tupleDescriptor;
return tupType;
}
/*
TupleDesc
ExecCopyTupType(TupleDesc td, int natts)
{
TupleDesc newTd;
int i;
newTd = CreateTemplateTupleDesc(natts);
i = 0;
while (i < natts)
{
newTd[i] = (Form_pg_attribute)palloc(sizeof(FormData_pg_attribute));
memmove(newTd[i], td[i], sizeof(FormData_pg_attribute));
i++;
}
return newTd;
}
*/
/* ----------------------------------------------------------------
* ExecTypeFromTL
*
* Currently there are about 4 different places where we create
* TupleDescriptors. They should all be merged, or perhaps
* be rewritten to call BuildDesc().
*
* old comments
* Forms attribute type info from the target list in the node.
* It assumes all domains are individually specified in the target list.
* It fails if the target list contains something like Emp.all
* which represents all the attributes from EMP relation.
*
* Conditions:
* The inner and outer subtrees should be initialized because it
* might be necessary to know the type infos of the subtrees.
* ----------------------------------------------------------------
*/
TupleDesc
ExecTypeFromTL(List *targetList)
{
List *tlcdr;
TupleDesc typeInfo;
Resdom *resdom;
Oid restype;
int len;
/* ----------------
* examine targetlist - if empty then return NULL
* ----------------
*/
len = ExecTargetListLength(targetList);
if (len == 0)
return NULL;
/* ----------------
* allocate a new typeInfo
* ----------------
*/
typeInfo = CreateTemplateTupleDesc(len);
/* ----------------
* notes: get resdom from (resdom expr)
* get_typbyval comes from src/lib/l-lisp/lsyscache.c
* ----------------
*/
tlcdr = targetList;
while (tlcdr != NIL)
{
TargetEntry *tle = lfirst(tlcdr);
if (tle->resdom != NULL)
{
resdom = tle->resdom;
restype = resdom->restype;
TupleDescInitEntry(typeInfo,
resdom->resno,
resdom->resname,
/* fix for SELECT NULL ... */
(restype ? restype : UNKNOWNOID),
resdom->restypmod,
0,
false);
/*
ExecSetTypeInfo(resdom->resno - 1,
typeInfo,
(Oid) restype,
resdom->resno,
resdom->reslen,
NameStr(*resdom->resname),
get_typbyval(restype),
get_typalign(restype));
*/
}
else
{
Resdom *fjRes;
List *fjTlistP;
List *fjList = lfirst(tlcdr);
#ifdef SETS_FIXED
TargetEntry *tle;
Fjoin *fjNode = ((TargetEntry *) lfirst(fjList))->fjoin;
tle = fjNode->fj_innerNode; /* ??? */
#endif
fjRes = tle->resdom;
restype = fjRes->restype;
TupleDescInitEntry(typeInfo,
fjRes->resno,
fjRes->resname,
restype,
fjRes->restypmod,
0,
false);
/*
ExecSetTypeInfo(fjRes->resno - 1,
typeInfo,
(Oid) restype,
fjRes->resno,
fjRes->reslen,
(char *) fjRes->resname,
get_typbyval(restype),
get_typalign(restype));
*/
foreach(fjTlistP, lnext(fjList))
{
TargetEntry *fjTle = lfirst(fjTlistP);
fjRes = fjTle->resdom;
TupleDescInitEntry(typeInfo,
fjRes->resno,
fjRes->resname,
restype,
fjRes->restypmod,
0,
false);
/*
ExecSetTypeInfo(fjRes->resno - 1,
typeInfo,
(Oid) fjRes->restype,
fjRes->resno,
fjRes->reslen,
(char *) fjRes->resname,
get_typbyval(fjRes->restype),
get_typalign(fjRes->restype));
*/
}
}
tlcdr = lnext(tlcdr);
}
return typeInfo;
}