postgresql/src/backend/executor/execTuples.c

2317 lines
61 KiB
C

/*-------------------------------------------------------------------------
*
* 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-2019, 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/htup_details.h"
#include "access/tupdesc_details.h"
#include "access/tuptoaster.h"
#include "funcapi.h"
#include "catalog/pg_type.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);
}
/*
* Attribute values are readily available in tts_values and tts_isnull array
* in a VirtualTupleTableSlot. So there should be no need to call either of the
* following two functions.
*/
static void
tts_virtual_getsomeattrs(TupleTableSlot *slot, int natts)
{
elog(ERROR, "getsomeattrs is not required to be called on a virtual tuple table slot");
}
static Datum
tts_virtual_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
{
elog(ERROR, "virtual tuple table slot does not have system attributes");
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 = dstslot->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;
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));
/* This slot has it's tuple already materialized. Nothing to do. */
if (TTS_SHOULDFREE(slot))
return;
slot->tts_flags |= TTS_FLAG_SHOULDFREE;
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
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);
}
/*
* 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;
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;
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)
{
elog(ERROR, "minimal tuple table slot does not have system attributes");
return 0; /* silence compiler warnings */
}
static void
tts_minimal_materialize(TupleTableSlot *slot)
{
MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
MemoryContext oldContext;
Assert(!TTS_EMPTY(slot));
/* This slot has it's tuple already materialized. Nothing to do. */
if (TTS_SHOULDFREE(slot))
return;
slot->tts_flags |= TTS_FLAG_SHOULDFREE;
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
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);
}
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);
/*
* 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;
}
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;
else
Assert(!TTS_SHOULDFREE(slot));
}
/*
* 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;
Assert(!BufferIsValid(bslot->buffer));
}
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;
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 already materialized nothing to do. */
if (TTS_SHOULDFREE(slot))
return;
slot->tts_flags |= TTS_FLAG_SHOULDFREE;
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
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.
*/
Assert(BufferIsValid(bslot->buffer));
if (likely(BufferIsValid(bslot->buffer)))
ReleaseBuffer(bslot->buffer);
bslot->buffer = InvalidBuffer;
}
MemoryContextSwitchTo(oldContext);
/*
* 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;
}
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_SHOULDFREE;
dstslot->tts_flags &= ~TTS_FLAG_EMPTY;
oldContext = MemoryContextSwitchTo(dstslot->tts_mcxt);
bdstslot->base.tuple = ExecCopySlotHeapTuple(srcslot);
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_SHOULDFREE;
slot->tts_flags &= ~TTS_FLAG_EMPTY;
oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
bslot->base.tuple = heap_copytuple(tuple);
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 TupleDesc
*
* Column names must be provided as an alias list (list of String nodes).
*
* For some callers, the supplied tupdesc has a named rowtype (not RECORD)
* and it is moderately likely that the alias list matches the column names
* already present in the tupdesc. If we do change any column names then
* we must reset the tupdesc's type to anonymous RECORD; but we avoid doing
* so if no names change.
*/
void
ExecTypeSetColNames(TupleDesc typeInfo, List *namesList)
{
bool modified = false;
int colno = 0;
ListCell *lc;
foreach(lc, namesList)
{
char *cname = strVal(lfirst(lc));
Form_pg_attribute attr;
/* Guard against too-long names list */
if (colno >= typeInfo->natts)
break;
attr = TupleDescAttr(typeInfo, colno);
colno++;
/* Ignore empty aliases (these must be for dropped columns) */
if (cname[0] == '\0')
continue;
/* Change tupdesc only if alias is actually different */
if (strcmp(cname, NameStr(attr->attname)) != 0)
{
namestrcpy(&(attr->attname), cname);
modified = true;
}
}
/* If we modified the tupdesc, it's now a new record type */
if (modified)
{
typeInfo->tdtypeid = RECORDOID;
typeInfo->tdtypmod = -1;
}
}
/*
* 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);
}