From b0db6011b23fcad02a4c5d76df0e1ddc4831399b Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Mon, 8 Dec 2025 05:00:12 +0100
Subject: [PATCH 11/11] subtransaction support for session variables DDL
 (CREATE, DROP)

---
 src/backend/access/transam/xact.c             |   4 +
 src/backend/commands/session_variable.c       | 109 ++++++++++++++++++
 src/include/commands/session_variable.h       |   3 +
 .../expected/session_variables_ddl.out        |  21 ++++
 .../regress/sql/session_variables_ddl.sql     |  12 ++
 5 files changed, 149 insertions(+)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 8a80e9c00af..57ee8da6d1f 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -5208,6 +5208,8 @@ CommitSubTransaction(void)
 	AtEOSubXact_SPI(true, s->subTransactionId);
 	AtEOSubXact_on_commit_actions(true, s->subTransactionId,
 								  s->parent->subTransactionId);
+	AtEOSubXact_SessionVariables(true, s->subTransactionId,
+								 s->parent->subTransactionId);
 	AtEOSubXact_Namespace(true, s->subTransactionId,
 						  s->parent->subTransactionId);
 	AtEOSubXact_Files(true, s->subTransactionId,
@@ -5377,6 +5379,8 @@ AbortSubTransaction(void)
 		AtEOSubXact_SPI(false, s->subTransactionId);
 		AtEOSubXact_on_commit_actions(false, s->subTransactionId,
 									  s->parent->subTransactionId);
+		AtEOSubXact_SessionVariables(false, s->subTransactionId,
+									 s->parent->subTransactionId);
 		AtEOSubXact_Namespace(false, s->subTransactionId,
 							  s->parent->subTransactionId);
 		AtEOSubXact_Files(false, s->subTransactionId,
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 28dd23172f6..a27a91c2362 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "access/xact.h"
 #include "catalog/pg_language.h"
 #include "commands/session_variable.h"
 #include "executor/executor.h"
@@ -59,6 +60,8 @@ typedef struct SVariableData
 	bool		stacked;
 	LocalTransactionId created_lxid;
 	LocalTransactionId dropped_lxid;
+	SubTransactionId created_subid;
+	SubTransactionId dropped_subid;
 } SVariableData;
 
 typedef SVariableData *SVariable;
@@ -349,6 +352,8 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 	svar->stacked = false;
 	svar->dropped_lxid = InvalidLocalTransactionId;
 	svar->created_lxid = MyProc->vxid.lxid;
+	svar->dropped_subid = InvalidSubTransactionId;
+	svar->created_subid = GetCurrentSubTransactionId();
 	created_or_dropped_lxid = MyProc->vxid.lxid;
 }
 
@@ -385,6 +390,7 @@ DropVariableByName(DropSessionVarStmt *stmt)
 						stmt->name)));
 
 	svar->dropped_lxid = MyProc->vxid.lxid;
+	svar->dropped_subid = GetCurrentSubTransactionId();
 	created_or_dropped_lxid = MyProc->vxid.lxid;
 }
 
@@ -454,6 +460,7 @@ AtPreEOXact_SessionVariables(bool isCommit)
 						free_stacked_svars(svar->prev);
 						svar->prev = NULL;
 						svar->created_lxid = InvalidLocalTransactionId;
+						svar->created_subid = InvalidSubTransactionId;
 					}
 				}
 				else
@@ -500,6 +507,7 @@ AtPreEOXact_SessionVariables(bool isCommit)
 
 						/* revert dropped flag */
 						svar->dropped_lxid = InvalidLocalTransactionId;
+						svar->dropped_subid = InvalidSubTransactionId;
 					}
 				}
 			}
@@ -509,6 +517,107 @@ AtPreEOXact_SessionVariables(bool isCommit)
 	}
 }
 
+/*
+ * Post-subcommit or post-subabort cleanup
+ *
+ * During subabort, we can immediately remove entries created during this
+ * subtransaction. During subcommit, just transfer entries marked during
+ * this subtransaction as being the parent's responsibility.
+ */
+void
+AtEOSubXact_SessionVariables(bool isCommit,
+							 SubTransactionId mySubid,
+							 SubTransactionId parentSubid)
+{
+	if (created_or_dropped_lxid != InvalidLocalTransactionId)
+	{
+		HASH_SEQ_STATUS status;
+		SVariable	svar;
+
+		Assert(created_or_dropped_lxid == MyProc->vxid.lxid);
+		Assert(sessionvars);
+
+		hash_seq_init(&status, sessionvars);
+
+		while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+		{
+			if ((svar->dropped_lxid != InvalidLocalTransactionId) ||
+				(svar->created_lxid != InvalidLocalTransactionId))
+			{
+				if (!isCommit)
+				{
+					SVariable iterator = svar;
+					SVariable last = NULL;
+					SVariable first = NULL;
+
+					/* remove entries or flags by current subtransactions */
+					while (iterator)
+					{
+						SVariable current = iterator;
+
+						iterator = current->prev;
+
+						if (current->dropped_subid == mySubid)
+						{
+							current->dropped_lxid = InvalidLocalTransactionId;
+							current->dropped_subid = InvalidSubTransactionId;
+						}
+
+						if (current->created_subid == mySubid)
+						{
+							free_svar_value(current);
+							if (current->stacked)
+								pfree(current);
+						}
+						else
+						{
+							/* remember first not deleted svar */
+							if (first == NULL)
+								first = current;
+
+							if (last)
+								last->prev = current;
+
+							last = current;
+						}
+					}
+
+					/* Some svars was removed - set hashtab entry or remove it */
+					if (!first)
+					{
+						/* we have to remove entry from hash table */
+						(void) hash_search(sessionvars,
+										   NameStr(svar->varname),
+										   HASH_REMOVE,
+										   NULL);
+					}
+					else if (first->stacked)
+					{
+						memcpy(svar, first, sizeof(SVariableData));
+						svar->stacked = false;
+						pfree(first);
+					}
+				}
+				else
+				{
+					SVariable iterator = svar;
+
+					/* transfer responsibility to parent */
+					while (iterator)
+					{
+						if (iterator->dropped_subid == mySubid)
+							iterator->dropped_subid = parentSubid;
+						if (iterator->created_subid == mySubid)
+							iterator->created_subid = parentSubid;
+
+						iterator = iterator->prev;
+					}
+				}
+			}
+		}
+	}
+}
+
 /*
  * Assign the result of the evaluated expression to the session variable
  */
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 1218c566767..45ccbe2f046 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -40,5 +40,8 @@ extern void ExecuteLetStmt(ParseState *pstate, LetStmt *stmt, ParamListInfo para
 extern void ResetSessionVariables(void);
 
 extern void AtPreEOXact_SessionVariables(bool isCommit);
+extern void AtEOSubXact_SessionVariables(bool isCommit,
+										 SubTransactionId mySubid,
+										 SubTransactionId parentSubid);
 
 #endif
diff --git a/src/test/regress/expected/session_variables_ddl.out b/src/test/regress/expected/session_variables_ddl.out
index c10680512ce..af8e0b51444 100644
--- a/src/test/regress/expected/session_variables_ddl.out
+++ b/src/test/regress/expected/session_variables_ddl.out
@@ -107,4 +107,25 @@ SELECT VARIABLE(x);
  Hi
 (1 row)
 
+BEGIN;
+SAVEPOINT s1;
+DROP VARIABLE x;
+CREATE TEMP VARIABLE x AS varchar;
+DROP VARIABLE x;
+CREATE TEMP VARIABLE x AS varchar;
+LET x = 'Hello';
+SELECT VARIABLE(x);
+   x   
+-------
+ Hello
+(1 row)
+
+ROLLBACK TO s1;
+SELECT VARIABLE(x);
+ x  
+----
+ Hi
+(1 row)
+
+COMMIT;
 DROP VARIABLE x;
diff --git a/src/test/regress/sql/session_variables_ddl.sql b/src/test/regress/sql/session_variables_ddl.sql
index 9e79feed92b..a522eb95b54 100644
--- a/src/test/regress/sql/session_variables_ddl.sql
+++ b/src/test/regress/sql/session_variables_ddl.sql
@@ -98,4 +98,16 @@ SELECT VARIABLE(x);
 ROLLBACK;
 SELECT VARIABLE(x);
 
+BEGIN;
+SAVEPOINT s1;
+DROP VARIABLE x;
+CREATE TEMP VARIABLE x AS varchar;
+DROP VARIABLE x;
+CREATE TEMP VARIABLE x AS varchar;
+LET x = 'Hello';
+SELECT VARIABLE(x);
+ROLLBACK TO s1;
+SELECT VARIABLE(x);
+COMMIT;
+
 DROP VARIABLE x;
-- 
2.52.0

