]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Add TupleTableSlotOps.is_current_xact_tuple() method
authorAlexander Korotkov <akorotkov@postgresql.org>
Thu, 21 Mar 2024 21:00:43 +0000 (23:00 +0200)
committerAlexander Korotkov <akorotkov@postgresql.org>
Thu, 21 Mar 2024 21:00:43 +0000 (23:00 +0200)
This allows us to abstract how/whether table AM uses transaction identifiers.
A custom table AM can use a custom slot, which may not store xmin directly,
but determine the tuple belonging to the current transaction in the other way.

Discussion: https://postgr.es/m/CAPpHfdurb9ycV8udYqM%3Do0sPS66PJ4RCBM1g-bBpvzUfogY0EA%40mail.gmail.com
Reviewed-by: Matthias van de Meent, Mark Dilger, Pavel Borisov
Reviewed-by: Nikita Malakhov, Japin Li
src/backend/executor/execTuples.c
src/backend/utils/adt/ri_triggers.c
src/include/executor/tuptable.h

index a7aa2ee02b1ed61cac26f9e5c8f70f6dad9d38bb..7a7c78604167f79445562e9bd466039ec9fdd4e2 100644 (file)
@@ -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,
index 2fe937750034d8c2899e30f95ce04ca6a8b683f1..62601a6d80c7be2fe44847f042bb8d2cd943f39b 100644 (file)
@@ -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 */
index 6133dbcd0a36c59a2f41a3c3644f75f4f6d94607..b82655e7e55519ae7dc7220ab571de6766c8a153 100644 (file)
@@ -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
  */