/*------------------------------------------------------------------------- * * 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-2023, 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_FLAG_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_FLAG_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); }