From a6340f59478f40cce91a1153da03bf7cfb45aea9 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)

If we support transactional DDL for CREATE, DROP session  variables, we should to
support subtransactions too. Implementation is simple. Any value has two new flags:
created_subid and dropped_subid. At the subtransaction end for rollback we
clean entries from the stack related to subtransactions. When commit we update
created_subid and dropped_subid for parent subtransaction.
---
 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 ccee226cafe..76dc2a843cf 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -5213,6 +5213,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,
@@ -5382,6 +5384,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 c36af7bc460..c80ab48ec47 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 "catalog/pg_type.h"
 #include "commands/session_variable.h"
@@ -61,6 +62,8 @@ typedef struct SVariableData
 	bool		stacked;
 	LocalTransactionId created_lxid;
 	LocalTransactionId dropped_lxid;
+	SubTransactionId created_subid;
+	SubTransactionId dropped_subid;
 } SVariableData;
 
 typedef SVariableData *SVariable;
@@ -351,6 +354,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;
 }
 
@@ -387,6 +392,7 @@ DropVariableByName(DropSessionVarStmt *stmt)
 						stmt->name)));
 
 	svar->dropped_lxid = MyProc->vxid.lxid;
+	svar->dropped_subid = GetCurrentSubTransactionId();
 	created_or_dropped_lxid = MyProc->vxid.lxid;
 }
 
@@ -456,6 +462,7 @@ AtPreEOXact_SessionVariables(bool isCommit)
 						free_stacked_svars(svar->prev);
 						svar->prev = NULL;
 						svar->created_lxid = InvalidLocalTransactionId;
+						svar->created_subid = InvalidSubTransactionId;
 					}
 				}
 				else
@@ -502,6 +509,7 @@ AtPreEOXact_SessionVariables(bool isCommit)
 
 						/* revert dropped flag */
 						svar->dropped_lxid = InvalidLocalTransactionId;
+						svar->dropped_subid = InvalidSubTransactionId;
 					}
 				}
 			}
@@ -511,6 +519,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 2d00471da96..9031b5c384c 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 7335f15ed39..5ec412ad7c9 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.53.0

