Hi hackers,

Attached is a patch introducing a new transaction-local GUC parameter:

    SET LOCAL immediate_global_insert_visibility = '<list of tables>';

While a table is listed in immediate_global_insert_visibility (which defaults
to ""), any rows inserted into that table via INSERT, INSERT ... ON CONFLICT,
or COPY are marked frozen in place (HEAP_XMIN_FROZEN) immediately after index
tuple insertion succeeds and flushed to WAL via XLogFlush().

Motivation & Use Cases
======================

Many workloads need a lightweight way to record durable, rollback-safe rows
from inside a transaction without the overhead of opening a separate backend
connection (e.g., via dblink) to simulate an autonomous transaction:

1. Rollback-Safe Audit / Error / Security Logging:
   Triggers, procedures, or application transactions can append audit or
   diagnostic records into a log table that must persist even if the enclosing
   transaction later fails a constraint check or executes ROLLBACK.

2. Simplified / Crash-Safe Two-Phase Commit (2PC) Coordinator Logging:
   When a local transaction coordinates distributed 2PC across remote shards
   (for example in PL/Proxy or federated FDW setups), the coordinator can
   record the Global Transaction ID (GID) into a local tracking table with
   immediate_global_insert_visibility enabled right before sending
   PREPARE TRANSACTION '<gid>' to remote nodes, and DELETE that row in the
   same local transaction once COMMIT PREPARED / ROLLBACK PREPARED succeeds:
   - Because the inserted row is frozen and WAL-flushed immediately, it is
     immediately visible to concurrent recovery monitors and survives even if
     the local transaction aborts or the local PostgreSQL instance crashes
     before 2PC completes.
   - When the coordinator subsequently executes DELETE on the row in the same
     transaction, HeapTupleHeaderAdjustCmax() sees HEAP_XMIN_COMMITTED and
     records a normal xmax (without allocating a ComboCid). Thus, the GID row
     remains visible to concurrent sessions while the deleting transaction is
     in progress, disappears when the deleting transaction commits, and
     survives if the deleting transaction aborts after the DELETE.

3. Progress reporting from inside a long-running transaction

Design & Implementation Details
===============================

1. SET LOCAL Enforcement (GUC_LOCAL_ONLY):
   To prevent accidental session-wide or cluster-wide activation, a new GUC
   flag GUC_LOCAL_ONLY (0x020000) is enforced in set_config_with_handle().
   Attempting to set immediate_global_insert_visibility via SET (without
   LOCAL), RESET, set_config(..., is_local=false), or configuration files is
   rejected with an error. At transaction or subtransaction end, the GUC stack
   automatically restores the previous setting.

2. Eager Relation OID Resolution:
   In check_immediate_global_insert_visibility(), the comma-separated table
   list is eagerly parsed and resolved to relation OIDs stored in GUC extra
   state. Partitioned tables (RELKIND_PARTITIONED_TABLE) are automatically
   expanded to include all leaf partitions via find_all_inheritors().

3. Freezing After ExecInsertIndexTuples() Succeeds:
   Rather than setting HEAP_XMIN_FROZEN before index insertion, the main heap
   tuple is inserted normally and frozen right after ExecInsertIndexTuples()
   succeeds via heap_freeze_inserted_tuple(), which sets HEAP_XMIN_FROZEN via
   heap_execute_freeze_tuple() and logs an XLOG_HEAP2_PRUNE_ON_ACCESS WAL
   record via log_heap_prune_and_freeze().
   This ensures that if a NOT NULL, CHECK, BEFORE trigger, or UNIQUE index
   constraint fails (or a speculative conflict occurs in INSERT ... ON
   CONFLICT), the heap tuple is never frozen and rolls back cleanly without
   leaving unindexed ghost tuples in the heap.

4. TOAST Chunk Handling (HEAP_INSERT_IMMEDIATE_FROZEN):
   Out-of-line TOAST chunks created during heap_prepare_insert() are frozen
   using a dedicated HEAP_INSERT_IMMEDIATE_FROZEN flag so that VACUUM never
   removes TOAST chunks belonging to a frozen main tuple whose inserting XID
   later aborted, while avoiding COPY FREEZE's visibility-map assertions in
   RelationGetBufferForTuple().

5. Configurable WAL Durability ('sync' vs 'async' mode):
   Each table entry (or the list) supports an optional durability modifier of
   'sync' (the default) or 'async' (e.g., 'tab1, tab2:async' or
   'async:tab1, tab2').
   - In 'sync' mode (default), single-row inserts flush WAL immediately in
     heap_freeze_inserted_tuple(), and multi-insert batches in
     CopyMultiInsertBufferFlush() perform a single XLogFlush() for the highest
     freeze LSN at the end of the batch, providing immediate crash safety.
   - In 'async' mode, the freeze WAL record is still logged and the tuple is
     immediately visible and rollback-safe in shared buffers, but XLogFlush()
     is skipped to avoid per-insert fsync latency.

6. Halloween Problem Protection:
   In HeapTupleSatisfiesMVCC(), when immediate_insert_visibility_active is
   true, frozen tuples whose raw xmin matches the current transaction and
   whose cmin >= snapshot->curcid are treated as invisible to the inserting
   command itself, preventing infinite loops in INSERT INTO t SELECT * FROM t
   while remaining immediately visible to subsequent commands and concurrent
   sessions.

Testing
=======

- Regression tests (src/test/regress/sql/immediate_insert_visibility.sql):
  Covers SET LOCAL enforcement, basic rollback survival, TOAST survival across
  ROLLBACK + VACUUM FREEZE, unique constraint failure rollback, Halloween
  problem protection, 2PC GID insert-then-delete patterns, INSERT ... ON
  CONFLICT, COPY multi-insert, partitioned tables, and role privilege checks.
- Isolation tests (src/test/isolation/specs/immediate-insert-visibility.spec):
  Verifies immediate visibility to concurrent sessions (including under a
  pre-existing REPEATABLE READ snapshot) while the inserting transaction is in
  progress, survival after ROLLBACK, and cross-session visibility during the
  2PC GID insert/delete lifecycle.

Feedback and comments are very welcome.

Best regards,
Hannu Krosing
From 63828d3736e0c20915a7d7a7186ddf8c6fc052fa Mon Sep 17 00:00:00 2001
From: Hannu Krosing <[email protected]>
Date: Sat, 17 Oct 2026 10:28:37 +0000
Subject: [PATCH v1] Add immediate_global_insert_visibility GUC for immediate
 frozen inserts

Introduce a transaction-local GUC parameter,
immediate_global_insert_visibility, which accepts a comma-separated list
of tables (expanding partitioned tables to their leaf partitions).
While a table is listed in immediate_global_insert_visibility, any rows
inserted into that table via INSERT, INSERT ... ON CONFLICT, or COPY are
frozen in place (HEAP_XMIN_FROZEN) immediately after index tuple
insertion succeeds and flushed to WAL via XLogFlush().

This provides a lightweight, low-overhead mechanism for autonomous
insert visibility and durability:
- Newly inserted rows become immediately visible to all concurrent
  snapshots (including pre-existing REPEATABLE READ / SERIALIZABLE
  snapshots) before the inserting transaction finishes.
- Inserted rows survive if the inserting transaction later executes
  ROLLBACK, aborts due to an error, or if the server crashes before the
  local transaction finishes.
- If the same transaction subsequently deletes or updates the frozen row
  (for example, a 2PC coordinator recording a prepared transaction GID
  locally before PREPARE TRANSACTION and deleting the entry after COMMIT
  PREPARED / ROLLBACK PREPARED), HeapTupleHeaderAdjustCmax() sees
  HEAP_XMIN_COMMITTED and writes a normal xmax. The row remains visible
  to concurrent recovery monitors until the deleting transaction commits,
  and survives if the deleting transaction aborts.

Implementation details:
- Add GUC_LOCAL_ONLY (0x020000) in guc.h/guc.c to enforce that
  immediate_global_insert_visibility can only be modified via SET LOCAL
  (or set_config(..., is_local=true)).
- Eagerly resolve listed relation names (and partition trees via
  find_all_inheritors()) into an OID array stored in GUC extra state.
- Freeze the main heap tuple via heap_freeze_inserted_tuple() right
  after ExecInsertIndexTuples() succeeds, WAL-logging via
  log_heap_prune_and_freeze(..., PRUNE_ON_ACCESS). This guarantees that
  a unique constraint violation or index error never leaves an unindexed
  frozen tuple on the heap page.
- Freeze out-of-line TOAST chunks via HEAP_INSERT_IMMEDIATE_FROZEN
  without triggering COPY FREEZE's visibility-map assertions in hio.c.
- Call XLogFlush() per single-row insert and once per batch in
  CopyMultiInsertBufferFlush().
- Guard against the Halloween problem in HeapTupleSatisfiesMVCC() when
  immediate_insert_visibility_active is set.
---
 doc/src/sgml/config.sgml                      |  40 ++
 src/backend/access/heap/heapam.c              | 450 +++++++++++++++++-
 src/backend/access/heap/heapam_visibility.c   |   6 +
 src/backend/commands/copyfrom.c               | 106 +++--
 src/backend/executor/nodeModifyTable.c        |  17 +
 src/backend/utils/misc/guc.c                  |  12 +
 src/backend/utils/misc/guc_parameters.dat     |   9 +
 src/backend/utils/misc/guc_tables.c           |   1 +
 src/include/access/heapam.h                   |   8 +
 src/include/utils/guc.h                       |   1 +
 src/include/utils/guc_hooks.h                 |   4 +
 .../expected/immediate-insert-visibility.out  |  94 ++++
 src/test/isolation/isolation_schedule         |   1 +
 .../specs/immediate-insert-visibility.spec    |  45 ++
 .../expected/immediate_insert_visibility.out  | 280 +++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 .../sql/immediate_insert_visibility.sql       | 205 ++++++++
 17 files changed, 1246 insertions(+), 35 deletions(-)
 create mode 100644 src/test/isolation/expected/immediate-insert-visibility.out
 create mode 100644 src/test/isolation/specs/immediate-insert-visibility.spec
 create mode 100644 src/test/regress/expected/immediate_insert_visibility.out
 create mode 100644 src/test/regress/sql/immediate_insert_visibility.sql

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e537b3b0043..03401d615d4 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -11107,6 +11107,46 @@ SET XML OPTION { DOCUMENT | CONTENT };
      </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-immediate-global-insert-visibility" xreflabel="immediate_global_insert_visibility">
+      <term><varname>immediate_global_insert_visibility</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>immediate_global_insert_visibility</varname></primary>
+       <secondary>configuration parameter</secondary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Specifies a comma-separated list of tables (including partitioned
+        tables, which expand to all leaf partitions) for which newly inserted
+        rows are frozen (<literal>HEAP_XMIN_FROZEN</literal>) immediately
+        after index insertion succeeds.  Rows inserted while a table is listed
+        in this parameter become immediately visible to all concurrent
+        transactions and survive even if the inserting transaction subsequently
+        rolls back.  The default value is an empty string (<literal>""</literal>).
+       </para>
+       <para>
+        Each table entry (or the list) may optionally specify a durability
+        mode of <literal>sync</literal> (the default) or <literal>async</literal>
+        using <literal><replaceable>table</replaceable>:sync</literal>,
+        <literal><replaceable>table</replaceable>:async</literal>,
+        <literal>sync:<replaceable>table</replaceable></literal>, or
+        <literal>async:<replaceable>table</replaceable></literal> (or a leading
+        <literal>sync</literal>/<literal>async</literal> list item).  In
+        <literal>sync</literal> mode, <function>XLogFlush</function> is called
+        immediately after freezing each inserted tuple (or multi-insert batch)
+        for immediate crash safety.  In <literal>async</literal> mode, the
+        freeze WAL record is still logged and the tuple is immediately visible
+        and rollback-safe, but <function>XLogFlush</function> is skipped.
+       </para>
+       <para>
+        This parameter can only be set within a transaction block using
+        <command>SET LOCAL</command> (or <function>set_config(..., true)</function>).
+        It is useful for rollback-safe audit logging and local two-phase
+        commit (2PC) coordinator state tracking.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
      <sect2 id="runtime-config-client-format">
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 5c1eaadd442..14bedd2b7a1 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -40,10 +40,15 @@
 #include "access/valid.h"
 #include "access/visibilitymap.h"
 #include "access/xloginsert.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_database_d.h"
+#include "catalog/pg_inherits.h"
 #include "commands/vacuum.h"
 #include "executor/instrument_node.h"
+#include "nodes/makefuncs.h"
+#include "nodes/miscnodes.h"
+#include "parser/scansup.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "storage/lmgr.h"
@@ -51,12 +56,33 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/datum.h"
+#include "utils/guc_hooks.h"
 #include "utils/injection_point.h"
 #include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/regproc.h"
 #include "utils/spccache.h"
 #include "utils/syscache.h"
 
 
+/* GUC variable and state for immediate_global_insert_visibility */
+char	   *immediate_global_insert_visibility = NULL;
+bool		immediate_insert_visibility_active = false;
+
+typedef struct
+{
+	Oid			relid;
+	bool		sync;
+} immediate_insert_visibility_rel;
+
+typedef struct
+{
+	int			numRels;
+	immediate_insert_visibility_rel rels[FLEXIBLE_ARRAY_MEMBER];
+} immediate_insert_visibility_extra;
+
+static immediate_insert_visibility_extra *current_immediate_vis_extra = NULL;
+
 static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 									 TransactionId xid, CommandId cid, uint32 options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
@@ -2094,7 +2120,8 @@ 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 & (HEAP_INSERT_FROZEN | HEAP_INSERT_IMMEDIATE_FROZEN)))
 		PageSetPrunable(page, xid);
 
 	MarkBufferDirty(buffer);
@@ -2245,7 +2272,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 & (HEAP_INSERT_FROZEN | HEAP_INSERT_IMMEDIATE_FROZEN))
 		HeapTupleHeaderSetXminFrozen(tup->t_data);
 
 	HeapTupleHeaderSetCmin(tup->t_data, cid);
@@ -2264,7 +2291,13 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 		return tup;
 	}
 	else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD)
-		return heap_toast_insert_or_update(relation, tup, NULL, options);
+	{
+		uint32		toast_options = options;
+
+		if (RelationHasImmediateInsertVisibility(relation))
+			toast_options |= HEAP_INSERT_IMMEDIATE_FROZEN;
+		return heap_toast_insert_or_update(relation, tup, NULL, toast_options);
+	}
 	else
 		return tup;
 }
@@ -6366,6 +6399,417 @@ heap_finish_speculative(Relation relation, const ItemPointerData *tid)
 	UnlockReleaseBuffer(buffer);
 }
 
+static void
+append_immediate_vis_oid(List **oidlist, List **synclist, Oid relid, bool sync)
+{
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, *oidlist, lc2, *synclist)
+	{
+		if (lfirst_oid(lc1) == relid)
+		{
+			lfirst_int(lc2) = sync ? 1 : 0;
+			return;
+		}
+	}
+	*oidlist = lappend_oid(*oidlist, relid);
+	*synclist = lappend_int(*synclist, sync ? 1 : 0);
+}
+
+/*
+ * check_hook: validate new immediate_global_insert_visibility and eagerly
+ * resolve relation OIDs and optional sync/async modifiers.
+ */
+bool
+check_immediate_global_insert_visibility(char **newval, void **extra,
+										 GucSource source)
+{
+	char	   *rawstring;
+	char	   *nextp;
+	List	   *oidlist = NIL;
+	List	   *synclist = NIL;
+	bool		cur_sync = true;
+
+	if (*newval == NULL || (*newval)[0] == '\0')
+	{
+		*extra = NULL;
+		return true;
+	}
+
+	if (!IsTransactionState() || MyDatabaseId == InvalidOid)
+	{
+		*extra = NULL;
+		return true;
+	}
+
+	rawstring = pstrdup(*newval);
+	nextp = rawstring;
+
+	while (scanner_isspace(*nextp))
+		nextp++;
+
+	if (*nextp != '\0')
+	{
+		bool		done = false;
+
+		do
+		{
+			char	   *item_start = nextp;
+			char	   *item_end = nextp;
+			char	   *colon_pos = NULL;
+			bool		in_quotes = false;
+			bool		item_sync;
+			List	   *names;
+			RangeVar   *rv;
+			Oid			relid;
+			char		relkind;
+			ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+			CHECK_FOR_INTERRUPTS();
+
+			while (*nextp != '\0' && (in_quotes || *nextp != ','))
+			{
+				if (*nextp == '"')
+					in_quotes = !in_quotes;
+				if (!scanner_isspace(*nextp) || in_quotes)
+					item_end = nextp + 1;
+				nextp++;
+			}
+
+			if (in_quotes || item_start == item_end)
+			{
+				GUC_check_errdetail("List syntax is invalid.");
+				pfree(rawstring);
+				list_free(oidlist);
+				list_free(synclist);
+				return false;
+			}
+
+			if (*nextp == ',')
+			{
+				nextp++;
+				while (scanner_isspace(*nextp))
+					nextp++;
+				if (*nextp == '\0')
+				{
+					GUC_check_errdetail("List syntax is invalid.");
+					pfree(rawstring);
+					list_free(oidlist);
+					list_free(synclist);
+					return false;
+				}
+			}
+			else if (*nextp == '\0')
+				done = true;
+
+			*item_end = '\0';
+
+			/*
+			 * Support standalone 'sync' or 'async' item setting the default
+			 * mode for subsequent tables in the list (e.g. 'async, t1, t2').
+			 */
+			if (!done && pg_strcasecmp(item_start, "sync") == 0)
+			{
+				cur_sync = true;
+				continue;
+			}
+			else if (!done && pg_strcasecmp(item_start, "async") == 0)
+			{
+				cur_sync = false;
+				continue;
+			}
+
+			item_sync = cur_sync;
+
+			/*
+			 * Check for an unquoted ':' modifier separator (either
+			 * 'sync:table' / 'async:table' prefix or 'table:sync' /
+			 * 'table:async' suffix).
+			 */
+			in_quotes = false;
+			for (char *p = item_start; *p != '\0'; p++)
+			{
+				if (*p == '"')
+					in_quotes = !in_quotes;
+				else if (*p == ':' && !in_quotes)
+				{
+					if (colon_pos != NULL)
+					{
+						GUC_check_errdetail("List syntax is invalid.");
+						pfree(rawstring);
+						list_free(oidlist);
+						list_free(synclist);
+						return false;
+					}
+					colon_pos = p;
+				}
+			}
+
+			if (colon_pos != NULL)
+			{
+				char	   *left_start = item_start;
+				char	   *left_end = colon_pos;
+				char	   *right_start = colon_pos + 1;
+				char	   *right_end = item_end;
+
+				while (left_end > left_start && scanner_isspace(left_end[-1]))
+					left_end--;
+				*left_end = '\0';
+
+				while (*right_start != '\0' && scanner_isspace(*right_start))
+					right_start++;
+				while (right_end > right_start && scanner_isspace(right_end[-1]))
+					right_end--;
+				*right_end = '\0';
+
+				if (*left_start == '\0' || *right_start == '\0')
+				{
+					GUC_check_errdetail("List syntax is invalid.");
+					pfree(rawstring);
+					list_free(oidlist);
+					list_free(synclist);
+					return false;
+				}
+
+				if (pg_strcasecmp(left_start, "sync") == 0)
+				{
+					cur_sync = true;
+					item_sync = true;
+					item_start = right_start;
+				}
+				else if (pg_strcasecmp(left_start, "async") == 0)
+				{
+					cur_sync = false;
+					item_sync = false;
+					item_start = right_start;
+				}
+				else if (pg_strcasecmp(right_start, "sync") == 0)
+				{
+					item_sync = true;
+					item_start = left_start;
+				}
+				else if (pg_strcasecmp(right_start, "async") == 0)
+				{
+					item_sync = false;
+					item_start = left_start;
+				}
+				else
+				{
+					GUC_check_errdetail("Invalid visibility mode \"%s\"; expected \"sync\" or \"async\".",
+										right_start);
+					pfree(rawstring);
+					list_free(oidlist);
+					list_free(synclist);
+					return false;
+				}
+			}
+
+			names = stringToQualifiedNameList(item_start, (Node *) &escontext);
+			if (names == NIL || escontext.error_occurred)
+			{
+				GUC_check_errdetail("List syntax is invalid.");
+				pfree(rawstring);
+				list_free(oidlist);
+				list_free(synclist);
+				return false;
+			}
+
+			rv = makeRangeVarFromNameList(names);
+			relid = RangeVarGetRelid(rv, NoLock, true);
+			if (!OidIsValid(relid))
+			{
+				GUC_check_errcode(ERRCODE_UNDEFINED_TABLE);
+				GUC_check_errmsg("relation \"%s\" does not exist", item_start);
+				pfree(rawstring);
+				list_free(oidlist);
+				list_free(synclist);
+				return false;
+			}
+
+			relkind = get_rel_relkind(relid);
+			if (relkind == RELKIND_RELATION)
+			{
+				append_immediate_vis_oid(&oidlist, &synclist, relid, item_sync);
+			}
+			else if (relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				List	   *children = find_all_inheritors(relid, NoLock, NULL);
+				ListCell   *lc;
+
+				foreach(lc, children)
+					append_immediate_vis_oid(&oidlist, &synclist,
+											 lfirst_oid(lc), item_sync);
+				list_free(children);
+			}
+			else
+			{
+				GUC_check_errcode(ERRCODE_WRONG_OBJECT_TYPE);
+				GUC_check_errmsg("\"%s\" is not a table", item_start);
+				pfree(rawstring);
+				list_free(oidlist);
+				list_free(synclist);
+				return false;
+			}
+		} while (!done);
+	}
+
+	if (oidlist != NIL)
+	{
+		immediate_insert_visibility_extra *myextra;
+		int			numRels = list_length(oidlist);
+		int			i = 0;
+		ListCell   *lc1,
+				   *lc2;
+
+		myextra = guc_malloc(LOG,
+							 offsetof(immediate_insert_visibility_extra, rels) +
+							 numRels * sizeof(immediate_insert_visibility_rel));
+		if (!myextra)
+		{
+			pfree(rawstring);
+			list_free(oidlist);
+			list_free(synclist);
+			return false;
+		}
+		myextra->numRels = numRels;
+		forboth(lc1, oidlist, lc2, synclist)
+		{
+			myextra->rels[i].relid = lfirst_oid(lc1);
+			myextra->rels[i].sync = (lfirst_int(lc2) != 0);
+			i++;
+		}
+		*extra = myextra;
+	}
+	else
+		*extra = NULL;
+
+	pfree(rawstring);
+	list_free(oidlist);
+	list_free(synclist);
+	return true;
+}
+
+/*
+ * assign_hook: update active immediate insert visibility OID list.
+ */
+void
+assign_immediate_global_insert_visibility(const char *newval, void *extra)
+{
+	current_immediate_vis_extra = (immediate_insert_visibility_extra *) extra;
+	immediate_insert_visibility_active =
+		(current_immediate_vis_extra != NULL &&
+		 current_immediate_vis_extra->numRels > 0);
+}
+
+/*
+ * Check whether the given relation is currently listed in
+ * immediate_global_insert_visibility, and optionally return whether
+ * synchronous WAL flush ('sync' mode) is enabled for it.
+ */
+bool
+RelationGetImmediateInsertVisibility(Relation relation, bool *sync)
+{
+	Oid			relid;
+
+	if (likely(!immediate_insert_visibility_active))
+		return false;
+
+	relid = RelationGetRelid(relation);
+	for (int i = 0; i < current_immediate_vis_extra->numRels; i++)
+	{
+		if (current_immediate_vis_extra->rels[i].relid == relid)
+		{
+			if (sync != NULL)
+				*sync = current_immediate_vis_extra->rels[i].sync;
+			return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Check whether the given relation is currently listed in
+ * immediate_global_insert_visibility.
+ */
+bool
+RelationHasImmediateInsertVisibility(Relation relation)
+{
+	return RelationGetImmediateInsertVisibility(relation, NULL);
+}
+
+/*
+ *	heap_freeze_inserted_tuple - freeze a newly inserted tuple in place
+ *
+ * Used by immediate_global_insert_visibility right after index tuple insertion
+ * succeeds, so that the tuple's xmin becomes immediately visible to all
+ * transactions and survives if the inserting transaction later rolls back.
+ * If 'flush' is true and the relation is WAL-logged, flushes WAL up to the
+ * freeze record's LSN.  Returns the LSN of the WAL record (or
+ * InvalidXLogRecPtr if unlogged).
+ */
+XLogRecPtr
+heap_freeze_inserted_tuple(Relation relation, const ItemPointerData *tid,
+						   bool flush)
+{
+	Buffer		buffer;
+	Page		page;
+	OffsetNumber offnum;
+	ItemId		lp;
+	HeapTupleHeader htup;
+	HeapTupleFreeze frz;
+	XLogRecPtr	recptr = InvalidXLogRecPtr;
+
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+	page = BufferGetPage(buffer);
+
+	offnum = ItemPointerGetOffsetNumber(tid);
+	if (offnum < 1 || offnum > PageGetMaxOffsetNumber(page))
+		elog(ERROR, "offnum out of range");
+	lp = PageGetItemId(page, offnum);
+	if (!ItemIdIsNormal(lp))
+		elog(ERROR, "invalid lp");
+
+	htup = (HeapTupleHeader) PageGetItem(page, lp);
+
+	frz.xmax = HeapTupleHeaderGetRawXmax(htup);
+	frz.t_infomask2 = htup->t_infomask2;
+	frz.t_infomask = htup->t_infomask | HEAP_XMIN_FROZEN;
+	frz.frzflags = 0;
+	frz.checkflags = 0;
+	frz.offset = offnum;
+
+	/* NO EREPORT(ERROR) from here till changes are logged */
+	START_CRIT_SECTION();
+
+	heap_execute_freeze_tuple(htup, &frz);
+	MarkBufferDirty(buffer);
+
+	if (RelationNeedsWAL(relation))
+	{
+		log_heap_prune_and_freeze(relation, buffer,
+								  InvalidBuffer, 0,
+								  InvalidTransactionId,
+								  false,
+								  PRUNE_ON_ACCESS,
+								  &frz, 1,
+								  NULL, 0,
+								  NULL, 0,
+								  NULL, 0);
+		recptr = PageGetLSN(page);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buffer);
+
+	if (flush && XLogRecPtrIsValid(recptr))
+		XLogFlush(recptr);
+
+	return recptr;
+}
+
 /*
  *	heap_abort_speculative - kill a speculatively inserted tuple
  *
diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c
index 361b76e5065..81f59b4c3b8 100644
--- a/src/backend/access/heap/heapam_visibility.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -1021,6 +1021,12 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
 		if (!HeapTupleHeaderXminFrozen(tuple) &&
 			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
 			return false;		/* treat as still in progress */
+		if (unlikely(immediate_insert_visibility_active) &&
+			HeapTupleHeaderXminFrozen(tuple) &&
+			TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)) &&
+			!(tuple->t_infomask & HEAP_COMBOCID) &&
+			HeapTupleHeaderGetRawCommandId(tuple) >= snapshot->curcid)
+			return false;		/* inserted after scan started */
 	}
 
 	/* by here, the inserting transaction has committed */
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 746b4fdf6c3..883cc1a1a9f 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -28,6 +28,7 @@
 #include "access/tableam.h"
 #include "access/tupconvert.h"
 #include "access/xact.h"
+#include "access/xlog.h"
 #include "catalog/namespace.h"
 #include "commands/copyapi.h"
 #include "commands/copyfrom_internal.h"
@@ -561,42 +562,77 @@ CopyMultiInsertBufferFlush(CopyMultiInsertInfo *miinfo,
 						   buffer->bistate);
 		MemoryContextSwitchTo(oldcontext);
 
-		for (i = 0; i < nused; i++)
 		{
-			/*
-			 * If there are any indexes, update them for all the inserted
-			 * tuples, and run AFTER ROW INSERT triggers.
-			 */
-			if (resultRelInfo->ri_NumIndices > 0)
-			{
-				List	   *recheckIndexes;
-
-				cstate->cur_lineno = buffer->linenos[i];
-				recheckIndexes =
-					ExecInsertIndexTuples(resultRelInfo,
-										  estate, 0, buffer->slots[i],
-										  NIL, NULL);
-				ExecARInsertTriggers(estate, resultRelInfo,
-									 slots[i], recheckIndexes,
-									 cstate->transition_capture);
-				list_free(recheckIndexes);
-			}
+			bool		immediate_vis_sync = false;
+			bool		immediate_vis =
+				RelationGetImmediateInsertVisibility(resultRelInfo->ri_RelationDesc,
+													 &immediate_vis_sync);
+			XLogRecPtr	last_freeze_lsn = InvalidXLogRecPtr;
 
-			/*
-			 * There's no indexes, but see if we need to run AFTER ROW INSERT
-			 * triggers anyway.
-			 */
-			else if (resultRelInfo->ri_TrigDesc != NULL &&
-					 (resultRelInfo->ri_TrigDesc->trig_insert_after_row ||
-					  resultRelInfo->ri_TrigDesc->trig_insert_new_table))
+			for (i = 0; i < nused; i++)
 			{
-				cstate->cur_lineno = buffer->linenos[i];
-				ExecARInsertTriggers(estate, resultRelInfo,
-									 slots[i], NIL,
-									 cstate->transition_capture);
+				/*
+				 * If there are any indexes, update them for all the inserted
+				 * tuples, and run AFTER ROW INSERT triggers.
+				 */
+				if (resultRelInfo->ri_NumIndices > 0)
+				{
+					List	   *recheckIndexes;
+
+					cstate->cur_lineno = buffer->linenos[i];
+					recheckIndexes =
+						ExecInsertIndexTuples(resultRelInfo,
+											  estate, 0, buffer->slots[i],
+											  NIL, NULL);
+					if (immediate_vis)
+					{
+						XLogRecPtr	lsn;
+
+						lsn = heap_freeze_inserted_tuple(resultRelInfo->ri_RelationDesc,
+														 &slots[i]->tts_tid,
+														 false);
+						if (lsn > last_freeze_lsn)
+							last_freeze_lsn = lsn;
+					}
+					ExecARInsertTriggers(estate, resultRelInfo,
+										 slots[i], recheckIndexes,
+										 cstate->transition_capture);
+					list_free(recheckIndexes);
+				}
+
+				/*
+				 * There's no indexes, but see if we need to run AFTER ROW
+				 * INSERT triggers anyway.
+				 */
+				else
+				{
+					if (immediate_vis)
+					{
+						XLogRecPtr	lsn;
+
+						lsn = heap_freeze_inserted_tuple(resultRelInfo->ri_RelationDesc,
+														 &slots[i]->tts_tid,
+														 false);
+						if (lsn > last_freeze_lsn)
+							last_freeze_lsn = lsn;
+					}
+
+					if (resultRelInfo->ri_TrigDesc != NULL &&
+						(resultRelInfo->ri_TrigDesc->trig_insert_after_row ||
+						 resultRelInfo->ri_TrigDesc->trig_insert_new_table))
+					{
+						cstate->cur_lineno = buffer->linenos[i];
+						ExecARInsertTriggers(estate, resultRelInfo,
+											 slots[i], NIL,
+											 cstate->transition_capture);
+					}
+				}
+
+				ExecClearTuple(slots[i]);
 			}
 
-			ExecClearTuple(slots[i]);
+			if (immediate_vis_sync && XLogRecPtrIsValid(last_freeze_lsn))
+				XLogFlush(last_freeze_lsn);
 		}
 
 		/* Update the row counter and progress of the COPY command */
@@ -1425,6 +1461,8 @@ CopyFrom(CopyFromState cstate)
 					}
 					else
 					{
+						bool		immediate_vis_sync;
+
 						/* OK, store the tuple and create index entries for it */
 						table_tuple_insert(resultRelInfo->ri_RelationDesc,
 										   myslot, mycid, ti_options, bistate);
@@ -1434,6 +1472,12 @@ CopyFrom(CopyFromState cstate)
 																   estate, 0,
 																   myslot, NIL,
 																   NULL);
+
+						if (RelationGetImmediateInsertVisibility(resultRelInfo->ri_RelationDesc,
+																 &immediate_vis_sync))
+							heap_freeze_inserted_tuple(resultRelInfo->ri_RelationDesc,
+													   &myslot->tts_tid,
+													   immediate_vis_sync);
 					}
 
 					/* AFTER ROW INSERT Triggers */
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 558ceece9bb..2f0e58b80d6 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -53,6 +53,7 @@
 
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/tableam.h"
 #include "access/tupconvert.h"
@@ -1259,9 +1260,20 @@ ExecInsert(ModifyTableContext *context,
 			}
 
 			/* Since there was no insertion conflict, we're done */
+			{
+				bool		immediate_vis_sync;
+
+				if (RelationGetImmediateInsertVisibility(resultRelationDesc,
+														 &immediate_vis_sync))
+					heap_freeze_inserted_tuple(resultRelationDesc,
+											   &slot->tts_tid,
+											   immediate_vis_sync);
+			}
 		}
 		else
 		{
+			bool		immediate_vis_sync;
+
 			/* insert the tuple normally */
 			table_tuple_insert(resultRelationDesc, slot,
 							   estate->es_output_cid,
@@ -1272,6 +1284,11 @@ ExecInsert(ModifyTableContext *context,
 				recheckIndexes = ExecInsertIndexTuples(resultRelInfo, estate,
 													   0, slot, NIL,
 													   NULL);
+
+			if (RelationGetImmediateInsertVisibility(resultRelationDesc,
+													 &immediate_vis_sync))
+				heap_freeze_inserted_tuple(resultRelationDesc, &slot->tts_tid,
+										   immediate_vis_sync);
 		}
 	}
 
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 21c9ccec1e2..c17046781ff 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3599,6 +3599,18 @@ set_config_with_handle(const char *name, config_handle *handle,
 		}
 	}
 
+	/* Disallow non-LOCAL changes to GUC_LOCAL_ONLY values */
+	if ((record->flags & GUC_LOCAL_ONLY) &&
+		source != PGC_S_DEFAULT &&
+		action != GUC_ACTION_LOCAL)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("parameter \"%s\" can only be set with SET LOCAL",
+						record->name)));
+		return 0;
+	}
+
 	/*
 	 * Should we set reset/stacked values?	(If so, the behavior is not
 	 * transactional.)	This is done either when we get a default value from
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 380679a76c0..c4d7b2d27fb 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1338,6 +1338,15 @@
   boot_val => 'false',
 },
 
+{ name => 'immediate_global_insert_visibility', type => 'string', context => 'PGC_USERSET', group => 'CLIENT_CONN_STATEMENT',
+  short_desc => 'Lists tables where inserts are immediately frozen and visible globally.',
+  flags => 'GUC_LIST_INPUT | GUC_LOCAL_ONLY | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_DISALLOW_IN_AUTO_FILE',
+  variable => 'immediate_global_insert_visibility',
+  boot_val => '""',
+  check_hook => 'check_immediate_global_insert_visibility',
+  assign_hook => 'assign_immediate_global_insert_visibility',
+},
+
 { name => 'in_hot_standby', type => 'bool', context => 'PGC_INTERNAL', group => 'PRESET_OPTIONS',
   short_desc => 'Shows whether hot standby is currently active.',
   flags => 'GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 342aaeef59a..6bc53776a28 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -31,6 +31,7 @@
 
 #include "access/commit_ts.h"
 #include "access/gin.h"
+#include "access/heapam.h"
 #include "access/slru.h"
 #include "access/toast_compression.h"
 #include "access/twophase.h"
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 9e35961fb9e..7e6f4c20d13 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -37,6 +37,7 @@
 #define HEAP_INSERT_FROZEN		TABLE_INSERT_FROZEN
 #define HEAP_INSERT_NO_LOGICAL	TABLE_INSERT_NO_LOGICAL
 #define HEAP_INSERT_SPECULATIVE 0x0010
+#define HEAP_INSERT_IMMEDIATE_FROZEN 0x0020
 
 /* "options" flag bits for heap_page_prune_and_freeze */
 #define HEAP_PAGE_PRUNE_MARK_UNUSED_NOW		(1 << 0)
@@ -385,6 +386,13 @@ extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
 							 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 PGDLLIMPORT char *immediate_global_insert_visibility;
+extern PGDLLIMPORT bool immediate_insert_visibility_active;
+extern bool RelationHasImmediateInsertVisibility(Relation relation);
+extern bool RelationGetImmediateInsertVisibility(Relation relation, bool *sync);
+extern XLogRecPtr heap_freeze_inserted_tuple(Relation relation,
+											 const ItemPointerData *tid,
+											 bool flush);
 extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
 							 HeapTuple newtup,
 							 CommandId cid, uint32 options,
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 164efba6b51..5e02a6452c7 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -229,6 +229,7 @@ typedef enum
 #define GUC_RUNTIME_COMPUTED   0x004000 /* delay processing in 'postgres -C' */
 #define GUC_ALLOW_IN_PARALLEL  0x008000 /* allow setting in parallel mode */
 #define GUC_SHOW_IN_OCTAL	   0x010000 /* show integer variable in octal */
+#define GUC_LOCAL_ONLY		   0x020000 /* can only be set with SET LOCAL */
 
 #define GUC_UNIT_KB			 0x01000000 /* value is in kilobytes */
 #define GUC_UNIT_BLOCKS		 0x02000000 /* value is in blocks */
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 06453a18c03..070d09a54f2 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -62,6 +62,10 @@ extern bool check_default_with_oids(bool *newval, void **extra,
 									GucSource source);
 extern const char *show_effective_wal_level(void);
 extern bool check_huge_page_size(int *newval, void **extra, GucSource source);
+extern bool check_immediate_global_insert_visibility(char **newval, void **extra,
+													 GucSource source);
+extern void assign_immediate_global_insert_visibility(const char *newval,
+													  void *extra);
 extern void assign_io_method(int newval, void *extra);
 extern bool check_io_max_concurrency(int *newval, void **extra, GucSource source);
 extern const char *show_in_hot_standby(void);
diff --git a/src/test/isolation/expected/immediate-insert-visibility.out b/src/test/isolation/expected/immediate-insert-visibility.out
new file mode 100644
index 00000000000..be96c9ea51d
--- /dev/null
+++ b/src/test/isolation/expected/immediate-insert-visibility.out
@@ -0,0 +1,94 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2_begin_rr s1_begin s1_insert s2_select s1_rollback s2_select s2_commit
+step s2_begin_rr: BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT count(*) FROM imm_iso_tab;
+count
+-----
+    0
+(1 row)
+
+step s1_begin: BEGIN; SET LOCAL immediate_global_insert_visibility = 'imm_iso_tab';
+step s1_insert: INSERT INTO imm_iso_tab VALUES (1, 'imm_row_1'); INSERT INTO norm_iso_tab VALUES (1, 'norm_row_1');
+step s2_select: SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_iso_tab ORDER BY id; SELECT id, val FROM norm_iso_tab ORDER BY id;
+id|val      |xmin_status
+--+---------+-----------
+ 1|imm_row_1|in progress
+(1 row)
+
+id|val
+--+---
+(0 rows)
+
+step s1_rollback: ROLLBACK;
+step s2_select: SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_iso_tab ORDER BY id; SELECT id, val FROM norm_iso_tab ORDER BY id;
+id|val      |xmin_status
+--+---------+-----------
+ 1|imm_row_1|aborted    
+(1 row)
+
+id|val
+--+---
+(0 rows)
+
+step s2_commit: COMMIT;
+
+starting permutation: s1_begin s1_insert_2pc s2_select s1_delete_2pc s2_select s1_commit s2_select
+step s1_begin: BEGIN; SET LOCAL immediate_global_insert_visibility = 'imm_iso_tab';
+step s1_insert_2pc: INSERT INTO imm_iso_tab VALUES (2, 'gid_2pc');
+step s2_select: SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_iso_tab ORDER BY id; SELECT id, val FROM norm_iso_tab ORDER BY id;
+id|val    |xmin_status
+--+-------+-----------
+ 2|gid_2pc|in progress
+(1 row)
+
+id|val
+--+---
+(0 rows)
+
+step s1_delete_2pc: DELETE FROM imm_iso_tab WHERE id = 2;
+step s2_select: SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_iso_tab ORDER BY id; SELECT id, val FROM norm_iso_tab ORDER BY id;
+id|val    |xmin_status
+--+-------+-----------
+ 2|gid_2pc|in progress
+(1 row)
+
+id|val
+--+---
+(0 rows)
+
+step s1_commit: COMMIT;
+step s2_select: SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_iso_tab ORDER BY id; SELECT id, val FROM norm_iso_tab ORDER BY id;
+id|val|xmin_status
+--+---+-----------
+(0 rows)
+
+id|val
+--+---
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_2pc s1_delete_2pc s2_select s1_rollback s2_select
+step s1_begin: BEGIN; SET LOCAL immediate_global_insert_visibility = 'imm_iso_tab';
+step s1_insert_2pc: INSERT INTO imm_iso_tab VALUES (2, 'gid_2pc');
+step s1_delete_2pc: DELETE FROM imm_iso_tab WHERE id = 2;
+step s2_select: SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_iso_tab ORDER BY id; SELECT id, val FROM norm_iso_tab ORDER BY id;
+id|val    |xmin_status
+--+-------+-----------
+ 2|gid_2pc|in progress
+(1 row)
+
+id|val
+--+---
+(0 rows)
+
+step s1_rollback: ROLLBACK;
+step s2_select: SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_iso_tab ORDER BY id; SELECT id, val FROM norm_iso_tab ORDER BY id;
+id|val    |xmin_status
+--+-------+-----------
+ 2|gid_2pc|aborted    
+(1 row)
+
+id|val
+--+---
+(0 rows)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index bf9a2037c70..ffc08341f82 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -132,3 +132,4 @@ test: ddl-dependency-locking
 test: tablespace-dependency-locking
 test: pub-concurrent-drop
 test: drop-owned-grant
+test: immediate-insert-visibility
diff --git a/src/test/isolation/specs/immediate-insert-visibility.spec b/src/test/isolation/specs/immediate-insert-visibility.spec
new file mode 100644
index 00000000000..24675418c86
--- /dev/null
+++ b/src/test/isolation/specs/immediate-insert-visibility.spec
@@ -0,0 +1,45 @@
+# Immediate global insert visibility cross-session isolation test
+#
+# Verifies that rows inserted into a table listed in
+# immediate_global_insert_visibility are immediately visible to concurrent
+# sessions (even under a REPEATABLE READ snapshot taken before the insert)
+# while the inserting transaction is still in progress, and remain visible
+# if the inserting transaction rolls back.
+# Also verifies the 2PC local GID log lifecycle across sessions.
+
+setup
+{
+ CREATE TABLE imm_iso_tab (id int PRIMARY KEY, val text);
+ CREATE TABLE norm_iso_tab (id int PRIMARY KEY, val text);
+}
+
+teardown
+{
+ DROP TABLE imm_iso_tab, norm_iso_tab;
+}
+
+session s1
+step s1_begin		{ BEGIN; SET LOCAL immediate_global_insert_visibility = 'imm_iso_tab'; }
+step s1_insert		{ INSERT INTO imm_iso_tab VALUES (1, 'imm_row_1'); INSERT INTO norm_iso_tab VALUES (1, 'norm_row_1'); }
+step s1_insert_2pc	{ INSERT INTO imm_iso_tab VALUES (2, 'gid_2pc'); }
+step s1_delete_2pc	{ DELETE FROM imm_iso_tab WHERE id = 2; }
+step s1_commit		{ COMMIT; }
+step s1_rollback	{ ROLLBACK; }
+
+session s2
+step s2_begin_rr	{ BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT count(*) FROM imm_iso_tab; }
+step s2_select		{ SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_iso_tab ORDER BY id; SELECT id, val FROM norm_iso_tab ORDER BY id; }
+step s2_commit		{ COMMIT; }
+
+# 1. Concurrent session with pre-existing REPEATABLE READ snapshot sees
+#    uncommitted immediate-visibility insert immediately, and still sees it
+#    after s1 rolls back.
+permutation s2_begin_rr s1_begin s1_insert s2_select s1_rollback s2_select s2_commit
+
+# 2. 2PC GID log pattern: s1 inserts frozen row then deletes it in same xact;
+#    s2 still sees the row while s1's DELETE is uncommitted, and sees it
+#    removed once s1 commits.
+permutation s1_begin s1_insert_2pc s2_select s1_delete_2pc s2_select s1_commit s2_select
+
+# 3. 2PC GID log pattern with abort after DELETE: row remains visible to s2.
+permutation s1_begin s1_insert_2pc s1_delete_2pc s2_select s1_rollback s2_select
diff --git a/src/test/regress/expected/immediate_insert_visibility.out b/src/test/regress/expected/immediate_insert_visibility.out
new file mode 100644
index 00000000000..fdca71ef4f5
--- /dev/null
+++ b/src/test/regress/expected/immediate_insert_visibility.out
@@ -0,0 +1,280 @@
+--
+-- Tests for immediate_global_insert_visibility
+--
+SHOW immediate_global_insert_visibility;
+ immediate_global_insert_visibility 
+------------------------------------
+ 
+(1 row)
+
+CREATE TABLE imm_vis_tab (
+    id int PRIMARY KEY,
+    val text,
+    large_val text
+);
+CREATE TABLE norm_vis_tab (
+    id int PRIMARY KEY,
+    val text
+);
+CREATE VIEW imm_vis_view AS SELECT * FROM imm_vis_tab;
+-- Must only be set with SET LOCAL
+SET immediate_global_insert_visibility = 'imm_vis_tab';
+ERROR:  parameter "immediate_global_insert_visibility" can only be set with SET LOCAL
+RESET immediate_global_insert_visibility;
+ERROR:  parameter "immediate_global_insert_visibility" can only be set with SET LOCAL
+SELECT set_config('immediate_global_insert_visibility', 'imm_vis_tab', false);
+ERROR:  parameter "immediate_global_insert_visibility" can only be set with SET LOCAL
+-- Validation of relation names in check_hook
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'nonexistent_table_xyz';
+ERROR:  relation "nonexistent_table_xyz" does not exist
+ROLLBACK;
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_view';
+ERROR:  "imm_vis_view" is not a table
+ROLLBACK;
+-- 1. Basic autonomous / immediate visibility insert surviving ROLLBACK
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+SHOW immediate_global_insert_visibility;
+ immediate_global_insert_visibility 
+------------------------------------
+ imm_vis_tab
+(1 row)
+
+INSERT INTO imm_vis_tab VALUES (1, 'survived rollback', repeat('a', 50000));
+INSERT INTO norm_vis_tab VALUES (1, 'rolled back');
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status, length(large_val) FROM imm_vis_tab;
+ id |        val        | xmin_status | length 
+----+-------------------+-------------+--------
+  1 | survived rollback | in progress |  50000
+(1 row)
+
+ROLLBACK;
+-- After ROLLBACK, GUC is reset and imm_vis_tab row (including TOAST) is still live
+-- even though raw t_xmin is from an aborted transaction (because HEAP_XMIN_FROZEN is set).
+SHOW immediate_global_insert_visibility;
+ immediate_global_insert_visibility 
+------------------------------------
+ 
+(1 row)
+
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status, length(large_val) FROM imm_vis_tab;
+ id |        val        | xmin_status | length 
+----+-------------------+-------------+--------
+  1 | survived rollback | aborted     |  50000
+(1 row)
+
+SELECT * FROM norm_vis_tab;
+ id | val 
+----+-----
+(0 rows)
+
+-- Verify VACUUM FREEZE does not corrupt or remove the TOASTed row from aborted xact
+VACUUM FREEZE imm_vis_tab;
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status, length(large_val) FROM imm_vis_tab;
+ id |        val        | xmin_status | length 
+----+-------------------+-------------+--------
+  1 | survived rollback | aborted     |  50000
+(1 row)
+
+-- 2. Unique constraint violation in ExecInsertIndexTuples() must NOT freeze the tuple
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+INSERT INTO imm_vis_tab VALUES (1, 'duplicate key should not freeze', NULL);
+ERROR:  duplicate key value violates unique constraint "imm_vis_tab_pkey"
+DETAIL:  Key (id)=(1) already exists.
+ROLLBACK;
+SET enable_indexscan = off;
+SET enable_bitmapscan = off;
+SELECT id, val FROM imm_vis_tab ORDER BY id;
+ id |        val        
+----+-------------------
+  1 | survived rollback
+(1 row)
+
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+-- 3. Halloween problem check: INSERT INTO ... SELECT FROM same table
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+INSERT INTO imm_vis_tab SELECT id + 10, val || ' copy', NULL FROM imm_vis_tab;
+SELECT id, val FROM imm_vis_tab ORDER BY id;
+ id |          val           
+----+------------------------
+  1 | survived rollback
+ 11 | survived rollback copy
+(2 rows)
+
+ROLLBACK;
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_vis_tab ORDER BY id;
+ id |          val           | xmin_status 
+----+------------------------+-------------
+  1 | survived rollback      | aborted
+ 11 | survived rollback copy | aborted
+(2 rows)
+
+-- 4. 2PC local prepare log pattern: INSERT frozen, then DELETE in same xact
+CREATE TABLE twophase_gid_log (
+    gid text PRIMARY KEY
+);
+-- Case A: Crash / abort before 2PC completes -> GID record survives
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'twophase_gid_log';
+INSERT INTO twophase_gid_log VALUES ('gid_crashed_1');
+ROLLBACK;
+SELECT gid, pg_xact_status(xmin::text::xid8) AS xmin_status FROM twophase_gid_log ORDER BY gid;
+      gid      | xmin_status 
+---------------+-------------
+ gid_crashed_1 | aborted
+(1 row)
+
+-- Case B: Normal 2PC completion -> GID inserted frozen, then deleted and committed
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'twophase_gid_log';
+INSERT INTO twophase_gid_log VALUES ('gid_completed_2');
+DELETE FROM twophase_gid_log WHERE gid = 'gid_completed_2';
+COMMIT;
+SELECT gid FROM twophase_gid_log ORDER BY gid;
+      gid      
+---------------
+ gid_crashed_1
+(1 row)
+
+-- Case C: GID inserted frozen, deleted, then transaction aborts -> GID still survives!
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'twophase_gid_log';
+INSERT INTO twophase_gid_log VALUES ('gid_delete_aborted_3');
+DELETE FROM twophase_gid_log WHERE gid = 'gid_delete_aborted_3';
+ROLLBACK;
+SELECT gid, pg_xact_status(xmin::text::xid8) AS xmin_status FROM twophase_gid_log ORDER BY gid;
+         gid          | xmin_status 
+----------------------+-------------
+ gid_crashed_1        | aborted
+ gid_delete_aborted_3 | aborted
+(2 rows)
+
+-- 5. INSERT ... ON CONFLICT support
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+INSERT INTO imm_vis_tab VALUES (1, 'conflict', NULL) ON CONFLICT (id) DO NOTHING;
+INSERT INTO imm_vis_tab VALUES (2, 'inserted via on conflict', NULL) ON CONFLICT (id) DO NOTHING;
+ROLLBACK;
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_vis_tab ORDER BY id;
+ id |           val            | xmin_status 
+----+--------------------------+-------------
+  1 | survived rollback        | aborted
+  2 | inserted via on conflict | aborted
+ 11 | survived rollback copy   | aborted
+(3 rows)
+
+-- 6. COPY (multi-insert) support
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+COPY imm_vis_tab (id, val, large_val) FROM STDIN;
+ROLLBACK;
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_vis_tab ORDER BY id;
+ id |           val            | xmin_status 
+----+--------------------------+-------------
+  1 | survived rollback        | aborted
+  2 | inserted via on conflict | aborted
+ 11 | survived rollback copy   | aborted
+ 20 | copy row 20              | aborted
+ 21 | copy row 21              | aborted
+(5 rows)
+
+-- 7. Partitioned table support
+CREATE TABLE imm_part_tab (
+    id int PRIMARY KEY,
+    val text
+) PARTITION BY RANGE (id);
+CREATE TABLE imm_part_1 PARTITION OF imm_part_tab FOR VALUES FROM (1) TO (100);
+CREATE TABLE imm_part_2 PARTITION OF imm_part_tab FOR VALUES FROM (100) TO (200);
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_part_tab';
+INSERT INTO imm_part_tab VALUES (10, 'part 1'), (150, 'part 2');
+ROLLBACK;
+SELECT tableoid::regclass, id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_part_tab ORDER BY id;
+  tableoid  | id  |  val   | xmin_status 
+------------+-----+--------+-------------
+ imm_part_1 |  10 | part 1 | aborted
+ imm_part_2 | 150 | part 2 | aborted
+(2 rows)
+
+-- 8. Sync / async modifiers and boundary inputs
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = ',';
+ERROR:  invalid value for parameter "immediate_global_insert_visibility": ","
+DETAIL:  List syntax is invalid.
+ROLLBACK;
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab:bogus';
+ERROR:  invalid value for parameter "immediate_global_insert_visibility": "imm_vis_tab:bogus"
+DETAIL:  Invalid visibility mode "bogus"; expected "sync" or "async".
+ROLLBACK;
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab:async';
+INSERT INTO imm_vis_tab VALUES (25, 'async suffix row', NULL);
+ROLLBACK;
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'async:imm_vis_tab, twophase_gid_log:sync';
+INSERT INTO imm_vis_tab VALUES (26, 'async prefix row', NULL);
+INSERT INTO twophase_gid_log VALUES ('gid_sync_override');
+ROLLBACK;
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'async, imm_vis_tab';
+INSERT INTO imm_vis_tab VALUES (27, 'async list keyword row', NULL);
+ROLLBACK;
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status
+FROM imm_vis_tab WHERE id IN (25, 26, 27) ORDER BY id;
+ id |          val           | xmin_status 
+----+------------------------+-------------
+ 25 | async suffix row       | aborted
+ 26 | async prefix row       | aborted
+ 27 | async list keyword row | aborted
+(3 rows)
+
+SELECT gid, pg_xact_status(xmin::text::xid8) AS xmin_status
+FROM twophase_gid_log WHERE gid = 'gid_sync_override';
+        gid        | xmin_status 
+-------------------+-------------
+ gid_sync_override | aborted
+(1 row)
+
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+SET LOCAL immediate_global_insert_visibility = '';
+INSERT INTO imm_vis_tab VALUES (30, 'not frozen after clearing guc', NULL);
+ROLLBACK;
+SELECT id, val FROM imm_vis_tab WHERE id = 30;
+ id | val 
+----+-----
+(0 rows)
+
+-- 9. Unprivileged role security check
+CREATE ROLE regress_imm_vis_user;
+BEGIN;
+SET LOCAL ROLE regress_imm_vis_user;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+INSERT INTO imm_vis_tab VALUES (40, 'unprivileged insert must fail', NULL);
+ERROR:  permission denied for table imm_vis_tab
+ROLLBACK;
+GRANT INSERT ON imm_vis_tab TO regress_imm_vis_user;
+BEGIN;
+SET LOCAL ROLE regress_imm_vis_user;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+INSERT INTO imm_vis_tab VALUES (40, 'granted unprivileged insert', NULL);
+ROLLBACK;
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_vis_tab WHERE id = 40;
+ id |             val             | xmin_status 
+----+-----------------------------+-------------
+ 40 | granted unprivileged insert | aborted
+(1 row)
+
+REVOKE ALL ON imm_vis_tab FROM regress_imm_vis_user;
+DROP ROLE regress_imm_vis_user;
+DROP VIEW imm_vis_view;
+DROP TABLE imm_vis_tab;
+DROP TABLE norm_vis_tab;
+DROP TABLE twophase_gid_log;
+DROP TABLE imm_part_tab;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 75063f87a4a..1c0010b1cea 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -128,7 +128,7 @@ test: partition_join partition_prune reloptions hash_part indexing partition_agg
 # ----------
 # Another group of parallel tests (compression)
 # ----------
-test: compression compression_lz4 compression_pglz cluster
+test: compression compression_lz4 compression_pglz cluster immediate_insert_visibility
 
 # event_trigger depends on create_am and cannot run concurrently with
 # any test that runs DDL
diff --git a/src/test/regress/sql/immediate_insert_visibility.sql b/src/test/regress/sql/immediate_insert_visibility.sql
new file mode 100644
index 00000000000..839f89490a2
--- /dev/null
+++ b/src/test/regress/sql/immediate_insert_visibility.sql
@@ -0,0 +1,205 @@
+--
+-- Tests for immediate_global_insert_visibility
+--
+
+SHOW immediate_global_insert_visibility;
+
+CREATE TABLE imm_vis_tab (
+    id int PRIMARY KEY,
+    val text,
+    large_val text
+);
+
+CREATE TABLE norm_vis_tab (
+    id int PRIMARY KEY,
+    val text
+);
+
+CREATE VIEW imm_vis_view AS SELECT * FROM imm_vis_tab;
+
+-- Must only be set with SET LOCAL
+SET immediate_global_insert_visibility = 'imm_vis_tab';
+RESET immediate_global_insert_visibility;
+SELECT set_config('immediate_global_insert_visibility', 'imm_vis_tab', false);
+
+-- Validation of relation names in check_hook
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'nonexistent_table_xyz';
+ROLLBACK;
+
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_view';
+ROLLBACK;
+
+-- 1. Basic autonomous / immediate visibility insert surviving ROLLBACK
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+SHOW immediate_global_insert_visibility;
+
+INSERT INTO imm_vis_tab VALUES (1, 'survived rollback', repeat('a', 50000));
+INSERT INTO norm_vis_tab VALUES (1, 'rolled back');
+
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status, length(large_val) FROM imm_vis_tab;
+ROLLBACK;
+
+-- After ROLLBACK, GUC is reset and imm_vis_tab row (including TOAST) is still live
+-- even though raw t_xmin is from an aborted transaction (because HEAP_XMIN_FROZEN is set).
+SHOW immediate_global_insert_visibility;
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status, length(large_val) FROM imm_vis_tab;
+SELECT * FROM norm_vis_tab;
+
+-- Verify VACUUM FREEZE does not corrupt or remove the TOASTed row from aborted xact
+VACUUM FREEZE imm_vis_tab;
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status, length(large_val) FROM imm_vis_tab;
+
+-- 2. Unique constraint violation in ExecInsertIndexTuples() must NOT freeze the tuple
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+INSERT INTO imm_vis_tab VALUES (1, 'duplicate key should not freeze', NULL);
+ROLLBACK;
+
+SET enable_indexscan = off;
+SET enable_bitmapscan = off;
+SELECT id, val FROM imm_vis_tab ORDER BY id;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+
+-- 3. Halloween problem check: INSERT INTO ... SELECT FROM same table
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+INSERT INTO imm_vis_tab SELECT id + 10, val || ' copy', NULL FROM imm_vis_tab;
+SELECT id, val FROM imm_vis_tab ORDER BY id;
+ROLLBACK;
+
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_vis_tab ORDER BY id;
+
+-- 4. 2PC local prepare log pattern: INSERT frozen, then DELETE in same xact
+CREATE TABLE twophase_gid_log (
+    gid text PRIMARY KEY
+);
+
+-- Case A: Crash / abort before 2PC completes -> GID record survives
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'twophase_gid_log';
+INSERT INTO twophase_gid_log VALUES ('gid_crashed_1');
+ROLLBACK;
+
+SELECT gid, pg_xact_status(xmin::text::xid8) AS xmin_status FROM twophase_gid_log ORDER BY gid;
+
+-- Case B: Normal 2PC completion -> GID inserted frozen, then deleted and committed
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'twophase_gid_log';
+INSERT INTO twophase_gid_log VALUES ('gid_completed_2');
+DELETE FROM twophase_gid_log WHERE gid = 'gid_completed_2';
+COMMIT;
+
+SELECT gid FROM twophase_gid_log ORDER BY gid;
+
+-- Case C: GID inserted frozen, deleted, then transaction aborts -> GID still survives!
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'twophase_gid_log';
+INSERT INTO twophase_gid_log VALUES ('gid_delete_aborted_3');
+DELETE FROM twophase_gid_log WHERE gid = 'gid_delete_aborted_3';
+ROLLBACK;
+
+SELECT gid, pg_xact_status(xmin::text::xid8) AS xmin_status FROM twophase_gid_log ORDER BY gid;
+
+-- 5. INSERT ... ON CONFLICT support
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+INSERT INTO imm_vis_tab VALUES (1, 'conflict', NULL) ON CONFLICT (id) DO NOTHING;
+INSERT INTO imm_vis_tab VALUES (2, 'inserted via on conflict', NULL) ON CONFLICT (id) DO NOTHING;
+ROLLBACK;
+
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_vis_tab ORDER BY id;
+
+-- 6. COPY (multi-insert) support
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+COPY imm_vis_tab (id, val, large_val) FROM STDIN;
+20	copy row 20	\N
+21	copy row 21	\N
+\.
+ROLLBACK;
+
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_vis_tab ORDER BY id;
+
+-- 7. Partitioned table support
+CREATE TABLE imm_part_tab (
+    id int PRIMARY KEY,
+    val text
+) PARTITION BY RANGE (id);
+
+CREATE TABLE imm_part_1 PARTITION OF imm_part_tab FOR VALUES FROM (1) TO (100);
+CREATE TABLE imm_part_2 PARTITION OF imm_part_tab FOR VALUES FROM (100) TO (200);
+
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_part_tab';
+INSERT INTO imm_part_tab VALUES (10, 'part 1'), (150, 'part 2');
+ROLLBACK;
+
+SELECT tableoid::regclass, id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_part_tab ORDER BY id;
+
+-- 8. Sync / async modifiers and boundary inputs
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = ',';
+ROLLBACK;
+
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab:bogus';
+ROLLBACK;
+
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab:async';
+INSERT INTO imm_vis_tab VALUES (25, 'async suffix row', NULL);
+ROLLBACK;
+
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'async:imm_vis_tab, twophase_gid_log:sync';
+INSERT INTO imm_vis_tab VALUES (26, 'async prefix row', NULL);
+INSERT INTO twophase_gid_log VALUES ('gid_sync_override');
+ROLLBACK;
+
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'async, imm_vis_tab';
+INSERT INTO imm_vis_tab VALUES (27, 'async list keyword row', NULL);
+ROLLBACK;
+
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status
+FROM imm_vis_tab WHERE id IN (25, 26, 27) ORDER BY id;
+SELECT gid, pg_xact_status(xmin::text::xid8) AS xmin_status
+FROM twophase_gid_log WHERE gid = 'gid_sync_override';
+
+BEGIN;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+SET LOCAL immediate_global_insert_visibility = '';
+INSERT INTO imm_vis_tab VALUES (30, 'not frozen after clearing guc', NULL);
+ROLLBACK;
+
+SELECT id, val FROM imm_vis_tab WHERE id = 30;
+
+-- 9. Unprivileged role security check
+CREATE ROLE regress_imm_vis_user;
+BEGIN;
+SET LOCAL ROLE regress_imm_vis_user;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+INSERT INTO imm_vis_tab VALUES (40, 'unprivileged insert must fail', NULL);
+ROLLBACK;
+
+GRANT INSERT ON imm_vis_tab TO regress_imm_vis_user;
+BEGIN;
+SET LOCAL ROLE regress_imm_vis_user;
+SET LOCAL immediate_global_insert_visibility = 'imm_vis_tab';
+INSERT INTO imm_vis_tab VALUES (40, 'granted unprivileged insert', NULL);
+ROLLBACK;
+
+SELECT id, val, pg_xact_status(xmin::text::xid8) AS xmin_status FROM imm_vis_tab WHERE id = 40;
+
+REVOKE ALL ON imm_vis_tab FROM regress_imm_vis_user;
+DROP ROLE regress_imm_vis_user;
+
+DROP VIEW imm_vis_view;
+DROP TABLE imm_vis_tab;
+DROP TABLE norm_vis_tab;
+DROP TABLE twophase_gid_log;
+DROP TABLE imm_part_tab;
-- 
2.56.0.176.g3575e65682-goog

Reply via email to