Hi, On 2018-08-20 19:51:33 +0530, Ashutosh Bapat wrote: > Sorry, forgot about that. Here's the patch set with that addressed.
Btw, you attach files as tar.zip, but they're actually gzip compressed... > From 838a463646a048b3dccff95079a514fdc86effb3 Mon Sep 17 00:00:00 2001 > From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> > Date: Mon, 13 Aug 2018 11:27:57 +0530 > Subject: [PATCH 01/11] Split ExecStoreTuple into ExecStoreHeapTuple and > ExecStoreBufferHeapTuple > > ExecStoreTuple() accepts a heap tuple from a buffer or constructed > on-the-fly. In the first case the caller passed a valid buffer and in > the later case it passes InvalidBuffer. In the first case, > ExecStoreTuple() pins the given buffer and in the later case it > records shouldFree flag. The function has some extra checks to > differentiate between the two cases. The usecases never overlap thus > spending extra cycles in checks is useless. Hence separate these > usecases into separate functions ExecStoreHeapTuple() to store > on-the-fly tuple and ExecStoreBufferHeapTuple() to store an on-disk > tuple from a buffer. This allows to shave some extra cycles while > storing a tuple in the slot. It doesn't *yet* allow shaving extra cycles, no? > * SLOT ACCESSORS > * ExecSetSlotDescriptor - set a slot's tuple descriptor > - * ExecStoreTuple - store a physical tuple in the > slot > + * ExecStoreHeapTuple - store an on-the-fly heap > tuple in the slot > + * ExecStoreBufferHeapTuple - store an on-disk heap tuple in the > slot > * ExecStoreMinimalTuple - store a minimal physical tuple in the > slot > * ExecClearTuple - clear contents of a slot > * ExecStoreVirtualTuple - mark slot as containing a virtual > * tuple I'd advocate for a separate patch ripping these out, they're almost always out of date. > /* -------------------------------- > - * ExecStoreTuple > + * ExecStoreHeapTuple > * > - * This function is used to store a physical tuple into a specified > + * 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: slot to store it in > - * buffer: disk buffer if tuple is in a disk page, else > InvalidBuffer > * shouldFree: true if ExecClearTuple should pfree() the tuple > * when done with it > * > - * If 'buffer' is not 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. > + * 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. > + * -------------------------------- > + */ > +TupleTableSlot * > +ExecStoreHeapTuple(HeapTuple tuple, > + TupleTableSlot *slot, > + bool shouldFree) > +{ > + /* > + * sanity checks > + */ > + Assert(tuple != NULL); > + Assert(slot != NULL); > + Assert(slot->tts_tupleDescriptor != NULL); > + > + /* > + * Free any old physical tuple belonging to the slot. > + */ > + if (slot->tts_shouldFree) > + heap_freetuple(slot->tts_tuple); > + if (slot->tts_shouldFreeMin) > + heap_free_minimal_tuple(slot->tts_mintuple); > + > + /* > + * Store the new tuple into the specified slot. > + */ > + slot->tts_isempty = false; > + slot->tts_shouldFree = shouldFree; > + slot->tts_shouldFreeMin = false; > + slot->tts_tuple = tuple; > + slot->tts_mintuple = NULL; > + > + /* Mark extracted state invalid */ > + slot->tts_nvalid = 0; > + > + return slot; > +} Uh, there could very well be a buffer previously stored in the slot, no? This can't currently be applied independently afaict. > From c4c55bc0d501400ffca2e7393039b2d38660cd2d Mon Sep 17 00:00:00 2001 > From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> > Date: Mon, 13 Aug 2018 11:27:57 +0530 > Subject: [PATCH 02/11] tts_nvalid in TupleTableSlot is AttrNumber > @@ -125,7 +125,7 @@ typedef struct TupleTableSlot > MemoryContext tts_mcxt; /* slot itself is in this context */ > Buffer tts_buffer; /* tuple's buffer, or > InvalidBuffer */ > #define FIELDNO_TUPLETABLESLOT_NVALID 9 > - int tts_nvalid; /* # of valid values in > tts_values */ > + AttrNumber tts_nvalid; /* # of valid values in > tts_values */ > #define FIELDNO_TUPLETABLESLOT_VALUES 10 > Datum *tts_values; /* current per-attribute values */ > #define FIELDNO_TUPLETABLESLOT_ISNULL 11 Shouldn't this be adapting at least a *few* more things, like slot_getattr's argument? > /* > - * 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; > - int missattnum; > - > - 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 > - { > - /* 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; > - } > - } > -} I would split out these moves into a separate commit, they are trivially committable separately. The commit's pretty big already, and that'd make it easier to see the actual differences. > - > -/* > * heap_compute_data_size > * Determine size of the data area of a tuple to be constructed > */ > @@ -1407,10 +1370,9 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc, > * re-computing information about previously extracted attributes. > * slot->tts_nvalid is the number of attributes already extracted. > */ > -static void > -slot_deform_tuple(TupleTableSlot *slot, int natts) > +void > +slot_deform_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, int > natts) > { This should be renamed to include "heap" in the name, as it's not going to be usable for, say, zheap. > -/* > - * slot_getsysattr > - * This function fetches a system attribute of the slot's current > tuple. > - * Unlike slot_getattr, if the slot does not contain system > attributes, > - * this will return false (with a NULL attribute value) instead of > - * throwing an error. > - */ > -bool > -slot_getsysattr(TupleTableSlot *slot, int attnum, > - Datum *value, bool *isnull) > -{ > - HeapTuple tuple = slot->tts_tuple; > - > - Assert(attnum < 0); /* else caller error */ > - if (tuple == NULL || > - tuple == &(slot->tts_minhdr)) > - { > - /* No physical tuple, or minimal tuple, so fail */ > - *value = (Datum) 0; > - *isnull = true; > - return false; > - } > - *value = heap_getsysattr(tuple, attnum, slot->tts_tupleDescriptor, > isnull); > - return true; > -} I think I was wrong at saying that we should remove this. I think you were right that it should become a callback... > +/* > + * This is a function used by all getattr() callbacks which deal with a heap > + * tuple or some tuple format which can be represented as a heap tuple e.g. a > + * minimal tuple. > + * > + * heap_getattr considers any attnum beyond the attributes available in the > + * tuple as NULL. This function however returns the values of missing > + * attributes from the tuple descriptor in that case. Also this function does > + * not support extracting system attributes. > + * > + * If the attribute needs to be fetched from the tuple, the function fills in > + * tts_values and tts_isnull arrays upto the required attnum. > + */ > +Datum > +tts_heap_getattr_common(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, > + int attnum, bool *isnull) > +{ > + HeapTupleHeader tup = tuple->t_data; > + Assert(slot->tts_nvalid < attnum); > + > + Assert(attnum > 0); > + > + if (attnum > HeapTupleHeaderGetNatts(tup)) > + return getmissingattr(slot->tts_tupleDescriptor, attnum, > isnull); > + > + /* > + * check if target attribute is null: no point in groveling through > tuple > + */ > + if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits)) > + { > + *isnull = true; > + return (Datum) 0; > + } I still think this is an optimization with a negative benefit, especially as it requires an extra callback. We should just rely on slot_deform_tuple and then access that. That'll also just access the null bitmap for the relevant column, and it'll make successive accesses cheaper. > @@ -2883,7 +2885,7 @@ CopyFrom(CopyState cstate) > if (slot == NULL) /* "do nothing" */ > skip_tuple = true; > else /* trigger might have > changed tuple */ > - tuple = ExecMaterializeSlot(slot); > + tuple = ExecFetchSlotTuple(slot, true); > } Could we do the Materialize vs Fetch vs Copy change separately? > diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c > index e1eb7c3..9957c70 100644 > --- a/src/backend/commands/matview.c > +++ b/src/backend/commands/matview.c > @@ -484,7 +484,7 @@ transientrel_receive(TupleTableSlot *slot, DestReceiver > *self) > * get the heap tuple out of the tuple table slot, making sure we have a > * writable copy > */ > - tuple = ExecMaterializeSlot(slot); > + tuple = ExecCopySlotTuple(slot); > > heap_insert(myState->transientrel, > tuple, > @@ -494,6 +494,9 @@ transientrel_receive(TupleTableSlot *slot, DestReceiver > *self) > > /* We know this is a newly created relation, so there are no indexes */ > > + /* Free the copied tuple. */ > + heap_freetuple(tuple); > + > return true; > } This'll potentially increase a fair amount of extra allocation overhead, no? > diff --git a/src/backend/executor/execExprInterp.c > b/src/backend/executor/execExprInterp.c > index 9d6e25a..1b4e726 100644 > --- a/src/backend/executor/execExprInterp.c > +++ b/src/backend/executor/execExprInterp.c > @@ -490,54 +490,21 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, > bool *isnull) > > EEO_CASE(EEOP_INNER_SYSVAR) > { > - int attnum = op->d.var.attnum; > - Datum d; > - > - /* these asserts must match defenses in slot_getattr */ > - Assert(innerslot->tts_tuple != NULL); > - Assert(innerslot->tts_tuple != > &(innerslot->tts_minhdr)); > - > - /* heap_getsysattr has sufficient defenses against bad > attnums */ > - d = heap_getsysattr(innerslot->tts_tuple, attnum, > - > innerslot->tts_tupleDescriptor, > - op->resnull); > - *op->resvalue = d; > + ExecEvalSysVar(state, op, econtext, innerslot); Please split this out into a separate patch. > +/* > + * TupleTableSlotOps implementation for BufferHeapTupleTableSlot. > + */ > + > +static void > +tts_buffer_init(TupleTableSlot *slot) > +{ > +} Should rename these to buffer_heap or such. > +/* > + * Store the given tuple into the given BufferHeapTupleTableSlot and pin the > + * given buffer. If the tuple already contained in the slot can be freed free > + * it. > + */ > +static void > +tts_buffer_store_tuple(TupleTableSlot *slot, HeapTuple tuple, Buffer buffer) > +{ > + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; > + > + if (IS_TTS_SHOULDFREE(slot)) > + { > + /* > + * A heap tuple stored in a BufferHeapTupleTableSlot should > have a > + * buffer associated with it, unless it's materialized. > + */ > + Assert(!BufferIsValid(bslot->buffer)); > + > + heap_freetuple(bslot->base.tuple); > + RESET_TTS_SHOULDFREE(slot); > + } > + > + RESET_TTS_EMPTY(slot); > + slot->tts_nvalid = 0; > + bslot->base.tuple = tuple; > + bslot->base.off = 0; > + > + /* > + * 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. > + * > + * 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; > + IncrBufferRefCount(buffer); > + } > +} This needs to also support storing a non-buffer tuple, I think. > +/* > + * TupleTableSlotOps for each of TupleTableSlotTypes. These are used to > + * identify the type of slot. > + */ > +const TupleTableSlotOps TTSOpsVirtual = { > + sizeof(TupleTableSlot), > + tts_virtual_init, > + tts_virtual_release, > + tts_virtual_clear, > + tts_virtual_getsomeattrs, > + tts_virtual_attisnull, > + tts_virtual_getattr, > + tts_virtual_materialize, > + tts_virtual_copyslot, > + > + /* > + * A virtual tuple table slot can not "own" a heap tuple or a minimal > + * tuple. > + */ > + NULL, > + NULL, > + tts_virtual_copy_heap_tuple, > + tts_virtual_copy_minimal_tuple, > +}; As we're now going to require C99, could you convert these into designated initializer style (i.e. .init = tts_heap_init etc)? > @@ -353,25 +1285,9 @@ ExecStoreHeapTuple(HeapTuple tuple, > Assert(slot != NULL); > Assert(slot->tts_tupleDescriptor != NULL); > > - /* > - * Free any old physical tuple belonging to the slot. > - */ > - if (IS_TTS_SHOULDFREE(slot)) > - heap_freetuple(slot->tts_tuple); > - if (IS_TTS_SHOULDFREEMIN(slot)) > - heap_free_minimal_tuple(slot->tts_mintuple); > - > - /* > - * Store the new tuple into the specified slot. > - */ > - RESET_TTS_EMPTY(slot); > - shouldFree ? SET_TTS_SHOULDFREE(slot) : RESET_TTS_SHOULDFREE(slot); > - RESET_TTS_SHOULDFREEMIN(slot); > - slot->tts_tuple = tuple; > - slot->tts_mintuple = NULL; > - > - /* Mark extracted state invalid */ > - slot->tts_nvalid = 0; > + if (!TTS_IS_HEAPTUPLE(slot)) > + elog(ERROR, "trying to store a heap tuple into wrong type of > slot"); > + tts_heap_store_tuple(slot, tuple, shouldFree); > > return slot; > } This should allow for buffer tuples too. > @@ -983,9 +1595,10 @@ ExecInitExtraTupleSlot(EState *estate, TupleDesc > tupledesc) > * ---------------- > */ > TupleTableSlot * > -ExecInitNullTupleSlot(EState *estate, TupleDesc tupType) > +ExecInitNullTupleSlot(EState *estate, TupleDesc tupType, > + const TupleTableSlotOps *tts_cb) > { > - TupleTableSlot *slot = ExecInitExtraTupleSlot(estate, tupType); > + TupleTableSlot *slot = ExecInitExtraTupleSlot(estate, tupType, tts_cb); > > return ExecStoreAllNullTuple(slot); > } It's a bit weird that the type name is *Ops but the param is tts_cb, no? > @@ -1590,7 +1590,8 @@ ExecHashTableInsert(HashJoinTable hashtable, > TupleTableSlot *slot, > uint32 hashvalue) > { > - MinimalTuple tuple = ExecFetchSlotMinimalTuple(slot); > + bool shouldFree; > + MinimalTuple tuple = ExecFetchSlotMinimalTuple(slot, &shouldFree); > int bucketno; > int batchno; > > @@ -1664,6 +1665,9 @@ ExecHashTableInsert(HashJoinTable hashtable, > hashvalue, > > &hashtable->innerBatchFile[batchno]); > } > + > + if (shouldFree) > + heap_free_minimal_tuple(tuple); > } Hm, how about splitting these out? > @@ -277,10 +277,32 @@ ExecInsert(ModifyTableState *mtstate, > OnConflictAction onconflict = node->onConflictAction; > > /* > - * get the heap tuple out of the tuple table slot, making sure we have a > - * writable copy > + * Get the heap tuple out of the tuple table slot, making sure we have a > + * writable copy. > + * > + * If the slot can contain a heap tuple, materialize the tuple within > the > + * slot itself so that the slot "owns" it and any changes to the tuple > + * reflect in the slot as well. > + * > + * Otherwise, store the copy of the heap tuple in > es_dml_input_tuple_slot, which > + * is assumed to be able to hold a heap tuple, so that repeated requests > + * for heap tuple in the following code will not create multiple copies > and > + * leak memory. Also keeping the tuple in the slot makes sure that it > will > + * be freed when it's no more needed either because a trigger modified > it > + * or when we are done processing it. > */ > - tuple = ExecMaterializeSlot(slot); > + if (!(TTS_IS_HEAPTUPLE(slot) || TTS_IS_BUFFERTUPLE(slot))) > + { > + TupleTableSlot *es_slot = estate->es_dml_input_tuple_slot; > + > + Assert(es_slot && TTS_IS_HEAPTUPLE(es_slot)); > + if (es_slot->tts_tupleDescriptor != slot->tts_tupleDescriptor) > + ExecSetSlotDescriptor(es_slot, > slot->tts_tupleDescriptor); > + ExecCopySlot(es_slot, slot); > + slot = es_slot; > + } > + > + tuple = ExecFetchSlotTuple(slot, true); I strongly dislike this. Could you look at my pluggable storage tree and see whether you could do something similar here? > @@ -2402,8 +2454,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, > int eflags) > */ > mtstate->ps.plan->targetlist = (List *) > linitial(node->returningLists); > > - /* Set up a slot for the output of the RETURNING projection(s) > */ > - ExecInitResultTupleSlotTL(estate, &mtstate->ps); > + /* > + * Set up a slot for the output of the RETURNING projection(s). > + * ExecDelete() requies the contents of the slot to be > + * saved/materialized, so use heap tuple table slot for a > DELETE. > + * Otherwise a virtual tuple table slot suffices. > + */ > + ExecInitResultTupleSlotTL(estate, &mtstate->ps, > + operation == > CMD_DELETE ? > + > &TTSOpsHeapTuple : &TTSOpsVirtual); > slot = mtstate->ps.ps_ResultTupleSlot; I'm not clear on why this this the case? > /* Need a > diff --git a/src/backend/executor/tqueue.c > b/src/backend/executor/tqueue.c > index ecdbe7f..ea2858b 100644 > --- a/src/backend/executor/tqueue.c > +++ b/src/backend/executor/tqueue.c > @@ -56,11 +56,28 @@ tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver > *self) > TQueueDestReceiver *tqueue = (TQueueDestReceiver *) self; > HeapTuple tuple; > shm_mq_result result; > + bool tuple_copied = false; > + > + /* Get the tuple out of slot, if necessary converting the slot's > contents > + * into a heap tuple by copying. In the later case we need to free the > copy. > + */ > + if (TTS_IS_HEAPTUPLE(slot) || TTS_IS_BUFFERTUPLE(slot)) > + { > + tuple = ExecFetchSlotTuple(slot, true); > + tuple_copied = false; > + } > + else > + { > + tuple = ExecCopySlotTuple(slot); > + tuple_copied = true; > + } To me needing this if() here is a bad sign, I think we want a ExecFetchSlotTuple* like api with a bool *shouldFree arg, like you did for minimal tuples instead. > diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c > index 66cc5c3..b44438b 100644 > --- a/src/backend/tcop/pquery.c > +++ b/src/backend/tcop/pquery.c > @@ -1071,7 +1071,7 @@ RunFromStore(Portal portal, ScanDirection direction, > uint64 count, > uint64 current_tuple_count = 0; > TupleTableSlot *slot; > > - slot = MakeSingleTupleTableSlot(portal->tupDesc); > + slot = MakeSingleTupleTableSlot(portal->tupDesc, &TTSOpsMinimalTuple); > > dest->rStartup(dest, CMD_SELECT, portal->tupDesc); These fairly rote changes make it really hard to see the actual meat of the changes in the patch. I think one way to address that would be to introduce stub &TTSOps* variables, add them to MakeSingleTupleTableSlot etc, but still have them return the "old style" slots. Then that can be reviewed separately. > @@ -1375,9 +1376,9 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS) > * previous row available for comparisons. This is accomplished by > * swapping the slot pointer variables after each row. > */ > - extraslot = MakeSingleTupleTableSlot(osastate->qstate->tupdesc); > + extraslot = MakeSingleTupleTableSlot(osastate->qstate->tupdesc, > + > &TTSOpsMinimalTuple); > slot2 = extraslot; > - > /* iterate till we find the hypothetical row */ > while (tuplesort_gettupleslot(osastate->sortstate, true, true, slot, > &abbrevVal)) superflous change. > diff --git a/src/backend/utils/sort/tuplestore.c > b/src/backend/utils/sort/tuplestore.c > index 5560a3e..ba98908 100644 > --- a/src/backend/utils/sort/tuplestore.c > +++ b/src/backend/utils/sort/tuplestore.c > @@ -1073,6 +1073,13 @@ tuplestore_gettuple(Tuplestorestate *state, bool > forward, > * pointer to a tuple held within the tuplestore. The latter is more > * efficient but the slot contents may be corrupted if additional writes to > * the tuplestore occur. (If using tuplestore_trim, see comments therein.) > + * > + * If the given slot can not contain a minimal tuple, the given minimal tuple > + * is converted into the form that the given slot can contain. Irrespective > of > + * the value of copy, that conversion might need to create a copy. If > + * should_free is set to true by tuplestore_gettuple(), the minimal tuple is > + * freed after the conversion, if necessary. Right now, the function only > + * supports slots of type HeapTupleTableSlot, other than > MinimalTupleTableSlot. > */ > bool > tuplestore_gettupleslot(Tuplestorestate *state, bool forward, > @@ -1085,12 +1092,25 @@ tuplestore_gettupleslot(Tuplestorestate *state, bool > forward, > > if (tuple) > { > - if (copy && !should_free) > + if (TTS_IS_MINIMALTUPLE(slot)) > { > - tuple = heap_copy_minimal_tuple(tuple); > + if (copy && !should_free) > + { > + tuple = heap_copy_minimal_tuple(tuple); > + should_free = true; > + } > + ExecStoreMinimalTuple(tuple, slot, should_free); > + } > + else if (TTS_IS_HEAPTUPLE(slot)) > + { > + HeapTuple htup = heap_tuple_from_minimal_tuple(tuple); > + > + if (should_free) > + heap_free_minimal_tuple(tuple); > should_free = true; > + > + ExecStoreHeapTuple(htup, slot, should_free); > } > - ExecStoreMinimalTuple(tuple, slot, should_free); > return true; > } > else Why is this a good idea? Shouldn't the caller always have a minimal slot here? This is problematic, because it means this'd need adapting for every new slot type, which'd be kinda against the idea of the whole thing... > +#define TTS_IS_VIRTUAL(slot) ((slot)->tts_cb == &TTSOpsVirtual) > +#define TTS_IS_HEAPTUPLE(slot) ((slot)->tts_cb == &TTSOpsHeapTuple) > +#define TTS_IS_MINIMALTUPLE(slot) ((slot)->tts_cb == &TTSOpsMinimalTuple) > +#define TTS_IS_BUFFERTUPLE(slot) ((slot)->tts_cb == &TTSOpsBufferTuple) > + > +extern Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot); this is a weird place for the function protoype? > +/* TupleTableSlotType specific routines */ > +typedef struct TupleTableSlotOps > +{ > + /* Minimum size of the slot */ > + size_t base_slot_size; > + > + /* Initialization. */ > + void (*init)(TupleTableSlot *slot); > + > + /* Destruction. */ > + void (*release)(TupleTableSlot *slot); > + > + /* > + * Clear the contents of the slot. Only the contents are expected to be > + * cleared and not the tuple descriptor. Typically an implementation of > + * this callback should free the memory allocated for the tuple > contained > + * in the slot. > + */ > + void (*clear)(TupleTableSlot *slot); > + > + /* > + * Fill up first natts entries of tts_values and tts_isnull arrays with > + * values from the tuple contained in the slot. The function may be > called > + * with natts more than the number of attributes available in the > tuple, in > + * which case it should fill up as many entries as the number of > available > + * attributes. The callback should update tts_nvalid with number of > entries > + * filled up. > + */ > + void (*getsomeattrs)(TupleTableSlot *slot, int natts); > + > + /* > + * Returns true if the attribute given by attnum is NULL, return false > + * otherwise. Some slot types may have more efficient methods to return > + * NULL-ness of a given attribute compared to checking NULL-ness after > + * calling getsomeattrs(). So this is a separate callback. We expect > this > + * callback to be invoked by slot_attisnull() only. That function > returns > + * if the information is available readily e.g. in tts_isnull array. > The > + * callback need not repeat the same. > + */ > + bool (*attisnull)(TupleTableSlot *slot, int attnum); > + > + /* > + * Returns value of the given attribute as a datum and sets isnull to > + * false, if it's not NULL. If the attribute is NULL, it sets isnull to > + * true. Some slot types may have more efficient methods to return > value of > + * a given attribute rather than returning the attribute value from > + * tts_values and tts_isnull after calling getsomeattrs(). So this is a > + * separate callback. We expect this callback to be invoked by > + * slot_getattr() only. That function returns if the information is > + * available readily e.g. in tts_values and tts_isnull array or can be > + * inferred from tuple descriptor. The callback need not repeat the > same. > + */ > + Datum (*getattr)(TupleTableSlot *slot, int attnum, bool *isnull); These two really show go. > +/* virtual or base type */ > +struct TupleTableSlot > { > NodeTag type; > #define FIELDNO_TUPLETABLESLOT_FLAGS 1 > - uint16 tts_flags; /* Boolean states */ > -#define FIELDNO_TUPLETABLESLOT_TUPLE 2 > - HeapTuple tts_tuple; /* physical tuple, or NULL if > virtual */ > -#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 3 > - TupleDesc tts_tupleDescriptor; /* slot's tuple descriptor */ > - MemoryContext tts_mcxt; /* slot itself is in this context */ > - Buffer tts_buffer; /* tuple's buffer, or > InvalidBuffer */ > -#define FIELDNO_TUPLETABLESLOT_NVALID 6 > + uint16 tts_flags; > +#define FIELDNO_TUPLETABLESLOT_NVALID 2 > AttrNumber tts_nvalid; /* # of valid values in > tts_values */ > -#define FIELDNO_TUPLETABLESLOT_VALUES 7 > + > + const TupleTableSlotOps *const tts_cb; > +#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 4 > + TupleDesc tts_tupleDescriptor; /* slot's tuple descriptor */ > +#define FIELDNO_TUPLETABLESLOT_VALUES 5 > Datum *tts_values; /* current per-attribute values */ > -#define FIELDNO_TUPLETABLESLOT_ISNULL 8 > +#define FIELDNO_TUPLETABLESLOT_ISNULL 6 > bool *tts_isnull; /* current per-attribute isnull flags */ > - MinimalTuple tts_mintuple; /* minimal tuple, or NULL if none */ > - HeapTupleData tts_minhdr; /* workspace for minimal-tuple-only > case */ > -#define FIELDNO_TUPLETABLESLOT_OFF 11 > - uint32 tts_off; /* saved state for > slot_deform_tuple */ > -} TupleTableSlot; > > -#define TTS_HAS_PHYSICAL_TUPLE(slot) \ > - ((slot)->tts_tuple != NULL && (slot)->tts_tuple != > &((slot)->tts_minhdr)) > + /* can we optimize away? */ > + MemoryContext tts_mcxt; /* slot itself is in this context */ > +}; the "can we" comment above is pretty clearly wrong, I think (Yes, I made it...). > From 84046126d5b8c9d780cb7134048cc717bda462a8 Mon Sep 17 00:00:00 2001 > From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> > Date: Mon, 13 Aug 2018 11:27:57 +0530 > Subject: [PATCH 07/11] Reset expression context just after resetting per tuple > context in ExecModifyTable(). > > Expression context saved in ps_ExprContext for ModifyTable node is > used to process returning clause and on conflict clause. For every > processed tuple it's reset in ExecProcessReturning() and > ExecOnConflictUpdate(). When a query has both RETURNING and ON > CONFLICT clauses, the reset happens twice and the first one of those > might reset memory used by the other. For some reason this doesn't > show up on HEAD, but is apparent when virtual tuple table slots, which > do not copy the datums in its own memory, are used for tuples returned > by RETURNING clause. > > This is fix for a query failing in sql/insert_conflict.sql > > insert into insertconflicttest as i values (23, 'Avocado') on conflict (key) > do update set fruit = excluded.*::text > returning *; > > Ashutosh Bapat, per suggestion by Andres Freund Doesn't this have to be earlier in the series? Phew, sorry if some things are daft, I'm kinda jetlagged... - Andres