Skip to content
Permalink
Browse files

Make TupleTableSlots extensible, finish split of existing slot type.

This commit completes the work prepared in 1a0586d, splitting the
old TupleTableSlot implementation (which could store buffer, heap,
minimal and virtual slots) into four different slot types.  As
described in the aforementioned commit, this is done with the goal of
making tuple table slots extensible, to allow for pluggable table
access methods.

To achieve runtime extensibility for TupleTableSlots, operations on
slots that can differ between types of slots are performed using the
TupleTableSlotOps struct provided at slot creation time.  That
includes information from the size of TupleTableSlot struct to be
allocated, initialization, deforming etc.  See the struct's definition
for more detailed information about callbacks TupleTableSlotOps.

I decided to rename TTSOpsBufferTuple to TTSOpsBufferHeapTuple and
ExecCopySlotTuple to ExecCopySlotHeapTuple, as that seems more
consistent with other naming introduced in recent patches.

There's plenty optimization potential in the slot implementation, but
according to benchmarking the state after this commit has similar
performance characteristics to before this set of changes, which seems
sufficient.

There's a few changes in execReplication.c that currently need to poke
through the slot abstraction, that'll be repaired once the pluggable
storage patchset provides the necessary infrastructure.

Author: Andres Freund and  Ashutosh Bapat, with changes by Amit Khandekar
Discussion: https://postgr.es/m/20181105210039.hh4vvi4vwoq5ba2q@alap3.anarazel.de
  • Loading branch information...
anarazel committed Nov 17, 2018
1 parent 0201d79 commit 4da597edf1bae0cf0453b5ed6fc4347b6334dfe1
@@ -71,6 +71,8 @@
#define VARLENA_ATT_IS_PACKABLE(att) \
((att)->attstorage != 'p')

static Datum getmissingattr(TupleDesc tupleDesc, int attnum, bool *isnull);


/* ----------------------------------------------------------------
* misc support routines
@@ -80,7 +82,7 @@
/*
* Return the missing value of an attribute, or NULL if there isn't one.
*/
Datum
static Datum
getmissingattr(TupleDesc tupleDesc,
int attnum, bool *isnull)
{
@@ -1350,186 +1352,6 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
values[attnum] = getmissingattr(tupleDesc, attnum + 1, &isnull[attnum]);
}

/*
* slot_deform_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.
*/
void
slot_deform_tuple(TupleTableSlot *slot, int natts)
{
HeapTuple tuple = slot->tts_tuple;
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? */

/*
* 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 = slot->tts_off;
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;
slot->tts_off = off;
if (slow)
slot->tts_flags |= TTS_FLAG_SLOW;
else
slot->tts_flags &= ~TTS_FLAG_SLOW;
}

/*
* slot_attisnull
* Detect whether an attribute of the slot is null, without
* actually fetching it.
*/
bool
slot_attisnull(TupleTableSlot *slot, int attnum)
{
HeapTuple tuple = slot->tts_tuple;
TupleDesc tupleDesc = slot->tts_tupleDescriptor;

/*
* system attributes are handled by heap_attisnull
*/
if (attnum <= 0)
{
if (tuple == NULL) /* internal error */
elog(ERROR, "cannot extract system attribute from virtual tuple");
if (tuple == &(slot->tts_minhdr)) /* internal error */
elog(ERROR, "cannot extract system attribute from minimal tuple");
return heap_attisnull(tuple, attnum, tupleDesc);
}

/*
* fast path if desired attribute already cached
*/
if (attnum <= slot->tts_nvalid)
return slot->tts_isnull[attnum - 1];

/*
* return NULL if attnum is out of range according to the tupdesc
*/
if (attnum > tupleDesc->natts)
return true;

/*
* otherwise we had better have a physical tuple (tts_nvalid should equal
* natts in all virtual-tuple cases)
*/
if (tuple == NULL) /* internal error */
elog(ERROR, "cannot extract attribute from empty tuple slot");

/* and let the tuple tell it */
return heap_attisnull(tuple, attnum, tupleDesc);
}

/*
* 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;
}

/*
* heap_freetuple
*/
@@ -2041,7 +2041,9 @@ FormIndexDatum(IndexInfo *indexInfo,
Datum iDatum;
bool isNull;

if (keycol != 0)
if (keycol < 0)
iDatum = slot_getsysattr(slot, keycol, &isNull);
else if (keycol != 0)
{
/*
* Plain index column; get the value we need directly from the
@@ -2850,7 +2850,7 @@ CopyFrom(CopyState cstate)
* freed after each batch insert.
*/
oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
tuple = ExecCopySlotTuple(slot);
tuple = ExecCopySlotHeapTuple(slot);
MemoryContextSwitchTo(oldcontext);
}

@@ -589,7 +589,7 @@ intorel_receive(TupleTableSlot *slot, DestReceiver *self)
* get the heap tuple out of the tuple table slot, making sure we have a
* writable copy
*/
tuple = ExecCopySlotTuple(slot);
tuple = ExecCopySlotHeapTuple(slot);

/*
* force assignment of new OID (see comments in ExecInsert)
@@ -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 = ExecCopySlotTuple(slot);
tuple = ExecCopySlotHeapTuple(slot);

heap_insert(myState->transientrel,
tuple,
@@ -218,27 +218,25 @@ execCurrentOf(CurrentOfExpr *cexpr,
ItemPointer tuple_tid;

#ifdef USE_ASSERT_CHECKING
if (!slot_getsysattr(scanstate->ss_ScanTupleSlot,
TableOidAttributeNumber,
&ldatum,
&lisnull))
ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot,
TableOidAttributeNumber,
&lisnull);
if (lisnull)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
cursor_name, table_name)));
Assert(!lisnull);
Assert(DatumGetObjectId(ldatum) == table_oid);
#endif

if (!slot_getsysattr(scanstate->ss_ScanTupleSlot,
SelfItemPointerAttributeNumber,
&ldatum,
&lisnull))
ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot,
SelfItemPointerAttributeNumber,
&lisnull);
if (lisnull)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
cursor_name, table_name)));
Assert(!lisnull);
tuple_tid = (ItemPointer) DatumGetPointer(ldatum);

*current_tid = *tuple_tid;
@@ -1875,11 +1875,11 @@ CheckOpSlotCompatibility(ExprEvalStep *op, TupleTableSlot *slot)
* Should probably fixed at some point, but for now it's easier to allow
* buffer and heap tuples to be used interchangably.
*/
if (slot->tts_ops == &TTSOpsBufferTuple &&
if (slot->tts_ops == &TTSOpsBufferHeapTuple &&
op->d.fetch.kind == &TTSOpsHeapTuple)
return;
if (slot->tts_ops == &TTSOpsHeapTuple &&
op->d.fetch.kind == &TTSOpsBufferTuple)
op->d.fetch.kind == &TTSOpsBufferHeapTuple)
return;

/*
@@ -4025,15 +4025,15 @@ void
ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext,
TupleTableSlot *slot)
{
bool success;
Datum d;

/* slot_getsysattr has sufficient defenses against bad attnums */
success = slot_getsysattr(slot,
op->d.var.attnum,
op->resvalue,
op->resnull);
d = slot_getsysattr(slot,
op->d.var.attnum,
op->resnull);
*op->resvalue = d;
/* this ought to be unreachable, but it's cheap enough to check */
if (unlikely(!success))
if (unlikely(*op->resnull))
elog(ERROR, "failed to fetch attribute from slot");
}

Oops, something went wrong.

0 comments on commit 4da597e

Please sign in to comment.
You can’t perform that action at this time.