From 8865e2ca3b1b6291e94d2a5476926095245a4e2f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 14 Apr 2025 09:26:52 +0900
Subject: [PATCH] Add pg_get_injection_points()

This is a system function that displays the information about the
injection points currently attached to the system, feeding from the
states of things in shared memory.
---
 src/include/catalog/pg_proc.dat               |  8 +++
 src/include/utils/injection_point.h           | 14 +++++
 src/backend/utils/misc/Makefile               |  1 +
 src/backend/utils/misc/injection_point.c      | 50 ++++++++++++++++
 .../utils/misc/injection_point_funcs.c        | 59 +++++++++++++++++++
 src/backend/utils/misc/meson.build            |  1 +
 .../expected/injection_points.out             | 16 +++++
 .../injection_points/sql/injection_points.sql |  7 +++
 src/test/regress/expected/misc_functions.out  |  7 +++
 src/test/regress/sql/misc_functions.sql       |  3 +
 doc/src/sgml/func.sgml                        | 46 +++++++++++++++
 src/tools/pgindent/typedefs.list              |  1 +
 12 files changed, 213 insertions(+)
 create mode 100644 src/backend/utils/misc/injection_point_funcs.c

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 62beb71da288..d087d9fda1e8 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12566,4 +12566,12 @@
   proargnames => '{pid,io_id,io_generation,state,operation,off,length,target,handle_data_len,raw_result,result,target_desc,f_sync,f_localmem,f_buffered}',
   prosrc => 'pg_get_aios' },
 
+# Injection point functions
+{ oid => '8490', descr => 'information about injection points attached',
+  proname => 'pg_get_injection_points', prorows => '3', proretset => 't',
+  provolatile => 'v', proparallel => 'r', prorettype => 'record',
+  proargtypes => '', proallargtypes => '{text,text,text}',
+  proargmodes => '{o,o,o}', proargnames => '{name,library,function}',
+  prosrc => 'pg_get_injection_points' },
+
 ]
diff --git a/src/include/utils/injection_point.h b/src/include/utils/injection_point.h
index 6ba64cd1ebc6..c5c78914b848 100644
--- a/src/include/utils/injection_point.h
+++ b/src/include/utils/injection_point.h
@@ -11,6 +11,17 @@
 #ifndef INJECTION_POINT_H
 #define INJECTION_POINT_H
 
+/*
+ * Injection point data, used when retrieving a list of all the attached
+ * injection points.
+ */
+typedef struct InjectionPointData
+{
+	const char *name;
+	const char *library;
+	const char *function;
+} InjectionPointData;
+
 /*
  * Injection points require --enable-injection-points.
  */
@@ -46,6 +57,9 @@ extern void InjectionPointCached(const char *name);
 extern bool IsInjectionPointAttached(const char *name);
 extern bool InjectionPointDetach(const char *name);
 
+/* Get the current set of injection points attached */
+extern InjectionPointData *InjectionPointList(uint32 *num_points);
+
 #ifdef EXEC_BACKEND
 extern PGDLLIMPORT struct InjectionPointsCtl *ActiveInjectionPoints;
 #endif
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index b362ae437710..93703633f69a 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -22,6 +22,7 @@ OBJS = \
 	guc_tables.o \
 	help_config.o \
 	injection_point.o \
+	injection_point_funcs.o \
 	pg_config.o \
 	pg_controldata.o \
 	pg_rusage.o \
diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c
index 97ab851f0a63..bf1a49f7472a 100644
--- a/src/backend/utils/misc/injection_point.c
+++ b/src/backend/utils/misc/injection_point.c
@@ -584,3 +584,53 @@ IsInjectionPointAttached(const char *name)
 	return false;				/* silence compiler */
 #endif
 }
+
+/*
+ * Retrieve a list of all the injection points currently attached.
+ *
+ * This list is palloc'd in the current memory context.
+ */
+InjectionPointData *
+InjectionPointList(uint32 *num_points)
+{
+#ifdef USE_INJECTION_POINTS
+	InjectionPointData *result;
+	uint32		max_inuse;
+
+	LWLockAcquire(InjectionPointLock, LW_SHARED);
+
+	max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
+
+	/*
+	 * This overestimates the allocation size, including slots that may be
+	 * free, for simplicity.
+	 */
+	result = palloc0(sizeof(InjectionPointData) * max_inuse);
+
+	for (int idx = 0; idx < max_inuse; idx++)
+	{
+		InjectionPointEntry *entry;
+		uint64		generation;
+
+		entry = &ActiveInjectionPoints->entries[idx];
+		generation = pg_atomic_read_u64(&entry->generation);
+
+		/* skip free slots */
+		if (generation % 2 == 0)
+			continue;
+
+		result[idx].name = pstrdup(entry->name);
+		result[idx].library = pstrdup(entry->library);
+		result[idx].function = pstrdup(entry->function);
+		(*num_points)++;
+	}
+
+	LWLockRelease(InjectionPointLock);
+
+	return result;
+
+#else
+	*num_points = 0;
+	return NULL;
+#endif
+}
diff --git a/src/backend/utils/misc/injection_point_funcs.c b/src/backend/utils/misc/injection_point_funcs.c
new file mode 100644
index 000000000000..750ac4abf67d
--- /dev/null
+++ b/src/backend/utils/misc/injection_point_funcs.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * injection_point_funcs.c
+ *
+ * SQL commands and SQL-accessible functions related to injection points.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/misc/injection_point_funcs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "utils/builtins.h"
+#include "utils/injection_point.h"
+
+/*
+ * pg_get_injection_points
+ *
+ * Return a table of all the injection points currently attached to the
+ * system.
+ */
+Datum
+pg_get_injection_points(PG_FUNCTION_ARGS)
+{
+#define NUM_PG_GET_INJECTION_POINTS 3
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	uint32		num_points = 0;
+	InjectionPointData *injps = NULL;
+
+	/* Build a tuplestore to return our results in */
+	InitMaterializedSRF(fcinfo, 0);
+
+	injps = InjectionPointList(&num_points);
+
+	for (uint32 idx = 0; idx < num_points; idx++)
+	{
+		Datum		values[NUM_PG_GET_INJECTION_POINTS];
+		bool		nulls[NUM_PG_GET_INJECTION_POINTS];
+
+		memset(values, 0, sizeof(values));
+		memset(nulls, 0, sizeof(nulls));
+
+		values[0] = PointerGetDatum(cstring_to_text(injps[idx].name));
+		values[1] = PointerGetDatum(cstring_to_text(injps[idx].library));
+		values[2] = PointerGetDatum(cstring_to_text(injps[idx].function));
+
+		/* shove row into tuplestore */
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+	}
+
+	return (Datum) 0;
+#undef NUM_PG_GET_INJECTION_POINTS
+}
diff --git a/src/backend/utils/misc/meson.build b/src/backend/utils/misc/meson.build
index 9e389a00d057..4a89f1e016ea 100644
--- a/src/backend/utils/misc/meson.build
+++ b/src/backend/utils/misc/meson.build
@@ -7,6 +7,7 @@ backend_sources += files(
   'guc_tables.c',
   'help_config.c',
   'injection_point.c',
+  'injection_point_funcs.c',
   'pg_config.c',
   'pg_controldata.c',
   'pg_rusage.c',
diff --git a/src/test/modules/injection_points/expected/injection_points.out b/src/test/modules/injection_points/expected/injection_points.out
index f25bbe4966ee..4b1436a3ef69 100644
--- a/src/test/modules/injection_points/expected/injection_points.out
+++ b/src/test/modules/injection_points/expected/injection_points.out
@@ -26,6 +26,15 @@ SELECT injection_points_attach('TestInjectionLog2', 'notice');
  
 (1 row)
 
+SELECT name, library, function FROM pg_get_injection_points()
+  ORDER BY name COLLATE "C";
+        name        |     library      |     function     
+--------------------+------------------+------------------
+ TestInjectionError | injection_points | injection_error
+ TestInjectionLog   | injection_points | injection_notice
+ TestInjectionLog2  | injection_points | injection_notice
+(3 rows)
+
 SELECT injection_points_run('TestInjectionBooh'); -- nothing
  injection_points_run 
 ----------------------
@@ -253,5 +262,12 @@ SELECT injection_points_detach('TestConditionLocal1');
  
 (1 row)
 
+-- No points should be left around.
+SELECT name, library, function FROM pg_get_injection_points()
+  ORDER BY name COLLATE "C";
+ name | library | function 
+------+---------+----------
+(0 rows)
+
 DROP EXTENSION injection_points;
 DROP FUNCTION wait_pid;
diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql
index e3a481d60449..b12c40551954 100644
--- a/src/test/modules/injection_points/sql/injection_points.sql
+++ b/src/test/modules/injection_points/sql/injection_points.sql
@@ -14,6 +14,9 @@ SELECT injection_points_attach('TestInjectionError', 'error');
 SELECT injection_points_attach('TestInjectionLog', 'notice');
 SELECT injection_points_attach('TestInjectionLog2', 'notice');
 
+SELECT name, library, function FROM pg_get_injection_points()
+  ORDER BY name COLLATE "C";
+
 SELECT injection_points_run('TestInjectionBooh'); -- nothing
 SELECT injection_points_run('TestInjectionLog2'); -- notice
 SELECT injection_points_run('TestInjectionLog'); -- notice
@@ -75,5 +78,9 @@ SELECT injection_points_detach('TestConditionError');
 SELECT injection_points_attach('TestConditionLocal1', 'error');
 SELECT injection_points_detach('TestConditionLocal1');
 
+-- No points should be left around.
+SELECT name, library, function FROM pg_get_injection_points()
+  ORDER BY name COLLATE "C";
+
 DROP EXTENSION injection_points;
 DROP FUNCTION wait_pid;
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3f5d16a6725..c09bff495045 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -802,6 +802,13 @@ SELECT count(*) > 0 AS ok FROM pg_control_system();
  t
 (1 row)
 
+-- Test function for injection point
+SELECT count(*) >= 0 AS ok FROM pg_get_injection_points();
+ ok 
+----
+ t
+(1 row)
+
 -- pg_split_walfile_name, pg_walfile_name & pg_walfile_name_offset
 SELECT * FROM pg_split_walfile_name(NULL);
  segment_number | timeline_id 
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index aaebb298330b..e351a19a8744 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -355,6 +355,9 @@ SELECT count(*) > 0 AS ok FROM pg_control_init();
 SELECT count(*) > 0 AS ok FROM pg_control_recovery();
 SELECT count(*) > 0 AS ok FROM pg_control_system();
 
+-- Test function for injection point
+SELECT count(*) >= 0 AS ok FROM pg_get_injection_points();
+
 -- pg_split_walfile_name, pg_walfile_name & pg_walfile_name_offset
 SELECT * FROM pg_split_walfile_name(NULL);
 SELECT * FROM pg_split_walfile_name('invalid');
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1c5cfee25d12..6c0e0b45fa83 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -28496,6 +28496,52 @@ acl      | {postgres=arwdDxtm/postgres,foo=r/postgres}
 
   </sect2>
 
+  <sect2 id="functions-info-injection-points">
+   <title>Injection Points Information Functions</title>
+
+   <para>
+    The functions shown in <xref linkend="functions-info-injection-points"/>
+    print information about the injection points.
+    See <xref linkend="xfunc-addin-injection-points" />.
+   </para>
+
+   <table id="functions-injection-points">
+    <title>WAL Summarization Information Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_get_injection_points</primary>
+        </indexterm>
+        <function>pg_get_injection_points</function> ()
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>library</parameter> <type>text</type>,
+        <parameter>function</parameter> <type>text</type> )
+       </para>
+       <para>
+        Returns information about the injection points currently attached
+        to the cluster.
+       </para></entry>
+      </row>
+     </tbody>
+
+    </tgroup>
+   </table>
+  </sect2>
+
   </sect1>
 
   <sect1 id="functions-admin">
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d16bc208654b..e7df2dc10684 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1281,6 +1281,7 @@ InjectionPointCacheEntry
 InjectionPointCallback
 InjectionPointCondition
 InjectionPointConditionType
+InjectionPointData
 InjectionPointEntry
 InjectionPointsCtl
 InjectionPointSharedState
-- 
2.49.0

