From e83351119281c4f3e67e1b3c92bf5a4538c62f35 Mon Sep 17 00:00:00 2001
From: Thom Brown <thom@linux.com>
Date: Sun, 12 Jul 2026 07:35:13 +0100
Subject: [PATCH 1/2] Add hooks for index_insert() and index_insert_cleanup()

Provide wrap-style hooks following the ExecutorRun_hook convention:
index_insert() and index_insert_cleanup() call the hook if set, and
otherwise the new standard_index_insert() respectively
standard_index_insert_cleanup().  This gives extensions a supported
interception point for index maintenance, enabling per-index
instrumentation (timing, WAL and buffer attribution), cumulative
per-index write statistics, and fault-injection testing, none of which
are currently possible without recreating every index under a wrapper
access method or modifying pg_am in place.

index_insert() is the single choke point for all index tuple
insertion: executor DML (including MERGE and ON CONFLICT), logical
replication apply, TOAST and catalog indexes, the CREATE INDEX
CONCURRENTLY validation phase, and deferred unique rechecks.  The
cleanup hook covers work deferred to index_insert_cleanup(), such as
GIN pending-list processing.

The cost when no hook is installed is a test of a global pointer
before the existing indirect call through rd_indam->aminsert;
benchmarks could not distinguish patched from unpatched builds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 src/backend/access/index/indexam.c | 52 ++++++++++++++++++++++++++++++
 src/include/access/genam.h         | 24 ++++++++++++++
 2 files changed, 76 insertions(+)

diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 7967e939847..91c23106691 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -206,6 +206,14 @@ validate_relation_as_index(Relation r)
 }
 
 
+/*
+ * Hooks for plugins to get control in index_insert() and
+ * index_insert_cleanup().  A hook is expected to call the corresponding
+ * standard_ function to perform the actual insertion or cleanup.
+ */
+index_insert_hook_type index_insert_hook = NULL;
+index_insert_cleanup_hook_type index_insert_cleanup_hook = NULL;
+
 /* ----------------
  *		index_insert - insert an index tuple into a relation
  * ----------------
@@ -219,6 +227,32 @@ index_insert(Relation indexRelation,
 			 IndexUniqueCheck checkUnique,
 			 bool indexUnchanged,
 			 IndexInfo *indexInfo)
+{
+	if (index_insert_hook)
+		return (*index_insert_hook) (indexRelation, values, isnull,
+									 heap_t_ctid, heapRelation,
+									 checkUnique, indexUnchanged,
+									 indexInfo);
+
+	return standard_index_insert(indexRelation, values, isnull,
+								 heap_t_ctid, heapRelation,
+								 checkUnique, indexUnchanged,
+								 indexInfo);
+}
+
+/* ----------------
+ *		standard_index_insert - standard implementation of index_insert
+ * ----------------
+ */
+bool
+standard_index_insert(Relation indexRelation,
+					  Datum *values,
+					  bool *isnull,
+					  ItemPointer heap_t_ctid,
+					  Relation heapRelation,
+					  IndexUniqueCheck checkUnique,
+					  bool indexUnchanged,
+					  IndexInfo *indexInfo)
 {
 	RELATION_CHECKS;
 	CHECK_REL_PROCEDURE(aminsert);
@@ -241,6 +275,24 @@ index_insert(Relation indexRelation,
 void
 index_insert_cleanup(Relation indexRelation,
 					 IndexInfo *indexInfo)
+{
+	if (index_insert_cleanup_hook)
+	{
+		(*index_insert_cleanup_hook) (indexRelation, indexInfo);
+		return;
+	}
+
+	standard_index_insert_cleanup(indexRelation, indexInfo);
+}
+
+/* -------------------------
+ *		standard_index_insert_cleanup - standard implementation of
+ *		index_insert_cleanup
+ * -------------------------
+ */
+void
+standard_index_insert_cleanup(Relation indexRelation,
+							  IndexInfo *indexInfo)
 {
 	RELATION_CHECKS;
 
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 68bfe405db3..81dd0775a89 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -144,6 +144,21 @@ extern Relation index_open(Oid relationId, LOCKMODE lockmode);
 extern Relation try_index_open(Oid relationId, LOCKMODE lockmode);
 extern void index_close(Relation relation, LOCKMODE lockmode);
 
+/* Hook for plugins to get control in index_insert() */
+typedef bool (*index_insert_hook_type) (Relation indexRelation,
+										Datum *values, bool *isnull,
+										ItemPointer heap_t_ctid,
+										Relation heapRelation,
+										IndexUniqueCheck checkUnique,
+										bool indexUnchanged,
+										IndexInfo *indexInfo);
+extern PGDLLIMPORT index_insert_hook_type index_insert_hook;
+
+/* Hook for plugins to get control in index_insert_cleanup() */
+typedef void (*index_insert_cleanup_hook_type) (Relation indexRelation,
+												IndexInfo *indexInfo);
+extern PGDLLIMPORT index_insert_cleanup_hook_type index_insert_cleanup_hook;
+
 extern bool index_insert(Relation indexRelation,
 						 Datum *values, bool *isnull,
 						 ItemPointer heap_t_ctid,
@@ -151,8 +166,17 @@ extern bool index_insert(Relation indexRelation,
 						 IndexUniqueCheck checkUnique,
 						 bool indexUnchanged,
 						 IndexInfo *indexInfo);
+extern bool standard_index_insert(Relation indexRelation,
+								  Datum *values, bool *isnull,
+								  ItemPointer heap_t_ctid,
+								  Relation heapRelation,
+								  IndexUniqueCheck checkUnique,
+								  bool indexUnchanged,
+								  IndexInfo *indexInfo);
 extern void index_insert_cleanup(Relation indexRelation,
 								 IndexInfo *indexInfo);
+extern void standard_index_insert_cleanup(Relation indexRelation,
+										  IndexInfo *indexInfo);
 
 extern IndexScanDesc index_beginscan(Relation heapRelation,
 									 Relation indexRelation,
-- 
2.47.3

