| /*------------------------------------------------------------------------- |
| * |
| * 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 "foreign/foreign.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" |
| |
| #include "cdb/cdbvars.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) |
| { |
| /* |
| * GPDB: AppendOptimized relations do need to get sysattrs AND use virtual |
| * tuples to pass around data. It is assumed that the caller knows what is |
| * doing, if the attribute is gp_segment_id. Otherwise, error out. |
| */ |
| if (attnum == GpSegmentIdAttributeNumber) |
| { |
| *isnull = false; |
| |
| return Int32GetDatum(GpIdentity.segindex); |
| } |
| else if (attnum == GpForeignServerAttributeNumber) |
| { |
| *isnull = false; |
| |
| return ObjectIdGetDatum(GetForeignServerSegByRelid(slot->tts_tableOid)); |
| } |
| |
| 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 = 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); |
| } |