Extend injection_points_attach() to accept a user-defined function

Started by Rahila Syed3 months ago10 messages
#1Rahila Syed
rahilasyed90@gmail.com
1 attachment(s)

Hi,

I would like to propose providing a sql interface to link a
user-defined function to an injection point.
Currently, if a user wants an injection point to invoke a custom
function, they must first define an SQL
function that attaches the injection point to the target/custom
function. This SQL function can then be called
in sql tests to attach to the injection point before running the tests.

The attached patch simplifies this by extending the
injection_points_attach() function to support a new
action type called "func".
The new "func" action enables users to attach a user-defined function
to an injection point.
When "func" is specified as action, the caller must provide:

1. the module name

2. the function name

as additional arguments to injection_points_attach(). These arguments
default to NULL when the
action is not "func".
This approach aims to simplify the process of assigning user-defined
functions to injection points,
eliminating the need to create separate SQL functions to attach each
injection point individually.

Thank you,
Rahila Syed

Attachments:

0001-Extend-injection_points_attach-function.patchapplication/octet-stream; name=0001-Extend-injection_points_attach-function.patchDownload
From b86b552fb5a465c23ce845971d371e9552cb552c Mon Sep 17 00:00:00 2001
From: Rahila Syed <rahilasyed.90@gmail.com>
Date: Tue, 28 Oct 2025 16:44:20 +0530
Subject: [PATCH] Extend injection_points_attach function

Add a new action called "func" which will assign
a user defined function to a given injection point.
This requires user to specify module name and function
name in addition to injection point name and action.
---
 .../injection_points--1.0.sql                 |  4 ++--
 .../injection_points/injection_points.c       | 19 +++++++++++++++++--
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/test/modules/injection_points/injection_points--1.0.sql b/src/test/modules/injection_points/injection_points--1.0.sql
index a7b61fbdfe6..da5ba0a40fa 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -9,10 +9,10 @@
 -- Attaches the action to the given injection point.
 --
 CREATE FUNCTION injection_points_attach(IN point_name TEXT,
-    IN action text)
+    IN action text, IN func TEXT default NULL, IN module TEXT default NULL)
 RETURNS void
 AS 'MODULE_PATHNAME', 'injection_points_attach'
-LANGUAGE C STRICT PARALLEL UNSAFE;
+LANGUAGE C PARALLEL UNSAFE;
 
 --
 -- injection_points_load()
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 31138301117..087d2b11a90 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -354,6 +354,7 @@ injection_points_attach(PG_FUNCTION_ARGS)
 	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
 	char	   *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
 	char	   *function;
+	char	   *mod_name;
 	InjectionPointCondition condition = {0};
 
 	if (strcmp(action, "error") == 0)
@@ -362,6 +363,15 @@ injection_points_attach(PG_FUNCTION_ARGS)
 		function = "injection_notice";
 	else if (strcmp(action, "wait") == 0)
 		function = "injection_wait";
+	else if (strcmp(action, "func") == 0)
+	{
+		if (PG_ARGISNULL(2))
+			elog(ERROR, "function name cannot be null for \"%s\" action", action);
+		function = text_to_cstring(PG_GETARG_TEXT_PP(2));
+		if (PG_ARGISNULL(3))
+			elog(ERROR, "module name cannot be null for \"%s\" action", action);
+		mod_name = text_to_cstring(PG_GETARG_TEXT_PP(3));
+	}
 	else
 		elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
 
@@ -372,8 +382,13 @@ injection_points_attach(PG_FUNCTION_ARGS)
 	}
 
 	pgstat_report_inj_fixed(1, 0, 0, 0, 0);
-	InjectionPointAttach(name, "injection_points", function, &condition,
-						 sizeof(InjectionPointCondition));
+
+	if (strcmp(action, "func") == 0)
+		InjectionPointAttach(name, mod_name, function, &condition,
+							 sizeof(InjectionPointCondition));
+	else
+		InjectionPointAttach(name, "injection_points", function, &condition,
+							 sizeof(InjectionPointCondition));
 
 	if (injection_point_local)
 	{
-- 
2.34.1

#2Michael Paquier
michael@paquier.xyz
In reply to: Rahila Syed (#1)
Re: Extend injection_points_attach() to accept a user-defined function

On Tue, Oct 28, 2025 at 06:11:25PM +0530, Rahila Syed wrote:

I would like to propose providing a sql interface to link a
user-defined function to an injection point.
Currently, if a user wants an injection point to invoke a custom
function, they must first define an SQL
function that attaches the injection point to the target/custom
function. This SQL function can then be called
in sql tests to attach to the injection point before running the tests.
The attached patch simplifies this by extending the
injection_points_attach() function to support a new
action type called "func".

@@ -354,6 +354,7 @@ injection_points_attach(PG_FUNCTION_ARGS)
+	char	   *mod_name;
[...]
@@ -362,6 +363,15 @@ injection_points_attach(PG_FUNCTION_ARGS)
 		function = "injection_notice";
 	else if (strcmp(action, "wait") == 0)
 		function = "injection_wait";
+	else if (strcmp(action, "func") == 0)

How about a simpler injection_points_attach(point_name text, func
text, module text) with a second SQL function, but a different number
of arguments? Using a new hardcoded action for this purpose is
confusing as your point is to introduce a SQL wrapper on top of
InjectionPointAttach(), and using input arguments that match with the
C function is an attractive option.
--
Michael

#3Rahila Syed
rahilasyed90@gmail.com
In reply to: Michael Paquier (#2)
1 attachment(s)
Re: Extend injection_points_attach() to accept a user-defined function

Hi,

How about a simpler injection_points_attach(point_name text, func
text, module text) with a second SQL function, but a different number
of arguments? Using a new hardcoded action for this purpose is
confusing as your point is to introduce a SQL wrapper on top of
InjectionPointAttach(), and using input arguments that match with the
C function is an attractive option.

Thank you for the suggestion.
I agree that having a separate SQL function for this would make the design
easier to understand.

Please find attached a patch that implements this.

Thank you,
Rahila Syed

Attachments:

v2-0001-Overload-the-injection_points_attach-sql-function.patchapplication/octet-stream; name=v2-0001-Overload-the-injection_points_attach-sql-function.patchDownload
From d692ed3dde2b5c6dbeb9a3a3cdb9c9e86c746937 Mon Sep 17 00:00:00 2001
From: Rahila Syed <rahilasyed.90@gmail.com>
Date: Wed, 29 Oct 2025 16:38:38 +0530
Subject: [PATCH] Overload the injection_points_attach sql function

Add a new sql function to assign C functions to injection
points. This function has same name but different arguments
as injection_points_attach. It takes as input module name and
function name in addition to injection point name.
---
 .../injection_points--1.0.sql                 | 11 ++++++
 .../injection_points/injection_points.c       | 37 +++++++++++++++++++
 2 files changed, 48 insertions(+)

diff --git a/src/test/modules/injection_points/injection_points--1.0.sql b/src/test/modules/injection_points/injection_points--1.0.sql
index a7b61fbdfe6..5bff4ca6539 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -14,6 +14,17 @@ RETURNS void
 AS 'MODULE_PATHNAME', 'injection_points_attach'
 LANGUAGE C STRICT PARALLEL UNSAFE;
 
+--
+-- injection_points_attach()
+--
+-- Attaches a function to the given injection point.
+--
+CREATE FUNCTION injection_points_attach(IN point_name TEXT,
+    IN library_name TEXT, IN function_name TEXT)
+RETURNS void
+AS 'MODULE_PATHNAME', 'injection_points_attach_func'
+LANGUAGE C STRICT PARALLEL UNSAFE;
+
 --
 -- injection_points_load()
 --
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 31138301117..ebc0d8835f2 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -391,6 +391,43 @@ injection_points_attach(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for creating an injection point.
+ */
+PG_FUNCTION_INFO_V1(injection_points_attach_func);
+Datum
+injection_points_attach_func(PG_FUNCTION_ARGS)
+{
+	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+	char	   *lib_name = text_to_cstring(PG_GETARG_TEXT_PP(1));
+	char	   *function = text_to_cstring(PG_GETARG_TEXT_PP(2));
+	InjectionPointCondition condition = {0};
+
+	if (injection_point_local)
+	{
+		condition.type = INJ_CONDITION_PID;
+		condition.pid = MyProcPid;
+	}
+
+	pgstat_report_inj_fixed(1, 0, 0, 0, 0);
+	InjectionPointAttach(name, lib_name, function, &condition,
+							 sizeof(InjectionPointCondition));
+	if (injection_point_local)
+	{
+		MemoryContext oldctx;
+
+		/* Local injection point, so track it for automated cleanup */
+		oldctx = MemoryContextSwitchTo(TopMemoryContext);
+		inj_list_local = lappend(inj_list_local, makeString(pstrdup(name)));
+		MemoryContextSwitchTo(oldctx);
+	}
+
+	/* Add entry for stats */
+	pgstat_create_inj(name);
+
+	PG_RETURN_VOID();
+
+}
 /*
  * SQL function for loading an injection point.
  */
-- 
2.34.1

#4Mihail Nikalayeu
mihailnikalayeu@gmail.com
In reply to: Rahila Syed (#3)
Re: Extend injection_points_attach() to accept a user-defined function

Hello!

I thought it may help me to implement some kind of notice+wait
required for [1]/messages/by-id/CADzfLwWRVj7wDy4Qj3CJTuWy6fvv9TTDBTHsUjC7F1SAN0LpeA@mail.gmail.com in order to stabilize the tests.

Is it possible to do something like this in the attached function?

RAISE NOTICE 'going to wait';
SELECT injection_points_run(some_point_with_wait"); -- wait called
inside injection point handler

Also, I think it is a good idea to add some tests to injection_points.sql.

Best regards,
Mikhail.

[1]: /messages/by-id/CADzfLwWRVj7wDy4Qj3CJTuWy6fvv9TTDBTHsUjC7F1SAN0LpeA@mail.gmail.com

#5Rahila Syed
rahilasyed90@gmail.com
In reply to: Mihail Nikalayeu (#4)
1 attachment(s)
Re: Extend injection_points_attach() to accept a user-defined function

Hi Mihail,

Thank you for looking into this thread.

I thought it may help me to implement some kind of notice+wait
required for [1] in order to stabilize the tests.

Is it possible to do something like this in the attached function?

RAISE NOTICE 'going to wait';
SELECT injection_points_run(some_point_with_wait"); -- wait called
inside injection point handler

One way to achieve this using the proposed SQL function is to create a
C function in a module like injection_points, which combines injection_notice
and injection_wait. You can then pass this combined function as an argument
to the proposed injection_points_attach() function.

Something as follows:

SELECT injection_points_attach('TestInjectionNoticeFunc',
'injection_points', 'injection_notice_and_wait');

Also, I think it is a good idea to add some tests to injection_points.sql.

PFA a rebased patch that contains the test.
The tests use the newly added SQL function to attach the injection_notice
function to an injection point

Thank you,
Rahila Syed

Attachments:

v3-0001-Overload-the-injection_points_attach-sql-function.patchapplication/octet-stream; name=v3-0001-Overload-the-injection_points_attach-sql-function.patchDownload
From f104b9475195bd073c598af009b0458f6126a9eb Mon Sep 17 00:00:00 2001
From: Rahila Syed <rahilasyed.90@gmail.com>
Date: Wed, 29 Oct 2025 16:38:38 +0530
Subject: [PATCH] Overload the injection_points_attach sql function

Add a new sql function to assign C functions to injection
points. This function has same name but different arguments
as injection_points_attach. It takes as input module name and
function name in addition to injection point name.
---
 .../expected/injection_points.out             | 29 +++++++++++++++
 .../injection_points--1.0.sql                 | 11 ++++++
 .../injection_points/injection_points.c       | 37 +++++++++++++++++++
 .../injection_points/sql/injection_points.sql |  9 ++++-
 4 files changed, 85 insertions(+), 1 deletion(-)

diff --git a/src/test/modules/injection_points/expected/injection_points.out b/src/test/modules/injection_points/expected/injection_points.out
index 382f3b0bf88..97365284366 100644
--- a/src/test/modules/injection_points/expected/injection_points.out
+++ b/src/test/modules/injection_points/expected/injection_points.out
@@ -307,6 +307,35 @@ SELECT injection_points_detach('TestConditionLocal1');
  
 (1 row)
 
+--
+-- Test the custom function variant of injection_points_attach
+SELECT injection_points_attach('TestInjectionNoticeFunc', 'injection_points', 'injection_notice');
+ injection_points_attach 
+-------------------------
+ 
+(1 row)
+
+-- The injection point should be present
+SELECT point_name, library, function FROM injection_points_list()
+  ORDER BY point_name COLLATE "C";
+       point_name        |     library      |     function     
+-------------------------+------------------+------------------
+ TestInjectionNoticeFunc | injection_points | injection_notice
+(1 row)
+
+SELECT injection_points_run('TestInjectionNoticeFunc', NULL); -- notice function
+NOTICE:  notice triggered for injection point TestInjectionNoticeFunc
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
+SELECT injection_points_detach('TestInjectionNoticeFunc');
+ injection_points_detach 
+-------------------------
+ 
+(1 row)
+
 -- No points should be left around.
 SELECT point_name, library, function FROM injection_points_list()
   ORDER BY point_name COLLATE "C";
diff --git a/src/test/modules/injection_points/injection_points--1.0.sql b/src/test/modules/injection_points/injection_points--1.0.sql
index a7b61fbdfe6..5bff4ca6539 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -14,6 +14,17 @@ RETURNS void
 AS 'MODULE_PATHNAME', 'injection_points_attach'
 LANGUAGE C STRICT PARALLEL UNSAFE;
 
+--
+-- injection_points_attach()
+--
+-- Attaches a function to the given injection point.
+--
+CREATE FUNCTION injection_points_attach(IN point_name TEXT,
+    IN library_name TEXT, IN function_name TEXT)
+RETURNS void
+AS 'MODULE_PATHNAME', 'injection_points_attach_func'
+LANGUAGE C STRICT PARALLEL UNSAFE;
+
 --
 -- injection_points_load()
 --
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 31138301117..ebc0d8835f2 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -391,6 +391,43 @@ injection_points_attach(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for creating an injection point.
+ */
+PG_FUNCTION_INFO_V1(injection_points_attach_func);
+Datum
+injection_points_attach_func(PG_FUNCTION_ARGS)
+{
+	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+	char	   *lib_name = text_to_cstring(PG_GETARG_TEXT_PP(1));
+	char	   *function = text_to_cstring(PG_GETARG_TEXT_PP(2));
+	InjectionPointCondition condition = {0};
+
+	if (injection_point_local)
+	{
+		condition.type = INJ_CONDITION_PID;
+		condition.pid = MyProcPid;
+	}
+
+	pgstat_report_inj_fixed(1, 0, 0, 0, 0);
+	InjectionPointAttach(name, lib_name, function, &condition,
+							 sizeof(InjectionPointCondition));
+	if (injection_point_local)
+	{
+		MemoryContext oldctx;
+
+		/* Local injection point, so track it for automated cleanup */
+		oldctx = MemoryContextSwitchTo(TopMemoryContext);
+		inj_list_local = lappend(inj_list_local, makeString(pstrdup(name)));
+		MemoryContextSwitchTo(oldctx);
+	}
+
+	/* Add entry for stats */
+	pgstat_create_inj(name);
+
+	PG_RETURN_VOID();
+
+}
 /*
  * SQL function for loading an injection point.
  */
diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql
index 874421e9c11..6d49400e180 100644
--- a/src/test/modules/injection_points/sql/injection_points.sql
+++ b/src/test/modules/injection_points/sql/injection_points.sql
@@ -87,7 +87,14 @@ SELECT injection_points_detach('TestConditionError');
 -- previously should work.
 SELECT injection_points_attach('TestConditionLocal1', 'error');
 SELECT injection_points_detach('TestConditionLocal1');
-
+--
+-- Test the custom function variant of injection_points_attach
+SELECT injection_points_attach('TestInjectionNoticeFunc', 'injection_points', 'injection_notice');
+-- The injection point should be present
+SELECT point_name, library, function FROM injection_points_list()
+  ORDER BY point_name COLLATE "C";
+SELECT injection_points_run('TestInjectionNoticeFunc', NULL); -- notice function
+SELECT injection_points_detach('TestInjectionNoticeFunc');
 -- No points should be left around.
 SELECT point_name, library, function FROM injection_points_list()
   ORDER BY point_name COLLATE "C";
-- 
2.34.1

#6Michael Paquier
michael@paquier.xyz
In reply to: Rahila Syed (#5)
Re: Extend injection_points_attach() to accept a user-defined function

On Tue, Nov 04, 2025 at 02:17:44PM +0530, Rahila Syed wrote:

PFA a rebased patch that contains the test.
The tests use the newly added SQL function to attach the injection_notice
function to an injection point

+	if (injection_point_local)
+	{
+		condition.type = INJ_CONDITION_PID;
+		condition.pid = MyProcPid;
+	}

Hmm. Is there a point in registering a condition that's linked to
the shared library injection_points? The automatic drop is kind of
nice to have, I guess, but it gives the illusion that an attached
callback will not be run. However, a callback from an entirely
different library *will* run anyway because it cannot look at the
condition registered, or the other library has an equivalent able to
treat a local condition with the same format and same structure as
what's in injection_points.c.

Different idea: we could allow one to pass a bytea that could be given
directly to InjectionPointAttach()? Without a use-case, I cannot get
much excited about that yet, but if someone has a use for it, why not.

Maybe we should also discard the pgstat_create_inj() call in this
path? The existing injection_points_detach() would be able to deal
with a point attached with a callback from a different library anyway.
Keeping pgstat_report_inj_fixed() feels OK in this new attach path.
--
Michael

#7Rahila Syed
rahilasyed90@gmail.com
In reply to: Michael Paquier (#6)
1 attachment(s)
Re: Extend injection_points_attach() to accept a user-defined function

Hi,

Thank you for your review.

+       if (injection_point_local)
+       {
+               condition.type = INJ_CONDITION_PID;
+               condition.pid = MyProcPid;
+       }

Hmm. Is there a point in registering a condition that's linked to
the shared library injection_points? The automatic drop is kind of
nice to have, I guess, but it gives the illusion that an attached
callback will not be run. However, a callback from an entirely
different library *will* run anyway because it cannot look at the
condition registered, or the other library has an equivalent able to
treat a local condition with the same format and same structure as
what's in injection_points.c.

The intention was to keep the implementation consistent across all
versions of injection_points_attach(). I agree that the conditional
implementation
is only feasible if the library defining the new function also supports the same
structure as injection_points.c. Therefore, I am okay with not adding
this support
for custom functions, since it would not be complete without the new function
being able to read the same structure.

Different idea: we could allow one to pass a bytea that could be given
directly to InjectionPointAttach()? Without a use-case, I cannot get
much excited about that yet, but if someone has a use for it, why not.

This seems helpful for situations where each module needs to provide its
own custom data to the injection point. This idea is implemented in the
attached patch.

Maybe we should also discard the pgstat_create_inj() call in this
path? The existing injection_points_detach() would be able to deal
with a point attached with a callback from a different library anyway.
Keeping pgstat_report_inj_fixed() feels OK in this new attach path.

OK, it makes sense to leave it out of this function for now. Since
pgstat_create_inj() currently only tracks the number of runs, it also
depends on any callback using the appropriate pgstat_report_* API
from the injection_point module. Without this, setting up the stats
infrastructure wouldn't be useful.

PFA the updated and rebased patch.

Thank you,
Rahila Syed

Attachments:

v4-0001-Overload-the-injection_points_attach-sql-function.patchapplication/octet-stream; name=v4-0001-Overload-the-injection_points_attach-sql-function.patchDownload
From 9c805ab18c88a12b4561aed11bbc23fa2519df05 Mon Sep 17 00:00:00 2001
From: Rahila Syed <rahilasyed.90@gmail.com>
Date: Wed, 29 Oct 2025 16:38:38 +0530
Subject: [PATCH] Overload the injection_points_attach sql function

Add a new sql function to assign C functions to injection
points. This function has same name but different arguments
as injection_points_attach. It takes as input module name and
function name in addition to injection point name.
---
 .../expected/injection_points.out             | 29 +++++++++++++++++++
 .../injection_points--1.0.sql                 | 11 +++++++
 .../injection_points/injection_points.c       | 29 +++++++++++++++++++
 .../injection_points/sql/injection_points.sql |  9 +++++-
 4 files changed, 77 insertions(+), 1 deletion(-)

diff --git a/src/test/modules/injection_points/expected/injection_points.out b/src/test/modules/injection_points/expected/injection_points.out
index 382f3b0bf88..39fa33e40cb 100644
--- a/src/test/modules/injection_points/expected/injection_points.out
+++ b/src/test/modules/injection_points/expected/injection_points.out
@@ -307,6 +307,35 @@ SELECT injection_points_detach('TestConditionLocal1');
  
 (1 row)
 
+--
+-- Test the custom function variant of injection_points_attach
+SELECT injection_points_attach('TestInjectionNoticeFunc', 'injection_points', 'injection_notice', NULL);
+ injection_points_attach 
+-------------------------
+ 
+(1 row)
+
+-- The injection point should be present
+SELECT point_name, library, function FROM injection_points_list()
+  ORDER BY point_name COLLATE "C";
+       point_name        |     library      |     function     
+-------------------------+------------------+------------------
+ TestInjectionNoticeFunc | injection_points | injection_notice
+(1 row)
+
+SELECT injection_points_run('TestInjectionNoticeFunc', NULL); -- notice function
+NOTICE:  notice triggered for injection point TestInjectionNoticeFunc
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
+SELECT injection_points_detach('TestInjectionNoticeFunc');
+ injection_points_detach 
+-------------------------
+ 
+(1 row)
+
 -- No points should be left around.
 SELECT point_name, library, function FROM injection_points_list()
   ORDER BY point_name COLLATE "C";
diff --git a/src/test/modules/injection_points/injection_points--1.0.sql b/src/test/modules/injection_points/injection_points--1.0.sql
index a7b61fbdfe6..ccb1679b666 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -14,6 +14,17 @@ RETURNS void
 AS 'MODULE_PATHNAME', 'injection_points_attach'
 LANGUAGE C STRICT PARALLEL UNSAFE;
 
+--
+-- injection_points_attach()
+--
+-- Attaches a function to the given injection point.
+--
+CREATE FUNCTION injection_points_attach(IN point_name TEXT,
+    IN library_name TEXT, IN function_name TEXT, IN private_data BYTEA)
+RETURNS void
+AS 'MODULE_PATHNAME', 'injection_points_attach_func'
+LANGUAGE C PARALLEL UNSAFE;
+
 --
 -- injection_points_load()
 --
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 31138301117..257e1af8424 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -391,6 +391,35 @@ injection_points_attach(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for creating an injection point.
+ */
+PG_FUNCTION_INFO_V1(injection_points_attach_func);
+Datum
+injection_points_attach_func(PG_FUNCTION_ARGS)
+{
+	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+	char	   *lib_name = text_to_cstring(PG_GETARG_TEXT_PP(1));
+	char	   *function = text_to_cstring(PG_GETARG_TEXT_PP(2));
+	bytea	*private_data = NULL;
+	int	private_data_size = 0;
+
+	if (!PG_ARGISNULL(3))
+	{
+		private_data = PG_GETARG_BYTEA_PP(3);
+		private_data_size = VARSIZE_ANY_EXHDR(private_data);
+	}
+
+	pgstat_report_inj_fixed(1, 0, 0, 0, 0);
+	if (private_data != NULL)
+		InjectionPointAttach(name, lib_name, function, VARDATA_ANY(private_data),
+								private_data_size);
+	else
+		InjectionPointAttach(name, lib_name, function, NULL,
+								0);
+	PG_RETURN_VOID();
+}
+
 /*
  * SQL function for loading an injection point.
  */
diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql
index 874421e9c11..d8652c42a84 100644
--- a/src/test/modules/injection_points/sql/injection_points.sql
+++ b/src/test/modules/injection_points/sql/injection_points.sql
@@ -87,7 +87,14 @@ SELECT injection_points_detach('TestConditionError');
 -- previously should work.
 SELECT injection_points_attach('TestConditionLocal1', 'error');
 SELECT injection_points_detach('TestConditionLocal1');
-
+--
+-- Test the custom function variant of injection_points_attach
+SELECT injection_points_attach('TestInjectionNoticeFunc', 'injection_points', 'injection_notice', NULL);
+-- The injection point should be present
+SELECT point_name, library, function FROM injection_points_list()
+  ORDER BY point_name COLLATE "C";
+SELECT injection_points_run('TestInjectionNoticeFunc', NULL); -- notice function
+SELECT injection_points_detach('TestInjectionNoticeFunc');
 -- No points should be left around.
 SELECT point_name, library, function FROM injection_points_list()
   ORDER BY point_name COLLATE "C";
-- 
2.34.1

#8Michael Paquier
michael@paquier.xyz
In reply to: Rahila Syed (#7)
Re: Extend injection_points_attach() to accept a user-defined function

On Fri, Nov 07, 2025 at 05:39:57PM +0530, Rahila Syed wrote:

OK, it makes sense to leave it out of this function for now. Since
pgstat_create_inj() currently only tracks the number of runs, it also
depends on any callback using the appropriate pgstat_report_* API
from the injection_point module. Without this, setting up the stats
infrastructure wouldn't be useful.

Yeah. What you are doing would be enough on simplicity ground. The
test added is also fine enough, it's safe to run even under an
installcheck. So LGTM to use a minimal implementation. Any thoughts
from others?
--
Michael

#9Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#8)
Re: Extend injection_points_attach() to accept a user-defined function

On Sun, Nov 09, 2025 at 08:35:55AM +0900, Michael Paquier wrote:

Yeah. What you are doing would be enough on simplicity ground. The
test added is also fine enough, it's safe to run even under an
installcheck. So LGTM to use a minimal implementation.

The patch had a one problem other than style. Contrary to its
existing cousin, the new function is not strict. Hence, it would
crash if given a NULL value for the point name, the library name or
the function name.

Fixed all that, adjusted a few comments, then applied the result.
--
Michael

#10Rahila Syed
rahilasyed90@gmail.com
In reply to: Michael Paquier (#9)
Re: Extend injection_points_attach() to accept a user-defined function

Hi,

Fixed all that, adjusted a few comments, then applied the result.

Thank you for making the fixes and committing the patch.

-Rahila Syed