Make TupleTableSlots extensible, finish split of existing slot type.

This commit completes the work prepared in 1a0586de36, splitting the
old TupleTableSlot implementation (which could store buffer, heap,
minimal and virtual slots) into four different slot types.  As
described in the aforementioned commit, this is done with the goal of
making tuple table slots extensible, to allow for pluggable table
access methods.

To achieve runtime extensibility for TupleTableSlots, operations on
slots that can differ between types of slots are performed using the
TupleTableSlotOps struct provided at slot creation time.  That
includes information from the size of TupleTableSlot struct to be
allocated, initialization, deforming etc.  See the struct's definition
for more detailed information about callbacks TupleTableSlotOps.

I decided to rename TTSOpsBufferTuple to TTSOpsBufferHeapTuple and
ExecCopySlotTuple to ExecCopySlotHeapTuple, as that seems more
consistent with other naming introduced in recent patches.

There's plenty optimization potential in the slot implementation, but
according to benchmarking the state after this commit has similar
performance characteristics to before this set of changes, which seems
sufficient.

There's a few changes in execReplication.c that currently need to poke
through the slot abstraction, that'll be repaired once the pluggable
storage patchset provides the necessary infrastructure.

Author: Andres Freund and  Ashutosh Bapat, with changes by Amit Khandekar
Discussion: https://postgr.es/m/20181105210039.hh4vvi4vwoq5ba2q@alap3.anarazel.de
This commit is contained in:
Andres Freund 2018-11-16 16:35:11 -08:00
parent 0201d79a55
commit 4da597edf1
27 changed files with 1501 additions and 863 deletions

View File

@ -71,6 +71,8 @@
#define VARLENA_ATT_IS_PACKABLE(att) \
((att)->attstorage != 'p')
static Datum getmissingattr(TupleDesc tupleDesc, int attnum, bool *isnull);
/* ----------------------------------------------------------------
* misc support routines
@ -80,7 +82,7 @@
/*
* Return the missing value of an attribute, or NULL if there isn't one.
*/
Datum
static Datum
getmissingattr(TupleDesc tupleDesc,
int attnum, bool *isnull)
{
@ -1350,186 +1352,6 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
values[attnum] = getmissingattr(tupleDesc, attnum + 1, &isnull[attnum]);
}
/*
* slot_deform_tuple
* Given a TupleTableSlot, extract data from the slot's physical tuple
* into its Datum/isnull arrays. Data is extracted up through the
* natts'th column (caller must ensure this is a legal column number).
*
* This is essentially an incremental version of heap_deform_tuple:
* on each call we extract attributes up to the one needed, without
* re-computing information about previously extracted attributes.
* slot->tts_nvalid is the number of attributes already extracted.
*/
void
slot_deform_tuple(TupleTableSlot *slot, int natts)
{
HeapTuple tuple = slot->tts_tuple;
TupleDesc tupleDesc = slot->tts_tupleDescriptor;
Datum *values = slot->tts_values;
bool *isnull = slot->tts_isnull;
HeapTupleHeader tup = tuple->t_data;
bool hasnulls = HeapTupleHasNulls(tuple);
int attnum;
char *tp; /* ptr to tuple data */
uint32 off; /* offset in tuple data */
bits8 *bp = tup->t_bits; /* ptr to null bitmap in tuple */
bool slow; /* can we use/set attcacheoff? */
/*
* Check whether the first call for this tuple, and initialize or restore
* loop state.
*/
attnum = slot->tts_nvalid;
if (attnum == 0)
{
/* Start from the first attribute */
off = 0;
slow = false;
}
else
{
/* Restore state from previous execution */
off = slot->tts_off;
slow = TTS_SLOW(slot);
}
tp = (char *) tup + tup->t_hoff;
for (; attnum < natts; attnum++)
{
Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
if (hasnulls && att_isnull(attnum, bp))
{
values[attnum] = (Datum) 0;
isnull[attnum] = true;
slow = true; /* can't use attcacheoff anymore */
continue;
}
isnull[attnum] = false;
if (!slow && thisatt->attcacheoff >= 0)
off = thisatt->attcacheoff;
else if (thisatt->attlen == -1)
{
/*
* We can only cache the offset for a varlena attribute if the
* offset is already suitably aligned, so that there would be no
* pad bytes in any case: then the offset will be valid for either
* an aligned or unaligned value.
*/
if (!slow &&
off == att_align_nominal(off, thisatt->attalign))
thisatt->attcacheoff = off;
else
{
off = att_align_pointer(off, thisatt->attalign, -1,
tp + off);
slow = true;
}
}
else
{
/* not varlena, so safe to use att_align_nominal */
off = att_align_nominal(off, thisatt->attalign);
if (!slow)
thisatt->attcacheoff = off;
}
values[attnum] = fetchatt(thisatt, tp + off);
off = att_addlength_pointer(off, thisatt->attlen, tp + off);
if (thisatt->attlen <= 0)
slow = true; /* can't use attcacheoff anymore */
}
/*
* Save state for next execution
*/
slot->tts_nvalid = attnum;
slot->tts_off = off;
if (slow)
slot->tts_flags |= TTS_FLAG_SLOW;
else
slot->tts_flags &= ~TTS_FLAG_SLOW;
}
/*
* slot_attisnull
* Detect whether an attribute of the slot is null, without
* actually fetching it.
*/
bool
slot_attisnull(TupleTableSlot *slot, int attnum)
{
HeapTuple tuple = slot->tts_tuple;
TupleDesc tupleDesc = slot->tts_tupleDescriptor;
/*
* system attributes are handled by heap_attisnull
*/
if (attnum <= 0)
{
if (tuple == NULL) /* internal error */
elog(ERROR, "cannot extract system attribute from virtual tuple");
if (tuple == &(slot->tts_minhdr)) /* internal error */
elog(ERROR, "cannot extract system attribute from minimal tuple");
return heap_attisnull(tuple, attnum, tupleDesc);
}
/*
* fast path if desired attribute already cached
*/
if (attnum <= slot->tts_nvalid)
return slot->tts_isnull[attnum - 1];
/*
* return NULL if attnum is out of range according to the tupdesc
*/
if (attnum > tupleDesc->natts)
return true;
/*
* otherwise we had better have a physical tuple (tts_nvalid should equal
* natts in all virtual-tuple cases)
*/
if (tuple == NULL) /* internal error */
elog(ERROR, "cannot extract attribute from empty tuple slot");
/* and let the tuple tell it */
return heap_attisnull(tuple, attnum, tupleDesc);
}
/*
* slot_getsysattr
* This function fetches a system attribute of the slot's current tuple.
* Unlike slot_getattr, if the slot does not contain system attributes,
* this will return false (with a NULL attribute value) instead of
* throwing an error.
*/
bool
slot_getsysattr(TupleTableSlot *slot, int attnum,
Datum *value, bool *isnull)
{
HeapTuple tuple = slot->tts_tuple;
Assert(attnum < 0); /* else caller error */
if (tuple == NULL ||
tuple == &(slot->tts_minhdr))
{
/* No physical tuple, or minimal tuple, so fail */
*value = (Datum) 0;
*isnull = true;
return false;
}
*value = heap_getsysattr(tuple, attnum, slot->tts_tupleDescriptor, isnull);
return true;
}
/*
* heap_freetuple
*/

View File

@ -2041,7 +2041,9 @@ FormIndexDatum(IndexInfo *indexInfo,
Datum iDatum;
bool isNull;
if (keycol != 0)
if (keycol < 0)
iDatum = slot_getsysattr(slot, keycol, &isNull);
else if (keycol != 0)
{
/*
* Plain index column; get the value we need directly from the

View File

@ -2850,7 +2850,7 @@ CopyFrom(CopyState cstate)
* freed after each batch insert.
*/
oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
tuple = ExecCopySlotTuple(slot);
tuple = ExecCopySlotHeapTuple(slot);
MemoryContextSwitchTo(oldcontext);
}

View File

@ -589,7 +589,7 @@ intorel_receive(TupleTableSlot *slot, DestReceiver *self)
* get the heap tuple out of the tuple table slot, making sure we have a
* writable copy
*/
tuple = ExecCopySlotTuple(slot);
tuple = ExecCopySlotHeapTuple(slot);
/*
* force assignment of new OID (see comments in ExecInsert)

View File

@ -484,7 +484,7 @@ transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
* get the heap tuple out of the tuple table slot, making sure we have a
* writable copy
*/
tuple = ExecCopySlotTuple(slot);
tuple = ExecCopySlotHeapTuple(slot);
heap_insert(myState->transientrel,
tuple,

View File

@ -218,27 +218,25 @@ execCurrentOf(CurrentOfExpr *cexpr,
ItemPointer tuple_tid;
#ifdef USE_ASSERT_CHECKING
if (!slot_getsysattr(scanstate->ss_ScanTupleSlot,
TableOidAttributeNumber,
&ldatum,
&lisnull))
ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot,
TableOidAttributeNumber,
&lisnull);
if (lisnull)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
cursor_name, table_name)));
Assert(!lisnull);
Assert(DatumGetObjectId(ldatum) == table_oid);
#endif
if (!slot_getsysattr(scanstate->ss_ScanTupleSlot,
SelfItemPointerAttributeNumber,
&ldatum,
&lisnull))
ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot,
SelfItemPointerAttributeNumber,
&lisnull);
if (lisnull)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
cursor_name, table_name)));
Assert(!lisnull);
tuple_tid = (ItemPointer) DatumGetPointer(ldatum);
*current_tid = *tuple_tid;

View File

@ -1875,11 +1875,11 @@ CheckOpSlotCompatibility(ExprEvalStep *op, TupleTableSlot *slot)
* Should probably fixed at some point, but for now it's easier to allow
* buffer and heap tuples to be used interchangably.
*/
if (slot->tts_ops == &TTSOpsBufferTuple &&
if (slot->tts_ops == &TTSOpsBufferHeapTuple &&
op->d.fetch.kind == &TTSOpsHeapTuple)
return;
if (slot->tts_ops == &TTSOpsHeapTuple &&
op->d.fetch.kind == &TTSOpsBufferTuple)
op->d.fetch.kind == &TTSOpsBufferHeapTuple)
return;
/*
@ -4025,15 +4025,15 @@ void
ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext,
TupleTableSlot *slot)
{
bool success;
Datum d;
/* slot_getsysattr has sufficient defenses against bad attnums */
success = slot_getsysattr(slot,
op->d.var.attnum,
op->resvalue,
op->resnull);
d = slot_getsysattr(slot,
op->d.var.attnum,
op->resnull);
*op->resvalue = d;
/* this ought to be unreachable, but it's cheap enough to check */
if (unlikely(!success))
if (unlikely(*op->resnull))
elog(ERROR, "failed to fetch attribute from slot");
}

View File

@ -170,8 +170,11 @@ retry:
HeapUpdateFailureData hufd;
HTSU_Result res;
HeapTupleData locktup;
HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)outslot;
ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
/* Only a heap tuple has item pointers. */
Assert(TTS_IS_HEAPTUPLE(outslot) || TTS_IS_BUFFERTUPLE(outslot));
ItemPointerCopy(&hslot->tuple->t_self, &locktup.t_self);
PushActiveSnapshot(GetLatestSnapshot());
@ -334,8 +337,11 @@ retry:
HeapUpdateFailureData hufd;
HTSU_Result res;
HeapTupleData locktup;
HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)outslot;
ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
/* Only a heap tuple has item pointers. */
Assert(TTS_IS_HEAPTUPLE(outslot) || TTS_IS_BUFFERTUPLE(outslot));
ItemPointerCopy(&hslot->tuple->t_self, &locktup.t_self);
PushActiveSnapshot(GetLatestSnapshot());
@ -456,6 +462,12 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
HeapTuple tuple;
ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
Relation rel = resultRelInfo->ri_RelationDesc;
HeapTupleTableSlot *hsearchslot = (HeapTupleTableSlot *)searchslot;
HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)slot;
/* We expect both searchslot and the slot to contain a heap tuple. */
Assert(TTS_IS_HEAPTUPLE(searchslot) || TTS_IS_BUFFERTUPLE(searchslot));
Assert(TTS_IS_HEAPTUPLE(slot) || TTS_IS_BUFFERTUPLE(slot));
/* For now we support only tables. */
Assert(rel->rd_rel->relkind == RELKIND_RELATION);
@ -467,8 +479,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
resultRelInfo->ri_TrigDesc->trig_update_before_row)
{
slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
&searchslot->tts_tuple->t_self,
NULL, slot);
&hsearchslot->tuple->t_self, NULL, slot);
if (slot == NULL) /* "do nothing" */
skip_tuple = true;
@ -488,19 +499,18 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
tuple = ExecFetchSlotHeapTuple(slot, true, NULL);
/* OK, update the tuple and index entries for it */
simple_heap_update(rel, &searchslot->tts_tuple->t_self,
slot->tts_tuple);
simple_heap_update(rel, &hsearchslot->tuple->t_self, hslot->tuple);
if (resultRelInfo->ri_NumIndices > 0 &&
!HeapTupleIsHeapOnly(slot->tts_tuple))
!HeapTupleIsHeapOnly(hslot->tuple))
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
estate, false, NULL,
NIL);
/* AFTER ROW UPDATE Triggers */
ExecARUpdateTriggers(estate, resultRelInfo,
&searchslot->tts_tuple->t_self,
NULL, tuple, recheckIndexes, NULL);
&hsearchslot->tuple->t_self, NULL, tuple,
recheckIndexes, NULL);
list_free(recheckIndexes);
}
@ -519,9 +529,11 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
bool skip_tuple = false;
ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
Relation rel = resultRelInfo->ri_RelationDesc;
HeapTupleTableSlot *hsearchslot = (HeapTupleTableSlot *)searchslot;
/* For now we support only tables. */
/* For now we support only tables and heap tuples. */
Assert(rel->rd_rel->relkind == RELKIND_RELATION);
Assert(TTS_IS_HEAPTUPLE(searchslot) || TTS_IS_BUFFERTUPLE(searchslot));
CheckCmdReplicaIdentity(rel, CMD_DELETE);
@ -530,8 +542,8 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
resultRelInfo->ri_TrigDesc->trig_delete_before_row)
{
skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
&searchslot->tts_tuple->t_self,
NULL, NULL);
&hsearchslot->tuple->t_self, NULL,
NULL);
}
if (!skip_tuple)
@ -539,11 +551,11 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
List *recheckIndexes = NIL;
/* OK, delete the tuple */
simple_heap_delete(rel, &searchslot->tts_tuple->t_self);
simple_heap_delete(rel, &hsearchslot->tuple->t_self);
/* AFTER ROW DELETE Triggers */
ExecARDeleteTriggers(estate, resultRelInfo,
&searchslot->tts_tuple->t_self, NULL, NULL);
&hsearchslot->tuple->t_self, NULL, NULL);
list_free(recheckIndexes);
}

View File

@ -78,8 +78,8 @@ ExecScanFetch(ScanState *node,
return ExecClearTuple(slot);
/* Store test tuple in the plan node's scan slot */
ExecStoreHeapTuple(estate->es_epqTuple[scanrelid - 1],
slot, false);
ExecForceStoreHeapTuple(estate->es_epqTuple[scanrelid - 1],
slot);
/* Check if it meets the access-method conditions */
if (!(*recheckMtd) (node, slot))

File diff suppressed because it is too large Load Diff

View File

@ -1741,7 +1741,7 @@ agg_retrieve_direct(AggState *aggstate)
* Make a copy of the first input tuple; we will use this
* for comparisons (in group mode) and for projection.
*/
aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
aggstate->grp_firstTuple = ExecCopySlotHeapTuple(outerslot);
}
else
{
@ -1800,9 +1800,8 @@ agg_retrieve_direct(AggState *aggstate)
* reserved for it. The tuple will be deleted when it is
* cleared from the slot.
*/
ExecStoreHeapTuple(aggstate->grp_firstTuple,
firstSlot,
true);
ExecForceStoreHeapTuple(aggstate->grp_firstTuple,
firstSlot);
aggstate->grp_firstTuple = NULL; /* don't keep two pointers */
/* set up for first advance_aggregates call */
@ -1858,7 +1857,7 @@ agg_retrieve_direct(AggState *aggstate)
if (!ExecQual(aggstate->phase->eqfunctions[node->numCols - 1],
tmpcontext))
{
aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
aggstate->grp_firstTuple = ExecCopySlotHeapTuple(outerslot);
break;
}
}

View File

@ -914,7 +914,7 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
*/
ExecInitScanTupleSlot(estate, &scanstate->ss,
RelationGetDescr(currentRelation),
&TTSOpsBufferTuple);
&TTSOpsBufferHeapTuple);
/*

View File

@ -931,9 +931,10 @@ ExecParallelHashJoinOuterGetTuple(PlanState *outerNode,
hashvalue);
if (tuple != NULL)
{
slot = ExecStoreMinimalTuple(tuple,
hjstate->hj_OuterTupleSlot,
false);
ExecForceStoreMinimalTuple(tuple,
hjstate->hj_OuterTupleSlot,
false);
slot = hjstate->hj_OuterTupleSlot;
return slot;
}
else
@ -1160,9 +1161,10 @@ ExecParallelHashJoinNewBatch(HashJoinState *hjstate)
while ((tuple = sts_parallel_scan_next(inner_tuples,
&hashvalue)))
{
slot = ExecStoreMinimalTuple(tuple,
hjstate->hj_HashTupleSlot,
false);
ExecForceStoreMinimalTuple(tuple,
hjstate->hj_HashTupleSlot,
false);
slot = hjstate->hj_HashTupleSlot;
ExecParallelHashTableInsertCurrentBatch(hashtable, slot,
hashvalue);
}
@ -1296,7 +1298,8 @@ ExecHashJoinGetSavedTuple(HashJoinState *hjstate,
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not read from hash-join temporary file: %m")));
return ExecStoreMinimalTuple(tuple, tupleSlot, true);
ExecForceStoreMinimalTuple(tuple, tupleSlot, true);
return tupleSlot;
}

View File

@ -950,7 +950,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
*/
ExecInitScanTupleSlot(estate, &indexstate->ss,
RelationGetDescr(currentRelation),
&TTSOpsBufferTuple);
&TTSOpsBufferHeapTuple);
/*
* Initialize result type and projection.

View File

@ -2384,7 +2384,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_existing =
ExecInitExtraTupleSlot(mtstate->ps.state,
mtstate->mt_partition_tuple_routing ?
NULL : relationDesc, &TTSOpsBufferTuple);
NULL : relationDesc, &TTSOpsBufferHeapTuple);
/* carried forward solely for the benefit of explain */
mtstate->mt_excludedtlist = node->exclRelTlist;

View File

@ -147,7 +147,7 @@ ExecInitSampleScan(SampleScan *node, EState *estate, int eflags)
/* and create slot with appropriate rowtype */
ExecInitScanTupleSlot(estate, &scanstate->ss,
RelationGetDescr(scanstate->ss.ss_currentRelation),
&TTSOpsBufferTuple);
&TTSOpsBufferHeapTuple);
/*
* Initialize result type and projection.

View File

@ -173,7 +173,7 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
/* and create slot with the appropriate rowtype */
ExecInitScanTupleSlot(estate, &scanstate->ss,
RelationGetDescr(scanstate->ss.ss_currentRelation),
&TTSOpsBufferTuple);
&TTSOpsBufferHeapTuple);
/*
* Initialize result type and projection.

View File

@ -252,7 +252,7 @@ setop_retrieve_direct(SetOpState *setopstate)
if (!TupIsNull(outerslot))
{
/* Make a copy of the first input tuple */
setopstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
setopstate->grp_firstTuple = ExecCopySlotHeapTuple(outerslot);
}
else
{
@ -303,7 +303,7 @@ setop_retrieve_direct(SetOpState *setopstate)
/*
* Save the first input tuple of the next group.
*/
setopstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
setopstate->grp_firstTuple = ExecCopySlotHeapTuple(outerslot);
break;
}

View File

@ -357,7 +357,7 @@ ExecScanSubPlan(SubPlanState *node,
*/
if (node->curTuple)
heap_freetuple(node->curTuple);
node->curTuple = ExecCopySlotTuple(slot);
node->curTuple = ExecCopySlotHeapTuple(slot);
result = heap_getattr(node->curTuple, 1, tdesc, isNull);
/* keep scanning subplan to make sure there's only one tuple */
@ -1137,7 +1137,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
*/
if (node->curTuple)
heap_freetuple(node->curTuple);
node->curTuple = ExecCopySlotTuple(slot);
node->curTuple = ExecCopySlotHeapTuple(slot);
/*
* Now set all the setParam params from the columns of the tuple

View File

@ -544,7 +544,7 @@ ExecInitTidScan(TidScan *node, EState *estate, int eflags)
*/
ExecInitScanTupleSlot(estate, &tidstate->ss,
RelationGetDescr(currentRelation),
&TTSOpsBufferTuple);
&TTSOpsBufferHeapTuple);
/*
* Initialize result type and projection.

View File

@ -1857,7 +1857,7 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
}
tuptable->vals[tuptable->alloced - tuptable->free] =
ExecCopySlotTuple(slot);
ExecCopySlotHeapTuple(slot);
(tuptable->free)--;
MemoryContextSwitchTo(oldcxt);

View File

@ -65,6 +65,8 @@ LLVMTypeRef StructFormPgAttribute;
LLVMTypeRef StructTupleConstr;
LLVMTypeRef StructtupleDesc;
LLVMTypeRef StructTupleTableSlot;
LLVMTypeRef StructHeapTupleTableSlot;
LLVMTypeRef StructMinimalTupleTableSlot;
LLVMTypeRef StructMemoryContextData;
LLVMTypeRef StructPGFinfoRecord;
LLVMTypeRef StructFmgrInfo;
@ -811,6 +813,8 @@ llvm_create_types(void)
StructFunctionCallInfoData = load_type(mod, "StructFunctionCallInfoData");
StructMemoryContextData = load_type(mod, "StructMemoryContextData");
StructTupleTableSlot = load_type(mod, "StructTupleTableSlot");
StructHeapTupleTableSlot = load_type(mod, "StructHeapTupleTableSlot");
StructMinimalTupleTableSlot = load_type(mod, "StructMinimalTupleTableSlot");
StructHeapTupleData = load_type(mod, "StructHeapTupleData");
StructtupleDesc = load_type(mod, "StructtupleDesc");
StructAggState = load_type(mod, "StructAggState");

View File

@ -93,6 +93,11 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc,
if (ops == &TTSOpsVirtual)
return NULL;
/* decline to JIT for slot types we don't know to handle */
if (ops != &TTSOpsHeapTuple && ops != &TTSOpsBufferHeapTuple &&
ops != &TTSOpsMinimalTuple)
return NULL;
mod = llvm_mutable_module(context);
funcname = llvm_expand_funcname(context, "deform");
@ -171,14 +176,44 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc,
v_tts_nulls =
l_load_struct_gep(b, v_slot, FIELDNO_TUPLETABLESLOT_ISNULL,
"tts_ISNULL");
v_slotoffp = LLVMBuildStructGEP(b, v_slot, FIELDNO_TUPLETABLESLOT_OFF, "");
v_flagsp = LLVMBuildStructGEP(b, v_slot, FIELDNO_TUPLETABLESLOT_FLAGS, "");
v_nvalidp = LLVMBuildStructGEP(b, v_slot, FIELDNO_TUPLETABLESLOT_NVALID, "");
v_tupleheaderp =
l_load_struct_gep(b, v_slot, FIELDNO_TUPLETABLESLOT_TUPLE,
"tupleheader");
if (ops == &TTSOpsHeapTuple || ops == &TTSOpsBufferHeapTuple)
{
LLVMValueRef v_heapslot;
v_heapslot =
LLVMBuildBitCast(b,
v_slot,
l_ptr(StructHeapTupleTableSlot),
"heapslot");
v_slotoffp = LLVMBuildStructGEP(b, v_heapslot, FIELDNO_HEAPTUPLETABLESLOT_OFF, "");
v_tupleheaderp =
l_load_struct_gep(b, v_heapslot, FIELDNO_HEAPTUPLETABLESLOT_TUPLE,
"tupleheader");
}
else if (ops == &TTSOpsMinimalTuple)
{
LLVMValueRef v_minimalslot;
v_minimalslot =
LLVMBuildBitCast(b,
v_slot,
l_ptr(StructMinimalTupleTableSlot),
"minimalslotslot");
v_slotoffp = LLVMBuildStructGEP(b, v_minimalslot, FIELDNO_MINIMALTUPLETABLESLOT_OFF, "");
v_tupleheaderp =
l_load_struct_gep(b, v_minimalslot, FIELDNO_MINIMALTUPLETABLESLOT_TUPLE,
"tupleheader");
}
else
{
/* should've returned at the start of the function */
pg_unreachable();
}
v_tuplep =
l_load_struct_gep(b, v_tupleheaderp, FIELDNO_HEAPTUPLEDATA_DATA,
"tuple");

View File

@ -59,6 +59,8 @@ FunctionCallInfoData StructFunctionCallInfoData;
HeapTupleData StructHeapTupleData;
MemoryContextData StructMemoryContextData;
TupleTableSlot StructTupleTableSlot;
HeapTupleTableSlot StructHeapTupleTableSlot;
MinimalTupleTableSlot StructMinimalTupleTableSlot;
struct tupleDesc StructtupleDesc;

View File

@ -835,7 +835,5 @@ extern MinimalTuple minimal_tuple_from_heap_tuple(HeapTuple htup);
extern size_t varsize_any(void *p);
extern HeapTuple heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
extern MinimalTuple minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
struct TupleTableSlot;
extern void slot_deform_tuple(struct TupleTableSlot *slot, int natts);
#endif /* HTUP_DETAILS_H */

View File

@ -20,11 +20,20 @@
/*----------
* The executor stores tuples in a "tuple table" which is a List of
* independent TupleTableSlots. There are several cases we need to handle:
* 1. physical tuple in a disk buffer page
* 2. physical tuple constructed in palloc'ed memory
* 3. "minimal" physical tuple constructed in palloc'ed memory
* 4. "virtual" tuple consisting of Datum/isnull arrays
* independent TupleTableSlots.
*
* There's various different types of tuple table slots, each being able to
* store different types of tuples. Additional types of slots can be added
* without modifying core code. The type of a slot is determined by the
* TupleTableSlotOps* passed to the slot creation routine. The builtin types
* of slots are
*
* 1. physical tuple in a disk buffer page (TTSOpsBufferHeapTuple)
* 2. physical tuple constructed in palloc'ed memory (TTSOpsHeapTuple)
* 3. "minimal" physical tuple constructed in palloc'ed memory
* (TTSOpsMinimalTuple)
* 4. "virtual" tuple consisting of Datum/isnull arrays (TTSOpsVirtual)
*
*
* The first two cases are similar in that they both deal with "materialized"
* tuples, but resource management is different. For a tuple in a disk page
@ -37,39 +46,28 @@
* parallel to case 1. Note that a minimal tuple has no "system columns".
* (Actually, it could have an OID, but we have no need to access the OID.)
*
* A "virtual" tuple is an optimization used to minimize physical data
* copying in a nest of plan nodes. Any pass-by-reference Datums in the
* tuple point to storage that is not directly associated with the
* TupleTableSlot; generally they will point to part of a tuple stored in
* a lower plan node's output TupleTableSlot, or to a function result
* A "virtual" tuple is an optimization used to minimize physical data copying
* in a nest of plan nodes. Until materialized pass-by-reference Datums in
* the slot point to storage that is not directly associated with the
* TupleTableSlot; generally they will point to part of a tuple stored in a
* lower plan node's output TupleTableSlot, or to a function result
* constructed in a plan node's per-tuple econtext. It is the responsibility
* of the generating plan node to be sure these resources are not released
* for as long as the virtual tuple needs to be valid. We only use virtual
* tuples in the result slots of plan nodes --- tuples to be copied anywhere
* else need to be "materialized" into physical tuples. Note also that a
* virtual tuple does not have any "system columns".
* of the generating plan node to be sure these resources are not released for
* as long as the virtual tuple needs to be valid or is materialized. Note
* also that a virtual tuple does not have any "system columns".
*
* It is also possible for a TupleTableSlot to hold both physical and minimal
* copies of a tuple. This is done when the slot is requested to provide
* the format other than the one it currently holds. (Originally we attempted
* to handle such requests by replacing one format with the other, but that
* had the fatal defect of invalidating any pass-by-reference Datums pointing
* into the existing slot contents.) Both copies must contain identical data
* payloads when this is the case.
* The Datum/isnull arrays of a TupleTableSlot serve double duty. For virtual
* slots they they are the authoritative data. For the other builtin slots,
* the arrays contain data extracted from the tuple. (In this state, any
* pass-by-reference Datums point into the physical tuple.) The extracted
* information is built "lazily", ie, only as needed. This serves to avoid
* repeated extraction of data from the physical tuple.
*
* The Datum/isnull arrays of a TupleTableSlot serve double duty. When the
* slot contains a virtual tuple, they are the authoritative data. When the
* slot contains a physical tuple, the arrays contain data extracted from
* the tuple. (In this state, any pass-by-reference Datums point into
* the physical tuple.) The extracted information is built "lazily",
* ie, only as needed. This serves to avoid repeated extraction of data
* from the physical tuple.
*
* A TupleTableSlot can also be "empty", indicated by flag TTS_EMPTY set in
* tts_flags, holding no valid data. This is the only valid state for a
* freshly-created slot that has not yet had a tuple descriptor assigned to it.
* In this state, TTS_SHOULDFREE should not be set in tts_flag, tts_tuple must
* be NULL, tts_buffer InvalidBuffer, and tts_nvalid zero.
* A TupleTableSlot can also be "empty", indicated by flag TTS_FLAG_EMPTY set
* in tts_flags, holding no valid data. This is the only valid state for a
* freshly-created slot that has not yet had a tuple descriptor assigned to
* it. In this state, TTS_SHOULDFREE should not be set in tts_flag, tts_tuple
* must be NULL, tts_buffer InvalidBuffer, and tts_nvalid zero.
*
* The tupleDescriptor is simply referenced, not copied, by the TupleTableSlot
* code. The caller of ExecSetSlotDescriptor() is responsible for providing
@ -83,32 +81,12 @@
* the slot and should be freed when the slot's reference to the tuple is
* dropped.
*
* If tts_buffer is not InvalidBuffer, then the slot is holding a pin
* on the indicated buffer page; drop the pin when we release the
* slot's reference to that buffer. (tts_shouldFree should always be
* false in such a case, since presumably tts_tuple is pointing at the
* buffer page.)
* tts_values/tts_isnull are allocated either when the slot is created (when
* the descriptor is provided), or when a descriptor is assigned to the slot;
* they are of length equal to the descriptor's natts.
*
* tts_nvalid indicates the number of valid columns in the tts_values/isnull
* arrays. When the slot is holding a "virtual" tuple this must be equal
* to the descriptor's natts. When the slot is holding a physical tuple
* this is equal to the number of columns we have extracted (we always
* extract columns from left to right, so there are no holes).
*
* tts_values/tts_isnull are allocated when a descriptor is assigned to the
* slot; they are of length equal to the descriptor's natts.
*
* tts_mintuple must always be NULL if the slot does not hold a "minimal"
* tuple. When it does, tts_mintuple points to the actual MinimalTupleData
* object (the thing to be pfree'd if tts_shouldFreeMin is true). If the slot
* has only a minimal and not also a regular physical tuple, then tts_tuple
* points at tts_minhdr and the fields of that struct are set correctly
* for access to the minimal tuple; in particular, tts_minhdr.t_data points
* MINIMAL_TUPLE_OFFSET bytes before tts_mintuple. This allows column
* extraction to treat the case identically to regular physical tuples.
*
* TTS_SLOW flag in tts_flags and tts_off are saved state for
* slot_deform_tuple, and should not be touched by any other code.
* The TTS_FLAG_SLOW flag and tts_off are saved state for
* slot_deform_heap_tuple, and should not be touched by any other code.
*----------
*/
@ -116,25 +94,22 @@
#define TTS_FLAG_EMPTY (1 << 1)
#define TTS_EMPTY(slot) (((slot)->tts_flags & TTS_FLAG_EMPTY) != 0)
/* should pfree tts_tuple? */
/* should pfree tuple "owned" by the slot? */
#define TTS_FLAG_SHOULDFREE (1 << 2)
#define TTS_SHOULDFREE(slot) (((slot)->tts_flags & TTS_FLAG_SHOULDFREE) != 0)
/* should pfree tts_mintuple? */
#define TTS_FLAG_SHOULDFREEMIN (1 << 3)
#define TTS_SHOULDFREEMIN(slot) (((slot)->tts_flags & TTS_FLAG_SHOULDFREEMIN) != 0)
/* saved state for slot_deform_tuple */
#define TTS_FLAG_SLOW (1 << 4)
/* saved state for slot_deform_heap_tuple */
#define TTS_FLAG_SLOW (1 << 3)
#define TTS_SLOW(slot) (((slot)->tts_flags & TTS_FLAG_SLOW) != 0)
/* fixed tuple descriptor */
#define TTS_FLAG_FIXED (1 << 5)
#define TTS_FLAG_FIXED (1 << 4)
#define TTS_FIXED(slot) (((slot)->tts_flags & TTS_FLAG_FIXED) != 0)
struct TupleTableSlotOps;
typedef struct TupleTableSlotOps TupleTableSlotOps;
/* base tuple table slot type */
typedef struct TupleTableSlot
{
NodeTag type;
@ -142,28 +117,99 @@ typedef struct TupleTableSlot
uint16 tts_flags; /* Boolean states */
#define FIELDNO_TUPLETABLESLOT_NVALID 2
AttrNumber tts_nvalid; /* # of valid values in tts_values */
#define FIELDNO_TUPLETABLESLOT_TUPLE 3
HeapTuple tts_tuple; /* physical tuple, or NULL if virtual */
const TupleTableSlotOps *const tts_ops; /* implementation of slot */
#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 5
#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 4
TupleDesc tts_tupleDescriptor; /* slot's tuple descriptor */
MemoryContext tts_mcxt; /* slot itself is in this context */
Buffer tts_buffer; /* tuple's buffer, or InvalidBuffer */
#define FIELDNO_TUPLETABLESLOT_OFF 8
uint32 tts_off; /* saved state for slot_deform_tuple */
#define FIELDNO_TUPLETABLESLOT_VALUES 9
#define FIELDNO_TUPLETABLESLOT_VALUES 5
Datum *tts_values; /* current per-attribute values */
#define FIELDNO_TUPLETABLESLOT_ISNULL 10
#define FIELDNO_TUPLETABLESLOT_ISNULL 6
bool *tts_isnull; /* current per-attribute isnull flags */
MinimalTuple tts_mintuple; /* minimal tuple, or NULL if none */
HeapTupleData tts_minhdr; /* workspace for minimal-tuple-only case */
MemoryContext tts_mcxt; /* slot itself is in this context */
} TupleTableSlot;
/* routines for a TupleTableSlot implementation */
struct TupleTableSlotOps
{
/* body will be replaced in later commit */
int dummy;
/* Minimum size of the slot */
size_t base_slot_size;
/* Initialization. */
void (*init)(TupleTableSlot *slot);
/* Destruction. */
void (*release)(TupleTableSlot *slot);
/*
* Clear the contents of the slot. Only the contents are expected to be
* cleared and not the tuple descriptor. Typically an implementation of
* this callback should free the memory allocated for the tuple contained
* in the slot.
*/
void (*clear)(TupleTableSlot *slot);
/*
* Fill up first natts entries of tts_values and tts_isnull arrays with
* values from the tuple contained in the slot. The function may be called
* with natts more than the number of attributes available in the tuple,
* in which case it should set tts_nvalid to the number of returned
* columns.
*/
void (*getsomeattrs)(TupleTableSlot *slot, int natts);
/*
* Returns value of the given system attribute as a datum and sets isnull
* to false, if it's not NULL. Throws an error if the slot type does not
* support system attributes.
*/
Datum (*getsysattr)(TupleTableSlot *slot, int attnum, bool *isnull);
/*
* Make the contents of the slot solely depend on the slot, and not on
* underlying resources (like another memory context, buffers, etc).
*/
void (*materialize)(TupleTableSlot *slot);
/*
* Copy the contents of the source slot into the destination slot's own
* context. Invoked using callback of the destination slot.
*/
void (*copyslot) (TupleTableSlot *dstslot, TupleTableSlot *srcslot);
/*
* Return a heap tuple "owned" by the slot. It is slot's responsibility to
* free the memory consumed by the heap tuple. If the slot can not "own" a
* heap tuple, it should not implement this callback and should set it as
* NULL.
*/
HeapTuple (*get_heap_tuple)(TupleTableSlot *slot);
/*
* Return a minimal tuple "owned" by the slot. It is slot's responsibility
* to free the memory consumed by the minimal tuple. If the slot can not
* "own" a minimal tuple, it should not implement this callback and should
* set it as NULL.
*/
MinimalTuple (*get_minimal_tuple)(TupleTableSlot *slot);
/*
* Return a copy of heap tuple representing the contents of the slot. The
* copy needs to be palloc'd in the current memory context. The slot
* itself is expected to remain unaffected. It is *not* expected to have
* meaningful "system columns" in the copy. The copy is not be "owned" by
* the slot i.e. the caller has to take responsibilty to free memory
* consumed by the slot.
*/
HeapTuple (*copy_heap_tuple)(TupleTableSlot *slot);
/*
* Return a copy of minimal tuple representing the contents of the slot. The
* copy needs to be palloc'd in the current memory context. The slot
* itself is expected to remain unaffected. It is *not* expected to have
* meaningful "system columns" in the copy. The copy is not be "owned" by
* the slot i.e. the caller has to take responsibilty to free memory
* consumed by the slot.
*/
MinimalTuple (*copy_minimal_tuple)(TupleTableSlot *slot);
};
/*
@ -173,10 +219,68 @@ struct TupleTableSlotOps
extern PGDLLIMPORT const TupleTableSlotOps TTSOpsVirtual;
extern PGDLLIMPORT const TupleTableSlotOps TTSOpsHeapTuple;
extern PGDLLIMPORT const TupleTableSlotOps TTSOpsMinimalTuple;
extern PGDLLIMPORT const TupleTableSlotOps TTSOpsBufferTuple;
extern PGDLLIMPORT const TupleTableSlotOps TTSOpsBufferHeapTuple;
#define TTS_HAS_PHYSICAL_TUPLE(slot) \
((slot)->tts_tuple != NULL && (slot)->tts_tuple != &((slot)->tts_minhdr))
#define TTS_IS_VIRTUAL(slot) ((slot)->tts_ops == &TTSOpsVirtual)
#define TTS_IS_HEAPTUPLE(slot) ((slot)->tts_ops == &TTSOpsHeapTuple)
#define TTS_IS_MINIMALTUPLE(slot) ((slot)->tts_ops == &TTSOpsMinimalTuple)
#define TTS_IS_BUFFERTUPLE(slot) ((slot)->tts_ops == &TTSOpsBufferHeapTuple)
/*
* Tuple table slot implementations.
*/
typedef struct VirtualTupleTableSlot
{
TupleTableSlot base;
char *data; /* data for materialized slots */
} VirtualTupleTableSlot;
typedef struct HeapTupleTableSlot
{
TupleTableSlot base;
#define FIELDNO_HEAPTUPLETABLESLOT_TUPLE 1
HeapTuple tuple; /* physical tuple */
#define FIELDNO_HEAPTUPLETABLESLOT_OFF 2
uint32 off; /* saved state for slot_deform_heap_tuple */
} HeapTupleTableSlot;
/* heap tuple residing in a buffer */
typedef struct BufferHeapTupleTableSlot
{
HeapTupleTableSlot base;
/*
* If buffer is not InvalidBuffer, then the slot is holding a pin on the
* indicated buffer page; drop the pin when we release the slot's
* reference to that buffer. (TTS_FLAG_SHOULDFREE should not be set be
* false in such a case, since presumably tts_tuple is pointing at the
* buffer page.)
*/
Buffer buffer; /* tuple's buffer, or InvalidBuffer */
} BufferHeapTupleTableSlot;
typedef struct MinimalTupleTableSlot
{
TupleTableSlot base;
/*
* In a minimal slot tuple points at minhdr and the fields of that struct
* are set correctly for access to the minimal tuple; in particular,
* minhdr.t_data points MINIMAL_TUPLE_OFFSET bytes before mintuple. This
* allows column extraction to treat the case identically to regular
* physical tuples.
*/
#define FIELDNO_MINIMALTUPLETABLESLOT_TUPLE 1
HeapTuple tuple; /* tuple wrapper */
MinimalTuple mintuple; /* minimal tuple, or NULL if none */
HeapTupleData minhdr; /* workspace for minimal-tuple-only case */
#define FIELDNO_MINIMALTUPLETABLESLOT_OFF 4
uint32 off; /* saved state for slot_deform_heap_tuple */
} MinimalTupleTableSlot;
/*
* TupIsNull -- is a TupleTableSlot empty?
@ -197,37 +301,26 @@ extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc);
extern TupleTableSlot *ExecStoreHeapTuple(HeapTuple tuple,
TupleTableSlot *slot,
bool shouldFree);
extern void ExecForceStoreHeapTuple(HeapTuple tuple, TupleTableSlot *slot);
extern TupleTableSlot *ExecStoreBufferHeapTuple(HeapTuple tuple,
TupleTableSlot *slot,
Buffer buffer);
extern TupleTableSlot *ExecStoreMinimalTuple(MinimalTuple mtup,
TupleTableSlot *slot,
bool shouldFree);
extern TupleTableSlot *ExecClearTuple(TupleTableSlot *slot);
extern void ExecForceStoreMinimalTuple(MinimalTuple mtup, TupleTableSlot *slot,
bool shouldFree);
extern TupleTableSlot *ExecStoreVirtualTuple(TupleTableSlot *slot);
extern TupleTableSlot *ExecStoreAllNullTuple(TupleTableSlot *slot);
extern HeapTuple ExecCopySlotTuple(TupleTableSlot *slot);
extern MinimalTuple ExecCopySlotMinimalTuple(TupleTableSlot *slot);
extern HeapTuple ExecFetchSlotHeapTuple(TupleTableSlot *slot, bool materialize, bool *shoulFree);
extern HeapTuple ExecFetchSlotHeapTuple(TupleTableSlot *slot, bool materialize, bool *shouldFree);
extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot,
bool *shouldFree);
extern Datum ExecFetchSlotHeapTupleDatum(TupleTableSlot *slot);
extern void ExecMaterializeSlot(TupleTableSlot *slot);
extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
TupleTableSlot *srcslot);
extern void slot_getmissingattrs(TupleTableSlot *slot, int startAttNum,
int lastAttNum);
extern Datum slot_getattr(TupleTableSlot *slot, int attnum,
bool *isnull);
/* in access/common/heaptuple.c */
extern bool slot_attisnull(TupleTableSlot *slot, int attnum);
extern bool slot_getsysattr(TupleTableSlot *slot, int attnum,
Datum *value, bool *isnull);
extern Datum getmissingattr(TupleDesc tupleDesc,
int attnum, bool *isnull);
extern void slot_getsomeattrs_int(TupleTableSlot *slot, int attnum);
#ifndef FRONTEND
/*
@ -253,6 +346,120 @@ slot_getallattrs(TupleTableSlot *slot)
slot_getsomeattrs(slot, slot->tts_tupleDescriptor->natts);
}
#endif
/*
* slot_attisnull
*
* Detect whether an attribute of the slot is null, without actually fetching
* it.
*/
static inline bool
slot_attisnull(TupleTableSlot *slot, int attnum)
{
AssertArg(attnum > 0);
if (attnum > slot->tts_nvalid)
slot_getsomeattrs(slot, attnum);
return slot->tts_isnull[attnum - 1];
}
/*
* slot_getattr - fetch one attribute of the slot's contents.
*/
static inline Datum
slot_getattr(TupleTableSlot *slot, int attnum,
bool *isnull)
{
AssertArg(attnum > 0);
if (attnum > slot->tts_nvalid)
slot_getsomeattrs(slot, attnum);
*isnull = slot->tts_isnull[attnum - 1];
return slot->tts_values[attnum - 1];
}
/*
* slot_getsysattr - fetch a system attribute of the slot's current tuple.
*
* If the slot type does not contain system attributes, this will throw an
* error. Hence before calling this function, callers should make sure that
* the slot type is the one that supports system attributes.
*/
static inline Datum
slot_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
{
AssertArg(attnum < 0); /* caller error */
/* Fetch the system attribute from the underlying tuple. */
return slot->tts_ops->getsysattr(slot, attnum, isnull);
}
/*
* ExecClearTuple - clear the slot's contents
*/
static inline TupleTableSlot *
ExecClearTuple(TupleTableSlot *slot)
{
slot->tts_ops->clear(slot);
return slot;
}
/* ExecMaterializeSlot - force a slot into the "materialized" state.
*
* This causes the slot's tuple to be a local copy not dependent on any
* external storage (i.e. pointing into a Buffer, or having allocations in
* another memory context).
*
* A typical use for this operation is to prepare a computed tuple for being
* stored on disk. The original data may or may not be virtual, but in any
* case we need a private copy for heap_insert to scribble on.
*/
static inline void
ExecMaterializeSlot(TupleTableSlot *slot)
{
slot->tts_ops->materialize(slot);
}
/*
* ExecCopySlotHeapTuple - return HeapTuple allocated in caller's context
*/
static inline HeapTuple
ExecCopySlotHeapTuple(TupleTableSlot *slot)
{
Assert(!TTS_EMPTY(slot));
return slot->tts_ops->copy_heap_tuple(slot);
}
/*
* ExecCopySlotMinimalTuple - return MinimalTuple allocated in caller's context
*/
static inline MinimalTuple
ExecCopySlotMinimalTuple(TupleTableSlot *slot)
{
return slot->tts_ops->copy_minimal_tuple(slot);
}
/*
* ExecCopySlot - copy one slot's contents into another.
*
* If a source's system attributes are supposed to be accessed in the target
* slot, the target slot and source slot types need to match.
*/
static inline TupleTableSlot *
ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
{
Assert(!TTS_EMPTY(srcslot));
dstslot->tts_ops->copyslot(dstslot, srcslot);
return dstslot;
}
#endif /* FRONTEND */
#endif /* TUPTABLE_H */

View File

@ -65,6 +65,8 @@ extern LLVMTypeRef TypeStorageBool;
extern LLVMTypeRef StructtupleDesc;
extern LLVMTypeRef StructHeapTupleData;
extern LLVMTypeRef StructTupleTableSlot;
extern LLVMTypeRef StructHeapTupleTableSlot;
extern LLVMTypeRef StructMinimalTupleTableSlot;
extern LLVMTypeRef StructMemoryContextData;
extern LLVMTypeRef StructFunctionCallInfoData;
extern LLVMTypeRef StructExprContext;