From 33149505dab4e7b8f7025e9e37711b9bfb33c7ad Mon Sep 17 00:00:00 2001
From: Sami Imseih <samimseih@gmail.com>
Date: Thu, 9 Apr 2026 17:19:45 +0000
Subject: [PATCH v1 1/1] Allow a condition string in an injection point

This allows a caller of InjectionPointAttach to pass
an arbitrary string which can be later matched when the
injection point is run. This allows for more flexible
conditions to be used for running an injection point.
For example, this could be used to match by a relname
or OID. The condition string is capped at 256 bytes
which seems good enough, but this could be bumped up
in the future. Using a variable-length string may be
useful, but seemed hardly worth it at this point.
---
 src/backend/utils/misc/injection_point.c      |  1 +
 .../expected/injection_points.out             | 68 +++++++++++++++++++
 .../injection_points--1.0.sql                 |  5 +-
 .../injection_points/injection_points.c       | 55 +++++++++------
 .../injection_points/sql/injection_points.sql | 17 +++++
 5 files changed, 124 insertions(+), 22 deletions(-)

diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c
index 9d30843a111..272ef5e578a 100644
--- a/src/backend/utils/misc/injection_point.c
+++ b/src/backend/utils/misc/injection_point.c
@@ -322,6 +322,7 @@ InjectionPointAttach(const char *name,
 	strlcpy(entry->name, name, sizeof(entry->name));
 	strlcpy(entry->library, library, sizeof(entry->library));
 	strlcpy(entry->function, function, sizeof(entry->function));
+	memset(entry->private_data, 0, INJ_PRIVATE_MAXLEN);
 	if (private_data != NULL)
 		memcpy(entry->private_data, private_data, private_data_size);
 
diff --git a/src/test/modules/injection_points/expected/injection_points.out b/src/test/modules/injection_points/expected/injection_points.out
index a3ccaee5472..0f5c1726d3a 100644
--- a/src/test/modules/injection_points/expected/injection_points.out
+++ b/src/test/modules/injection_points/expected/injection_points.out
@@ -262,6 +262,32 @@ NOTICE:  notice triggered for injection point TestConditionLocal2
  
 (1 row)
 
+-- Local injection point condition string matching.
+SELECT injection_points_attach('TestConditionLocalString', 'notice', 'LocalData');
+ injection_points_attach 
+-------------------------
+ 
+(1 row)
+
+SELECT injection_points_run('TestConditionLocalString', 'LocalData'); -- notice
+NOTICE:  notice triggered for injection point TestConditionLocalString (LocalData)
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
+SELECT injection_points_run('TestConditionLocalString', 'WrongData'); -- nothing
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
+SELECT injection_points_run('TestConditionLocalString', NULL); -- nothing
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
 SELECT pg_backend_pid() AS oldpid \gset
 -- reload, local injection points should be gone.
 \c
@@ -285,6 +311,12 @@ SELECT injection_points_run('TestConditionLocal2'); -- nothing
  
 (1 row)
 
+SELECT injection_points_run('TestConditionLocalString', 'LocalData'); -- nothing
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
 SELECT injection_points_run('TestConditionError'); -- error
 ERROR:  error triggered for injection point TestConditionError
 SELECT injection_points_detach('TestConditionError');
@@ -307,6 +339,42 @@ SELECT injection_points_detach('TestConditionLocal1');
  
 (1 row)
 
+-- injection point condition string matching.
+SELECT injection_points_attach('TestConditionString', 'notice', 'MyString');
+ injection_points_attach 
+-------------------------
+ 
+(1 row)
+
+SELECT injection_points_run('TestConditionString', 'MyString'); -- notice
+NOTICE:  notice triggered for injection point TestConditionString (MyString)
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
+SELECT injection_points_run('TestConditionString', 'WrongString'); -- nothing
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
+SELECT injection_points_run('TestConditionString', NULL); -- nothing
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
+SELECT injection_points_detach('TestConditionString');
+ injection_points_detach 
+-------------------------
+ 
+(1 row)
+
+-- condition string too long for attach.
+SELECT injection_points_attach('TestInjectionError', 'error', repeat('a', 256));
+ERROR:  injection point condition string too long
+DETAIL:  injection point condition string must be less than 256 characters.
 -- Function variant for attach.
 SELECT injection_points_attach(repeat('a', 64), 'injection_points',
   'injection_notice', NULL);
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 861c7355d4e..2efb307f5bf 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,11 @@
 -- Attaches the action to the given injection point.
 --
 CREATE FUNCTION injection_points_attach(IN point_name TEXT,
-    IN action text)
+    IN action text,
+    IN condition_string text DEFAULT NULL)
 RETURNS void
 AS 'MODULE_PATHNAME', 'injection_points_attach'
-LANGUAGE C STRICT PARALLEL UNSAFE;
+LANGUAGE C PARALLEL UNSAFE;
 
 --
 -- injection_points_attach()
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 0f1af513673..22b902a4729 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -39,6 +39,7 @@ PG_MODULE_MAGIC;
 /* Maximum number of waits usable in injection points at once */
 #define INJ_MAX_WAIT	8
 #define INJ_NAME_MAXLEN	64
+#define INJ_DATA_MAXLEN 256
 
 /*
  * Conditions related to injection points.  This tracks in shared memory the
@@ -51,8 +52,8 @@ PG_MODULE_MAGIC;
  */
 typedef enum InjectionPointConditionType
 {
-	INJ_CONDITION_ALWAYS = 0,	/* always run */
-	INJ_CONDITION_PID,			/* PID restriction */
+	INJ_CONDITION_PID = 1 << 0, /* PID restriction */
+	INJ_CONDITION_STRING = 1 << 1,	/* generic string match against arg */
 } InjectionPointConditionType;
 
 typedef struct InjectionPointCondition
@@ -62,6 +63,9 @@ typedef struct InjectionPointCondition
 
 	/* ID of the process where the injection point is allowed to run */
 	int			pid;
+
+	/* String to match against the argument at run time */
+	char		str[INJ_DATA_MAXLEN];
 } InjectionPointCondition;
 
 /*
@@ -173,21 +177,21 @@ injection_init_shmem(void)
  * otherwise.
  */
 static bool
-injection_point_allowed(const InjectionPointCondition *condition)
+injection_point_allowed(const InjectionPointCondition *condition,
+						const char *arg)
 {
-	bool		result = true;
-
-	switch (condition->type)
-	{
-		case INJ_CONDITION_PID:
-			if (MyProcPid != condition->pid)
-				result = false;
-			break;
-		case INJ_CONDITION_ALWAYS:
-			break;
-	}
-
-	return result;
+	/* did not match the condition PID */
+	if ((condition->type & INJ_CONDITION_PID) &&
+		MyProcPid != condition->pid)
+		return false;
+
+	/* did not match the condition string */
+	if ((condition->type & INJ_CONDITION_STRING) &&
+		(condition->str[0] == '\0' ||
+		 arg == NULL || strcmp(condition->str, arg) != 0))
+		return false;
+
+	return true;
 }
 
 /*
@@ -219,7 +223,7 @@ injection_error(const char *name, const void *private_data, void *arg)
 	const InjectionPointCondition *condition = private_data;
 	char	   *argstr = arg;
 
-	if (!injection_point_allowed(condition))
+	if (!injection_point_allowed(condition, argstr))
 		return;
 
 	if (argstr)
@@ -235,7 +239,7 @@ injection_notice(const char *name, const void *private_data, void *arg)
 	const InjectionPointCondition *condition = private_data;
 	char	   *argstr = arg;
 
-	if (!injection_point_allowed(condition))
+	if (!injection_point_allowed(condition, argstr))
 		return;
 
 	if (argstr)
@@ -257,7 +261,7 @@ injection_wait(const char *name, const void *private_data, void *arg)
 	if (inj_state == NULL)
 		injection_init_shmem();
 
-	if (!injection_point_allowed(condition))
+	if (!injection_point_allowed(condition, arg))
 		return;
 
 	/*
@@ -318,6 +322,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	   *str = PG_ARGISNULL(2) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(2));
 	char	   *function;
 	InjectionPointCondition condition = {0};
 
@@ -332,10 +337,20 @@ injection_points_attach(PG_FUNCTION_ARGS)
 
 	if (injection_point_local)
 	{
-		condition.type = INJ_CONDITION_PID;
+		condition.type |= INJ_CONDITION_PID;
 		condition.pid = MyProcPid;
 	}
 
+	if (str)
+	{
+		if (strlen(str) >= INJ_DATA_MAXLEN)
+			ereport(ERROR,
+					(errmsg("injection point condition string too long"),
+					 errdetail("injection point condition string must be less than %d characters.", INJ_DATA_MAXLEN)));
+		condition.type |= INJ_CONDITION_STRING;
+		strlcpy(condition.str, str, INJ_DATA_MAXLEN);
+	}
+
 	InjectionPointAttach(name, "injection_points", function, &condition,
 						 sizeof(InjectionPointCondition));
 
diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql
index ba14df706ef..805936f7064 100644
--- a/src/test/modules/injection_points/sql/injection_points.sql
+++ b/src/test/modules/injection_points/sql/injection_points.sql
@@ -72,6 +72,12 @@ SELECT injection_points_attach('TestConditionLocal2', 'notice');
 SELECT injection_points_run('TestConditionLocal1'); -- error
 SELECT injection_points_run('TestConditionLocal2'); -- notice
 
+-- Local injection point condition string matching.
+SELECT injection_points_attach('TestConditionLocalString', 'notice', 'LocalData');
+SELECT injection_points_run('TestConditionLocalString', 'LocalData'); -- notice
+SELECT injection_points_run('TestConditionLocalString', 'WrongData'); -- nothing
+SELECT injection_points_run('TestConditionLocalString', NULL); -- nothing
+
 SELECT pg_backend_pid() AS oldpid \gset
 
 -- reload, local injection points should be gone.
@@ -81,6 +87,7 @@ SELECT pg_backend_pid() AS oldpid \gset
 SELECT wait_pid(:'oldpid');
 SELECT injection_points_run('TestConditionLocal1'); -- nothing
 SELECT injection_points_run('TestConditionLocal2'); -- nothing
+SELECT injection_points_run('TestConditionLocalString', 'LocalData'); -- nothing
 SELECT injection_points_run('TestConditionError'); -- error
 SELECT injection_points_detach('TestConditionError');
 -- Attaching injection points that use the same name as one defined locally
@@ -88,6 +95,16 @@ SELECT injection_points_detach('TestConditionError');
 SELECT injection_points_attach('TestConditionLocal1', 'error');
 SELECT injection_points_detach('TestConditionLocal1');
 
+-- injection point condition string matching.
+SELECT injection_points_attach('TestConditionString', 'notice', 'MyString');
+SELECT injection_points_run('TestConditionString', 'MyString'); -- notice
+SELECT injection_points_run('TestConditionString', 'WrongString'); -- nothing
+SELECT injection_points_run('TestConditionString', NULL); -- nothing
+SELECT injection_points_detach('TestConditionString');
+
+-- condition string too long for attach.
+SELECT injection_points_attach('TestInjectionError', 'error', repeat('a', 256));
+
 -- Function variant for attach.
 SELECT injection_points_attach(repeat('a', 64), 'injection_points',
   'injection_notice', NULL);
-- 
2.50.1

