diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index a7aa2ee02b..7a7c786041 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -60,6 +60,7 @@ #include "access/heaptoast.h" #include "access/htup_details.h" #include "access/tupdesc_details.h" +#include "access/xact.h" #include "catalog/pg_type.h" #include "funcapi.h" #include "nodes/nodeFuncs.h" @@ -148,6 +149,22 @@ tts_virtual_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) return 0; /* silence compiler warnings */ } +/* + * VirtualTupleTableSlots never have storage tuples. We generally + * shouldn't get here, but provide a user-friendly message if we do. + */ +static bool +tts_virtual_is_current_xact_tuple(TupleTableSlot *slot) +{ + Assert(!TTS_EMPTY(slot)); + + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("don't have a storage tuple in this context"))); + + return false; /* silence compiler warnings */ +} + /* * To materialize a virtual slot all the datums that aren't passed by value * have to be copied into the slot's memory context. To do so, compute the @@ -354,6 +371,29 @@ tts_heap_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) slot->tts_tupleDescriptor, isnull); } +static bool +tts_heap_is_current_xact_tuple(TupleTableSlot *slot) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + TransactionId xmin; + + Assert(!TTS_EMPTY(slot)); + + /* + * In some code paths it's possible to get here with a non-materialized + * slot, in which case we can't check if tuple is created by the current + * transaction. + */ + if (!hslot->tuple) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("don't have a storage tuple in this context"))); + + xmin = HeapTupleHeaderGetRawXmin(hslot->tuple->t_data); + + return TransactionIdIsCurrentTransactionId(xmin); +} + static void tts_heap_materialize(TupleTableSlot *slot) { @@ -521,6 +561,18 @@ tts_minimal_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) return 0; /* silence compiler warnings */ } +static bool +tts_minimal_is_current_xact_tuple(TupleTableSlot *slot) +{ + Assert(!TTS_EMPTY(slot)); + + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("don't have a storage tuple in this context"))); + + return false; /* silence compiler warnings */ +} + static void tts_minimal_materialize(TupleTableSlot *slot) { @@ -714,6 +766,29 @@ tts_buffer_heap_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) slot->tts_tupleDescriptor, isnull); } +static bool +tts_buffer_is_current_xact_tuple(TupleTableSlot *slot) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + TransactionId xmin; + + Assert(!TTS_EMPTY(slot)); + + /* + * In some code paths it's possible to get here with a non-materialized + * slot, in which case we can't check if tuple is created by the current + * transaction. + */ + if (!bslot->base.tuple) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("don't have a storage tuple in this context"))); + + xmin = HeapTupleHeaderGetRawXmin(bslot->base.tuple->t_data); + + return TransactionIdIsCurrentTransactionId(xmin); +} + static void tts_buffer_heap_materialize(TupleTableSlot *slot) { @@ -1029,6 +1104,7 @@ const TupleTableSlotOps TTSOpsVirtual = { .getsomeattrs = tts_virtual_getsomeattrs, .getsysattr = tts_virtual_getsysattr, .materialize = tts_virtual_materialize, + .is_current_xact_tuple = tts_virtual_is_current_xact_tuple, .copyslot = tts_virtual_copyslot, /* @@ -1048,6 +1124,7 @@ const TupleTableSlotOps TTSOpsHeapTuple = { .clear = tts_heap_clear, .getsomeattrs = tts_heap_getsomeattrs, .getsysattr = tts_heap_getsysattr, + .is_current_xact_tuple = tts_heap_is_current_xact_tuple, .materialize = tts_heap_materialize, .copyslot = tts_heap_copyslot, .get_heap_tuple = tts_heap_get_heap_tuple, @@ -1065,6 +1142,7 @@ const TupleTableSlotOps TTSOpsMinimalTuple = { .clear = tts_minimal_clear, .getsomeattrs = tts_minimal_getsomeattrs, .getsysattr = tts_minimal_getsysattr, + .is_current_xact_tuple = tts_minimal_is_current_xact_tuple, .materialize = tts_minimal_materialize, .copyslot = tts_minimal_copyslot, @@ -1082,6 +1160,7 @@ const TupleTableSlotOps TTSOpsBufferHeapTuple = { .clear = tts_buffer_heap_clear, .getsomeattrs = tts_buffer_heap_getsomeattrs, .getsysattr = tts_buffer_heap_getsysattr, + .is_current_xact_tuple = tts_buffer_is_current_xact_tuple, .materialize = tts_buffer_heap_materialize, .copyslot = tts_buffer_heap_copyslot, .get_heap_tuple = tts_buffer_heap_get_heap_tuple, diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 2fe9377500..62601a6d80 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -1260,9 +1260,6 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, { const RI_ConstraintInfo *riinfo; int ri_nullcheck; - Datum xminDatum; - TransactionId xmin; - bool isnull; /* * AfterTriggerSaveEvent() handles things such that this function is never @@ -1330,10 +1327,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, * this if we knew the INSERT trigger already fired, but there is no easy * way to know that.) */ - xminDatum = slot_getsysattr(oldslot, MinTransactionIdAttributeNumber, &isnull); - Assert(!isnull); - xmin = DatumGetTransactionId(xminDatum); - if (TransactionIdIsCurrentTransactionId(xmin)) + if (slot_is_current_xact_tuple(oldslot)) return true; /* If all old and new key values are equal, no check is needed */ diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h index 6133dbcd0a..b82655e7e5 100644 --- a/src/include/executor/tuptable.h +++ b/src/include/executor/tuptable.h @@ -166,6 +166,12 @@ struct TupleTableSlotOps */ Datum (*getsysattr) (TupleTableSlot *slot, int attnum, bool *isnull); + /* + * Check if the tuple is created by the current transaction. Throws an + * error if the slot doesn't contain the storage tuple. + */ + bool (*is_current_xact_tuple) (TupleTableSlot *slot); + /* * Make the contents of the slot solely depend on the slot, and not on * underlying resources (like another memory context, buffers, etc). @@ -426,6 +432,21 @@ slot_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) return slot->tts_ops->getsysattr(slot, attnum, isnull); } +/* + * slot_is_current_xact_tuple - check if the slot's current tuple is created + * by the current transaction. + * + * If the slot does not contain a storage tuple, this will throw an error. + * Hence before calling this function, callers should make sure that the + * slot type supports storage tuples and that there is currently one inside + * the slot. + */ +static inline bool +slot_is_current_xact_tuple(TupleTableSlot *slot) +{ + return slot->tts_ops->is_current_xact_tuple(slot); +} + /* * ExecClearTuple - clear the slot's contents */