agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
table AM option passing
17+ messages / 6 participants
[nested] [flat]

* table AM option passing
@ 2026-03-17 16:50  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 3 replies; 17+ messages in thread

From: Álvaro Herrera @ 2026-03-17 16:50 UTC (permalink / raw)
  To: Pg Hackers <pgsql-hackers@lists.postgresql.org>

Hello,

In the table AM world, we provide table_tuple_insert() that gets some
behavior-changing options via a bitmask; and then we have
table_tuple_update and table_tuple_delete which take a couple of
booleans for the same purpose.  To me it seems that it would make sense
to make these things consistent.

Now I don't want consistency just for the sake of it.  The issue is that
the REPACK CONCURRENTLY patch wants to add one more option to those two
routines.  (In reality, the repack patch as posted doesn't do that
because it deals with heapam.c routines directly instead of going
through table-AM, so there was no need to touch tableam.h; but of course
that's not a good way to implement it, because then you can't repack
tables that aren't heapam-based.  So that patch would get more invasive
as we add those bool options everywhere.)

We could solve this easily by adding one more boolean to each, but I
think this is a good time to instead consolidate the API by using a
bitmask; this also allows us not to have changingPart in the wrong place
of the heap_delete API.

So here's a patch to do that, which shouldn't change any behavior.

(This change is vaguely similar to b7271aa1d71a, except I used 'int'
instead of 'bits32', to keep the interface consistent with the existing
heap_insert() one.  Maybe I should make all three take bits64 instead?
We don't actually have that type at present, so I'd have to add that
too.)


While at it, I noticed that the table_insert() and heap_insert() uses
one set of value definitions for each half of the interface; that is, in
tableam.h we have

/* "options" flag bits for table_tuple_insert */
/* TABLE_INSERT_SKIP_WAL was 0x0001; RelationNeedsWAL() now governs */
#define TABLE_INSERT_SKIP_FSM		0x0002
#define TABLE_INSERT_FROZEN		0x0004
#define TABLE_INSERT_NO_LOGICAL		0x0008

and in heapam.h we have
/* "options" flag bits for heap_insert */
#define HEAP_INSERT_SKIP_FSM		TABLE_INSERT_SKIP_FSM
#define HEAP_INSERT_FROZEN		TABLE_INSERT_FROZEN
#define HEAP_INSERT_NO_LOGICAL		TABLE_INSERT_NO_LOGICAL
#define HEAP_INSERT_SPECULATIVE 	0x0010

This seems rather odd to me -- how could heapam.c have a different set
of behaviors than what table AM uses?  I find it even more weird that
HEAP_INSERT_SPECULATIVE is defined so that as soon as some future patch
defines the next "free" tableam.h flag value to do something new, we'll
have a conflict.  I think this would be cleaner if we removed from
heapam.h the flags that correspond to anything in tableam.h, and use
heapam.c and all its direct callers use the tableam.h flag definitions
instead; and perhaps move HEAP_INSERT_SPECULATIVE to be at the other end
of the bitmask (0x1000) -- maybe simply say in tableam.h that the first
byte of the options int is reserved for internal use.

Anyway, this is the reason I only defined these flags in tableam.h and
nothing appears in heapam.h about it.  They're just something heapam.c
is forced to know about.

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
Al principio era UNIX, y UNIX habló y dijo: "Hello world\n".
No dijo "Hello New Jersey\n", ni "Hello USA\n".

Attachments:

  [text/x-diff] v1-0001-table_tuple_update-_delete-use-an-options-bitmask.patch (11.7K, ../../202603171606.kf6pmhscqbqz@alvherre.pgsql/2-v1-0001-table_tuple_update-_delete-use-an-options-bitmask.patch)
  download | inline diff:
From 794f655f33bb89d979a9948810fdd4800a8241ff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Tue, 17 Mar 2026 17:21:14 +0100
Subject: [PATCH v1] table_tuple_update/_delete(): use an options bitmask

This replaces a couple of booleans, making the interface more similar to
what table_tuple_insert() uses, and better suited for future expansion.

Discussion: https://postgr.es/m/202603171606.kf6pmhscqbqz@alvherre.pgsql
---
 src/backend/access/heap/heapam.c         | 15 +++++----
 src/backend/access/heap/heapam_handler.c | 10 +++---
 src/backend/access/table/tableam.c       |  3 +-
 src/backend/executor/nodeModifyTable.c   | 11 ++++---
 src/include/access/heapam.h              |  6 ++--
 src/include/access/tableam.h             | 40 ++++++++++++++++--------
 6 files changed, 52 insertions(+), 33 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index e5bd062de77..1cf74ed8c46 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2852,8 +2852,8 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
  */
 TM_Result
 heap_delete(Relation relation, const ItemPointerData *tid,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, bool changingPart)
+			CommandId cid, Snapshot crosscheck, int options,
+			TM_FailureData *tmfd)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
@@ -2863,6 +2863,8 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 	BlockNumber block;
 	Buffer		buffer;
 	Buffer		vmbuffer = InvalidBuffer;
+	bool		wait = (options & TABLE_DELETE_WAIT) != 0;
+	bool		changingPart = (options & TABLE_DELETE_CHANGING_PART) != 0;
 	TransactionId new_xmax;
 	uint16		new_infomask,
 				new_infomask2;
@@ -3281,8 +3283,8 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
 
 	result = heap_delete(relation, tid,
 						 GetCurrentCommandId(true), InvalidSnapshot,
-						 true /* wait for commit */ ,
-						 &tmfd, false /* changingPart */ );
+						 TABLE_DELETE_WAIT,
+						 &tmfd);
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -3321,7 +3323,7 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  */
 TM_Result
 heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
-			CommandId cid, Snapshot crosscheck, bool wait,
+			CommandId cid, Snapshot crosscheck, int options,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
 			TU_UpdateIndexes *update_indexes)
 {
@@ -3340,6 +3342,7 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 	bool		old_key_copied = false;
 	Page		page,
 				newpage;
+	bool		wait = (options & TABLE_UPDATE_WAIT) != 0;
 	BlockNumber block;
 	MultiXactStatus mxact_status;
 	Buffer		buffer,
@@ -4576,7 +4579,7 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 
 	result = heap_update(relation, otid, tup,
 						 GetCurrentCommandId(true), InvalidSnapshot,
-						 true /* wait for commit */ ,
+						 TABLE_UPDATE_WAIT,
 						 &tmfd, &lockmode, update_indexes);
 	switch (result)
 	{
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 253a735b6c1..3703d2d18a6 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -310,22 +310,22 @@ heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
 
 static TM_Result
 heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
-					Snapshot snapshot, Snapshot crosscheck, bool wait,
-					TM_FailureData *tmfd, bool changingPart)
+					Snapshot snapshot, Snapshot crosscheck, int options,
+					TM_FailureData *tmfd)
 {
 	/*
 	 * Currently Deleting of index tuples are handled at vacuum, in case if
 	 * the storage itself is cleaning the dead tuples by itself, it is the
 	 * time to call the index tuple deletion also.
 	 */
-	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
+	return heap_delete(relation, tid, cid, crosscheck, options, tmfd);
 }
 
 
 static TM_Result
 heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 					CommandId cid, Snapshot snapshot, Snapshot crosscheck,
-					bool wait, TM_FailureData *tmfd,
+					int options, TM_FailureData *tmfd,
 					LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes)
 {
 	bool		shouldFree = true;
@@ -336,7 +336,7 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	slot->tts_tableOid = RelationGetRelid(relation);
 	tuple->t_tableOid = slot->tts_tableOid;
 
-	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
+	result = heap_update(relation, otid, tuple, cid, crosscheck, options,
 						 tmfd, lockmode, update_indexes);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index dfda1af412e..4c759f29b3e 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -319,8 +319,7 @@ simple_table_tuple_delete(Relation rel, ItemPointer tid, Snapshot snapshot)
 	result = table_tuple_delete(rel, tid,
 								GetCurrentCommandId(true),
 								snapshot, InvalidSnapshot,
-								true /* wait for commit */ ,
-								&tmfd, false /* changingPart */ );
+								TABLE_DELETE_WAIT, &tmfd);
 
 	switch (result)
 	{
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 4cd5e262e0f..680c29f35d5 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1522,14 +1522,17 @@ ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 			  ItemPointer tupleid, bool changingPart)
 {
 	EState	   *estate = context->estate;
+	int			options = TABLE_DELETE_WAIT;
+
+	if (changingPart)
+		options |= TABLE_DELETE_CHANGING_PART;
 
 	return table_tuple_delete(resultRelInfo->ri_RelationDesc, tupleid,
 							  estate->es_output_cid,
 							  estate->es_snapshot,
 							  estate->es_crosscheck_snapshot,
-							  true /* wait for commit */ ,
-							  &context->tmfd,
-							  changingPart);
+							  options,
+							  &context->tmfd);
 }
 
 /*
@@ -2333,7 +2336,7 @@ lreplace:
 								estate->es_output_cid,
 								estate->es_snapshot,
 								estate->es_crosscheck_snapshot,
-								true /* wait for commit */ ,
+								TABLE_UPDATE_WAIT,
 								&context->tmfd, &updateCxt->lockmode,
 								&updateCxt->updateIndexes);
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 2fdc50b865b..f74ba9817a7 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -385,13 +385,13 @@ extern void heap_multi_insert(Relation relation, TupleTableSlot **slots,
 							  int ntuples, CommandId cid, int options,
 							  BulkInsertState bistate);
 extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
-							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, bool changingPart);
+							 CommandId cid, Snapshot crosscheck, int options,
+							 TM_FailureData *tmfd);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
 extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
 							 HeapTuple newtup,
-							 CommandId cid, Snapshot crosscheck, bool wait,
+							 CommandId cid, Snapshot crosscheck, int options,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode,
 							 TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 06084752245..15f6d6a3436 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -260,6 +260,13 @@ typedef struct TM_IndexDeleteOp
 #define TABLE_INSERT_FROZEN			0x0004
 #define TABLE_INSERT_NO_LOGICAL		0x0008
 
+/* "options" flag bits for table_tuple_update */
+#define TABLE_UPDATE_WAIT			0x0001
+
+/* "options" flag bits for table_tuple_delete */
+#define TABLE_DELETE_WAIT			0x0001
+#define TABLE_DELETE_CHANGING_PART	0x0002
+
 /* flag bits for table_tuple_lock */
 /* Follow tuples whose update is in progress if lock modes don't conflict  */
 #define TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS	(1 << 0)
@@ -535,9 +542,8 @@ typedef struct TableAmRoutine
 								 CommandId cid,
 								 Snapshot snapshot,
 								 Snapshot crosscheck,
-								 bool wait,
-								 TM_FailureData *tmfd,
-								 bool changingPart);
+								 int options,
+								 TM_FailureData *tmfd);
 
 	/* see table_tuple_update() for reference about parameters */
 	TM_Result	(*tuple_update) (Relation rel,
@@ -546,7 +552,7 @@ typedef struct TableAmRoutine
 								 CommandId cid,
 								 Snapshot snapshot,
 								 Snapshot crosscheck,
-								 bool wait,
+								 int options,
 								 TM_FailureData *tmfd,
 								 LockTupleMode *lockmode,
 								 TU_UpdateIndexes *update_indexes);
@@ -1459,9 +1465,14 @@ table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots,
  *	cid - delete command ID (used for visibility test, and stored into
  *		cmax if successful)
  *	crosscheck - if not InvalidSnapshot, also check tuple against this
- *	wait - true if should wait for any conflicting update to commit/abort
- *	changingPart - true iff the tuple is being moved to another partition
- *		table due to an update of the partition key. Otherwise, false.
+ *	options - These allow the caller to specify options that may change
+ *	the behavior of the AM.  The AM will ignore options that it does not
+ *	support.
+ *		TABLE_DELETE_WAIT -- set if should wait for any conflicting
+ *		update/delete to commit/abort
+ *		TABLE_DELETE_CHANGING_PART -- set iff the tuple is being moved to
+ *		another partition table due to an update of the partition key.
+ *		Otherwise, false.
  *
  * Output parameters:
  *	tmfd - filled in failure cases (see below)
@@ -1476,12 +1487,12 @@ table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots,
  */
 static inline TM_Result
 table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
-				   Snapshot snapshot, Snapshot crosscheck, bool wait,
-				   TM_FailureData *tmfd, bool changingPart)
+				   Snapshot snapshot, Snapshot crosscheck, int options,
+				   TM_FailureData *tmfd)
 {
 	return rel->rd_tableam->tuple_delete(rel, tid, cid,
 										 snapshot, crosscheck,
-										 wait, tmfd, changingPart);
+										 options, tmfd);
 }
 
 /*
@@ -1496,7 +1507,10 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
  *	cid - update command ID (used for visibility test, and stored into
  *		cmax/cmin if successful)
  *	crosscheck - if not InvalidSnapshot, also check old tuple against this
- *	wait - true if should wait for any conflicting update to commit/abort
+ *	options - These allow the caller to specify options that may change the
+ *	behavior of the AM. The AM will ignore options that it does not support.
+ *		TABLE_UPDATE_WAIT -- set if should wait for any conflicting update to
+ *		commit/abort
  *
  * Output parameters:
  *	slot - newly constructed tuple data to store
@@ -1522,12 +1536,12 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
 static inline TM_Result
 table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot,
 				   CommandId cid, Snapshot snapshot, Snapshot crosscheck,
-				   bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
+				   int options, TM_FailureData *tmfd, LockTupleMode *lockmode,
 				   TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
 										 cid, snapshot, crosscheck,
-										 wait, tmfd,
+										 options, tmfd,
 										 lockmode, update_indexes);
 }
 
-- 
2.47.3

^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-03-17 17:31  Nathan Bossart <nathandbossart@gmail.com>
  parent: Álvaro Herrera <alvherre@kurilemu.de>
  2 siblings, 1 reply; 17+ messages in thread

From: Nathan Bossart @ 2026-03-17 17:31 UTC (permalink / raw)
  To: Álvaro Herrera <alvherre@kurilemu.de>; +Cc: Pg Hackers <pgsql-hackers@lists.postgresql.org>

On Tue, Mar 17, 2026 at 05:50:41PM +0100, Álvaro Herrera wrote:
> We could solve this easily by adding one more boolean to each, but I
> think this is a good time to instead consolidate the API by using a
> bitmask; this also allows us not to have changingPart in the wrong place
> of the heap_delete API.
> 
> So here's a patch to do that, which shouldn't change any behavior.

Seems entirely reasonable to me.  I read through the patch and nothing
stood out.

> (This change is vaguely similar to b7271aa1d71a, except I used 'int'
> instead of 'bits32', to keep the interface consistent with the existing
> heap_insert() one.  Maybe I should make all three take bits64 instead?
> We don't actually have that type at present, so I'd have to add that
> too.)

Why bits64 and not bits32?  I must be missing something.

> While at it, I noticed that the table_insert() and heap_insert() uses
> one set of value definitions for each half of the interface; that is, in
> tableam.h we have
> 
> /* "options" flag bits for table_tuple_insert */
> /* TABLE_INSERT_SKIP_WAL was 0x0001; RelationNeedsWAL() now governs */
> #define TABLE_INSERT_SKIP_FSM		0x0002
> #define TABLE_INSERT_FROZEN		0x0004
> #define TABLE_INSERT_NO_LOGICAL		0x0008
> 
> and in heapam.h we have
> /* "options" flag bits for heap_insert */
> #define HEAP_INSERT_SKIP_FSM		TABLE_INSERT_SKIP_FSM
> #define HEAP_INSERT_FROZEN		TABLE_INSERT_FROZEN
> #define HEAP_INSERT_NO_LOGICAL		TABLE_INSERT_NO_LOGICAL
> #define HEAP_INSERT_SPECULATIVE 	0x0010
> 
> This seems rather odd to me -- how could heapam.c have a different set
> of behaviors than what table AM uses?  I find it even more weird that
> HEAP_INSERT_SPECULATIVE is defined so that as soon as some future patch
> defines the next "free" tableam.h flag value to do something new, we'll
> have a conflict.  I think this would be cleaner if we removed from
> heapam.h the flags that correspond to anything in tableam.h, and use
> heapam.c and all its direct callers use the tableam.h flag definitions
> instead; and perhaps move HEAP_INSERT_SPECULATIVE to be at the other end
> of the bitmask (0x1000) -- maybe simply say in tableam.h that the first
> byte of the options int is reserved for internal use.

Probably a good idea.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-03-17 19:47  Álvaro Herrera <alvherre@kurilemu.de>
  parent: Nathan Bossart <nathandbossart@gmail.com>
  0 siblings, 2 replies; 17+ messages in thread

From: Álvaro Herrera @ 2026-03-17 19:47 UTC (permalink / raw)
  To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: Pg Hackers <pgsql-hackers@lists.postgresql.org>

Hi Nathan, thanks for looking,

On 2026-Mar-17, Nathan Bossart wrote:

> On Tue, Mar 17, 2026 at 05:50:41PM +0100, Álvaro Herrera wrote:

> > (This change is vaguely similar to b7271aa1d71a, except I used 'int'
> > instead of 'bits32', to keep the interface consistent with the existing
> > heap_insert() one.  Maybe I should make all three take bits64 instead?
> > We don't actually have that type at present, so I'd have to add that
> > too.)
> 
> Why bits64 and not bits32?  I must be missing something.

augh, that's just a thinko -- yeah, we could use bits32 here and that
wouldn't represent a reduction in number of possible flags.

Does anybody oppose changing table_tuple_insert() to use bits32 instead
of integer for the 'options' argument?

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/
"I dream about dreams about dreams", sang the nightingale
under the pale moon (Sandman)





^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-03-17 20:04  Nathan Bossart <nathandbossart@gmail.com>
  parent: Álvaro Herrera <alvherre@kurilemu.de>
  1 sibling, 0 replies; 17+ messages in thread

From: Nathan Bossart @ 2026-03-17 20:04 UTC (permalink / raw)
  To: Álvaro Herrera <alvherre@kurilemu.de>; +Cc: Pg Hackers <pgsql-hackers@lists.postgresql.org>

On Tue, Mar 17, 2026 at 08:47:22PM +0100, Álvaro Herrera wrote:
> Does anybody oppose changing table_tuple_insert() to use bits32 instead
> of integer for the 'options' argument?

No objections here.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-03-17 21:01  Andres Freund <andres@anarazel.de>
  parent: Álvaro Herrera <alvherre@kurilemu.de>
  2 siblings, 1 reply; 17+ messages in thread

From: Andres Freund @ 2026-03-17 21:01 UTC (permalink / raw)
  To: Álvaro Herrera <alvherre@kurilemu.de>; +Cc: Pg Hackers <pgsql-hackers@lists.postgresql.org>

Hi,

On 2026-03-17 17:50:41 +0100, Álvaro Herrera wrote:
> In the table AM world, we provide table_tuple_insert() that gets some
> behavior-changing options via a bitmask; and then we have
> table_tuple_update and table_tuple_delete which take a couple of
> booleans for the same purpose.  To me it seems that it would make sense
> to make these things consistent.

I'm not sure these cases are *entirely* comparable. The boolean argument for
table_tuple_delete()/table_tuple_update() is whether to wait, which doesn't
influence the on-disk layout, whereas TABLE_INSERT_* do influence the disk
layout.


> While at it, I noticed that the table_insert() and heap_insert() uses
> one set of value definitions for each half of the interface; that is, in
> tableam.h we have
>
> /* "options" flag bits for table_tuple_insert */
> /* TABLE_INSERT_SKIP_WAL was 0x0001; RelationNeedsWAL() now governs */
> #define TABLE_INSERT_SKIP_FSM		0x0002
> #define TABLE_INSERT_FROZEN		0x0004
> #define TABLE_INSERT_NO_LOGICAL		0x0008
>
> and in heapam.h we have
> /* "options" flag bits for heap_insert */
> #define HEAP_INSERT_SKIP_FSM		TABLE_INSERT_SKIP_FSM
> #define HEAP_INSERT_FROZEN		TABLE_INSERT_FROZEN
> #define HEAP_INSERT_NO_LOGICAL		TABLE_INSERT_NO_LOGICAL
> #define HEAP_INSERT_SPECULATIVE 	0x0010
>
> This seems rather odd to me -- how could heapam.c have a different set
> of behaviors than what table AM uses?

I am not sure I understand what you mean by that. Just that the flags better
always have the same values?

I think the background for the HEAP_* ones to exist is just that there were
(probably are) direct callers to heap_insert() and it seemed a bit odd to
refer to the generic flags and that there was a need for a heap specific
private flag.


> I find it even more weird that HEAP_INSERT_SPECULATIVE is defined so that as
> soon as some future patch defines the next "free" tableam.h flag value to do
> something new, we'll have a conflict.  I think this would be cleaner if we
> removed from heapam.h the flags that correspond to anything in tableam.h,
> and use heapam.c and all its direct callers use the tableam.h flag
> definitions instead; and perhaps move HEAP_INSERT_SPECULATIVE to be at the
> other end of the bitmask (0x1000) -- maybe simply say in tableam.h that the
> first byte of the options int is reserved for internal use.

I'm ok with both of those.



> From 794f655f33bb89d979a9948810fdd4800a8241ff Mon Sep 17 00:00:00 2001
> From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
> Date: Tue, 17 Mar 2026 17:21:14 +0100
> Subject: [PATCH v1] table_tuple_update/_delete(): use an options bitmask
>
> This replaces a couple of booleans, making the interface more similar to
> what table_tuple_insert() uses, and better suited for future expansion.
>
> Discussion: https://postgr.es/m/202603171606.kf6pmhscqbqz@alvherre.pgsql
> ---
>  src/backend/access/heap/heapam.c         | 15 +++++----
>  src/backend/access/heap/heapam_handler.c | 10 +++---
>  src/backend/access/table/tableam.c       |  3 +-
>  src/backend/executor/nodeModifyTable.c   | 11 ++++---
>  src/include/access/heapam.h              |  6 ++--
>  src/include/access/tableam.h             | 40 ++++++++++++++++--------
>  6 files changed, 52 insertions(+), 33 deletions(-)
>
> diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
> index e5bd062de77..1cf74ed8c46 100644
> --- a/src/backend/access/heap/heapam.c
> +++ b/src/backend/access/heap/heapam.c
> @@ -2852,8 +2852,8 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
>   */
>  TM_Result
>  heap_delete(Relation relation, const ItemPointerData *tid,
> -			CommandId cid, Snapshot crosscheck, bool wait,
> -			TM_FailureData *tmfd, bool changingPart)
> +			CommandId cid, Snapshot crosscheck, int options,
> +			TM_FailureData *tmfd)

If we introduce new flag things, we should make them unsigned imo. It's a bad
habit that we don't do that everywhere.  I've spent a fair bit of time finding
bugs due to that in the past (e.g. 2a2e1b470b9).



Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-03-17 21:09  Andres Freund <andres@anarazel.de>
  parent: Álvaro Herrera <alvherre@kurilemu.de>
  1 sibling, 1 reply; 17+ messages in thread

From: Andres Freund @ 2026-03-17 21:09 UTC (permalink / raw)
  To: Álvaro Herrera <alvherre@kurilemu.de>; +Cc: Nathan Bossart <nathandbossart@gmail.com>; Pg Hackers <pgsql-hackers@lists.postgresql.org>

Hi,

On 2026-03-17 20:47:22 +0100, Álvaro Herrera wrote:
> On 2026-Mar-17, Nathan Bossart wrote:
> 
> > On Tue, Mar 17, 2026 at 05:50:41PM +0100, Álvaro Herrera wrote:
> 
> > > (This change is vaguely similar to b7271aa1d71a, except I used 'int'
> > > instead of 'bits32', to keep the interface consistent with the existing
> > > heap_insert() one.  Maybe I should make all three take bits64 instead?
> > > We don't actually have that type at present, so I'd have to add that
> > > too.)
> > 
> > Why bits64 and not bits32?  I must be missing something.
> 
> augh, that's just a thinko -- yeah, we could use bits32 here and that
> wouldn't represent a reduction in number of possible flags.
> 
> Does anybody oppose changing table_tuple_insert() to use bits32 instead
> of integer for the 'options' argument?

Personally I object to the existence of the bits* types, to me they're just
noise over using the corresponding unsigned integer types. One more thing that
one has to just know what it means without there being any actual improved
type checking or such. It's not like using bits* would make it any easier to
make the underlying type a struct or such (which is different to
e.g. TransactionId, we could probably replace that with a struct without crazy
amounts of trouble).

I think we should just rip the bits* types out and replace them with the
underlying types.

...

I do however think we should make the table_tuple_insert options argument
unsigned though.  So I guess I might have to swallow the bitter pill of the
bits* type.

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-03-18 20:20  Zsolt Parragi <zsolt.parragi@percona.com>
  parent: Álvaro Herrera <alvherre@kurilemu.de>
  2 siblings, 0 replies; 17+ messages in thread

From: Zsolt Parragi @ 2026-03-18 20:20 UTC (permalink / raw)
  To: Álvaro Herrera <alvherre@kurilemu.de>; +Cc: Pg Hackers <pgsql-hackers@lists.postgresql.org>

Hello!

I think there's a change missing in simple_table_tuple_update that
works by accident, as true == 1 == TABLE_UPDATE_WAIT.

Maybe the values could use a different starting value instead of 1 to
surface possible issues?

+ * TABLE_DELETE_WAIT -- set if should wait for any conflicting
+ * update/delete to commit/abort
+ * TABLE_DELETE_CHANGING_PART -- set iff the tuple is being moved to
+ * another partition table due to an update of the partition key.
+ * Otherwise, false.

"Otherwise, false" seems like a leftover from the previous comment version?

tableam.h also have two leftover "wait == false" comments.





^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-03-18 21:17  Nathan Bossart <nathandbossart@gmail.com>
  parent: Andres Freund <andres@anarazel.de>
  0 siblings, 0 replies; 17+ messages in thread

From: Nathan Bossart @ 2026-03-18 21:17 UTC (permalink / raw)
  To: Andres Freund <andres@anarazel.de>; +Cc: Álvaro Herrera <alvherre@kurilemu.de>; Pg Hackers <pgsql-hackers@lists.postgresql.org>

On Tue, Mar 17, 2026 at 05:09:49PM -0400, Andres Freund wrote:
> Personally I object to the existence of the bits* types, to me they're just
> noise over using the corresponding unsigned integer types. One more thing that
> one has to just know what it means without there being any actual improved
> type checking or such. It's not like using bits* would make it any easier to
> make the underlying type a struct or such (which is different to
> e.g. TransactionId, we could probably replace that with a struct without crazy
> amounts of trouble).

Yeah, I don't see why you'd prefer bits32 over uint32.  If anything, uint32
is probably less confusing because most hackers will have used it before.
AFAICT the bits* types are a relic of the 80s, and there used to be other
types like bool8 and word32, all of which were just uint* behind the
scenes.  Those were removed in 2004 by commit ca7a1f0c86.  I assume bits*
was left behind because it was still in use.

> I think we should just rip the bits* types out and replace them with the
> underlying types.

+1.  If there seems to be consensus, I'm happy to write the patch.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-03-29 21:38  Álvaro Herrera <alvherre@kurilemu.de>
  parent: Andres Freund <andres@anarazel.de>
  0 siblings, 1 reply; 17+ messages in thread

From: Álvaro Herrera @ 2026-03-29 21:38 UTC (permalink / raw)
  To: Andres Freund <andres@anarazel.de>; +Cc: Pg Hackers <pgsql-hackers@lists.postgresql.org>; Zsolt Parragi <zsolt.parragi@percona.com>; Nathan Bossart <nathandbossart@gmail.com>

Hello,

Thanks Zsolt for the notes -- I have fixed these.

On 2026-Mar-17, Andres Freund wrote:

> On 2026-03-17 17:50:41 +0100, Álvaro Herrera wrote:
> > In the table AM world, we provide table_tuple_insert() that gets some
> > behavior-changing options via a bitmask; and then we have
> > table_tuple_update and table_tuple_delete which take a couple of
> > booleans for the same purpose.  To me it seems that it would make sense
> > to make these things consistent.
> 
> I'm not sure these cases are *entirely* comparable. The boolean argument for
> table_tuple_delete()/table_tuple_update() is whether to wait, which doesn't
> influence the on-disk layout, whereas TABLE_INSERT_* do influence the disk
> layout.

True -- I agree that 'wait' is not "the same kind" of option, makes
sense that it would be separate from options that affect how the tuple
is stored.  tuple_delete's changingPart however looks like it should be
part of a bitmask of options.

The repack patch wants to add further options to both tuple_update and
tuple_delete, namely NO_LOGICAL which pretty much mirrors what
TABLE_INSERT_NO_LOGICAL does.

So the attached 0001 adds a 'bits32 options' argument to both
table_update and table_delete, and makes TABLE_DELETE_CHANGING_PARTITION
the first option for table_delete to replace the 'bool changingPart'.
There are no options for table_update() at present, but it seems
inconsistent to not have an argument to it, so I added one that for now
remains unused.  The first such option is going to be added by repack.


> > While at it, I noticed that the table_insert() and heap_insert() uses
> > one set of value definitions for each half of the interface; [...]

> I am not sure I understand what you mean by that. Just that the flags better
> always have the same values?
> 
> I think the background for the HEAP_* ones to exist is just that there were
> (probably are) direct callers to heap_insert() and it seemed a bit odd to
> refer to the generic flags and that there was a need for a heap specific
> private flag.

Yeah, it makes sense -- I mean, it's not wrong.  But I think it's a bit
awkward.  In a way, it's as if heapam.c continues to live in a world
where tableam.h could one day be removed, so it (heapam.h) needs to
redefine its interface in an agnostic way.  But I think that's somewhat
delusional, and there's no value in keeping this separation.  My
proposal is simply that heapam.c can be made to work under the tableam.h
names of those flag bits, and no abstraction is being broken.  0002 does
this, as well as move HEAP_INSERT_SPECULATIVE be the highest bit rather
than immediately follow the TABLE_INSERT_* ones.

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
Essentially, you're proposing Kevlar shoes as a solution for the problem
that you want to walk around carrying a loaded gun aimed at your foot.
(Tom Lane)

Attachments:

  [text/x-diff] v2-0001-Make-table_update-table_delete-take-bitmask-optio.patch (16.2K, ../../202603291633.dkzj7sp3b3lq@alvherre.pgsql/2-v2-0001-Make-table_update-table_delete-take-bitmask-optio.patch)
  download | inline diff:
From 3a05de90cc4b4fc6ea6a600b71056cbe8a84a57e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Sun, 29 Mar 2026 22:57:27 +0200
Subject: [PATCH v2 1/2] Make table_update/table_delete take bitmask options

Right now, table_update doesn't recognize any; but table_delete has an
option TABLE_DELETE_CHANGING_PARTITION to replace the boolean
changingPart argument it previously had.

The reason for this is to more cleanly introduce further options -- in
particular, the REPACK patch wants to add a NO_LOGICAL option to both
these functions, to match the one that table_insert already has.

While at it, change the 'int options' argument of the insert side to
'bits32', which is unsigned, per Andres.  XXX this could perhaps be a
separate commit.
---
 src/backend/access/heap/heapam.c         | 24 +++++++------
 src/backend/access/heap/heapam_handler.c | 17 +++++----
 src/backend/access/table/tableam.c       |  6 ++--
 src/backend/executor/nodeModifyTable.c   |  9 +++--
 src/include/access/heapam.h              | 11 +++---
 src/include/access/tableam.h             | 44 +++++++++++++++---------
 6 files changed, 67 insertions(+), 44 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index eb1f67f31cd..1b873f55c70 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -57,7 +57,7 @@
 
 
 static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
-									 TransactionId xid, CommandId cid, int options);
+									 TransactionId xid, CommandId cid, bits32 options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 								  Buffer newbuf, HeapTuple oldtup,
 								  HeapTuple newtup, HeapTuple old_key_tuple,
@@ -2148,7 +2148,7 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate)
  */
 void
 heap_insert(Relation relation, HeapTuple tup, CommandId cid,
-			int options, BulkInsertState bistate)
+			bits32 options, BulkInsertState bistate)
 {
 	TransactionId xid = GetCurrentTransactionId();
 	HeapTuple	heaptup;
@@ -2339,7 +2339,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
  */
 static HeapTuple
 heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
-					CommandId cid, int options)
+					CommandId cid, bits32 options)
 {
 	/*
 	 * To allow parallel inserts, we need to ensure that they are safe to be
@@ -2419,7 +2419,7 @@ heap_multi_insert_pages(HeapTuple *heaptuples, int done, int ntuples, Size saveF
  */
 void
 heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate)
+				  CommandId cid, bits32 options, BulkInsertState bistate)
 {
 	TransactionId xid = GetCurrentTransactionId();
 	HeapTuple  *heaptuples;
@@ -2850,8 +2850,8 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
  */
 TM_Result
 heap_delete(Relation relation, const ItemPointerData *tid,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, bool changingPart)
+			CommandId cid, bits32 options, Snapshot crosscheck,
+			bool wait, TM_FailureData *tmfd)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
@@ -2864,6 +2864,7 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 	TransactionId new_xmax;
 	uint16		new_infomask,
 				new_infomask2;
+	bool		changingPart = (options & TABLE_DELETE_CHANGING_PARTITION) != 0;
 	bool		have_tuple_lock = false;
 	bool		iscombo;
 	bool		all_visible_cleared = false;
@@ -3278,9 +3279,11 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
 	TM_FailureData tmfd;
 
 	result = heap_delete(relation, tid,
-						 GetCurrentCommandId(true), InvalidSnapshot,
+						 GetCurrentCommandId(true),
+						 0,
+						 InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &tmfd, false /* changingPart */ );
+						 &tmfd);
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -3319,7 +3322,7 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  */
 TM_Result
 heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
-			CommandId cid, Snapshot crosscheck, bool wait,
+			CommandId cid, bits32 options, Snapshot crosscheck, bool wait,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
 			TU_UpdateIndexes *update_indexes)
 {
@@ -4573,7 +4576,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 	LockTupleMode lockmode;
 
 	result = heap_update(relation, otid, tup,
-						 GetCurrentCommandId(true), InvalidSnapshot,
+						 GetCurrentCommandId(true), 0,
+						 InvalidSnapshot,
 						 true /* wait for commit */ ,
 						 &tmfd, &lockmode, update_indexes);
 	switch (result)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index d40878928e1..dd03c8c29a6 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -252,7 +252,7 @@ heapam_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot,
 
 static void
 heapam_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
-					int options, BulkInsertState bistate)
+					bits32 options, BulkInsertState bistate)
 {
 	bool		shouldFree = true;
 	HeapTuple	tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
@@ -271,7 +271,7 @@ heapam_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
 
 static void
 heapam_tuple_insert_speculative(Relation relation, TupleTableSlot *slot,
-								CommandId cid, int options,
+								CommandId cid, bits32 options,
 								BulkInsertState bistate, uint32 specToken)
 {
 	bool		shouldFree = true;
@@ -311,21 +311,23 @@ heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
 
 static TM_Result
 heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
-					Snapshot snapshot, Snapshot crosscheck, bool wait,
-					TM_FailureData *tmfd, bool changingPart)
+					bits32 options, Snapshot snapshot, Snapshot crosscheck,
+					bool wait, TM_FailureData *tmfd)
 {
 	/*
 	 * Currently Deleting of index tuples are handled at vacuum, in case if
 	 * the storage itself is cleaning the dead tuples by itself, it is the
 	 * time to call the index tuple deletion also.
 	 */
-	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
+	return heap_delete(relation, tid, cid, options, crosscheck, wait,
+					   tmfd);
 }
 
 
 static TM_Result
 heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
-					CommandId cid, Snapshot snapshot, Snapshot crosscheck,
+					CommandId cid, bits32 options pg_attribute_unused(),
+					Snapshot snapshot, Snapshot crosscheck,
 					bool wait, TM_FailureData *tmfd,
 					LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes)
 {
@@ -337,7 +339,8 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	slot->tts_tableOid = RelationGetRelid(relation);
 	tuple->t_tableOid = slot->tts_tableOid;
 
-	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
+	result = heap_update(relation, otid, tuple, cid, options,
+						 crosscheck, wait,
 						 tmfd, lockmode, update_indexes);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index dfda1af412e..50206ae782f 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -318,9 +318,9 @@ simple_table_tuple_delete(Relation rel, ItemPointer tid, Snapshot snapshot)
 
 	result = table_tuple_delete(rel, tid,
 								GetCurrentCommandId(true),
-								snapshot, InvalidSnapshot,
+								0, snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, false /* changingPart */ );
+								&tmfd);
 
 	switch (result)
 	{
@@ -367,7 +367,7 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 
 	result = table_tuple_update(rel, otid, slot,
 								GetCurrentCommandId(true),
-								snapshot, InvalidSnapshot,
+								0, snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
 								&tmfd, &lockmode, update_indexes);
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 4cd5e262e0f..9f763f756b0 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1522,14 +1522,18 @@ ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 			  ItemPointer tupleid, bool changingPart)
 {
 	EState	   *estate = context->estate;
+	bits32		options = 0;
+
+	if (changingPart)
+		options |= TABLE_DELETE_CHANGING_PARTITION;
 
 	return table_tuple_delete(resultRelInfo->ri_RelationDesc, tupleid,
 							  estate->es_output_cid,
+							  options,
 							  estate->es_snapshot,
 							  estate->es_crosscheck_snapshot,
 							  true /* wait for commit */ ,
-							  &context->tmfd,
-							  changingPart);
+							  &context->tmfd);
 }
 
 /*
@@ -2331,6 +2335,7 @@ lreplace:
 	 */
 	result = table_tuple_update(resultRelationDesc, tupleid, slot,
 								estate->es_output_cid,
+								0,
 								estate->es_snapshot,
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 9b403203006..80bcda02cb7 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -379,18 +379,19 @@ extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
 extern void heap_insert(Relation relation, HeapTuple tup, CommandId cid,
-						int options, BulkInsertState bistate);
+						bits32 options, BulkInsertState bistate);
 extern void heap_multi_insert(Relation relation, TupleTableSlot **slots,
-							  int ntuples, CommandId cid, int options,
+							  int ntuples, CommandId cid, bits32 options,
 							  BulkInsertState bistate);
 extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
-							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, bool changingPart);
+							 CommandId cid, bits32 options, Snapshot crosscheck,
+							 bool wait, TM_FailureData *tmfd);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
 extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
 							 HeapTuple newtup,
-							 CommandId cid, Snapshot crosscheck, bool wait,
+							 CommandId cid, bits32 options,
+							 Snapshot crosscheck, bool wait,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode,
 							 TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 06084752245..3c82b80d45d 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -254,7 +254,10 @@ typedef struct TM_IndexDeleteOp
 	TM_IndexStatus *status;
 } TM_IndexDeleteOp;
 
-/* "options" flag bits for table_tuple_insert */
+/*
+ * "options" flag bits for table_tuple_insert.  Access methods may define
+ * their own bits for internal use, as long as they don't collide with these.
+ */
 /* TABLE_INSERT_SKIP_WAL was 0x0001; RelationNeedsWAL() now governs */
 #define TABLE_INSERT_SKIP_FSM		0x0002
 #define TABLE_INSERT_FROZEN			0x0004
@@ -266,6 +269,11 @@ typedef struct TM_IndexDeleteOp
 /* Follow update chain and lock latest version of tuple */
 #define TUPLE_LOCK_FLAG_FIND_LAST_VERSION		(1 << 1)
 
+/* "options" flag bits for table_tuple_delete */
+#define TABLE_DELETE_CHANGING_PARTITION			(1 << 0)
+
+/* "options" flag bits for table_tuple_update */
+/* XXX none at present */
 
 /* Typedef for callback function for table_index_build_scan */
 typedef void (*IndexBuildCallback) (Relation index,
@@ -508,14 +516,14 @@ typedef struct TableAmRoutine
 
 	/* see table_tuple_insert() for reference about parameters */
 	void		(*tuple_insert) (Relation rel, TupleTableSlot *slot,
-								 CommandId cid, int options,
+								 CommandId cid, bits32 options,
 								 BulkInsertStateData *bistate);
 
 	/* see table_tuple_insert_speculative() for reference about parameters */
 	void		(*tuple_insert_speculative) (Relation rel,
 											 TupleTableSlot *slot,
 											 CommandId cid,
-											 int options,
+											 bits32 options,
 											 BulkInsertStateData *bistate,
 											 uint32 specToken);
 
@@ -527,23 +535,24 @@ typedef struct TableAmRoutine
 
 	/* see table_multi_insert() for reference about parameters */
 	void		(*multi_insert) (Relation rel, TupleTableSlot **slots, int nslots,
-								 CommandId cid, int options, BulkInsertStateData *bistate);
+								 CommandId cid, bits32 options, BulkInsertStateData *bistate);
 
 	/* see table_tuple_delete() for reference about parameters */
 	TM_Result	(*tuple_delete) (Relation rel,
 								 ItemPointer tid,
 								 CommandId cid,
+								 bits32 options,
 								 Snapshot snapshot,
 								 Snapshot crosscheck,
 								 bool wait,
-								 TM_FailureData *tmfd,
-								 bool changingPart);
+								 TM_FailureData *tmfd);
 
 	/* see table_tuple_update() for reference about parameters */
 	TM_Result	(*tuple_update) (Relation rel,
 								 ItemPointer otid,
 								 TupleTableSlot *slot,
 								 CommandId cid,
+								 bits32 options,
 								 Snapshot snapshot,
 								 Snapshot crosscheck,
 								 bool wait,
@@ -574,7 +583,7 @@ typedef struct TableAmRoutine
 	 *
 	 * Optional callback.
 	 */
-	void		(*finish_bulk_insert) (Relation rel, int options);
+	void		(*finish_bulk_insert) (Relation rel, bits32 options);
 
 
 	/* ------------------------------------------------------------------------
@@ -1386,7 +1395,7 @@ table_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
  */
 static inline void
 table_tuple_insert(Relation rel, TupleTableSlot *slot, CommandId cid,
-				   int options, BulkInsertStateData *bistate)
+				   bits32 options, BulkInsertStateData *bistate)
 {
 	rel->rd_tableam->tuple_insert(rel, slot, cid, options,
 								  bistate);
@@ -1405,7 +1414,7 @@ table_tuple_insert(Relation rel, TupleTableSlot *slot, CommandId cid,
  */
 static inline void
 table_tuple_insert_speculative(Relation rel, TupleTableSlot *slot,
-							   CommandId cid, int options,
+							   CommandId cid, bits32 options,
 							   BulkInsertStateData *bistate,
 							   uint32 specToken)
 {
@@ -1441,7 +1450,7 @@ table_tuple_complete_speculative(Relation rel, TupleTableSlot *slot,
  */
 static inline void
 table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots,
-				   CommandId cid, int options, BulkInsertStateData *bistate)
+				   CommandId cid, bits32 options, BulkInsertStateData *bistate)
 {
 	rel->rd_tableam->multi_insert(rel, slots, nslots,
 								  cid, options, bistate);
@@ -1476,12 +1485,12 @@ table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots,
  */
 static inline TM_Result
 table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
-				   Snapshot snapshot, Snapshot crosscheck, bool wait,
-				   TM_FailureData *tmfd, bool changingPart)
+				   bits32 options, Snapshot snapshot, Snapshot crosscheck,
+				   bool wait, TM_FailureData *tmfd)
 {
-	return rel->rd_tableam->tuple_delete(rel, tid, cid,
+	return rel->rd_tableam->tuple_delete(rel, tid, cid, options,
 										 snapshot, crosscheck,
-										 wait, tmfd, changingPart);
+										 wait, tmfd);
 }
 
 /*
@@ -1521,12 +1530,13 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
  */
 static inline TM_Result
 table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot,
-				   CommandId cid, Snapshot snapshot, Snapshot crosscheck,
+				   CommandId cid, bits32 options,
+				   Snapshot snapshot, Snapshot crosscheck,
 				   bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
 				   TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
-										 cid, snapshot, crosscheck,
+										 cid, options, snapshot, crosscheck,
 										 wait, tmfd,
 										 lockmode, update_indexes);
 }
@@ -1582,7 +1592,7 @@ table_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot,
  * tuple_insert and multi_insert with a BulkInsertState specified.
  */
 static inline void
-table_finish_bulk_insert(Relation rel, int options)
+table_finish_bulk_insert(Relation rel, bits32 options)
 {
 	/* optional callback */
 	if (rel->rd_tableam && rel->rd_tableam->finish_bulk_insert)
-- 
2.47.3

  [text/x-diff] v2-0002-Define-heap_insert-to-obey-tableam.h-option-bits-.patch (8.1K, ../../202603291633.dkzj7sp3b3lq@alvherre.pgsql/3-v2-0002-Define-heap_insert-to-obey-tableam.h-option-bits-.patch)
  download | inline diff:
From 716f317468168dccdaa4e9161b63b4763ee9ae73 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Sun, 29 Mar 2026 23:11:42 +0200
Subject: [PATCH v2 2/2] Define heap_insert to obey tableam.h option bits
 directly

Redefining the bits at the heapam.h interface is no good.
---
 src/backend/access/heap/heapam.c         | 18 +++++++++---------
 src/backend/access/heap/hio.c            | 10 +++++-----
 src/backend/access/heap/rewriteheap.c    |  4 ++--
 src/backend/replication/logical/decode.c |  2 +-
 src/include/access/heapam.h              | 11 ++++++-----
 5 files changed, 23 insertions(+), 22 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 1b873f55c70..f257003fa65 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2137,9 +2137,9 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate)
  * See table_tuple_insert for comments about most of the input flags, except
  * that this routine directly takes a tuple rather than a slot.
  *
- * There's corresponding HEAP_INSERT_ options to all the TABLE_INSERT_
- * options, and there additionally is HEAP_INSERT_SPECULATIVE which is used to
- * implement table_tuple_insert_speculative().
+ * In addition to the TABLE_INSERT_ options, there additionally is
+ * HEAP_INSERT_SPECULATIVE which is used to implement
+ * table_tuple_insert_speculative().
  *
  * On return the header fields of *tup are updated to match the stored tuple;
  * in particular tup->t_self receives the actual TID where the tuple was
@@ -2267,7 +2267,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		 * image. (XXX We could alternatively store a pointer into the FPW).
 		 */
 		if (RelationIsLogicallyLogged(relation) &&
-			!(options & HEAP_INSERT_NO_LOGICAL))
+			!(options & TABLE_INSERT_NO_LOGICAL))
 		{
 			xlrec.flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
 			bufflags |= REGBUF_KEEP_DATA;
@@ -2356,7 +2356,7 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 	tup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
 	tup->t_data->t_infomask |= HEAP_XMAX_INVALID;
 	HeapTupleHeaderSetXmin(tup->t_data, xid);
-	if (options & HEAP_INSERT_FROZEN)
+	if (options & TABLE_INSERT_FROZEN)
 		HeapTupleHeaderSetXminFrozen(tup->t_data);
 
 	HeapTupleHeaderSetCmin(tup->t_data, cid);
@@ -2437,7 +2437,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 	int			npages_used = 0;
 
 	/* currently not needed (thus unsupported) for heap_multi_insert() */
-	Assert(!(options & HEAP_INSERT_NO_LOGICAL));
+	Assert(!(options & TABLE_INSERT_NO_LOGICAL));
 
 	AssertHasSnapshotForToast(relation);
 
@@ -2527,7 +2527,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 		starting_with_empty_page = PageGetMaxOffsetNumber(page) == 0;
 
-		if (starting_with_empty_page && (options & HEAP_INSERT_FROZEN))
+		if (starting_with_empty_page && (options & TABLE_INSERT_FROZEN))
 		{
 			all_frozen_set = true;
 			/* Lock the vmbuffer before entering the critical section */
@@ -2575,7 +2575,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 		 * page, mark it as all-frozen and update the visibility map. We're
 		 * already holding a pin on the vmbuffer.
 		 */
-		if (PageIsAllVisible(page) && !(options & HEAP_INSERT_FROZEN))
+		if (PageIsAllVisible(page) && !(options & TABLE_INSERT_FROZEN))
 		{
 			all_visible_cleared = true;
 			PageClearAllVisible(page);
@@ -2643,7 +2643,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 			/*
 			 * We don't have to worry about including a conflict xid in the
-			 * WAL record, as HEAP_INSERT_FROZEN intentionally violates
+			 * WAL record, as TABLE_INSERT_FROZEN intentionally violates
 			 * visibility rules.
 			 */
 			if (all_frozen_set)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 1097f44a74e..146c8cc4a68 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -469,12 +469,12 @@ RelationAddBlocks(Relation relation, BulkInsertState bistate,
  *	which is indicated by these arguments not being InvalidBuffer on entry.
  *
  *	We normally use FSM to help us find free space.  However,
- *	if HEAP_INSERT_SKIP_FSM is specified, we just append a new empty page to
+ *	if TABLE_INSERT_SKIP_FSM is specified, we just append a new empty page to
  *	the end of the relation if the tuple won't fit on the current target page.
  *	This can save some cycles when we know the relation is new and doesn't
  *	contain useful amounts of free space.
  *
- *	HEAP_INSERT_SKIP_FSM is also useful for non-WAL-logged additions to a
+ *	TABLE_INSERT_SKIP_FSM is also useful for non-WAL-logged additions to a
  *	relation, if the caller holds exclusive lock and is careful to invalidate
  *	relation's smgr_targblock before the first insertion --- that ensures that
  *	all insertions will occur into newly added pages and not be intermixed
@@ -503,7 +503,7 @@ RelationGetBufferForTuple(Relation relation, Size len,
 						  Buffer *vmbuffer, Buffer *vmbuffer_other,
 						  int num_pages)
 {
-	bool		use_fsm = !(options & HEAP_INSERT_SKIP_FSM);
+	bool		use_fsm = !(options & TABLE_INSERT_SKIP_FSM);
 	Buffer		buffer = InvalidBuffer;
 	Page		page;
 	Size		nearlyEmptyFreeSpace,
@@ -621,7 +621,7 @@ loop:
 			/*
 			 * If the page is empty, pin vmbuffer to set all_frozen bit later.
 			 */
-			if ((options & HEAP_INSERT_FROZEN) &&
+			if ((options & TABLE_INSERT_FROZEN) &&
 				(PageGetMaxOffsetNumber(BufferGetPage(buffer)) == 0))
 				visibilitymap_pin(relation, targetBlock, vmbuffer);
 
@@ -774,7 +774,7 @@ loop:
 	 * do IO while the buffer is locked, so we unlock the page first if IO is
 	 * needed (necessitating checks below).
 	 */
-	if (options & HEAP_INSERT_FROZEN)
+	if (options & TABLE_INSERT_FROZEN)
 	{
 		Assert(PageGetMaxOffsetNumber(page) == 0);
 
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 6b19ac3030d..b21dd6bb26b 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -618,14 +618,14 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 	}
 	else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD)
 	{
-		int			options = HEAP_INSERT_SKIP_FSM;
+		int			options = TABLE_INSERT_SKIP_FSM;
 
 		/*
 		 * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data
 		 * for the TOAST table are not logically decoded.  The main heap is
 		 * WAL-logged as XLOG FPI records, which are not logically decoded.
 		 */
-		options |= HEAP_INSERT_NO_LOGICAL;
+		options |= TABLE_INSERT_NO_LOGICAL;
 
 		heaptup = heap_toast_insert_or_update(state->rs_new_rel, tup, NULL,
 											  options);
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 3c027bcb2f7..d80a26ce88e 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -898,7 +898,7 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	/*
 	 * Ignore insert records without new tuples (this does happen when
-	 * raw_heap_insert marks the TOAST record as HEAP_INSERT_NO_LOGICAL).
+	 * raw_heap_insert marks the TOAST record as TABLE_INSERT_NO_LOGICAL).
 	 */
 	if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
 		return;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 80bcda02cb7..97ef403ee43 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -33,11 +33,12 @@
 #include "utils/snapshot.h"
 
 
-/* "options" flag bits for heap_insert */
-#define HEAP_INSERT_SKIP_FSM	TABLE_INSERT_SKIP_FSM
-#define HEAP_INSERT_FROZEN		TABLE_INSERT_FROZEN
-#define HEAP_INSERT_NO_LOGICAL	TABLE_INSERT_NO_LOGICAL
-#define HEAP_INSERT_SPECULATIVE 0x0010
+/*
+ * "options" flag bits for heap_insert.  That routine also obeys bits defined
+ * for table_tuple_insert; see tableam.h.  We define these starting from the
+ * opposite end of the options word.
+ */
+#define HEAP_INSERT_SPECULATIVE (1 << 31)
 
 /* "options" flag bits for heap_page_prune_and_freeze */
 #define HEAP_PAGE_PRUNE_MARK_UNUSED_NOW		(1 << 0)
-- 
2.47.3

^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-03-30 12:08  Álvaro Herrera <alvherre@kurilemu.de>
  parent: Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 1 reply; 17+ messages in thread

From: Álvaro Herrera @ 2026-03-30 12:08 UTC (permalink / raw)
  To: Andres Freund <andres@anarazel.de>; +Cc: Pg Hackers <pgsql-hackers@lists.postgresql.org>; Zsolt Parragi <zsolt.parragi@percona.com>; Nathan Bossart <nathandbossart@gmail.com>

Hello,

I just pushed the part of 0001 that modifies the API of table_insert and
its sibling functions.  So here 0001 adds the options bitmask to
table_update and table_delete, which is important for REPACK; and 0002
is identical as before and makes the interface (IMO) a bit more
future-proof.

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/
"Every machine is a smoke machine if you operate it wrong enough."
https://twitter.com/libseybieda/status/1541673325781196801

Attachments:

  [text/x-diff] v3-0001-give-options-bitmask-to-table_delete-table_update.patch (10.2K, ../../202603301206.adis56q72squ@alvherre.pgsql/2-v3-0001-give-options-bitmask-to-table_delete-table_update.patch)
  download | inline diff:
From 3ca3cf3e22d7281571a9b83be71600d052659bdb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Mon, 30 Mar 2026 13:27:42 +0200
Subject: [PATCH v3 1/2] give options bitmask to table_delete/table_update

---
 src/backend/access/heap/heapam.c         | 16 ++++++++++------
 src/backend/access/heap/heapam_handler.c | 13 ++++++++-----
 src/backend/access/table/tableam.c       |  6 +++---
 src/backend/executor/nodeModifyTable.c   |  9 +++++++--
 src/include/access/heapam.h              |  7 ++++---
 src/include/access/tableam.h             | 23 +++++++++++++++--------
 6 files changed, 47 insertions(+), 27 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 882deda6b95..1b873f55c70 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2850,8 +2850,8 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
  */
 TM_Result
 heap_delete(Relation relation, const ItemPointerData *tid,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, bool changingPart)
+			CommandId cid, bits32 options, Snapshot crosscheck,
+			bool wait, TM_FailureData *tmfd)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
@@ -2864,6 +2864,7 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 	TransactionId new_xmax;
 	uint16		new_infomask,
 				new_infomask2;
+	bool		changingPart = (options & TABLE_DELETE_CHANGING_PARTITION) != 0;
 	bool		have_tuple_lock = false;
 	bool		iscombo;
 	bool		all_visible_cleared = false;
@@ -3278,9 +3279,11 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
 	TM_FailureData tmfd;
 
 	result = heap_delete(relation, tid,
-						 GetCurrentCommandId(true), InvalidSnapshot,
+						 GetCurrentCommandId(true),
+						 0,
+						 InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &tmfd, false /* changingPart */ );
+						 &tmfd);
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -3319,7 +3322,7 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  */
 TM_Result
 heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
-			CommandId cid, Snapshot crosscheck, bool wait,
+			CommandId cid, bits32 options, Snapshot crosscheck, bool wait,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
 			TU_UpdateIndexes *update_indexes)
 {
@@ -4573,7 +4576,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 	LockTupleMode lockmode;
 
 	result = heap_update(relation, otid, tup,
-						 GetCurrentCommandId(true), InvalidSnapshot,
+						 GetCurrentCommandId(true), 0,
+						 InvalidSnapshot,
 						 true /* wait for commit */ ,
 						 &tmfd, &lockmode, update_indexes);
 	switch (result)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 7c2194b152b..dd03c8c29a6 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -311,21 +311,23 @@ heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
 
 static TM_Result
 heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
-					Snapshot snapshot, Snapshot crosscheck, bool wait,
-					TM_FailureData *tmfd, bool changingPart)
+					bits32 options, Snapshot snapshot, Snapshot crosscheck,
+					bool wait, TM_FailureData *tmfd)
 {
 	/*
 	 * Currently Deleting of index tuples are handled at vacuum, in case if
 	 * the storage itself is cleaning the dead tuples by itself, it is the
 	 * time to call the index tuple deletion also.
 	 */
-	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
+	return heap_delete(relation, tid, cid, options, crosscheck, wait,
+					   tmfd);
 }
 
 
 static TM_Result
 heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
-					CommandId cid, Snapshot snapshot, Snapshot crosscheck,
+					CommandId cid, bits32 options pg_attribute_unused(),
+					Snapshot snapshot, Snapshot crosscheck,
 					bool wait, TM_FailureData *tmfd,
 					LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes)
 {
@@ -337,7 +339,8 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	slot->tts_tableOid = RelationGetRelid(relation);
 	tuple->t_tableOid = slot->tts_tableOid;
 
-	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
+	result = heap_update(relation, otid, tuple, cid, options,
+						 crosscheck, wait,
 						 tmfd, lockmode, update_indexes);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index dfda1af412e..50206ae782f 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -318,9 +318,9 @@ simple_table_tuple_delete(Relation rel, ItemPointer tid, Snapshot snapshot)
 
 	result = table_tuple_delete(rel, tid,
 								GetCurrentCommandId(true),
-								snapshot, InvalidSnapshot,
+								0, snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, false /* changingPart */ );
+								&tmfd);
 
 	switch (result)
 	{
@@ -367,7 +367,7 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 
 	result = table_tuple_update(rel, otid, slot,
 								GetCurrentCommandId(true),
-								snapshot, InvalidSnapshot,
+								0, snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
 								&tmfd, &lockmode, update_indexes);
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 4cd5e262e0f..9f763f756b0 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1522,14 +1522,18 @@ ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 			  ItemPointer tupleid, bool changingPart)
 {
 	EState	   *estate = context->estate;
+	bits32		options = 0;
+
+	if (changingPart)
+		options |= TABLE_DELETE_CHANGING_PARTITION;
 
 	return table_tuple_delete(resultRelInfo->ri_RelationDesc, tupleid,
 							  estate->es_output_cid,
+							  options,
 							  estate->es_snapshot,
 							  estate->es_crosscheck_snapshot,
 							  true /* wait for commit */ ,
-							  &context->tmfd,
-							  changingPart);
+							  &context->tmfd);
 }
 
 /*
@@ -2331,6 +2335,7 @@ lreplace:
 	 */
 	result = table_tuple_update(resultRelationDesc, tupleid, slot,
 								estate->es_output_cid,
+								0,
 								estate->es_snapshot,
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 53e86c0d6e1..80bcda02cb7 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -384,13 +384,14 @@ extern void heap_multi_insert(Relation relation, TupleTableSlot **slots,
 							  int ntuples, CommandId cid, bits32 options,
 							  BulkInsertState bistate);
 extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
-							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, bool changingPart);
+							 CommandId cid, bits32 options, Snapshot crosscheck,
+							 bool wait, TM_FailureData *tmfd);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
 extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
 							 HeapTuple newtup,
-							 CommandId cid, Snapshot crosscheck, bool wait,
+							 CommandId cid, bits32 options,
+							 Snapshot crosscheck, bool wait,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode,
 							 TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 3b017e043a0..3c82b80d45d 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -269,6 +269,11 @@ typedef struct TM_IndexDeleteOp
 /* Follow update chain and lock latest version of tuple */
 #define TUPLE_LOCK_FLAG_FIND_LAST_VERSION		(1 << 1)
 
+/* "options" flag bits for table_tuple_delete */
+#define TABLE_DELETE_CHANGING_PARTITION			(1 << 0)
+
+/* "options" flag bits for table_tuple_update */
+/* XXX none at present */
 
 /* Typedef for callback function for table_index_build_scan */
 typedef void (*IndexBuildCallback) (Relation index,
@@ -536,17 +541,18 @@ typedef struct TableAmRoutine
 	TM_Result	(*tuple_delete) (Relation rel,
 								 ItemPointer tid,
 								 CommandId cid,
+								 bits32 options,
 								 Snapshot snapshot,
 								 Snapshot crosscheck,
 								 bool wait,
-								 TM_FailureData *tmfd,
-								 bool changingPart);
+								 TM_FailureData *tmfd);
 
 	/* see table_tuple_update() for reference about parameters */
 	TM_Result	(*tuple_update) (Relation rel,
 								 ItemPointer otid,
 								 TupleTableSlot *slot,
 								 CommandId cid,
+								 bits32 options,
 								 Snapshot snapshot,
 								 Snapshot crosscheck,
 								 bool wait,
@@ -1479,12 +1485,12 @@ table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots,
  */
 static inline TM_Result
 table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
-				   Snapshot snapshot, Snapshot crosscheck, bool wait,
-				   TM_FailureData *tmfd, bool changingPart)
+				   bits32 options, Snapshot snapshot, Snapshot crosscheck,
+				   bool wait, TM_FailureData *tmfd)
 {
-	return rel->rd_tableam->tuple_delete(rel, tid, cid,
+	return rel->rd_tableam->tuple_delete(rel, tid, cid, options,
 										 snapshot, crosscheck,
-										 wait, tmfd, changingPart);
+										 wait, tmfd);
 }
 
 /*
@@ -1524,12 +1530,13 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
  */
 static inline TM_Result
 table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot,
-				   CommandId cid, Snapshot snapshot, Snapshot crosscheck,
+				   CommandId cid, bits32 options,
+				   Snapshot snapshot, Snapshot crosscheck,
 				   bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
 				   TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
-										 cid, snapshot, crosscheck,
+										 cid, options, snapshot, crosscheck,
 										 wait, tmfd,
 										 lockmode, update_indexes);
 }
-- 
2.47.3

  [text/x-diff] v3-0002-Define-heap_insert-to-obey-tableam.h-option-bits-.patch (8.1K, ../../202603301206.adis56q72squ@alvherre.pgsql/3-v3-0002-Define-heap_insert-to-obey-tableam.h-option-bits-.patch)
  download | inline diff:
From 437e12b07d04dee3c61d5dad070084b0eebfaf54 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Sun, 29 Mar 2026 23:11:42 +0200
Subject: [PATCH v3 2/2] Define heap_insert to obey tableam.h option bits
 directly

Redefining the bits at the heapam.h interface is no good.
---
 src/backend/access/heap/heapam.c         | 18 +++++++++---------
 src/backend/access/heap/hio.c            | 10 +++++-----
 src/backend/access/heap/rewriteheap.c    |  4 ++--
 src/backend/replication/logical/decode.c |  2 +-
 src/include/access/heapam.h              | 11 ++++++-----
 5 files changed, 23 insertions(+), 22 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 1b873f55c70..f257003fa65 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2137,9 +2137,9 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate)
  * See table_tuple_insert for comments about most of the input flags, except
  * that this routine directly takes a tuple rather than a slot.
  *
- * There's corresponding HEAP_INSERT_ options to all the TABLE_INSERT_
- * options, and there additionally is HEAP_INSERT_SPECULATIVE which is used to
- * implement table_tuple_insert_speculative().
+ * In addition to the TABLE_INSERT_ options, there additionally is
+ * HEAP_INSERT_SPECULATIVE which is used to implement
+ * table_tuple_insert_speculative().
  *
  * On return the header fields of *tup are updated to match the stored tuple;
  * in particular tup->t_self receives the actual TID where the tuple was
@@ -2267,7 +2267,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		 * image. (XXX We could alternatively store a pointer into the FPW).
 		 */
 		if (RelationIsLogicallyLogged(relation) &&
-			!(options & HEAP_INSERT_NO_LOGICAL))
+			!(options & TABLE_INSERT_NO_LOGICAL))
 		{
 			xlrec.flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
 			bufflags |= REGBUF_KEEP_DATA;
@@ -2356,7 +2356,7 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 	tup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
 	tup->t_data->t_infomask |= HEAP_XMAX_INVALID;
 	HeapTupleHeaderSetXmin(tup->t_data, xid);
-	if (options & HEAP_INSERT_FROZEN)
+	if (options & TABLE_INSERT_FROZEN)
 		HeapTupleHeaderSetXminFrozen(tup->t_data);
 
 	HeapTupleHeaderSetCmin(tup->t_data, cid);
@@ -2437,7 +2437,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 	int			npages_used = 0;
 
 	/* currently not needed (thus unsupported) for heap_multi_insert() */
-	Assert(!(options & HEAP_INSERT_NO_LOGICAL));
+	Assert(!(options & TABLE_INSERT_NO_LOGICAL));
 
 	AssertHasSnapshotForToast(relation);
 
@@ -2527,7 +2527,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 		starting_with_empty_page = PageGetMaxOffsetNumber(page) == 0;
 
-		if (starting_with_empty_page && (options & HEAP_INSERT_FROZEN))
+		if (starting_with_empty_page && (options & TABLE_INSERT_FROZEN))
 		{
 			all_frozen_set = true;
 			/* Lock the vmbuffer before entering the critical section */
@@ -2575,7 +2575,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 		 * page, mark it as all-frozen and update the visibility map. We're
 		 * already holding a pin on the vmbuffer.
 		 */
-		if (PageIsAllVisible(page) && !(options & HEAP_INSERT_FROZEN))
+		if (PageIsAllVisible(page) && !(options & TABLE_INSERT_FROZEN))
 		{
 			all_visible_cleared = true;
 			PageClearAllVisible(page);
@@ -2643,7 +2643,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 			/*
 			 * We don't have to worry about including a conflict xid in the
-			 * WAL record, as HEAP_INSERT_FROZEN intentionally violates
+			 * WAL record, as TABLE_INSERT_FROZEN intentionally violates
 			 * visibility rules.
 			 */
 			if (all_frozen_set)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 1097f44a74e..146c8cc4a68 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -469,12 +469,12 @@ RelationAddBlocks(Relation relation, BulkInsertState bistate,
  *	which is indicated by these arguments not being InvalidBuffer on entry.
  *
  *	We normally use FSM to help us find free space.  However,
- *	if HEAP_INSERT_SKIP_FSM is specified, we just append a new empty page to
+ *	if TABLE_INSERT_SKIP_FSM is specified, we just append a new empty page to
  *	the end of the relation if the tuple won't fit on the current target page.
  *	This can save some cycles when we know the relation is new and doesn't
  *	contain useful amounts of free space.
  *
- *	HEAP_INSERT_SKIP_FSM is also useful for non-WAL-logged additions to a
+ *	TABLE_INSERT_SKIP_FSM is also useful for non-WAL-logged additions to a
  *	relation, if the caller holds exclusive lock and is careful to invalidate
  *	relation's smgr_targblock before the first insertion --- that ensures that
  *	all insertions will occur into newly added pages and not be intermixed
@@ -503,7 +503,7 @@ RelationGetBufferForTuple(Relation relation, Size len,
 						  Buffer *vmbuffer, Buffer *vmbuffer_other,
 						  int num_pages)
 {
-	bool		use_fsm = !(options & HEAP_INSERT_SKIP_FSM);
+	bool		use_fsm = !(options & TABLE_INSERT_SKIP_FSM);
 	Buffer		buffer = InvalidBuffer;
 	Page		page;
 	Size		nearlyEmptyFreeSpace,
@@ -621,7 +621,7 @@ loop:
 			/*
 			 * If the page is empty, pin vmbuffer to set all_frozen bit later.
 			 */
-			if ((options & HEAP_INSERT_FROZEN) &&
+			if ((options & TABLE_INSERT_FROZEN) &&
 				(PageGetMaxOffsetNumber(BufferGetPage(buffer)) == 0))
 				visibilitymap_pin(relation, targetBlock, vmbuffer);
 
@@ -774,7 +774,7 @@ loop:
 	 * do IO while the buffer is locked, so we unlock the page first if IO is
 	 * needed (necessitating checks below).
 	 */
-	if (options & HEAP_INSERT_FROZEN)
+	if (options & TABLE_INSERT_FROZEN)
 	{
 		Assert(PageGetMaxOffsetNumber(page) == 0);
 
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 6b19ac3030d..b21dd6bb26b 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -618,14 +618,14 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 	}
 	else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD)
 	{
-		int			options = HEAP_INSERT_SKIP_FSM;
+		int			options = TABLE_INSERT_SKIP_FSM;
 
 		/*
 		 * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data
 		 * for the TOAST table are not logically decoded.  The main heap is
 		 * WAL-logged as XLOG FPI records, which are not logically decoded.
 		 */
-		options |= HEAP_INSERT_NO_LOGICAL;
+		options |= TABLE_INSERT_NO_LOGICAL;
 
 		heaptup = heap_toast_insert_or_update(state->rs_new_rel, tup, NULL,
 											  options);
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 3c027bcb2f7..d80a26ce88e 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -898,7 +898,7 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	/*
 	 * Ignore insert records without new tuples (this does happen when
-	 * raw_heap_insert marks the TOAST record as HEAP_INSERT_NO_LOGICAL).
+	 * raw_heap_insert marks the TOAST record as TABLE_INSERT_NO_LOGICAL).
 	 */
 	if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
 		return;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 80bcda02cb7..97ef403ee43 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -33,11 +33,12 @@
 #include "utils/snapshot.h"
 
 
-/* "options" flag bits for heap_insert */
-#define HEAP_INSERT_SKIP_FSM	TABLE_INSERT_SKIP_FSM
-#define HEAP_INSERT_FROZEN		TABLE_INSERT_FROZEN
-#define HEAP_INSERT_NO_LOGICAL	TABLE_INSERT_NO_LOGICAL
-#define HEAP_INSERT_SPECULATIVE 0x0010
+/*
+ * "options" flag bits for heap_insert.  That routine also obeys bits defined
+ * for table_tuple_insert; see tableam.h.  We define these starting from the
+ * opposite end of the options word.
+ */
+#define HEAP_INSERT_SPECULATIVE (1 << 31)
 
 /* "options" flag bits for heap_page_prune_and_freeze */
 #define HEAP_PAGE_PRUNE_MARK_UNUSED_NOW		(1 << 0)
-- 
2.47.3

^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-03-31 16:10  Álvaro Herrera <alvherre@kurilemu.de>
  parent: Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 2 replies; 17+ messages in thread

From: Álvaro Herrera @ 2026-03-31 16:10 UTC (permalink / raw)
  To: Andres Freund <andres@anarazel.de>; +Cc: Pg Hackers <pgsql-hackers@lists.postgresql.org>; Zsolt Parragi <zsolt.parragi@percona.com>; Nathan Bossart <nathandbossart@gmail.com>

Hi,

On 2026-Mar-30, Álvaro Herrera wrote:

> I just pushed the part of 0001 that modifies the API of table_insert and
> its sibling functions.  So here 0001 adds the options bitmask to
> table_update and table_delete, which is important for REPACK; and 0002
> is identical as before and makes the interface (IMO) a bit more
> future-proof.

FWIW I just posted 0001 as part of the repack series here [1]; 0002 is
inessential, so I didn't; but here's both of them.  I have drafted
commit messages also.

[1] https://postgr.es/m/202603311523.iqhng5ljkzpq@alvherre.pgsql

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
"Pensar que el espectro que vemos es ilusorio no lo despoja de espanto,
sólo le suma el nuevo terror de la locura" (Perelandra, C.S. Lewis)

Attachments:

  [text/x-diff] v4-0001-Give-options-parameter-to-table_delete-table_upda.patch (10.6K, ../../202603311555.w42t5agupy3z@alvherre.pgsql/2-v4-0001-Give-options-parameter-to-table_delete-table_upda.patch)
  download | inline diff:
From 56035a75616830ab498092b96d64da5845f1ae19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Mon, 30 Mar 2026 13:27:42 +0200
Subject: [PATCH v4 1/2] Give 'options' parameter to table_delete/table_update

The table_insert() method already has an equivalent argument, so this
makes sense just on consistency grounds, for future growth.

table_delete() can immediately use it to carry the 'changingPart'
boolean (which is arguably misplaced in the current API); for
table_update we don't have any options at present, but an upcoming patch
would add one.

Discussion: https://postgr.es/m/202603171606.kf6pmhscqbqz@alvherre.pgsql
---
 src/backend/access/heap/heapam.c         | 16 ++++++++++------
 src/backend/access/heap/heapam_handler.c | 13 ++++++++-----
 src/backend/access/table/tableam.c       |  6 +++---
 src/backend/executor/nodeModifyTable.c   |  9 +++++++--
 src/include/access/heapam.h              |  7 ++++---
 src/include/access/tableam.h             | 23 +++++++++++++++--------
 6 files changed, 47 insertions(+), 27 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index d34136d2e94..0645b2d5f58 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2862,8 +2862,8 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
  */
 TM_Result
 heap_delete(Relation relation, const ItemPointerData *tid,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, bool changingPart)
+			CommandId cid, uint32 options, Snapshot crosscheck,
+			bool wait, TM_FailureData *tmfd)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
@@ -2876,6 +2876,7 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 	TransactionId new_xmax;
 	uint16		new_infomask,
 				new_infomask2;
+	bool		changingPart = (options & TABLE_DELETE_CHANGING_PARTITION) != 0;
 	bool		have_tuple_lock = false;
 	bool		iscombo;
 	bool		all_visible_cleared = false;
@@ -3290,9 +3291,11 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
 	TM_FailureData tmfd;
 
 	result = heap_delete(relation, tid,
-						 GetCurrentCommandId(true), InvalidSnapshot,
+						 GetCurrentCommandId(true),
+						 0,
+						 InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &tmfd, false /* changingPart */ );
+						 &tmfd);
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -3331,7 +3334,7 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  */
 TM_Result
 heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
-			CommandId cid, Snapshot crosscheck, bool wait,
+			CommandId cid, uint32 options, Snapshot crosscheck, bool wait,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
 			TU_UpdateIndexes *update_indexes)
 {
@@ -4585,7 +4588,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 	LockTupleMode lockmode;
 
 	result = heap_update(relation, otid, tup,
-						 GetCurrentCommandId(true), InvalidSnapshot,
+						 GetCurrentCommandId(true), 0,
+						 InvalidSnapshot,
 						 true /* wait for commit */ ,
 						 &tmfd, &lockmode, update_indexes);
 	switch (result)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index cdd153c6b6d..69debeff516 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -313,21 +313,23 @@ heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
 
 static TM_Result
 heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
-					Snapshot snapshot, Snapshot crosscheck, bool wait,
-					TM_FailureData *tmfd, bool changingPart)
+					uint32 options, Snapshot snapshot, Snapshot crosscheck,
+					bool wait, TM_FailureData *tmfd)
 {
 	/*
 	 * Currently Deleting of index tuples are handled at vacuum, in case if
 	 * the storage itself is cleaning the dead tuples by itself, it is the
 	 * time to call the index tuple deletion also.
 	 */
-	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
+	return heap_delete(relation, tid, cid, options, crosscheck, wait,
+					   tmfd);
 }
 
 
 static TM_Result
 heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
-					CommandId cid, Snapshot snapshot, Snapshot crosscheck,
+					CommandId cid, uint32 options pg_attribute_unused(),
+					Snapshot snapshot, Snapshot crosscheck,
 					bool wait, TM_FailureData *tmfd,
 					LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes)
 {
@@ -339,7 +341,8 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	slot->tts_tableOid = RelationGetRelid(relation);
 	tuple->t_tableOid = slot->tts_tableOid;
 
-	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
+	result = heap_update(relation, otid, tuple, cid, options,
+						 crosscheck, wait,
 						 tmfd, lockmode, update_indexes);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 86481d7c029..68ff0966f1c 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -320,9 +320,9 @@ simple_table_tuple_delete(Relation rel, ItemPointer tid, Snapshot snapshot)
 
 	result = table_tuple_delete(rel, tid,
 								GetCurrentCommandId(true),
-								snapshot, InvalidSnapshot,
+								0, snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, false /* changingPart */ );
+								&tmfd);
 
 	switch (result)
 	{
@@ -369,7 +369,7 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 
 	result = table_tuple_update(rel, otid, slot,
 								GetCurrentCommandId(true),
-								snapshot, InvalidSnapshot,
+								0, snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
 								&tmfd, &lockmode, update_indexes);
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 582bcc367c0..76728f08734 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1522,14 +1522,18 @@ ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 			  ItemPointer tupleid, bool changingPart)
 {
 	EState	   *estate = context->estate;
+	uint32		options = 0;
+
+	if (changingPart)
+		options |= TABLE_DELETE_CHANGING_PARTITION;
 
 	return table_tuple_delete(resultRelInfo->ri_RelationDesc, tupleid,
 							  estate->es_output_cid,
+							  options,
 							  estate->es_snapshot,
 							  estate->es_crosscheck_snapshot,
 							  true /* wait for commit */ ,
-							  &context->tmfd,
-							  changingPart);
+							  &context->tmfd);
 }
 
 /*
@@ -2331,6 +2335,7 @@ lreplace:
 	 */
 	result = table_tuple_update(resultRelationDesc, tupleid, slot,
 								estate->es_output_cid,
+								0,
 								estate->es_snapshot,
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 6018dacf0f7..77560b22877 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -382,13 +382,14 @@ extern void heap_multi_insert(Relation relation, TupleTableSlot **slots,
 							  int ntuples, CommandId cid, uint32 options,
 							  BulkInsertState bistate);
 extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
-							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, bool changingPart);
+							 CommandId cid, uint32 options, Snapshot crosscheck,
+							 bool wait, TM_FailureData *tmfd);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
 extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
 							 HeapTuple newtup,
-							 CommandId cid, Snapshot crosscheck, bool wait,
+							 CommandId cid, uint32 options,
+							 Snapshot crosscheck, bool wait,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode,
 							 TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index ab2e7fc1dfe..6cdd949f58d 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -287,6 +287,11 @@ typedef struct TM_IndexDeleteOp
 /* Follow update chain and lock latest version of tuple */
 #define TUPLE_LOCK_FLAG_FIND_LAST_VERSION		(1 << 1)
 
+/* "options" flag bits for table_tuple_delete */
+#define TABLE_DELETE_CHANGING_PARTITION			(1 << 0)
+
+/* "options" flag bits for table_tuple_update */
+/* XXX none at present */
 
 /* Typedef for callback function for table_index_build_scan */
 typedef void (*IndexBuildCallback) (Relation index,
@@ -558,17 +563,18 @@ typedef struct TableAmRoutine
 	TM_Result	(*tuple_delete) (Relation rel,
 								 ItemPointer tid,
 								 CommandId cid,
+								 uint32 options,
 								 Snapshot snapshot,
 								 Snapshot crosscheck,
 								 bool wait,
-								 TM_FailureData *tmfd,
-								 bool changingPart);
+								 TM_FailureData *tmfd);
 
 	/* see table_tuple_update() for reference about parameters */
 	TM_Result	(*tuple_update) (Relation rel,
 								 ItemPointer otid,
 								 TupleTableSlot *slot,
 								 CommandId cid,
+								 uint32 options,
 								 Snapshot snapshot,
 								 Snapshot crosscheck,
 								 bool wait,
@@ -1533,12 +1539,12 @@ table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots,
  */
 static inline TM_Result
 table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
-				   Snapshot snapshot, Snapshot crosscheck, bool wait,
-				   TM_FailureData *tmfd, bool changingPart)
+				   uint32 options, Snapshot snapshot, Snapshot crosscheck,
+				   bool wait, TM_FailureData *tmfd)
 {
-	return rel->rd_tableam->tuple_delete(rel, tid, cid,
+	return rel->rd_tableam->tuple_delete(rel, tid, cid, options,
 										 snapshot, crosscheck,
-										 wait, tmfd, changingPart);
+										 wait, tmfd);
 }
 
 /*
@@ -1578,12 +1584,13 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
  */
 static inline TM_Result
 table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot,
-				   CommandId cid, Snapshot snapshot, Snapshot crosscheck,
+				   CommandId cid, uint32 options,
+				   Snapshot snapshot, Snapshot crosscheck,
 				   bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
 				   TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
-										 cid, snapshot, crosscheck,
+										 cid, options, snapshot, crosscheck,
 										 wait, tmfd,
 										 lockmode, update_indexes);
 }
-- 
2.47.3

  [text/x-diff] v4-0002-Change-heapam.c-to-obey-tableam.h-option-bits-dir.patch (9.0K, ../../202603311555.w42t5agupy3z@alvherre.pgsql/3-v4-0002-Change-heapam.c-to-obey-tableam.h-option-bits-dir.patch)
  download | inline diff:
From d7bf70e73ec52bb94d088f1be43712aff97a12c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Sun, 29 Mar 2026 23:11:42 +0200
Subject: [PATCH v4 2/2] Change heapam.c to obey tableam.h option bits directly

Redefining the bits at the heapam.h interface serves no purpose.  This
was done to maintain the independence of heapam when used outside of
table AM (which is very widespread, given that all system catalogs use
it that way); however, heapam no longer lives in a world where tableam.h
doesn't exist, so this separation of concerns is of no use.

Also, whenever somebody next defines a new TABLE_INSERT_* bit, the value
value would collide with HEAP_INSERT_SPECULATIVE.  Move the latter to
the other end of the bits word.

Discussion: https://postgr.es/m/202603171606.kf6pmhscqbqz@alvherre.pgsql
---
 src/backend/access/heap/heapam.c         | 20 ++++++++++----------
 src/backend/access/heap/hio.c            | 10 +++++-----
 src/backend/access/heap/rewriteheap.c    |  4 ++--
 src/backend/replication/logical/decode.c |  2 +-
 src/include/access/heapam.h              | 11 ++++++-----
 5 files changed, 24 insertions(+), 23 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 0645b2d5f58..8c5634ece24 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2138,9 +2138,9 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate)
  * See table_tuple_insert for comments about most of the input flags, except
  * that this routine directly takes a tuple rather than a slot.
  *
- * There's corresponding HEAP_INSERT_ options to all the TABLE_INSERT_
- * options, and there additionally is HEAP_INSERT_SPECULATIVE which is used to
- * implement table_tuple_insert_speculative().
+ * In addition to the TABLE_INSERT_ options, there additionally is
+ * HEAP_INSERT_SPECULATIVE which is used to implement
+ * table_tuple_insert_speculative().
  *
  * On return the header fields of *tup are updated to match the stored tuple;
  * in particular tup->t_self receives the actual TID where the tuple was
@@ -2228,7 +2228,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 	 * Don't set it if we are in bootstrap mode or we are inserting a frozen
 	 * tuple, as there is no further pruning/freezing needed in those cases.
 	 */
-	if (TransactionIdIsNormal(xid) && !(options & HEAP_INSERT_FROZEN))
+	if (TransactionIdIsNormal(xid) && !(options & TABLE_INSERT_FROZEN))
 		PageSetPrunable(page, xid);
 
 	MarkBufferDirty(buffer);
@@ -2275,7 +2275,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		 * image. (XXX We could alternatively store a pointer into the FPW).
 		 */
 		if (RelationIsLogicallyLogged(relation) &&
-			!(options & HEAP_INSERT_NO_LOGICAL))
+			!(options & TABLE_INSERT_NO_LOGICAL))
 		{
 			xlrec.flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
 			bufflags |= REGBUF_KEEP_DATA;
@@ -2364,7 +2364,7 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 	tup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
 	tup->t_data->t_infomask |= HEAP_XMAX_INVALID;
 	HeapTupleHeaderSetXmin(tup->t_data, xid);
-	if (options & HEAP_INSERT_FROZEN)
+	if (options & TABLE_INSERT_FROZEN)
 		HeapTupleHeaderSetXminFrozen(tup->t_data);
 
 	HeapTupleHeaderSetCmin(tup->t_data, cid);
@@ -2445,7 +2445,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 	int			npages_used = 0;
 
 	/* currently not needed (thus unsupported) for heap_multi_insert() */
-	Assert(!(options & HEAP_INSERT_NO_LOGICAL));
+	Assert(!(options & TABLE_INSERT_NO_LOGICAL));
 
 	AssertHasSnapshotForToast(relation);
 
@@ -2535,7 +2535,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 		starting_with_empty_page = PageGetMaxOffsetNumber(page) == 0;
 
-		if (starting_with_empty_page && (options & HEAP_INSERT_FROZEN))
+		if (starting_with_empty_page && (options & TABLE_INSERT_FROZEN))
 		{
 			all_frozen_set = true;
 			/* Lock the vmbuffer before entering the critical section */
@@ -2583,7 +2583,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 		 * page, mark it as all-frozen and update the visibility map. We're
 		 * already holding a pin on the vmbuffer.
 		 */
-		if (PageIsAllVisible(page) && !(options & HEAP_INSERT_FROZEN))
+		if (PageIsAllVisible(page) && !(options & TABLE_INSERT_FROZEN))
 		{
 			all_visible_cleared = true;
 			PageClearAllVisible(page);
@@ -2655,7 +2655,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 			/*
 			 * We don't have to worry about including a conflict xid in the
-			 * WAL record, as HEAP_INSERT_FROZEN intentionally violates
+			 * WAL record, as TABLE_INSERT_FROZEN intentionally violates
 			 * visibility rules.
 			 */
 			if (all_frozen_set)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 1097f44a74e..146c8cc4a68 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -469,12 +469,12 @@ RelationAddBlocks(Relation relation, BulkInsertState bistate,
  *	which is indicated by these arguments not being InvalidBuffer on entry.
  *
  *	We normally use FSM to help us find free space.  However,
- *	if HEAP_INSERT_SKIP_FSM is specified, we just append a new empty page to
+ *	if TABLE_INSERT_SKIP_FSM is specified, we just append a new empty page to
  *	the end of the relation if the tuple won't fit on the current target page.
  *	This can save some cycles when we know the relation is new and doesn't
  *	contain useful amounts of free space.
  *
- *	HEAP_INSERT_SKIP_FSM is also useful for non-WAL-logged additions to a
+ *	TABLE_INSERT_SKIP_FSM is also useful for non-WAL-logged additions to a
  *	relation, if the caller holds exclusive lock and is careful to invalidate
  *	relation's smgr_targblock before the first insertion --- that ensures that
  *	all insertions will occur into newly added pages and not be intermixed
@@ -503,7 +503,7 @@ RelationGetBufferForTuple(Relation relation, Size len,
 						  Buffer *vmbuffer, Buffer *vmbuffer_other,
 						  int num_pages)
 {
-	bool		use_fsm = !(options & HEAP_INSERT_SKIP_FSM);
+	bool		use_fsm = !(options & TABLE_INSERT_SKIP_FSM);
 	Buffer		buffer = InvalidBuffer;
 	Page		page;
 	Size		nearlyEmptyFreeSpace,
@@ -621,7 +621,7 @@ loop:
 			/*
 			 * If the page is empty, pin vmbuffer to set all_frozen bit later.
 			 */
-			if ((options & HEAP_INSERT_FROZEN) &&
+			if ((options & TABLE_INSERT_FROZEN) &&
 				(PageGetMaxOffsetNumber(BufferGetPage(buffer)) == 0))
 				visibilitymap_pin(relation, targetBlock, vmbuffer);
 
@@ -774,7 +774,7 @@ loop:
 	 * do IO while the buffer is locked, so we unlock the page first if IO is
 	 * needed (necessitating checks below).
 	 */
-	if (options & HEAP_INSERT_FROZEN)
+	if (options & TABLE_INSERT_FROZEN)
 	{
 		Assert(PageGetMaxOffsetNumber(page) == 0);
 
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 6b19ac3030d..b21dd6bb26b 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -618,14 +618,14 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 	}
 	else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD)
 	{
-		int			options = HEAP_INSERT_SKIP_FSM;
+		int			options = TABLE_INSERT_SKIP_FSM;
 
 		/*
 		 * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data
 		 * for the TOAST table are not logically decoded.  The main heap is
 		 * WAL-logged as XLOG FPI records, which are not logically decoded.
 		 */
-		options |= HEAP_INSERT_NO_LOGICAL;
+		options |= TABLE_INSERT_NO_LOGICAL;
 
 		heaptup = heap_toast_insert_or_update(state->rs_new_rel, tup, NULL,
 											  options);
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 3c027bcb2f7..d80a26ce88e 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -898,7 +898,7 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	/*
 	 * Ignore insert records without new tuples (this does happen when
-	 * raw_heap_insert marks the TOAST record as HEAP_INSERT_NO_LOGICAL).
+	 * raw_heap_insert marks the TOAST record as TABLE_INSERT_NO_LOGICAL).
 	 */
 	if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
 		return;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 77560b22877..10b26e56ff0 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -33,11 +33,12 @@
 #include "utils/snapshot.h"
 
 
-/* "options" flag bits for heap_insert */
-#define HEAP_INSERT_SKIP_FSM	TABLE_INSERT_SKIP_FSM
-#define HEAP_INSERT_FROZEN		TABLE_INSERT_FROZEN
-#define HEAP_INSERT_NO_LOGICAL	TABLE_INSERT_NO_LOGICAL
-#define HEAP_INSERT_SPECULATIVE 0x0010
+/*
+ * "options" flag bits for heap_insert.  That routine also obeys bits defined
+ * for table_tuple_insert; see tableam.h.  We define these starting from the
+ * opposite end of the options word.
+ */
+#define HEAP_INSERT_SPECULATIVE (1 << 31)
 
 /* "options" flag bits for heap_page_prune_and_freeze */
 #define HEAP_PAGE_PRUNE_MARK_UNUSED_NOW		(1 << 0)
-- 
2.47.3

^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-03-31 23:20  Zsolt Parragi <zsolt.parragi@percona.com>
  parent: Álvaro Herrera <alvherre@kurilemu.de>
  1 sibling, 0 replies; 17+ messages in thread

From: Zsolt Parragi @ 2026-03-31 23:20 UTC (permalink / raw)
  To: Álvaro Herrera <alvherre@kurilemu.de>; +Cc: Andres Freund <andres@anarazel.de>; Pg Hackers <pgsql-hackers@lists.postgresql.org>; Nathan Bossart <nathandbossart@gmail.com>

 static inline TM_Result
 table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
-    Snapshot snapshot, Snapshot crosscheck, bool wait,
-    TM_FailureData *tmfd, bool changingPart)
+    uint32 options, Snapshot snapshot, Snapshot crosscheck,
+    bool wait, TM_FailureData *tmfd)

The doc comment still referneces changingPart
Similarly table_tuple_update doesn't document the new options parameter.

@@ -339,7 +341,8 @@ heapam_tuple_update(Relation relation, ItemPointer
otid, TupleTableSlot *slot,
  slot->tts_tableOid = RelationGetRelid(relation);
  tuple->t_tableOid = slot->tts_tableOid;

- result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
+ result = heap_update(relation, otid, tuple, cid, options,
+ crosscheck, wait,
  tmfd, lockmode, update_indexes);
  ItemPointerCopy(&tuple->t_self, &slot->tts_tid);

options is marked pg_attribute_unused above, that seems misleading.
Should the annotation be part of the heap_update signature instead?





^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-04-01 06:33  Chao Li <li.evan.chao@gmail.com>
  parent: Álvaro Herrera <alvherre@kurilemu.de>
  1 sibling, 1 reply; 17+ messages in thread

From: Chao Li @ 2026-04-01 06:33 UTC (permalink / raw)
  To: Álvaro Herrera <alvherre@kurilemu.de>; +Cc: Andres Freund <andres@anarazel.de>; Pg Hackers <pgsql-hackers@lists.postgresql.org>; Zsolt Parragi <zsolt.parragi@percona.com>; Nathan Bossart <nathandbossart@gmail.com>



> On Apr 1, 2026, at 00:10, Álvaro Herrera <alvherre@kurilemu.de> wrote:
> 
> Hi,
> 
> On 2026-Mar-30, Álvaro Herrera wrote:
> 
>> I just pushed the part of 0001 that modifies the API of table_insert and
>> its sibling functions.  So here 0001 adds the options bitmask to
>> table_update and table_delete, which is important for REPACK; and 0002
>> is identical as before and makes the interface (IMO) a bit more
>> future-proof.
> 
> FWIW I just posted 0001 as part of the repack series here [1]; 0002 is
> inessential, so I didn't; but here's both of them.  I have drafted
> commit messages also.
> 
> [1] https://postgr.es/m/202603311523.iqhng5ljkzpq@alvherre.pgsql
> 
> -- 
> Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
> "Pensar que el espectro que vemos es ilusorio no lo despoja de espanto,
> sólo le suma el nuevo terror de la locura" (Perelandra, C.S. Lewis)
> <v4-0001-Give-options-parameter-to-table_delete-table_upda.patch><v4-0002-Change-heapam.c-to-obey-tableam.h-option-bits-dir.patch>

Looks like v4-0001/0002 are pure refactoring. A few small comments:

1 - 0001
For table_tuple_delete(), options is added and changingPart is removed, but the header comment should be updated to reflect the change.

2 - 0002
For table_tuple_update(), options is added, the header comment should be updated as well.

3 - 0002
Now TABLE_INSERT_SKIP_FSM, TABLE_INSERT_FROZEN, TABLE_INSERT_NO_LOGICAL belong to the same options word as HEAP_INSERT_SPECULATIVE, but they are still defined as:
```
#define TABLE_INSERT_SKIP_FSM 0x0002
#define TABLE_INSERT_FROZEN 0x0004
#define TABLE_INSERT_NO_LOGICAL 0x0008
```

Could it make sense to change them to the left-shift form?

4 - 0002 
In heap_multi_insert(), heap_prepare_insert(), and heap_insert(), options is defined as uint32, but in RelationGetBufferForTuple() and raw_heap_insert() it is still defined as int. Would it make sense to take this opportunity to change all “options" to uint32 for consistency? Otherwise, if this is left for later, a trivial follow-up patch just to change int to uint32 may be harder to get processed.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/









^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-04-01 14:27  Álvaro Herrera <alvherre@kurilemu.de>
  parent: Chao Li <li.evan.chao@gmail.com>
  0 siblings, 1 reply; 17+ messages in thread

From: Álvaro Herrera @ 2026-04-01 14:27 UTC (permalink / raw)
  To: Chao Li <li.evan.chao@gmail.com>; +Cc: Andres Freund <andres@anarazel.de>; Pg Hackers <pgsql-hackers@lists.postgresql.org>; Zsolt Parragi <zsolt.parragi@percona.com>; Nathan Bossart <nathandbossart@gmail.com>

On 2026-Apr-01, Chao Li wrote:

> 1 - 0001
> For table_tuple_delete(), options is added and changingPart is
> removed, but the header comment should be updated to reflect the
> change.

True, fixed.

> 2 - 0002
> For table_tuple_update(), options is added, the header comment should
> be updated as well.

Done.

> 3 - 0002
> Now TABLE_INSERT_SKIP_FSM, TABLE_INSERT_FROZEN, TABLE_INSERT_NO_LOGICAL belong to the same options word as HEAP_INSERT_SPECULATIVE, but they are still defined as:
> ```
> #define TABLE_INSERT_SKIP_FSM 0x0002
> #define TABLE_INSERT_FROZEN 0x0004
> #define TABLE_INSERT_NO_LOGICAL 0x0008
> ```
> 
> Could it make sense to change them to the left-shift form?

Yeah, I don't know.  I don't like this style, but some people like it,
and I don't want to get into an argument about this kind of thing.

> 4 - 0002 
> In heap_multi_insert(), heap_prepare_insert(), and heap_insert(),
> options is defined as uint32, but in RelationGetBufferForTuple() and
> raw_heap_insert() it is still defined as int. Would it make sense to
> take this opportunity to change all “options" to uint32 for
> consistency? Otherwise, if this is left for later, a trivial follow-up
> patch just to change int to uint32 may be harder to get processed.

Hmm, this is actually a problem in 1bd6f22f43ac.  I added a preliminary
patch that should fix this.

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/
"No renuncies a nada. No te aferres a nada."

Attachments:

  [text/x-diff] v5-0001-Fix-callers-of-heap_insert-and-siblings-to-use-ui.patch (10.5K, ../../202604011417.bwthhrwmivd4@alvherre.pgsql/2-v5-0001-Fix-callers-of-heap_insert-and-siblings-to-use-ui.patch)
  download | inline diff:
From 368d46e17eaff4b79a8451713169c3580348c3fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Wed, 1 Apr 2026 15:06:15 +0200
Subject: [PATCH v5 1/3] Fix callers of heap_insert and siblings to use uint32
 for options, not int

Oversight in commit 1bd6f22f43ac: I was way too optimistic about the
compiler letting me know what variables needed to be updated, and missed
a few of them.  Clean it up.

Reported-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/40E570EE-5A60-49D8-B8F7-2F8F2B7C8DFA@gmail.com
---
 src/backend/access/common/toast_internals.c | 2 +-
 src/backend/access/heap/heaptoast.c         | 2 +-
 src/backend/access/heap/hio.c               | 2 +-
 src/backend/access/heap/rewriteheap.c       | 2 +-
 src/backend/access/table/toast_helper.c     | 2 +-
 src/backend/commands/copyfrom.c             | 8 ++++----
 src/backend/commands/createas.c             | 2 +-
 src/backend/commands/matview.c              | 2 +-
 src/backend/commands/tablecmds.c            | 8 ++++----
 src/include/access/heaptoast.h              | 2 +-
 src/include/access/hio.h                    | 2 +-
 src/include/access/toast_helper.h           | 2 +-
 src/include/access/toast_internals.h        | 2 +-
 13 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index 4d0da07135e..77d42e7ed65 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -117,7 +117,7 @@ toast_compress_datum(Datum value, char cmethod)
  */
 Datum
 toast_save_datum(Relation rel, Datum value,
-				 varlena *oldexternal, int options)
+				 varlena *oldexternal, uint32 options)
 {
 	Relation	toastrel;
 	Relation   *toastidxs;
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index ba541bd60c9..03f885a25b0 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -94,7 +94,7 @@ heap_toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
  */
 HeapTuple
 heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
-							int options)
+							uint32 options)
 {
 	HeapTuple	result_tuple;
 	TupleDesc	tupleDesc;
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 1097f44a74e..e96e0f77d92 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -498,7 +498,7 @@ RelationAddBlocks(Relation relation, BulkInsertState bistate,
  */
 Buffer
 RelationGetBufferForTuple(Relation relation, Size len,
-						  Buffer otherBuffer, int options,
+						  Buffer otherBuffer, uint32 options,
 						  BulkInsertState bistate,
 						  Buffer *vmbuffer, Buffer *vmbuffer_other,
 						  int num_pages)
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 6b19ac3030d..f707b102c72 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -618,7 +618,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 	}
 	else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD)
 	{
-		int			options = HEAP_INSERT_SKIP_FSM;
+		uint32		options = HEAP_INSERT_SKIP_FSM;
 
 		/*
 		 * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data
diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c
index 0d792a60ca0..2f2022d9951 100644
--- a/src/backend/access/table/toast_helper.c
+++ b/src/backend/access/table/toast_helper.c
@@ -253,7 +253,7 @@ toast_tuple_try_compression(ToastTupleContext *ttc, int attribute)
  * Move an attribute to external storage.
  */
 void
-toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options)
+toast_tuple_externalize(ToastTupleContext *ttc, int attribute, uint32 options)
 {
 	Datum	   *value = &ttc->ttc_values[attribute];
 	Datum		old_value = *value;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index aa253b587aa..64ac3063c61 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -101,7 +101,7 @@ typedef struct CopyMultiInsertInfo
 	CopyFromState cstate;		/* Copy state for this CopyMultiInsertInfo */
 	EState	   *estate;			/* Executor state used for COPY */
 	CommandId	mycid;			/* Command Id used for COPY */
-	int			ti_options;		/* table insert options */
+	uint32		ti_options;		/* table insert options */
 } CopyMultiInsertInfo;
 
 
@@ -401,7 +401,7 @@ CopyMultiInsertInfoSetupBuffer(CopyMultiInsertInfo *miinfo,
 static void
 CopyMultiInsertInfoInit(CopyMultiInsertInfo *miinfo, ResultRelInfo *rri,
 						CopyFromState cstate, EState *estate, CommandId mycid,
-						int ti_options)
+						uint32 ti_options)
 {
 	miinfo->multiInsertBuffers = NIL;
 	miinfo->bufferedTuples = 0;
@@ -535,7 +535,7 @@ CopyMultiInsertBufferFlush(CopyMultiInsertInfo *miinfo,
 	else
 	{
 		CommandId	mycid = miinfo->mycid;
-		int			ti_options = miinfo->ti_options;
+		uint32		ti_options = miinfo->ti_options;
 		bool		line_buf_valid = cstate->line_buf_valid;
 		uint64		save_cur_lineno = cstate->cur_lineno;
 		MemoryContext oldcontext;
@@ -792,7 +792,7 @@ CopyFrom(CopyFromState cstate)
 	PartitionTupleRouting *proute = NULL;
 	ErrorContextCallback errcallback;
 	CommandId	mycid = GetCurrentCommandId(true);
-	int			ti_options = 0; /* start with default options for insert */
+	uint32		ti_options = 0; /* start with default options for insert */
 	BulkInsertState bistate = NULL;
 	CopyInsertMethod insertMethod;
 	CopyMultiInsertInfo multiInsertInfo = {0};	/* pacify compiler */
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 270e9bf3110..6dbb831ca89 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -56,7 +56,7 @@ typedef struct
 	Relation	rel;			/* relation to write to */
 	ObjectAddress reladdr;		/* address of rel, for ExecCreateTableAs */
 	CommandId	output_cid;		/* cmin to insert in output tuples */
-	int			ti_options;		/* table_tuple_insert performance options */
+	uint32		ti_options;		/* table_tuple_insert performance options */
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_intorel;
 
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 81a55a33ef2..d3be8939011 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -49,7 +49,7 @@ typedef struct
 	/* These fields are filled by transientrel_startup: */
 	Relation	transientrel;	/* relation to write to */
 	CommandId	output_cid;		/* cmin to insert in output tuples */
-	int			ti_options;		/* table_tuple_insert performance options */
+	uint32		ti_options;		/* table_tuple_insert performance options */
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_transientrel;
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8b4ebc6f226..0ce2e81f9c2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6195,7 +6195,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 	EState	   *estate;
 	CommandId	mycid;
 	BulkInsertState bistate;
-	int			ti_options;
+	uint32		ti_options;
 	ExprState  *partqualstate = NULL;
 
 	/*
@@ -22835,7 +22835,7 @@ MergePartitionsMoveRows(List **wqueue, List *mergingPartitions, Relation newPart
 	ListCell   *ltab;
 
 	/* The FSM is empty, so don't bother using it. */
-	int			ti_options = TABLE_INSERT_SKIP_FSM;
+	uint32		ti_options = TABLE_INSERT_SKIP_FSM;
 	BulkInsertState bistate;	/* state of bulk inserts for partition */
 	TupleTableSlot *dstslot;
 
@@ -23226,7 +23226,7 @@ createSplitPartitionContext(Relation partRel)
  * deleteSplitPartitionContext: delete context for partition
  */
 static void
-deleteSplitPartitionContext(SplitPartitionContext *pc, List **wqueue, int ti_options)
+deleteSplitPartitionContext(SplitPartitionContext *pc, List **wqueue, uint32 ti_options)
 {
 	ListCell   *ltab;
 
@@ -23268,7 +23268,7 @@ SplitPartitionMoveRows(List **wqueue, Relation rel, Relation splitRel,
 					   List *partlist, List *newPartRels)
 {
 	/* The FSM is empty, so don't bother using it. */
-	int			ti_options = TABLE_INSERT_SKIP_FSM;
+	uint32		ti_options = TABLE_INSERT_SKIP_FSM;
 	CommandId	mycid;
 	EState	   *estate;
 	ListCell   *listptr,
diff --git a/src/include/access/heaptoast.h b/src/include/access/heaptoast.h
index 725c0ce7554..631cb1836b9 100644
--- a/src/include/access/heaptoast.h
+++ b/src/include/access/heaptoast.h
@@ -95,7 +95,7 @@
  * ----------
  */
 extern HeapTuple heap_toast_insert_or_update(Relation rel, HeapTuple newtup,
-											 HeapTuple oldtup, int options);
+											 HeapTuple oldtup, uint32 options);
 
 /* ----------
  * heap_toast_delete -
diff --git a/src/include/access/hio.h b/src/include/access/hio.h
index d8e63a54ea5..60cfc375fd5 100644
--- a/src/include/access/hio.h
+++ b/src/include/access/hio.h
@@ -54,7 +54,7 @@ typedef struct BulkInsertStateData
 extern void RelationPutHeapTuple(Relation relation, Buffer buffer,
 								 HeapTuple tuple, bool token);
 extern Buffer RelationGetBufferForTuple(Relation relation, Size len,
-										Buffer otherBuffer, int options,
+										Buffer otherBuffer, uint32 options,
 										BulkInsertStateData *bistate,
 										Buffer *vmbuffer, Buffer *vmbuffer_other,
 										int num_pages);
diff --git a/src/include/access/toast_helper.h b/src/include/access/toast_helper.h
index e8ecb995cb3..2ec92397f26 100644
--- a/src/include/access/toast_helper.h
+++ b/src/include/access/toast_helper.h
@@ -107,7 +107,7 @@ extern int	toast_tuple_find_biggest_attribute(ToastTupleContext *ttc,
 											   bool check_main);
 extern void toast_tuple_try_compression(ToastTupleContext *ttc, int attribute);
 extern void toast_tuple_externalize(ToastTupleContext *ttc, int attribute,
-									int options);
+									uint32 options);
 extern void toast_tuple_cleanup(ToastTupleContext *ttc);
 
 extern void toast_delete_external(Relation rel, const Datum *values, const bool *isnull,
diff --git a/src/include/access/toast_internals.h b/src/include/access/toast_internals.h
index d382db34262..bf45889a642 100644
--- a/src/include/access/toast_internals.h
+++ b/src/include/access/toast_internals.h
@@ -50,7 +50,7 @@ extern Oid	toast_get_valid_index(Oid toastoid, LOCKMODE lock);
 
 extern void toast_delete_datum(Relation rel, Datum value, bool is_speculative);
 extern Datum toast_save_datum(Relation rel, Datum value,
-							  varlena *oldexternal, int options);
+							  varlena *oldexternal, uint32 options);
 
 extern int	toast_open_indexes(Relation toastrel,
 							   LOCKMODE lock,
-- 
2.47.3

  [text/x-diff] v5-0002-Give-options-parameter-to-table_delete-table_upda.patch (12.3K, ../../202604011417.bwthhrwmivd4@alvherre.pgsql/3-v5-0002-Give-options-parameter-to-table_delete-table_upda.patch)
  download | inline diff:
From c8e58c1a0c4aa3d946ceaa655c0ada793350fb85 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Mon, 30 Mar 2026 13:27:42 +0200
Subject: [PATCH v5 2/3] Give 'options' parameter to table_delete/table_update
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The table_insert() method already has an equivalent argument, so this
makes sense just on consistency grounds, for future growth.

table_delete() can immediately use it to carry the 'changingPart'
boolean (which is arguably misplaced in the current API); for
table_update we don't have any options at present, but an upcoming patch
would add one.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/202603171606.kf6pmhscqbqz@alvherre.pgsql
---
 src/backend/access/heap/heapam.c         | 16 +++++++-----
 src/backend/access/heap/heapam_handler.c | 13 ++++++----
 src/backend/access/table/tableam.c       |  6 ++---
 src/backend/executor/nodeModifyTable.c   |  9 +++++--
 src/include/access/heapam.h              |  7 +++---
 src/include/access/tableam.h             | 31 +++++++++++++++---------
 6 files changed, 52 insertions(+), 30 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index d34136d2e94..6bff0032db2 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2862,8 +2862,8 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
  */
 TM_Result
 heap_delete(Relation relation, const ItemPointerData *tid,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, bool changingPart)
+			CommandId cid, uint32 options, Snapshot crosscheck,
+			bool wait, TM_FailureData *tmfd)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
@@ -2876,6 +2876,7 @@ heap_delete(Relation relation, const ItemPointerData *tid,
 	TransactionId new_xmax;
 	uint16		new_infomask,
 				new_infomask2;
+	bool		changingPart = (options & TABLE_DELETE_CHANGING_PARTITION) != 0;
 	bool		have_tuple_lock = false;
 	bool		iscombo;
 	bool		all_visible_cleared = false;
@@ -3290,9 +3291,11 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
 	TM_FailureData tmfd;
 
 	result = heap_delete(relation, tid,
-						 GetCurrentCommandId(true), InvalidSnapshot,
+						 GetCurrentCommandId(true),
+						 0,
+						 InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &tmfd, false /* changingPart */ );
+						 &tmfd);
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -3331,7 +3334,7 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
  */
 TM_Result
 heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
-			CommandId cid, Snapshot crosscheck, bool wait,
+			CommandId cid, uint32 options pg_attribute_unused(), Snapshot crosscheck, bool wait,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
 			TU_UpdateIndexes *update_indexes)
 {
@@ -4585,7 +4588,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 	LockTupleMode lockmode;
 
 	result = heap_update(relation, otid, tup,
-						 GetCurrentCommandId(true), InvalidSnapshot,
+						 GetCurrentCommandId(true), 0,
+						 InvalidSnapshot,
 						 true /* wait for commit */ ,
 						 &tmfd, &lockmode, update_indexes);
 	switch (result)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index cdd153c6b6d..1be8ea4845a 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -313,21 +313,23 @@ heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
 
 static TM_Result
 heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
-					Snapshot snapshot, Snapshot crosscheck, bool wait,
-					TM_FailureData *tmfd, bool changingPart)
+					uint32 options, Snapshot snapshot, Snapshot crosscheck,
+					bool wait, TM_FailureData *tmfd)
 {
 	/*
 	 * Currently Deleting of index tuples are handled at vacuum, in case if
 	 * the storage itself is cleaning the dead tuples by itself, it is the
 	 * time to call the index tuple deletion also.
 	 */
-	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
+	return heap_delete(relation, tid, cid, options, crosscheck, wait,
+					   tmfd);
 }
 
 
 static TM_Result
 heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
-					CommandId cid, Snapshot snapshot, Snapshot crosscheck,
+					CommandId cid, uint32 options,
+					Snapshot snapshot, Snapshot crosscheck,
 					bool wait, TM_FailureData *tmfd,
 					LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes)
 {
@@ -339,7 +341,8 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	slot->tts_tableOid = RelationGetRelid(relation);
 	tuple->t_tableOid = slot->tts_tableOid;
 
-	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
+	result = heap_update(relation, otid, tuple, cid, options,
+						 crosscheck, wait,
 						 tmfd, lockmode, update_indexes);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 86481d7c029..68ff0966f1c 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -320,9 +320,9 @@ simple_table_tuple_delete(Relation rel, ItemPointer tid, Snapshot snapshot)
 
 	result = table_tuple_delete(rel, tid,
 								GetCurrentCommandId(true),
-								snapshot, InvalidSnapshot,
+								0, snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
-								&tmfd, false /* changingPart */ );
+								&tmfd);
 
 	switch (result)
 	{
@@ -369,7 +369,7 @@ simple_table_tuple_update(Relation rel, ItemPointer otid,
 
 	result = table_tuple_update(rel, otid, slot,
 								GetCurrentCommandId(true),
-								snapshot, InvalidSnapshot,
+								0, snapshot, InvalidSnapshot,
 								true /* wait for commit */ ,
 								&tmfd, &lockmode, update_indexes);
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 582bcc367c0..76728f08734 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1522,14 +1522,18 @@ ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 			  ItemPointer tupleid, bool changingPart)
 {
 	EState	   *estate = context->estate;
+	uint32		options = 0;
+
+	if (changingPart)
+		options |= TABLE_DELETE_CHANGING_PARTITION;
 
 	return table_tuple_delete(resultRelInfo->ri_RelationDesc, tupleid,
 							  estate->es_output_cid,
+							  options,
 							  estate->es_snapshot,
 							  estate->es_crosscheck_snapshot,
 							  true /* wait for commit */ ,
-							  &context->tmfd,
-							  changingPart);
+							  &context->tmfd);
 }
 
 /*
@@ -2331,6 +2335,7 @@ lreplace:
 	 */
 	result = table_tuple_update(resultRelationDesc, tupleid, slot,
 								estate->es_output_cid,
+								0,
 								estate->es_snapshot,
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index f46c83e88f3..54067b828e4 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -382,13 +382,14 @@ extern void heap_multi_insert(Relation relation, TupleTableSlot **slots,
 							  int ntuples, CommandId cid, uint32 options,
 							  BulkInsertState bistate);
 extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
-							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, bool changingPart);
+							 CommandId cid, uint32 options, Snapshot crosscheck,
+							 bool wait, TM_FailureData *tmfd);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
 extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
 							 HeapTuple newtup,
-							 CommandId cid, Snapshot crosscheck, bool wait,
+							 CommandId cid, uint32 options,
+							 Snapshot crosscheck, bool wait,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode,
 							 TU_UpdateIndexes *update_indexes);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 57892152957..9b5d7424dda 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -282,13 +282,18 @@ typedef struct TM_IndexDeleteOp
 #define TABLE_INSERT_FROZEN			0x0004
 #define TABLE_INSERT_NO_LOGICAL		0x0008
 
+/* "options" flag bits for table_tuple_delete */
+#define TABLE_DELETE_CHANGING_PARTITION			(1 << 0)
+
+/* "options" flag bits for table_tuple_update */
+/* XXX none at present */
+
 /* flag bits for table_tuple_lock */
 /* Follow tuples whose update is in progress if lock modes don't conflict  */
 #define TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS	(1 << 0)
 /* Follow update chain and lock latest version of tuple */
 #define TUPLE_LOCK_FLAG_FIND_LAST_VERSION		(1 << 1)
 
-
 /* Typedef for callback function for table_index_build_scan */
 typedef void (*IndexBuildCallback) (Relation index,
 									ItemPointer tid,
@@ -559,17 +564,18 @@ typedef struct TableAmRoutine
 	TM_Result	(*tuple_delete) (Relation rel,
 								 ItemPointer tid,
 								 CommandId cid,
+								 uint32 options,
 								 Snapshot snapshot,
 								 Snapshot crosscheck,
 								 bool wait,
-								 TM_FailureData *tmfd,
-								 bool changingPart);
+								 TM_FailureData *tmfd);
 
 	/* see table_tuple_update() for reference about parameters */
 	TM_Result	(*tuple_update) (Relation rel,
 								 ItemPointer otid,
 								 TupleTableSlot *slot,
 								 CommandId cid,
+								 uint32 options,
 								 Snapshot snapshot,
 								 Snapshot crosscheck,
 								 bool wait,
@@ -1516,10 +1522,11 @@ table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots,
  *	tid - TID of tuple to be deleted
  *	cid - delete command ID (used for visibility test, and stored into
  *		cmax if successful)
+ *	options - bitmask of options.  Supported values:
+ *		TABLE_DELETE_CHANGING_PARTITION: the tuple is being moved to another
+ *		partition table due to an update of the partition key.
  *	crosscheck - if not InvalidSnapshot, also check tuple against this
  *	wait - true if should wait for any conflicting update to commit/abort
- *	changingPart - true iff the tuple is being moved to another partition
- *		table due to an update of the partition key. Otherwise, false.
  *
  * Output parameters:
  *	tmfd - filled in failure cases (see below)
@@ -1534,12 +1541,12 @@ table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots,
  */
 static inline TM_Result
 table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
-				   Snapshot snapshot, Snapshot crosscheck, bool wait,
-				   TM_FailureData *tmfd, bool changingPart)
+				   uint32 options, Snapshot snapshot, Snapshot crosscheck,
+				   bool wait, TM_FailureData *tmfd)
 {
-	return rel->rd_tableam->tuple_delete(rel, tid, cid,
+	return rel->rd_tableam->tuple_delete(rel, tid, cid, options,
 										 snapshot, crosscheck,
-										 wait, tmfd, changingPart);
+										 wait, tmfd);
 }
 
 /*
@@ -1553,6 +1560,7 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
  *	otid - TID of old tuple to be replaced
  *	cid - update command ID (used for visibility test, and stored into
  *		cmax/cmin if successful)
+ *	options - bitmask of options.  No values are currently recognized.
  *	crosscheck - if not InvalidSnapshot, also check old tuple against this
  *	wait - true if should wait for any conflicting update to commit/abort
  *
@@ -1579,12 +1587,13 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
  */
 static inline TM_Result
 table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot,
-				   CommandId cid, Snapshot snapshot, Snapshot crosscheck,
+				   CommandId cid, uint32 options,
+				   Snapshot snapshot, Snapshot crosscheck,
 				   bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
 				   TU_UpdateIndexes *update_indexes)
 {
 	return rel->rd_tableam->tuple_update(rel, otid, slot,
-										 cid, snapshot, crosscheck,
+										 cid, options, snapshot, crosscheck,
 										 wait, tmfd,
 										 lockmode, update_indexes);
 }
-- 
2.47.3

  [text/x-diff] v5-0003-Define-heap_insert-to-obey-tableam.h-option-bits-.patch (8.7K, ../../202604011417.bwthhrwmivd4@alvherre.pgsql/4-v5-0003-Define-heap_insert-to-obey-tableam.h-option-bits-.patch)
  download | inline diff:
From 6d527c234238cc2085fe7552fb840a59bb5ecf80 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Sun, 29 Mar 2026 23:11:42 +0200
Subject: [PATCH v5 3/3] Define heap_insert to obey tableam.h option bits
 directly

Redefining the bits at the heapam.h interface serves no purpose.

Also, whenever somebody next defines a new TABLE_INSERT_* bit, the value
value would collide with HEAP_INSERT_SPECULATIVE.  Move the latter to
the other end of the bits word.
---
 src/backend/access/heap/heapam.c         | 20 ++++++++++----------
 src/backend/access/heap/hio.c            | 10 +++++-----
 src/backend/access/heap/rewriteheap.c    |  4 ++--
 src/backend/replication/logical/decode.c |  2 +-
 src/include/access/heapam.h              | 11 ++++++-----
 5 files changed, 24 insertions(+), 23 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 6bff0032db2..efcde4e8094 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2138,9 +2138,9 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate)
  * See table_tuple_insert for comments about most of the input flags, except
  * that this routine directly takes a tuple rather than a slot.
  *
- * There's corresponding HEAP_INSERT_ options to all the TABLE_INSERT_
- * options, and there additionally is HEAP_INSERT_SPECULATIVE which is used to
- * implement table_tuple_insert_speculative().
+ * In addition to the TABLE_INSERT_ options, there additionally is
+ * HEAP_INSERT_SPECULATIVE which is used to implement
+ * table_tuple_insert_speculative().
  *
  * On return the header fields of *tup are updated to match the stored tuple;
  * in particular tup->t_self receives the actual TID where the tuple was
@@ -2228,7 +2228,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 	 * Don't set it if we are in bootstrap mode or we are inserting a frozen
 	 * tuple, as there is no further pruning/freezing needed in those cases.
 	 */
-	if (TransactionIdIsNormal(xid) && !(options & HEAP_INSERT_FROZEN))
+	if (TransactionIdIsNormal(xid) && !(options & TABLE_INSERT_FROZEN))
 		PageSetPrunable(page, xid);
 
 	MarkBufferDirty(buffer);
@@ -2275,7 +2275,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		 * image. (XXX We could alternatively store a pointer into the FPW).
 		 */
 		if (RelationIsLogicallyLogged(relation) &&
-			!(options & HEAP_INSERT_NO_LOGICAL))
+			!(options & TABLE_INSERT_NO_LOGICAL))
 		{
 			xlrec.flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
 			bufflags |= REGBUF_KEEP_DATA;
@@ -2364,7 +2364,7 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 	tup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
 	tup->t_data->t_infomask |= HEAP_XMAX_INVALID;
 	HeapTupleHeaderSetXmin(tup->t_data, xid);
-	if (options & HEAP_INSERT_FROZEN)
+	if (options & TABLE_INSERT_FROZEN)
 		HeapTupleHeaderSetXminFrozen(tup->t_data);
 
 	HeapTupleHeaderSetCmin(tup->t_data, cid);
@@ -2445,7 +2445,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 	int			npages_used = 0;
 
 	/* currently not needed (thus unsupported) for heap_multi_insert() */
-	Assert(!(options & HEAP_INSERT_NO_LOGICAL));
+	Assert(!(options & TABLE_INSERT_NO_LOGICAL));
 
 	AssertHasSnapshotForToast(relation);
 
@@ -2535,7 +2535,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 		starting_with_empty_page = PageGetMaxOffsetNumber(page) == 0;
 
-		if (starting_with_empty_page && (options & HEAP_INSERT_FROZEN))
+		if (starting_with_empty_page && (options & TABLE_INSERT_FROZEN))
 		{
 			all_frozen_set = true;
 			/* Lock the vmbuffer before entering the critical section */
@@ -2583,7 +2583,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 		 * page, mark it as all-frozen and update the visibility map. We're
 		 * already holding a pin on the vmbuffer.
 		 */
-		if (PageIsAllVisible(page) && !(options & HEAP_INSERT_FROZEN))
+		if (PageIsAllVisible(page) && !(options & TABLE_INSERT_FROZEN))
 		{
 			all_visible_cleared = true;
 			PageClearAllVisible(page);
@@ -2655,7 +2655,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 			/*
 			 * We don't have to worry about including a conflict xid in the
-			 * WAL record, as HEAP_INSERT_FROZEN intentionally violates
+			 * WAL record, as TABLE_INSERT_FROZEN intentionally violates
 			 * visibility rules.
 			 */
 			if (all_frozen_set)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index e96e0f77d92..17f87f693ea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -469,12 +469,12 @@ RelationAddBlocks(Relation relation, BulkInsertState bistate,
  *	which is indicated by these arguments not being InvalidBuffer on entry.
  *
  *	We normally use FSM to help us find free space.  However,
- *	if HEAP_INSERT_SKIP_FSM is specified, we just append a new empty page to
+ *	if TABLE_INSERT_SKIP_FSM is specified, we just append a new empty page to
  *	the end of the relation if the tuple won't fit on the current target page.
  *	This can save some cycles when we know the relation is new and doesn't
  *	contain useful amounts of free space.
  *
- *	HEAP_INSERT_SKIP_FSM is also useful for non-WAL-logged additions to a
+ *	TABLE_INSERT_SKIP_FSM is also useful for non-WAL-logged additions to a
  *	relation, if the caller holds exclusive lock and is careful to invalidate
  *	relation's smgr_targblock before the first insertion --- that ensures that
  *	all insertions will occur into newly added pages and not be intermixed
@@ -503,7 +503,7 @@ RelationGetBufferForTuple(Relation relation, Size len,
 						  Buffer *vmbuffer, Buffer *vmbuffer_other,
 						  int num_pages)
 {
-	bool		use_fsm = !(options & HEAP_INSERT_SKIP_FSM);
+	bool		use_fsm = !(options & TABLE_INSERT_SKIP_FSM);
 	Buffer		buffer = InvalidBuffer;
 	Page		page;
 	Size		nearlyEmptyFreeSpace,
@@ -621,7 +621,7 @@ loop:
 			/*
 			 * If the page is empty, pin vmbuffer to set all_frozen bit later.
 			 */
-			if ((options & HEAP_INSERT_FROZEN) &&
+			if ((options & TABLE_INSERT_FROZEN) &&
 				(PageGetMaxOffsetNumber(BufferGetPage(buffer)) == 0))
 				visibilitymap_pin(relation, targetBlock, vmbuffer);
 
@@ -774,7 +774,7 @@ loop:
 	 * do IO while the buffer is locked, so we unlock the page first if IO is
 	 * needed (necessitating checks below).
 	 */
-	if (options & HEAP_INSERT_FROZEN)
+	if (options & TABLE_INSERT_FROZEN)
 	{
 		Assert(PageGetMaxOffsetNumber(page) == 0);
 
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index f707b102c72..ce4f41672f9 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -618,14 +618,14 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 	}
 	else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD)
 	{
-		uint32		options = HEAP_INSERT_SKIP_FSM;
+		uint32		options = TABLE_INSERT_SKIP_FSM;
 
 		/*
 		 * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data
 		 * for the TOAST table are not logically decoded.  The main heap is
 		 * WAL-logged as XLOG FPI records, which are not logically decoded.
 		 */
-		options |= HEAP_INSERT_NO_LOGICAL;
+		options |= TABLE_INSERT_NO_LOGICAL;
 
 		heaptup = heap_toast_insert_or_update(state->rs_new_rel, tup, NULL,
 											  options);
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 3c027bcb2f7..d80a26ce88e 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -898,7 +898,7 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	/*
 	 * Ignore insert records without new tuples (this does happen when
-	 * raw_heap_insert marks the TOAST record as HEAP_INSERT_NO_LOGICAL).
+	 * raw_heap_insert marks the TOAST record as TABLE_INSERT_NO_LOGICAL).
 	 */
 	if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
 		return;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 54067b828e4..20e952ec64f 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -32,11 +32,12 @@
 #include "utils/snapshot.h"
 
 
-/* "options" flag bits for heap_insert */
-#define HEAP_INSERT_SKIP_FSM	TABLE_INSERT_SKIP_FSM
-#define HEAP_INSERT_FROZEN		TABLE_INSERT_FROZEN
-#define HEAP_INSERT_NO_LOGICAL	TABLE_INSERT_NO_LOGICAL
-#define HEAP_INSERT_SPECULATIVE 0x0010
+/*
+ * "options" flag bits for heap_insert.  That routine also obeys bits defined
+ * for table_tuple_insert; see tableam.h.  We define these starting from the
+ * opposite end of the options word.
+ */
+#define HEAP_INSERT_SPECULATIVE (1 << 31)
 
 /* "options" flag bits for heap_page_prune_and_freeze */
 #define HEAP_PAGE_PRUNE_MARK_UNUSED_NOW		(1 << 0)
-- 
2.47.3

^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-04-01 15:16  Antonin Houska <ah@cybertec.at>
  parent: Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 1 reply; 17+ messages in thread

From: Antonin Houska @ 2026-04-01 15:16 UTC (permalink / raw)
  To: alvherre@kurilemu.de; +Cc: Chao Li <li.evan.chao@gmail.com>; Andres Freund <andres@anarazel.de>; Pg Hackers <pgsql-hackers@lists.postgresql.org>; Zsolt Parragi <zsolt.parragi@percona.com>; Nathan Bossart <nathandbossart@gmail.com>

Álvaro Herrera <alvherre@kurilemu.de> wrote:

> Hmm, this is actually a problem in 1bd6f22f43ac.  I added a preliminary
> patch that should fix this.

I've reviewed this patch set too. The only question that occurs to me is
whether INSERT_SKIP_FSM and INSERT_FROZEN shouldn't actually be prefixed with
HEAP_, as these are rather low-level and thus might be considered
AM-specific.

(I like the idea to use the high bits to avoid collisions.)

-- 
Antonin Houska
Web: https://www.cybertec-postgresql.com





^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-04-01 18:41  Álvaro Herrera <alvherre@kurilemu.de>
  parent: Antonin Houska <ah@cybertec.at>
  0 siblings, 1 reply; 17+ messages in thread

From: Álvaro Herrera @ 2026-04-01 18:41 UTC (permalink / raw)
  To: Antonin Houska <ah@cybertec.at>; +Cc: Chao Li <li.evan.chao@gmail.com>; Andres Freund <andres@anarazel.de>; Pg Hackers <pgsql-hackers@lists.postgresql.org>; Zsolt Parragi <zsolt.parragi@percona.com>; Nathan Bossart <nathandbossart@gmail.com>

On 2026-Apr-01, Antonin Houska wrote:

> I've reviewed this patch set too. The only question that occurs to me is
> whether INSERT_SKIP_FSM and INSERT_FROZEN shouldn't actually be prefixed with
> HEAP_, as these are rather low-level and thus might be considered
> AM-specific.

Thanks!  I pushed 0001 and 0002 from this patchset, with minimal
cosmetic corrections.

I realized that patch 0003 is doing two different things, and they
should each be their own patch which can be rejected if we don't like
them; so I split it in two.  One moves the heapam.h-private bit to the
32th bit.  The other removes the duplicity of definitions, so that
heapam.h doesn't have to feel it needs to redefine the tableam.h
interface.

At this point these patches could be considered WIP in the sense that I
wouldn't commit exactly as is (minor corrections might be appropriate,
for example to the nearby comments), but I would like to know opinions
in case we decide to just throw them away.

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/
"Las navajas y los monos deben estar siempre distantes"   (Germán Poo)

Attachments:

  [text/x-diff] v6-0001-Move-heapam.h-specific-flag-bit.patch (1.0K, ../../202604011827.p4mx4bh2ti46@alvherre.pgsql/2-v6-0001-Move-heapam.h-specific-flag-bit.patch)
  download | inline diff:
From d57ef52cf5122a5911afb58929e756a9550554aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Wed, 1 Apr 2026 20:31:28 +0200
Subject: [PATCH v6 1/2] Move heapam.h-specific flag bit

This avoids possible future collisions.
---
 src/include/access/heapam.h | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 54067b828e4..a542e89fd29 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -36,7 +36,11 @@
 #define HEAP_INSERT_SKIP_FSM	TABLE_INSERT_SKIP_FSM
 #define HEAP_INSERT_FROZEN		TABLE_INSERT_FROZEN
 #define HEAP_INSERT_NO_LOGICAL	TABLE_INSERT_NO_LOGICAL
-#define HEAP_INSERT_SPECULATIVE 0x0010
+/*
+ * private flag bits for heap_insert: we define these starting from the
+ * opposite end of the options word as the ones in tableam.h.
+ */
+#define HEAP_INSERT_SPECULATIVE (1 << 31)
 
 /* "options" flag bits for heap_page_prune_and_freeze */
 #define HEAP_PAGE_PRUNE_MARK_UNUSED_NOW		(1 << 0)
-- 
2.47.3

  [text/x-diff] v6-0002-Define-heap_insert-to-obey-tableam.h-option-bits-.patch (8.4K, ../../202604011827.p4mx4bh2ti46@alvherre.pgsql/3-v6-0002-Define-heap_insert-to-obey-tableam.h-option-bits-.patch)
  download | inline diff:
From 01e0cd2dcbe46a1fb93d1d678abcf63994aeb76e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Sun, 29 Mar 2026 23:11:42 +0200
Subject: [PATCH v6 2/2] Define heap_insert to obey tableam.h option bits
 directly

Redefining the bits at the heapam.h interface serves no purpose.

Also, whenever somebody next defines a new TABLE_INSERT_* bit, the value
value would collide with HEAP_INSERT_SPECULATIVE.  Move the latter to
the other end of the bits word.
---
 src/backend/access/heap/heapam.c         | 20 ++++++++++----------
 src/backend/access/heap/hio.c            | 10 +++++-----
 src/backend/access/heap/rewriteheap.c    |  4 ++--
 src/backend/replication/logical/decode.c |  2 +-
 src/include/access/heapam.h              |  4 ----
 5 files changed, 18 insertions(+), 22 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 6bff0032db2..efcde4e8094 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2138,9 +2138,9 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate)
  * See table_tuple_insert for comments about most of the input flags, except
  * that this routine directly takes a tuple rather than a slot.
  *
- * There's corresponding HEAP_INSERT_ options to all the TABLE_INSERT_
- * options, and there additionally is HEAP_INSERT_SPECULATIVE which is used to
- * implement table_tuple_insert_speculative().
+ * In addition to the TABLE_INSERT_ options, there additionally is
+ * HEAP_INSERT_SPECULATIVE which is used to implement
+ * table_tuple_insert_speculative().
  *
  * On return the header fields of *tup are updated to match the stored tuple;
  * in particular tup->t_self receives the actual TID where the tuple was
@@ -2228,7 +2228,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 	 * Don't set it if we are in bootstrap mode or we are inserting a frozen
 	 * tuple, as there is no further pruning/freezing needed in those cases.
 	 */
-	if (TransactionIdIsNormal(xid) && !(options & HEAP_INSERT_FROZEN))
+	if (TransactionIdIsNormal(xid) && !(options & TABLE_INSERT_FROZEN))
 		PageSetPrunable(page, xid);
 
 	MarkBufferDirty(buffer);
@@ -2275,7 +2275,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 		 * image. (XXX We could alternatively store a pointer into the FPW).
 		 */
 		if (RelationIsLogicallyLogged(relation) &&
-			!(options & HEAP_INSERT_NO_LOGICAL))
+			!(options & TABLE_INSERT_NO_LOGICAL))
 		{
 			xlrec.flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
 			bufflags |= REGBUF_KEEP_DATA;
@@ -2364,7 +2364,7 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 	tup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
 	tup->t_data->t_infomask |= HEAP_XMAX_INVALID;
 	HeapTupleHeaderSetXmin(tup->t_data, xid);
-	if (options & HEAP_INSERT_FROZEN)
+	if (options & TABLE_INSERT_FROZEN)
 		HeapTupleHeaderSetXminFrozen(tup->t_data);
 
 	HeapTupleHeaderSetCmin(tup->t_data, cid);
@@ -2445,7 +2445,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 	int			npages_used = 0;
 
 	/* currently not needed (thus unsupported) for heap_multi_insert() */
-	Assert(!(options & HEAP_INSERT_NO_LOGICAL));
+	Assert(!(options & TABLE_INSERT_NO_LOGICAL));
 
 	AssertHasSnapshotForToast(relation);
 
@@ -2535,7 +2535,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 		starting_with_empty_page = PageGetMaxOffsetNumber(page) == 0;
 
-		if (starting_with_empty_page && (options & HEAP_INSERT_FROZEN))
+		if (starting_with_empty_page && (options & TABLE_INSERT_FROZEN))
 		{
 			all_frozen_set = true;
 			/* Lock the vmbuffer before entering the critical section */
@@ -2583,7 +2583,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 		 * page, mark it as all-frozen and update the visibility map. We're
 		 * already holding a pin on the vmbuffer.
 		 */
-		if (PageIsAllVisible(page) && !(options & HEAP_INSERT_FROZEN))
+		if (PageIsAllVisible(page) && !(options & TABLE_INSERT_FROZEN))
 		{
 			all_visible_cleared = true;
 			PageClearAllVisible(page);
@@ -2655,7 +2655,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 			/*
 			 * We don't have to worry about including a conflict xid in the
-			 * WAL record, as HEAP_INSERT_FROZEN intentionally violates
+			 * WAL record, as TABLE_INSERT_FROZEN intentionally violates
 			 * visibility rules.
 			 */
 			if (all_frozen_set)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index e96e0f77d92..17f87f693ea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -469,12 +469,12 @@ RelationAddBlocks(Relation relation, BulkInsertState bistate,
  *	which is indicated by these arguments not being InvalidBuffer on entry.
  *
  *	We normally use FSM to help us find free space.  However,
- *	if HEAP_INSERT_SKIP_FSM is specified, we just append a new empty page to
+ *	if TABLE_INSERT_SKIP_FSM is specified, we just append a new empty page to
  *	the end of the relation if the tuple won't fit on the current target page.
  *	This can save some cycles when we know the relation is new and doesn't
  *	contain useful amounts of free space.
  *
- *	HEAP_INSERT_SKIP_FSM is also useful for non-WAL-logged additions to a
+ *	TABLE_INSERT_SKIP_FSM is also useful for non-WAL-logged additions to a
  *	relation, if the caller holds exclusive lock and is careful to invalidate
  *	relation's smgr_targblock before the first insertion --- that ensures that
  *	all insertions will occur into newly added pages and not be intermixed
@@ -503,7 +503,7 @@ RelationGetBufferForTuple(Relation relation, Size len,
 						  Buffer *vmbuffer, Buffer *vmbuffer_other,
 						  int num_pages)
 {
-	bool		use_fsm = !(options & HEAP_INSERT_SKIP_FSM);
+	bool		use_fsm = !(options & TABLE_INSERT_SKIP_FSM);
 	Buffer		buffer = InvalidBuffer;
 	Page		page;
 	Size		nearlyEmptyFreeSpace,
@@ -621,7 +621,7 @@ loop:
 			/*
 			 * If the page is empty, pin vmbuffer to set all_frozen bit later.
 			 */
-			if ((options & HEAP_INSERT_FROZEN) &&
+			if ((options & TABLE_INSERT_FROZEN) &&
 				(PageGetMaxOffsetNumber(BufferGetPage(buffer)) == 0))
 				visibilitymap_pin(relation, targetBlock, vmbuffer);
 
@@ -774,7 +774,7 @@ loop:
 	 * do IO while the buffer is locked, so we unlock the page first if IO is
 	 * needed (necessitating checks below).
 	 */
-	if (options & HEAP_INSERT_FROZEN)
+	if (options & TABLE_INSERT_FROZEN)
 	{
 		Assert(PageGetMaxOffsetNumber(page) == 0);
 
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index f707b102c72..ce4f41672f9 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -618,14 +618,14 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 	}
 	else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD)
 	{
-		uint32		options = HEAP_INSERT_SKIP_FSM;
+		uint32		options = TABLE_INSERT_SKIP_FSM;
 
 		/*
 		 * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data
 		 * for the TOAST table are not logically decoded.  The main heap is
 		 * WAL-logged as XLOG FPI records, which are not logically decoded.
 		 */
-		options |= HEAP_INSERT_NO_LOGICAL;
+		options |= TABLE_INSERT_NO_LOGICAL;
 
 		heaptup = heap_toast_insert_or_update(state->rs_new_rel, tup, NULL,
 											  options);
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 3c027bcb2f7..d80a26ce88e 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -898,7 +898,7 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	/*
 	 * Ignore insert records without new tuples (this does happen when
-	 * raw_heap_insert marks the TOAST record as HEAP_INSERT_NO_LOGICAL).
+	 * raw_heap_insert marks the TOAST record as TABLE_INSERT_NO_LOGICAL).
 	 */
 	if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
 		return;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index a542e89fd29..0c5a776587e 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -32,10 +32,6 @@
 #include "utils/snapshot.h"
 
 
-/* "options" flag bits for heap_insert */
-#define HEAP_INSERT_SKIP_FSM	TABLE_INSERT_SKIP_FSM
-#define HEAP_INSERT_FROZEN		TABLE_INSERT_FROZEN
-#define HEAP_INSERT_NO_LOGICAL	TABLE_INSERT_NO_LOGICAL
 /*
  * private flag bits for heap_insert: we define these starting from the
  * opposite end of the options word as the ones in tableam.h.
-- 
2.47.3

^ permalink  raw  reply  [nested|flat] 17+ messages in thread

* Re: table AM option passing
@ 2026-04-02 05:39  Antonin Houska <ah@cybertec.at>
  parent: Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 17+ messages in thread

From: Antonin Houska @ 2026-04-02 05:39 UTC (permalink / raw)
  To: alvherre@kurilemu.de; +Cc: Chao Li <li.evan.chao@gmail.com>; Andres Freund <andres@anarazel.de>; Pg Hackers <pgsql-hackers@lists.postgresql.org>; Zsolt Parragi <zsolt.parragi@percona.com>; Nathan Bossart <nathandbossart@gmail.com>

Álvaro Herrera <alvherre@kurilemu.de> wrote:

> On 2026-Apr-01, Antonin Houska wrote:

> I realized that patch 0003 is doing two different things, and they
> should each be their own patch which can be rejected if we don't like
> them; so I split it in two.  One moves the heapam.h-private bit to the
> 32th bit.

I'm sorry I haven't recalled yesterday, but this technique resembles the
DSM keys in parallel.c:


  /*
   * Magic numbers for per-context parallel state sharing.  Higher-level code
   * should use smaller values, leaving these very large ones for use by this
   * module.
   */
  #define PARALLEL_KEY_FIXED		UINT64CONST(0xFFFFFFFFFFFF0001)
  ...


What I found inspiring here is that the "core" uses the high bits while users
of the API use the lower ones. Perhaps it'd be appropriate in v6-0001 to
reserve the high bits for the TABLE_ options and leave the lower ones for the
HEAP_ options. If someone implements a new AM (possibly as an extension), it
should be more comfortable for him.

-- 
Antonin Houska
Web: https://www.cybertec-postgresql.com





^ permalink  raw  reply  [nested|flat] 17+ messages in thread


end of thread, other threads:[~2026-04-02 05:39 UTC | newest]

Thread overview: 17+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-03-17 16:50 table AM option passing Álvaro Herrera <alvherre@kurilemu.de>
2026-03-17 17:31 ` Nathan Bossart <nathandbossart@gmail.com>
2026-03-17 19:47   ` Álvaro Herrera <alvherre@kurilemu.de>
2026-03-17 20:04     ` Nathan Bossart <nathandbossart@gmail.com>
2026-03-17 21:09     ` Andres Freund <andres@anarazel.de>
2026-03-18 21:17       ` Nathan Bossart <nathandbossart@gmail.com>
2026-03-17 21:01 ` Andres Freund <andres@anarazel.de>
2026-03-29 21:38   ` Álvaro Herrera <alvherre@kurilemu.de>
2026-03-30 12:08     ` Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 16:10       ` Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 23:20         ` Zsolt Parragi <zsolt.parragi@percona.com>
2026-04-01 06:33         ` Chao Li <li.evan.chao@gmail.com>
2026-04-01 14:27           ` Álvaro Herrera <alvherre@kurilemu.de>
2026-04-01 15:16             ` Antonin Houska <ah@cybertec.at>
2026-04-01 18:41               ` Álvaro Herrera <alvherre@kurilemu.de>
2026-04-02 05:39                 ` Antonin Houska <ah@cybertec.at>
2026-03-18 20:20 ` Zsolt Parragi <zsolt.parragi@percona.com>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox