diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
commit | 46651ce6fe013220ed397add242004d764fc0153 (patch) | |
tree | 6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/executor/execTuples.c | |
parent | Initial commit. (diff) | |
download | postgresql-14-upstream.tar.xz postgresql-14-upstream.zip |
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/executor/execTuples.c')
-rw-r--r-- | src/backend/executor/execTuples.c | 2339 |
1 files changed, 2339 insertions, 0 deletions
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c new file mode 100644 index 0000000..5004b3b --- /dev/null +++ b/src/backend/executor/execTuples.c @@ -0,0 +1,2339 @@ +/*------------------------------------------------------------------------- + * + * execTuples.c + * Routines dealing with TupleTableSlots. These are used for resource + * management associated with tuples (eg, releasing buffer pins for + * tuples in disk buffers, or freeing the memory occupied by transient + * tuples). Slots also provide access abstraction that lets us implement + * "virtual" tuples to reduce data-copying overhead. + * + * 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.). + * + * + * EXAMPLE OF HOW TABLE ROUTINES WORK + * Suppose we have a query such as SELECT emp.name FROM emp and we have + * a single SeqScan node in the query plan. + * + * At ExecutorStart() + * ---------------- + * + * - ExecInitSeqScan() calls ExecInitScanTupleSlot() to construct a + * TupleTableSlots for the tuples returned by the access method, and + * ExecInitResultTypeTL() to define the node's return + * type. ExecAssignScanProjectionInfo() will, if necessary, create + * another TupleTableSlot for the tuples resulting from performing + * target list projections. + * + * During ExecutorRun() + * ---------------- + * - SeqNext() calls ExecStoreBufferHeapTuple() to place the tuple + * returned by the access method into the scan tuple slot. + * + * - ExecSeqScan() (via ExecScan), if necessary, calls ExecProject(), + * putting the result of the projection in the result tuple slot. If + * not necessary, it directly returns the slot returned by SeqNext(). + * + * - ExecutePlan() calls the output function. + * + * 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). It also allows us + * to avoid physically constructing projection tuples in many cases. + * + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/executor/execTuples.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/heaptoast.h" +#include "access/htup_details.h" +#include "access/tupdesc_details.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "nodes/nodeFuncs.h" +#include "storage/bufmgr.h" +#include "utils/builtins.h" +#include "utils/expandeddatum.h" +#include "utils/lsyscache.h" +#include "utils/typcache.h" + +static TupleDesc ExecTypeFromTLInternal(List *targetList, + bool skipjunk); +static pg_attribute_always_inline void slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, + int natts); +static inline void tts_buffer_heap_store_tuple(TupleTableSlot *slot, + HeapTuple tuple, + Buffer buffer, + bool transfer_pin); +static void tts_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, bool shouldFree); + + +const TupleTableSlotOps TTSOpsVirtual; +const TupleTableSlotOps TTSOpsHeapTuple; +const TupleTableSlotOps TTSOpsMinimalTuple; +const TupleTableSlotOps TTSOpsBufferHeapTuple; + + +/* + * TupleTableSlotOps implementations. + */ + +/* + * TupleTableSlotOps implementation for VirtualTupleTableSlot. + */ +static void +tts_virtual_init(TupleTableSlot *slot) +{ +} + +static void +tts_virtual_release(TupleTableSlot *slot) +{ +} + +static void +tts_virtual_clear(TupleTableSlot *slot) +{ + if (unlikely(TTS_SHOULDFREE(slot))) + { + VirtualTupleTableSlot *vslot = (VirtualTupleTableSlot *) slot; + + pfree(vslot->data); + vslot->data = NULL; + + slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; + } + + slot->tts_nvalid = 0; + slot->tts_flags |= TTS_FLAG_EMPTY; + ItemPointerSetInvalid(&slot->tts_tid); +} + +/* + * VirtualTupleTableSlots always have fully populated tts_values and + * tts_isnull arrays. So this function should never be called. + */ +static void +tts_virtual_getsomeattrs(TupleTableSlot *slot, int natts) +{ + elog(ERROR, "getsomeattrs is not required to be called on a virtual tuple table slot"); +} + +/* + * VirtualTupleTableSlots never provide system attributes (except those + * handled generically, such as tableoid). We generally shouldn't get + * here, but provide a user-friendly message if we do. + */ +static Datum +tts_virtual_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) +{ + Assert(!TTS_EMPTY(slot)); + + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot retrieve a system column in this context"))); + + return 0; /* silence compiler warnings */ +} + +/* + * To materialize a virtual slot all the datums that aren't passed by value + * have to be copied into the slot's memory context. To do so, compute the + * required size, and allocate enough memory to store all attributes. That's + * good for cache hit ratio, but more importantly requires only memory + * allocation/deallocation. + */ +static void +tts_virtual_materialize(TupleTableSlot *slot) +{ + VirtualTupleTableSlot *vslot = (VirtualTupleTableSlot *) slot; + TupleDesc desc = slot->tts_tupleDescriptor; + Size sz = 0; + char *data; + + /* already materialized */ + if (TTS_SHOULDFREE(slot)) + return; + + /* compute size of memory required */ + for (int natt = 0; natt < desc->natts; natt++) + { + Form_pg_attribute att = TupleDescAttr(desc, natt); + Datum val; + + if (att->attbyval || slot->tts_isnull[natt]) + continue; + + val = slot->tts_values[natt]; + + if (att->attlen == -1 && + VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val))) + { + /* + * We want to flatten the expanded value so that the materialized + * slot doesn't depend on it. + */ + sz = att_align_nominal(sz, att->attalign); + sz += EOH_get_flat_size(DatumGetEOHP(val)); + } + else + { + sz = att_align_nominal(sz, att->attalign); + sz = att_addlength_datum(sz, att->attlen, val); + } + } + + /* all data is byval */ + if (sz == 0) + return; + + /* allocate memory */ + vslot->data = data = MemoryContextAlloc(slot->tts_mcxt, sz); + slot->tts_flags |= TTS_FLAG_SHOULDFREE; + + /* and copy all attributes into the pre-allocated space */ + for (int natt = 0; natt < desc->natts; natt++) + { + Form_pg_attribute att = TupleDescAttr(desc, natt); + Datum val; + + if (att->attbyval || slot->tts_isnull[natt]) + continue; + + val = slot->tts_values[natt]; + + if (att->attlen == -1 && + VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val))) + { + Size data_length; + + /* + * We want to flatten the expanded value so that the materialized + * slot doesn't depend on it. + */ + ExpandedObjectHeader *eoh = DatumGetEOHP(val); + + data = (char *) att_align_nominal(data, + att->attalign); + data_length = EOH_get_flat_size(eoh); + EOH_flatten_into(eoh, data, data_length); + + slot->tts_values[natt] = PointerGetDatum(data); + data += data_length; + } + else + { + Size data_length = 0; + + data = (char *) att_align_nominal(data, att->attalign); + data_length = att_addlength_datum(data_length, att->attlen, val); + + memcpy(data, DatumGetPointer(val), data_length); + + slot->tts_values[natt] = PointerGetDatum(data); + data += data_length; + } + } +} + +static void +tts_virtual_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot) +{ + TupleDesc srcdesc = srcslot->tts_tupleDescriptor; + + Assert(srcdesc->natts <= dstslot->tts_tupleDescriptor->natts); + + tts_virtual_clear(dstslot); + + slot_getallattrs(srcslot); + + for (int natt = 0; natt < srcdesc->natts; natt++) + { + dstslot->tts_values[natt] = srcslot->tts_values[natt]; + dstslot->tts_isnull[natt] = srcslot->tts_isnull[natt]; + } + + dstslot->tts_nvalid = srcdesc->natts; + dstslot->tts_flags &= ~TTS_FLAG_EMPTY; + + /* make sure storage doesn't depend on external memory */ + tts_virtual_materialize(dstslot); +} + +static HeapTuple +tts_virtual_copy_heap_tuple(TupleTableSlot *slot) +{ + Assert(!TTS_EMPTY(slot)); + + return heap_form_tuple(slot->tts_tupleDescriptor, + slot->tts_values, + slot->tts_isnull); +} + +static MinimalTuple +tts_virtual_copy_minimal_tuple(TupleTableSlot *slot) +{ + Assert(!TTS_EMPTY(slot)); + + return heap_form_minimal_tuple(slot->tts_tupleDescriptor, + slot->tts_values, + slot->tts_isnull); +} + + +/* + * TupleTableSlotOps implementation for HeapTupleTableSlot. + */ + +static void +tts_heap_init(TupleTableSlot *slot) +{ +} + +static void +tts_heap_release(TupleTableSlot *slot) +{ +} + +static void +tts_heap_clear(TupleTableSlot *slot) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + /* Free the memory for the heap tuple if it's allowed. */ + if (TTS_SHOULDFREE(slot)) + { + heap_freetuple(hslot->tuple); + slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; + } + + slot->tts_nvalid = 0; + slot->tts_flags |= TTS_FLAG_EMPTY; + ItemPointerSetInvalid(&slot->tts_tid); + hslot->off = 0; + hslot->tuple = NULL; +} + +static void +tts_heap_getsomeattrs(TupleTableSlot *slot, int natts) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + slot_deform_heap_tuple(slot, hslot->tuple, &hslot->off, natts); +} + +static Datum +tts_heap_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + /* + * In some code paths it's possible to get here with a non-materialized + * slot, in which case we can't retrieve system columns. + */ + if (!hslot->tuple) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot retrieve a system column in this context"))); + + return heap_getsysattr(hslot->tuple, attnum, + slot->tts_tupleDescriptor, isnull); +} + +static void +tts_heap_materialize(TupleTableSlot *slot) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + MemoryContext oldContext; + + Assert(!TTS_EMPTY(slot)); + + /* If slot has its tuple already materialized, nothing to do. */ + if (TTS_SHOULDFREE(slot)) + return; + + oldContext = MemoryContextSwitchTo(slot->tts_mcxt); + + /* + * Have to deform from scratch, otherwise tts_values[] entries could point + * into the non-materialized tuple (which might be gone when accessed). + */ + slot->tts_nvalid = 0; + hslot->off = 0; + + if (!hslot->tuple) + hslot->tuple = heap_form_tuple(slot->tts_tupleDescriptor, + slot->tts_values, + slot->tts_isnull); + else + { + /* + * The tuple contained in this slot is not allocated in the memory + * context of the given slot (else it would have TTS_SHOULDFREE set). + * Copy the tuple into the given slot's memory context. + */ + hslot->tuple = heap_copytuple(hslot->tuple); + } + + slot->tts_flags |= TTS_FLAG_SHOULDFREE; + + MemoryContextSwitchTo(oldContext); +} + +static void +tts_heap_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot) +{ + HeapTuple tuple; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(dstslot->tts_mcxt); + tuple = ExecCopySlotHeapTuple(srcslot); + MemoryContextSwitchTo(oldcontext); + + ExecStoreHeapTuple(tuple, dstslot, true); +} + +static HeapTuple +tts_heap_get_heap_tuple(TupleTableSlot *slot) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + if (!hslot->tuple) + tts_heap_materialize(slot); + + return hslot->tuple; +} + +static HeapTuple +tts_heap_copy_heap_tuple(TupleTableSlot *slot) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + if (!hslot->tuple) + tts_heap_materialize(slot); + + return heap_copytuple(hslot->tuple); +} + +static MinimalTuple +tts_heap_copy_minimal_tuple(TupleTableSlot *slot) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + if (!hslot->tuple) + tts_heap_materialize(slot); + + return minimal_tuple_from_heap_tuple(hslot->tuple); +} + +static void +tts_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, bool shouldFree) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + tts_heap_clear(slot); + + slot->tts_nvalid = 0; + hslot->tuple = tuple; + hslot->off = 0; + slot->tts_flags &= ~(TTS_FLAG_EMPTY | TTS_FLAG_SHOULDFREE); + slot->tts_tid = tuple->t_self; + + if (shouldFree) + slot->tts_flags |= TTS_FLAG_SHOULDFREE; +} + + +/* + * TupleTableSlotOps implementation for MinimalTupleTableSlot. + */ + +static void +tts_minimal_init(TupleTableSlot *slot) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + /* + * Initialize the heap tuple pointer to access attributes of the minimal + * tuple contained in the slot as if its a heap tuple. + */ + mslot->tuple = &mslot->minhdr; +} + +static void +tts_minimal_release(TupleTableSlot *slot) +{ +} + +static void +tts_minimal_clear(TupleTableSlot *slot) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + if (TTS_SHOULDFREE(slot)) + { + heap_free_minimal_tuple(mslot->mintuple); + slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; + } + + slot->tts_nvalid = 0; + slot->tts_flags |= TTS_FLAG_EMPTY; + ItemPointerSetInvalid(&slot->tts_tid); + mslot->off = 0; + mslot->mintuple = NULL; +} + +static void +tts_minimal_getsomeattrs(TupleTableSlot *slot, int natts) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + slot_deform_heap_tuple(slot, mslot->tuple, &mslot->off, natts); +} + +static Datum +tts_minimal_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) +{ + Assert(!TTS_EMPTY(slot)); + + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot retrieve a system column in this context"))); + + return 0; /* silence compiler warnings */ +} + +static void +tts_minimal_materialize(TupleTableSlot *slot) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + MemoryContext oldContext; + + Assert(!TTS_EMPTY(slot)); + + /* If slot has its tuple already materialized, nothing to do. */ + if (TTS_SHOULDFREE(slot)) + return; + + oldContext = MemoryContextSwitchTo(slot->tts_mcxt); + + /* + * Have to deform from scratch, otherwise tts_values[] entries could point + * into the non-materialized tuple (which might be gone when accessed). + */ + slot->tts_nvalid = 0; + mslot->off = 0; + + if (!mslot->mintuple) + { + mslot->mintuple = heap_form_minimal_tuple(slot->tts_tupleDescriptor, + slot->tts_values, + slot->tts_isnull); + } + else + { + /* + * The minimal tuple contained in this slot is not allocated in the + * memory context of the given slot (else it would have TTS_SHOULDFREE + * set). Copy the minimal tuple into the given slot's memory context. + */ + mslot->mintuple = heap_copy_minimal_tuple(mslot->mintuple); + } + + slot->tts_flags |= TTS_FLAG_SHOULDFREE; + + Assert(mslot->tuple == &mslot->minhdr); + + mslot->minhdr.t_len = mslot->mintuple->t_len + MINIMAL_TUPLE_OFFSET; + mslot->minhdr.t_data = (HeapTupleHeader) ((char *) mslot->mintuple - MINIMAL_TUPLE_OFFSET); + + MemoryContextSwitchTo(oldContext); +} + +static void +tts_minimal_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot) +{ + MemoryContext oldcontext; + MinimalTuple mintuple; + + oldcontext = MemoryContextSwitchTo(dstslot->tts_mcxt); + mintuple = ExecCopySlotMinimalTuple(srcslot); + MemoryContextSwitchTo(oldcontext); + + ExecStoreMinimalTuple(mintuple, dstslot, true); +} + +static MinimalTuple +tts_minimal_get_minimal_tuple(TupleTableSlot *slot) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + if (!mslot->mintuple) + tts_minimal_materialize(slot); + + return mslot->mintuple; +} + +static HeapTuple +tts_minimal_copy_heap_tuple(TupleTableSlot *slot) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + if (!mslot->mintuple) + tts_minimal_materialize(slot); + + return heap_tuple_from_minimal_tuple(mslot->mintuple); +} + +static MinimalTuple +tts_minimal_copy_minimal_tuple(TupleTableSlot *slot) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + if (!mslot->mintuple) + tts_minimal_materialize(slot); + + return heap_copy_minimal_tuple(mslot->mintuple); +} + +static void +tts_minimal_store_tuple(TupleTableSlot *slot, MinimalTuple mtup, bool shouldFree) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + tts_minimal_clear(slot); + + Assert(!TTS_SHOULDFREE(slot)); + Assert(TTS_EMPTY(slot)); + + slot->tts_flags &= ~TTS_FLAG_EMPTY; + slot->tts_nvalid = 0; + mslot->off = 0; + + mslot->mintuple = mtup; + Assert(mslot->tuple == &mslot->minhdr); + mslot->minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET; + mslot->minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET); + /* no need to set t_self or t_tableOid since we won't allow access */ + + if (shouldFree) + slot->tts_flags |= TTS_FLAG_SHOULDFREE; +} + + +/* + * TupleTableSlotOps implementation for BufferHeapTupleTableSlot. + */ + +static void +tts_buffer_heap_init(TupleTableSlot *slot) +{ +} + +static void +tts_buffer_heap_release(TupleTableSlot *slot) +{ +} + +static void +tts_buffer_heap_clear(TupleTableSlot *slot) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + /* + * Free the memory for heap tuple if allowed. A tuple coming from buffer + * can never be freed. But we may have materialized a tuple from buffer. + * Such a tuple can be freed. + */ + if (TTS_SHOULDFREE(slot)) + { + /* We should have unpinned the buffer while materializing the tuple. */ + Assert(!BufferIsValid(bslot->buffer)); + + heap_freetuple(bslot->base.tuple); + slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; + } + + if (BufferIsValid(bslot->buffer)) + ReleaseBuffer(bslot->buffer); + + slot->tts_nvalid = 0; + slot->tts_flags |= TTS_FLAG_EMPTY; + ItemPointerSetInvalid(&slot->tts_tid); + bslot->base.tuple = NULL; + bslot->base.off = 0; + bslot->buffer = InvalidBuffer; +} + +static void +tts_buffer_heap_getsomeattrs(TupleTableSlot *slot, int natts) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + slot_deform_heap_tuple(slot, bslot->base.tuple, &bslot->base.off, natts); +} + +static Datum +tts_buffer_heap_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + /* + * In some code paths it's possible to get here with a non-materialized + * slot, in which case we can't retrieve system columns. + */ + if (!bslot->base.tuple) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot retrieve a system column in this context"))); + + return heap_getsysattr(bslot->base.tuple, attnum, + slot->tts_tupleDescriptor, isnull); +} + +static void +tts_buffer_heap_materialize(TupleTableSlot *slot) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + MemoryContext oldContext; + + Assert(!TTS_EMPTY(slot)); + + /* If slot has its tuple already materialized, nothing to do. */ + if (TTS_SHOULDFREE(slot)) + return; + + oldContext = MemoryContextSwitchTo(slot->tts_mcxt); + + /* + * Have to deform from scratch, otherwise tts_values[] entries could point + * into the non-materialized tuple (which might be gone when accessed). + */ + bslot->base.off = 0; + slot->tts_nvalid = 0; + + if (!bslot->base.tuple) + { + /* + * Normally BufferHeapTupleTableSlot should have a tuple + buffer + * associated with it, unless it's materialized (which would've + * returned above). But when it's useful to allow storing virtual + * tuples in a buffer slot, which then also needs to be + * materializable. + */ + bslot->base.tuple = heap_form_tuple(slot->tts_tupleDescriptor, + slot->tts_values, + slot->tts_isnull); + } + else + { + bslot->base.tuple = heap_copytuple(bslot->base.tuple); + + /* + * A heap tuple stored in a BufferHeapTupleTableSlot should have a + * buffer associated with it, unless it's materialized or virtual. + */ + if (likely(BufferIsValid(bslot->buffer))) + ReleaseBuffer(bslot->buffer); + bslot->buffer = InvalidBuffer; + } + + /* + * We don't set TTS_FLAG_SHOULDFREE until after releasing the buffer, if + * any. This avoids having a transient state that would fall foul of our + * assertions that a slot with TTS_FLAG_SHOULDFREE doesn't own a buffer. + * In the unlikely event that ReleaseBuffer() above errors out, we'd + * effectively leak the copied tuple, but that seems fairly harmless. + */ + slot->tts_flags |= TTS_FLAG_SHOULDFREE; + + MemoryContextSwitchTo(oldContext); +} + +static void +tts_buffer_heap_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot) +{ + BufferHeapTupleTableSlot *bsrcslot = (BufferHeapTupleTableSlot *) srcslot; + BufferHeapTupleTableSlot *bdstslot = (BufferHeapTupleTableSlot *) dstslot; + + /* + * If the source slot is of a different kind, or is a buffer slot that has + * been materialized / is virtual, make a new copy of the tuple. Otherwise + * make a new reference to the in-buffer tuple. + */ + if (dstslot->tts_ops != srcslot->tts_ops || + TTS_SHOULDFREE(srcslot) || + !bsrcslot->base.tuple) + { + MemoryContext oldContext; + + ExecClearTuple(dstslot); + dstslot->tts_flags &= ~TTS_FLAG_EMPTY; + oldContext = MemoryContextSwitchTo(dstslot->tts_mcxt); + bdstslot->base.tuple = ExecCopySlotHeapTuple(srcslot); + dstslot->tts_flags |= TTS_FLAG_SHOULDFREE; + MemoryContextSwitchTo(oldContext); + } + else + { + Assert(BufferIsValid(bsrcslot->buffer)); + + tts_buffer_heap_store_tuple(dstslot, bsrcslot->base.tuple, + bsrcslot->buffer, false); + + /* + * The HeapTupleData portion of the source tuple might be shorter + * lived than the destination slot. Therefore copy the HeapTuple into + * our slot's tupdata, which is guaranteed to live long enough (but + * will still point into the buffer). + */ + memcpy(&bdstslot->base.tupdata, bdstslot->base.tuple, sizeof(HeapTupleData)); + bdstslot->base.tuple = &bdstslot->base.tupdata; + } +} + +static HeapTuple +tts_buffer_heap_get_heap_tuple(TupleTableSlot *slot) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + if (!bslot->base.tuple) + tts_buffer_heap_materialize(slot); + + return bslot->base.tuple; +} + +static HeapTuple +tts_buffer_heap_copy_heap_tuple(TupleTableSlot *slot) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + if (!bslot->base.tuple) + tts_buffer_heap_materialize(slot); + + return heap_copytuple(bslot->base.tuple); +} + +static MinimalTuple +tts_buffer_heap_copy_minimal_tuple(TupleTableSlot *slot) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + if (!bslot->base.tuple) + tts_buffer_heap_materialize(slot); + + return minimal_tuple_from_heap_tuple(bslot->base.tuple); +} + +static inline void +tts_buffer_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, + Buffer buffer, bool transfer_pin) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + if (TTS_SHOULDFREE(slot)) + { + /* materialized slot shouldn't have a buffer to release */ + Assert(!BufferIsValid(bslot->buffer)); + + heap_freetuple(bslot->base.tuple); + slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; + } + + slot->tts_flags &= ~TTS_FLAG_EMPTY; + slot->tts_nvalid = 0; + bslot->base.tuple = tuple; + bslot->base.off = 0; + slot->tts_tid = tuple->t_self; + + /* + * If tuple is on a disk page, keep the page pinned as long as we hold a + * pointer into it. We assume the caller already has such a pin. If + * transfer_pin is true, we'll transfer that pin to this slot, if not + * we'll pin it again ourselves. + * + * This is coded to optimize the case where the slot previously held a + * tuple on the same disk page: in that case releasing and re-acquiring + * the pin is a waste of cycles. This is a common situation during + * seqscans, so it's worth troubling over. + */ + if (bslot->buffer != buffer) + { + if (BufferIsValid(bslot->buffer)) + ReleaseBuffer(bslot->buffer); + + bslot->buffer = buffer; + + if (!transfer_pin && BufferIsValid(buffer)) + IncrBufferRefCount(buffer); + } + else if (transfer_pin && BufferIsValid(buffer)) + { + /* + * In transfer_pin mode the caller won't know about the same-page + * optimization, so we gotta release its pin. + */ + ReleaseBuffer(buffer); + } +} + +/* + * slot_deform_heap_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. + * + * This is marked as always inline, so the different offp for different types + * of slots gets optimized away. + */ +static pg_attribute_always_inline void +slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, + int natts) +{ + 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? */ + + /* We can only fetch as many attributes as the tuple has. */ + natts = Min(HeapTupleHeaderGetNatts(tuple->t_data), natts); + + /* + * 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 = *offp; + 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; + *offp = off; + if (slow) + slot->tts_flags |= TTS_FLAG_SLOW; + else + slot->tts_flags &= ~TTS_FLAG_SLOW; +} + + +const TupleTableSlotOps TTSOpsVirtual = { + .base_slot_size = sizeof(VirtualTupleTableSlot), + .init = tts_virtual_init, + .release = tts_virtual_release, + .clear = tts_virtual_clear, + .getsomeattrs = tts_virtual_getsomeattrs, + .getsysattr = tts_virtual_getsysattr, + .materialize = tts_virtual_materialize, + .copyslot = tts_virtual_copyslot, + + /* + * A virtual tuple table slot can not "own" a heap tuple or a minimal + * tuple. + */ + .get_heap_tuple = NULL, + .get_minimal_tuple = NULL, + .copy_heap_tuple = tts_virtual_copy_heap_tuple, + .copy_minimal_tuple = tts_virtual_copy_minimal_tuple +}; + +const TupleTableSlotOps TTSOpsHeapTuple = { + .base_slot_size = sizeof(HeapTupleTableSlot), + .init = tts_heap_init, + .release = tts_heap_release, + .clear = tts_heap_clear, + .getsomeattrs = tts_heap_getsomeattrs, + .getsysattr = tts_heap_getsysattr, + .materialize = tts_heap_materialize, + .copyslot = tts_heap_copyslot, + .get_heap_tuple = tts_heap_get_heap_tuple, + + /* A heap tuple table slot can not "own" a minimal tuple. */ + .get_minimal_tuple = NULL, + .copy_heap_tuple = tts_heap_copy_heap_tuple, + .copy_minimal_tuple = tts_heap_copy_minimal_tuple +}; + +const TupleTableSlotOps TTSOpsMinimalTuple = { + .base_slot_size = sizeof(MinimalTupleTableSlot), + .init = tts_minimal_init, + .release = tts_minimal_release, + .clear = tts_minimal_clear, + .getsomeattrs = tts_minimal_getsomeattrs, + .getsysattr = tts_minimal_getsysattr, + .materialize = tts_minimal_materialize, + .copyslot = tts_minimal_copyslot, + + /* A minimal tuple table slot can not "own" a heap tuple. */ + .get_heap_tuple = NULL, + .get_minimal_tuple = tts_minimal_get_minimal_tuple, + .copy_heap_tuple = tts_minimal_copy_heap_tuple, + .copy_minimal_tuple = tts_minimal_copy_minimal_tuple +}; + +const TupleTableSlotOps TTSOpsBufferHeapTuple = { + .base_slot_size = sizeof(BufferHeapTupleTableSlot), + .init = tts_buffer_heap_init, + .release = tts_buffer_heap_release, + .clear = tts_buffer_heap_clear, + .getsomeattrs = tts_buffer_heap_getsomeattrs, + .getsysattr = tts_buffer_heap_getsysattr, + .materialize = tts_buffer_heap_materialize, + .copyslot = tts_buffer_heap_copyslot, + .get_heap_tuple = tts_buffer_heap_get_heap_tuple, + + /* A buffer heap tuple table slot can not "own" a minimal tuple. */ + .get_minimal_tuple = NULL, + .copy_heap_tuple = tts_buffer_heap_copy_heap_tuple, + .copy_minimal_tuple = tts_buffer_heap_copy_minimal_tuple +}; + + +/* ---------------------------------------------------------------- + * tuple table create/delete functions + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * MakeTupleTableSlot + * + * Basic routine to make an empty TupleTableSlot of given + * TupleTableSlotType. If tupleDesc is specified the slot's descriptor is + * fixed for its lifetime, gaining some efficiency. If that's + * undesirable, pass NULL. + * -------------------------------- + */ +TupleTableSlot * +MakeTupleTableSlot(TupleDesc tupleDesc, + const TupleTableSlotOps *tts_ops) +{ + Size basesz, + allocsz; + TupleTableSlot *slot; + + basesz = tts_ops->base_slot_size; + + /* + * When a fixed descriptor is specified, we can reduce overhead by + * allocating the entire slot in one go. + */ + if (tupleDesc) + allocsz = MAXALIGN(basesz) + + MAXALIGN(tupleDesc->natts * sizeof(Datum)) + + MAXALIGN(tupleDesc->natts * sizeof(bool)); + else + allocsz = basesz; + + slot = palloc0(allocsz); + /* const for optimization purposes, OK to modify at allocation time */ + *((const TupleTableSlotOps **) &slot->tts_ops) = tts_ops; + slot->type = T_TupleTableSlot; + slot->tts_flags |= TTS_FLAG_EMPTY; + if (tupleDesc != NULL) + slot->tts_flags |= TTS_FLAG_FIXED; + slot->tts_tupleDescriptor = tupleDesc; + slot->tts_mcxt = CurrentMemoryContext; + slot->tts_nvalid = 0; + + if (tupleDesc != NULL) + { + slot->tts_values = (Datum *) + (((char *) slot) + + MAXALIGN(basesz)); + slot->tts_isnull = (bool *) + (((char *) slot) + + MAXALIGN(basesz) + + MAXALIGN(tupleDesc->natts * sizeof(Datum))); + + PinTupleDesc(tupleDesc); + } + + /* + * And allow slot type specific initialization. + */ + slot->tts_ops->init(slot); + + return slot; +} + +/* -------------------------------- + * ExecAllocTableSlot + * + * Create a tuple table slot within a tuple table (which is just a List). + * -------------------------------- + */ +TupleTableSlot * +ExecAllocTableSlot(List **tupleTable, TupleDesc desc, + const TupleTableSlotOps *tts_ops) +{ + TupleTableSlot *slot = MakeTupleTableSlot(desc, tts_ops); + + *tupleTable = lappend(*tupleTable, slot); + + return slot; +} + +/* -------------------------------- + * ExecResetTupleTable + * + * This releases any resources (buffer pins, tupdesc refcounts) + * held by the tuple table, and optionally releases the memory + * occupied by the tuple table data structure. + * It is expected that this routine be called by ExecEndPlan(). + * -------------------------------- + */ +void +ExecResetTupleTable(List *tupleTable, /* tuple table */ + bool shouldFree) /* true if we should free memory */ +{ + ListCell *lc; + + foreach(lc, tupleTable) + { + TupleTableSlot *slot = lfirst_node(TupleTableSlot, lc); + + /* Always release resources and reset the slot to empty */ + ExecClearTuple(slot); + slot->tts_ops->release(slot); + if (slot->tts_tupleDescriptor) + { + ReleaseTupleDesc(slot->tts_tupleDescriptor); + slot->tts_tupleDescriptor = NULL; + } + + /* If shouldFree, release memory occupied by the slot itself */ + if (shouldFree) + { + if (!TTS_FIXED(slot)) + { + if (slot->tts_values) + pfree(slot->tts_values); + if (slot->tts_isnull) + pfree(slot->tts_isnull); + } + pfree(slot); + } + } + + /* If shouldFree, release the list structure */ + if (shouldFree) + list_free(tupleTable); +} + +/* -------------------------------- + * MakeSingleTupleTableSlot + * + * This is a convenience routine for operations that need a standalone + * TupleTableSlot not gotten from the main executor tuple table. It makes + * a single slot of given TupleTableSlotType and initializes it to use the + * given tuple descriptor. + * -------------------------------- + */ +TupleTableSlot * +MakeSingleTupleTableSlot(TupleDesc tupdesc, + const TupleTableSlotOps *tts_ops) +{ + TupleTableSlot *slot = MakeTupleTableSlot(tupdesc, tts_ops); + + return slot; +} + +/* -------------------------------- + * ExecDropSingleTupleTableSlot + * + * Release a TupleTableSlot made with MakeSingleTupleTableSlot. + * DON'T use this on a slot that's part of a tuple table list! + * -------------------------------- + */ +void +ExecDropSingleTupleTableSlot(TupleTableSlot *slot) +{ + /* This should match ExecResetTupleTable's processing of one slot */ + Assert(IsA(slot, TupleTableSlot)); + ExecClearTuple(slot); + slot->tts_ops->release(slot); + if (slot->tts_tupleDescriptor) + ReleaseTupleDesc(slot->tts_tupleDescriptor); + if (!TTS_FIXED(slot)) + { + if (slot->tts_values) + pfree(slot->tts_values); + if (slot->tts_isnull) + pfree(slot->tts_isnull); + } + pfree(slot); +} + + +/* ---------------------------------------------------------------- + * tuple table slot accessor functions + * ---------------------------------------------------------------- + */ + +/* -------------------------------- + * ExecSetSlotDescriptor + * + * This function is used to set the tuple descriptor associated + * with the slot's tuple. The passed descriptor must have lifespan + * at least equal to the slot's. If it is a reference-counted descriptor + * then the reference count is incremented for as long as the slot holds + * a reference. + * -------------------------------- + */ +void +ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */ + TupleDesc tupdesc) /* new tuple descriptor */ +{ + Assert(!TTS_FIXED(slot)); + + /* For safety, make sure slot is empty before changing it */ + ExecClearTuple(slot); + + /* + * Release any old descriptor. Also release old Datum/isnull arrays if + * present (we don't bother to check if they could be re-used). + */ + if (slot->tts_tupleDescriptor) + ReleaseTupleDesc(slot->tts_tupleDescriptor); + + if (slot->tts_values) + pfree(slot->tts_values); + if (slot->tts_isnull) + pfree(slot->tts_isnull); + + /* + * Install the new descriptor; if it's refcounted, bump its refcount. + */ + slot->tts_tupleDescriptor = tupdesc; + PinTupleDesc(tupdesc); + + /* + * Allocate Datum/isnull arrays of the appropriate size. These must have + * the same lifetime as the slot, so allocate in the slot's own context. + */ + slot->tts_values = (Datum *) + MemoryContextAlloc(slot->tts_mcxt, tupdesc->natts * sizeof(Datum)); + slot->tts_isnull = (bool *) + MemoryContextAlloc(slot->tts_mcxt, tupdesc->natts * sizeof(bool)); +} + +/* -------------------------------- + * ExecStoreHeapTuple + * + * This function is used to store an on-the-fly physical tuple into a specified + * slot in the tuple table. + * + * tuple: tuple to store + * slot: TTSOpsHeapTuple type slot to store it in + * shouldFree: true if ExecClearTuple should pfree() the tuple + * when done with it + * + * shouldFree is normally set 'true' for tuples constructed on-the-fly. But it + * can be 'false' 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. + * + * If the target slot is not guaranteed to be TTSOpsHeapTuple type slot, use + * the, more expensive, ExecForceStoreHeapTuple(). + * -------------------------------- + */ +TupleTableSlot * +ExecStoreHeapTuple(HeapTuple tuple, + TupleTableSlot *slot, + bool shouldFree) +{ + /* + * sanity checks + */ + Assert(tuple != NULL); + Assert(slot != NULL); + Assert(slot->tts_tupleDescriptor != NULL); + + if (unlikely(!TTS_IS_HEAPTUPLE(slot))) + elog(ERROR, "trying to store a heap tuple into wrong type of slot"); + tts_heap_store_tuple(slot, tuple, shouldFree); + + slot->tts_tableOid = tuple->t_tableOid; + + return slot; +} + +/* -------------------------------- + * ExecStoreBufferHeapTuple + * + * This function is used to store an on-disk physical tuple from a buffer + * into a specified slot in the tuple table. + * + * tuple: tuple to store + * slot: TTSOpsBufferHeapTuple type slot to store it in + * buffer: disk buffer if tuple is in a disk page, else 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. + * + * Return value is just the passed-in slot pointer. + * + * If the target slot is not guaranteed to be TTSOpsBufferHeapTuple type slot, + * use the, more expensive, ExecForceStoreHeapTuple(). + * -------------------------------- + */ +TupleTableSlot * +ExecStoreBufferHeapTuple(HeapTuple tuple, + TupleTableSlot *slot, + Buffer buffer) +{ + /* + * sanity checks + */ + Assert(tuple != NULL); + Assert(slot != NULL); + Assert(slot->tts_tupleDescriptor != NULL); + Assert(BufferIsValid(buffer)); + + if (unlikely(!TTS_IS_BUFFERTUPLE(slot))) + elog(ERROR, "trying to store an on-disk heap tuple into wrong type of slot"); + tts_buffer_heap_store_tuple(slot, tuple, buffer, false); + + slot->tts_tableOid = tuple->t_tableOid; + + return slot; +} + +/* + * Like ExecStoreBufferHeapTuple, but transfer an existing pin from the caller + * to the slot, i.e. the caller doesn't need to, and may not, release the pin. + */ +TupleTableSlot * +ExecStorePinnedBufferHeapTuple(HeapTuple tuple, + TupleTableSlot *slot, + Buffer buffer) +{ + /* + * sanity checks + */ + Assert(tuple != NULL); + Assert(slot != NULL); + Assert(slot->tts_tupleDescriptor != NULL); + Assert(BufferIsValid(buffer)); + + if (unlikely(!TTS_IS_BUFFERTUPLE(slot))) + elog(ERROR, "trying to store an on-disk heap tuple into wrong type of slot"); + tts_buffer_heap_store_tuple(slot, tuple, buffer, true); + + slot->tts_tableOid = tuple->t_tableOid; + + return slot; +} + +/* + * Store a minimal tuple into TTSOpsMinimalTuple type slot. + * + * If the target slot is not guaranteed to be TTSOpsMinimalTuple type slot, + * use the, more expensive, ExecForceStoreMinimalTuple(). + */ +TupleTableSlot * +ExecStoreMinimalTuple(MinimalTuple mtup, + TupleTableSlot *slot, + bool shouldFree) +{ + /* + * sanity checks + */ + Assert(mtup != NULL); + Assert(slot != NULL); + Assert(slot->tts_tupleDescriptor != NULL); + + if (unlikely(!TTS_IS_MINIMALTUPLE(slot))) + elog(ERROR, "trying to store a minimal tuple into wrong type of slot"); + tts_minimal_store_tuple(slot, mtup, shouldFree); + + return slot; +} + +/* + * Store a HeapTuple into any kind of slot, performing conversion if + * necessary. + */ +void +ExecForceStoreHeapTuple(HeapTuple tuple, + TupleTableSlot *slot, + bool shouldFree) +{ + if (TTS_IS_HEAPTUPLE(slot)) + { + ExecStoreHeapTuple(tuple, slot, shouldFree); + } + else if (TTS_IS_BUFFERTUPLE(slot)) + { + MemoryContext oldContext; + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + ExecClearTuple(slot); + slot->tts_flags &= ~TTS_FLAG_EMPTY; + oldContext = MemoryContextSwitchTo(slot->tts_mcxt); + bslot->base.tuple = heap_copytuple(tuple); + slot->tts_flags |= TTS_FLAG_SHOULDFREE; + MemoryContextSwitchTo(oldContext); + + if (shouldFree) + pfree(tuple); + } + else + { + ExecClearTuple(slot); + heap_deform_tuple(tuple, slot->tts_tupleDescriptor, + slot->tts_values, slot->tts_isnull); + ExecStoreVirtualTuple(slot); + + if (shouldFree) + { + ExecMaterializeSlot(slot); + pfree(tuple); + } + } +} + +/* + * Store a MinimalTuple into any kind of slot, performing conversion if + * necessary. + */ +void +ExecForceStoreMinimalTuple(MinimalTuple mtup, + TupleTableSlot *slot, + bool shouldFree) +{ + if (TTS_IS_MINIMALTUPLE(slot)) + { + tts_minimal_store_tuple(slot, mtup, shouldFree); + } + else + { + HeapTupleData htup; + + ExecClearTuple(slot); + + htup.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET; + htup.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET); + heap_deform_tuple(&htup, slot->tts_tupleDescriptor, + slot->tts_values, slot->tts_isnull); + ExecStoreVirtualTuple(slot); + + if (shouldFree) + { + ExecMaterializeSlot(slot); + pfree(mtup); + } + } +} + +/* -------------------------------- + * ExecStoreVirtualTuple + * Mark a slot as containing a virtual tuple. + * + * The protocol for loading a slot with virtual tuple data is: + * * Call ExecClearTuple to mark the slot empty. + * * Store data into the Datum/isnull arrays. + * * Call ExecStoreVirtualTuple to mark the slot valid. + * This is a bit unclean but it avoids one round of data copying. + * -------------------------------- + */ +TupleTableSlot * +ExecStoreVirtualTuple(TupleTableSlot *slot) +{ + /* + * sanity checks + */ + Assert(slot != NULL); + Assert(slot->tts_tupleDescriptor != NULL); + Assert(TTS_EMPTY(slot)); + + slot->tts_flags &= ~TTS_FLAG_EMPTY; + slot->tts_nvalid = slot->tts_tupleDescriptor->natts; + + return slot; +} + +/* -------------------------------- + * ExecStoreAllNullTuple + * Set up the slot to contain a null in every column. + * + * At first glance this might sound just like ExecClearTuple, but it's + * entirely different: the slot ends up full, not empty. + * -------------------------------- + */ +TupleTableSlot * +ExecStoreAllNullTuple(TupleTableSlot *slot) +{ + /* + * sanity checks + */ + Assert(slot != NULL); + Assert(slot->tts_tupleDescriptor != NULL); + + /* Clear any old contents */ + ExecClearTuple(slot); + + /* + * Fill all the columns of the virtual tuple with nulls + */ + MemSet(slot->tts_values, 0, + slot->tts_tupleDescriptor->natts * sizeof(Datum)); + memset(slot->tts_isnull, true, + slot->tts_tupleDescriptor->natts * sizeof(bool)); + + return ExecStoreVirtualTuple(slot); +} + +/* + * Store a HeapTuple in datum form, into a slot. That always requires + * deforming it and storing it in virtual form. + * + * Until the slot is materialized, the contents of the slot depend on the + * datum. + */ +void +ExecStoreHeapTupleDatum(Datum data, TupleTableSlot *slot) +{ + HeapTupleData tuple = {0}; + HeapTupleHeader td; + + td = DatumGetHeapTupleHeader(data); + + tuple.t_len = HeapTupleHeaderGetDatumLength(td); + tuple.t_self = td->t_ctid; + tuple.t_data = td; + + ExecClearTuple(slot); + + heap_deform_tuple(&tuple, slot->tts_tupleDescriptor, + slot->tts_values, slot->tts_isnull); + ExecStoreVirtualTuple(slot); +} + +/* + * ExecFetchSlotHeapTuple - fetch HeapTuple representing the slot's content + * + * The returned HeapTuple represents the slot's content as closely as + * possible. + * + * If materialize is true, the contents of the slots will be made independent + * from the underlying storage (i.e. all buffer pins are released, memory is + * allocated in the slot's context). + * + * If shouldFree is not-NULL it'll be set to true if the returned tuple has + * been allocated in the calling memory context, and must be freed by the + * caller (via explicit pfree() or a memory context reset). + * + * NB: If materialize is true, modifications of the returned tuple are + * allowed. But it depends on the type of the slot whether such modifications + * will also affect the slot's contents. While that is not the nicest + * behaviour, all such modifications are in the process of being removed. + */ +HeapTuple +ExecFetchSlotHeapTuple(TupleTableSlot *slot, bool materialize, bool *shouldFree) +{ + /* + * sanity checks + */ + Assert(slot != NULL); + Assert(!TTS_EMPTY(slot)); + + /* Materialize the tuple so that the slot "owns" it, if requested. */ + if (materialize) + slot->tts_ops->materialize(slot); + + if (slot->tts_ops->get_heap_tuple == NULL) + { + if (shouldFree) + *shouldFree = true; + return slot->tts_ops->copy_heap_tuple(slot); + } + else + { + if (shouldFree) + *shouldFree = false; + return slot->tts_ops->get_heap_tuple(slot); + } +} + +/* -------------------------------- + * ExecFetchSlotMinimalTuple + * Fetch the slot's minimal physical tuple. + * + * If the given tuple table slot can hold a minimal tuple, indicated by a + * non-NULL get_minimal_tuple callback, the function returns the minimal + * tuple returned by that callback. It assumes that the minimal tuple + * returned by the callback is "owned" by the slot i.e. the slot is + * responsible for freeing the memory consumed by the tuple. Hence it sets + * *shouldFree to false, indicating that the caller should not free the + * memory consumed by the minimal tuple. In this case the returned minimal + * tuple should be considered as read-only. + * + * If that callback is not supported, it calls copy_minimal_tuple callback + * which is expected to return a copy of minimal tuple representing the + * contents of the slot. In this case *shouldFree is set to true, + * indicating the caller that it should free the memory consumed by the + * minimal tuple. In this case the returned minimal tuple may be written + * up. + * -------------------------------- + */ +MinimalTuple +ExecFetchSlotMinimalTuple(TupleTableSlot *slot, + bool *shouldFree) +{ + /* + * sanity checks + */ + Assert(slot != NULL); + Assert(!TTS_EMPTY(slot)); + + if (slot->tts_ops->get_minimal_tuple) + { + if (shouldFree) + *shouldFree = false; + return slot->tts_ops->get_minimal_tuple(slot); + } + else + { + if (shouldFree) + *shouldFree = true; + return slot->tts_ops->copy_minimal_tuple(slot); + } +} + +/* -------------------------------- + * ExecFetchSlotHeapTupleDatum + * Fetch the slot's tuple as a composite-type Datum. + * + * The result is always freshly palloc'd in the caller's memory context. + * -------------------------------- + */ +Datum +ExecFetchSlotHeapTupleDatum(TupleTableSlot *slot) +{ + HeapTuple tup; + TupleDesc tupdesc; + bool shouldFree; + Datum ret; + + /* Fetch slot's contents in regular-physical-tuple form */ + tup = ExecFetchSlotHeapTuple(slot, false, &shouldFree); + tupdesc = slot->tts_tupleDescriptor; + + /* Convert to Datum form */ + ret = heap_copy_tuple_as_datum(tup, tupdesc); + + if (shouldFree) + pfree(tup); + + return ret; +} + +/* ---------------------------------------------------------------- + * convenience initialization routines + * ---------------------------------------------------------------- + */ + +/* ---------------- + * ExecInitResultTypeTL + * + * Initialize result type, using the plan node's targetlist. + * ---------------- + */ +void +ExecInitResultTypeTL(PlanState *planstate) +{ + TupleDesc tupDesc = ExecTypeFromTL(planstate->plan->targetlist); + + planstate->ps_ResultTupleDesc = tupDesc; +} + +/* -------------------------------- + * ExecInit{Result,Scan,Extra}TupleSlot[TL] + * + * These are convenience routines to initialize the specified slot + * in nodes inheriting the appropriate state. ExecInitExtraTupleSlot + * is used for initializing special-purpose slots. + * -------------------------------- + */ + +/* ---------------- + * ExecInitResultTupleSlotTL + * + * Initialize result tuple slot, using the tuple descriptor previously + * computed with ExecInitResultTypeTL(). + * ---------------- + */ +void +ExecInitResultSlot(PlanState *planstate, const TupleTableSlotOps *tts_ops) +{ + TupleTableSlot *slot; + + slot = ExecAllocTableSlot(&planstate->state->es_tupleTable, + planstate->ps_ResultTupleDesc, tts_ops); + planstate->ps_ResultTupleSlot = slot; + + planstate->resultopsfixed = planstate->ps_ResultTupleDesc != NULL; + planstate->resultops = tts_ops; + planstate->resultopsset = true; +} + +/* ---------------- + * ExecInitResultTupleSlotTL + * + * Initialize result tuple slot, using the plan node's targetlist. + * ---------------- + */ +void +ExecInitResultTupleSlotTL(PlanState *planstate, + const TupleTableSlotOps *tts_ops) +{ + ExecInitResultTypeTL(planstate); + ExecInitResultSlot(planstate, tts_ops); +} + +/* ---------------- + * ExecInitScanTupleSlot + * ---------------- + */ +void +ExecInitScanTupleSlot(EState *estate, ScanState *scanstate, + TupleDesc tupledesc, const TupleTableSlotOps *tts_ops) +{ + scanstate->ss_ScanTupleSlot = ExecAllocTableSlot(&estate->es_tupleTable, + tupledesc, tts_ops); + scanstate->ps.scandesc = tupledesc; + scanstate->ps.scanopsfixed = tupledesc != NULL; + scanstate->ps.scanops = tts_ops; + scanstate->ps.scanopsset = true; +} + +/* ---------------- + * ExecInitExtraTupleSlot + * + * Return a newly created slot. If tupledesc is non-NULL the slot will have + * that as its fixed tupledesc. Otherwise the caller needs to use + * ExecSetSlotDescriptor() to set the descriptor before use. + * ---------------- + */ +TupleTableSlot * +ExecInitExtraTupleSlot(EState *estate, + TupleDesc tupledesc, + const TupleTableSlotOps *tts_ops) +{ + return ExecAllocTableSlot(&estate->es_tupleTable, tupledesc, tts_ops); +} + +/* ---------------- + * ExecInitNullTupleSlot + * + * Build a slot containing an all-nulls tuple of the given type. + * This is used as a substitute for an input tuple when performing an + * outer join. + * ---------------- + */ +TupleTableSlot * +ExecInitNullTupleSlot(EState *estate, TupleDesc tupType, + const TupleTableSlotOps *tts_ops) +{ + TupleTableSlot *slot = ExecInitExtraTupleSlot(estate, tupType, tts_ops); + + return ExecStoreAllNullTuple(slot); +} + +/* --------------------------------------------------------------- + * Routines for setting/accessing attributes in a slot. + * --------------------------------------------------------------- + */ + +/* + * Fill in missing values for a TupleTableSlot. + * + * This is only exposed because it's needed for JIT compiled tuple + * deforming. That exception aside, there should be no callers outside of this + * file. + */ +void +slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum) +{ + AttrMissing *attrmiss = NULL; + + if (slot->tts_tupleDescriptor->constr) + attrmiss = slot->tts_tupleDescriptor->constr->missing; + + if (!attrmiss) + { + /* no missing values array at all, so just fill everything in as NULL */ + memset(slot->tts_values + startAttNum, 0, + (lastAttNum - startAttNum) * sizeof(Datum)); + memset(slot->tts_isnull + startAttNum, 1, + (lastAttNum - startAttNum) * sizeof(bool)); + } + else + { + int missattnum; + + /* if there is a missing values array we must process them one by one */ + for (missattnum = startAttNum; + missattnum < lastAttNum; + missattnum++) + { + slot->tts_values[missattnum] = attrmiss[missattnum].am_value; + slot->tts_isnull[missattnum] = !attrmiss[missattnum].am_present; + } + } +} + +/* + * slot_getsomeattrs_int - workhorse for slot_getsomeattrs() + */ +void +slot_getsomeattrs_int(TupleTableSlot *slot, int attnum) +{ + /* Check for caller errors */ + Assert(slot->tts_nvalid < attnum); /* checked in slot_getsomeattrs */ + Assert(attnum > 0); + + if (unlikely(attnum > slot->tts_tupleDescriptor->natts)) + elog(ERROR, "invalid attribute number %d", attnum); + + /* Fetch as many attributes as possible from the underlying tuple. */ + slot->tts_ops->getsomeattrs(slot, attnum); + + /* + * If the underlying tuple doesn't have enough attributes, tuple + * descriptor must have the missing attributes. + */ + if (unlikely(slot->tts_nvalid < attnum)) + { + slot_getmissingattrs(slot, slot->tts_nvalid, attnum); + slot->tts_nvalid = attnum; + } +} + +/* ---------------------------------------------------------------- + * ExecTypeFromTL + * + * Generate a tuple descriptor for the result tuple of a targetlist. + * (A parse/plan tlist must be passed, not an ExprState tlist.) + * Note that resjunk columns, if any, are included in the result. + * + * Currently there are about 4 different places where we create + * TupleDescriptors. They should all be merged, or perhaps + * be rewritten to call BuildDesc(). + * ---------------------------------------------------------------- + */ +TupleDesc +ExecTypeFromTL(List *targetList) +{ + return ExecTypeFromTLInternal(targetList, false); +} + +/* ---------------------------------------------------------------- + * ExecCleanTypeFromTL + * + * Same as above, but resjunk columns are omitted from the result. + * ---------------------------------------------------------------- + */ +TupleDesc +ExecCleanTypeFromTL(List *targetList) +{ + return ExecTypeFromTLInternal(targetList, true); +} + +static TupleDesc +ExecTypeFromTLInternal(List *targetList, bool skipjunk) +{ + TupleDesc typeInfo; + ListCell *l; + int len; + int cur_resno = 1; + + if (skipjunk) + len = ExecCleanTargetListLength(targetList); + else + len = ExecTargetListLength(targetList); + typeInfo = CreateTemplateTupleDesc(len); + + foreach(l, targetList) + { + TargetEntry *tle = lfirst(l); + + if (skipjunk && tle->resjunk) + continue; + TupleDescInitEntry(typeInfo, + cur_resno, + tle->resname, + exprType((Node *) tle->expr), + exprTypmod((Node *) tle->expr), + 0); + TupleDescInitEntryCollation(typeInfo, + cur_resno, + exprCollation((Node *) tle->expr)); + cur_resno++; + } + + return typeInfo; +} + +/* + * ExecTypeFromExprList - build a tuple descriptor from a list of Exprs + * + * This is roughly like ExecTypeFromTL, but we work from bare expressions + * not TargetEntrys. No names are attached to the tupledesc's columns. + */ +TupleDesc +ExecTypeFromExprList(List *exprList) +{ + TupleDesc typeInfo; + ListCell *lc; + int cur_resno = 1; + + typeInfo = CreateTemplateTupleDesc(list_length(exprList)); + + foreach(lc, exprList) + { + Node *e = lfirst(lc); + + TupleDescInitEntry(typeInfo, + cur_resno, + NULL, + exprType(e), + exprTypmod(e), + 0); + TupleDescInitEntryCollation(typeInfo, + cur_resno, + exprCollation(e)); + cur_resno++; + } + + return typeInfo; +} + +/* + * ExecTypeSetColNames - set column names in a RECORD TupleDesc + * + * Column names must be provided as an alias list (list of String nodes). + */ +void +ExecTypeSetColNames(TupleDesc typeInfo, List *namesList) +{ + int colno = 0; + ListCell *lc; + + /* It's only OK to change col names in a not-yet-blessed RECORD type */ + Assert(typeInfo->tdtypeid == RECORDOID); + Assert(typeInfo->tdtypmod < 0); + + foreach(lc, namesList) + { + char *cname = strVal(lfirst(lc)); + Form_pg_attribute attr; + + /* Guard against too-long names list (probably can't happen) */ + if (colno >= typeInfo->natts) + break; + attr = TupleDescAttr(typeInfo, colno); + colno++; + + /* + * Do nothing for empty aliases or dropped columns (these cases + * probably can't arise in RECORD types, either) + */ + if (cname[0] == '\0' || attr->attisdropped) + continue; + + /* OK, assign the column name */ + namestrcpy(&(attr->attname), cname); + } +} + +/* + * BlessTupleDesc - make a completed tuple descriptor useful for SRFs + * + * Rowtype Datums returned by a function must contain valid type information. + * This happens "for free" if the tupdesc came from a relcache entry, but + * not if we have manufactured a tupdesc for a transient RECORD datatype. + * In that case we have to notify typcache.c of the existence of the type. + */ +TupleDesc +BlessTupleDesc(TupleDesc tupdesc) +{ + if (tupdesc->tdtypeid == RECORDOID && + tupdesc->tdtypmod < 0) + assign_record_type_typmod(tupdesc); + + return tupdesc; /* just for notational convenience */ +} + +/* + * TupleDescGetAttInMetadata - Build an AttInMetadata structure based on the + * supplied TupleDesc. AttInMetadata can be used in conjunction with C strings + * to produce a properly formed tuple. + */ +AttInMetadata * +TupleDescGetAttInMetadata(TupleDesc tupdesc) +{ + int natts = tupdesc->natts; + int i; + Oid atttypeid; + Oid attinfuncid; + FmgrInfo *attinfuncinfo; + Oid *attioparams; + int32 *atttypmods; + AttInMetadata *attinmeta; + + attinmeta = (AttInMetadata *) palloc(sizeof(AttInMetadata)); + + /* "Bless" the tupledesc so that we can make rowtype datums with it */ + attinmeta->tupdesc = BlessTupleDesc(tupdesc); + + /* + * Gather info needed later to call the "in" function for each attribute + */ + attinfuncinfo = (FmgrInfo *) palloc0(natts * sizeof(FmgrInfo)); + attioparams = (Oid *) palloc0(natts * sizeof(Oid)); + atttypmods = (int32 *) palloc0(natts * sizeof(int32)); + + for (i = 0; i < natts; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + + /* Ignore dropped attributes */ + if (!att->attisdropped) + { + atttypeid = att->atttypid; + getTypeInputInfo(atttypeid, &attinfuncid, &attioparams[i]); + fmgr_info(attinfuncid, &attinfuncinfo[i]); + atttypmods[i] = att->atttypmod; + } + } + attinmeta->attinfuncs = attinfuncinfo; + attinmeta->attioparams = attioparams; + attinmeta->atttypmods = atttypmods; + + return attinmeta; +} + +/* + * BuildTupleFromCStrings - build a HeapTuple given user data in C string form. + * values is an array of C strings, one for each attribute of the return tuple. + * A NULL string pointer indicates we want to create a NULL field. + */ +HeapTuple +BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values) +{ + TupleDesc tupdesc = attinmeta->tupdesc; + int natts = tupdesc->natts; + Datum *dvalues; + bool *nulls; + int i; + HeapTuple tuple; + + dvalues = (Datum *) palloc(natts * sizeof(Datum)); + nulls = (bool *) palloc(natts * sizeof(bool)); + + /* + * Call the "in" function for each non-dropped attribute, even for nulls, + * to support domains. + */ + for (i = 0; i < natts; i++) + { + if (!TupleDescAttr(tupdesc, i)->attisdropped) + { + /* Non-dropped attributes */ + dvalues[i] = InputFunctionCall(&attinmeta->attinfuncs[i], + values[i], + attinmeta->attioparams[i], + attinmeta->atttypmods[i]); + if (values[i] != NULL) + nulls[i] = false; + else + nulls[i] = true; + } + else + { + /* Handle dropped attributes by setting to NULL */ + dvalues[i] = (Datum) 0; + nulls[i] = true; + } + } + + /* + * Form a tuple + */ + tuple = heap_form_tuple(tupdesc, dvalues, nulls); + + /* + * Release locally palloc'd space. XXX would probably be good to pfree + * values of pass-by-reference datums, as well. + */ + pfree(dvalues); + pfree(nulls); + + return tuple; +} + +/* + * HeapTupleHeaderGetDatum - convert a HeapTupleHeader pointer to a Datum. + * + * This must *not* get applied to an on-disk tuple; the tuple should be + * freshly made by heap_form_tuple or some wrapper routine for it (such as + * BuildTupleFromCStrings). Be sure also that the tupledesc used to build + * the tuple has a properly "blessed" rowtype. + * + * Formerly this was a macro equivalent to PointerGetDatum, relying on the + * fact that heap_form_tuple fills in the appropriate tuple header fields + * for a composite Datum. However, we now require that composite Datums not + * contain any external TOAST pointers. We do not want heap_form_tuple itself + * to enforce that; more specifically, the rule applies only to actual Datums + * and not to HeapTuple structures. Therefore, HeapTupleHeaderGetDatum is + * now a function that detects whether there are externally-toasted fields + * and constructs a new tuple with inlined fields if so. We still need + * heap_form_tuple to insert the Datum header fields, because otherwise this + * code would have no way to obtain a tupledesc for the tuple. + * + * Note that if we do build a new tuple, it's palloc'd in the current + * memory context. Beware of code that changes context between the initial + * heap_form_tuple/etc call and calling HeapTuple(Header)GetDatum. + * + * For performance-critical callers, it could be worthwhile to take extra + * steps to ensure that there aren't TOAST pointers in the output of + * heap_form_tuple to begin with. It's likely however that the costs of the + * typcache lookup and tuple disassembly/reassembly are swamped by TOAST + * dereference costs, so that the benefits of such extra effort would be + * minimal. + * + * XXX it would likely be better to create wrapper functions that produce + * a composite Datum from the field values in one step. However, there's + * enough code using the existing APIs that we couldn't get rid of this + * hack anytime soon. + */ +Datum +HeapTupleHeaderGetDatum(HeapTupleHeader tuple) +{ + Datum result; + TupleDesc tupDesc; + + /* No work if there are no external TOAST pointers in the tuple */ + if (!HeapTupleHeaderHasExternal(tuple)) + return PointerGetDatum(tuple); + + /* Use the type data saved by heap_form_tuple to look up the rowtype */ + tupDesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(tuple), + HeapTupleHeaderGetTypMod(tuple)); + + /* And do the flattening */ + result = toast_flatten_tuple_to_datum(tuple, + HeapTupleHeaderGetDatumLength(tuple), + tupDesc); + + ReleaseTupleDesc(tupDesc); + + return result; +} + + +/* + * Functions for sending tuples to the frontend (or other specified destination) + * as though it is a SELECT result. These are used by utility commands that + * need to project directly to the destination and don't need or want full + * table function capability. Currently used by EXPLAIN and SHOW ALL. + */ +TupOutputState * +begin_tup_output_tupdesc(DestReceiver *dest, + TupleDesc tupdesc, + const TupleTableSlotOps *tts_ops) +{ + TupOutputState *tstate; + + tstate = (TupOutputState *) palloc(sizeof(TupOutputState)); + + tstate->slot = MakeSingleTupleTableSlot(tupdesc, tts_ops); + tstate->dest = dest; + + tstate->dest->rStartup(tstate->dest, (int) CMD_SELECT, tupdesc); + + return tstate; +} + +/* + * write a single tuple + */ +void +do_tup_output(TupOutputState *tstate, Datum *values, bool *isnull) +{ + TupleTableSlot *slot = tstate->slot; + int natts = slot->tts_tupleDescriptor->natts; + + /* make sure the slot is clear */ + ExecClearTuple(slot); + + /* insert data */ + memcpy(slot->tts_values, values, natts * sizeof(Datum)); + memcpy(slot->tts_isnull, isnull, natts * sizeof(bool)); + + /* mark slot as containing a virtual tuple */ + ExecStoreVirtualTuple(slot); + + /* send the tuple to the receiver */ + (void) tstate->dest->receiveSlot(slot, tstate->dest); + + /* clean up */ + ExecClearTuple(slot); +} + +/* + * write a chunk of text, breaking at newline characters + * + * Should only be used with a single-TEXT-attribute tupdesc. + */ +void +do_text_output_multiline(TupOutputState *tstate, const char *txt) +{ + Datum values[1]; + bool isnull[1] = {false}; + + while (*txt) + { + const char *eol; + int len; + + eol = strchr(txt, '\n'); + if (eol) + { + len = eol - txt; + eol++; + } + else + { + len = strlen(txt); + eol = txt + len; + } + + values[0] = PointerGetDatum(cstring_to_text_with_len(txt, len)); + do_tup_output(tstate, values, isnull); + pfree(DatumGetPointer(values[0])); + txt = eol; + } +} + +void +end_tup_output(TupOutputState *tstate) +{ + tstate->dest->rShutdown(tstate->dest); + /* note that destroying the dest is not ours to do */ + ExecDropSingleTupleTableSlot(tstate->slot); + pfree(tstate); +} |