Re: TupleTableSlot abstraction

Поиск
Список
Период
Сортировка
От Andres Freund
Тема Re: TupleTableSlot abstraction
Дата
Msg-id 20180824011634.q2lrc6b26mjwoi5s@alap3.anarazel.de
обсуждение исходный текст
Ответ на Re: TupleTableSlot abstraction  (Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>)
Ответы Re: TupleTableSlot abstraction
Список 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


В списке pgsql-hackers по дате отправления:

Предыдущее
От: Thomas Munro
Дата:
Сообщение: Re: [HACKERS] Optional message to user when terminating/cancelling backend
Следующее
От: Peter Geoghegan
Дата:
Сообщение: Re: [HACKERS] Re: Improve OR conditions on joined columns (commonstar schema problem)