Re: TupleTableSlot abstraction

Andres Freund <andres@anarazel.de>

From: Andres Freund <andres@anarazel.de>
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Cc: pgsql-hackers <pgsql-hackers@postgresql.org>, Robert Haas <robertmhaas@gmail.com>, Haribabu Kommi <kommi.haribabu@gmail.com>, Alvaro Herrera <alvherre@2ndquadrant.com>, Amit Kapila <amit.kapila16@gmail.com>
Date: 2018-08-24T01:16:34Z
Lists: pgsql-hackers
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.bapat@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.bapat@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.bapat@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


Commits

  1. Fix memory leak when inserting tuple at relation creation for CTAS

  2. Build HashState's hashkeys expression with the correct parent.

  3. Move TupleTableSlots boolean member into one flag variable.

  4. Move generic slot support functions from heaptuple.c into execTuples.c.

  5. Remove absolete function TupleDescGetSlot().

  6. Remove function list from prologue of execTuples.c.

  7. Split ExecStoreTuple into ExecStoreHeapTuple and ExecStoreBufferHeapTuple.

  8. Change TupleTableSlot->tts_nvalid to type AttrNumber.

  9. Deduplicate code between slot_getallattrs() and slot_getsomeattrs().

  10. Mention ownership requirements for REFRESH MATERIALIZED VIEW in docs

  11. Spell "partitionwise" consistently.

  12. Fix WHERE CURRENT OF when the referenced cursor uses an index-only scan.