Subject: Re: proposal: schema variables

From: Laurenz Albe <[email-masked]>
Date: Wed, 24 Jul 2024 19:02:11 +0200

On Wed, 2024-07-24 at 17:19 +0200, Pavel Stehule wrote:
> >   This is buggy:
> > 
> >     CREATE VARIABLE str AS text NOT NULL DEFAULT NULL;
> > 
> >   Ugh.
> > 
> >     SELECT str;
> >     ERROR:  null value is not allowed for NOT NULL session variable "laurenz.str"
> >     DETAIL:  The result of DEFAULT expression is NULL.
> > 
> >   Perhaps that is a leftover from the previous coding, but I think there need be
> >   no check upon SELECT.  It should be enough to check during CREATE VARIABLE and
> >   LET.
> 
> I think it is correct. When you use SELECT str, then DEFAULT expression is
> executed, and then the result is assigned to a variable, and there is NOT NULL
> guard, which raises an exception. The variable is not initialized when you run
> DDL, but it is initialized when you first read or write from/to the variable.
> The DEFAULT expression is not evaluated in DDL time. In this case, I can detect
> the problem in DDL time because the result is transformed to NULL node, but
> generally there can be SQL non immutable function, and then I need to wait until
> the DEFAULT expression will be evaluated - and it is time of first reading.
> Unfortunately, there is not an available check if some expression is NULL,
> that I can use in DDL time, but I have it in plpgsql_check.

That makes sense to me.

In that case, I think we can drop the requirement that NOT NULL variables
need a default clause.

> 
> >   I can see the usefulness of IMMUTABLE variables, but I am surprised that
> >   they are reset by DISCARD.  What is the use case you have in mind?
> >   The use case I can envision is an application that sets a value right after
> >   authentication, for use with row-level security.  But then it would be harmful
> >   if the user could reset the variable with DISCARD.
> 
> Primary I think about IMMUTABLE variables like about some form of cache.
> This cache is protected against unwanted repeated write - and can help with
> detection of this situation.

We can leave it as it is.  The IMMUTABLE feature need not go into the first
release, so that can be discussed some more later.

Thanks for the new patch set; I'll look at it soon.

Yours,
Laurenz Albe

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 24 Jul 2024 20:01:01 +0200

> In that case, I think we can drop the requirement that NOT NULL variables

removed

> Thanks for the new patch set; I'll look at it soon.

Thank you very much

Regards

Pavel

> Laurenz Albe
From f14a864a88c843e8e429497a53a241d1aea194c6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

This is a function designed for testing and debugging.  It returns the
content of sessionvars as-is, and can therefore display entries about
session variables that were dropped but for which this backend didn't
process the shared invalidations yet.
---
 src/backend/commands/session_variable.c | 102 ++++++++++++++++++++++++
 src/include/catalog/pg_proc.dat         |   8 ++
 2 files changed, 110 insertions(+)

diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index e190ef7389c..c8ddefc20e4 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -600,3 +600,105 @@ ExecuteLetStmt(ParseState *pstate,
 
 	PopActiveSnapshot();
 }
+
+/*
+ * pg_session_variables - designed for testing
+ *
+ * This is a function designed for testing and debugging.  It returns the
+ * content of sessionvars as-is, and can therefore display entries about
+ * session variables that were dropped but for which this backend didn't
+ * process the shared invalidations yet.
+ */
+Datum
+pg_session_variables(PG_FUNCTION_ARGS)
+{
+#define NUM_PG_SESSION_VARIABLES_ATTS 8
+
+	elog(DEBUG1, "pg_session_variables start");
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	if (sessionvars)
+	{
+		ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+		HASH_SEQ_STATUS status;
+		SVariable	svar;
+
+		hash_seq_init(&status, sessionvars);
+
+		while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+		{
+			Datum		values[NUM_PG_SESSION_VARIABLES_ATTS];
+			bool		nulls[NUM_PG_SESSION_VARIABLES_ATTS];
+			HeapTuple	tp;
+			bool		var_is_valid = false;
+
+			memset(values, 0, sizeof(values));
+			memset(nulls, 0, sizeof(nulls));
+
+			values[0] = ObjectIdGetDatum(svar->varid);
+			values[3] = ObjectIdGetDatum(svar->typid);
+
+			/* check if session variable is visible in system catalog */
+			tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+			/*
+			 * Sessionvars can hold data of variables removed from catalog,
+			 * (and not purged) and then namespacename and name cannot be read
+			 * from catalog.
+			 */
+			if (HeapTupleIsValid(tp))
+			{
+				Form_pg_variable varform = (Form_pg_variable) GETSTRUCT(tp);
+
+				/* When we see data in catalog */
+				if (svar->create_lsn == varform->varcreate_lsn)
+				{
+					/* and when when these data are not out of date */
+					values[1] = CStringGetTextDatum(
+													get_namespace_name(varform->varnamespace));
+
+					values[2] = CStringGetTextDatum(NameStr(varform->varname));
+					values[4] = CStringGetTextDatum(format_type_be(svar->typid));
+					values[5] = BoolGetDatum(false);
+
+					values[6] = BoolGetDatum(
+											 object_aclcheck(VariableRelationId, svar->varid,
+															 GetUserId(), ACL_SELECT) == ACLCHECK_OK);
+
+					values[7] = BoolGetDatum(
+											 object_aclcheck(VariableRelationId, svar->varid,
+															 GetUserId(), ACL_UPDATE) == ACLCHECK_OK);
+
+					var_is_valid = true;
+				}
+
+				ReleaseSysCache(tp);
+			}
+
+			if (!var_is_valid)
+			{
+				/*
+				 * When session variable was removed from catalog, but we
+				 * haven't processed the invlidation yet. In this case, we can
+				 * display only few oids. Other data are not available
+				 * (without Form_pg_variable record), or can be lost (because
+				 * there is not protection by dependency (more).
+				 */
+				nulls[1] = true;
+				nulls[2] = true;
+				nulls[4] = true;
+				nulls[6] = true;
+				nulls[7] = true;
+
+				values[5] = BoolGetDatum(true);
+			}
+
+			tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+		}
+	}
+
+	elog(DEBUG1, "pg_session_variables end");
+
+	return (Datum) 0;
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 644beb90432..394f50a55bf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12211,4 +12211,12 @@
   proargnames => '{summarized_tli,summarized_lsn,pending_lsn,summarizer_pid}',
   prosrc => 'pg_get_wal_summarizer_state' },
 
+{ oid => '8488', descr => 'list of used session variables',
+  proname => 'pg_session_variables', prorows => '1000', proretset => 't',
+  provolatile => 's', proparallel => 'r', prorettype => 'record',
+  proargtypes => '', proallargtypes => '{oid,text,text,oid,text,bool,bool,bool}',
+  proargmodes => '{o,o,o,o,o,o,o,o}',
+  proargnames => '{varid,schema,name,typid,typname,removed,can_select,can_update}',
+  prosrc => 'pg_session_variables' },
+
 ]
-- 
2.45.2

From b6141d53ed62b66ffeaf73aaaab5c814bcac7e4d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

This patch introduces new system catalog table pg_variable. This table holds metadata about
session variables created by command CREATE VARIABLE, and dropped by command DROP VARIABLE.
Both commands are implemented by this patch. Possibility to change owner, schema or rename.

Access to session variables can be controlled by SELECT or UPDATE rights. Both rights are
introduced by this patch too.

This patch enhancing pg_dump and psql to support session variables. The changes are related
to system catalog.

This patch is not short, but the code is simple.
---
 doc/src/sgml/catalogs.sgml                    | 120 +++++++++
 doc/src/sgml/ddl.sgml                         |  20 ++
 doc/src/sgml/event-trigger.sgml               |  24 ++
 doc/src/sgml/glossary.sgml                    |  16 ++
 doc/src/sgml/ref/allfiles.sgml                |   3 +
 .../sgml/ref/alter_default_privileges.sgml    |  26 +-
 doc/src/sgml/ref/alter_variable.sgml          | 178 ++++++++++++
 doc/src/sgml/ref/comment.sgml                 |   1 +
 doc/src/sgml/ref/create_schema.sgml           |   7 +-
 doc/src/sgml/ref/create_variable.sgml         | 155 +++++++++++
 doc/src/sgml/ref/drop_variable.sgml           | 117 ++++++++
 doc/src/sgml/ref/grant.sgml                   |   6 +
 doc/src/sgml/ref/revoke.sgml                  |   7 +
 doc/src/sgml/reference.sgml                   |   3 +
 src/backend/catalog/Makefile                  |   1 +
 src/backend/catalog/aclchk.c                  |  77 ++++++
 src/backend/catalog/dependency.c              |   7 +
 src/backend/catalog/meson.build               |   1 +
 src/backend/catalog/namespace.c               | 138 ++++++++++
 src/backend/catalog/objectaddress.c           | 121 ++++++++-
 src/backend/catalog/pg_shdepend.c             |   2 +
 src/backend/catalog/pg_variable.c             | 255 ++++++++++++++++++
 src/backend/commands/alter.c                  |   9 +
 src/backend/commands/dropcmds.c               |   4 +
 src/backend/commands/event_trigger.c          |   4 +
 src/backend/commands/seclabel.c               |   1 +
 src/backend/commands/tablecmds.c              |  42 +++
 src/backend/parser/gram.y                     | 105 +++++++-
 src/backend/parser/parse_utilcmd.c            |  12 +
 src/backend/tcop/utility.c                    |  16 ++
 src/backend/utils/adt/acl.c                   |   7 +
 src/backend/utils/cache/lsyscache.c           | 113 ++++++++
 src/bin/pg_dump/common.c                      |   3 +
 src/bin/pg_dump/dumputils.c                   |   6 +
 src/bin/pg_dump/pg_backup.h                   |   2 +
 src/bin/pg_dump/pg_backup_archiver.c          |   9 +
 src/bin/pg_dump/pg_dump.c                     | 198 +++++++++++++-
 src/bin/pg_dump/pg_dump.h                     |  19 ++
 src/bin/pg_dump/pg_dump_sort.c                |   6 +
 src/bin/pg_dump/t/002_pg_dump.pl              |  64 +++++
 src/bin/psql/command.c                        |   3 +
 src/bin/psql/describe.c                       |  96 +++++++
 src/bin/psql/describe.h                       |   3 +
 src/bin/psql/help.c                           |   1 +
 src/bin/psql/tab-complete.c                   |  44 ++-
 src/include/catalog/Makefile                  |   3 +-
 src/include/catalog/meson.build               |   1 +
 src/include/catalog/namespace.h               |   5 +-
 src/include/catalog/pg_default_acl.h          |   1 +
 src/include/catalog/pg_proc.dat               |   3 +
 src/include/catalog/pg_variable.h             |  81 ++++++
 src/include/nodes/parsenodes.h                |  16 ++
 src/include/parser/kwlist.h                   |   2 +
 src/include/tcop/cmdtaglist.h                 |   3 +
 src/include/utils/acl.h                       |   1 +
 src/include/utils/lsyscache.h                 |   9 +
 src/test/regress/expected/dependency.out      |  16 ++
 src/test/regress/expected/oidjoins.out        |   4 +
 src/test/regress/expected/psql.out            |  50 ++++
 .../regress/expected/session_variables.out    |  57 ++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/dependency.sql           |  13 +
 src/test/regress/sql/psql.sql                 |  21 ++
 src/test/regress/sql/session_variables.sql    |  61 +++++
 src/tools/pgindent/typedefs.list              |   4 +
 65 files changed, 2377 insertions(+), 28 deletions(-)
 create mode 100644 doc/src/sgml/ref/alter_variable.sgml
 create mode 100644 doc/src/sgml/ref/create_variable.sgml
 create mode 100644 doc/src/sgml/ref/drop_variable.sgml
 create mode 100644 src/backend/catalog/pg_variable.c
 create mode 100644 src/include/catalog/pg_variable.h
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b654fae1b2f..5ba18899345 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -369,6 +369,11 @@
       <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
       <entry>mappings of users to foreign servers</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+      <entry>session variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -9726,4 +9731,119 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </table>
  </sect1>
 
+ <sect1 id="catalog-pg-variable">
+  <title><structname>pg_variable</structname></title>
+
+  <indexterm zone="catalog-pg-variable">
+   <primary>pg_variable</primary>
+  </indexterm>
+
+  <para>
+   The table <structname>pg_variable</structname> provides information about
+   session variables.
+  </para>
+
+  <table>
+   <title><structname>pg_variable</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vartype</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The OID of the variable's data type
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varname</structfield> <type>name</type>
+      </para>
+      <para>
+       Name of the session variable
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varnamespace</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The OID of the namespace that contains this variable
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varowner</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Owner of the variable
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vartypmod</structfield> <type>int4</type>
+      </para>
+      <para>
+       <structfield>vartypmod</structfield> records type-specific data
+       supplied at variable creation time (for example, the maximum
+       length of a <type>varchar</type> column).  It is passed to
+       type-specific input functions and length coercion functions.
+       The value will generally be -1 for types that do not need <structfield>vartypmod</structfield>.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varcollation</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-collation"><structname>pg_collation</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The defined collation of the variable, or zero if the variable is
+       not of a collatable data type.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varacl</structfield> <type>aclitem[]</type>
+      </para>
+      <para>
+       Access privileges; see
+       <xref linkend="sql-grant"/> and
+       <xref linkend="sql-revoke"/>
+       for details
+      </para></entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
 </chapter>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 626d35514cc..ee1d8ea060c 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5298,6 +5298,26 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>
 
+  <sect1 id="ddl-session-variables">
+   <title>Session Variables</title>
+
+   <indexterm zone="ddl-session-variables">
+    <primary>Session variables</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>session variable</primary>
+   </indexterm>
+
+   <para>
+    Session variables are database objects that can hold a value.
+    Session variables, like relations, exist within a schema and their access
+    is controlled via <command>GRANT</command> and <command>REVOKE</command>
+    commands.  A session variable can be created by the <command>CREATE
+    VARIABLE</command> command.
+   </para>
+  </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>
 
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 8e009cca050..15b2af99dca 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -424,6 +424,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>ALTER VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>ALTER VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
@@ -712,6 +720,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>CREATE VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>CREATE VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
@@ -1000,6 +1016,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>DROP VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>DROP VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index 405fe6dc8b9..a31994efe6c 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -1654,6 +1654,22 @@
    </glossdef>
   </glossentry>
 
+  <glossentry id="glossary-session-variable">
+   <glossterm>Session variable</glossterm>
+   <glossdef>
+    <para>
+     A persistent database object that holds a value in session memory.  This
+     memory is not shared across sessions, and after session end, this memory
+     (the value) is released. The access (read or write) to session variables
+     is controlled by access rights similarly to other database object access
+     rights.
+    </para>
+    <para>
+     For more information, see <xref linkend="ddl-session-variables"/>.
+    </para>
+   </glossdef>
+  </glossentry>
+
   <glossentry id="glossary-shared-memory">
    <glossterm>Shared memory</glossterm>
    <glossdef>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..2f67de3e21b 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -47,6 +47,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY alterType          SYSTEM "alter_type.sgml">
 <!ENTITY alterUser          SYSTEM "alter_user.sgml">
 <!ENTITY alterUserMapping   SYSTEM "alter_user_mapping.sgml">
+<!ENTITY alterVariable      SYSTEM "alter_variable.sgml">
 <!ENTITY alterView          SYSTEM "alter_view.sgml">
 <!ENTITY analyze            SYSTEM "analyze.sgml">
 <!ENTITY begin              SYSTEM "begin.sgml">
@@ -99,6 +100,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createType         SYSTEM "create_type.sgml">
 <!ENTITY createUser         SYSTEM "create_user.sgml">
 <!ENTITY createUserMapping  SYSTEM "create_user_mapping.sgml">
+<!ENTITY createVariable     SYSTEM "create_variable.sgml">
 <!ENTITY createView         SYSTEM "create_view.sgml">
 <!ENTITY deallocate         SYSTEM "deallocate.sgml">
 <!ENTITY declare            SYSTEM "declare.sgml">
@@ -147,6 +149,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropType           SYSTEM "drop_type.sgml">
 <!ENTITY dropUser           SYSTEM "drop_user.sgml">
 <!ENTITY dropUserMapping    SYSTEM "drop_user_mapping.sgml">
+<!ENTITY dropVariable       SYSTEM "drop_variable.sgml">
 <!ENTITY dropView           SYSTEM "drop_view.sgml">
 <!ENTITY end                SYSTEM "end.sgml">
 <!ENTITY execute            SYSTEM "execute.sgml">
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 89aacec4fab..ae902d7f371 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -51,6 +51,10 @@ GRANT { { USAGE | CREATE }
     ON SCHEMAS
     TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
 
+GRANT { SELECT | UPDATE | ALL [ PRIVILEGES ] }
+    ON VARIABLES
+    TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
+
 REVOKE [ GRANT OPTION FOR ]
     { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
     [, ...] | ALL [ PRIVILEGES ] }
@@ -83,6 +87,12 @@ REVOKE [ GRANT OPTION FOR ]
     ON SCHEMAS
     FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
     [ CASCADE | RESTRICT ]
+
+REVOKE [ GRANT OPTION FOR ]
+    { { SELECT | UPDATE } [, ...] | ALL [ PRIVILEGES ] }
+    ON VARIABLES
+    FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
+    [ CASCADE | RESTRICT ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -117,14 +127,14 @@ REVOKE [ GRANT OPTION FOR ]
   <para>
    Currently,
    only the privileges for schemas, tables (including views and foreign
-   tables), sequences, functions, and types (including domains) can be
-   altered.  For this command, functions include aggregates and procedures.
-   The words <literal>FUNCTIONS</literal> and <literal>ROUTINES</literal> are
-   equivalent in this command.  (<literal>ROUTINES</literal> is preferred
-   going forward as the standard term for functions and procedures taken
-   together.  In earlier PostgreSQL releases, only the
-   word <literal>FUNCTIONS</literal> was allowed.  It is not possible to set
-   default privileges for functions and procedures separately.)
+   tables), sequences, functions, types (including domains) and session
+   variables can be altered.  For this command, functions include aggregates
+   and procedures.  The words <literal>FUNCTIONS</literal> and
+   <literal>ROUTINES</literal> are equivalent in this command.
+   (<literal>ROUTINES</literal> is preferred going forward as the standard
+   term for functions and procedures taken together.  In earlier PostgreSQL
+   releases, only the word <literal>FUNCTIONS</literal> was allowed.  It is not
+   possible to set default privileges for functions and procedures separately.)
   </para>
 
   <para>
diff --git a/doc/src/sgml/ref/alter_variable.sgml b/doc/src/sgml/ref/alter_variable.sgml
new file mode 100644
index 00000000000..d87570e7d8b
--- /dev/null
+++ b/doc/src/sgml/ref/alter_variable.sgml
@@ -0,0 +1,178 @@
+<!--
+doc/src/sgml/ref/alter_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-altervariable">
+ <indexterm zone="sql-altervariable">
+  <primary>ALTER VARIABLE</primary>
+ </indexterm>
+
+ <indexterm>
+  <primary>session variable</primary>
+  <secondary>altering</secondary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER VARIABLE</refname>
+  <refpurpose>
+   change the definition of a session variable
+  </refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER VARIABLE <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable class="parameter">new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
+ALTER VARIABLE <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
+ALTER VARIABLE <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   The <command>ALTER VARIABLE</command> command changes the definition of an
+   existing session variable. There are several subforms:
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>OWNER</literal></term>
+    <listitem>
+     <para>
+      This form changes the owner of the session variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RENAME</literal></term>
+    <listitem>
+     <para>
+      This form changes the name of the session variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>SET SCHEMA</literal></term>
+    <listitem>
+     <para>
+      This form moves the session variable into another schema.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+  </para>
+
+  <para>
+   Only the owner or a superuser is allowed to alter a session variable.
+   In order to move a session variable from one schema to another, the user
+   must also have the <literal>CREATE</literal> privilege on the new schema (or
+   be a superuser).
+
+   In order to move the session variable ownership from one role to another,
+   the user must also be a direct or indirect member of the new
+   owning role, and that role must have the <literal>CREATE</literal> privilege
+   on the session variable's schema (or be a superuser). These restrictions
+   enforce that altering the owner doesn't do anything you couldn't do by
+   dropping and recreating the session variable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+   <para>
+    <variablelist>
+     <varlistentry>
+      <term><replaceable class="parameter">name</replaceable></term>
+      <listitem>
+       <para>
+        The name (possibly schema-qualified) of the existing session variable
+        to alter.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">new_name</replaceable></term>
+      <listitem>
+       <para>
+        The new name for the session variable.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">new_owner</replaceable></term>
+      <listitem>
+       <para>
+        The user name of the new owner of the session variable.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">new_schema</replaceable></term>
+      <listitem>
+       <para>
+        The new schema for the session variable.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+  </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To rename a session variable:
+<programlisting>
+ALTER VARIABLE foo RENAME TO boo;
+</programlisting>
+  </para>
+
+  <para>
+   To change the owner of the session variable <literal>boo</literal> to
+   <literal>joe</literal>:
+<programlisting>
+ALTER VARIABLE boo OWNER TO joe;
+</programlisting>
+  </para>
+
+  <para>
+   To change the schema of the session variable <literal>boo</literal> to
+   <literal>private</literal>:
+<programlisting>
+ALTER VARIABLE boo SET SCHEMA private;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   Session variables and this command in particular are a PostgreSQL extension.
+  </para>
+ </refsect1>
+
+ <refsect1 id="sql-altervariable-see-also">
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createvariable"/></member>
+   <member><xref linkend="sql-dropvariable"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index 5b43c56b133..21cd80818fb 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -65,6 +65,7 @@ COMMENT ON
   TRANSFORM FOR <replaceable>type_name</replaceable> LANGUAGE <replaceable>lang_name</replaceable> |
   TRIGGER <replaceable class="parameter">trigger_name</replaceable> ON <replaceable class="parameter">table_name</replaceable> |
   TYPE <replaceable class="parameter">object_name</replaceable> |
+  VARIABLE <replaceable class="parameter">object_name</replaceable> |
   VIEW <replaceable class="parameter">object_name</replaceable>
 } IS { <replaceable class="parameter">string_literal</replaceable> | NULL }
 
diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc6..a834c876bc9 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -103,9 +103,10 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
         schema. Currently, only <command>CREATE
         TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
-        TRIGGER</command> and <command>GRANT</command> are accepted as clauses
-        within <command>CREATE SCHEMA</command>. Other kinds of objects may
-        be created in separate commands after the schema is created.
+        TRIGGER</command>, <command>GRANT</command> and <command>CREATE
+        VARIABLE</command> are accepted as clauses within <command>CREATE
+        SCHEMA</command>. Other kinds of objects may be created in separate
+        commands after the schema is created.
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
new file mode 100644
index 00000000000..2568bdff35c
--- /dev/null
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -0,0 +1,155 @@
+<!--
+doc/src/sgml/ref/create_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-createvariable">
+ <indexterm zone="sql-createvariable">
+  <primary>CREATE VARIABLE</primary>
+ </indexterm>
+
+ <indexterm>
+  <primary>session variable</primary>
+  <secondary>defining</secondary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE VARIABLE</refname>
+  <refpurpose>define a session variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
+</synopsis>
+ </refsynopsisdiv>
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   The <command>CREATE VARIABLE</command> command creates a session variable.
+   Session variables, like relations, exist within a schema and their access is
+   controlled via <command>GRANT</command> and <command>REVOKE</command>
+   commands.
+  </para>
+
+  <para>
+   The value of a session variable is local to the current session. Retrieving
+   a session variable's value returns either a NULL, unless its value is set to
+   something else in the current session with a LET command. The content of
+   a session variable is not transactional. This is the  same as regular
+   variables in procedural languages.
+  </para>
+
+  <para>
+   Session variables are retrieved by the <command>SELECT</command>
+   command.  Their value is set with the <command>LET</command> command.
+  </para>
+
+  <note>
+   <para>
+    Inside a query or an expression, the session variable can be shadowed by
+    column or by routine's variable or routine argument. Such collisions of
+    identifiers can be resolved by using qualified identifiers. Session variables
+    can use schema name, columns can use table aliases, routine variables
+    can use block labels, and routine arguments can use the routine name.
+   </para>
+  </note>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+
+   <varlistentry  id="sql-createvariable-if-not-exists">
+    <term><literal>IF NOT EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the name already exists. A notice is issued in
+      this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="sql-createvariable-name">
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name, optionally schema-qualified, of the session variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="sql-createvariable-data_type">
+    <term><replaceable class="parameter">data_type</replaceable></term>
+    <listitem>
+     <para>
+      The name, optionally schema-qualified, of the data type of the session
+      variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="sql-createvariable-collate">
+    <term><literal>COLLATE <replaceable>collation</replaceable></literal></term>
+    <listitem>
+     <para>
+      The <literal>COLLATE</literal> clause assigns a collation to the session
+      variable (which must be of a collatable data type).  If not specified,
+      the data type's default collation is used.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   Use the <command>DROP VARIABLE</command> command to remove a session
+   variable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create an date session variable <literal>var1</literal>:
+<programlisting>
+CREATE VARIABLE var1 AS date;
+LET var1 = current_date;
+SELECT var1;
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   The <command>CREATE VARIABLE</command> command is a
+   <productname>PostgreSQL</productname> extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-altervariable"/></member>
+   <member><xref linkend="sql-dropvariable"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml
new file mode 100644
index 00000000000..5bdb3560f0b
--- /dev/null
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -0,0 +1,117 @@
+<!--
+doc/src/sgml/ref/drop_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-dropvariable">
+ <indexterm zone="sql-dropvariable">
+  <primary>DROP VARIABLE</primary>
+ </indexterm>
+
+ <indexterm>
+  <primary>session variable</primary>
+  <secondary>removing</secondary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP VARIABLE</refname>
+  <refpurpose>remove a session variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP VARIABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [, ...] [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP VARIABLE</command> removes a session variable.
+   A session variable can only be removed by its owner or a superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the session variable does not exist. A notice is
+      issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name, optionally schema-qualified, of a session variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the session variable (such as
+      views), and in turn all objects that depend on those objects
+      (see <xref linkend="ddl-depend"/>).
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the session variable if any objects depend on it.  This is
+      the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To remove the session variable <literal>var1</literal>:
+
+<programlisting>
+DROP VARIABLE var1;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   The <command>DROP VARIABLE</command> command is a
+   <productname>PostgreSQL</productname> extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-altervariable"/></member>
+   <member><xref linkend="sql-createvariable"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index 999f657d5c0..78ff10fcf5e 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -101,6 +101,12 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
     [ WITH { ADMIN | INHERIT | SET } { OPTION | TRUE | FALSE } ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
 
+GRANT { SELECT | UPDATE | ALL [ PRIVILEGES ] }
+    ON VARIABLE <replaceable>variable_name</replaceable> [, ...]
+       | ALL VARIABLES IN SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...] }
+    TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
+    [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
+
 <phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase>
 
     [ GROUP ] <replaceable class="parameter">role_name</replaceable>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 8df492281a1..626c0231d0d 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -137,6 +137,13 @@ REVOKE [ { ADMIN | INHERIT | SET } OPTION FOR ]
   | CURRENT_ROLE
   | CURRENT_USER
   | SESSION_USER
+
+REVOKE [ GRANT OPTION FOR ]
+    { { SELECT | UPDATE } [, ...] | ALL [ PRIVILEGES ] }
+    ON VARIABLE <replaceable>variable_name</replaceable> [, ...]
+       | ALL VARIABLES IN SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...] }
+    FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
+    [ CASCADE | RESTRICT ]
 </synopsis>
  </refsynopsisdiv>
 
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..25578f3946c 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -75,6 +75,7 @@
    &alterType;
    &alterUser;
    &alterUserMapping;
+   &alterVariable;
    &alterView;
    &analyze;
    &begin;
@@ -127,6 +128,7 @@
    &createType;
    &createUser;
    &createUserMapping;
+   &createVariable;
    &createView;
    &deallocate;
    &declare;
@@ -175,6 +177,7 @@
    &dropType;
    &dropUser;
    &dropUserMapping;
+   &dropVariable;
    &dropView;
    &end;
    &execute;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1589a75fd53..7a90fcfc457 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -45,6 +45,7 @@ OBJS = \
 	pg_shdepend.o \
 	pg_subscription.o \
 	pg_type.o \
+	pg_variable.o \
 	storage.o \
 	toasting.o
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index a44ccee3b68..6968b1e6ebe 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -64,6 +64,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
@@ -290,6 +291,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
 		case OBJECT_PARAMETER_ACL:
 			whole_mask = ACL_ALL_RIGHTS_PARAMETER_ACL;
 			break;
+		case OBJECT_VARIABLE:
+			whole_mask = ACL_ALL_RIGHTS_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d", objtype);
 			/* not reached, but keep compiler quiet */
@@ -534,6 +538,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
 			all_privileges = ACL_ALL_RIGHTS_PARAMETER_ACL;
 			errormsg = gettext_noop("invalid privilege type %s for parameter");
 			break;
+		case OBJECT_VARIABLE:
+			all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			errormsg = gettext_noop("invalid privilege type %s for session variable");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) stmt->objtype);
@@ -639,6 +647,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 		case OBJECT_PARAMETER_ACL:
 			ExecGrant_Parameter(istmt);
 			break;
+		case OBJECT_VARIABLE:
+			ExecGrant_common(istmt, VariableRelationId, ACL_ALL_RIGHTS_VARIABLE, NULL);
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
@@ -829,6 +840,18 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
 					objects = lappend_oid(objects, parameterId);
 			}
 			break;
+		case OBJECT_VARIABLE:
+			foreach(cell, objnames)
+			{
+				RangeVar   *varvar = (RangeVar *) lfirst(cell);
+				Oid			relOid;
+
+				relOid = LookupVariable(varvar->schemaname,
+										varvar->relname,
+										false);
+				objects = lappend_oid(objects, relOid);
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) objtype);
@@ -918,6 +941,32 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 					table_close(rel, AccessShareLock);
 				}
 				break;
+			case OBJECT_VARIABLE:
+				{
+					ScanKeyData key;
+					Relation	rel;
+					TableScanDesc scan;
+					HeapTuple	tuple;
+
+					ScanKeyInit(&key,
+								Anum_pg_variable_varnamespace,
+								BTEqualStrategyNumber, F_OIDEQ,
+								ObjectIdGetDatum(namespaceId));
+
+					rel = table_open(VariableRelationId, AccessShareLock);
+					scan = table_beginscan_catalog(rel, 1, &key);
+
+					while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+					{
+						Oid			oid = ((Form_pg_proc) GETSTRUCT(tuple))->oid;
+
+						objects = lappend_oid(objects, oid);
+					}
+
+					table_endscan(scan);
+					table_close(rel, AccessShareLock);
+				}
+				break;
 			default:
 				/* should not happen */
 				elog(ERROR, "unrecognized GrantStmt.objtype: %d",
@@ -1077,6 +1126,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
 			all_privileges = ACL_ALL_RIGHTS_SCHEMA;
 			errormsg = gettext_noop("invalid privilege type %s for schema");
 			break;
+		case OBJECT_VARIABLE:
+			all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			errormsg = gettext_noop("invalid privilege type %s for session variable");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) action->objtype);
@@ -1268,6 +1321,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
 				this_privileges = ACL_ALL_RIGHTS_SCHEMA;
 			break;
 
+		case OBJECT_VARIABLE:
+			objtype = DEFACLOBJ_VARIABLE;
+			if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
+				this_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) iacls->objtype);
@@ -1511,6 +1570,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case DEFACLOBJ_NAMESPACE:
 				iacls.objtype = OBJECT_SCHEMA;
 				break;
+			case DEFACLOBJ_VARIABLE:
+				iacls.objtype = OBJECT_VARIABLE;
+				break;
 			default:
 				/* Shouldn't get here */
 				elog(ERROR, "unexpected default ACL type: %d",
@@ -1571,6 +1633,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case ParameterAclRelationId:
 				istmt.objtype = OBJECT_PARAMETER_ACL;
 				break;
+			case VariableRelationId:
+				istmt.objtype = OBJECT_VARIABLE;
+				break;
 			default:
 				elog(ERROR, "unexpected object class %u", classid);
 				break;
@@ -2810,6 +2875,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("permission denied for type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("permission denied for session variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("permission denied for view %s");
 						break;
@@ -2921,6 +2989,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("must be owner of type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("must be owner of session variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("must be owner of view %s");
 						break;
@@ -3069,6 +3140,8 @@ pg_aclmask(ObjectType objtype, Oid object_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return object_aclmask(TypeRelationId, object_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return object_aclmask(VariableRelationId, object_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) objtype);
@@ -4336,6 +4409,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 			defaclobjtype = DEFACLOBJ_NAMESPACE;
 			break;
 
+		case OBJECT_VARIABLE:
+			defaclobjtype = DEFACLOBJ_VARIABLE;
+			break;
+
 		default:
 			return NULL;
 	}
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0489cbabcb8..5a5e3f7b56a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -65,12 +65,14 @@
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
 #include "commands/policy.h"
 #include "commands/publicationcmds.h"
+#include "commands/schemacmds.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
 #include "commands/trigger.h"
@@ -1444,6 +1446,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemovePublicationById(object->objectId);
 			break;
 
+		case VariableRelationId:
+			DropVariableById(object->objectId);
+			break;
+
 		case CastRelationId:
 		case CollationRelationId:
 		case ConversionRelationId:
@@ -1467,6 +1473,7 @@ doDeletion(const ObjectAddress *object, int flags)
 			DropObjectById(object);
 			break;
 
+
 			/*
 			 * These global object types are not supported here.
 			 */
diff --git a/src/backend/catalog/meson.build b/src/backend/catalog/meson.build
index 2f3ded8a0e7..bfdb4da3301 100644
--- a/src/backend/catalog/meson.build
+++ b/src/backend/catalog/meson.build
@@ -32,6 +32,7 @@ backend_sources += files(
   'pg_shdepend.c',
   'pg_subscription.c',
   'pg_type.c',
+  'pg_variable.c',
   'storage.c',
   'toasting.c',
 )
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 43b707699d7..c1824f9115f 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -41,6 +41,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "common/hashfn_unstable.h"
 #include "funcapi.h"
@@ -971,6 +972,69 @@ RelationIsVisibleExt(Oid relid, bool *is_missing)
 	return visible;
 }
 
+/*
+ * VariableIsVisible
+ *		Determine whether a variable (identified by OID) is visible in the
+ *		current search path. Visible means "would be found by searching
+ *		for the unqualified variable name".
+ */
+bool
+VariableIsVisible(Oid varid)
+{
+	HeapTuple	vartup;
+	Form_pg_variable varform;
+	Oid			varnamespace;
+	bool		visible;
+
+	vartup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+	if (!HeapTupleIsValid(vartup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+	varform = (Form_pg_variable) GETSTRUCT(vartup);
+
+	recomputeNamespacePath();
+
+	/*
+	 * Quick check: if it ain't in the path at all, it ain't visible. Items in
+	 * the system namespace are surely in the path and so we needn't even do
+	 * list_member_oid() for them.
+	 */
+	varnamespace = varform->varnamespace;
+	if (varnamespace != PG_CATALOG_NAMESPACE &&
+		!list_member_oid(activeSearchPath, varnamespace))
+		visible = false;
+	else
+	{
+		/*
+		 * If it is in the path, it might still not be visible; it could be
+		 * hidden by another variable of the same name earlier in the path. So
+		 * we must do a slow check for conflicting relations.
+		 */
+		char	   *varname = NameStr(varform->varname);
+		ListCell   *l;
+
+		visible = false;
+		foreach(l, activeSearchPath)
+		{
+			Oid			namespaceId = lfirst_oid(l);
+
+			if (namespaceId == varnamespace)
+			{
+				/* Found it first in path */
+				visible = true;
+				break;
+			}
+			if (OidIsValid(get_varname_varid(varname, namespaceId)))
+			{
+				/* Found something else first in path */
+				break;
+			}
+		}
+	}
+
+	ReleaseSysCache(vartup);
+
+	return visible;
+}
 
 /*
  * TypenameGetTypid
@@ -3274,6 +3338,69 @@ TSConfigIsVisibleExt(Oid cfgid, bool *is_missing)
 	return visible;
 }
 
+/*
+ * Returns oid of session variable specified by possibly qualified identifier.
+ *
+ * If not found, returns InvalidOid if missing_ok, else throws error.
+ */
+Oid
+LookupVariable(const char *nspname,
+			   const char *varname,
+			   bool missing_ok)
+{
+	Oid			namespaceId;
+	Oid			varoid = InvalidOid;
+	ListCell   *l;
+
+	if (nspname)
+	{
+		namespaceId = LookupExplicitNamespace(nspname, missing_ok);
+
+		/*
+		 * If nspname is not a known namespace, then nspname.varname cannot be
+		 * any usable session variable.
+		 */
+		if (OidIsValid(namespaceId))
+		{
+			varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+									 PointerGetDatum(varname),
+									 ObjectIdGetDatum(namespaceId));
+		}
+	}
+	else
+	{
+		/* Iterate over schemas in search_path */
+		recomputeNamespacePath();
+
+		foreach(l, activeSearchPath)
+		{
+			namespaceId = lfirst_oid(l);
+
+			varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+									 PointerGetDatum(varname),
+									 ObjectIdGetDatum(namespaceId));
+
+			if (OidIsValid(varoid))
+				break;
+		}
+	}
+
+	if (!OidIsValid(varoid) && !missing_ok)
+	{
+		if (nspname)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("session variable \"%s.%s\" does not exist",
+							nspname, varname)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("session variable \"%s\" does not exist",
+							varname)));
+	}
+
+	return varoid;
+}
 
 /*
  * DeconstructQualifiedName
@@ -5075,3 +5202,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(isOtherTempNamespace(oid));
 }
+
+Datum
+pg_variable_is_visible(PG_FUNCTION_ARGS)
+{
+	Oid			oid = PG_GETARG_OID(0);
+
+	if (!SearchSysCacheExists1(VARIABLEOID, ObjectIdGetDatum(oid)))
+		PG_RETURN_NULL();
+
+	PG_RETURN_BOOL(VariableIsVisible(oid));
+}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 85a7b7e641a..ee70eeeed44 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -62,6 +62,7 @@
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
@@ -636,6 +637,20 @@ static const ObjectPropertyType ObjectProperty[] =
 		OBJECT_USER_MAPPING,
 		false
 	},
+	{
+		"session variable",
+		VariableRelationId,
+		VariableOidIndexId,
+		VARIABLEOID,
+		VARIABLENAMENSP,
+		Anum_pg_variable_oid,
+		Anum_pg_variable_varname,
+		Anum_pg_variable_varnamespace,
+		Anum_pg_variable_varowner,
+		Anum_pg_variable_varacl,
+		OBJECT_VARIABLE,
+		true
+	}
 };
 
 /*
@@ -831,6 +846,9 @@ static const struct object_type_map
 	},
 	{
 		"statistics object", OBJECT_STATISTIC_EXT
+	},
+	{
+		"session variable", OBJECT_VARIABLE
 	}
 };
 
@@ -856,6 +874,7 @@ static ObjectAddress get_object_address_attrdef(ObjectType objtype,
 												bool missing_ok);
 static ObjectAddress get_object_address_type(ObjectType objtype,
 											 TypeName *typename, bool missing_ok);
+static ObjectAddress get_object_address_variable(List *object, bool missing_ok);
 static ObjectAddress get_object_address_opcf(ObjectType objtype, List *object,
 											 bool missing_ok);
 static ObjectAddress get_object_address_opf_member(ObjectType objtype,
@@ -1126,6 +1145,9 @@ get_object_address(ObjectType objtype, Node *object,
 															 missing_ok);
 				address.objectSubId = 0;
 				break;
+			case OBJECT_VARIABLE:
+				address = get_object_address_variable(castNode(List, object), missing_ok);
+				break;
 				/* no default, to let compiler warn about missing case */
 		}
 
@@ -2000,16 +2022,20 @@ get_object_address_defacl(List *object, bool missing_ok)
 		case DEFACLOBJ_NAMESPACE:
 			objtype_str = "schemas";
 			break;
+		case DEFACLOBJ_VARIABLE:
+			objtype_str = "variables";
+			break;
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("unrecognized default ACL object type \"%c\"", objtype),
-					 errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
+					 errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
 							 DEFACLOBJ_RELATION,
 							 DEFACLOBJ_SEQUENCE,
 							 DEFACLOBJ_FUNCTION,
 							 DEFACLOBJ_TYPE,
-							 DEFACLOBJ_NAMESPACE)));
+							 DEFACLOBJ_NAMESPACE,
+							 DEFACLOBJ_VARIABLE)));
 	}
 
 	/*
@@ -2093,6 +2119,24 @@ textarray_to_strvaluelist(ArrayType *arr)
 	return list;
 }
 
+/*
+ * Find the ObjectAddress for a session variable
+ */
+static ObjectAddress
+get_object_address_variable(List *object, bool missing_ok)
+{
+	ObjectAddress address;
+	char	   *nspname = NULL;
+	char	   *varname = NULL;
+
+	ObjectAddressSet(address, VariableRelationId, InvalidOid);
+
+	DeconstructQualifiedName(object, &nspname, &varname);
+	address.objectId = LookupVariable(nspname, varname, missing_ok);
+
+	return address;
+}
+
 /*
  * SQL-callable version of get_object_address
  */
@@ -2287,6 +2331,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 		case OBJECT_TABCONSTRAINT:
 		case OBJECT_OPCLASS:
 		case OBJECT_OPFAMILY:
+		case OBJECT_VARIABLE:
 			objnode = (Node *) name;
 			break;
 		case OBJECT_ACCESS_METHOD:
@@ -2458,6 +2503,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 		case OBJECT_STATISTIC_EXT:
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSCONFIGURATION:
+		case OBJECT_VARIABLE:
 			if (!object_ownercheck(address.classId, address.objectId, roleid))
 				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
 							   NameListToString(castNode(List, object)));
@@ -3446,6 +3492,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case VariableRelationId:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
+				Form_pg_variable varform;
+
+				tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for session variable %u",
+						 object->objectId);
+
+				varform = (Form_pg_variable) GETSTRUCT(tup);
+
+				if (VariableIsVisible(object->objectId))
+					nspname = NULL;
+				else
+					nspname = get_namespace_name(varform->varnamespace);
+
+				appendStringInfo(&buffer, _("session variable %s"),
+								 quote_qualified_identifier(nspname,
+															NameStr(varform->varname)));
+
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		case TSParserRelationId:
 			{
 				HeapTuple	tup;
@@ -3798,6 +3870,16 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 										 _("default privileges on new schemas belonging to role %s"),
 										 rolename);
 						break;
+					case DEFACLOBJ_VARIABLE:
+						if (nspname)
+							appendStringInfo(&buffer,
+											 _("default privileges on new session variables belonging to role %s in schema %s"),
+											 rolename, nspname);
+						else
+							appendStringInfo(&buffer,
+											 _("default privileges on new session variables belonging to role %s"),
+											 rolename);
+						break;
 					default:
 						/* shouldn't get here */
 						if (nspname)
@@ -4614,6 +4696,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			appendStringInfoString(&buffer, "transform");
 			break;
 
+		case VariableRelationId:
+			appendStringInfoString(&buffer, "session variable");
+			break;
+
 		default:
 			elog(ERROR, "unsupported object class: %u", object->classId);
 	}
@@ -5720,6 +5806,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 						appendStringInfoString(&buffer,
 											   " on schemas");
 						break;
+					case DEFACLOBJ_VARIABLE:
+						appendStringInfoString(&buffer,
+											   " on session variables");
+						break;
 				}
 
 				if (objname)
@@ -5960,6 +6050,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+		case VariableRelationId:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
+				Form_pg_variable varform;
+
+				tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for session variable %u",
+						 object->objectId);
+
+				varform = (Form_pg_variable) GETSTRUCT(tup);
+
+				schema = get_namespace_name_or_temp(varform->varnamespace);
+				varname = NameStr(varform->varname);
+
+				appendStringInfo(&buffer, "%s",
+								 quote_qualified_identifier(schema, varname));
+
+				if (objname)
+					*objname = list_make2(schema, varname);
+
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			elog(ERROR, "unsupported object class: %u", object->classId);
 	}
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 753afb88453..1ea3d6db05c 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -46,6 +46,7 @@
 #include "catalog/pg_ts_dict.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "commands/alter.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
@@ -1714,6 +1715,7 @@ shdepReassignOwned_Owner(Form_pg_shdepend sdepForm, Oid newrole)
 		case DatabaseRelationId:
 		case TSConfigRelationId:
 		case TSDictionaryRelationId:
+		case VariableRelationId:
 			AlterObjectOwner_internal(sdepForm->classid,
 									  sdepForm->objid,
 									  newrole);
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
new file mode 100644
index 00000000000..f65121440a9
--- /dev/null
+++ b/src/backend/catalog/pg_variable.c
@@ -0,0 +1,255 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.c
+ *		session variables
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		src/backend/catalog/pg_variable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_variable.h"
+#include "miscadmin.h"
+#include "parser/parse_type.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+static ObjectAddress create_variable(const char *varName,
+									 Oid varNamespace,
+									 Oid varType,
+									 int32 varTypmod,
+									 Oid varOwner,
+									 Oid varCollation,
+									 bool if_not_exists);
+
+
+/*
+ * Creates entry in pg_variable table
+ */
+static ObjectAddress
+create_variable(const char *varName,
+				Oid varNamespace,
+				Oid varType,
+				int32 varTypmod,
+				Oid varOwner,
+				Oid varCollation,
+				bool if_not_exists)
+{
+	Acl		   *varacl;
+	NameData	varname;
+	bool		nulls[Natts_pg_variable];
+	Datum		values[Natts_pg_variable];
+	Relation	rel;
+	HeapTuple	tup;
+	TupleDesc	tupdesc;
+	ObjectAddress myself,
+				referenced;
+	ObjectAddresses *addrs;
+	Oid			varid;
+
+	Assert(varName);
+	Assert(OidIsValid(varNamespace));
+	Assert(OidIsValid(varType));
+	Assert(OidIsValid(varOwner));
+
+	rel = table_open(VariableRelationId, RowExclusiveLock);
+
+	/*
+	 * Check for duplicates. Note that this does not really prevent
+	 * duplicates, it's here just to provide nicer error message in common
+	 * case. The real protection is the unique key on the catalog.
+	 */
+	if (SearchSysCacheExists2(VARIABLENAMENSP,
+							  PointerGetDatum(varName),
+							  ObjectIdGetDatum(varNamespace)))
+	{
+		if (if_not_exists)
+			ereport(NOTICE,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("session variable \"%s\" already exists, skipping",
+							varName)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("session variable \"%s\" already exists",
+							varName)));
+
+		table_close(rel, RowExclusiveLock);
+
+		return InvalidObjectAddress;
+	}
+
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	namestrcpy(&varname, varName);
+
+	varid = GetNewOidWithIndex(rel, VariableOidIndexId, Anum_pg_variable_oid);
+
+	values[Anum_pg_variable_oid - 1] = ObjectIdGetDatum(varid);
+	values[Anum_pg_variable_varname - 1] = NameGetDatum(&varname);
+	values[Anum_pg_variable_varnamespace - 1] = ObjectIdGetDatum(varNamespace);
+	values[Anum_pg_variable_vartype - 1] = ObjectIdGetDatum(varType);
+	values[Anum_pg_variable_vartypmod - 1] = Int32GetDatum(varTypmod);
+	values[Anum_pg_variable_varowner - 1] = ObjectIdGetDatum(varOwner);
+	values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation);
+
+	varacl = get_user_default_acl(OBJECT_VARIABLE, varOwner,
+								  varNamespace);
+	if (varacl != NULL)
+		values[Anum_pg_variable_varacl - 1] = PointerGetDatum(varacl);
+	else
+		nulls[Anum_pg_variable_varacl - 1] = true;
+
+	tupdesc = RelationGetDescr(rel);
+
+	tup = heap_form_tuple(tupdesc, values, nulls);
+	CatalogTupleInsert(rel, tup);
+	Assert(OidIsValid(varid));
+
+	addrs = new_object_addresses();
+
+	ObjectAddressSet(myself, VariableRelationId, varid);
+
+	/* dependency on namespace */
+	ObjectAddressSet(referenced, NamespaceRelationId, varNamespace);
+	add_exact_object_address(&referenced, addrs);
+
+	/* dependency on used type */
+	ObjectAddressSet(referenced, TypeRelationId, varType);
+	add_exact_object_address(&referenced, addrs);
+
+	/* dependency on collation */
+	if (OidIsValid(varCollation) &&
+		varCollation != DEFAULT_COLLATION_OID)
+	{
+		ObjectAddressSet(referenced, CollationRelationId, varCollation);
+		add_exact_object_address(&referenced, addrs);
+	}
+
+	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
+	free_object_addresses(addrs);
+
+	/* dependency on owner */
+	recordDependencyOnOwner(VariableRelationId, varid, varOwner);
+
+	/* dependencies on roles mentioned in default ACL */
+	recordDependencyOnNewAcl(VariableRelationId, varid, 0, varOwner, varacl);
+
+	/* dependency on extension */
+	recordDependencyOnCurrentExtension(&myself, false);
+
+	heap_freetuple(tup);
+
+	/* Post creation hook for new function */
+	InvokeObjectPostCreateHook(VariableRelationId, varid, 0);
+
+	table_close(rel, RowExclusiveLock);
+
+	return myself;
+}
+
+/*
+ * Creates a new variable
+ *
+ * Used by CREATE VARIABLE command
+ */
+ObjectAddress
+CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
+{
+	Oid			namespaceid;
+	AclResult	aclresult;
+	Oid			typid;
+	int32		typmod;
+	Oid			varowner = GetUserId();
+	Oid			collation;
+	Oid			typcollation;
+	ObjectAddress variable;
+
+	namespaceid =
+		RangeVarGetAndCheckCreationNamespace(stmt->variable, NoLock, NULL);
+
+	typenameTypeIdAndMod(pstate, stmt->typeName, &typid, &typmod);
+
+	/* Disallow pseudotypes */
+	if (get_typtype(typid) == TYPTYPE_PSEUDO)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("session variable cannot be pseudo-type %s",
+						format_type_be(typid))));
+
+	aclresult = object_aclcheck(TypeRelationId, typid, GetUserId(), ACL_USAGE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error_type(aclresult, typid);
+
+	typcollation = get_typcollation(typid);
+
+	if (stmt->collClause)
+		collation = LookupCollation(pstate,
+									stmt->collClause->collname,
+									stmt->collClause->location);
+	else
+		collation = typcollation;;
+
+	/* Complain if COLLATE is applied to an uncollatable type */
+	if (OidIsValid(collation) && !OidIsValid(typcollation))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("collations are not supported by type %s",
+						format_type_be(typid)),
+				 parser_errposition(pstate, stmt->collClause->location)));
+
+	variable = create_variable(stmt->variable->relname,
+							   namespaceid,
+							   typid,
+							   typmod,
+							   varowner,
+							   collation,
+							   stmt->if_not_exists);
+
+	elog(DEBUG1, "record for session variable \"%s\" (oid:%d) was created in pg_variable",
+		 stmt->variable->relname, variable.objectId);
+
+	/* We want SessionVariableCreatePostprocess to see the catalog changes. */
+	CommandCounterIncrement();
+
+	return variable;
+}
+
+/*
+ * Drop variable by OID, and register the needed session variable
+ * cleanup.
+ */
+void
+DropVariableById(Oid varid)
+{
+	Relation	rel;
+	HeapTuple	tup;
+
+	rel = table_open(VariableRelationId, RowExclusiveLock);
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+
+	CatalogTupleDelete(rel, &tup->t_self);
+
+	ReleaseSysCache(tup);
+
+	table_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 4f99ebb4470..63fa99fad34 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -41,6 +41,7 @@
 #include "catalog/pg_ts_dict.h"
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
+#include "catalog/pg_variable.h"
 #include "commands/alter.h"
 #include "commands/collationcmds.h"
 #include "commands/dbcommands.h"
@@ -139,6 +140,10 @@ report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
 			Assert(OidIsValid(nspOid));
 			msgfmt = gettext_noop("text search configuration \"%s\" already exists in schema \"%s\"");
 			break;
+		case VariableRelationId:
+			Assert(OidIsValid(nspOid));
+			msgfmt = gettext_noop("session variable \"%s\" already exists in schema \"%s\"");
+			break;
 		default:
 			elog(ERROR, "unsupported object class: %u", classId);
 			break;
@@ -418,6 +423,7 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_PUBLICATION:
 		case OBJECT_SUBSCRIPTION:
+		case OBJECT_VARIABLE:
 			{
 				ObjectAddress address;
 				Relation	catalog;
@@ -561,6 +567,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_VARIABLE:
 			{
 				Relation	catalog;
 				Relation	relation;
@@ -645,6 +652,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 		case TSDictionaryRelationId:
 		case TSTemplateRelationId:
 		case TSConfigRelationId:
+		case VariableRelationId:
 			{
 				Relation	catalog;
 
@@ -875,6 +883,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 		case OBJECT_TABLESPACE:
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSCONFIGURATION:
+		case OBJECT_VARIABLE:
 			{
 				Relation	relation;
 				ObjectAddress address;
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 85eec7e3947..3cce17e92af 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -476,6 +476,10 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 			msg = gettext_noop("publication \"%s\" does not exist, skipping");
 			name = strVal(object);
 			break;
+		case OBJECT_VARIABLE:
+			msg = gettext_noop("session variable \"%s\" does not exist, skipping");
+			name = NameListToString(castNode(List, object));
+			break;
 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 7a5ed6b9850..6d41aa7ec3e 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -2156,6 +2156,8 @@ stringify_grant_objtype(ObjectType objtype)
 			return "TABLESPACE";
 		case OBJECT_TYPE:
 			return "TYPE";
+		case OBJECT_VARIABLE:
+			return "VARIABLE";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
@@ -2239,6 +2241,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 			return "TABLESPACES";
 		case OBJECT_TYPE:
 			return "TYPES";
+		case OBJECT_VARIABLE:
+			return "VARIABLES";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5607273bf9f..c026ab5dcb8 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -92,6 +92,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_USER_MAPPING:
+		case OBJECT_VARIABLE:
 			return false;
 
 			/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 721d24783b4..bc8569922f1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -51,6 +51,7 @@
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
@@ -6724,6 +6725,8 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
  * Eventually, we'd like to propagate the check or rewrite operation
  * into such tables, but for now, just error out if we find any.
  *
+ * Check if the type "typeOid" is used as type of some session variable too.
+ *
  * Caller should provide either the associated relation of a rowtype,
  * or a type name (not both) for use in the error message, if any.
  *
@@ -6786,6 +6789,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
+		/* Don't allow change of type used by session's variable */
+		if (pg_depend->classid == VariableRelationId)
+		{
+			Oid			varid = pg_depend->objid;
+
+			if (origTypeName)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it",
+								origTypeName,
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter foreign table \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_RELATION ||
+					 origRelation->rd_rel->relkind == RELKIND_MATVIEW ||
+					 origRelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter table \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+
+			continue;
+		}
+
 		/* Else, ignore dependees that aren't relations */
 		if (pg_depend->classid != RelationRelationId)
 			continue;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a043fd4c669..57b5f6882b1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -53,6 +53,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_trigger.h"
+#include "catalog/pg_variable.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "gramparse.h"
@@ -295,8 +296,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
 		CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt
 		CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
-		CreateSchemaStmt CreateSeqStmt CreateStmt CreateStatsStmt CreateTableSpaceStmt
-		CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
+		CreateSchemaStmt CreateSessionVarStmt CreateSeqStmt CreateStmt CreateStatsStmt
+		CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
 		CreateAssertionStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
 		CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
 		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
@@ -790,8 +791,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UESCAPE UNBOUNDED UNCONDITIONAL UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
-	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIABLE VARIABLES
+	VARIADIC VARYING VERBOSE VERSION_P VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -1061,6 +1062,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1605,6 +1607,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;

@@ -5202,6 +5205,34 @@ create_extension_opt_item:
 				}
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY :
+ *				CREATE VARIABLE varname [AS] type
+ *
+ *****************************************************************************/
+
+CreateSessionVarStmt:
+			CREATE VARIABLE qualified_name opt_as Typename opt_collate_clause
+				{
+					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
+					n->variable = $3;
+					n->typeName = $5;
+					n->collClause = (CollateClause *) $6;
+					n->if_not_exists = false;
+					$$ = (Node *) n;
+				}
+			| CREATE VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause
+				{
+					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
+					n->variable = $6;
+					n->typeName = $8;
+					n->collClause = (CollateClause *) $9;
+					n->if_not_exists = true;
+					$$ = (Node *) n;
+				}
+		;
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
@@ -7006,6 +7037,7 @@ object_type_any_name:
 			| TEXT_P SEARCH DICTIONARY				{ $$ = OBJECT_TSDICTIONARY; }
 			| TEXT_P SEARCH TEMPLATE				{ $$ = OBJECT_TSTEMPLATE; }
 			| TEXT_P SEARCH CONFIGURATION			{ $$ = OBJECT_TSCONFIGURATION; }
+			| VARIABLE								{ $$ = OBJECT_VARIABLE; }
 		;
 
 /*
@@ -7882,6 +7914,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
+			| VARIABLE qualified_name_list
+				{
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_VARIABLE;
+					n->objs = $2;
+					$$ = n;
+				}
 			| ALL TABLES IN_P SCHEMA name_list
 				{
 					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -7927,6 +7967,14 @@ privilege_target:
 					n->objs = $5;
 					$$ = n;
 				}
+			| ALL VARIABLES IN_P SCHEMA name_list
+				{
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+					n->targtype = ACL_TARGET_ALL_IN_SCHEMA;
+					n->objtype = OBJECT_VARIABLE;
+					n->objs = $5;
+					$$ = n;
+				}
 		;

@@ -8124,6 +8172,7 @@ defacl_privilege_target:
 			| SEQUENCES		{ $$ = OBJECT_SEQUENCE; }
 			| TYPES_P		{ $$ = OBJECT_TYPE; }
 			| SCHEMAS		{ $$ = OBJECT_SCHEMA; }
+			| VARIABLES		{ $$ = OBJECT_VARIABLE; }
 		;

@@ -9906,6 +9955,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
+			| ALTER VARIABLE any_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_VARIABLE;
+					n->object = (Node *) $3;
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER VARIABLE IF_P EXISTS any_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_VARIABLE;
+					n->object = (Node *) $5;
+					n->newname = $8;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
 		;
 
 opt_column: COLUMN
@@ -10267,6 +10334,24 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
+			| ALTER VARIABLE any_name SET SCHEMA name
+				{
+					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+					n->objectType = OBJECT_VARIABLE;
+					n->object = (Node *) $3;
+					n->newschema = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER VARIABLE IF_P EXISTS any_name SET SCHEMA name
+				{
+					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+					n->objectType = OBJECT_VARIABLE;
+					n->object = (Node *) $5;
+					n->newschema = $8;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*****************************************************************************
@@ -10548,6 +10633,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
 					n->newowner = $6;
 					$$ = (Node *) n;
 				}
+			| ALTER VARIABLE any_name OWNER TO RoleSpec
+				{
+					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+					n->objectType = OBJECT_VARIABLE;
+					n->object = (Node *) $3;
+					n->newowner = $6;
+					$$ = (Node *)n;
+				}
 		;

@@ -17878,6 +17971,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18533,6 +18628,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index d5c2b2ff0b0..2fba90c95a0 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -107,6 +107,7 @@ typedef struct
 	List	   *indexes;		/* CREATE INDEX items */
 	List	   *triggers;		/* CREATE TRIGGER items */
 	List	   *grants;			/* GRANT items */
+	List	   *variables;		/* CREATE VARIABLE items */
 } CreateSchemaStmtContext;

@@ -3966,6 +3967,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 	cxt.indexes = NIL;
 	cxt.triggers = NIL;
 	cxt.grants = NIL;
+	cxt.variables = NIL;
 
 	/*
 	 * Run through each schema element in the schema element list. Separate
@@ -4034,6 +4036,15 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				cxt.grants = lappend(cxt.grants, element);
 				break;
 
+			case T_CreateSessionVarStmt:
+				{
+					CreateSessionVarStmt *elp = (CreateSessionVarStmt *) element;
+
+					setSchemaName(cxt.schemaname, &elp->variable->schemaname);
+					cxt.variables = lappend(cxt.variables, element);
+				}
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -4047,6 +4058,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 	result = list_concat(result, cxt.indexes);
 	result = list_concat(result, cxt.triggers);
 	result = list_concat(result, cxt.grants);
+	result = list_concat(result, cxt.variables);
 
 	return result;
 }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index fa66b8017ed..ce8db7cda43 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -23,6 +23,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_inherits.h"
+#include "catalog/pg_variable.h"
 #include "catalog/toasting.h"
 #include "commands/alter.h"
 #include "commands/async.h"
@@ -182,6 +183,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_CreateRangeStmt:
 		case T_CreateRoleStmt:
 		case T_CreateSchemaStmt:
+		case T_CreateSessionVarStmt:
 		case T_CreateSeqStmt:
 		case T_CreateStatsStmt:
 		case T_CreateStmt:
@@ -1386,6 +1388,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				}
 				break;
 
+			case T_CreateSessionVarStmt:
+				address = CreateVariable(pstate, (CreateSessionVarStmt *) parsetree);
+				break;
+
 				/*
 				 * ************* object creation / destruction **************
 				 */
@@ -2338,6 +2344,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_STATISTIC_EXT:
 			tag = CMDTAG_ALTER_STATISTICS;
 			break;
+		case OBJECT_VARIABLE:
+			tag = CMDTAG_ALTER_VARIABLE;
+			break;
 		default:
 			tag = CMDTAG_UNKNOWN;
 			break;
@@ -2646,6 +2655,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_STATISTIC_EXT:
 					tag = CMDTAG_DROP_STATISTICS;
 					break;
+				case OBJECT_VARIABLE:
+					tag = CMDTAG_DROP_VARIABLE;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
@@ -3222,6 +3234,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_CreateSessionVarStmt:
+			tag = CMDTAG_CREATE_VARIABLE;
+			break;
+
 		default:
 			elog(WARNING, "unrecognized node type: %d",
 				 (int) nodeTag(parsetree));
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index d7b39140b3d..5f80af4f5e0 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -850,6 +850,10 @@ acldefault(ObjectType objtype, Oid ownerId)
 			world_default = ACL_NO_RIGHTS;
 			owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL;
 			break;
+		case OBJECT_VARIABLE:
+			world_default = ACL_NO_RIGHTS;
+			owner_default = ACL_ALL_RIGHTS_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d", (int) objtype);
 			world_default = ACL_NO_RIGHTS;	/* keep compiler quiet */
@@ -947,6 +951,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
 		case 'T':
 			objtype = OBJECT_TYPE;
 			break;
+		case 'V':
+			objtype = OBJECT_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized object type abbreviation: %c", objtypec);
 	}
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 48a280d089b..9a5e23665cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -38,6 +38,7 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3714,3 +3715,115 @@ get_subscription_name(Oid subid, bool missing_ok)
 
 	return subname;
 }
+
+/*				---------- PG_VARIABLE CACHE ----------				 */
+
+/*
+ * get_varname_varid
+ *		Given name and namespace of variable, look up the OID.
+ */
+Oid
+get_varname_varid(const char *varname, Oid varnamespace)
+{
+	return GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+						   PointerGetDatum(varname),
+						   ObjectIdGetDatum(varnamespace));
+}
+
+/*
+ * get_session_variable_name
+ *		Returns a palloc'd copy of the name of a given session variable.
+ */
+char *
+get_session_variable_name(Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	char	   *varname;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	varname = pstrdup(NameStr(varform->varname));
+
+	ReleaseSysCache(tup);
+
+	return varname;
+}
+
+/*
+ * get_session_variable_namespace
+ *		Returns the pg_namespace OID associated with a given session variable.
+ */
+Oid
+get_session_variable_namespace(Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	Oid			varnamespace;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	varnamespace = varform->varnamespace;
+
+	ReleaseSysCache(tup);
+
+	return varnamespace;
+}
+
+/*
+ * Returns the type, typmod and collid of the given session variable.
+ */
+Oid
+get_session_variable_type(Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	Oid			vartype;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	vartype = varform->vartype;
+
+	ReleaseSysCache(tup);
+
+	return vartype;
+}
+
+/*
+ * Returns the type, typmod and collid of the given session variable.
+ */
+void
+get_session_variable_type_typmod_collid(Oid varid, Oid *typid, int32 *typmod,
+										Oid *collid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	*typid = varform->vartype;
+	*typmod = varform->vartypmod;
+	*collid = varform->varcollation;
+
+	ReleaseSysCache(tup);
+}
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index c323b5bd3d9..4079ad603ac 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -247,6 +247,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	pg_log_info("reading subscription membership of tables");
 	getSubscriptionTables(fout);
 
+	pg_log_info("reading variables");
+	getVariables(fout);
+
 	free(inhinfo);				/* not needed any longer */
 
 	*numTablesPtr = numTables;
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 5649859aa1e..50de5d0ea29 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -511,6 +511,12 @@ do { \
 		CONVERT_PRIV('r', "SELECT");
 		CONVERT_PRIV('w', "UPDATE");
 	}
+	else if (strcmp(type, "VARIABLE") == 0 ||
+			 strcmp(type, "VARIABLES") == 0)
+	{
+		CONVERT_PRIV('r', "SELECT");
+		CONVERT_PRIV('w', "UPDATE");
+	}
 	else
 		abort();
 
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index fbf5f1c515e..e81cfde274e 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -133,12 +133,14 @@ typedef struct _restoreOptions
 	int			selFunction;
 	int			selTrigger;
 	int			selTable;
+	int			selVariable;
 	SimpleStringList indexNames;
 	SimpleStringList functionNames;
 	SimpleStringList schemaNames;
 	SimpleStringList schemaExcludeNames;
 	SimpleStringList triggerNames;
 	SimpleStringList tableNames;
+	SimpleStringList variableNames;
 
 	int			useDB;
 	ConnParams	cparams;		/* parameters to use if useDB */
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 68e321212d9..76846cc7632 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3098,6 +3098,14 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
 					!simple_string_list_member(&ropt->triggerNames, te->tag))
 					return 0;
 			}
+			else if (strcmp(te->desc, "VARIABLE") == 0)
+			{
+				if (!ropt->selVariable)
+					return 0;
+				if (ropt->variableNames.head != NULL &&
+					!simple_string_list_member(&ropt->variableNames, te->tag))
+					return 0;
+			}
 			else
 				return 0;
 		}
@@ -3647,6 +3655,7 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te)
 		strcmp(type, "TEXT SEARCH DICTIONARY") == 0 ||
 		strcmp(type, "TEXT SEARCH CONFIGURATION") == 0 ||
 		strcmp(type, "TYPE") == 0 ||
+		strcmp(type, "VARIABLE") == 0 ||
 		strcmp(type, "VIEW") == 0 ||
 	/* non-schema-specified objects */
 		strcmp(type, "DATABASE") == 0 ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b8b1888bd33..64a6336a897 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -324,6 +324,7 @@ static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo);
 static void dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo);
 static void dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo);
 static void dumpSubscriptionTable(Archive *fout, const SubRelInfo *subrinfo);
+static void dumpVariable(Archive *fout, const VariableInfo *varinfo);
 static void dumpDatabase(Archive *fout);
 static void dumpDatabaseConfig(Archive *AH, PQExpBuffer outbuf,
 							   const char *dbname, Oid dboid);
@@ -5313,6 +5314,193 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 	return next_possible_free_oid;
 }
 
+/*
+ * getVariables
+ *	  get information about variables
+ */
+void
+getVariables(Archive *fout)
+{
+	PQExpBuffer query;
+	PGresult   *res;
+	VariableInfo *varinfo;
+	int			i_tableoid;
+	int			i_oid;
+	int			i_varname;
+	int			i_varnamespace;
+	int			i_vartype;
+	int			i_vartypname;
+	int			i_varowner;
+	int			i_varcollation;
+	int			i_varacl;
+	int			i_acldefault;
+	int			i,
+				ntups;
+
+	if (fout->remoteVersion < 180000)
+		return;
+
+	query = createPQExpBuffer();
+
+	resetPQExpBuffer(query);
+
+	/* Get the variables in current database. */
+	appendPQExpBuffer(query,
+					  "SELECT v.tableoid, v.oid, v.varname,\n"
+					  "v.varnamespace,\n"
+					  "v.vartype,\n"
+					  "pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n"
+					  "CASE WHEN v.varcollation <> t.typcollation "
+					  "THEN v.varcollation ELSE 0 END AS varcollation,\n"
+					  "v.varowner,\n"
+					  "v.varacl,\n"
+					  "acldefault('V', v.varowner) AS acldefault\n"
+					  "FROM pg_catalog.pg_variable v\n"
+					  "JOIN pg_catalog.pg_type t "
+					  "ON (v.vartype = t.oid)");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	i_tableoid = PQfnumber(res, "tableoid");
+	i_oid = PQfnumber(res, "oid");
+	i_varname = PQfnumber(res, "varname");
+	i_varnamespace = PQfnumber(res, "varnamespace");
+	i_vartype = PQfnumber(res, "vartype");
+	i_vartypname = PQfnumber(res, "vartypname");
+	i_varcollation = PQfnumber(res, "varcollation");
+
+	i_varowner = PQfnumber(res, "varowner");
+	i_varacl = PQfnumber(res, "varacl");
+	i_acldefault = PQfnumber(res, "acldefault");
+
+	varinfo = pg_malloc(ntups * sizeof(VariableInfo));
+
+	for (i = 0; i < ntups; i++)
+	{
+		TypeInfo   *vtype;
+
+		varinfo[i].dobj.objType = DO_VARIABLE;
+		varinfo[i].dobj.catId.tableoid =
+			atooid(PQgetvalue(res, i, i_tableoid));
+		varinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+		AssignDumpId(&varinfo[i].dobj);
+		varinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_varname));
+		varinfo[i].dobj.namespace =
+			findNamespace(atooid(PQgetvalue(res, i, i_varnamespace)));
+
+		varinfo[i].vartype = atooid(PQgetvalue(res, i, i_vartype));
+		varinfo[i].vartypname = pg_strdup(PQgetvalue(res, i, i_vartypname));
+		varinfo[i].varcollation = atooid(PQgetvalue(res, i, i_varcollation));
+
+		varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl));
+		varinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault));
+		varinfo[i].dacl.privtype = 0;
+		varinfo[i].dacl.initprivs = NULL;
+		varinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_varowner));
+
+		/* Decide whether we want to dump it */
+		selectDumpableObject(&(varinfo[i].dobj), fout);
+
+		/* Do not try to dump ACL if no ACL exists. */
+		if (!PQgetisnull(res, i, i_varacl))
+			varinfo[i].dobj.components |= DUMP_COMPONENT_ACL;
+
+		if (strlen(varinfo[i].rolname) == 0)
+			pg_log_warning("owner of variable \"%s\" appears to be invalid",
+						   varinfo[i].dobj.name);
+
+		/* Decide whether we want to dump it */
+		selectDumpableObject(&(varinfo[i].dobj), fout);
+
+		vtype = findTypeByOid(varinfo[i].vartype);
+		addObjectDependency(&varinfo[i].dobj, vtype->dobj.dumpId);
+	}
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpVariable
+ *	  dump the definition of the given session variable
+ */
+static void
+dumpVariable(Archive *fout, const VariableInfo *varinfo)
+{
+	DumpOptions *dopt = fout->dopt;
+
+	PQExpBuffer delq;
+	PQExpBuffer query;
+	char	   *qualvarname;
+	const char *vartypname;
+	Oid			varcollation;
+
+	/* Skip if not to be dumped */
+	if (!varinfo->dobj.dump || dopt->dataOnly)
+		return;
+
+	delq = createPQExpBuffer();
+	query = createPQExpBuffer();
+
+	qualvarname = pg_strdup(fmtQualifiedDumpable(varinfo));
+	vartypname = varinfo->vartypname;
+	varcollation = varinfo->varcollation;
+
+	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
+					  qualvarname);
+
+	appendPQExpBuffer(query, "CREATE VARIABLE %s AS %s",
+					  qualvarname, vartypname);
+
+	if (OidIsValid(varcollation))
+	{
+		CollInfo   *coll;
+
+		coll = findCollationByOid(varcollation);
+		if (coll)
+			appendPQExpBuffer(query, " COLLATE %s",
+							  fmtQualifiedDumpable(coll));
+	}
+
+	appendPQExpBuffer(query, ";\n");
+
+	if (varinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
+		ArchiveEntry(fout, varinfo->dobj.catId, varinfo->dobj.dumpId,
+					 ARCHIVE_OPTS(.tag = varinfo->dobj.name,
+								  .namespace = varinfo->dobj.namespace->dobj.name,
+								  .owner = varinfo->rolname,
+								  .description = "VARIABLE",
+								  .section = SECTION_PRE_DATA,
+								  .createStmt = query->data,
+								  .dropStmt = delq->data));
+
+	/* Dump comment if any */
+	if (varinfo->dobj.dump & DUMP_COMPONENT_COMMENT)
+		dumpComment(fout, "VARIABLE", qualvarname,
+					NULL, varinfo->rolname,
+					varinfo->dobj.catId, 0, varinfo->dobj.dumpId);
+
+	/* Dump ACL if any */
+	if (varinfo->dobj.dump & DUMP_COMPONENT_ACL)
+	{
+		char	   *qvarname = pg_strdup(fmtId(varinfo->dobj.name));
+
+		dumpACL(fout, varinfo->dobj.dumpId, InvalidDumpId, "VARIABLE",
+				qvarname, NULL,
+				varinfo->dobj.namespace->dobj.name, NULL, varinfo->rolname,
+				&varinfo->dacl);
+
+		free(qvarname);
+	}
+
+	destroyPQExpBuffer(delq);
+	destroyPQExpBuffer(query);
+
+	free(qualvarname);
+}
+
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
@@ -9875,7 +10063,8 @@ getAdditionalACLs(Archive *fout)
 					dobj->objType == DO_TABLE ||
 					dobj->objType == DO_PROCLANG ||
 					dobj->objType == DO_FDW ||
-					dobj->objType == DO_FOREIGN_SERVER)
+					dobj->objType == DO_FOREIGN_SERVER ||
+					dobj->objType == DO_VARIABLE)
 				{
 					DumpableObjectWithAcl *daobj = (DumpableObjectWithAcl *) dobj;
 
@@ -10474,6 +10663,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION_REL:
 			dumpSubscriptionTable(fout, (const SubRelInfo *) dobj);
 			break;
+		case DO_VARIABLE:
+			dumpVariable(fout, (VariableInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -14923,6 +15115,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
 		case DEFACLOBJ_NAMESPACE:
 			type = "SCHEMAS";
 			break;
+		case DEFACLOBJ_VARIABLE:
+			type = "VARIABLES";
+			break;
 		default:
 			/* shouldn't get here */
 			pg_fatal("unrecognized object type in default privileges: %d",
@@ -18370,6 +18565,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_CONVERSION:
 			case DO_TABLE:
 			case DO_TABLE_ATTACH:
+			case DO_VARIABLE:
 			case DO_ATTRDEF:
 			case DO_PROCLANG:
 			case DO_CAST:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 4b2e5870a9c..36a69a08324 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -52,6 +52,7 @@ typedef enum
 	DO_TABLE,
 	DO_TABLE_ATTACH,
 	DO_ATTRDEF,
+	DO_VARIABLE,
 	DO_INDEX,
 	DO_INDEX_ATTACH,
 	DO_STATSEXT,
@@ -692,6 +693,23 @@ typedef struct _SubRelInfo
 	char	   *srsublsn;
 } SubRelInfo;
 
+/*
+ * The VariableInfo struct is used to represent session variables
+ */
+typedef struct _VariableInfo
+{
+	DumpableObject dobj;
+	DumpableAcl dacl;
+	Oid			vartype;
+	char	   *vartypname;
+	char	   *varacl;
+	char	   *rvaracl;
+	char	   *initvaracl;
+	char	   *initrvaracl;
+	Oid			varcollation;
+	const char *rolname;		/* name of owner, or empty string */
+} VariableInfo;
+
 /*
  *	common utility functions
  */
@@ -775,5 +793,6 @@ extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
 								 int numTables);
 extern void getSubscriptions(Archive *fout);
 extern void getSubscriptionTables(Archive *fout);
+extern void getVariables(Archive *fout);
 
 #endif							/* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 4cb754caa55..ebb9bf037c9 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -78,6 +78,7 @@ enum dbObjectTypePriorities
 	PRIO_DUMMY_TYPE,
 	PRIO_ATTRDEF,
 	PRIO_LARGE_OBJECT,
+	PRIO_VARIABLE,
 	PRIO_PRE_DATA_BOUNDARY,		/* boundary! */
 	PRIO_TABLE_DATA,
 	PRIO_SEQUENCE_SET,
@@ -119,6 +120,7 @@ static const int dbObjectTypePriority[] =
 	[DO_TABLE] = PRIO_TABLE,
 	[DO_TABLE_ATTACH] = PRIO_TABLE_ATTACH,
 	[DO_ATTRDEF] = PRIO_ATTRDEF,
+	[DO_VARIABLE] = PRIO_VARIABLE,
 	[DO_INDEX] = PRIO_INDEX,
 	[DO_INDEX_ATTACH] = PRIO_INDEX_ATTACH,
 	[DO_STATSEXT] = PRIO_STATSEXT,
@@ -1490,6 +1492,10 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "POST-DATA BOUNDARY  (ID %d)",
 					 obj->dumpId);
 			return;
+		case DO_VARIABLE:
+			snprintf(buf, bufsize,
+					 "VARIABLE %s  (ID %d OID %u)",
+					 obj->name, obj->dumpId, obj->catId.oid);
 	}
 	/* shouldn't get here */
 	snprintf(buf, bufsize,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d3dd8784d64..d62db242287 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -789,6 +789,16 @@ my %tests = (
 		unlike => { no_privs => 1, },
 	  },
 
+	'ALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT SELECT ON VARIABLES TO PUBLIC'
+	  => {
+		create_order => 56,
+		create_sql   => 'ALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT SELECT ON VARIABLES TO PUBLIC;',
+		regexp => qr/^
+			\QALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT SELECT ON VARIABLES TO PUBLIC;\E/xm,
+		like => { %full_runs, section_post_data => 1, },
+		unlike => { no_privs => 1, },
+	  },
+
 	'ALTER ROLE regress_dump_test_role' => {
 		regexp => qr/^
 			\QALTER ROLE regress_dump_test_role WITH \E
@@ -1638,6 +1648,23 @@ my %tests = (
 		},
 	},
 
+	'COMMENT ON VARIABLE dump_test.variable1' => {
+		create_order => 71,
+		create_sql   => 'COMMENT ON VARIABLE dump_test.variable1
+					   IS \'comment on variable\';',
+		regexp =>
+		  qr/^\QCOMMENT ON VARIABLE dump_test.variable1 IS 'comment on variable';\E/m,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data     => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_measurement    => 1,
+		},
+	},
+
 	'COPY test_table' => {
 		create_order => 4,
 		create_sql => 'INSERT INTO dump_test.test_table (col1) '
@@ -3896,6 +3923,24 @@ my %tests = (
 		},
 	},
 
+	'CREATE VARIABLE test_variable' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE VARIABLE dump_test.variable1 AS integer;',
+		regexp => qr/^
+			\QCREATE VARIABLE dump_test.variable1 AS integer;\E/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_measurement    => 1,
+		},
+	},
+
 	'CREATE VIEW test_view' => {
 		create_order => 61,
 		create_sql => 'CREATE VIEW dump_test.test_view
@@ -4358,6 +4403,25 @@ my %tests = (
 		like => {},
 	},
 
+	'GRANT SELECT ON VARIABLE dump_test.variable1' => {
+		create_order => 73,
+		create_sql =>
+		  'GRANT SELECT ON VARIABLE dump_test.variable1 TO regress_dump_test_role;',
+		regexp => qr/^
+			\QGRANT SELECT ON VARIABLE dump_test.variable1 TO regress_dump_test_role;\E
+			/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			no_privs                 => 1,
+			only_dump_measurement    => 1,
+		},
+	},
+
 	'REFRESH MATERIALIZED VIEW matview' => {
 		regexp => qr/^\QREFRESH MATERIALIZED VIEW dump_test.matview;\E/m,
 		like =>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 180781ecd05..26a9a3fac7a 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -986,6 +986,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 						break;
 				}
 				break;
+			case 'V':			/* Variables */
+				success = listVariables(pattern, show_verbose);
+				break;
 			case 'x':			/* Extensions */
 				if (show_verbose)
 					success = listExtensionContents(pattern);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 7c9a1f234c6..adf83ed1970 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5148,6 +5148,102 @@ error_return:
 	return false;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
+listVariables(const char *pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+	static const bool translate_columns[] = {false, false, false, false, false, false, false};
+
+	if (pset.sversion < 180000)
+	{
+		char		sverbuf[32];
+
+		pg_log_error("The server (version %s) does not support session variables.",
+					 formatPGVersionNumber(pset.sversion, false,
+										   sverbuf, sizeof(sverbuf)));
+		return true;
+	}
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT n.nspname as \"%s\",\n"
+					  "  v.varname as \"%s\",\n"
+					  "  pg_catalog.format_type(v.vartype, v.vartypmod) as \"%s\",\n"
+					  "  (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type bt\n"
+					  "   WHERE c.oid = v.varcollation AND bt.oid = v.vartype AND v.varcollation <> bt.typcollation) as \"%s\",\n"
+					  "  pg_catalog.pg_get_userbyid(v.varowner) as \"%s\"\n",
+					  gettext_noop("Schema"),
+					  gettext_noop("Name"),
+					  gettext_noop("Type"),
+					  gettext_noop("Collation"),
+					  gettext_noop("Owner"));
+
+	if (verbose)
+	{
+		appendPQExpBufferStr(&buf, ",\n  ");
+		printACLColumn(&buf, "v.varacl");
+		appendPQExpBuffer(&buf,
+						  ",\n  pg_catalog.obj_description(v.oid, 'pg_variable') AS \"%s\"",
+						  gettext_noop("Description"));
+	}
+
+	appendPQExpBufferStr(&buf,
+						 "\nFROM pg_catalog.pg_variable v"
+						 "\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = v.varnamespace");
+
+	appendPQExpBufferStr(&buf, "\nWHERE true\n");
+	if (!pattern)
+		appendPQExpBufferStr(&buf, "      AND n.nspname <> 'pg_catalog'\n"
+							 "      AND n.nspname <> 'information_schema'\n");
+
+	if (!validateSQLNamePattern(&buf, pattern, true, false,
+								"n.nspname", "v.varname", NULL,
+								"pg_catalog.pg_variable_is_visible(v.oid)",
+								NULL, 3))
+		return false;
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	/*
+	 * Most functions in this file are content to print an empty table when
+	 * there are no matching objects.  We intentionally deviate from that
+	 * here, but only in !quiet mode, for historical reasons.
+	 */
+	if (PQntuples(res) == 0 && !pset.quiet)
+	{
+		if (pattern)
+			pg_log_error("Did not find any session variable named \"%s\".",
+						 pattern);
+		else
+			pg_log_error("Did not find any session variables.");
+	}
+	else
+	{
+		myopt.nullPrint = NULL;
+		myopt.title = _("List of variables");
+		myopt.translate_header = true;
+		myopt.translate_columns = translate_columns;
+		myopt.n_translate_columns = lengthof(translate_columns);
+
+		printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+	}
+
+	PQclear(res);
+	return true;
+}
 
 /*
  * \dFp
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 273f974538e..4e15a180ea6 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -149,4 +149,7 @@ extern bool listOpFamilyFunctions(const char *access_method_pattern,
 /* \dl or \lo_list */
 extern bool listLargeObjects(bool verbose);
 
+/* \dV */
+extern bool listVariables(const char *pattern, bool varbose);
+
 #endif							/* DESCRIBE_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 6f58a110748..bdc327ffe30 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -265,6 +265,7 @@ slashUsage(unsigned short int pager)
 	HELP0("  \\dT[S+] [PATTERN]      list data types\n");
 	HELP0("  \\du[S+] [PATTERN]      list roles\n");
 	HELP0("  \\dv[S+] [PATTERN]      list views\n");
+	HELP0("  \\dV     [PATTERN]      list session variables\n");
 	HELP0("  \\dx[+]  [PATTERN]      list extensions\n");
 	HELP0("  \\dX     [PATTERN]      list extended statistics\n");
 	HELP0("  \\dy[+]  [PATTERN]      list event triggers\n");
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 891face1b65..70e1969963d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -963,6 +963,13 @@ static const SchemaQuery Query_for_trigger_of_table = {
 	.refnamespace = "c1.relnamespace",
 };
 
+static const SchemaQuery Query_for_list_of_variables = {
+	.min_server_version = 180000,
+	.catname = "pg_catalog.pg_variable v",
+	.viscondition = "pg_catalog.pg_variable_is_visible(v.oid)",
+	.namespace = "v.varnamespace",
+	.result = "v.varname",
+};
 
 /*
  * Queries to get lists of names of various kinds of things, possibly
@@ -1283,6 +1290,7 @@ static const pgsql_thing_t words_after_create[] = {
 																			 * TABLE ... */
 	{"USER", Query_for_list_of_roles, NULL, NULL, Keywords_for_user_thing},
 	{"USER MAPPING FOR", NULL, NULL, NULL},
+	{"VARIABLE", NULL, NULL, &Query_for_list_of_variables},
 	{"VIEW", NULL, NULL, &Query_for_list_of_views},
 	{NULL}						/* end of list */
 };
@@ -1722,7 +1730,7 @@ psql_completion(const char *text, int start, int end)
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\dP", "\\dPi", "\\dPt",
 		"\\drds", "\\drg", "\\dRs", "\\dRp", "\\ds",
-		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dX", "\\dy",
+		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dX", "\\dy", "\\dV",
 		"\\echo", "\\edit", "\\ef", "\\elif", "\\else", "\\encoding",
 		"\\endif", "\\errverbose", "\\ev",
 		"\\f",
@@ -2224,6 +2232,9 @@ psql_completion(const char *text, int start, int end)
 										  "ALL");
 	else if (Matches("ALTER", "SYSTEM", "SET", MatchAny))
 		COMPLETE_WITH("TO");
+	/* ALTER VARIABLE <name> */
+	else if (Matches("ALTER", "VARIABLE", MatchAny))
+		COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA");
 	/* ALTER VIEW <name> */
 	else if (Matches("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME", "RESET", "SET");
@@ -2804,7 +2815,7 @@ psql_completion(const char *text, int start, int end)
 					  "ROUTINE", "RULE", "SCHEMA", "SEQUENCE", "SERVER",
 					  "STATISTICS", "SUBSCRIPTION", "TABLE",
 					  "TABLESPACE", "TEXT SEARCH", "TRANSFORM FOR",
-					  "TRIGGER", "TYPE", "VIEW");
+					  "TRIGGER", "TYPE", "VARIABLE", "VIEW");
 	else if (Matches("COMMENT", "ON", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	else if (Matches("COMMENT", "ON", "CONSTRAINT"))
@@ -3266,7 +3277,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
-		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+		COMPLETE_WITH("SEQUENCE", "TABLE", "VARIABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE, SEQUENCE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 	{
@@ -3586,6 +3597,13 @@ psql_completion(const char *text, int start, int end)
 		else if (TailMatches("=", MatchAnyExcept("*)")))
 			COMPLETE_WITH(",", ")");
 	}
+/* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	/* Complete CREATE VARIABLE <name> with AS */
+	else if (TailMatches("CREATE", "VARIABLE", MatchAny))
+		COMPLETE_WITH("AS");
+	else if (TailMatches("VARIABLE", MatchAny, "AS"))
+		/* Complete CREATE VARIABLE <name> with AS types */
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS or WITH */
@@ -3849,6 +3867,12 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny, "LANGUAGE", MatchAny))
 		COMPLETE_WITH("CASCADE", "RESTRICT");
 
+	/* DROP VARIABLE */
+	else if (Matches("DROP", "VARIABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
+	else if (Matches("DROP", "VARIABLE", MatchAny))
+		COMPLETE_WITH("CASCADE", "RESTRICT");
+
 /* EXECUTE */
 	else if (Matches("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -4052,7 +4076,7 @@ psql_completion(const char *text, int start, int end)
 		 * objects supported.
 		 */
 		if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
-			COMPLETE_WITH("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS");
+			COMPLETE_WITH("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS", "VARIABLES");
 		else
 			COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_grantables,
 											"ALL FUNCTIONS IN SCHEMA",
@@ -4074,7 +4098,8 @@ psql_completion(const char *text, int start, int end)
 											"SEQUENCE",
 											"TABLE",
 											"TABLESPACE",
-											"TYPE");
+											"TYPE",
+											"VARIABLE");
 	}
 	else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL") ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "ALL"))
@@ -4082,7 +4107,8 @@ psql_completion(const char *text, int start, int end)
 					  "PROCEDURES IN SCHEMA",
 					  "ROUTINES IN SCHEMA",
 					  "SEQUENCES IN SCHEMA",
-					  "TABLES IN SCHEMA");
+					  "TABLES IN SCHEMA",
+					  "VARIABLES IN SCHEMA");
 	else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN") ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH("DATA WRAPPER", "SERVER");
@@ -4118,6 +4144,8 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		else if (TailMatches("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
+		else if (TailMatches("VARIABLE"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
 		else if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH("TO");
 		else
@@ -4421,7 +4449,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 	else if (Matches("PREPARE", MatchAny, "AS"))
-		COMPLETE_WITH("SELECT", "UPDATE", "INSERT INTO", "DELETE FROM");
+		COMPLETE_WITH("SELECT", "UPDATE", "INSERT INTO", "DELETE FROM", "LET");
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -4889,6 +4917,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	else if (TailMatchesCS("\\dv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
+	else if (TailMatchesCS("\\dV*"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
 	else if (TailMatchesCS("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
 	else if (TailMatchesCS("\\dX*"))
diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile
index 167f91a6e3f..507f3808218 100644
--- a/src/include/catalog/Makefile
+++ b/src/include/catalog/Makefile
@@ -81,7 +81,8 @@ CATALOG_HEADERS := \
 	pg_publication_namespace.h \
 	pg_publication_rel.h \
 	pg_subscription.h \
-	pg_subscription_rel.h
+	pg_subscription_rel.h \
+	pg_variable.h
 
 GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h)
 
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index f70d1daba52..6eec79d2eec 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -69,6 +69,7 @@ catalog_headers = [
   'pg_publication_rel.h',
   'pg_subscription.h',
   'pg_subscription_rel.h',
+  'pg_variable.h',
 ]
 
 # The .dat files we need can just be listed alphabetically.
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 8d434d48d57..4172b0cd268 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -97,6 +97,8 @@ extern Oid	TypenameGetTypid(const char *typname);
 extern Oid	TypenameGetTypidExtended(const char *typname, bool temp_ok);
 extern bool TypeIsVisible(Oid typid);
 
+extern bool VariableIsVisible(Oid varid);
+
 extern FuncCandidateList FuncnameGetCandidates(List *names,
 											   int nargs, List *argnames,
 											   bool expand_variadic,
@@ -169,11 +171,12 @@ extern SearchPathMatcher *GetSearchPathMatcher(MemoryContext context);
 extern SearchPathMatcher *CopySearchPathMatcher(SearchPathMatcher *path);
 extern bool SearchPathMatchesCurrentEnvironment(SearchPathMatcher *path);
 
+extern Oid	LookupVariable(const char *nspname, const char *varname, bool missing_ok);
+
 extern Oid	get_collation_oid(List *collname, bool missing_ok);
 extern Oid	get_conversion_oid(List *conname, bool missing_ok);
 extern Oid	FindDefaultConversionProc(int32 for_encoding, int32 to_encoding);
 
-
 /* initialization & transaction cleanup code */
 extern void InitializeSearchPath(void);
 extern void AtEOXact_Namespace(bool isCommit, bool parallel);
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index d272cdf08b4..529d53c6bb6 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -68,6 +68,7 @@ MAKE_SYSCACHE(DEFACLROLENSPOBJ, pg_default_acl_role_nsp_obj_index, 8);
 #define DEFACLOBJ_FUNCTION		'f' /* function */
 #define DEFACLOBJ_TYPE			'T' /* type */
 #define DEFACLOBJ_NAMESPACE		'n' /* namespace */
+#define DEFACLOBJ_VARIABLE		'V' /* variable */
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 73d9cf85826..644beb90432 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6445,6 +6445,9 @@
   proname => 'pg_collation_is_visible', procost => '10', provolatile => 's',
   prorettype => 'bool', proargtypes => 'oid',
   prosrc => 'pg_collation_is_visible' },
+{ oid => '9221', descr => 'is session variable visible in search path?',
+  proname => 'pg_variable_is_visible', procost => '10', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'oid', prosrc => 'pg_variable_is_visible' },
 
 { oid => '2854', descr => 'get OID of current session\'s temp schema, if any',
   proname => 'pg_my_temp_schema', provolatile => 's', proparallel => 'r',
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
new file mode 100644
index 00000000000..10fba805847
--- /dev/null
+++ b/src/include/catalog/pg_variable.h
@@ -0,0 +1,81 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.h
+ *	  definition of session variables system catalog (pg_variables)
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_variable.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_VARIABLE_H
+#define PG_VARIABLE_H
+
+#include "catalog/genbki.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_variable_d.h"
+#include "utils/acl.h"
+
+/* ----------------
+ *		pg_variable definition.  cpp turns this into
+ *		typedef struct FormData_pg_variable
+ * ----------------
+ */
+CATALOG(pg_variable,9222,VariableRelationId)
+{
+	Oid			oid;			/* oid */
+
+	/* OID of entry in pg_type for variable's type */
+	Oid			vartype BKI_LOOKUP(pg_type);
+
+	/* variable name */
+	NameData	varname;
+
+	/* OID of namespace containing variable class */
+	Oid			varnamespace BKI_LOOKUP(pg_namespace);
+
+	/* variable owner */
+	Oid			varowner BKI_LOOKUP(pg_authid);
+
+	/* typmod for variable's type */
+	int32		vartypmod BKI_DEFAULT(-1);
+
+	/* variable collation */
+	Oid			varcollation BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_collation);
+
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+
+	/* access permissions */
+	aclitem		varacl[1] BKI_DEFAULT(_null_);
+
+#endif
+} FormData_pg_variable;
+
+/* ----------------
+ *		Form_pg_variable corresponds to a pointer to a tuple with
+ *		the format of pg_variable relation.
+ * ----------------
+ */
+typedef FormData_pg_variable *Form_pg_variable;
+
+DECLARE_TOAST(pg_variable, 9223, 9224);
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_variable_oid_index, 9225, VariableOidIndexId, pg_variable, btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_variable_varname_nsp_index, 9226, VariableNameNspIndexId, pg_variable, btree(varname name_ops, varnamespace oid_ops));
+
+extern ObjectAddress CreateVariable(ParseState *pstate,
+									CreateSessionVarStmt *stmt);
+extern void DropVariableById(Oid varid);
+
+MAKE_SYSCACHE(VARIABLENAMENSP, pg_variable_varname_nsp_index, 8);
+MAKE_SYSCACHE(VARIABLEOID, pg_variable_oid_index, 8);
+
+#endif							/* PG_VARIABLE_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 85a62b538e5..80ea5928eef 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2309,6 +2309,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW,
 } ObjectType;
 
@@ -3425,6 +3426,21 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+
+/* ----------------------
+ *		{Create|Alter} VARIABLE Statement
+ * ----------------------
+ */
+typedef struct CreateSessionVarStmt
+{
+	NodeTag		type;
+	RangeVar   *variable;		/* the variable to create */
+	TypeName   *typeName;		/* the type of variable */
+	CollateClause *collClause;
+	bool		if_not_exists;	/* do nothing if it already exists */
+} CreateSessionVarStmt;
+
+
 /* ----------------------
  *		Create Function Statement
  * ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f7fe834cf45..c884cd5c467 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -486,6 +486,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("variables", VARIABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 7fdcec6dd93..9465df7b2ff 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -68,6 +68,7 @@ PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false)
 PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_VARIABLE, "ALTER VARIABLE", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false)
 PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false)
 PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false)
@@ -123,6 +124,7 @@ PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_VARIABLE, "CREATE VARIABLE", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false)
 PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false)
 PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false)
@@ -175,6 +177,7 @@ PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_VARIABLE, "DROP VARIABLE", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false)
 PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false)
 PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false)
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 731d84b2a93..98aab98229e 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -169,6 +169,7 @@ typedef struct ArrayType Acl;
 #define ACL_ALL_RIGHTS_SCHEMA		(ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE	(ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE			(ACL_USAGE)
+#define ACL_ALL_RIGHTS_VARIABLE		(ACL_SELECT|ACL_UPDATE)
 
 /* operation codes for pg_*_aclmask */
 typedef enum
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 20446f6f836..8a3684e711f 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -134,6 +134,7 @@ extern char get_func_prokind(Oid funcid);
 extern bool get_func_leakproof(Oid funcid);
 extern RegProcedure get_func_support(Oid funcid);
 extern Oid	get_relname_relid(const char *relname, Oid relnamespace);
+extern Oid	get_varname_varid(const char *varname, Oid varnamespace);
 extern char *get_rel_name(Oid relid);
 extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
@@ -206,6 +207,14 @@ extern char *get_publication_name(Oid pubid, bool missing_ok);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
 extern char *get_subscription_name(Oid subid, bool missing_ok);
 
+extern char *get_session_variable_name(Oid varid);
+extern Oid	get_session_variable_namespace(Oid varid);
+extern Oid	get_session_variable_type(Oid varid);
+extern void get_session_variable_type_typmod_collid(Oid varid,
+													Oid *typid,
+													int32 *typmod,
+													Oid *collid);
+
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
 #define type_is_array_domain(typid)  (get_base_element_type(typid) != InvalidOid)
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 74d9ff2998d..68dca9ec0d9 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -151,3 +151,19 @@ owner of type deptest_t
 DROP OWNED BY regress_dep_user2, regress_dep_user0;
 DROP USER regress_dep_user2;
 DROP USER regress_dep_user0;
+-- dependency on type
+CREATE DOMAIN vardomain AS int;
+CREATE TYPE vartype AS (a int, b int, c vardomain);
+CREATE VARIABLE var1 AS vartype;
+-- should to fail
+DROP DOMAIN vardomain;
+ERROR:  cannot drop type vardomain because other objects depend on it
+DETAIL:  column c of composite type vartype depends on type vardomain
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TYPE vartype;
+ERROR:  cannot drop type vartype because other objects depend on it
+DETAIL:  session variable var1 depends on type vartype
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP VARIABLE var1;
+DROP TYPE vartype;
+DROP DOMAIN vardomain;
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be3..d9953321402 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -266,3 +266,7 @@ NOTICE:  checking pg_subscription {subdbid} => pg_database {oid}
 NOTICE:  checking pg_subscription {subowner} => pg_authid {oid}
 NOTICE:  checking pg_subscription_rel {srsubid} => pg_subscription {oid}
 NOTICE:  checking pg_subscription_rel {srrelid} => pg_class {oid}
+NOTICE:  checking pg_variable {vartype} => pg_type {oid}
+NOTICE:  checking pg_variable {varnamespace} => pg_namespace {oid}
+NOTICE:  checking pg_variable {varowner} => pg_authid {oid}
+NOTICE:  checking pg_variable {varcollation} => pg_collation {oid}
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 3bbe4c5f974..c89067490f7 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5828,6 +5828,30 @@ COMMIT;
 # final ON_ERROR_ROLLBACK: off
 DROP TABLE bla;
 DROP FUNCTION psql_error;
+-- session variable test
+CREATE ROLE regress_variable_owner;
+SET ROLE TO regress_variable_owner;
+CREATE VARIABLE var1 AS varchar COLLATE "C";
+\dV+ var1
+                                            List of variables
+ Schema | Name |       Type        | Collation |         Owner          | Access privileges | Description 
+--------+------+-------------------+-----------+------------------------+-------------------+-------------
+ public | var1 | character varying | C         | regress_variable_owner |                   | 
+(1 row)
+
+GRANT SELECT ON VARIABLE var1 TO PUBLIC;
+COMMENT ON VARIABLE var1 IS 'some description';
+\dV+ var1
+                                                              List of variables
+ Schema | Name |       Type        | Collation |         Owner          |                Access privileges                 |   Description    
+--------+------+-------------------+-----------+------------------------+--------------------------------------------------+------------------
+ public | var1 | character varying | C         | regress_variable_owner | regress_variable_owner=rw/regress_variable_owner+| some description
+        |      |                   |           |                        | =r/regress_variable_owner                        | 
+(1 row)
+
+DROP VARIABLE var1;
+SET ROLE TO DEFAULT;
+DROP ROLE regress_variable_owner;
 -- check describing invalid multipart names
 \dA regression.heap
 improper qualified name (too many dotted names): regression.heap
@@ -6049,6 +6073,12 @@ cross-database references are not implemented: nonesuch.public.func_deps_stat
 improper qualified name (too many dotted names): regression.myevt
 \dy nonesuch.myevt
 improper qualified name (too many dotted names): nonesuch.myevt
+\dV host.regression.public.var
+improper qualified name (too many dotted names): host.regression.public.var
+\dV regression|mydb.public.var
+cross-database references are not implemented: regression|mydb.public.var
+\dV nonesuch.public.var
+cross-database references are not implemented: nonesuch.public.var
 -- check that dots within quoted name segments are not counted
 \dA "no.such.access.method"
 List of access methods
@@ -6283,6 +6313,12 @@ List of schemas
 ------+-------+-------+---------+----------+------
 (0 rows)
 
+\dV "no.such.variable"
+            List of variables
+ Schema | Name | Type | Collation | Owner 
+--------+------+------+-----------+-------
+(0 rows)
+
 -- again, but with dotted schema qualifications.
 \dA "no.such.schema"."no.such.access.method"
 improper qualified name (too many dotted names): "no.such.schema"."no.such.access.method"
@@ -6452,6 +6488,12 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
 \dy "no.such.schema"."no.such.event.trigger"
 improper qualified name (too many dotted names): "no.such.schema"."no.such.event.trigger"
+\dV "no.such.schema"."no.such.variable"
+            List of variables
+ Schema | Name | Type | Collation | Owner 
+--------+------+------+-----------+-------
+(0 rows)
+
 -- again, but with current database and dotted schema qualifications.
 \dt regression."no.such.schema"."no.such.table.relation"
       List of relations
@@ -6585,6 +6627,12 @@ List of text search templates
 --------+------+------------+-----------+--------------+-----
 (0 rows)
 
+\dV regression."no.such.schema"."no.such.variable"
+            List of variables
+ Schema | Name | Type | Collation | Owner 
+--------+------+------+-----------+-------
+(0 rows)
+
 -- again, but with dotted database and dotted schema qualifications.
 \dt "no.such.database"."no.such.schema"."no.such.table.relation"
 cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.table.relation"
@@ -6632,6 +6680,8 @@ cross-database references are not implemented: "no.such.database"."no.such.schem
 cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.data.type"
 \dX "no.such.database"."no.such.schema"."no.such.extended.statistics"
 cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.extended.statistics"
+\dV "no.such.database"."no.such.schema"."no.such.variable"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.variable"
 -- check \drg and \du
 CREATE ROLE regress_du_role0;
 CREATE ROLE regress_du_role1;
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
new file mode 100644
index 00000000000..aca766042db
--- /dev/null
+++ b/src/test/regress/expected/session_variables.out
@@ -0,0 +1,57 @@
+CREATE ROLE regress_variable_owner;
+-- should be ok
+CREATE VARIABLE var1 AS int;
+-- should fail, pseudotypes are not allowed
+CREATE VARIABLE var2 AS anyelement;
+ERROR:  session variable cannot be pseudo-type anyelement
+-- should be ok, do nothing
+DROP VARIABLE IF EXISTS var2;
+NOTICE:  session variable "var2" does not exist, skipping
+-- do nothing
+CREATE VARIABLE IF NOT EXISTS var1 AS int;
+NOTICE:  session variable "var1" already exists, skipping
+-- should fail
+CREATE VARIABLE var1 AS int;
+ERROR:  session variable "var1" already exists
+-- should be ok
+DROP VARIABLE IF EXISTS var1;
+-- check comment on variable
+CREATE VARIABLE var1 AS int;
+COMMENT ON VARIABLE var1 IS 'some variable comment';
+SELECT pg_catalog.obj_description(oid, 'pg_variable') FROM pg_variable WHERE varname = 'var1';
+    obj_description    
+-----------------------
+ some variable comment
+(1 row)
+
+DROP VARIABLE var1;
+--- check access rights and supported ALTER
+CREATE SCHEMA svartest;
+GRANT ALL ON SCHEMA svartest TO regress_variable_owner;
+CREATE VARIABLE svartest.var1 AS int;
+CREATE ROLE regress_variable_reader;
+GRANT SELECT ON VARIABLE svartest.var1 TO regress_variable_reader;
+REVOKE ALL ON VARIABLE svartest.var1 FROM regress_variable_reader;
+ALTER VARIABLE svartest.var1 OWNER TO regress_variable_owner;
+ALTER VARIABLE svartest.var1 RENAME TO varxx;
+ALTER VARIABLE svartest.varxx SET SCHEMA public;
+DROP VARIABLE public.varxx;
+ALTER DEFAULT PRIVILEGES
+   FOR ROLE regress_variable_owner
+   IN SCHEMA svartest
+   GRANT SELECT ON VARIABLES TO regress_variable_reader;
+-- creating variable with default privileges
+SET ROLE TO regress_variable_owner;
+CREATE VARIABLE svartest.var1 AS int;
+SET ROLE TO DEFAULT;
+\dV+ svartest.var1
+                                                        List of variables
+  Schema  | Name |  Type   | Collation |         Owner          |                Access privileges                 | Description 
+----------+------+---------+-----------+------------------------+--------------------------------------------------+-------------
+ svartest | var1 | integer |           | regress_variable_owner | regress_variable_owner=rw/regress_variable_owner+| 
+          |      |         |           |                        | regress_variable_reader=r/regress_variable_owner | 
+(1 row)
+
+DROP VARIABLE svartest.var1;
+DROP SCHEMA svartest;
+DROP ROLE regress_variable_owner;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 2429ec2bbaa..ed6893d4796 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml session_variables
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/sql/dependency.sql b/src/test/regress/sql/dependency.sql
index 8d74ed7122c..f24df9a7886 100644
--- a/src/test/regress/sql/dependency.sql
+++ b/src/test/regress/sql/dependency.sql
@@ -114,3 +114,16 @@ DROP USER regress_dep_user2;
 DROP OWNED BY regress_dep_user2, regress_dep_user0;
 DROP USER regress_dep_user2;
 DROP USER regress_dep_user0;
+
+-- dependency on type
+CREATE DOMAIN vardomain AS int;
+CREATE TYPE vartype AS (a int, b int, c vardomain);
+CREATE VARIABLE var1 AS vartype;
+
+-- should to fail
+DROP DOMAIN vardomain;
+DROP TYPE vartype;
+
+DROP VARIABLE var1;
+DROP TYPE vartype;
+DROP DOMAIN vardomain;
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 3b3c6f6e294..b80d56a27af 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1591,6 +1591,19 @@ COMMIT;
 DROP TABLE bla;
 DROP FUNCTION psql_error;
 
+-- session variable test
+CREATE ROLE regress_variable_owner;
+SET ROLE TO regress_variable_owner;
+CREATE VARIABLE var1 AS varchar COLLATE "C";
+\dV+ var1
+GRANT SELECT ON VARIABLE var1 TO PUBLIC;
+COMMENT ON VARIABLE var1 IS 'some description';
+\dV+ var1
+DROP VARIABLE var1;
+
+SET ROLE TO DEFAULT;
+DROP ROLE regress_variable_owner;
+
 -- check describing invalid multipart names
 \dA regression.heap
 \dA nonesuch.heap
@@ -1702,6 +1715,9 @@ DROP FUNCTION psql_error;
 \dX nonesuch.public.func_deps_stat
 \dy regression.myevt
 \dy nonesuch.myevt
+\dV host.regression.public.var
+\dV regression|mydb.public.var
+\dV nonesuch.public.var
 
 -- check that dots within quoted name segments are not counted
 \dA "no.such.access.method"
@@ -1743,6 +1759,8 @@ DROP FUNCTION psql_error;
 \dx "no.such.installed.extension"
 \dX "no.such.extended.statistics"
 \dy "no.such.event.trigger"
+\dV "no.such.variable"
+
 
 -- again, but with dotted schema qualifications.
 \dA "no.such.schema"."no.such.access.method"
@@ -1783,6 +1801,7 @@ DROP FUNCTION psql_error;
 \dx "no.such.schema"."no.such.installed.extension"
 \dX "no.such.schema"."no.such.extended.statistics"
 \dy "no.such.schema"."no.such.event.trigger"
+\dV "no.such.schema"."no.such.variable"
 
 -- again, but with current database and dotted schema qualifications.
 \dt regression."no.such.schema"."no.such.table.relation"
@@ -1807,6 +1826,7 @@ DROP FUNCTION psql_error;
 \dP regression."no.such.schema"."no.such.partitioned.relation"
 \dT regression."no.such.schema"."no.such.data.type"
 \dX regression."no.such.schema"."no.such.extended.statistics"
+\dV regression."no.such.schema"."no.such.variable"
 
 -- again, but with dotted database and dotted schema qualifications.
 \dt "no.such.database"."no.such.schema"."no.such.table.relation"
@@ -1832,6 +1852,7 @@ DROP FUNCTION psql_error;
 \dP "no.such.database"."no.such.schema"."no.such.partitioned.relation"
 \dT "no.such.database"."no.such.schema"."no.such.data.type"
 \dX "no.such.database"."no.such.schema"."no.such.extended.statistics"
+\dV "no.such.database"."no.such.schema"."no.such.variable"
 
 -- check \drg and \du
 CREATE ROLE regress_du_role0;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
new file mode 100644
index 00000000000..42dfb95a022
--- /dev/null
+++ b/src/test/regress/sql/session_variables.sql
@@ -0,0 +1,61 @@
+CREATE ROLE regress_variable_owner;
+
+-- should be ok
+CREATE VARIABLE var1 AS int;
+
+-- should fail, pseudotypes are not allowed
+CREATE VARIABLE var2 AS anyelement;
+
+-- should be ok, do nothing
+DROP VARIABLE IF EXISTS var2;
+
+-- do nothing
+CREATE VARIABLE IF NOT EXISTS var1 AS int;
+
+-- should fail
+CREATE VARIABLE var1 AS int;
+
+-- should be ok
+DROP VARIABLE IF EXISTS var1;
+
+-- check comment on variable
+CREATE VARIABLE var1 AS int;
+COMMENT ON VARIABLE var1 IS 'some variable comment';
+SELECT pg_catalog.obj_description(oid, 'pg_variable') FROM pg_variable WHERE varname = 'var1';
+
+DROP VARIABLE var1;
+
+--- check access rights and supported ALTER
+CREATE SCHEMA svartest;
+GRANT ALL ON SCHEMA svartest TO regress_variable_owner;
+
+CREATE VARIABLE svartest.var1 AS int;
+
+CREATE ROLE regress_variable_reader;
+
+GRANT SELECT ON VARIABLE svartest.var1 TO regress_variable_reader;
+REVOKE ALL ON VARIABLE svartest.var1 FROM regress_variable_reader;
+
+ALTER VARIABLE svartest.var1 OWNER TO regress_variable_owner;
+ALTER VARIABLE svartest.var1 RENAME TO varxx;
+ALTER VARIABLE svartest.varxx SET SCHEMA public;
+
+DROP VARIABLE public.varxx;
+
+ALTER DEFAULT PRIVILEGES
+   FOR ROLE regress_variable_owner
+   IN SCHEMA svartest
+   GRANT SELECT ON VARIABLES TO regress_variable_reader;
+
+-- creating variable with default privileges
+SET ROLE TO regress_variable_owner;
+CREATE VARIABLE svartest.var1 AS int;
+SET ROLE TO DEFAULT;
+
+\dV+ svartest.var1
+
+DROP VARIABLE svartest.var1;
+
+DROP SCHEMA svartest;
+
+DROP ROLE regress_variable_owner;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d7f9217ce..a18e81125b5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -533,6 +533,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
@@ -872,6 +873,7 @@ FormData_pg_ts_parser
 FormData_pg_ts_template
 FormData_pg_type
 FormData_pg_user_mapping
+FormData_pg_variable
 FormExtraData_pg_attribute
 Form_pg_aggregate
 Form_pg_am
@@ -931,6 +933,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
+Form_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
@@ -3061,6 +3064,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.45.2

From 3ab6a9be6d2bc3d71cec0268640b9874b0274d4f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

Accept of sinval message invalidates entries in sessionvars hash table. These entries
are validated before any read or write operations on session variables. When the entry
cannot be validated, then it is removed. The removing can be delayed when variable
is dropped inside current transaction. This transaction can be aborted and in this case
we want to preserve content of the variable.
---
 src/backend/access/transam/xact.c             |   4 +
 src/backend/catalog/pg_variable.c             |   4 +
 src/backend/commands/session_variable.c       | 177 +++++++++++++-
 src/include/commands/session_variable.h       |   3 +
 .../isolation/expected/session-variable.out   | 109 +++++++++
 src/test/isolation/isolation_schedule         |   1 +
 .../isolation/specs/session-variable.spec     |  50 ++++
 .../regress/expected/session_variables.out    | 219 ++++++++++++++++++
 src/test/regress/sql/session_variables.sql    | 121 ++++++++++
 9 files changed, 682 insertions(+), 6 deletions(-)
 create mode 100644 src/test/isolation/expected/session-variable.out
 create mode 100644 src/test/isolation/specs/session-variable.spec

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index d119ab909dc..2d3ed8607ab 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -36,6 +36,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/session_variable.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "common/pg_prng.h"
@@ -2305,6 +2306,9 @@ CommitTransaction(void)
 	 */
 	smgrDoPendingSyncs(true, is_parallel_worker);
 
+	/* Remove values of dropped session variables from memory */
+	AtPreEOXact_SessionVariables();
+
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
index 1c73181c3da..0b21204bc47 100644
--- a/src/backend/catalog/pg_variable.c
+++ b/src/backend/catalog/pg_variable.c
@@ -22,6 +22,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
 #include "miscadmin.h"
 #include "parser/parse_type.h"
 #include "utils/builtins.h"
@@ -254,4 +255,7 @@ DropVariableById(Oid varid)
 	ReleaseSysCache(tup);
 
 	table_close(rel, RowExclusiveLock);
+
+	/* Do the necessary cleanup if needed in local memory */
+	SessionVariableDropPostprocess(varid);
 }
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index c7968587125..d9169820a7f 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_variable.h"
 #include "commands/session_variable.h"
 #include "executor/svariableReceiver.h"
@@ -75,6 +76,14 @@ typedef struct SVariableData
 	void	   *domain_check_extra;
 	LocalTransactionId domain_check_extra_lxid;
 
+	/*
+	 * Top level local transaction id of the last transaction that dropped the
+	 * variable if any.  We need this information to avoid freeing memory for
+	 * variable dropped by the local backend that may be eventually
+	 * rollbacked.
+	 */
+	LocalTransactionId drop_lxid;
+
 	/*
 	 * Stored value and type description can be outdated when we receive
 	 * sinval message. We have to check always if the stored data are
@@ -91,6 +100,19 @@ static HTAB *sessionvars = NULL;	/* hash table for session variables */
 
 static MemoryContext SVariableMemoryContext = NULL;
 
+/* true after accepted sinval message */
+static bool needs_validation = false;
+
+/*
+ * The content of session variables is not removed immediately. When it
+ * is possible we do this at the transaction end. But when the transaction failed,
+ * we cannot do it, because we lost access to the system catalog. So we
+ * try to do it in the next transaction before any get or set of any session
+ * variable. We don't want to repeat this opening cleaning in transaction,
+ * So we store the id of the transaction where opening validation was done.
+ */
+static LocalTransactionId validated_lxid = InvalidLocalTransactionId;
+
 /*
  * Callback function for session variable invalidation.
  */
@@ -123,6 +145,40 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
 		if (hashvalue == 0 || svar->hashvalue == hashvalue)
 		{
 			svar->is_valid = false;
+
+			needs_validation = true;
+		}
+	}
+}
+
+/*
+ * Handle the local memory cleanup for a DROP VARIABLE command.
+ *
+ * Caller should take care of removing the pg_variable entry first.
+ */
+void
+SessionVariableDropPostprocess(Oid varid)
+{
+	Assert(LocalTransactionIdIsValid(MyProc->vxid.lxid));
+
+	if (sessionvars)
+	{
+		bool		found;
+		SVariable	svar = (SVariable) hash_search(sessionvars, &varid,
+												   HASH_FIND, &found);
+
+		if (found)
+		{
+			/*
+			 * Save the current top level local transaction id to make sure we
+			 * don't automatically remove the local variable storage in
+			 * validate_all_session_variables, as the DROP VARIABLE will send
+			 * an invalidation message.
+			 */
+			svar->is_valid = false;
+			svar->drop_lxid = MyProc->vxid.lxid;
+
+			needs_validation = true;
 		}
 	}
 }
@@ -176,6 +232,83 @@ is_session_variable_valid(SVariable svar)
 	return result;
 }
 
+/*
+ * It checks all possibly invalid entries against the system catalog.
+ * During this validation, the system cache can be invalidated, and the
+ * some sinval message can be accepted. This routine doesn't ensure
+ * all living entries of sessionvars will have is_valid flag, but it ensures
+ * that all entries are checked once.
+ *
+ * This routine is called before any usage (read, write, debug) of session
+ * variables (only once in a transaction) or at a transaction end.  At the
+ * end of transaction (specified by true of argument atEOX) we can
+ * throw all invalid variables. Inside the transaction (atEOX is false) we want
+ * to postpone cleaning of variables dropped inside the current transaction.
+ * The current transaction can be aborted, the related drop command will be
+ * aborted too. In this case we don't want lose the content of the variable,
+ * and therefore we need to hold the content of the dropped session variable
+ * until the end of the transaction (where the variable was dropped).
+ */
+static void
+remove_invalid_session_variables(bool atEOX)
+{
+	HASH_SEQ_STATUS status;
+	SVariable	svar;
+
+	/*
+	 * The validation requires an access to system catalog, and then the
+	 * session state should be "in transaction".
+	 */
+	Assert(IsTransactionState());
+
+	if (!needs_validation || !sessionvars)
+		return;
+
+	/*
+	 * Reset this flag here, before we start the validation. It can be set to
+	 * on by incomming sinval message.
+	 */
+	needs_validation = false;
+
+	elog(DEBUG1, "effective call of validate_all_session_variables()");
+
+	hash_seq_init(&status, sessionvars);
+	while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+	{
+		if (!svar->is_valid)
+		{
+			if (!atEOX && svar->drop_lxid == MyProc->vxid.lxid)
+			{
+				needs_validation = true;
+				continue;
+			}
+
+			if (!is_session_variable_valid(svar))
+			{
+				Oid			varid = svar->varid;
+
+				free_session_variable_value(svar);
+				hash_search(sessionvars, &varid, HASH_REMOVE, NULL);
+				svar = NULL;
+			}
+			else
+				svar->is_valid = true;
+		}
+	}
+}
+
+/*
+ * Remove values of dropped session variables from memory.
+ */
+void
+AtPreEOXact_SessionVariables(void)
+{
+	/* We cannot to do it when transaction is aborted */
+	Assert(IsTransactionState());
+
+	remove_invalid_session_variables(true);
+}
+
 /*
  * Update attributes cached in svar
  */
@@ -205,6 +338,8 @@ setup_session_variable(SVariable svar, Oid varid)
 	svar->domain_check_extra = NULL;
 	svar->domain_check_extra_lxid = InvalidLocalTransactionId;
 
+	svar->drop_lxid = InvalidTransactionId;
+
 	svar->isnull = true;
 	svar->value = (Datum) 0;
 
@@ -335,26 +470,44 @@ get_session_variable(Oid varid)
 	if (!sessionvars)
 		create_sessionvars_hashtables();
 
+	if (validated_lxid == InvalidLocalTransactionId ||
+		validated_lxid != MyProc->vxid.lxid)
+	{
+		/* Throw invalid entries, skip entries dropped by this transaction. */
+		remove_invalid_session_variables(false);
+
+		/* Don't repeat it in this transaction */
+		validated_lxid = MyProc->vxid.lxid;
+	}
+
 	svar = (SVariable) hash_search(sessionvars, &varid,
 								   HASH_ENTER, &found);
 
 	if (found)
 	{
+		/*
+		 * The session variable can be dropped by DROP VARIABLE command,
+		 * but effect of this command can be reverted by ROLLBACK to savepoint,
+		 * so we can work here with readable value in variable marked as invalid.
+		 */
 		if (!svar->is_valid)
 		{
 			/*
 			 * The variable can be flagged as invalid by processing invalidation
 			 * message, but can be validated by recheck against system catalog.
+			 *
+			 * If we access this session variable, then the variable should be
+			 * possibly validated. The oid should be valid, because related
+			 * session variable is locked already, and remove_invalid_session_variables
+			 * should to remove variables dropped by other transactions.
 			 */
 			if (is_session_variable_valid(svar))
 				svar->is_valid = true;
 			else
-				/*
-				 * When the value cannot be validated, we can safely throw it.
-				 * There is oid in catalog, but it is related to different
-				 * session variable (different create_lsn).
-				 */
-				free_session_variable_value(svar);
+			{
+				/* We don't expect it */
+				elog(ERROR, "unexpected state of session variable %u", varid);
+			}
 		}
 	}
 	else
@@ -415,6 +568,16 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 	if (!sessionvars)
 		create_sessionvars_hashtables();
 
+	if (validated_lxid == InvalidLocalTransactionId ||
+		validated_lxid != MyProc->vxid.lxid)
+	{
+		/* Throw invalid entries, skip entries dropped by this transaction. */
+		remove_invalid_session_variables(false);
+
+		/* Don't repeat it in this transaction */
+		validated_lxid = MyProc->vxid.lxid;
+	}
+
 	svar = (SVariable) hash_search(sessionvars, &varid,
 								   HASH_ENTER, &found);
 
@@ -622,6 +785,8 @@ pg_session_variables(PG_FUNCTION_ARGS)
 
 	elog(DEBUG1, "pg_session_variables start");
 
+	remove_invalid_session_variables(false);
+
 	InitMaterializedSRF(fcinfo, 0);
 
 	if (sessionvars)
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 443afbafd4a..0d4c635ae7b 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -21,6 +21,9 @@
 #include "tcop/cmdtag.h"
 #include "utils/queryenvironment.h"
 
+extern void SessionVariableDropPostprocess(Oid varid);
+extern void AtPreEOXact_SessionVariables(void);
+
 extern void SetSessionVariable(Oid varid, Datum value, bool isNull);
 extern void SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull);
 extern Datum GetSessionVariable(Oid varid, bool *isNull, Oid *typid);
diff --git a/src/test/isolation/expected/session-variable.out b/src/test/isolation/expected/session-variable.out
new file mode 100644
index 00000000000..2968cce0895
--- /dev/null
+++ b/src/test/isolation/expected/session-variable.out
@@ -0,0 +1,109 @@
+Parsed test spec with 4 sessions
+
+starting permutation: let val drop val
+step let: LET myvar = 'test';
+step val: SELECT myvar;
+myvar
+-----
+test 
+(1 row)
+
+step drop: DROP VARIABLE myvar;
+step val: SELECT myvar;
+ERROR:  column "myvar" does not exist
+
+starting permutation: let val s1 drop val sr1
+step let: LET myvar = 'test';
+step val: SELECT myvar;
+myvar
+-----
+test 
+(1 row)
+
+step s1: BEGIN;
+step drop: DROP VARIABLE myvar;
+step val: SELECT myvar;
+ERROR:  column "myvar" does not exist
+step sr1: ROLLBACK;
+
+starting permutation: let val dbg drop create dbg val
+step let: LET myvar = 'test';
+step val: SELECT myvar;
+myvar
+-----
+test 
+(1 row)
+
+step dbg: SELECT schema, name, removed FROM pg_session_variables();
+schema|name |removed
+------+-----+-------
+public|myvar|f      
+(1 row)
+
+step drop: DROP VARIABLE myvar;
+step create: CREATE VARIABLE myvar AS text;
+step dbg: SELECT schema, name, removed FROM pg_session_variables();
+schema|name|removed
+------+----+-------
+(0 rows)
+
+step val: SELECT myvar;
+myvar
+-----
+     
+(1 row)
+
+
+starting permutation: let val s1 dbg drop create dbg val sr1
+step let: LET myvar = 'test';
+step val: SELECT myvar;
+myvar
+-----
+test 
+(1 row)
+
+step s1: BEGIN;
+step dbg: SELECT schema, name, removed FROM pg_session_variables();
+schema|name |removed
+------+-----+-------
+public|myvar|f      
+(1 row)
+
+step drop: DROP VARIABLE myvar;
+step create: CREATE VARIABLE myvar AS text;
+step dbg: SELECT schema, name, removed FROM pg_session_variables();
+schema|name |removed
+------+-----+-------
+public|myvar|f      
+(1 row)
+
+step val: SELECT myvar;
+myvar
+-----
+     
+(1 row)
+
+step sr1: ROLLBACK;
+
+starting permutation: create3 let3 s3 create4 let4 drop4 drop3 inval3 discard sc3 state
+step create3: CREATE VARIABLE myvar3 AS text;
+step let3: LET myvar3 = 'test';
+step s3: BEGIN;
+step create4: CREATE VARIABLE myvar4 AS text;
+step let4: LET myvar4 = 'test';
+step drop4: DROP VARIABLE myvar4;
+step drop3: DROP VARIABLE myvar3;
+step inval3: SELECT COUNT(*) >= 0 FROM pg_foreign_table;
+?column?
+--------
+t       
+(1 row)
+
+step discard: DISCARD VARIABLES;
+step sc3: COMMIT;
+step state: SELECT varname FROM pg_variable;
+varname
+-------
+myvar  
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 6da98cffaca..046bb1a497f 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -117,3 +117,4 @@ test: serializable-parallel-2
 test: serializable-parallel-3
 test: matview-write-skew
 test: lock-nowait
+test: session-variable
diff --git a/src/test/isolation/specs/session-variable.spec b/src/test/isolation/specs/session-variable.spec
new file mode 100644
index 00000000000..c864fee4006
--- /dev/null
+++ b/src/test/isolation/specs/session-variable.spec
@@ -0,0 +1,50 @@
+# Test session variables memory cleanup for sinval
+
+setup
+{
+    CREATE VARIABLE myvar AS text;
+}
+
+teardown
+{
+    DROP VARIABLE IF EXISTS myvar;
+}
+
+session s1
+step s1		{ BEGIN; }
+step let	{ LET myvar = 'test'; }
+step val	{ SELECT myvar; }
+step dbg	{ SELECT schema, name, removed FROM pg_session_variables(); }
+step sr1	{ ROLLBACK; }
+
+session s2
+step drop		{ DROP VARIABLE myvar; }
+step create		{ CREATE VARIABLE myvar AS text; }
+
+session s3
+step s3			{ BEGIN; }
+step let3		{ LET myvar3 = 'test'; }
+step create4	{ CREATE VARIABLE myvar4 AS text; }
+step let4		{ LET myvar4 = 'test'; }
+step drop4		{ DROP VARIABLE myvar4; }
+step inval3		{ SELECT COUNT(*) >= 0 FROM pg_foreign_table; }
+step discard	{ DISCARD VARIABLES; }
+step sc3		{ COMMIT; }
+step state		{ SELECT varname FROM pg_variable; }
+
+session s4
+step create3	{ CREATE VARIABLE myvar3 AS text; }
+step drop3		{ DROP VARIABLE myvar3; }
+
+# Concurrent drop of a known variable should lead to an error
+permutation let val drop val
+# Same, but with an explicit transaction
+permutation let val s1 drop val sr1
+# Concurrent drop/create of a known variable should lead to empty variable
+permutation let val dbg drop create dbg val
+# Concurrent drop/create of a known variable should lead to empty variable
+# We need a transaction to make sure that we won't accept invalidation when
+# calling the dbg step after the concurrent drop
+permutation let val s1 dbg drop create dbg val sr1
+# test for DISCARD ALL when all internal queues have actions registered
+permutation create3 let3 s3 create4 let4 drop4 drop3 inval3 discard sc3 state
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 2339fc14f3f..fca21f4137d 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -1029,3 +1029,222 @@ SELECT count(*) FROM pg_session_variables();
 -------
      0
 (1 row)
+
+-- dropped variables should be removed from memory
+-- at the end of transaction or before next usage
+-- of any session variable in next transaction.
+LET var1 = 'Ahoj';
+SELECT name, typname, can_select, can_update FROM pg_session_variables();
+ name |      typname      | can_select | can_update 
+------+-------------------+------------+------------
+ var1 | character varying | t          | t
+(1 row)
+
+DROP VARIABLE var1;
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+ count 
+-------
+     0
+(1 row)
+
+-- the content of value should be preserved when variable is dropped
+-- by aborted transaction
+CREATE VARIABLE var1 AS varchar;
+LET var1 = 'Ahoj';
+BEGIN;
+DROP VARIABLE var1;
+-- should fail
+SELECT var1;
+ERROR:  column "var1" does not exist
+LINE 1: SELECT var1;
+               ^
+ROLLBACK;
+-- should be ok
+SELECT var1;
+ var1 
+------
+ Ahoj
+(1 row)
+
+-- another test
+BEGIN;
+DROP VARIABLE var1;
+CREATE VARIABLE var1 AS int;
+LET var1 = 100;
+-- should be ok, result 100
+SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+ROLLBACK;
+-- should be ok, result 'Ahoj'
+SELECT var1;
+ var1 
+------
+ Ahoj
+(1 row)
+
+DROP VARIABLE var1;
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+ count 
+-------
+     0
+(1 row)
+
+BEGIN;
+  CREATE VARIABLE var1 AS int;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+  SELECT name, typname, can_select, can_update FROM pg_session_variables();
+ name | typname | can_select | can_update 
+------+---------+------------+------------
+ var1 | integer | t          | t
+(1 row)
+
+  DROP VARIABLE var1;
+COMMIT;
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+ count 
+-------
+     0
+(1 row)
+
+BEGIN;
+  CREATE VARIABLE var1 AS int;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+  SELECT name, typname, can_select, can_update FROM pg_session_variables();
+ name | typname | can_select | can_update 
+------+---------+------------+------------
+ var1 | integer | t          | t
+(1 row)
+
+  DROP VARIABLE var1;
+COMMIT;
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+ count 
+-------
+     0
+(1 row)
+
+CREATE VARIABLE var1 AS int;
+CREATE VARIABLE var2 AS int;
+LET var1 = 10;
+LET var2 = 0;
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+  ROLLBACK TO s1;
+  SAVEPOINT s2;
+  DROP VARIABLE var1;
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+  ROLLBACK TO s2;
+COMMIT;
+-- should be ok
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+  ROLLBACK TO s1;
+  SAVEPOINT s2;
+  DROP VARIABLE var1;
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+ROLLBACK;
+-- should be ok
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+  SAVEPOINT s2;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+  ROLLBACK TO s1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+COMMIT;
+-- should be ok
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+-- repeated aborted transaction
+BEGIN; DROP VARIABLE var1; ROLLBACK;
+BEGIN; DROP VARIABLE var1; ROLLBACK;
+BEGIN; DROP VARIABLE var1; ROLLBACK;
+-- should be ok
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+DROP VARIABLE var1, var2;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 60d062b30cc..5186cd9dc9f 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -720,3 +720,124 @@ DISCARD VARIABLES;
 
 -- should be zero again
 SELECT count(*) FROM pg_session_variables();
+
+-- dropped variables should be removed from memory
+-- at the end of transaction or before next usage
+-- of any session variable in next transaction.
+
+LET var1 = 'Ahoj';
+SELECT name, typname, can_select, can_update FROM pg_session_variables();
+DROP VARIABLE var1;
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+
+-- the content of value should be preserved when variable is dropped
+-- by aborted transaction
+CREATE VARIABLE var1 AS varchar;
+LET var1 = 'Ahoj';
+BEGIN;
+DROP VARIABLE var1;
+
+-- should fail
+SELECT var1;
+
+ROLLBACK;
+
+-- should be ok
+SELECT var1;
+
+-- another test
+BEGIN;
+DROP VARIABLE var1;
+CREATE VARIABLE var1 AS int;
+LET var1 = 100;
+-- should be ok, result 100
+SELECT var1;
+ROLLBACK;
+-- should be ok, result 'Ahoj'
+SELECT var1;
+
+DROP VARIABLE var1;
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+
+BEGIN;
+  CREATE VARIABLE var1 AS int;
+  LET var1 = 100;
+  SELECT var1;
+  SELECT name, typname, can_select, can_update FROM pg_session_variables();
+  DROP VARIABLE var1;
+COMMIT;
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+
+BEGIN;
+  CREATE VARIABLE var1 AS int;
+  LET var1 = 100;
+  SELECT var1;
+  SELECT name, typname, can_select, can_update FROM pg_session_variables();
+  DROP VARIABLE var1;
+COMMIT;
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+
+CREATE VARIABLE var1 AS int;
+CREATE VARIABLE var2 AS int;
+LET var1 = 10;
+LET var2 = 0;
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+  ROLLBACK TO s1;
+  SAVEPOINT s2;
+  DROP VARIABLE var1;
+  SELECT var2;
+  ROLLBACK TO s2;
+COMMIT;
+-- should be ok
+SELECT var1;
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+  ROLLBACK TO s1;
+  SAVEPOINT s2;
+  DROP VARIABLE var1;
+  SELECT var2;
+ROLLBACK;
+-- should be ok
+SELECT var1;
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+
+  SAVEPOINT s2;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+  ROLLBACK TO s1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+COMMIT;
+-- should be ok
+SELECT var1;
+
+-- repeated aborted transaction
+BEGIN; DROP VARIABLE var1; ROLLBACK;
+BEGIN; DROP VARIABLE var1; ROLLBACK;
+BEGIN; DROP VARIABLE var1; ROLLBACK;
+
+-- should be ok
+SELECT var1;
+
+DROP VARIABLE var1, var2;
-- 
2.45.2

From ecdf2c9784a1c547a5d6d10337071dcd6c2953f0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

Implementation of DISCARD VARIABLES commands by removing hash table with session variables
and resetting related memory context.
---
 doc/src/sgml/ref/discard.sgml                 | 13 +++-
 src/backend/commands/discard.c                |  6 ++
 src/backend/commands/session_variable.c       | 28 ++++++++-
 src/backend/parser/gram.y                     |  6 ++
 src/backend/tcop/utility.c                    |  3 +
 src/bin/psql/tab-complete.c                   |  2 +-
 src/include/commands/session_variable.h       |  2 +
 src/include/nodes/parsenodes.h                |  1 +
 src/include/tcop/cmdtaglist.h                 |  1 +
 .../regress/expected/session_variables.out    | 62 +++++++++++++++++++
 src/test/regress/sql/session_variables.sql    | 36 +++++++++++
 11 files changed, 157 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml
index bf44c523cac..61b967f9c9b 100644
--- a/doc/src/sgml/ref/discard.sgml
+++ b/doc/src/sgml/ref/discard.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }
+DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP | VARIABLES }
 </synopsis>
  </refsynopsisdiv>
 
@@ -66,6 +66,16 @@ DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>VARIABLES</literal></term>
+    <listitem>
+     <para>
+      Resets the value of all session variables. If a variable
+      is later reused, it is re-initialized to <literal>NULL</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>TEMPORARY</literal> or <literal>TEMP</literal></term>
     <listitem>
@@ -93,6 +103,7 @@ SELECT pg_advisory_unlock_all();
 DISCARD PLANS;
 DISCARD TEMP;
 DISCARD SEQUENCES;
+DISCARD VARIABLES;
 </programlisting></para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/commands/discard.c b/src/backend/commands/discard.c
index 92d983ac748..d06ebaba6f4 100644
--- a/src/backend/commands/discard.c
+++ b/src/backend/commands/discard.c
@@ -18,6 +18,7 @@
 #include "commands/async.h"
 #include "commands/discard.h"
 #include "commands/prepare.h"
+#include "commands/session_variable.h"
 #include "commands/sequence.h"
 #include "utils/guc.h"
 #include "utils/portal.h"
@@ -48,6 +49,10 @@ DiscardCommand(DiscardStmt *stmt, bool isTopLevel)
 			ResetTempTableNamespace();
 			break;
 
+		case DISCARD_VARIABLES:
+			ResetSessionVariables();
+			break;
+
 		default:
 			elog(ERROR, "unrecognized DISCARD target: %d", stmt->target);
 	}
@@ -75,4 +80,5 @@ DiscardAll(bool isTopLevel)
 	ResetPlanCache();
 	ResetTempTableNamespace();
 	ResetSequenceCaches();
+	ResetSessionVariables();
 }
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index c8ddefc20e4..c7968587125 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -102,7 +102,13 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
 
 	elog(DEBUG1, "pg_variable_cache_callback %u %u", cacheid, hashvalue);
 
-	Assert(sessionvars);
+	/*
+	 * There is no guarantee of sessionvars being initialized, even when
+	 * receiving an invalidation callback, as DISCARD [ ALL | VARIABLES ]
+	 * destroys the hash table entirely.
+	 */
+	if (!sessionvars)
+		return;
 
 	/*
 	 * When the hashvalue is not specified, then we have to recheck all
@@ -702,3 +708,23 @@ pg_session_variables(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
+
+/*
+ * Fast drop of the complete content of all session variables hash table, and
+ * cleanup of any list that wouldn't be relevant anymore.
+ * This is used by DISCARD VARIABLES (and DISCARD ALL) command.
+ */
+void
+ResetSessionVariables(void)
+{
+	/* Destroy hash table and reset related memory context */
+	if (sessionvars)
+	{
+		hash_destroy(sessionvars);
+		sessionvars = NULL;
+	}
+
+	/* Release memory allocated by session variables */
+	if (SVariableMemoryContext != NULL)
+		MemoryContextReset(SVariableMemoryContext);
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f5367369cf7..4ac319bf225 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2074,7 +2074,13 @@ DiscardStmt:
 					n->target = DISCARD_SEQUENCES;
 					$$ = (Node *) n;
 				}
+			| DISCARD VARIABLES
+				{
+					DiscardStmt *n = makeNode(DiscardStmt);
 
+					n->target = DISCARD_VARIABLES;
+					$$ = (Node *) n;
+				}
 		;

diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 662dbcd1dd6..db6004a05ce 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -2959,6 +2959,9 @@ CreateCommandTag(Node *parsetree)
 				case DISCARD_SEQUENCES:
 					tag = CMDTAG_DISCARD_SEQUENCES;
 					break;
+				case DISCARD_VARIABLES:
+					tag = CMDTAG_DISCARD_VARIABLES;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index c7e89487c72..df4c9cb4eef 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3740,7 +3740,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 	else if (Matches("DISCARD"))
-		COMPLETE_WITH("ALL", "PLANS", "SEQUENCES", "TEMP");
+		COMPLETE_WITH("ALL", "PLANS", "SEQUENCES", "TEMP", "VARIABLES");
 
 /* DO */
 	else if (Matches("DO"))
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index b3f03c65827..443afbafd4a 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -29,4 +29,6 @@ extern Datum GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expect
 extern void ExecuteLetStmt(ParseState *pstate, LetStmt *stmt, ParamListInfo params,
 						   QueryEnvironment *queryEnv, QueryCompletion *qc);
 
+extern void ResetSessionVariables(void);
+
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 74bf5af7766..fbab338a938 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3965,6 +3965,7 @@ typedef enum DiscardMode
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
 	DISCARD_TEMP,
+	DISCARD_VARIABLES,
 } DiscardMode;
 
 typedef struct DiscardStmt
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index a921af2486f..bd7964aea6e 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -135,6 +135,7 @@ PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_VARIABLES, "DISCARD VARIABLES", false, false, false)
 PG_CMDTAG(CMDTAG_DO, "DO", false, false, false)
 PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 281bdd7920b..2339fc14f3f 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -967,3 +967,65 @@ DROP VARIABLE :"DBNAME".:"DBNAME".b;
 DROP VARIABLE :"DBNAME".:"DBNAME".:"DBNAME";
 DROP SCHEMA :"DBNAME";
 RESET search_path;
+-- memory cleaning by DISCARD command
+CREATE VARIABLE var1 AS varchar;
+LET var1 = 'Hello';
+SELECT var1;
+ var1  
+-------
+ Hello
+(1 row)
+
+DISCARD ALL;
+SELECT var1;
+ var1 
+------
+ 
+(1 row)
+
+LET var1 = 'AHOJ';
+SELECT var1;
+ var1 
+------
+ AHOJ
+(1 row)
+
+DISCARD VARIABLES;
+SELECT var1;
+ var1 
+------
+ 
+(1 row)
+
+DROP VARIABLE var1;
+-- initial test of debug pg_session_variables function
+-- should be zero now
+DISCARD VARIABLES;
+SELECT count(*) FROM pg_session_variables();
+ count 
+-------
+     0
+(1 row)
+
+CREATE VARIABLE var1 AS varchar;
+-- should be zero still
+SELECT count(*) FROM pg_session_variables();
+ count 
+-------
+     0
+(1 row)
+
+LET var1 = 'AHOJ';
+SELECT name, typname, can_select, can_update FROM pg_session_variables();
+ name |      typname      | can_select | can_update 
+------+-------------------+------------+------------
+ var1 | character varying | t          | t
+(1 row)
+
+DISCARD VARIABLES;
+-- should be zero again
+SELECT count(*) FROM pg_session_variables();
+ count 
+-------
+     0
+(1 row)
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index cce52e7ae6e..60d062b30cc 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -684,3 +684,39 @@ DROP VARIABLE :"DBNAME".:"DBNAME".:"DBNAME";
 DROP SCHEMA :"DBNAME";
 
 RESET search_path;
+
+-- memory cleaning by DISCARD command
+CREATE VARIABLE var1 AS varchar;
+LET var1 = 'Hello';
+SELECT var1;
+
+DISCARD ALL;
+SELECT var1;
+
+LET var1 = 'AHOJ';
+SELECT var1;
+
+DISCARD VARIABLES;
+SELECT var1;
+
+DROP VARIABLE var1;
+
+-- initial test of debug pg_session_variables function
+-- should be zero now
+DISCARD VARIABLES;
+
+SELECT count(*) FROM pg_session_variables();
+
+CREATE VARIABLE var1 AS varchar;
+
+-- should be zero still
+SELECT count(*) FROM pg_session_variables();
+
+LET var1 = 'AHOJ';
+
+SELECT name, typname, can_select, can_update FROM pg_session_variables();
+
+DISCARD VARIABLES;
+
+-- should be zero again
+SELECT count(*) FROM pg_session_variables();
-- 
2.45.2

From c922f9f2ae1ec6fa57991ba22a4b40675802d45d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

Session variables are stored in session memory in dedicated hash table. The
write access is done by LET command. The read access is by SELECT command.
The access rights should be checked.

The identifiers of session variables should be shadowed by possible column's
identifiers always. This is by design feature. We don't want to break an
application by creating some session variable that is badly named.

The limits of this patch (solved by other patches):

- session variables block parallel execution
- session variables blocks simple expression evaluation (in plpgsql)
- SQL functions with session variables are not inlined
- CALL statement is not supported (usage of direct access to express executor)
- memory used by dropped session variables is not released

Implementations of EXPLAIN LET and PREPARE LET statements
are in separate patches (for better readability)
---
 doc/src/sgml/catalogs.sgml                    |  13 +
 doc/src/sgml/ddl.sgml                         |  39 +
 doc/src/sgml/event-trigger.sgml               |  24 +
 doc/src/sgml/plpgsql.sgml                     |  12 +
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/ref/alter_variable.sgml          |   1 +
 doc/src/sgml/ref/create_variable.sgml         |   1 +
 doc/src/sgml/ref/drop_variable.sgml           |   1 +
 doc/src/sgml/ref/let.sgml                     |  97 ++
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/catalog/dependency.c              |   5 +
 src/backend/catalog/namespace.c               | 326 +++++++
 src/backend/catalog/pg_variable.c             |   2 +
 src/backend/commands/Makefile                 |   1 +
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/session_variable.c       | 602 ++++++++++++
 src/backend/executor/Makefile                 |   1 +
 src/backend/executor/execExpr.c               |  42 +
 src/backend/executor/execMain.c               |  47 +
 src/backend/executor/meson.build              |   1 +
 src/backend/executor/svariableReceiver.c      | 201 ++++
 src/backend/nodes/nodeFuncs.c                 |  10 +
 src/backend/optimizer/plan/planner.c          |   8 +
 src/backend/optimizer/plan/setrefs.c          | 138 ++-
 src/backend/optimizer/prep/prepjointree.c     |   3 +
 src/backend/optimizer/util/clauses.c          |  35 +-
 src/backend/parser/analyze.c                  | 276 +++++-
 src/backend/parser/gram.y                     |  49 +-
 src/backend/parser/parse_agg.c                |   9 +
 src/backend/parser/parse_expr.c               | 224 ++++-
 src/backend/parser/parse_func.c               |   2 +
 src/backend/tcop/dest.c                       |   7 +
 src/backend/tcop/pquery.c                     |   3 +
 src/backend/tcop/utility.c                    |  16 +
 src/backend/utils/adt/ruleutils.c             |  46 +
 src/backend/utils/cache/plancache.c           |  41 +-
 src/backend/utils/fmgr/fmgr.c                 |  10 +-
 src/bin/psql/tab-complete.c                   |  12 +-
 src/include/catalog/namespace.h               |   2 +
 src/include/catalog/pg_variable.h             |   7 +
 src/include/commands/session_variable.h       |  32 +
 src/include/executor/execdesc.h               |   4 +
 src/include/executor/svariableReceiver.h      |  22 +
 src/include/nodes/execnodes.h                 |  19 +
 src/include/nodes/parsenodes.h                |  17 +
 src/include/nodes/pathnodes.h                 |   5 +
 src/include/nodes/plannodes.h                 |   4 +-
 src/include/nodes/primnodes.h                 |   6 +
 src/include/optimizer/planmain.h              |   2 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/parser/parse_node.h               |   3 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 src/include/tcop/dest.h                       |   1 +
 src/pl/plpgsql/src/pl_exec.c                  |   3 +-
 .../regress/expected/session_variables.out    | 912 ++++++++++++++++++
 src/test/regress/sql/session_variables.sql    | 625 ++++++++++++
 src/tools/pgindent/typedefs.list              |   5 +
 57 files changed, 3955 insertions(+), 24 deletions(-)
 create mode 100644 doc/src/sgml/ref/let.sgml
 create mode 100644 src/backend/commands/session_variable.c
 create mode 100644 src/backend/executor/svariableReceiver.c
 create mode 100644 src/include/commands/session_variable.h
 create mode 100644 src/include/executor/svariableReceiver.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 5ba18899345..5d495554429 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9777,6 +9777,19 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varcreate_lsn</structfield> <type>XLogRecPtr</type>
+      </para>
+      <para>
+       LSN of the transaction where variable was created. It is used
+       (in combination with <structfield>oid</structfield>) as everytime
+       unique identifier. Only <structfield>oid</structfield> cannot be
+       used for this purpose, because unused <structfield>oid</structfield>
+       can be reused.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>varname</structfield> <type>name</type>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index ee1d8ea060c..92730799f40 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5316,6 +5316,45 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     commands.  A session variable can be created by the <command>CREATE
     VARIABLE</command> command.
    </para>
+
+   <para>
+    The value of a session variable is set with the <command>LET</command> SQL
+    command.  While session variables share properties with tables, their value
+    cannot be updated with an <command>UPDATE</command> command. The value of a
+    session variable may be retrieved by the <command>SELECT</command> SQL
+    command.
+<programlisting>
+CREATE VARIABLE var1 AS date;
+LET var1 = current_date;
+SELECT var1;
+</programlisting>
+
+    or
+
+<programlisting>
+CREATE VARIABLE public.current_user_id AS integer;
+GRANT READ ON VARIABLE public.current_user_id TO PUBLIC;
+LET current_user_id = (SELECT id FROM users WHERE usename = session_user);
+SELECT current_user_id;
+</programlisting>
+   </para>
+
+   <para>
+    The value of a session variable is local to the current session. Retrieving
+    a variable's value returns a <literal>NULL</literal>, unless its value has
+    been set to something else in the current session using the
+    <command>LET</command> command. The content of a variable is not
+    transactional. This is the same as regular variables in PL languages.
+    The session variables are persistent, but the content of session variables is
+    temporary and not shared (like the content of temporary tables).
+   </para>
+
+   <para>
+    The session variables can be shadowed by column references in a query. When
+    a query contains identifiers or qualified identifiers that could be used as
+    both a session variable identifiers and as column identifier, then the
+    column identifier is preferred every time.
+   </para>
   </sect1>
 
  <sect1 id="ddl-others">
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 15b2af99dca..578b97afeef 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -432,6 +432,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>ALTER VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>ALTER VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
@@ -728,6 +736,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>CREATE VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>CREATE VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
@@ -1024,6 +1040,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>DROP VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>DROP VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index a6759238672..d6d4ebd1a03 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -6034,6 +6034,18 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE;
 </programlisting>
     </para>
    </sect3>
+
+   <sect3 id="plpgsql-session-variables">
+    <title><command>Session variables</command></title>
+
+    <para>
+     The <application>PL/pgSQL</application> language has no packages, and
+     therefore no package variables or package constants.
+     <productname>PostgreSQL</productname> has session variables.
+     Session variables can be created by <command>CREATE VARIABLE</command>,
+     as described in <xref linkend="sql-createvariable"/>.
+    </para>
+   </sect3>
   </sect2>
 
   <sect2 id="plpgsql-porting-appendix">
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 2f67de3e21b..cc3bd5ab540 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -158,6 +158,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY grant              SYSTEM "grant.sgml">
 <!ENTITY importForeignSchema SYSTEM "import_foreign_schema.sgml">
 <!ENTITY insert             SYSTEM "insert.sgml">
+<!ENTITY let                SYSTEM "let.sgml">
 <!ENTITY listen             SYSTEM "listen.sgml">
 <!ENTITY load               SYSTEM "load.sgml">
 <!ENTITY lock               SYSTEM "lock.sgml">
diff --git a/doc/src/sgml/ref/alter_variable.sgml b/doc/src/sgml/ref/alter_variable.sgml
index d87570e7d8b..d2036351e53 100644
--- a/doc/src/sgml/ref/alter_variable.sgml
+++ b/doc/src/sgml/ref/alter_variable.sgml
@@ -173,6 +173,7 @@ ALTER VARIABLE boo SET SCHEMA private;
   <simplelist type="inline">
    <member><xref linkend="sql-createvariable"/></member>
    <member><xref linkend="sql-dropvariable"/></member>
+   <member><xref linkend="sql-let"/></member>
   </simplelist>
  </refsect1>
 </refentry>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index 2568bdff35c..c95bd32742f 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -149,6 +149,7 @@ SELECT var1;
   <simplelist type="inline">
    <member><xref linkend="sql-altervariable"/></member>
    <member><xref linkend="sql-dropvariable"/></member>
+   <member><xref linkend="sql-let"/></member>
   </simplelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml
index 5bdb3560f0b..67988b5fcd8 100644
--- a/doc/src/sgml/ref/drop_variable.sgml
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -111,6 +111,7 @@ DROP VARIABLE var1;
   <simplelist type="inline">
    <member><xref linkend="sql-altervariable"/></member>
    <member><xref linkend="sql-createvariable"/></member>
+   <member><xref linkend="sql-let"/></member>
   </simplelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/let.sgml b/doc/src/sgml/ref/let.sgml
new file mode 100644
index 00000000000..63968eddd90
--- /dev/null
+++ b/doc/src/sgml/ref/let.sgml
@@ -0,0 +1,97 @@
+<!--
+doc/src/sgml/ref/let.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-let">
+ <indexterm zone="sql-let">
+  <primary>LET</primary>
+ </indexterm>
+
+ <indexterm>
+  <primary>session variable</primary>
+  <secondary>changing</secondary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>LET</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>LET</refname>
+  <refpurpose>change a session variable's value</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+LET <replaceable class="parameter">session_variable</replaceable> = <replaceable class="parameter">sql_expression</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   The <command>LET</command> command assigns a value to the specified session
+   variable.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>session_variable</literal></term>
+    <listitem>
+     <para>
+      The name of the session variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>sql_expression</literal></term>
+    <listitem>
+     <para>
+      An SQL expression (can be subquery in parenthesis). The result must
+      be of castable to the same data type as the session variable (in
+      implicit or assignment context).
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+
+  <para>
+   Example:
+<programlisting>
+CREATE VARIABLE myvar AS integer;
+LET myvar = 10;
+LET myvar = (SELECT sum(val) FROM tab);
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   The <command>LET</command> is a <productname>PostgreSQL</productname>
+   extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-altervariable"/></member>
+   <member><xref linkend="sql-createvariable"/></member>
+   <member><xref linkend="sql-dropvariable"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 25578f3946c..13e4adc5df3 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -186,6 +186,7 @@
    &grant;
    &importForeignSchema;
    &insert;
+   &let;
    &listen;
    &load;
    &lock;
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 5a5e3f7b56a..17119f615ea 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1865,6 +1865,11 @@ find_expr_references_walker(Node *node,
 	{
 		Param	   *param = (Param *) node;
 
+		/* A variable parameter depends on the session variable */
+		if (param->paramkind == PARAM_VARIABLE)
+			add_object_address(VariableRelationId, param->paramvarid, 0,
+							   context->addrs);
+
 		/* A parameter must depend on the parameter's datatype */
 		add_object_address(TypeRelationId, param->paramtype, 0,
 						   context->addrs);
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index c1824f9115f..f81e006424e 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -3402,6 +3402,332 @@ LookupVariable(const char *nspname,
 	return varoid;
 }
 
+/*
+ * The input list contains names with indirection expressions used as the left
+ * part of LET statement. The following routine returns a new list with only
+ * initial strings (names) - without indirection expressions.
+ */
+List *
+NamesFromList(List *names)
+{
+	ListCell   *l;
+	List	   *result = NIL;
+
+	foreach(l, names)
+	{
+		Node	   *n = lfirst(l);
+
+		if (IsA(n, String))
+		{
+			result = lappend(result, n);
+		}
+		else
+			break;
+	}
+
+	return result;
+}
+
+/*
+ * IdentifyVariable - try to find variable identified by list of names.
+ *
+ * Before this call we don't know, how these fields should be mapped to
+ * schema name, variable name and attribute name. In this routine
+ * we try to apply passed names to all possible combinations of schema name,
+ * variable name and attribute name, and we count valid combinations.
+ *
+ * Returns oid of identified variable. When last field of names list is
+ * identified as an attribute, then the argument attrname contains the name of
+ * this field.
+ *
+ * When there is is no valid combinations, we can be sure the specified list of
+ * names cannot identify a session variable. In this case we return InvalidOid.
+ *
+ * We can find more valid combination than one.
+ * Example: users can have session variable x in schema y, and
+ * session variable y with attribute x inside some schema from
+ * search path. In this situation the meaning of expression "y"."x"
+ * is ambiguous. In this case this routine returns oid of variable
+ * x in schema y, and the output parameter "not_unique" is set to
+ * true. In this case this variable is locked.
+ *
+ * The AccessShareLock is created on related session variable. The lock
+ * will be kept for the whole transaction.
+ *
+ * Note: the out attrname should be used only when the session variable
+ * is identified. When the session variable is not identified, then this
+ * output variable can hold reference to some string, but isn't sure
+ * about its semantics.
+ *
+ * When we use this routine for identification of shadowed variable,
+ * we don't want to raise any error. Shadowing column reference is correct,
+ * and we don't want to break execution due shadowing check.
+ */
+Oid
+IdentifyVariable(List *names, char **attrname, bool *not_unique, bool noerror)
+{
+	Node	   *field1 = NULL;
+	Node	   *field2 = NULL;
+	Node	   *field3 = NULL;
+	Node	   *field4 = NULL;
+	char	   *a = NULL;
+	char	   *b = NULL;
+	char	   *c = NULL;
+	char	   *d = NULL;
+	Oid			varid = InvalidOid;
+	Oid			old_varid = InvalidOid;
+	uint64		inval_count;
+	bool		retry = false;
+
+	/*
+	 * DDL operations can change the results of a name lookup.  Since all such
+	 * operations will generate invalidation messages, we keep track of
+	 * whether any such messages show up while we're performing the operation,
+	 * and retry until either (1) no more invalidation messages show up or (2)
+	 * the answer doesn't change.
+	 */
+	for (;;)
+	{
+		Oid			varoid_without_attr = InvalidOid;
+		Oid			varoid_with_attr = InvalidOid;
+
+		*not_unique = false;
+		*attrname = NULL;
+		varid = InvalidOid;
+
+		inval_count = SharedInvalidMessageCounter;
+
+		switch (list_length(names))
+		{
+			case 1:
+				field1 = linitial(names);
+
+				Assert(IsA(field1, String));
+
+				varid = LookupVariable(NULL, strVal(field1), true);
+				break;
+
+			case 2:
+				field1 = linitial(names);
+				field2 = lsecond(names);
+
+				Assert(IsA(field1, String));
+				a = strVal(field1);
+
+				if (IsA(field2, String))
+				{
+					/* when both fields are of string type */
+					b = strVal(field2);
+
+					/*
+					 * a.b can mean "schema"."variable" or "variable"."field".
+					 * Check both variants, and returns InvalidOid with
+					 * not_unique flag, when both interpretations are
+					 * possible.
+					 */
+					varoid_without_attr = LookupVariable(a, b, true);
+					varoid_with_attr = LookupVariable(NULL, a, true);
+				}
+				else
+				{
+					/* The last field of list can be star too. */
+					Assert(IsA(field2, A_Star));
+
+					/*
+					 * In this case, the field1 should be variable name. But
+					 * direct unboxing of composite session variables is not
+					 * supported now, and then we don't need to try lookup
+					 * related variable.
+					 *
+					 * Unboxing is supported by syntax (var).*
+					 */
+					return InvalidOid;
+				}
+
+				if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+				{
+					*not_unique = true;
+					varid = varoid_without_attr;
+				}
+				else if (OidIsValid(varoid_without_attr))
+				{
+					varid = varoid_without_attr;
+				}
+				else if (OidIsValid(varoid_with_attr))
+				{
+					*attrname = b;
+					varid = varoid_with_attr;
+				}
+				break;
+
+			case 3:
+				{
+					bool		field1_is_catalog = false;
+
+					field1 = linitial(names);
+					field2 = lsecond(names);
+					field3 = lthird(names);
+
+					Assert(IsA(field1, String));
+					Assert(IsA(field2, String));
+
+					a = strVal(field1);
+					b = strVal(field2);
+
+					if (IsA(field3, String))
+					{
+						c = strVal(field3);
+
+						/*
+						 * a.b.c can mean catalog.schema.variable or
+						 * schema.variable.field.
+						 *
+						 * Check both variants, and set not_unique flag, when
+						 * both interpretations are possible.
+						 *
+						 * When third node is star, only possible
+						 * interpretation is schema.variable.*, but this
+						 * pattern is not supported now.
+						 */
+						varoid_with_attr = LookupVariable(a, b, true);
+
+						/*
+						 * check pattern catalog.schema.variable only when
+						 * there is possibility to success.
+						 */
+						if (strcmp(a, get_database_name(MyDatabaseId)) == 0)
+						{
+							field1_is_catalog = true;
+							varoid_without_attr = LookupVariable(b, c, true);
+						}
+					}
+					else
+					{
+						Assert(IsA(field3, A_Star));
+						return InvalidOid;
+					}
+
+					if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+					{
+						*not_unique = true;
+						varid = varoid_without_attr;
+					}
+					else if (OidIsValid(varoid_without_attr))
+					{
+						varid = varoid_without_attr;
+					}
+					else if (OidIsValid(varoid_with_attr))
+					{
+						*attrname = c;
+						varid = varoid_with_attr;
+					}
+
+					/*
+					 * When we didn't find variable, we can (when it is
+					 * allowed) raise cross-database reference error.
+					 */
+					if (!OidIsValid(varid) && !noerror && !field1_is_catalog)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cross-database references are not implemented: %s",
+										NameListToString(names))));
+				}
+				break;
+
+			case 4:
+				{
+					field1 = linitial(names);
+					field2 = lsecond(names);
+					field3 = lthird(names);
+					field4 = lfourth(names);
+
+					Assert(IsA(field1, String));
+					Assert(IsA(field2, String));
+					Assert(IsA(field3, String));
+
+					a = strVal(field1);
+					b = strVal(field2);
+					c = strVal(field3);
+
+					/*
+					 * In this case, "a" is used as catalog name - check it.
+					 */
+					if (strcmp(a, get_database_name(MyDatabaseId)) != 0)
+					{
+						if (!noerror)
+							ereport(ERROR,
+									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									 errmsg("cross-database references are not implemented: %s",
+											NameListToString(names))));
+					}
+					else
+					{
+						if (IsA(field4, String))
+						{
+							d = strVal(field4);
+						}
+						else
+						{
+							Assert(IsA(field4, A_Star));
+							return InvalidOid;
+						}
+
+						*attrname = d;
+						varid = LookupVariable(b, c, true);
+					}
+				}
+				break;
+
+			default:
+				if (!noerror)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("improper qualified name (too many dotted names): %s",
+									NameListToString(names))));
+				return InvalidOid;
+		}
+
+		/*
+		 * If, upon retry, we get back the same OID we did last time, then the
+		 * invalidation messages we processed did not change the final answer.
+		 * So we're done.
+		 *
+		 * If we got a different OID, we've locked the variable that used to
+		 * have this name rather than the one that does now.  So release the
+		 * lock.
+		 */
+		if (retry)
+		{
+			if (old_varid == varid)
+				break;
+
+			if (OidIsValid(old_varid))
+				UnlockDatabaseObject(VariableRelationId, old_varid, 0, AccessShareLock);
+		}
+
+		/*
+		 * Lock the variable.  This will also accept any pending invalidation
+		 * messages.  If we got back InvalidOid, indicating not found, then
+		 * there's nothing to lock, but we accept invalidation messages
+		 * anyway, to flush any negative catcache entries that may be
+		 * lingering.
+		 */
+		if (!OidIsValid(varid))
+			AcceptInvalidationMessages();
+		else if (OidIsValid(varid))
+			LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+		if (inval_count == SharedInvalidMessageCounter)
+			break;
+
+		retry = true;
+		old_varid = varid;
+		varid = InvalidOid;
+	}
+
+	return varid;
+}
+
 /*
  * DeconstructQualifiedName
  *		Given a possibly-qualified name expressed as a list of String nodes,
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
index f65121440a9..1c73181c3da 100644
--- a/src/backend/catalog/pg_variable.c
+++ b/src/backend/catalog/pg_variable.c
@@ -26,6 +26,7 @@
 #include "parser/parse_type.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/pg_lsn.h"
 #include "utils/syscache.h"
 
 static ObjectAddress create_variable(const char *varName,
@@ -101,6 +102,7 @@ create_variable(const char *varName,
 	varid = GetNewOidWithIndex(rel, VariableOidIndexId, Anum_pg_variable_oid);
 
 	values[Anum_pg_variable_oid - 1] = ObjectIdGetDatum(varid);
+	values[Anum_pg_variable_varcreate_lsn - 1] = LSNGetDatum(GetXLogInsertRecPtr());
 	values[Anum_pg_variable_varname - 1] = NameGetDatum(&varname);
 	values[Anum_pg_variable_varnamespace - 1] = ObjectIdGetDatum(varNamespace);
 	values[Anum_pg_variable_vartype - 1] = ObjectIdGetDatum(varType);
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91c..1cfaeca51ea 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -50,6 +50,7 @@ OBJS = \
 	schemacmds.o \
 	seclabel.o \
 	sequence.o \
+	session_variable.o \
 	statscmds.o \
 	subscriptioncmds.o \
 	tablecmds.o \
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 6dd00a4abde..ca621be5ecb 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -38,6 +38,7 @@ backend_sources += files(
   'schemacmds.c',
   'seclabel.c',
   'sequence.c',
+  'session_variable.c',
   'statscmds.c',
   'subscriptioncmds.c',
   'tablecmds.c',
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
new file mode 100644
index 00000000000..e190ef7389c
--- /dev/null
+++ b/src/backend/commands/session_variable.c
@@ -0,0 +1,602 @@
+/*-------------------------------------------------------------------------
+ *
+ * session_variable.c
+ *	  session variable creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/session_variable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
+#include "executor/svariableReceiver.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "rewrite/rewriteHandler.h"
+#include "storage/lmgr.h"
+#include "storage/proc.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+/*
+ * Values of session variables are stored in the backend local memory
+ * inside sessionvars hash table in binary format inside a dedicated memory
+ * context SVariableMemoryContext.  The hash key is oid
+ * of related entry in pg_variable table. But long term unambiguity of oid is
+ * not guaranteed. As an example, for a value with one oid a session can be
+ * inactive for long time, while in the meantime the related session variable
+ * can be dropped in another session, assigned oid can be released, and
+ * theoreticaly this oid can be assigned to different session variable.
+ * At the end, the reading of value stored in old session should to fail,
+ * because related entry in pg_variable will not be consistent with
+ * stored value. This is reason why we do check consistency between stored
+ * value and catalog by create_lsn value.
+ *
+ * Before any usage (not only read in transaction) we need to check consistency
+ * with pg_variable entry. When there is not entry with stored oid, the related
+ * variable was dropped, and stored value is not consistent. When entry with
+ * known oid, but lsn number is different, entry of pg_variable was created
+ * for different variable and stored value is not consistent again.
+ */
+typedef struct SVariableData
+{
+	Oid			varid;			/* pg_variable OID of the variable (hash key) */
+	XLogRecPtr	create_lsn;
+
+	bool		isnull;
+	Datum		value;
+
+	Oid			typid;
+	int16		typlen;
+	bool		typbyval;
+
+	bool		is_domain;
+
+	/*
+	 * domain_check_extra holds an extra (cache) for domain check.
+	 * This extra is usually stored in fn_mcxt. We do not have same
+	 * memory context for session variables, but we can use
+	 * TopTransactionContext instead. Fresh extra is forced when
+	 * we detect we are in a different transaction (different
+	 * local transaction id domain_check_extra_lxid).
+	 */
+	void	   *domain_check_extra;
+	LocalTransactionId domain_check_extra_lxid;
+
+	/*
+	 * Stored value and type description can be outdated when we receive
+	 * sinval message. We have to check always if the stored data are
+	 * trustful.
+	 */
+	bool		is_valid;
+
+	uint32		hashvalue;		/* used for pairing sinval message */
+} SVariableData;
+
+typedef SVariableData *SVariable;
+
+static HTAB *sessionvars = NULL;	/* hash table for session variables */
+
+static MemoryContext SVariableMemoryContext = NULL;
+
+/*
+ * Callback function for session variable invalidation.
+ */
+static void
+pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
+{
+	HASH_SEQ_STATUS status;
+	SVariable	svar;
+
+	elog(DEBUG1, "pg_variable_cache_callback %u %u", cacheid, hashvalue);
+
+	Assert(sessionvars);
+
+	/*
+	 * When the hashvalue is not specified, then we have to recheck all
+	 * currently used session variables. Since we can't guarantee the exact
+	 * session variable from its hashValue, we also have to iterate over all
+	 * items of the sessionvars hash table.
+	 */
+	hash_seq_init(&status, sessionvars);
+
+	while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+	{
+		if (hashvalue == 0 || svar->hashvalue == hashvalue)
+		{
+			svar->is_valid = false;
+		}
+	}
+}
+
+/*
+ * Release stored value, free memory
+ */
+static void
+free_session_variable_value(SVariable svar)
+{
+	/* Clean current value */
+	if (!svar->isnull)
+	{
+		if (!svar->typbyval)
+			pfree(DatumGetPointer(svar->value));
+
+		svar->isnull = true;
+	}
+
+	svar->value = (Datum) 0;
+}
+
+/*
+ * Returns true when the entry in pg_variable is consistent with
+ * the given session variable.
+ */
+static bool
+is_session_variable_valid(SVariable svar)
+{
+	HeapTuple	tp;
+	bool		result = false;
+
+	Assert(OidIsValid(svar->varid));
+
+	tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+	if (HeapTupleIsValid(tp))
+	{
+		/*
+		 * In this case, the only oid cannot be used as unique identifier,
+		 * because the oid counter can wraparound, and the oid can be used for
+		 * new other session variable. We do a second check against 64bit
+		 * unique identifier.
+		 */
+		if (svar->create_lsn == ((Form_pg_variable) GETSTRUCT(tp))->varcreate_lsn)
+			result = true;
+
+		ReleaseSysCache(tp);
+	}
+
+	return result;
+}
+
+/*
+ * Update attributes cached in svar
+ */
+static void
+setup_session_variable(SVariable svar, Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+
+	Assert(OidIsValid(varid));
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	svar->varid = varid;
+	svar->create_lsn = varform->varcreate_lsn;
+
+	svar->typid = varform->vartype;
+
+	get_typlenbyval(svar->typid, &svar->typlen, &svar->typbyval);
+
+	svar->is_domain = (get_typtype(varform->vartype) == TYPTYPE_DOMAIN);
+	svar->domain_check_extra = NULL;
+	svar->domain_check_extra_lxid = InvalidLocalTransactionId;
+
+	svar->isnull = true;
+	svar->value = (Datum) 0;
+
+	svar->is_valid = true;
+
+	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
+											ObjectIdGetDatum(varid));
+
+	ReleaseSysCache(tup);
+}
+
+/*
+ * Assign some content to the session variable. It's copied to
+ * SVariableMemoryContext if necessary.
+ *
+ * If any error happens, the existing value shouldn't be modified.
+ */
+static void
+set_session_variable(SVariable svar, Datum value, bool isnull)
+{
+	Datum		newval;
+	SVariableData locsvar,
+			   *_svar;
+
+	Assert(svar);
+	Assert(!isnull || value == (Datum) 0);
+
+	/*
+	 * Use typbyval, typbylen from session variable only when they are
+	 * trustable (the invalidation message was not accepted for this variable).
+	 * When the variable is possibly invalid, force setup.
+	 *
+	 * Do not do it against passed svar, it should be unchanged, when an
+	 * assignment is not successful (the datumCopy can fail).
+	 */
+	if (!svar->is_valid)
+	{
+		setup_session_variable(&locsvar, svar->varid);
+		_svar = &locsvar;
+	}
+	else
+		_svar = svar;
+
+	if (!isnull)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+
+		newval = datumCopy(value, _svar->typbyval, _svar->typlen);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else
+		newval = value;
+
+	free_session_variable_value(svar);
+
+	/* We can overwrite old variable now. No error expected */
+	if (svar != _svar)
+		memcpy(svar, _svar, sizeof(SVariableData));
+
+	svar->value = newval;
+	svar->isnull = isnull;
+
+	/*
+	 * XXX While unlikely, an error here is possible. It wouldn't leak memory
+	 * as the allocated chunk has already been correctly assigned to the
+	 * session variable, but would contradict this function contract, which is
+	 * that this function should either succeed or leave the current value
+	 * untouched.
+	 */
+	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new value",
+		 get_namespace_name(get_session_variable_namespace(svar->varid)),
+		 get_session_variable_name(svar->varid),
+		 svar->varid);
+}
+
+/*
+ * Create the hash table for storing session variables.
+ */
+static void
+create_sessionvars_hashtables(void)
+{
+	HASHCTL		vars_ctl;
+
+	Assert(!sessionvars);
+
+	if (!SVariableMemoryContext)
+	{
+		/* Read sinval messages */
+		CacheRegisterSyscacheCallback(VARIABLEOID,
+									  pg_variable_cache_callback,
+									  (Datum) 0);
+
+		/* We need our own long lived memory context */
+		SVariableMemoryContext =
+			AllocSetContextCreate(TopMemoryContext,
+								  "session variables",
+								  ALLOCSET_START_SMALL_SIZES);
+	}
+
+	memset(&vars_ctl, 0, sizeof(vars_ctl));
+	vars_ctl.keysize = sizeof(Oid);
+	vars_ctl.entrysize = sizeof(SVariableData);
+	vars_ctl.hcxt = SVariableMemoryContext;
+
+	sessionvars = hash_create("Session variables", 64, &vars_ctl,
+							  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+/*
+ * Search a seesion variable in the hash table given its oid. If it
+ * doesn't exist, then insert it there.
+ *
+ * Caller is responsible for doing permission checks.
+ *
+ * As side effect this function acquires AccessShareLock on
+ * related session variable until the end of the transaction.
+ */
+static SVariable
+get_session_variable(Oid varid)
+{
+	SVariable	svar;
+	bool		found;
+
+	/* Protect used session variable against drop until transaction end */
+	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	if (!sessionvars)
+		create_sessionvars_hashtables();
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+								   HASH_ENTER, &found);
+
+	if (found)
+	{
+		if (!svar->is_valid)
+		{
+			/*
+			 * The variable can be flagged as invalid by processing invalidation
+			 * message, but can be validated by recheck against system catalog.
+			 */
+			if (is_session_variable_valid(svar))
+				svar->is_valid = true;
+			else
+				/*
+				 * When the value cannot be validated, we can safely throw it.
+				 * There is oid in catalog, but it is related to different
+				 * session variable (different create_lsn).
+				 */
+				free_session_variable_value(svar);
+		}
+	}
+	else
+		svar->is_valid = false;
+
+	/*
+	 * Force setup for not yet initialized variables or variables that cannot
+	 * be validated.
+	 */
+	if (!svar->is_valid)
+	{
+		setup_session_variable(svar, varid);
+
+		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by READ)",
+			 get_namespace_name(get_session_variable_namespace(varid)),
+			 get_session_variable_name(varid),
+			 varid);
+	}
+
+	/* Ensure so returned data is still correct domain */
+	if (svar->is_domain)
+	{
+		/*
+		 * Store domain_check extra in TopTransactionContext. When we are in
+		 * other transaction, the domain_check_extra cache is not valid
+		 * anymore.
+		 */
+		if (svar->domain_check_extra_lxid != MyProc->vxid.lxid)
+			svar->domain_check_extra = NULL;
+
+		domain_check(svar->value, svar->isnull,
+					 svar->typid, &svar->domain_check_extra,
+					 TopTransactionContext);
+
+		svar->domain_check_extra_lxid = MyProc->vxid.lxid;
+	}
+
+	return svar;
+}
+
+/*
+ * Store the given value in an SVariable, and cache it if not already present.
+ *
+ * Caller is responsible for doing permission checks.
+ *
+ * As side effect this function acquires AccessShareLock on
+ * related session variable until the end of the transaction.
+ */
+void
+SetSessionVariable(Oid varid, Datum value, bool isNull)
+{
+	SVariable	svar;
+	bool		found;
+
+	/* Protect used session variable against drop until transaction end */
+	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	if (!sessionvars)
+		create_sessionvars_hashtables();
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+								   HASH_ENTER, &found);
+
+	if (!found)
+	{
+		setup_session_variable(svar, varid);
+
+		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by WRITE)",
+			 get_namespace_name(get_session_variable_namespace(svar->varid)),
+			 get_session_variable_name(svar->varid),
+			 varid);
+	}
+
+	/*
+	 * This should either succeed or fail without changing the currently
+	 * stored value.
+	 */
+	set_session_variable(svar, value, isNull);
+}
+
+/*
+ * Wrapper around SetSessionVariable after checking for correct permission.
+ */
+void
+SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull)
+{
+	AclResult	aclresult;
+
+	/*
+	 * Is caller allowed to update the session variable?
+	 */
+	aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));
+
+	SetSessionVariable(varid, value, isNull);
+}
+
+/*
+ * Returns copy of value stored in variable.
+ */
+static inline Datum
+copy_session_variable_value(SVariable svar, bool *isNull)
+{
+	Datum		value;
+
+	/* force copy of non NULL value */
+	if (!svar->isnull)
+	{
+		value = datumCopy(svar->value, svar->typbyval, svar->typlen);
+		*isNull = false;
+	}
+	else
+	{
+		value = (Datum) 0;
+		*isNull = true;
+	}
+
+	return value;
+}
+
+/*
+ * Returns a copy of the value of the session variable (in current memory
+ * context) specified by its oid. Caller is responsible for doing permission
+ * checks.
+ */
+Datum
+GetSessionVariable(Oid varid, bool *isNull, Oid *typid)
+{
+	SVariable	svar;
+
+	svar = get_session_variable(varid);
+
+	/*
+	 * Although svar is freshly validated in this point, the svar->is_valid can
+	 * be false, due possible accepting invalidation message inside domain
+	 * check. Now, the validation is done after lock, that can also accept
+	 * invalidation message, so validation should be trustful.
+	 *
+	 * For now, we don't need to repeat validation. Only svar should be valid
+	 * pointer.
+	 */
+	Assert(svar);
+
+	*typid = svar->typid;
+
+	return copy_session_variable_value(svar, isNull);
+}
+
+/*
+ * Returns a copy of the value of the session variable specified by its oid
+ * with a check of the expected type. Like previous GetSessionVariable, the
+ * caller is responsible for doing permission checks.
+ */
+Datum
+GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
+{
+	SVariable	svar;
+
+	svar = get_session_variable(varid);
+
+	Assert(svar && svar->is_valid);
+
+	if (expected_typid != svar->typid)
+		elog(ERROR, "type of variable \"%s.%s\" is different than expected",
+			 get_namespace_name(get_session_variable_namespace(varid)),
+			 get_session_variable_name(varid));
+
+	return copy_session_variable_value(svar, isNull);
+}
+
+/*
+ * Assign result of evaluated expression to session variable
+ */
+void
+ExecuteLetStmt(ParseState *pstate,
+			   LetStmt *stmt,
+			   ParamListInfo params,
+			   QueryEnvironment *queryEnv,
+			   QueryCompletion *qc)
+{
+	Query	   *query = castNode(Query, stmt->query);
+	List	   *rewritten;
+	DestReceiver *dest;
+	AclResult	aclresult;
+	PlannedStmt *plan;
+	QueryDesc  *queryDesc;
+	Oid			varid = query->resultVariable;
+
+	Assert(OidIsValid(varid));
+
+	/*
+	 * Is it allowed to write to session variable?
+	 */
+	aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));
+
+	/* Create dest receiver for LET */
+	dest = CreateVariableDestReceiver(varid);
+
+	/* run rewriter */
+	query = copyObject(query);
+
+	rewritten = QueryRewrite(query);
+
+	Assert(list_length(rewritten) == 1);
+
+	query = linitial_node(Query, rewritten);
+	Assert(query->commandType == CMD_SELECT);
+
+	/* plan the query */
+	plan = pg_plan_query(query, pstate->p_sourcetext,
+						 CURSOR_OPT_PARALLEL_OK, params);
+
+	/*
+	 * Use a snapshot with an updated command ID to ensure this query sees
+	 * results of any previously executed queries.  (This could only matter if
+	 * the planner executed an allegedly-stable function that changed the
+	 * database contents, but let's do it anyway to be parallel to the EXPLAIN
+	 * code path.)
+	 */
+	PushCopiedSnapshot(GetActiveSnapshot());
+	UpdateActiveSnapshotCommandId();
+
+	/* Create a QueryDesc, redirecting output to our tuple receiver */
+	queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
+								GetActiveSnapshot(), InvalidSnapshot,
+								dest, params, queryEnv, 0);
+
+	/* call ExecutorStart to prepare the plan for execution */
+	ExecutorStart(queryDesc, 0);
+
+	/*
+	 * Run the plan to completion. The result should be only one row. For an
+	 * check too_many_rows we need to read two rows.
+	 */
+	ExecutorRun(queryDesc, ForwardScanDirection, 2L, true);
+
+	/* save the rowcount if we're given a qc to fill */
+	if (qc)
+		SetQueryCompletion(qc, CMDTAG_LET, queryDesc->estate->es_processed);
+
+	/* and clean up */
+	ExecutorFinish(queryDesc);
+	ExecutorEnd(queryDesc);
+
+	FreeQueryDesc(queryDesc);
+
+	PopActiveSnapshot();
+}
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 11118d0ce02..71248a34f26 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -76,6 +76,7 @@ OBJS = \
 	nodeWindowAgg.o \
 	nodeWorktablescan.o \
 	spi.o \
+	svariableReceiver.o \
 	tqueue.o \
 	tstoreReceiver.o
 
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index ccd48637784..972233ec25b 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -34,6 +34,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -987,6 +988,47 @@ ExecInitExprRec(Expr *node, ExprState *state,
 						scratch.d.param.paramtype = param->paramtype;
 						ExprEvalPushStep(state, &scratch);
 						break;
+
+					case PARAM_VARIABLE:
+						{
+							int			es_num_session_variables = 0;
+							SessionVariableValue *es_session_variables = NULL;
+							SessionVariableValue *var;
+
+							if (state->parent && state->parent->state)
+							{
+								es_session_variables = state->parent->state->es_session_variables;
+								es_num_session_variables = state->parent->state->es_num_session_variables;
+							}
+
+							Assert(es_session_variables);
+
+							/*
+							 * Use buffered session variables when the
+							 * buffer with copied values is avaiable
+							 * (standard query executor mode)
+							 */
+
+							/* Parameter sanity checks. */
+							if (param->paramid >= es_num_session_variables)
+								elog(ERROR, "paramid of PARAM_VARIABLE param is out of range");
+
+							var = &es_session_variables[param->paramid];
+
+							if (var->typid != param->paramtype)
+								elog(ERROR, "type of buffered value is different than PARAM_VARIABLE type");
+
+							/*
+							 * In this case, pass the value like a
+							 * constant.
+							 */
+							scratch.opcode = EEOP_CONST;
+							scratch.d.constval.value = var->value;
+							scratch.d.constval.isnull = var->isnull;
+							ExprEvalPushStep(state, &scratch);
+						}
+						break;
+
 					case PARAM_EXTERN:
 
 						/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4d7c92d63c1..f2fb680defc 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -43,7 +43,9 @@
 #include "access/xact.h"
 #include "catalog/namespace.h"
 #include "catalog/partition.h"
+#include "catalog/pg_variable.h"
 #include "commands/matview.h"
+#include "commands/session_variable.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/nodeSubplan.h"
@@ -196,6 +198,51 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	Assert(queryDesc->sourceText != NULL);
 	estate->es_sourceText = queryDesc->sourceText;
 
+	/*
+	 * The executor doesn't work with session variables directly. Values of
+	 * related session variables are copied to dedicated array, and this array
+	 * is passed to executor.
+	 */
+	if (queryDesc->plannedstmt->sessionVariables)
+	{
+		ListCell   *lc;
+		int			nSessionVariables;
+		int			i = 0;
+
+		/*
+		 * In this case, the query uses session variables, but we have to
+		 * prepare the array with passed values (of used session variables)
+		 * first.
+		 */
+		Assert(!IsParallelWorker());
+		nSessionVariables = list_length(queryDesc->plannedstmt->sessionVariables);
+
+		/* Create the array used for passing values of used session variables */
+		estate->es_session_variables = (SessionVariableValue *)
+			palloc(nSessionVariables * sizeof(SessionVariableValue));
+
+		/* Fill the array */
+		foreach(lc, queryDesc->plannedstmt->sessionVariables)
+		{
+			AclResult	aclresult;
+			Oid			varid = lfirst_oid(lc);
+
+			aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_SELECT);
+			if (aclresult != ACLCHECK_OK)
+				aclcheck_error(aclresult, OBJECT_VARIABLE,
+							   get_session_variable_name(varid));
+
+			estate->es_session_variables[i].varid = varid;
+			estate->es_session_variables[i].value = GetSessionVariable(varid,
+																	   &estate->es_session_variables[i].isnull,
+																	   &estate->es_session_variables[i].typid);
+
+			i++;
+		}
+
+		estate->es_num_session_variables = nSessionVariables;
+	}
+
 	/*
 	 * Fill in the query environment, if any, from queryDesc.
 	 */
diff --git a/src/backend/executor/meson.build b/src/backend/executor/meson.build
index b511a429adf..b6662c6e8fc 100644
--- a/src/backend/executor/meson.build
+++ b/src/backend/executor/meson.build
@@ -64,6 +64,7 @@ backend_sources += files(
   'nodeWindowAgg.c',
   'nodeWorktablescan.c',
   'spi.c',
+  'svariableReceiver.c',
   'tqueue.c',
   'tstoreReceiver.c',
 )
diff --git a/src/backend/executor/svariableReceiver.c b/src/backend/executor/svariableReceiver.c
new file mode 100644
index 00000000000..76cd8a0fb38
--- /dev/null
+++ b/src/backend/executor/svariableReceiver.c
@@ -0,0 +1,201 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.c
+ *	  An implementation of DestReceiver that stores the result value in
+ *	  a session variable.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/svariableReceiver.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "miscadmin.h"
+
+#include "access/detoast.h"
+#include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
+#include "executor/svariableReceiver.h"
+#include "storage/lock.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+/*
+ * This DestReceiver is used by the LET command for storing the result to
+ * session variable.  The result has to have only one tuple with only one
+ * not deleted attribute.  The row counter (field "rows") is incremented
+ * after receiving of any row, and the error is raised when there are no rows
+ * or there are more than one received rows.  Because received tuple can have
+ * deleted attributes, we need to find wirst not deleted attribute
+ * (field "slot_offset"). The value is detoasted before storing to session
+ * variable.
+ */
+typedef struct
+{
+	DestReceiver pub;
+	Oid			varid;
+	bool		need_detoast;		/* we need detoast attr? */
+	int			slot_offset;		/* position of not deleted attr */
+	int			rows;				/* row counter */
+} SVariableState;
+
+/*
+ * Prepare to receive tuples from executor.
+ */
+static void
+svariableStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+	SVariableState *myState = (SVariableState *) self;
+	int			natts = typeinfo->natts;
+	int			outcols = 0;
+	int			i;
+	LOCKTAG		locktag PG_USED_FOR_ASSERTS_ONLY;
+
+	Assert(myState->pub.mydest == DestVariable);
+	Assert(OidIsValid(myState->varid));
+	Assert(SearchSysCacheExists1(VARIABLEOID, myState->varid));
+
+#ifdef USE_ASSERT_CHECKING
+
+	SET_LOCKTAG_OBJECT(locktag,
+					   MyDatabaseId,
+					   VariableRelationId,
+					   myState->varid,
+					   0);
+
+	Assert(LockHeldByMe(&locktag, AccessShareLock, false));
+
+#endif
+
+	for (i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+		Oid			typid;
+		Oid			collid;
+		int32		typmod;
+
+		if (attr->attisdropped)
+			continue;
+
+		if (++outcols > 1)
+			continue;
+
+		get_session_variable_type_typmod_collid(myState->varid,
+												&typid,
+												&typmod,
+												&collid);
+
+		/*
+		 * double check - the type and typmod of target variable should be
+		 * same as type and typmod of assignment expression. It should be, the
+		 * expression is wrapped by cast to target type and typmod.
+		 */
+		if (attr->atttypid != typid ||
+			(attr->atttypmod >= 0 &&
+			 attr->atttypmod != typmod))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("target session variable is of type %s"
+							" but expression is of type %s",
+							format_type_with_typemod(typid, typmod),
+							format_type_with_typemod(attr->atttypid,
+													 attr->atttypmod))));
+
+		myState->need_detoast = attr->attlen == -1;
+		myState->slot_offset = i;
+	}
+
+	if (outcols != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg_plural("assignment expression returned %d column",
+							   "assignment expression returned %d columns",
+							   outcols,
+							   outcols)));
+
+	myState->rows = 0;
+}
+
+/*
+ * Receive a tuple from the executor and store it in session variable.
+ */
+static bool
+svariableReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
+{
+	SVariableState *myState = (SVariableState *) self;
+	Datum		value;
+	bool		isnull;
+	bool		freeval = false;
+
+	/* Make sure the tuple is fully deconstructed */
+	slot_getallattrs(slot);
+
+	value = slot->tts_values[myState->slot_offset];
+	isnull = slot->tts_isnull[myState->slot_offset];
+
+	if (myState->need_detoast && !isnull && VARATT_IS_EXTERNAL(DatumGetPointer(value)))
+	{
+		value = PointerGetDatum(detoast_external_attr((struct varlena *)
+													  DatumGetPointer(value)));
+		freeval = true;
+	}
+
+	myState->rows += 1;
+
+	if (myState->rows > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_ROWS),
+				 errmsg("expression returned more than one row")));
+
+	SetSessionVariable(myState->varid, value, isnull);
+
+	if (freeval)
+		pfree(DatumGetPointer(value));
+
+	return true;
+}
+
+/*
+ * Clean up at end of an executor run
+ */
+static void
+svariableShutdownReceiver(DestReceiver *self)
+{
+	if (((SVariableState *) self)->rows == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NO_DATA_FOUND),
+				 errmsg("expression returned no rows")));
+}
+
+/*
+ * Destroy receiver when done with it
+ */
+static void
+svariableDestroyReceiver(DestReceiver *self)
+{
+	pfree(self);
+}
+
+/*
+ * Initially create a DestReceiver object.
+ */
+DestReceiver *
+CreateVariableDestReceiver(Oid varid)
+{
+	SVariableState *self = (SVariableState *) palloc0(sizeof(SVariableState));
+
+	self->pub.receiveSlot = svariableReceiveSlot;
+	self->pub.rStartup = svariableStartupReceiver;
+	self->pub.rShutdown = svariableShutdownReceiver;
+	self->pub.rDestroy = svariableDestroyReceiver;
+	self->pub.mydest = DestVariable;
+
+	self->varid = varid;
+
+	return (DestReceiver *) self;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index d2e2af4f811..07fe05deafe 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -4328,6 +4328,16 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_LetStmt:
+			{
+				LetStmt    *stmt = (LetStmt *) node;
+
+				if (WALK(stmt->target))
+					return true;
+				if (WALK(stmt->query))
+					return true;
+			}
+			break;
 		case T_PLAssignStmt:
 			{
 				PLAssignStmt *stmt = (PLAssignStmt *) node;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 948afd90948..230ad1a9a3a 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -323,6 +323,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->lastPlanNodeId = 0;
 	glob->transientPlan = false;
 	glob->dependsOnRole = false;
+	glob->sessionVariables = NIL;
 
 	/*
 	 * Assess whether it's feasible to use parallel mode for this query. We
@@ -557,6 +558,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->paramExecTypes = glob->paramExecTypes;
 	/* utilityStmt should be null, but we might as well copy it */
 	result->utilityStmt = parse->utilityStmt;
+	result->sessionVariables = glob->sessionVariables;
 	result->stmt_location = parse->stmt_location;
 	result->stmt_len = parse->stmt_len;
 
@@ -727,6 +729,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root,
 	 */
 	pull_up_subqueries(root);
 
+	/*
+	 * Check if some subquery uses session variable. Flag hasSessionVariables
+	 * should be true if query or some subquery uses any session variable.
+	 */
+	pull_up_has_session_variables(root);
+
 	/*
 	 * If this is a simple UNION ALL query, flatten it into an appendrel. We
 	 * do this now because it requires applying pull_up_subqueries to the leaf
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 7aed84584c6..3f36697f669 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -209,7 +209,9 @@ static List *set_returning_clause_references(PlannerInfo *root,
 static List *set_windowagg_runcondition_references(PlannerInfo *root,
 												   List *runcondition,
 												   Plan *plan);
-
+static bool pull_up_has_session_variables_walker(Node *node,
+												 PlannerInfo *root);
+static void record_plan_variable_dependency(PlannerInfo *root, Oid varid);
 
 /*****************************************************************************
  *
@@ -1308,6 +1310,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 	return plan;
 }
 
+/*
+ * Search usage of session variables in subqueries
+ */
+void
+pull_up_has_session_variables(PlannerInfo *root)
+{
+	Query	   *query = root->parse;
+
+	if (query->hasSessionVariables)
+	{
+		root->hasSessionVariables = true;
+	}
+	else
+	{
+		(void) query_tree_walker(query,
+								 pull_up_has_session_variables_walker,
+								 (void *) root, 0);
+	}
+}
+
+static bool
+pull_up_has_session_variables_walker(Node *node, PlannerInfo *root)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+
+		if (query->hasSessionVariables)
+		{
+			root->hasSessionVariables = true;
+			return false;
+		}
+
+		/* Recurse into subselects */
+		return query_tree_walker((Query *) node,
+								 pull_up_has_session_variables_walker,
+								 (void *) root, 0);
+	}
+	return expression_tree_walker(node, pull_up_has_session_variables_walker,
+								  (void *) root);
+}
+
 /*
  * set_indexonlyscan_references
  *		Do set_plan_references processing on an IndexOnlyScan
@@ -1956,8 +2002,9 @@ copyVar(Var *var)
  * This is code that is common to all variants of expression-fixing.
  * We must look up operator opcode info for OpExpr and related nodes,
  * add OIDs from regclass Const nodes into root->glob->relationOids, and
- * add PlanInvalItems for user-defined functions into root->glob->invalItems.
- * We also fill in column index lists for GROUPING() expressions.
+ * add PlanInvalItems for user-defined functions and session variables into
+ * root->glob->invalItems.  We also fill in column index lists for GROUPING()
+ * expressions.
  *
  * We assume it's okay to update opcode info in-place.  So this could possibly
  * scribble on the planner's input data structures, but it's OK.
@@ -2047,15 +2094,28 @@ fix_expr_common(PlannerInfo *root, Node *node)
 				g->cols = cols;
 		}
 	}
+	else if (IsA(node, Param))
+	{
+		Param	   *p = (Param *) node;
+
+		if (p->paramkind == PARAM_VARIABLE)
+			record_plan_variable_dependency(root, p->paramvarid);
+	}
 }
 
 /*
  * fix_param_node
  *		Do set_plan_references processing on a Param
+ *		Collect session variables list and replace variable oid by
+ *		index to collected list.
  *
  * If it's a PARAM_MULTIEXPR, replace it with the appropriate Param from
  * root->multiexpr_params; otherwise no change is needed.
  * Just for paranoia's sake, we make a copy of the node in either case.
+ *
+ * If it's a PARAM_VARIABLE, then we collect used session variables in
+ * list root->glob->sessionVariable. We should assign Param paramvarid
+ * too, and it is position of related session variable in mentioned list.
  */
 static Node *
 fix_param_node(PlannerInfo *root, Param *p)
@@ -2074,6 +2134,41 @@ fix_param_node(PlannerInfo *root, Param *p)
 			elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", p->paramid);
 		return copyObject(list_nth(params, colno - 1));
 	}
+
+	if (p->paramkind == PARAM_VARIABLE)
+	{
+		ListCell   *lc;
+		int			n = 0;
+		bool		found = false;
+
+		/* We will modify object */
+		p = (Param *) copyObject(p);
+
+		/*
+		 * Now, we can actualize list of session variables, and we can
+		 * complete paramid parameter.
+		 */
+		foreach(lc, root->glob->sessionVariables)
+		{
+			if (lfirst_oid(lc) == p->paramvarid)
+			{
+				p->paramid = n;
+				found = true;
+				break;
+			}
+			n += 1;
+		}
+
+		if (!found)
+		{
+			root->glob->sessionVariables = lappend_oid(root->glob->sessionVariables,
+													   p->paramvarid);
+			p->paramid = n;
+		}
+
+		return (Node *) p;
+	}
+
 	return (Node *) copyObject(p);
 }
 
@@ -2135,7 +2230,10 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
  * replacing Aggref nodes that should be replaced by initplan output Params,
  * choosing the best implementation for AlternativeSubPlans,
  * looking up operator opcode info for OpExpr and related nodes,
- * and adding OIDs from regclass Const nodes into root->glob->relationOids.
+ * adding OIDs from regclass Const nodes into root->glob->relationOids,
+ * and assigning paramvarid to PARAM_VARIABLE params, and collecting
+ * of OIDs of session variables in root->glob->sessionVariables list
+ * (paramvarid is an position of related session variable in this list).
  *
  * 'node': the expression to be modified
  * 'rtoffset': how much to increment varnos by
@@ -2157,7 +2255,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
 		root->multiexpr_params != NIL ||
 		root->glob->lastPHId != 0 ||
 		root->minmax_aggs != NIL ||
-		root->hasAlternativeSubPlans)
+		root->hasAlternativeSubPlans ||
+		root->hasSessionVariables)
 	{
 		return fix_scan_expr_mutator(node, &context);
 	}
@@ -3520,6 +3619,25 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
 	}
 }
 
+/*
+ * Record dependency on a session variable. The variable can be used as a
+ * session variable in expression list, or target of LET statement.
+ */
+static void
+record_plan_variable_dependency(PlannerInfo *root, Oid varid)
+{
+	PlanInvalItem *inval_item = makeNode(PlanInvalItem);
+
+	/* paramid is still session variable id */
+	inval_item->cacheId = VARIABLEOID;
+	inval_item->hashValue = GetSysCacheHashValue1(VARIABLEOID,
+												  ObjectIdGetDatum(varid));
+
+	/* Append this variable to global, register dependency */
+	root->glob->invalItems = lappend(root->glob->invalItems,
+									 inval_item);
+}
+
 /*
  * extract_query_dependencies
  *		Given a rewritten, but not yet planned, query or queries
@@ -3605,9 +3723,9 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 			}
 
 			/*
-			 * Ignore other utility statements, except those (such as EXPLAIN)
-			 * that contain a parsed-but-not-planned query.  For those, we
-			 * just need to transfer our attention to the contained query.
+			 * Ignore other utility statements, except those (such as EXPLAIN
+			 * or LET) that contain a parsed-but-not-planned query.  For those,
+			 * we just need to transfer our attention to the contained query.
 			 */
 			query = UtilityContainsQuery(query->utilityStmt);
 			if (query == NULL)
@@ -3630,6 +3748,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 					lappend_oid(context->glob->relationOids, rte->relid);
 		}
 
+		/* Record dependency on targer variable of LET command */
+		if (OidIsValid(query->resultVariable))
+			record_plan_variable_dependency(context, query->resultVariable);
+
 		/* And recurse into the query's subexpressions */
 		return query_tree_walker(query, extract_query_dependencies_walker,
 								 (void *) context, 0);
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 969e257f70b..a22ea037299 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1314,6 +1314,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	/* If subquery had any RLS conditions, now main query does too */
 	parse->hasRowSecurity |= subquery->hasRowSecurity;
 
+	/* If subquery had session variables, now main query does too */
+	parse->hasSessionVariables |= subquery->hasSessionVariables;
+
 	/*
 	 * subquery won't be pulled up if it hasAggs, hasWindowFuncs, or
 	 * hasTargetSRFs, so no work needed on those flags
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index b4e085e9d4b..1b1fb6ecf81 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -24,6 +24,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/session_variable.h"
 #include "executor/executor.h"
 #include "executor/functions.h"
 #include "funcapi.h"
@@ -935,6 +936,13 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 		if (param->paramkind == PARAM_EXTERN)
 			return false;
 
+		/* We doesn't support passing session variables to workers */
+		if (param->paramkind == PARAM_VARIABLE)
+		{
+			if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+				return true;
+		}
+
 		if (param->paramkind != PARAM_EXEC ||
 			!list_member_int(context->safe_param_ids, param->paramid))
 		{
@@ -2389,6 +2397,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
  *	  value of the Param.
  * 2. Fold stable, as well as immutable, functions to constants.
  * 3. Reduce PlaceHolderVar nodes to their contained expressions.
+ * 4. Current value of session variable can be used for estimation too.
  *--------------------
  */
 Node *
@@ -2515,6 +2524,29 @@ eval_const_expressions_mutator(Node *node,
 						}
 					}
 				}
+				else if (param->paramkind == PARAM_VARIABLE &&
+						 context->estimate)
+				{
+					int16		typLen;
+					bool		typByVal;
+					Datum		pval;
+					bool		isnull;
+
+					get_typlenbyval(param->paramtype,
+									&typLen, &typByVal);
+
+					pval = GetSessionVariableWithTypeCheck(param->paramvarid,
+														   &isnull,
+														   param->paramtype);
+
+					return (Node *) makeConst(param->paramtype,
+											  param->paramtypmod,
+											  param->paramcollid,
+											  (int) typLen,
+											  pval,
+											  isnull,
+											  typByVal);
+				}
 
 				/*
 				 * Not replaceable, so just copy the Param (no need to
@@ -4706,7 +4738,8 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 		querytree->limitOffset ||
 		querytree->limitCount ||
 		querytree->setOperations ||
-		list_length(querytree->targetList) != 1)
+		(list_length(querytree->targetList) != 1) ||
+		querytree->hasSessionVariables)
 		goto fail;
 
 	/* If the function result is composite, resolve it */
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 28fed9d87f6..2c0a66fb2cc 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,9 +25,12 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/defrem.h"
+#include "commands/session_variable.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -50,6 +53,7 @@
 #include "parser/parsetree.h"
 #include "utils/backend_status.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
@@ -82,6 +86,8 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 										 CreateTableAsStmt *stmt);
 static Query *transformCallStmt(ParseState *pstate,
 								CallStmt *stmt);
+static Query *transformLetStmt(ParseState *pstate,
+							   LetStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 								   LockingClause *lc, bool pushedDown);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
@@ -325,6 +331,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
 		case T_UpdateStmt:
 		case T_DeleteStmt:
 		case T_MergeStmt:
+		case T_LetStmt:
 			(void) test_raw_expression_coverage(parseTree, NULL);
 			break;
 		default:
@@ -403,6 +410,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
 									   (CallStmt *) parseTree);
 			break;
 
+		case T_LetStmt:
+			result = transformLetStmt(pstate,
+									  (LetStmt *) parseTree);
+			break;
+
 		default:
 
 			/*
@@ -454,6 +466,7 @@ stmt_requires_parse_analysis(RawStmt *parseTree)
 		case T_SelectStmt:
 		case T_ReturnStmt:
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			result = true;
 			break;
 
@@ -562,6 +575,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasAggs = pstate->p_hasAggs;
+	qry->hasSessionVariables = pstate->p_hasSessionVariables;
 
 	assign_query_collations(pstate, qry);
 
@@ -988,6 +1002,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
+	qry->hasSessionVariables = pstate->p_hasSessionVariables;
 
 	assign_query_collations(pstate, qry);
 
@@ -1453,6 +1468,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasAggs = pstate->p_hasAggs;
+	qry->hasSessionVariables = pstate->p_hasSessionVariables;
 
 	foreach(l, stmt->lockingClause)
 	{
@@ -1679,12 +1695,247 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
+	qry->hasSessionVariables = pstate->p_hasSessionVariables;
 
 	assign_query_collations(pstate, qry);
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+ *	  transform an Let Statement
+ */
+static Query *
+transformLetStmt(ParseState *pstate, LetStmt *stmt)
+{
+	Query	   *query;
+	Query	   *result;
+	List	   *exprList = NIL;
+	List	   *exprListCoer = NIL;
+	ListCell   *lc;
+	ListCell   *indirection_head = NULL;
+	Query	   *selectQuery;
+	Oid			varid;
+	char	   *attrname = NULL;
+	bool		not_unique;
+	bool		is_rowtype;
+	Oid			typid;
+	int32		typmod;
+	Oid			collid;
+	AclResult	aclresult;
+	List	   *names = NULL;
+	int			indirection_start;
+	int			i = 0;
+
+	/* There can't be any outer WITH to worry about */
+	Assert(pstate->p_ctenamespace == NIL);
+
+	names = NamesFromList(stmt->target);
+
+	/*
+	 * The AccessShareLock is created on related session variable. The lock
+	 * will be kept for the whole transaction.
+	 */
+	varid = IdentifyVariable(names, &attrname, &not_unique, false);
+	if (not_unique)
+		ereport(ERROR,
+				(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+				 errmsg("target \"%s\" of LET command is ambiguous",
+						NameListToString(names)),
+				 parser_errposition(pstate, stmt->location)));
+
+	if (!OidIsValid(varid))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("session variable \"%s\" doesn't exist",
+						NameListToString(names)),
+				 parser_errposition(pstate, stmt->location)));
+
+	/*
+	 * Calculate start of possible position of an indirection in list, and
+	 * when it is inside the list, store pointer on first node of indirection.
+	 */
+	indirection_start = list_length(names) - (attrname ? 1 : 0);
+	if (list_length(stmt->target) > indirection_start)
+		indirection_head = list_nth_cell(stmt->target, indirection_start);
+
+	get_session_variable_type_typmod_collid(varid, &typid, &typmod, &collid);
+
+	is_rowtype = type_is_rowtype(typid);
+
+	if (attrname && !is_rowtype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("type \"%s\" of target session variable \"%s.%s\" is not a composite type",
+						format_type_be(typid),
+						get_namespace_name(get_session_variable_namespace(varid)),
+						get_session_variable_name(varid)),
+				 parser_errposition(pstate, stmt->location)));
+
+	aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, NameListToString(names));
+
+	/*
+	 * The behavior of EXPR_KIND_LET_TARGET is almost same like
+	 * EXPR_KIND_UPDATE_TARGET. EXPR_KIND_LET_TARGET was introduced to be
+	 * possible to raise more correct errors, where the command LET is
+	 * mentioned (instead UPDATE command).
+	 */
+	pstate->p_expr_kind = EXPR_KIND_LET_TARGET;
+
+	/* We need to postpone conversion of unknown-type to text */
+	pstate->p_resolve_unknowns = false;
+
+	selectQuery = transformStmt(pstate, stmt->query);
+
+	/* The grammar should have produced a SELECT */
+	Assert(IsA(selectQuery, Query) && selectQuery->commandType == CMD_SELECT);
+
+	/*----------
+	 * Generate an expression list for the LET that selects all the
+	 * non-resjunk columns from the subquery.
+	 *----------
+	 */
+	exprList = NIL;
+	foreach(lc, selectQuery->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+		if (tle->resjunk)
+			continue;
+
+		exprList = lappend(exprList, tle->expr);
+	}
+
+	/* don't allow multicolumn result */
+	if (list_length(exprList) != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg_plural("assignment expression returned %d column",
+							   "assignment expression returned %d columns",
+							   list_length(exprList),
+							   list_length(exprList)),
+				 parser_errposition(pstate,
+									exprLocation((Node *) exprList))));
+
+	exprListCoer = NIL;
+
+	foreach(lc, exprList)
+	{
+		Expr	   *expr = (Expr *) lfirst(lc);
+		Expr	   *coerced_expr;
+		Param	   *param;
+		Oid			exprtypid;
+
+		/* now we can read type of expression */
+		exprtypid = exprType((Node *) expr);
+
+		param = makeNode(Param);
+		param->paramkind = PARAM_VARIABLE;
+		param->paramvarid = varid;
+		param->paramtype = typid;
+		param->paramtypmod = typmod;
+
+		if (indirection_head)
+		{
+			bool		targetIsArray;
+			char	   *targetName;
+
+			targetName = get_session_variable_name(varid);
+			targetIsArray = OidIsValid(get_element_type(typid));
+
+			pstate->p_hasSessionVariables = true;
+
+			coerced_expr = (Expr *)
+				transformAssignmentIndirection(pstate,
+											   (Node *) param,
+											   targetName,
+											   targetIsArray,
+											   typid,
+											   typmod,
+											   InvalidOid,
+											   stmt->target,
+											   indirection_head,
+											   (Node *) expr,
+											   COERCION_ASSIGNMENT,
+											   stmt->location);
+		}
+		else
+			coerced_expr = (Expr *)
+				coerce_to_target_type(pstate,
+									  (Node *) expr,
+									  exprtypid,
+									  typid, typmod,
+									  COERCION_ASSIGNMENT,
+									  COERCE_IMPLICIT_CAST,
+									  stmt->location);
+
+		if (coerced_expr == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("variable \"%s.%s\" is of type %s,"
+							" but expression is of type %s",
+							get_namespace_name(get_session_variable_namespace(varid)),
+							get_session_variable_name(varid),
+							format_type_be(typid),
+							format_type_be(exprtypid)),
+					 errhint("You will need to rewrite or cast the expression."),
+					 parser_errposition(pstate, exprLocation((Node *) expr))));
+
+		exprListCoer = lappend(exprListCoer, coerced_expr);
+	}
+
+	/*
+	 * Generate query's target list using the computed list of expressions.
+	 */
+	query = makeNode(Query);
+	query->commandType = CMD_SELECT;
+
+	foreach(lc, exprListCoer)
+	{
+		Expr	   *expr = (Expr *) lfirst(lc);
+		TargetEntry *tle;
+
+		tle = makeTargetEntry(expr,
+							  i + 1,
+							  FigureColname((Node *) expr),
+							  false);
+		query->targetList = lappend(query->targetList, tle);
+	}
+
+	/* done building the range table and jointree */
+	query->rtable = pstate->p_rtable;
+	query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+	query->hasTargetSRFs = pstate->p_hasTargetSRFs;
+	query->hasSubLinks = pstate->p_hasSubLinks;
+	query->hasSessionVariables = pstate->p_hasSessionVariables;
+
+	/* This is top query */
+	query->canSetTag = true;
+
+	/*
+	 * Save target session variable id. This value is used multiple times: by
+	 * query rewriter (for getting related defexpr), by planner (for acquiring
+	 * AccessShareLock on target variable), and by executor (we need to know
+	 * target variable id).
+	 */
+	query->resultVariable = varid;
+
+	assign_query_collations(pstate, query);
+
+	stmt->query = (Node *) query;
+
+	/* represent the command as a utility Query */
+	result = makeNode(Query);
+	result->commandType = CMD_UTILITY;
+	result->utilityStmt = (Node *) stmt;
+
+	return result;
+}
+
 /*
  * transformSetOperationStmt -
  *	  transforms a set-operations tree
@@ -1930,6 +2181,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasAggs = pstate->p_hasAggs;
+	qry->hasSessionVariables = pstate->p_hasSessionVariables;
 
 	foreach(l, lockingClause)
 	{
@@ -2404,6 +2656,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasAggs = pstate->p_hasAggs;
+	qry->hasSessionVariables = pstate->p_hasSessionVariables;
 
 	assign_query_collations(pstate, qry);
 
@@ -2471,6 +2724,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
+	qry->hasSessionVariables = pstate->p_hasSessionVariables;
 
 	assign_query_collations(pstate, qry);
 
@@ -2658,9 +2912,15 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 	/*
 	 * Transform the target reference.  Typically we will get back a Param
 	 * node, but there's no reason to be too picky about its type.
+	 *
+	 * The session variables should not be used as target of PL/pgSQL assign
+	 * statement. So we should to use dedicated expression kind, and disallow
+	 * session variables there. The dedicated context allows to eliminate
+	 * false alarm warnings related to possibility of target PL/pgSQL variable
+	 * shadows a session variable.
 	 */
 	target = transformExpr(pstate, (Node *) cref,
-						   EXPR_KIND_UPDATE_TARGET);
+						   EXPR_KIND_ASSIGN_TARGET);
 	targettype = exprType(target);
 	targettypmod = exprTypmod(target);
 	targetcollation = exprCollation(target);
@@ -2702,6 +2962,10 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 	 */
 	type_id = exprType((Node *) tle->expr);
 
+	/*
+	 * For indirection processing and additional casts we can use expr_kind
+	 * EXPR_KIND_UPDATE_TARGET
+	 */
 	pstate->p_expr_kind = EXPR_KIND_UPDATE_TARGET;
 
 	if (indirection)
@@ -2844,6 +3108,8 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 							   (LockingClause *) lfirst(l), false);
 	}
 
+	qry->hasSessionVariables = pstate->p_hasSessionVariables;
+
 	assign_query_collations(pstate, qry);
 
 	/* this must be done after collations, for reliable comparison of exprs */
@@ -3117,6 +3383,14 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 							 true,
 							 stmt->funccall->location);
 
+	/*
+	 * The arguments of CALL statement are evaluated by direct
+	 * expression executor call. This path is unsupported yet,
+	 * so block it. It will be enabled by some other patch.
+	 */
+	if (pstate->p_hasSessionVariables)
+		elog(ERROR, "session variable cannot be used as an argument");
+
 	assign_expr_collations(pstate, node);
 
 	fexpr = castNode(FuncExpr, node);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 57b5f6882b1..f5367369cf7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -307,7 +307,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropTransformStmt
 		DropUserMappingStmt ExplainStmt FetchStmt
 		GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
-		ListenStmt LoadStmt LockStmt MergeStmt NotifyStmt ExplainableStmt PreparableStmt
+		LetStmt ListenStmt LoadStmt LockStmt MergeStmt NotifyStmt ExplainableStmt PreparableStmt
 		CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
 		RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
 		RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@@ -457,6 +457,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				TriggerTransitions TriggerReferencing
 				vacuum_relation_list opt_vacuum_relation_list
 				drop_option_list pub_obj_list
+				let_target
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
@@ -749,7 +750,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEEP KEY KEYS
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
-	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+	LEADING LEAKPROOF LEAST LEFT LET LEVEL LIKE LIMIT LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
@@ -1100,6 +1101,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -12768,6 +12770,47 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY:
+ *				LET STATEMENTS
+ *
+ *****************************************************************************/
+LetStmt:	LET let_target '=' a_expr
+				{
+					LetStmt	   *n = makeNode(LetStmt);
+					SelectStmt *select;
+					ResTarget  *res;
+
+					n->target = $2;
+
+					select = makeNode(SelectStmt);
+					res = makeNode(ResTarget);
+
+					/* Create target list for implicit query */
+					res->name = NULL;
+					res->indirection = NIL;
+					res->val = (Node *) $4;
+					res->location = @4;
+
+					select->targetList = list_make1(res);
+					n->query = (Node *) select;
+
+					n->location = @2;
+					$$ = (Node *) n;
+				}
+		;
+
+let_target:
+			ColId opt_indirection
+				{
+					$$ = list_make1(makeString($1));
+					if ($2)
+						  $$ = list_concat($$,
+										   check_indirection($2, yyscanner));
+				}
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -17799,6 +17842,7 @@ unreserved_keyword:
 			| LARGE_P
 			| LAST_P
 			| LEAKPROOF
+			| LET
 			| LEVEL
 			| LISTEN
 			| LOAD
@@ -18412,6 +18456,7 @@ bare_label_keyword:
 			| LEAKPROOF
 			| LEAST
 			| LEFT
+			| LET
 			| LEVEL
 			| LIKE
 			| LISTEN
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index bee7d8346a3..b494c885e9f 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -577,6 +577,11 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			errkind = true;
 			break;
 
+		case EXPR_KIND_ASSIGN_TARGET:
+		case EXPR_KIND_LET_TARGET:
+			errkind = true;
+			break;
+
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
@@ -967,6 +972,10 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_ASSIGN_TARGET:
+		case EXPR_KIND_LET_TARGET:
+			errkind = true;
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 8577f278065..73be2a8e10a 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -18,6 +18,7 @@
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -34,18 +35,19 @@
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
 #include "parser/parse_type.h"
+#include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
 #include "utils/fmgroids.h"
 #include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 /* GUC parameters */
 bool		Transform_null_equals = false;
 
-
 static Node *transformExprRecurse(ParseState *pstate, Node *expr);
 static Node *transformParamRef(ParseState *pstate, ParamRef *pref);
 static Node *transformAExprOp(ParseState *pstate, A_Expr *a);
@@ -108,6 +110,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,
 							  Node *ltree, Node *rtree, int location);
 static Node *make_nulltest_from_distinct(ParseState *pstate,
 										 A_Expr *distincta, Node *arg);
+static Node *makeParamSessionVariable(ParseState *pstate,
+									  Oid varid, Oid typid, int32 typmod, Oid collid,
+									  char *attrname, int location);

 /*
@@ -501,6 +506,89 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	return result;
 }
 
+/*
+ * Returns true if the given expression kind is valid for session variables
+ * Session variables can be used everywhere where external parameters can be
+ * used. Session variables are not allowed in DDL commands or in constraints.
+ *
+ * An identifier can be parsed as a session variable only for expression kinds
+ * where session variables are allowed. This is the primary usage of this
+ * function.
+ *
+ * Second usage of this function is to decide whether "column does not exist" or
+ * "column or variable does not exist" error message should be printed.
+ * When we are in an expression where session variables cannot be used, we raise
+ * the first form of error message.
+ */
+static bool
+expr_kind_allows_session_variables(ParseExprKind p_expr_kind)
+{
+	bool		result = false;
+
+	switch (p_expr_kind)
+	{
+		case EXPR_KIND_NONE:
+			Assert(false);		/* can't happen */
+			return false;
+
+			/* allow */
+		case EXPR_KIND_OTHER:
+		case EXPR_KIND_JOIN_ON:
+		case EXPR_KIND_FROM_SUBSELECT:
+		case EXPR_KIND_FROM_FUNCTION:
+		case EXPR_KIND_WHERE:
+		case EXPR_KIND_HAVING:
+		case EXPR_KIND_FILTER:
+		case EXPR_KIND_WINDOW_PARTITION:
+		case EXPR_KIND_WINDOW_ORDER:
+		case EXPR_KIND_WINDOW_FRAME_RANGE:
+		case EXPR_KIND_WINDOW_FRAME_ROWS:
+		case EXPR_KIND_WINDOW_FRAME_GROUPS:
+		case EXPR_KIND_SELECT_TARGET:
+		case EXPR_KIND_INSERT_TARGET:
+		case EXPR_KIND_UPDATE_SOURCE:
+		case EXPR_KIND_UPDATE_TARGET:
+		case EXPR_KIND_MERGE_WHEN:
+		case EXPR_KIND_MERGE_RETURNING:
+		case EXPR_KIND_GROUP_BY:
+		case EXPR_KIND_ORDER_BY:
+		case EXPR_KIND_DISTINCT_ON:
+		case EXPR_KIND_LIMIT:
+		case EXPR_KIND_OFFSET:
+		case EXPR_KIND_RETURNING:
+		case EXPR_KIND_VALUES:
+		case EXPR_KIND_VALUES_SINGLE:
+		case EXPR_KIND_ALTER_COL_TRANSFORM:
+		case EXPR_KIND_EXECUTE_PARAMETER:
+		case EXPR_KIND_POLICY:
+		case EXPR_KIND_CALL_ARGUMENT:
+		case EXPR_KIND_COPY_WHERE:
+		case EXPR_KIND_LET_TARGET:
+			result = true;
+			break;
+
+			/* not allow */
+		case EXPR_KIND_CHECK_CONSTRAINT:
+		case EXPR_KIND_DOMAIN_CHECK:
+		case EXPR_KIND_COLUMN_DEFAULT:
+		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_INDEX_EXPRESSION:
+		case EXPR_KIND_INDEX_PREDICATE:
+		case EXPR_KIND_STATS_EXPRESSION:
+		case EXPR_KIND_TRIGGER_WHEN:
+		case EXPR_KIND_PARTITION_BOUND:
+		case EXPR_KIND_PARTITION_EXPRESSION:
+		case EXPR_KIND_GENERATED_COLUMN:
+		case EXPR_KIND_JOIN_USING:
+		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_ASSIGN_TARGET:
+			result = false;
+			break;
+	}
+
+	return result;
+}
+
 /*
  * Transform a ColumnRef.
  *
@@ -577,6 +665,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_COPY_WHERE:
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_ASSIGN_TARGET:
+		case EXPR_KIND_LET_TARGET:
 			/* okay */
 			break;
 
@@ -848,6 +938,64 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 					 parser_errposition(pstate, cref->location)));
 	}
 
+	/*
+	 * There are contexts where session's variables are not allowed. The
+	 * question is if we want to identify session's variables in these
+	 * contexts? The code can be more simple, when we don't do it, but then we
+	 * cannot to raise maybe useful message like "you cannot to use session
+	 * variables here".
+	 */
+	if (expr_kind_allows_session_variables(pstate->p_expr_kind))
+	{
+		Oid			varid = InvalidOid;
+		char	   *attrname = NULL;
+		bool		not_unique;
+
+		/*
+		 * Session variables are shadowed by columns, routine's variables or
+		 * routine's arguments. We don't want to use session variable
+		 * when it is not exactly shadowed, but RTE like this is valid:
+		 *
+		 * CREATE TYPE T AS (c int); CREATE VARIABLE foo AS T; CREATE TABLE
+		 * foo(a int, b int);
+		 *
+		 * SELECT foo.a, foo.b, foo.c FROM foo;
+		 *
+		 * This case can be messy and then we disallow it. When we know that a
+		 * possible variable will be shadowed, we don't try to identify
+		 * variable.
+		 */
+		if (!node && !(relname && crerr == CRERR_NO_COLUMN))
+		{
+			/*
+			 * The AccessShareLock is created on related session variable. The
+			 * lock will be kept for the whole transaction.
+			 */
+			varid = IdentifyVariable(cref->fields, &attrname, &not_unique, false);
+
+			if (OidIsValid(varid))
+			{
+				Oid			typid;
+				int32		typmod;
+				Oid			collid;
+
+				if (not_unique)
+					ereport(ERROR,
+							(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+							 errmsg("session variable reference \"%s\" is ambiguous",
+									NameListToString(cref->fields)),
+							 parser_errposition(pstate, cref->location)));
+
+				get_session_variable_type_typmod_collid(varid, &typid, &typmod,
+														&collid);
+
+				node = makeParamSessionVariable(pstate,
+												varid, typid, typmod, collid,
+												attrname, cref->location);
+			}
+		}
+	}
+
 	/*
 	 * Throw error if no translation found.
 	 */
@@ -882,6 +1030,72 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
+ * Generate param variable for reference to session variable
+ */
+static Node *
+makeParamSessionVariable(ParseState *pstate,
+						 Oid varid, Oid typid, int32 typmod, Oid collid,
+						 char *attrname, int location)
+{
+	Param	   *param;
+
+	param = makeNode(Param);
+
+	param->paramkind = PARAM_VARIABLE;
+	param->paramvarid = varid;
+	param->paramtype = typid;
+	param->paramtypmod = typmod;
+	param->paramcollid = collid;
+
+	pstate->p_hasSessionVariables = true;
+
+	if (attrname != NULL)
+	{
+		TupleDesc	tupdesc;
+		int			i;
+
+		tupdesc = lookup_rowtype_tupdesc_noerror(typid, typmod, true);
+		if (!tupdesc)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("variable \"%s.%s\" is of type \"%s\", which is not a composite type",
+							get_namespace_name(get_session_variable_namespace(varid)),
+							get_session_variable_name(varid),
+							format_type_be(typid)),
+					 parser_errposition(pstate, location)));
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+			if (strcmp(attrname, NameStr(att->attname)) == 0 &&
+				!att->attisdropped)
+			{
+				/* Success, so generate a FieldSelect expression */
+				FieldSelect *fselect = makeNode(FieldSelect);
+
+				fselect->arg = (Expr *) param;
+				fselect->fieldnum = i + 1;
+				fselect->resulttype = att->atttypid;
+				fselect->resulttypmod = att->atttypmod;
+				/* save attribute's collation for parse_collate.c */
+				fselect->resultcollid = att->attcollation;
+
+				ReleaseTupleDesc(tupdesc);
+				return (Node *) fselect;
+			}
+		}
+
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("could not identify column \"%s\" in variable", attrname),
+				 parser_errposition(pstate, location)));
+	}
+
+	return (Node *) param;
+}
+
 static Node *
 transformParamRef(ParseState *pstate, ParamRef *pref)
 {
@@ -1816,6 +2030,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_VALUES:
 		case EXPR_KIND_VALUES_SINGLE:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_LET_TARGET:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -1859,6 +2074,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_GENERATED_COLUMN:
 			err = _("cannot use subquery in column generation expression");
 			break;
+		case EXPR_KIND_ASSIGN_TARGET:
+			err = _("cannot use subquery as target of assign statement");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3196,6 +3414,10 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_ASSIGN_TARGET:
+			return "ASSIGN";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 9b23344a3b1..9aa4f60768b 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2656,6 +2656,8 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 			err = _("set-returning functions are not allowed in column generation expressions");
 			break;
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_ASSIGN_TARGET:
+		case EXPR_KIND_LET_TARGET:
 			errkind = true;
 			break;
 
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 96f80b30463..dac78ba5cce 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -38,6 +38,7 @@
 #include "executor/functions.h"
 #include "executor/tqueue.h"
 #include "executor/tstoreReceiver.h"
+#include "executor/svariableReceiver.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 
@@ -155,6 +156,9 @@ CreateDestReceiver(CommandDest dest)
 
 		case DestExplainSerialize:
 			return CreateExplainSerializeDestReceiver(NULL);
+
+		case DestVariable:
+			return CreateVariableDestReceiver(InvalidOid);
 	}
 
 	/* should never get here */
@@ -191,6 +195,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
 		case DestTransientRel:
 		case DestTupleQueue:
 		case DestExplainSerialize:
+		case DestVariable:
 			break;
 	}
 }
@@ -237,6 +242,7 @@ NullCommand(CommandDest dest)
 		case DestTransientRel:
 		case DestTupleQueue:
 		case DestExplainSerialize:
+		case DestVariable:
 			break;
 	}
 }
@@ -281,6 +287,7 @@ ReadyForQuery(CommandDest dest)
 		case DestTransientRel:
 		case DestTupleQueue:
 		case DestExplainSerialize:
+		case DestVariable:
 			break;
 	}
 }
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 0c45fcf318f..85b809cb071 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -86,6 +86,9 @@ CreateQueryDesc(PlannedStmt *plannedstmt,
 	qd->queryEnv = queryEnv;
 	qd->instrument_options = instrument_options;	/* instrumentation wanted? */
 
+	qd->num_session_variables = 0;
+	qd->session_variables = NULL;
+
 	/* null these fields until set by ExecutorStart */
 	qd->tupDesc = NULL;
 	qd->estate = NULL;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ce8db7cda43..662dbcd1dd6 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -49,6 +49,7 @@
 #include "commands/schemacmds.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
+#include "commands/session_variable.h"
 #include "commands/subscriptioncmds.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -235,6 +236,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 				 * Commands inside the DO block or the called procedure might
@@ -1064,6 +1066,11 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_LetStmt:
+			ExecuteLetStmt(pstate, (LetStmt *) parsetree, params,
+						   queryEnv, qc);
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2203,6 +2210,10 @@ UtilityContainsQuery(Node *parsetree)
 				return UtilityContainsQuery(qry->utilityStmt);
 			return qry;
 
+		case T_LetStmt:
+			qry = castNode(Query, ((LetStmt *) parsetree)->query);
+			return qry;
+
 		default:
 			return NULL;
 	}
@@ -2401,6 +2412,10 @@ CreateCommandTag(Node *parsetree)
 			tag = CMDTAG_SELECT;
 			break;
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -3286,6 +3301,7 @@ GetCommandLogLevel(Node *parsetree)
 			break;
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 653685bffc5..a0edd5e0493 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -37,6 +37,7 @@
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/defrem.h"
 #include "commands/tablespace.h"
 #include "common/keywords.h"
@@ -519,6 +520,7 @@ static char *generate_function_name(Oid funcid, int nargs,
 static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
 static void add_cast_to(StringInfo buf, Oid typid);
 static char *generate_qualified_type_name(Oid typid);
+static char *generate_session_variable_name(Oid varid);
 static text *string_to_text(char *str);
 static char *flatten_reloptions(Oid relid);
 static void get_reloptions(StringInfo buf, Datum reloptions);
@@ -8351,6 +8353,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
+	/* translate paramvarid to session variable name */
+	if (param->paramkind == PARAM_VARIABLE)
+	{
+		appendStringInfo(context->buf, "%s",
+						 generate_session_variable_name(param->paramvarid));
+		return;
+	}
+
 	/*
 	 * Alternatively, maybe it's a subplan output, which we print as a
 	 * reference to the subplan.  (We could drill down into the subplan and
@@ -13131,6 +13141,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*
+ * generate_session_variable_name
+ *		Compute the name to display for a session variable specified by OID
+ *
+ * The result includes all necessary quoting and schema-prefixing.
+ */
+static char *
+generate_session_variable_name(Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	char	   *varname;
+	char	   *nspname;
+	char	   *result;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	varname = NameStr(varform->varname);
+
+	if (!VariableIsVisible(varid))
+		nspname = get_namespace_name_or_temp(varform->varnamespace);
+	else
+		nspname = NULL;
+
+	result = quote_qualified_identifier(nspname, varname);
+
+	ReleaseSysCache(tup);
+
+	return result;
+}
+
 /*
  * Given a C string, produce a TEXT datum.
  *
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 5af1a168ec2..92284f77c78 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -58,6 +58,7 @@
 
 #include "access/transam.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_variable.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -162,6 +163,7 @@ InitPlanCache(void)
 	CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
 	CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheSysCallback, (Datum) 0);
 	CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheSysCallback, (Datum) 0);
+	CacheRegisterSyscacheCallback(VARIABLEOID, PlanCacheObjectCallback, (Datum) 0);
 }
 
 /*
@@ -1903,18 +1905,33 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
 
 	/*
 	 * Recurse into sublink subqueries, too.  But we already did the ones in
-	 * the rtable and cteList.
+	 * the rtable and cteList. We need to force recursive call for session
+	 * variables too, to find and lock variables used in query (see
+	 * ScanQueryWalker).
 	 */
-	if (parsetree->hasSubLinks)
+	if (parsetree->hasSubLinks ||
+		parsetree->hasSessionVariables)
 	{
 		query_tree_walker(parsetree, ScanQueryWalker,
 						  (void *) &acquire,
 						  QTW_IGNORE_RC_SUBQUERIES);
 	}
+
+	/* Process session variables */
+	if (OidIsValid(parsetree->resultVariable))
+	{
+		if (acquire)
+			LockDatabaseObject(VariableRelationId, parsetree->resultVariable,
+							   0, AccessShareLock);
+		else
+			UnlockDatabaseObject(VariableRelationId, parsetree->resultVariable,
+								 0, AccessShareLock);
+	}
 }
 
 /*
- * Walker to find sublink subqueries for ScanQueryForLocks
+ * Walker to find sublink subqueries or referenced session variables
+ * for ScanQueryForLocks
  */
 static bool
 ScanQueryWalker(Node *node, bool *acquire)
@@ -1929,6 +1946,20 @@ ScanQueryWalker(Node *node, bool *acquire)
 		ScanQueryForLocks(castNode(Query, sub->subselect), *acquire);
 		/* Fall through to process lefthand args of SubLink */
 	}
+	else if (IsA(node, Param))
+	{
+		Param	   *p = (Param *) node;
+
+		if (p->paramkind == PARAM_VARIABLE)
+		{
+			if (acquire)
+				LockDatabaseObject(VariableRelationId, p->paramvarid,
+								   0, AccessShareLock);
+			else
+				UnlockDatabaseObject(VariableRelationId, p->paramvarid,
+									 0, AccessShareLock);
+		}
+	}
 
 	/*
 	 * Do NOT recurse into Query nodes, because ScanQueryForLocks already
@@ -2060,7 +2091,9 @@ PlanCacheRelCallback(Datum arg, Oid relid)
 
 /*
  * PlanCacheObjectCallback
- *		Syscache inval callback function for PROCOID and TYPEOID caches
+ *		Syscache inval callback function for TYPEOID, PROCOID, NAMESPACEOID,
+ * OPEROID, AMOPOPID, FOREIGNSERVEROID, FOREIGNDATAWRAPPEROID and VARIABLEOID
+ * caches.
  *
  * Invalidate all plans mentioning the object with the specified hash value,
  * or all plans mentioning any member of this cache if hashvalue == 0.
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index e48a86be54b..eaae0c5a933 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -2026,9 +2026,13 @@ get_call_expr_arg_stable(Node *expr, int argnum)
 	 */
 	if (IsA(arg, Const))
 		return true;
-	if (IsA(arg, Param) &&
-		((Param *) arg)->paramkind == PARAM_EXTERN)
-		return true;
+	if (IsA(arg, Param))
+	{
+		Param	   *p = (Param *) arg;
+
+		if (p->paramkind == PARAM_EXTERN || p->paramkind == PARAM_VARIABLE)
+			return true;
+	}
 
 	return false;
 }
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 70e1969963d..c7e89487c72 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1709,8 +1709,8 @@ psql_completion(const char *text, int start, int end)
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CALL", "CHECKPOINT", "CLOSE", "CLUSTER",
 		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
 		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
-		"FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LISTEN", "LOAD", "LOCK",
-		"MERGE INTO", "MOVE", "NOTIFY", "PREPARE",
+		"FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LET",
+		"LISTEN", "LOAD", "LOCK", "MERGE INTO", "MOVE", "NOTIFY", "PREPARE",
 		"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
 		"RESET", "REVOKE", "ROLLBACK",
 		"SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
@@ -4288,6 +4288,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("VALUES") && !TailMatches("DEFAULT", "VALUES"))
 		COMPLETE_WITH("(");
 
+/* LET */
+	/* If prev. word is LET suggest a list of variables */
+	else if (Matches("LET"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
+	/* Complete LET <variable> with "=" */
+	else if (TailMatches("LET", MatchAny))
+		COMPLETE_WITH("=");
+
 /* LOCK */
 	/* Complete LOCK [TABLE] [ONLY] with a list of tables */
 	else if (Matches("LOCK"))
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 4172b0cd268..855ca530964 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -171,7 +171,9 @@ extern SearchPathMatcher *GetSearchPathMatcher(MemoryContext context);
 extern SearchPathMatcher *CopySearchPathMatcher(SearchPathMatcher *path);
 extern bool SearchPathMatchesCurrentEnvironment(SearchPathMatcher *path);
 
+extern List *NamesFromList(List *names);
 extern Oid	LookupVariable(const char *nspname, const char *varname, bool missing_ok);
+extern Oid	IdentifyVariable(List *names, char **attrname, bool *not_unique, bool noerror);
 
 extern Oid	get_collation_oid(List *collname, bool missing_ok);
 extern Oid	get_conversion_oid(List *conname, bool missing_ok);
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
index 10fba805847..1f4112790af 100644
--- a/src/include/catalog/pg_variable.h
+++ b/src/include/catalog/pg_variable.h
@@ -26,6 +26,10 @@
 /* ----------------
  *		pg_variable definition.  cpp turns this into
  *		typedef struct FormData_pg_variable
+ *
+ * The column varcreate_lsn of XlogRecPtr type (8-byte) should be on position
+ * divisible by 8 unconditionally and before varname column of NameData type.
+ * see sanity_check:check_columns
  * ----------------
  */
 CATALOG(pg_variable,9222,VariableRelationId)
@@ -35,6 +39,9 @@ CATALOG(pg_variable,9222,VariableRelationId)
 	/* OID of entry in pg_type for variable's type */
 	Oid			vartype BKI_LOOKUP(pg_type);
 
+	/* used for identity check [oid, create_lsn] */
+	XLogRecPtr	varcreate_lsn;
+
 	/* variable name */
 	NameData	varname;
 
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
new file mode 100644
index 00000000000..b3f03c65827
--- /dev/null
+++ b/src/include/commands/session_variable.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * sessionvariable.h
+ *	  prototypes for sessionvariable.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/session_variable.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SESSIONVARIABLE_H
+#define SESSIONVARIABLE_H
+
+#include "nodes/params.h"
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/cmdtag.h"
+#include "utils/queryenvironment.h"
+
+extern void SetSessionVariable(Oid varid, Datum value, bool isNull);
+extern void SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull);
+extern Datum GetSessionVariable(Oid varid, bool *isNull, Oid *typid);
+extern Datum GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid);
+
+extern void ExecuteLetStmt(ParseState *pstate, LetStmt *stmt, ParamListInfo params,
+						   QueryEnvironment *queryEnv, QueryCompletion *qc);
+
+#endif
diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h
index 0a7274e26c8..d1b2f59e0cf 100644
--- a/src/include/executor/execdesc.h
+++ b/src/include/executor/execdesc.h
@@ -48,6 +48,10 @@ typedef struct QueryDesc
 	EState	   *estate;			/* executor's query-wide state */
 	PlanState  *planstate;		/* tree of per-plan-node state */
 
+	/* reference to session variables buffer */
+	int			num_session_variables;
+	SessionVariableValue *session_variables;
+
 	/* This field is set by ExecutorRun */
 	bool		already_executed;	/* true if previously executed */
 
diff --git a/src/include/executor/svariableReceiver.h b/src/include/executor/svariableReceiver.h
new file mode 100644
index 00000000000..db44d8b94c6
--- /dev/null
+++ b/src/include/executor/svariableReceiver.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.h
+ *	  prototypes for svariableReceiver.c
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/svariableReceiver.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SVARIABLE_RECEIVER_H
+#define SVARIABLE_RECEIVER_H
+
+#include "tcop/dest.h"
+
+extern DestReceiver *CreateVariableDestReceiver(Oid varid);
+
+#endif							/* SVARIABLE_RECEIVER_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cac684d9b3a..bec7b043da3 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -609,6 +609,18 @@ typedef struct AsyncRequest
 								 * tuples) */
 } AsyncRequest;
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+	Oid			varid;
+	Oid			typid;
+	bool		isnull;
+	Datum		value;
+} SessionVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -661,6 +673,13 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
 
+	/* Variables info: */
+	/* number of used session variables */
+	int			es_num_session_variables;
+
+	/* array of copied values of session variables */
+	SessionVariableValue *es_session_variables;
+
 	QueryEnvironment *es_queryEnv;	/* query environment */
 
 	/* Other working state: */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 80ea5928eef..74bf5af7766 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -142,6 +142,9 @@ typedef struct Query
 	 */
 	int			resultRelation pg_node_attr(query_jumble_ignore);
 
+	/* target variable of LET statement */
+	Oid			resultVariable;
+
 	/* has aggregates in tlist or havingQual */
 	bool		hasAggs pg_node_attr(query_jumble_ignore);
 	/* has window functions in tlist */
@@ -160,6 +163,8 @@ typedef struct Query
 	bool		hasForUpdate pg_node_attr(query_jumble_ignore);
 	/* rewriter has applied some RLS policy */
 	bool		hasRowSecurity pg_node_attr(query_jumble_ignore);
+	/* uses session variables */
+	bool		hasSessionVariables pg_node_attr(query_jumble_ignore);
 	/* is a RETURN statement */
 	bool		isReturn pg_node_attr(query_jumble_ignore);
 
@@ -2095,6 +2100,18 @@ typedef struct MergeStmt
 	WithClause *withClause;		/* WITH clause */
 } MergeStmt;
 
+/* ----------------------
+ *		Let Statement
+ * ----------------------
+ */
+typedef struct LetStmt
+{
+	NodeTag		type;
+	List	   *target;			/* target variable */
+	Node	   *query;			/* source expression */
+	int			location;
+} LetStmt;
+
 /* ----------------------
  *		Select Statement
  *
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 14ccfc1ac1c..c16385512fc 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -163,6 +163,9 @@ typedef struct PlannerGlobal
 
 	/* partition descriptors */
 	PartitionDirectory partition_directory pg_node_attr(read_write_ignore);
+
+	/* list of used session variables */
+	List	   *sessionVariables;
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -508,6 +511,8 @@ struct PlannerInfo
 	bool		placeholdersFrozen;
 	/* true if planning a recursive WITH item */
 	bool		hasRecursion;
+	/* true if session variables were used */
+	bool		hasSessionVariables;
 
 	/*
 	 * Information about aggregates. Filled by preprocess_aggrefs().
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 1aeeaec95e1..6b9b9904fdb 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -49,7 +49,7 @@ typedef struct PlannedStmt
 
 	NodeTag		type;
 
-	CmdType		commandType;	/* select|insert|update|delete|merge|utility */
+	CmdType		commandType;	/* select|let|insert|update|delete|merge|utility */
 
 	uint64		queryId;		/* query identifier (copied from Query) */
 
@@ -94,6 +94,8 @@ typedef struct PlannedStmt
 
 	Node	   *utilityStmt;	/* non-null if this is utility stmt */
 
+	List	   *sessionVariables;	/* OIDs for PARAM_VARIABLE Params */
+
 	/* statement location in source string (copied from Query) */
 	ParseLoc	stmt_location;	/* start location, or -1 if unknown */
 	ParseLoc	stmt_len;		/* length in bytes; 0 means "rest of string" */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index ea47652adb8..722df699537 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -361,6 +361,9 @@ typedef struct Const
  *				of the `paramid' field contain the SubLink's subLinkId, and
  *				the low-order 16 bits contain the column number.  (This type
  *				of Param is also converted to PARAM_EXEC during planning.)
+ *
+ *		PARAM_VARIABLE:  The parameter is an access to session variable
+ *				paramid holds varid.
  */
 typedef enum ParamKind
 {
@@ -368,6 +371,7 @@ typedef enum ParamKind
 	PARAM_EXEC,
 	PARAM_SUBLINK,
 	PARAM_MULTIEXPR,
+	PARAM_VARIABLE,
 } ParamKind;
 
 typedef struct Param
@@ -380,6 +384,8 @@ typedef struct Param
 	int32		paramtypmod pg_node_attr(query_jumble_ignore);
 	/* OID of collation, or InvalidOid if none */
 	Oid			paramcollid pg_node_attr(query_jumble_ignore);
+	/* OID of session variable if it is used */
+	Oid			paramvarid;
 	/* token location, or -1 if unknown */
 	ParseLoc	location;
 } Param;
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index aafc1737921..de471692e82 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -120,4 +120,6 @@ extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
 extern void record_plan_type_dependency(PlannerInfo *root, Oid typid);
 extern bool extract_query_dependencies_walker(Node *node, PlannerInfo *context);
 
+extern void pull_up_has_session_variables(PlannerInfo *root);
+
 #endif							/* PLANMAIN_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index c884cd5c467..2e88d7d9508 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -256,6 +256,7 @@ PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("let", LET, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD, AS_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 5b781d87a9d..6d936919659 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -82,6 +82,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_COPY_WHERE,		/* WHERE condition in COPY FROM */
 	EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */
 	EXPR_KIND_CYCLE_MARK,		/* cycle mark value */
+	EXPR_KIND_ASSIGN_TARGET,	/* PL/pgSQL assignment target */
+	EXPR_KIND_LET_TARGET,		/* LET target */
 } ParseExprKind;

@@ -225,6 +227,7 @@ struct ParseState
 	bool		p_hasTargetSRFs;
 	bool		p_hasSubLinks;
 	bool		p_hasModifyingCTE;
+	bool		p_hasSessionVariables;
 
 	Node	   *p_last_srf;		/* most recent set-returning func/op found */
 
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 9465df7b2ff..a921af2486f 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -186,6 +186,7 @@ PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
 PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
 PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
 PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
+PG_CMDTAG(CMDTAG_LET, "LET", false, false, false)
 PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
 PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index a3d521b6f97..286e571f27c 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -97,6 +97,7 @@ typedef enum
 	DestTransientRel,			/* results sent to transient relation */
 	DestTupleQueue,				/* results sent to tuple queue */
 	DestExplainSerialize,		/* results are serialized and discarded */
+	DestVariable,				/* results sents to session variable */
 } CommandDest;
 
 /* ----------------
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 239b3250a95..a9e1e323ad1 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8101,7 +8101,8 @@ exec_is_simple_query(PLpgSQL_expr *expr)
 		query->sortClause ||
 		query->limitOffset ||
 		query->limitCount ||
-		query->setOperations)
+		query->setOperations ||
+		query->hasSessionVariables)
 		return false;
 
 	/*
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index aca766042db..281bdd7920b 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -55,3 +55,915 @@ SET ROLE TO DEFAULT;
 DROP VARIABLE svartest.var1;
 DROP SCHEMA svartest;
 DROP ROLE regress_variable_owner;
+-- check access rights
+CREATE ROLE regress_noowner;
+CREATE VARIABLE var1 AS int;
+CREATE OR REPLACE FUNCTION sqlfx(int)
+RETURNS int AS $$ SELECT $1 + var1 $$ LANGUAGE sql;
+CREATE OR REPLACE FUNCTION sqlfx_sd(int)
+RETURNS int AS $$ SELECT $1 + var1 $$ LANGUAGE sql SECURITY DEFINER;
+CREATE OR REPLACE FUNCTION plpgsqlfx(int)
+RETURNS int AS $$ BEGIN RETURN $1 + var1; END $$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION plpgsqlfx_sd(int)
+RETURNS int AS $$ BEGIN RETURN $1 + var1; END $$ LANGUAGE plpgsql SECURITY DEFINER;
+LET var1 = 10;
+-- should be ok
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+SELECT sqlfx(20);
+ sqlfx 
+-------
+    30
+(1 row)
+
+SELECT sqlfx_sd(20);
+ sqlfx_sd 
+----------
+       30
+(1 row)
+
+SELECT plpgsqlfx(20);
+ plpgsqlfx 
+-----------
+        30
+(1 row)
+
+SELECT plpgsqlfx_sd(20);
+ plpgsqlfx_sd 
+--------------
+           30
+(1 row)
+
+-- should to fail
+SET ROLE TO regress_noowner;
+SELECT var1;
+ERROR:  permission denied for session variable var1
+SELECT sqlfx(20);
+ERROR:  permission denied for session variable var1
+CONTEXT:  SQL function "sqlfx" statement 1
+SELECT plpgsqlfx(20);
+ERROR:  permission denied for session variable var1
+CONTEXT:  SQL expression "$1 + var1"
+PL/pgSQL function plpgsqlfx(integer) line 1 at RETURN
+-- should be ok
+SELECT sqlfx_sd(20);
+ sqlfx_sd 
+----------
+       30
+(1 row)
+
+SELECT plpgsqlfx_sd(20);
+ plpgsqlfx_sd 
+--------------
+           30
+(1 row)
+
+SET ROLE TO DEFAULT;
+GRANT SELECT ON VARIABLE var1 TO regress_noowner;
+-- should be ok
+SET ROLE TO regress_noowner;
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+SELECT sqlfx(20);
+ sqlfx 
+-------
+    30
+(1 row)
+
+SELECT plpgsqlfx(20);
+ plpgsqlfx 
+-----------
+        30
+(1 row)
+
+SET ROLE TO DEFAULT;
+DROP VARIABLE var1;
+DROP FUNCTION sqlfx(int);
+DROP FUNCTION plpgsqlfx(int);
+DROP FUNCTION sqlfx_sd(int);
+DROP FUNCTION plpgsqlfx_sd(int);
+DROP ROLE regress_noowner;
+-- use variables inside views
+CREATE VARIABLE var1 AS numeric;
+-- use variables in views
+CREATE VIEW test_view AS SELECT COALESCE(var1 + v, 0) AS result FROM generate_series(1,2) g(v);
+SELECT * FROM test_view;
+ result 
+--------
+      0
+      0
+(2 rows)
+
+LET var1 = 3.14;
+SELECT * FROM test_view;
+ result 
+--------
+   4.14
+   5.14
+(2 rows)
+
+-- start a new session
+\c
+SELECT * FROM test_view;
+ result 
+--------
+      0
+      0
+(2 rows)
+
+LET var1 = 3.14;
+SELECT * FROM test_view;
+ result 
+--------
+   4.14
+   5.14
+(2 rows)
+
+-- should fail, dependency
+DROP VARIABLE var1;
+ERROR:  cannot drop session variable var1 because other objects depend on it
+DETAIL:  view test_view depends on session variable var1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- should be ok
+DROP VARIABLE var1 CASCADE;
+NOTICE:  drop cascades to view test_view
+CREATE VARIABLE var1 text;
+CREATE VARIABLE var2 text;
+-- use variables in SQL functions
+CREATE OR REPLACE FUNCTION sqlfx1(varchar)
+RETURNS varchar AS $$ SELECT var1 || ', ' || $1 $$ LANGUAGE sql;
+CREATE OR REPLACE FUNCTION sqlfx2( varchar)
+RETURNS varchar AS $$ SELECT var2 || ', ' || $1 $$ LANGUAGE sql;
+LET var1 = 'str1';
+LET var2 = 'str2';
+SELECT sqlfx1(sqlfx2('Hello'));
+      sqlfx1       
+-------------------
+ str1, str2, Hello
+(1 row)
+
+-- inlining is blocked
+EXPLAIN (COSTS OFF, VERBOSE) SELECT sqlfx1(sqlfx2('Hello'));
+                      QUERY PLAN                      
+------------------------------------------------------
+ Result
+   Output: sqlfx1(sqlfx2('Hello'::character varying))
+(2 rows)
+
+DROP FUNCTION sqlfx1(varchar);
+DROP FUNCTION sqlfx2(varchar);
+DROP VARIABLE var1;
+DROP VARIABLE var2;
+-- access from cached plans should to work
+CREATE VARIABLE var1 AS numeric;
+CREATE OR REPLACE FUNCTION plpgsqlfx()
+RETURNS numeric AS $$ BEGIN RETURN var1; END $$ LANGUAGE plpgsql;
+set plan_cache_mode TO force_generic_plan;
+LET var1 = 3.14;
+SELECT plpgsqlfx();
+ plpgsqlfx 
+-----------
+      3.14
+(1 row)
+
+LET var1 = 3.14 * 2;
+SELECT plpgsqlfx();
+ plpgsqlfx 
+-----------
+      6.28
+(1 row)
+
+DROP VARIABLE var1;
+-- dependency (plan invalidation) should to work
+CREATE VARIABLE var1 AS numeric;
+LET var1 = 3.14 * 3;
+SELECT plpgsqlfx();
+ plpgsqlfx 
+-----------
+      9.42
+(1 row)
+
+LET var1 = 3.14 * 4;
+SELECT plpgsqlfx();
+ plpgsqlfx 
+-----------
+     12.56
+(1 row)
+
+DROP VARIABLE var1;
+DROP FUNCTION plpgsqlfx();
+set plan_cache_mode TO DEFAULT;
+-- usage LET statement in plpgsql should to work
+CREATE VARIABLE var1 int;
+CREATE VARIABLE var2 numeric[];
+DO $$
+BEGIN
+  LET var2 = '{}'::int[];
+  FOR i IN 1..10
+  LOOP
+    LET var1 = i;
+    LET var2[var1] = i;
+  END LOOP;
+  RAISE NOTICE 'result array: %', var2;
+END;
+$$;
+NOTICE:  result array: {1,2,3,4,5,6,7,8,9,10}
+DROP VARIABLE var1;
+DROP VARIABLE var2;
+-- CALL statement is not supported yet
+-- requires direct access to session variable from expression executor
+CREATE VARIABLE v int;
+CREATE PROCEDURE p(arg int) AS $$ BEGIN RAISE NOTICE '%', arg; END $$ LANGUAGE plpgsql;
+-- should not crash (but is not supported yet)
+CALL p(v);
+ERROR:  session variable cannot be used as an argument
+DO $$ BEGIN CALL p(v); END $$;
+ERROR:  session variable cannot be used as an argument
+CONTEXT:  SQL statement "CALL p(v)"
+PL/pgSQL function inline_code_block line 1 at CALL
+DROP PROCEDURE p(int);
+DROP VARIABLE v;
+-- test search path
+CREATE SCHEMA svartest;
+CREATE VARIABLE svartest.var1 AS numeric;
+-- should to fail
+LET var1 = pi();
+ERROR:  session variable "var1" doesn't exist
+LINE 1: LET var1 = pi();
+            ^
+SELECT var1;
+ERROR:  column "var1" does not exist
+LINE 1: SELECT var1;
+               ^
+-- should be ok
+LET svartest.var1 = pi();
+SELECT svartest.var1;
+       var1       
+------------------
+ 3.14159265358979
+(1 row)
+
+SET search_path TO svartest;
+-- should be ok
+LET var1 = pi() + 10;
+SELECT var1;
+       var1       
+------------------
+ 13.1415926535898
+(1 row)
+
+RESET search_path;
+DROP SCHEMA svartest CASCADE;
+NOTICE:  drop cascades to session variable svartest.var1
+CREATE VARIABLE var1 AS text;
+-- variables can be updated under RO transaction
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET var1 = 'hello';
+COMMIT;
+SELECT var1;
+ var1  
+-------
+ hello
+(1 row)
+
+DROP VARIABLE var1;
+-- test of domains
+CREATE DOMAIN int_domain AS int NOT NULL CHECK (VALUE > 100);
+CREATE VARIABLE var1 AS int_domain;
+-- should fail
+SELECT var1;
+ERROR:  domain int_domain does not allow null values
+-- should be ok
+LET var1 = 1000;
+SELECT var1;
+ var1 
+------
+ 1000
+(1 row)
+
+-- should fail
+LET var1 = 10;
+ERROR:  value for domain int_domain violates check constraint "int_domain_check"
+-- should fail
+LET var1 = NULL;
+ERROR:  domain int_domain does not allow null values
+-- note - domain defaults are not supported yet (like PLpgSQL)
+DROP VARIABLE var1;
+DROP DOMAIN int_domain;
+-- an expression should be unknown-type
+CREATE VARIABLE var1 AS int4multirange[];
+-- should be ok
+LET var1 = NULL;
+LET var1 = '{"{[2,8),[11,14)}","{[5,8),[12,14)}"}';
+LET var1[2] = '{[5,8),[12,100)}';
+SELECT var1;
+                  var1                  
+----------------------------------------
+ {"{[2,8),[11,14)}","{[5,8),[12,100)}"}
+(1 row)
+
+--It should to work in plpgsql too
+DO $$
+BEGIN
+  LET var1 = NULL;
+  LET var1 = '{"{[2,8),[11,14)}","{[5,8),[12,14)}"}';
+  LET var1[2] = '{[5,8),[12,100)}';
+
+  RAISE NOTICE '%', var1;
+END;
+$$;
+NOTICE:  {"{[2,8),[11,14)}","{[5,8),[12,100)}"}
+DROP VARIABLE var1;
+CREATE SCHEMA svartest CREATE VARIABLE var1 AS int CREATE TABLE foo(a int);
+LET svartest.var1 = 100;
+SELECT svartest.var1;
+ var1 
+------
+  100
+(1 row)
+
+SET search_path to public, svartest;
+SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+DROP SCHEMA svartest CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table foo
+drop cascades to session variable var1
+CREATE VARIABLE var1 AS int;
+CREATE VARIABLE var2 AS int[];
+LET var1 = 2;
+LET var2 = '{}'::int[];
+LET var2[var1] = 0;
+SELECT var2;
+   var2    
+-----------
+ [2:2]={0}
+(1 row)
+
+DROP VARIABLE var1, var2;
+CREATE VARIABLE var1 AS int;
+CREATE VARIABLE var2 AS int[];
+LET var1 = 2;
+LET var2 = '{}'::int[];
+SELECT var2;
+ var2 
+------
+ {}
+(1 row)
+
+DROP VARIABLE var1, var2;
+-- the LET statement should be disallowed in CTE
+CREATE VARIABLE var1 AS int;
+WITH x AS (LET var1 = 100) SELECT * FROM x;
+ERROR:  syntax error at or near "LET"
+LINE 1: WITH x AS (LET var1 = 100) SELECT * FROM x;
+                   ^
+-- should be ok
+LET var1 = generate_series(1, 1);
+-- should fail
+LET var1 = generate_series(1, 2);
+ERROR:  expression returned more than one row
+LET var1 = generate_series(1, 0);
+ERROR:  expression returned no rows
+DROP VARIABLE var1;
+-- composite variables
+CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2));
+CREATE VARIABLE v1 AS sv_xyz;
+CREATE VARIABLE v2 AS sv_xyz;
+LET v1 = (1, 2, 3.14);
+LET v2 = (10, 20, 3.14 * 10);
+-- should work too - there are prepared casts
+LET v1 = (1, 2, 3);
+SELECT v1;
+     v1     
+------------
+ (1,2,3.00)
+(1 row)
+
+SELECT v2;
+      v2       
+---------------
+ (10,20,31.40)
+(1 row)
+
+SELECT (v1).*;
+ x | y |  z   
+---+---+------
+ 1 | 2 | 3.00
+(1 row)
+
+SELECT (v2).*;
+ x  | y  |   z   
+----+----+-------
+ 10 | 20 | 31.40
+(1 row)
+
+SELECT v1.x + v1.z;
+ ?column? 
+----------
+     4.00
+(1 row)
+
+SELECT v2.x + v2.z;
+ ?column? 
+----------
+    41.40
+(1 row)
+
+-- access to composite fields should be safe too
+CREATE ROLE regress_var_test_role;
+SET ROLE TO regress_var_test_role;
+-- should fail
+SELECT v2.x;
+ERROR:  permission denied for session variable v2
+SET ROLE TO DEFAULT;
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+DROP ROLE regress_var_test_role;
+CREATE TYPE t1 AS (a int, b numeric, c text);
+CREATE VARIABLE v1 AS t1;
+LET v1 = (1, pi(), 'hello');
+SELECT v1;
+             v1             
+----------------------------
+ (1,3.14159265358979,hello)
+(1 row)
+
+LET v1.b = 10.2222;
+SELECT v1;
+        v1         
+-------------------
+ (1,10.2222,hello)
+(1 row)
+
+-- should fail, attribute doesn't exist
+LET v1.x = 10;
+ERROR:  cannot assign to field "x" of column "v1" because there is no such column in data type t1
+LINE 1: LET v1.x = 10;
+            ^
+-- should fail, don't allow multi column query
+LET v1 = (NULL::t1).*;
+ERROR:  assignment expression returned 3 columns
+LINE 1: LET v1 = (NULL::t1).*;
+                  ^
+-- allow DROP or ADD ATTRIBUTE on composite types
+-- should be ok
+ALTER TYPE t1 DROP ATTRIBUTE c;
+SELECT v1;
+     v1      
+-------------
+ (1,10.2222)
+(1 row)
+
+-- should be ok
+ALTER TYPE t1 ADD ATTRIBUTE c int;
+SELECT v1;
+      v1      
+--------------
+ (1,10.2222,)
+(1 row)
+
+LET v1 = (10, 10.3, 20);
+SELECT v1;
+      v1      
+--------------
+ (10,10.3,20)
+(1 row)
+
+-- should be ok
+ALTER TYPE t1 DROP ATTRIBUTE b;
+SELECT v1;
+   v1    
+---------
+ (10,20)
+(1 row)
+
+-- should fail, disallow data type change
+ALTER TYPE t1 ALTER ATTRIBUTE c TYPE int;
+ERROR:  cannot alter type "t1" because session variable "public.v1" uses it
+DROP VARIABLE v1;
+DROP TYPE t1;
+-- the table type can be used as composite type too
+CREATE TABLE svar_test(a int, b numeric, c date);
+CREATE VARIABLE var1 AS svar_test;
+LET var1 = (10, pi(), '2023-05-26');
+SELECT var1;
+               var1               
+----------------------------------
+ (10,3.14159265358979,05-26-2023)
+(1 row)
+
+-- should fail due dependency
+ALTER TABLE svar_test ALTER COLUMN a TYPE text;
+ERROR:  cannot alter table "svar_test" because session variable "public.var1" uses it
+-- should fail
+DROP TABLE svar_test;
+ERROR:  cannot drop table svar_test because other objects depend on it
+DETAIL:  session variable var1 depends on type svar_test
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP VARIABLE var1;
+DROP TABLE svar_test;
+-- arrays are supported
+CREATE VARIABLE var1 AS numeric[];
+LET var1 = ARRAY[1.1,2.1];
+LET var1[1] = 10.1;
+SELECT var1;
+    var1    
+------------
+ {10.1,2.1}
+(1 row)
+
+-- LET target doesn't allow srf, should fail
+LET var1[generate_series(1,3)] = 100;
+ERROR:  set-returning functions are not allowed in LET
+LINE 1: LET var1[generate_series(1,3)] = 100;
+                 ^
+DROP VARIABLE var1;
+-- arrays inside composite
+CREATE TYPE t1 AS (a numeric, b numeric[]);
+CREATE VARIABLE var1 AS t1;
+LET var1 = (10.1, ARRAY[0.0, 0.0]);
+LET var1.a = 10.2;
+SELECT var1;
+        var1        
+--------------------
+ (10.2,"{0.0,0.0}")
+(1 row)
+
+LET var1.b[1] = 10.3;
+SELECT var1;
+        var1         
+---------------------
+ (10.2,"{10.3,0.0}")
+(1 row)
+
+DROP VARIABLE var1;
+DROP TYPE t1;
+-- Encourage use of parallel plans
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers_per_gather = 2;
+-- test on query with workers
+CREATE TABLE svar_test(a int);
+INSERT INTO svar_test SELECT * FROM generate_series(1,1000);
+ANALYZE svar_test;
+CREATE VARIABLE zero int;
+LET zero = 0;
+-- result should be 100
+SELECT count(*) FROM svar_test WHERE a%10 = zero;
+ count 
+-------
+   100
+(1 row)
+
+-- parallel execution is not supported yet
+EXPLAIN (COSTS OFF) SELECT count(*) FROM svar_test WHERE a%10 = zero;
+            QUERY PLAN             
+-----------------------------------
+ Aggregate
+   ->  Seq Scan on svar_test
+         Filter: ((a % 10) = zero)
+(3 rows)
+
+LET zero = (SELECT count(*) FROM svar_test);
+-- result should be 1000
+SELECT zero;
+ zero 
+------
+ 1000
+(1 row)
+
+DROP VARIABLE zero;
+DROP TABLE svar_test;
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers_per_gather;
+-- the result of view should be same in parallel mode too
+CREATE VARIABLE var1 AS int;
+LET var1 = 10;
+CREATE VIEW var1view AS SELECT COALESCE(var1, 0) AS result;
+SELECT * FROM var1view;
+ result 
+--------
+     10
+(1 row)
+
+SET debug_parallel_query TO on;
+SELECT * FROM var1view;
+ result 
+--------
+     10
+(1 row)
+
+SET debug_parallel_query TO off;
+DROP VIEW var1view;
+DROP VARIABLE var1;
+CREATE VARIABLE varid int;
+CREATE TABLE svar_test(id int, v int);
+LET varid = 1;
+INSERT INTO svar_test VALUES(varid, 100);
+SELECT * FROM svar_test;
+ id |  v  
+----+-----
+  1 | 100
+(1 row)
+
+UPDATE svar_test SET v = 200 WHERE id = varid;
+SELECT * FROM svar_test;
+ id |  v  
+----+-----
+  1 | 200
+(1 row)
+
+DELETE FROM svar_test WHERE id = varid;
+SELECT * FROM svar_test;
+ id | v 
+----+---
+(0 rows)
+
+DROP TABLE svar_test;
+DROP VARIABLE varid;
+-- visibility check
+-- variables should be shadowed always
+CREATE VARIABLE var1 AS text;
+SELECT var1.relname FROM pg_class var1 WHERE var1.relname = 'pg_class';
+ relname  
+----------
+ pg_class
+(1 row)
+
+DROP VARIABLE var1;
+CREATE TABLE xxtab(avar int);
+INSERT INTO xxtab VALUES(333);
+CREATE TYPE xxtype AS (avar int);
+CREATE VARIABLE xxtab AS xxtype;
+INSERT INTO xxtab VALUES(10);
+-- it is ambiguous, but columns are preferred
+SELECT xxtab.avar FROM xxtab;
+ avar 
+------
+  333
+   10
+(2 rows)
+
+-- should be ok
+SELECT avar FROM xxtab;
+ avar 
+------
+  333
+   10
+(2 rows)
+
+CREATE VARIABLE public.avar AS int;
+-- should be ok, see the table
+SELECT avar FROM xxtab;
+ avar 
+------
+  333
+   10
+(2 rows)
+
+-- should be ok
+SELECT public.avar FROM xxtab;
+ avar 
+------
+     
+     
+(2 rows)
+
+DROP VARIABLE xxtab;
+SELECT xxtab.avar FROM xxtab;
+ avar 
+------
+  333
+   10
+(2 rows)
+
+DROP VARIABLE public.avar;
+DROP TYPE xxtype;
+DROP TABLE xxtab;
+-- The variable can be shadowed by table or by alias
+CREATE TYPE public.svar_type AS (a int, b int, c int);
+CREATE VARIABLE public.svar AS public.svar_type;
+CREATE TABLE public.svar(a int, b int);
+INSERT INTO public.svar VALUES(10, 20);
+LET public.svar = (100, 200, 300);
+-- should be ok
+-- show table
+SELECT * FROM public.svar;
+ a  | b  
+----+----
+ 10 | 20
+(1 row)
+
+SELECT svar.a FROM public.svar;
+ a  
+----
+ 10
+(1 row)
+
+SELECT svar.* FROM public.svar;
+ a  | b  
+----+----
+ 10 | 20
+(1 row)
+
+-- show variable
+SELECT public.svar;
+     svar      
+---------------
+ (100,200,300)
+(1 row)
+
+SELECT public.svar.c;
+  c  
+-----
+ 300
+(1 row)
+
+SELECT (public.svar).*;
+  a  |  b  |  c  
+-----+-----+-----
+ 100 | 200 | 300
+(1 row)
+
+-- the variable is shadowed, raise error
+SELECT public.svar.c FROM public.svar;
+ERROR:  column svar.c does not exist
+LINE 1: SELECT public.svar.c FROM public.svar;
+               ^
+-- can be fixed by alias
+SELECT public.svar.c FROM public.svar x;
+  c  
+-----
+ 300
+(1 row)
+
+SELECT svar.a FROM public.svar;
+ a  
+----
+ 10
+(1 row)
+
+SELECT svar.* FROM public.svar;
+ a  | b  
+----+----
+ 10 | 20
+(1 row)
+
+-- show variable
+SELECT public.svar;
+     svar      
+---------------
+ (100,200,300)
+(1 row)
+
+SELECT public.svar.c;
+  c  
+-----
+ 300
+(1 row)
+
+SELECT (public.svar).*;
+  a  |  b  |  c  
+-----+-----+-----
+ 100 | 200 | 300
+(1 row)
+
+-- the variable is shadowed, raise error
+SELECT public.svar.c FROM public.svar;
+ERROR:  column svar.c does not exist
+LINE 1: SELECT public.svar.c FROM public.svar;
+               ^
+-- can be fixed by alias
+SELECT public.svar.c FROM public.svar x;
+  c  
+-----
+ 300
+(1 row)
+
+DROP VARIABLE public.svar;
+DROP TABLE public.svar;
+DROP TYPE public.svar_type;
+CREATE TYPE ab AS (a integer, b integer);
+CREATE VARIABLE v_ab AS ab;
+CREATE TABLE v_ab (a integer, b integer);
+INSERT INTO v_ab VALUES(10,20);
+-- we should to see table
+SELECT v_ab.a FROM v_ab;
+ a  
+----
+ 10
+(1 row)
+
+CREATE SCHEMA v_ab;
+CREATE VARIABLE v_ab.a AS integer;
+-- we should to see table
+SELECT v_ab.a FROM v_ab;
+ a  
+----
+ 10
+(1 row)
+
+DROP VARIABLE v_ab;
+DROP TABLE v_ab;
+DROP TYPE ab;
+CREATE TYPE t_am_type AS (b int);
+CREATE SCHEMA xxx_am;
+SET search_path TO public;
+CREATE VARIABLE xxx_am AS t_am_type;
+LET xxx_am = ROW(10);
+-- should be ok
+SELECT xxx_am;
+ xxx_am 
+--------
+ (10)
+(1 row)
+
+CREATE VARIABLE xxx_am.b AS int;
+LET :"DBNAME".xxx_am.b = 20;
+-- should be still ok
+SELECT xxx_am;
+ xxx_am 
+--------
+ (10)
+(1 row)
+
+-- should fail, the reference should be ambiguous
+SELECT xxx_am.b;
+ERROR:  session variable reference "xxx_am.b" is ambiguous
+LINE 1: SELECT xxx_am.b;
+               ^
+-- enhanced references should be ok
+SELECT public.xxx_am.b;
+ b  
+----
+ 10
+(1 row)
+
+SELECT :"DBNAME".xxx_am.b;
+ b  
+----
+ 20
+(1 row)
+
+CREATE TABLE xxx_am(b  int);
+INSERT INTO xxx_am VALUES(10);
+-- we should to see table
+SELECT xxx_am.b FROM xxx_am;
+ b  
+----
+ 10
+(1 row)
+
+SELECT x.b FROM xxx_am x;
+ b  
+----
+ 10
+(1 row)
+
+DROP TABLE xxx_am;
+DROP VARIABLE public.xxx_am;
+DROP VARIABLE xxx_am.b;
+DROP SCHEMA xxx_am;
+CREATE SCHEMA :"DBNAME";
+CREATE VARIABLE :"DBNAME".:"DBNAME".:"DBNAME" AS t_am_type;
+CREATE VARIABLE :"DBNAME".:"DBNAME".b AS int;
+SET search_path TO :"DBNAME";
+-- should be ambiguous
+SELECT :"DBNAME".b;
+ERROR:  session variable reference "regression.b" is ambiguous
+LINE 1: SELECT "regression".b;
+               ^
+-- should be ambiguous too
+SELECT :"DBNAME".:"DBNAME".b;
+ERROR:  session variable reference "regression.regression.b" is ambiguous
+LINE 1: SELECT "regression"."regression".b;
+               ^
+CREATE TABLE :"DBNAME"(b int);
+-- should be ok
+SELECT :"DBNAME".b FROM :"DBNAME";
+ b 
+---
+(0 rows)
+
+DROP TABLE :"DBNAME";
+DROP VARIABLE :"DBNAME".:"DBNAME".b;
+DROP VARIABLE :"DBNAME".:"DBNAME".:"DBNAME";
+DROP SCHEMA :"DBNAME";
+RESET search_path;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 42dfb95a022..cce52e7ae6e 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -59,3 +59,628 @@ DROP VARIABLE svartest.var1;
 DROP SCHEMA svartest;
 
 DROP ROLE regress_variable_owner;
+
+-- check access rights
+CREATE ROLE regress_noowner;
+
+CREATE VARIABLE var1 AS int;
+
+CREATE OR REPLACE FUNCTION sqlfx(int)
+RETURNS int AS $$ SELECT $1 + var1 $$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION sqlfx_sd(int)
+RETURNS int AS $$ SELECT $1 + var1 $$ LANGUAGE sql SECURITY DEFINER;
+
+CREATE OR REPLACE FUNCTION plpgsqlfx(int)
+RETURNS int AS $$ BEGIN RETURN $1 + var1; END $$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION plpgsqlfx_sd(int)
+RETURNS int AS $$ BEGIN RETURN $1 + var1; END $$ LANGUAGE plpgsql SECURITY DEFINER;
+
+LET var1 = 10;
+-- should be ok
+SELECT var1;
+SELECT sqlfx(20);
+SELECT sqlfx_sd(20);
+SELECT plpgsqlfx(20);
+SELECT plpgsqlfx_sd(20);
+
+-- should to fail
+SET ROLE TO regress_noowner;
+
+SELECT var1;
+SELECT sqlfx(20);
+SELECT plpgsqlfx(20);
+
+-- should be ok
+SELECT sqlfx_sd(20);
+SELECT plpgsqlfx_sd(20);
+
+SET ROLE TO DEFAULT;
+GRANT SELECT ON VARIABLE var1 TO regress_noowner;
+
+-- should be ok
+SET ROLE TO regress_noowner;
+
+SELECT var1;
+SELECT sqlfx(20);
+SELECT plpgsqlfx(20);
+
+SET ROLE TO DEFAULT;
+DROP VARIABLE var1;
+DROP FUNCTION sqlfx(int);
+DROP FUNCTION plpgsqlfx(int);
+DROP FUNCTION sqlfx_sd(int);
+DROP FUNCTION plpgsqlfx_sd(int);
+
+DROP ROLE regress_noowner;
+
+-- use variables inside views
+CREATE VARIABLE var1 AS numeric;
+
+-- use variables in views
+CREATE VIEW test_view AS SELECT COALESCE(var1 + v, 0) AS result FROM generate_series(1,2) g(v);
+SELECT * FROM test_view;
+LET var1 = 3.14;
+SELECT * FROM test_view;
+
+-- start a new session
+\c
+
+SELECT * FROM test_view;
+LET var1 = 3.14;
+SELECT * FROM test_view;
+
+-- should fail, dependency
+DROP VARIABLE var1;
+
+-- should be ok
+DROP VARIABLE var1 CASCADE;
+
+CREATE VARIABLE var1 text;
+CREATE VARIABLE var2 text;
+
+-- use variables in SQL functions
+CREATE OR REPLACE FUNCTION sqlfx1(varchar)
+RETURNS varchar AS $$ SELECT var1 || ', ' || $1 $$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION sqlfx2( varchar)
+RETURNS varchar AS $$ SELECT var2 || ', ' || $1 $$ LANGUAGE sql;
+
+LET var1 = 'str1';
+LET var2 = 'str2';
+
+SELECT sqlfx1(sqlfx2('Hello'));
+
+-- inlining is blocked
+EXPLAIN (COSTS OFF, VERBOSE) SELECT sqlfx1(sqlfx2('Hello'));
+
+DROP FUNCTION sqlfx1(varchar);
+DROP FUNCTION sqlfx2(varchar);
+DROP VARIABLE var1;
+DROP VARIABLE var2;
+
+-- access from cached plans should to work
+CREATE VARIABLE var1 AS numeric;
+
+CREATE OR REPLACE FUNCTION plpgsqlfx()
+RETURNS numeric AS $$ BEGIN RETURN var1; END $$ LANGUAGE plpgsql;
+
+set plan_cache_mode TO force_generic_plan;
+
+LET var1 = 3.14;
+SELECT plpgsqlfx();
+LET var1 = 3.14 * 2;
+SELECT plpgsqlfx();
+
+DROP VARIABLE var1;
+
+-- dependency (plan invalidation) should to work
+CREATE VARIABLE var1 AS numeric;
+
+LET var1 = 3.14 * 3;
+SELECT plpgsqlfx();
+LET var1 = 3.14 * 4;
+SELECT plpgsqlfx();
+
+DROP VARIABLE var1;
+DROP FUNCTION plpgsqlfx();
+
+set plan_cache_mode TO DEFAULT;
+
+-- usage LET statement in plpgsql should to work
+CREATE VARIABLE var1 int;
+CREATE VARIABLE var2 numeric[];
+
+DO $$
+BEGIN
+  LET var2 = '{}'::int[];
+  FOR i IN 1..10
+  LOOP
+    LET var1 = i;
+    LET var2[var1] = i;
+  END LOOP;
+  RAISE NOTICE 'result array: %', var2;
+END;
+$$;
+
+DROP VARIABLE var1;
+DROP VARIABLE var2;
+
+-- CALL statement is not supported yet
+-- requires direct access to session variable from expression executor
+CREATE VARIABLE v int;
+
+CREATE PROCEDURE p(arg int) AS $$ BEGIN RAISE NOTICE '%', arg; END $$ LANGUAGE plpgsql;
+
+-- should not crash (but is not supported yet)
+CALL p(v);
+
+DO $$ BEGIN CALL p(v); END $$;
+
+DROP PROCEDURE p(int);
+DROP VARIABLE v;
+
+-- test search path
+CREATE SCHEMA svartest;
+CREATE VARIABLE svartest.var1 AS numeric;
+
+-- should to fail
+LET var1 = pi();
+SELECT var1;
+
+-- should be ok
+LET svartest.var1 = pi();
+SELECT svartest.var1;
+
+SET search_path TO svartest;
+
+-- should be ok
+LET var1 = pi() + 10;
+SELECT var1;
+
+RESET search_path;
+DROP SCHEMA svartest CASCADE;
+
+CREATE VARIABLE var1 AS text;
+
+-- variables can be updated under RO transaction
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET var1 = 'hello';
+COMMIT;
+
+SELECT var1;
+
+DROP VARIABLE var1;
+
+-- test of domains
+CREATE DOMAIN int_domain AS int NOT NULL CHECK (VALUE > 100);
+CREATE VARIABLE var1 AS int_domain;
+
+-- should fail
+SELECT var1;
+
+-- should be ok
+LET var1 = 1000;
+SELECT var1;
+
+-- should fail
+LET var1 = 10;
+
+-- should fail
+LET var1 = NULL;
+
+-- note - domain defaults are not supported yet (like PLpgSQL)
+
+DROP VARIABLE var1;
+DROP DOMAIN int_domain;
+
+-- an expression should be unknown-type
+CREATE VARIABLE var1 AS int4multirange[];
+-- should be ok
+LET var1 = NULL;
+LET var1 = '{"{[2,8),[11,14)}","{[5,8),[12,14)}"}';
+LET var1[2] = '{[5,8),[12,100)}';
+SELECT var1;
+
+--It should to work in plpgsql too
+DO $$
+BEGIN
+  LET var1 = NULL;
+  LET var1 = '{"{[2,8),[11,14)}","{[5,8),[12,14)}"}';
+  LET var1[2] = '{[5,8),[12,100)}';
+
+  RAISE NOTICE '%', var1;
+END;
+$$;
+
+DROP VARIABLE var1;
+
+CREATE SCHEMA svartest CREATE VARIABLE var1 AS int CREATE TABLE foo(a int);
+LET svartest.var1 = 100;
+SELECT svartest.var1;
+
+SET search_path to public, svartest;
+
+SELECT var1;
+
+DROP SCHEMA svartest CASCADE;
+
+CREATE VARIABLE var1 AS int;
+CREATE VARIABLE var2 AS int[];
+
+LET var1 = 2;
+LET var2 = '{}'::int[];
+
+LET var2[var1] = 0;
+
+SELECT var2;
+
+DROP VARIABLE var1, var2;
+
+CREATE VARIABLE var1 AS int;
+CREATE VARIABLE var2 AS int[];
+
+LET var1 = 2;
+LET var2 = '{}'::int[];
+
+SELECT var2;
+
+DROP VARIABLE var1, var2;
+
+-- the LET statement should be disallowed in CTE
+CREATE VARIABLE var1 AS int;
+WITH x AS (LET var1 = 100) SELECT * FROM x;
+
+-- should be ok
+LET var1 = generate_series(1, 1);
+
+-- should fail
+LET var1 = generate_series(1, 2);
+LET var1 = generate_series(1, 0);
+
+DROP VARIABLE var1;
+
+-- composite variables
+CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2));
+
+CREATE VARIABLE v1 AS sv_xyz;
+CREATE VARIABLE v2 AS sv_xyz;
+
+LET v1 = (1, 2, 3.14);
+LET v2 = (10, 20, 3.14 * 10);
+
+-- should work too - there are prepared casts
+LET v1 = (1, 2, 3);
+
+SELECT v1;
+SELECT v2;
+SELECT (v1).*;
+SELECT (v2).*;
+
+SELECT v1.x + v1.z;
+SELECT v2.x + v2.z;
+
+-- access to composite fields should be safe too
+CREATE ROLE regress_var_test_role;
+
+SET ROLE TO regress_var_test_role;
+
+-- should fail
+SELECT v2.x;
+
+SET ROLE TO DEFAULT;
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+DROP ROLE regress_var_test_role;
+
+CREATE TYPE t1 AS (a int, b numeric, c text);
+
+CREATE VARIABLE v1 AS t1;
+LET v1 = (1, pi(), 'hello');
+SELECT v1;
+LET v1.b = 10.2222;
+SELECT v1;
+
+-- should fail, attribute doesn't exist
+LET v1.x = 10;
+
+-- should fail, don't allow multi column query
+LET v1 = (NULL::t1).*;
+
+-- allow DROP or ADD ATTRIBUTE on composite types
+-- should be ok
+ALTER TYPE t1 DROP ATTRIBUTE c;
+SELECT v1;
+
+-- should be ok
+ALTER TYPE t1 ADD ATTRIBUTE c int;
+SELECT v1;
+
+LET v1 = (10, 10.3, 20);
+SELECT v1;
+
+-- should be ok
+ALTER TYPE t1 DROP ATTRIBUTE b;
+SELECT v1;
+
+-- should fail, disallow data type change
+ALTER TYPE t1 ALTER ATTRIBUTE c TYPE int;
+
+DROP VARIABLE v1;
+DROP TYPE t1;
+
+-- the table type can be used as composite type too
+CREATE TABLE svar_test(a int, b numeric, c date);
+CREATE VARIABLE var1 AS svar_test;
+
+LET var1 = (10, pi(), '2023-05-26');
+SELECT var1;
+
+-- should fail due dependency
+ALTER TABLE svar_test ALTER COLUMN a TYPE text;
+
+-- should fail
+DROP TABLE svar_test;
+
+DROP VARIABLE var1;
+DROP TABLE svar_test;
+
+-- arrays are supported
+CREATE VARIABLE var1 AS numeric[];
+LET var1 = ARRAY[1.1,2.1];
+LET var1[1] = 10.1;
+SELECT var1;
+
+-- LET target doesn't allow srf, should fail
+LET var1[generate_series(1,3)] = 100;
+
+DROP VARIABLE var1;
+
+-- arrays inside composite
+CREATE TYPE t1 AS (a numeric, b numeric[]);
+CREATE VARIABLE var1 AS t1;
+LET var1 = (10.1, ARRAY[0.0, 0.0]);
+LET var1.a = 10.2;
+SELECT var1;
+LET var1.b[1] = 10.3;
+SELECT var1;
+
+DROP VARIABLE var1;
+DROP TYPE t1;
+
+-- Encourage use of parallel plans
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers_per_gather = 2;
+
+-- test on query with workers
+CREATE TABLE svar_test(a int);
+INSERT INTO svar_test SELECT * FROM generate_series(1,1000);
+ANALYZE svar_test;
+CREATE VARIABLE zero int;
+LET zero = 0;
+
+-- result should be 100
+SELECT count(*) FROM svar_test WHERE a%10 = zero;
+
+-- parallel execution is not supported yet
+EXPLAIN (COSTS OFF) SELECT count(*) FROM svar_test WHERE a%10 = zero;
+
+LET zero = (SELECT count(*) FROM svar_test);
+
+-- result should be 1000
+SELECT zero;
+
+DROP VARIABLE zero;
+DROP TABLE svar_test;
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers_per_gather;
+
+-- the result of view should be same in parallel mode too
+CREATE VARIABLE var1 AS int;
+LET var1 = 10;
+
+CREATE VIEW var1view AS SELECT COALESCE(var1, 0) AS result;
+
+SELECT * FROM var1view;
+
+SET debug_parallel_query TO on;
+
+SELECT * FROM var1view;
+
+SET debug_parallel_query TO off;
+
+DROP VIEW var1view;
+DROP VARIABLE var1;
+
+CREATE VARIABLE varid int;
+CREATE TABLE svar_test(id int, v int);
+
+LET varid = 1;
+INSERT INTO svar_test VALUES(varid, 100);
+SELECT * FROM svar_test;
+UPDATE svar_test SET v = 200 WHERE id = varid;
+SELECT * FROM svar_test;
+DELETE FROM svar_test WHERE id = varid;
+SELECT * FROM svar_test;
+
+DROP TABLE svar_test;
+DROP VARIABLE varid;
+
+
+-- visibility check
+-- variables should be shadowed always
+CREATE VARIABLE var1 AS text;
+SELECT var1.relname FROM pg_class var1 WHERE var1.relname = 'pg_class';
+
+DROP VARIABLE var1;
+
+CREATE TABLE xxtab(avar int);
+
+INSERT INTO xxtab VALUES(333);
+
+CREATE TYPE xxtype AS (avar int);
+
+CREATE VARIABLE xxtab AS xxtype;
+
+INSERT INTO xxtab VALUES(10);
+
+-- it is ambiguous, but columns are preferred
+SELECT xxtab.avar FROM xxtab;
+
+-- should be ok
+SELECT avar FROM xxtab;
+
+CREATE VARIABLE public.avar AS int;
+
+-- should be ok, see the table
+SELECT avar FROM xxtab;
+
+-- should be ok
+SELECT public.avar FROM xxtab;
+
+DROP VARIABLE xxtab;
+
+SELECT xxtab.avar FROM xxtab;
+
+DROP VARIABLE public.avar;
+
+DROP TYPE xxtype;
+
+DROP TABLE xxtab;
+
+-- The variable can be shadowed by table or by alias
+CREATE TYPE public.svar_type AS (a int, b int, c int);
+CREATE VARIABLE public.svar AS public.svar_type;
+
+CREATE TABLE public.svar(a int, b int);
+
+INSERT INTO public.svar VALUES(10, 20);
+
+LET public.svar = (100, 200, 300);
+
+-- should be ok
+-- show table
+SELECT * FROM public.svar;
+SELECT svar.a FROM public.svar;
+SELECT svar.* FROM public.svar;
+
+-- show variable
+SELECT public.svar;
+SELECT public.svar.c;
+SELECT (public.svar).*;
+
+-- the variable is shadowed, raise error
+SELECT public.svar.c FROM public.svar;
+
+-- can be fixed by alias
+SELECT public.svar.c FROM public.svar x;
+
+SELECT svar.a FROM public.svar;
+SELECT svar.* FROM public.svar;
+
+-- show variable
+SELECT public.svar;
+SELECT public.svar.c;
+SELECT (public.svar).*;
+
+-- the variable is shadowed, raise error
+SELECT public.svar.c FROM public.svar;
+
+-- can be fixed by alias
+SELECT public.svar.c FROM public.svar x;
+
+DROP VARIABLE public.svar;
+DROP TABLE public.svar;
+DROP TYPE public.svar_type;
+
+CREATE TYPE ab AS (a integer, b integer);
+
+CREATE VARIABLE v_ab AS ab;
+
+CREATE TABLE v_ab (a integer, b integer);
+INSERT INTO v_ab VALUES(10,20);
+
+-- we should to see table
+SELECT v_ab.a FROM v_ab;
+
+CREATE SCHEMA v_ab;
+
+CREATE VARIABLE v_ab.a AS integer;
+
+-- we should to see table
+SELECT v_ab.a FROM v_ab;
+
+DROP VARIABLE v_ab;
+DROP TABLE v_ab;
+DROP TYPE ab;
+
+CREATE TYPE t_am_type AS (b int);
+CREATE SCHEMA xxx_am;
+
+SET search_path TO public;
+
+CREATE VARIABLE xxx_am AS t_am_type;
+LET xxx_am = ROW(10);
+
+-- should be ok
+SELECT xxx_am;
+
+CREATE VARIABLE xxx_am.b AS int;
+LET :"DBNAME".xxx_am.b = 20;
+
+-- should be still ok
+SELECT xxx_am;
+
+-- should fail, the reference should be ambiguous
+SELECT xxx_am.b;
+
+-- enhanced references should be ok
+SELECT public.xxx_am.b;
+SELECT :"DBNAME".xxx_am.b;
+
+CREATE TABLE xxx_am(b  int);
+INSERT INTO xxx_am VALUES(10);
+
+-- we should to see table
+SELECT xxx_am.b FROM xxx_am;
+SELECT x.b FROM xxx_am x;
+
+DROP TABLE xxx_am;
+DROP VARIABLE public.xxx_am;
+DROP VARIABLE xxx_am.b;
+DROP SCHEMA xxx_am;
+
+CREATE SCHEMA :"DBNAME";
+
+CREATE VARIABLE :"DBNAME".:"DBNAME".:"DBNAME" AS t_am_type;
+CREATE VARIABLE :"DBNAME".:"DBNAME".b AS int;
+
+SET search_path TO :"DBNAME";
+
+-- should be ambiguous
+SELECT :"DBNAME".b;
+
+-- should be ambiguous too
+SELECT :"DBNAME".:"DBNAME".b;
+
+CREATE TABLE :"DBNAME"(b int);
+
+-- should be ok
+SELECT :"DBNAME".b FROM :"DBNAME";
+
+DROP TABLE :"DBNAME";
+
+DROP VARIABLE :"DBNAME".:"DBNAME".b;
+DROP VARIABLE :"DBNAME".:"DBNAME".:"DBNAME";
+DROP SCHEMA :"DBNAME";
+
+RESET search_path;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a18e81125b5..bb9d831481d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1494,6 +1494,7 @@ LargeObjectDesc
 Latch
 LauncherLastStartTimesEntry
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
@@ -2590,6 +2591,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
@@ -2782,6 +2784,9 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariable
+SVariableData
+SVariableState
 Syn
 SyncOps
 SyncRepConfigData
-- 
2.45.2

From 2b3cfa66c3d0bc2b72c68b07c164961e7c852440 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

Enhancing ExplainOnePlan is necessary to be EXPLAIN ANALYZE LET fully workable.
In this case we want to be result of query or expression written to target variable.
---
 doc/src/sgml/ref/explain.sgml                 |  3 +-
 src/backend/commands/explain.c                | 31 ++++++++++++--
 src/backend/commands/prepare.c                |  5 ++-
 src/backend/parser/gram.y                     |  3 +-
 src/include/commands/explain.h                |  3 +-
 .../regress/expected/session_variables.out    | 40 +++++++++++++++++++
 src/test/regress/sql/session_variables.sql    | 21 ++++++++++
 7 files changed, 97 insertions(+), 9 deletions(-)

diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml
index db9d3a8549a..caccc70658c 100644
--- a/doc/src/sgml/ref/explain.sgml
+++ b/doc/src/sgml/ref/explain.sgml
@@ -98,7 +98,8 @@ EXPLAIN [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] <rep
     <command>INSERT</command>, <command>UPDATE</command>,
     <command>DELETE</command>, <command>MERGE</command>,
     <command>CREATE TABLE AS</command>,
-    or <command>EXECUTE</command> statement
+    <command>EXECUTE</command>,
+    or <command>LET</command> statement
     without letting the command affect your data, use this approach:
 <programlisting>
 BEGIN;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 5771aabf40a..8ed28715f3f 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
 #include "commands/createas.h"
 #include "commands/defrem.h"
 #include "commands/prepare.h"
+#include "executor/svariableReceiver.h"
 #include "foreign/fdwapi.h"
 #include "jit/jit.h"
 #include "libpq/pqformat.h"
@@ -507,8 +508,9 @@ standard_ExplainOneQuery(Query *query, int cursorOptions,
 	}
 
 	/* run it (if needed) and produce output */
-	ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
-				   &planduration, (es->buffers ? &bufusage : NULL),
+	ExplainOnePlan(plan, into, query->resultVariable, es, queryString,
+				   params, queryEnv, &planduration,
+				   (es->buffers ? &bufusage : NULL),
 				   es->memory ? &mem_counters : NULL);
 }
 
@@ -592,6 +594,25 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
 		else
 			ExplainDummyGroup("Notify", NULL, es);
 	}
+	else if (IsA(utilityStmt, LetStmt))
+	{
+		LetStmt    *letstmt = (LetStmt *) utilityStmt;
+		List	   *rewritten;
+		Query	   *query;
+
+		if (es->format == EXPLAIN_FORMAT_TEXT)
+			appendStringInfoString(es->str, "SET SESSION VARIABLE\n");
+		else
+			ExplainDummyGroup("Set Session Variable", NULL, es);
+
+		rewritten = QueryRewrite(castNode(Query, copyObject(letstmt->query)));
+
+		Assert(list_length(rewritten) == 1);
+		query = linitial_node(Query, rewritten);
+		ExplainOneQuery(query,
+						CURSOR_OPT_PARALLEL_OK, NULL, es,
+						queryString, params, queryEnv);
+	}
 	else
 	{
 		if (es->format == EXPLAIN_FORMAT_TEXT)
@@ -615,8 +636,8 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
  * to call it.
  */
 void
-ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
-			   const char *queryString, ParamListInfo params,
+ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, Oid targetvar,
+			   ExplainState *es, const char *queryString, ParamListInfo params,
 			   QueryEnvironment *queryEnv, const instr_time *planduration,
 			   const BufferUsage *bufusage,
 			   const MemoryContextCounters *mem_counters)
@@ -665,6 +686,8 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
 	 */
 	if (into)
 		dest = CreateIntoRelDestReceiver(into);
+	else if (OidIsValid(targetvar))
+		dest = CreateVariableDestReceiver(targetvar);
 	else if (es->serialize != EXPLAIN_SERIALIZE_NONE)
 		dest = CreateExplainSerializeDestReceiver(es);
 	else
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 07257d4db94..364df777ea7 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -655,8 +655,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 		PlannedStmt *pstmt = lfirst_node(PlannedStmt, p);
 
 		if (pstmt->commandType != CMD_UTILITY)
-			ExplainOnePlan(pstmt, into, es, query_string, paramLI, queryEnv,
-						   &planduration, (es->buffers ? &bufusage : NULL),
+			ExplainOnePlan(pstmt, into, InvalidOid, es, query_string, paramLI,
+						   queryEnv, &planduration,
+						   (es->buffers ? &bufusage : NULL),
 						   es->memory ? &mem_counters : NULL);
 		else
 			ExplainOneUtility(pstmt->utilityStmt, into, es, query_string,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4ac319bf225..fcacef276cf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -12132,7 +12132,8 @@ ExplainableStmt:
 			| CreateAsStmt
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
-			| ExecuteStmt					/* by default all are $$=$1 */
+			| ExecuteStmt
+			| LetStmt						/* by default all are $$=$1 */
 		;
 
 /*****************************************************************************
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index 9b8b351d9a2..89656625066 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -101,7 +101,8 @@ extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
 							  ExplainState *es, const char *queryString,
 							  ParamListInfo params, QueryEnvironment *queryEnv);
 
-extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into,
+extern void ExplainOnePlan(PlannedStmt *plannedstmt,
+						   IntoClause *into, Oid targetvar,
 						   ExplainState *es, const char *queryString,
 						   ParamListInfo params, QueryEnvironment *queryEnv,
 						   const instr_time *planduration,
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 2f903243767..11eaea669db 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -1270,3 +1270,43 @@ SET session_variables_ambiguity_warning TO off;
 DROP TABLE public.xxtab;
 DROP SCHEMA xxtab CASCADE;
 NOTICE:  drop cascades to session variable xxtab.avar
+CREATE VARIABLE var1 bigint;
+CREATE TABLE var_tab_test_table(a int);
+INSERT INTO var_tab_test_table SELECT * FROM generate_series(1,10);
+VACUUM ANALYZE var_tab_test_table;
+EXPLAIN (COSTS OFF) LET var1 = (SELECT count(*) FROM var_tab_test_table);
+                  QUERY PLAN                  
+----------------------------------------------
+ SET SESSION VARIABLE
+ Result
+   InitPlan 1
+     ->  Aggregate
+           ->  Seq Scan on var_tab_test_table
+(5 rows)
+
+-- should be NULL
+SELECT var1;
+ var1 
+------
+     
+(1 row)
+
+EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE, SUMMARY OFF) LET var1 = (SELECT count(*) FROM var_tab_test_table);
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ SET SESSION VARIABLE
+ Result (actual rows=1 loops=1)
+   InitPlan 1
+     ->  Aggregate (actual rows=1 loops=1)
+           ->  Seq Scan on var_tab_test_table (actual rows=10 loops=1)
+(5 rows)
+
+-- should be 10
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+DROP VARIABLE var1;
+DROP TABLE var_tab_test_table;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index a5d3af6ade1..fa0a57fc77e 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -861,3 +861,24 @@ SET session_variables_ambiguity_warning TO off;
 
 DROP TABLE public.xxtab;
 DROP SCHEMA xxtab CASCADE;
+
+CREATE VARIABLE var1 bigint;
+
+CREATE TABLE var_tab_test_table(a int);
+
+INSERT INTO var_tab_test_table SELECT * FROM generate_series(1,10);
+
+VACUUM ANALYZE var_tab_test_table;
+
+EXPLAIN (COSTS OFF) LET var1 = (SELECT count(*) FROM var_tab_test_table);
+
+-- should be NULL
+SELECT var1;
+
+EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE, SUMMARY OFF) LET var1 = (SELECT count(*) FROM var_tab_test_table);
+
+-- should be 10
+SELECT var1;
+
+DROP VARIABLE var1;
+DROP TABLE var_tab_test_table;
-- 
2.45.2

From e74bbec75c358283d7616609cff6795f87a189b7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---
 src/pl/plpgsql/src/Makefile                   |   3 +-
 .../src/expected/plpgsql_session_variable.out | 382 ++++++++++++++++++
 src/pl/plpgsql/src/meson.build                |   1 +
 .../src/sql/plpgsql_session_variable.sql      | 289 +++++++++++++
 4 files changed, 674 insertions(+), 1 deletion(-)
 create mode 100644 src/pl/plpgsql/src/expected/plpgsql_session_variable.out
 create mode 100644 src/pl/plpgsql/src/sql/plpgsql_session_variable.sql

diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 63cb96fae3e..bbcae27d422 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -35,7 +35,8 @@ REGRESS_OPTS = --dbname=$(PL_TESTDB)
 REGRESS = plpgsql_array plpgsql_cache plpgsql_call plpgsql_control \
 	plpgsql_copy plpgsql_domain plpgsql_misc \
 	plpgsql_record plpgsql_simple plpgsql_transaction \
-	plpgsql_trap plpgsql_trigger plpgsql_varprops
+	plpgsql_trap plpgsql_trigger plpgsql_varprops \
+	plpgsql_session_variable
 
 # where to find gen_keywordlist.pl and subsidiary files
 TOOLSDIR = $(top_srcdir)/src/tools
diff --git a/src/pl/plpgsql/src/expected/plpgsql_session_variable.out b/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
new file mode 100644
index 00000000000..6e8b4134c06
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
@@ -0,0 +1,382 @@
+-- test of session variables
+CREATE VARIABLE plpgsql_sv_var AS numeric;
+LET plpgsql_sv_var = pi();
+-- passing parameters to DO block
+DO $$
+BEGIN
+  RAISE NOTICE 'value of session variable is %', plpgsql_sv_var;
+END;
+$$;
+NOTICE:  value of session variable is 3.14159265358979
+-- passing output from DO block;
+DO $$
+BEGIN
+  LET plpgsql_sv_var = 2 * pi();
+END
+$$;
+SELECT plpgsql_sv_var AS "pi_multiply_2";
+  pi_multiply_2   
+------------------
+ 6.28318530717959
+(1 row)
+
+DROP VARIABLE plpgsql_sv_var;
+-- test access from PL/pgSQL
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS numeric;
+CREATE VARIABLE plpgsql_sv_var3 AS varchar;
+CREATE OR REPLACE FUNCTION writer_func()
+RETURNS void AS $$
+BEGIN
+  LET plpgsql_sv_var1 = 10;
+  LET plpgsql_sv_var2 = pi();
+  -- very long value
+  LET plpgsql_sv_var3 = format('(%s)', repeat('*', 10000));
+END;
+$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION updater_func()
+RETURNS void AS $$
+BEGIN
+  LET plpgsql_sv_var1 = plpgsql_sv_var1 + 100;
+  LET plpgsql_sv_var2 = plpgsql_sv_var2 + 100000000000;
+  -- very long value
+  LET plpgsql_sv_var3 = plpgsql_sv_var3 || format('(%s)', repeat('*', 10000));
+END;
+$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION reader_func()
+RETURNS void AS $$
+BEGIN
+  RAISE NOTICE 'var1 = %', plpgsql_sv_var1;
+  RAISE NOTICE 'var2 = %', plpgsql_sv_var2;
+  RAISE NOTICE 'length of var3 = %', length(plpgsql_sv_var3);
+END;
+$$ LANGUAGE plpgsql;
+-- execute under transaction
+BEGIN;
+SELECT writer_func();
+ writer_func 
+-------------
+ 
+(1 row)
+
+SELECT reader_func();
+NOTICE:  var1 = 10
+NOTICE:  var2 = 3.14159265358979
+NOTICE:  length of var3 = 10002
+ reader_func 
+-------------
+ 
+(1 row)
+
+SELECT updater_func();
+ updater_func 
+--------------
+ 
+(1 row)
+
+SELECT reader_func();
+NOTICE:  var1 = 110
+NOTICE:  var2 = 100000000003.14159265358979
+NOTICE:  length of var3 = 20004
+ reader_func 
+-------------
+ 
+(1 row)
+
+END;
+-- execute out of transaction
+SELECT writer_func();
+ writer_func 
+-------------
+ 
+(1 row)
+
+SELECT reader_func();
+NOTICE:  var1 = 10
+NOTICE:  var2 = 3.14159265358979
+NOTICE:  length of var3 = 10002
+ reader_func 
+-------------
+ 
+(1 row)
+
+SELECT updater_func();
+ updater_func 
+--------------
+ 
+(1 row)
+
+SELECT reader_func();
+NOTICE:  var1 = 110
+NOTICE:  var2 = 100000000003.14159265358979
+NOTICE:  length of var3 = 20004
+ reader_func 
+-------------
+ 
+(1 row)
+
+-- execute inside PL/pgSQL block
+DO $$
+BEGIN
+  PERFORM writer_func();
+  PERFORM reader_func();
+  PERFORM updater_func();
+  PERFORM reader_func();
+END;
+$$;
+NOTICE:  var1 = 10
+NOTICE:  var2 = 3.14159265358979
+NOTICE:  length of var3 = 10002
+NOTICE:  var1 = 110
+NOTICE:  var2 = 100000000003.14159265358979
+NOTICE:  length of var3 = 20004
+-- plan caches should be correctly invalidated
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2, plpgsql_sv_var3;
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS numeric;
+CREATE VARIABLE plpgsql_sv_var3 AS varchar;
+-- should to work again
+DO $$
+BEGIN
+  PERFORM writer_func();
+  PERFORM reader_func();
+  PERFORM updater_func();
+  PERFORM reader_func();
+END;
+$$;
+NOTICE:  var1 = 10
+NOTICE:  var2 = 3.14159265358979
+NOTICE:  length of var3 = 10002
+NOTICE:  var1 = 110
+NOTICE:  var2 = 100000000003.14159265358979
+NOTICE:  length of var3 = 20004
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2, plpgsql_sv_var3;
+DROP FUNCTION writer_func;
+DROP FUNCTION reader_func;
+DROP FUNCTION updater_func;
+-- another check of correct plan cache invalidation
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS int[];
+CREATE OR REPLACE FUNCTION test_func()
+RETURNS void AS $$
+DECLARE v int[] DEFAULT '{}';
+BEGIN
+  LET plpgsql_sv_var1 = 1;
+  v[plpgsql_sv_var1] = 100;
+  RAISE NOTICE '%', v;
+  LET plpgsql_sv_var2 = v;
+  LET plpgsql_sv_var2[plpgsql_sv_var1] = -1;
+  RAISE NOTICE '%', plpgsql_sv_var2;
+END;
+$$ LANGUAGE plpgsql;
+SELECT test_func();
+NOTICE:  {100}
+NOTICE:  {-1}
+ test_func 
+-----------
+ 
+(1 row)
+
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2;
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS int[];
+SELECT test_func();
+NOTICE:  {100}
+NOTICE:  {-1}
+ test_func 
+-----------
+ 
+(1 row)
+
+DROP FUNCTION test_func();
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2;
+-- check secure access
+CREATE ROLE regress_var_owner_role;
+CREATE ROLE regress_var_reader_role;
+CREATE ROLE regress_var_exec_role;
+GRANT ALL ON SCHEMA public TO regress_var_owner_role, regress_var_reader_role, regress_var_exec_role;
+SET ROLE TO regress_var_owner_role;
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+LET plpgsql_sv_var1 = 10;
+SET ROLE TO DEFAULT;
+SET ROLE TO regress_var_reader_role;
+CREATE OR REPLACE FUNCTION var_read_func()
+RETURNS void AS $$
+BEGIN
+  RAISE NOTICE '%', plpgsql_sv_var1;
+END;
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+SET ROLE TO DEFAULT;
+SET ROLE TO regress_var_exec_role;
+-- should to fail
+SELECT var_read_func();
+ERROR:  permission denied for session variable plpgsql_sv_var1
+CONTEXT:  SQL expression "plpgsql_sv_var1"
+PL/pgSQL function var_read_func() line 3 at RAISE
+SET ROLE TO DEFAULT;
+SET ROLE TO regress_var_owner_role;
+GRANT SELECT ON VARIABLE plpgsql_sv_var1 TO regress_var_reader_role;
+SET ROLE TO DEFAULT;
+SET ROLE TO regress_var_exec_role;
+-- should be ok
+SELECT var_read_func();
+NOTICE:  10
+ var_read_func 
+---------------
+ 
+(1 row)
+
+SET ROLE TO DEFAULT;
+SET ROLE TO regress_var_owner_role;
+DROP VARIABLE plpgsql_sv_var1;
+SET ROLE TO DEFAULT;
+SET ROLE TO regress_var_exec_role;
+-- should to fail, but not crash
+SELECT var_read_func();
+ERROR:  column "plpgsql_sv_var1" does not exist
+LINE 1: plpgsql_sv_var1
+        ^
+QUERY:  plpgsql_sv_var1
+CONTEXT:  PL/pgSQL function var_read_func() line 3 at RAISE
+SET ROLE TO DEFAULT;
+DROP FUNCTION var_read_func;
+REVOKE ALL ON SCHEMA public FROM regress_var_owner_role, regress_var_reader_role, regress_var_exec_role;
+DROP ROLE regress_var_owner_role;
+DROP ROLE regress_var_reader_role;
+DROP ROLE regress_var_exec_role;
+-- returns updated value
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE OR REPLACE FUNCTION inc_var_int(int)
+RETURNS int AS $$
+BEGIN
+  LET plpgsql_sv_var1 = COALESCE(plpgsql_sv_var1 + $1, $1);
+  RETURN plpgsql_sv_var1;
+END;
+$$ LANGUAGE plpgsql;
+SELECT inc_var_int(1);
+ inc_var_int 
+-------------
+           1
+(1 row)
+
+SELECT inc_var_int(1);
+ inc_var_int 
+-------------
+           2
+(1 row)
+
+SELECT inc_var_int(1);
+ inc_var_int 
+-------------
+           3
+(1 row)
+
+SELECT inc_var_int(1) FROM generate_series(1,10);
+ inc_var_int 
+-------------
+           4
+           5
+           6
+           7
+           8
+           9
+          10
+          11
+          12
+          13
+(10 rows)
+
+CREATE VARIABLE plpgsql_sv_var2 AS numeric;
+LET plpgsql_sv_var2 = 0.0;
+CREATE OR REPLACE FUNCTION inc_var_num(numeric)
+RETURNS int AS $$
+BEGIN
+  LET plpgsql_sv_var2 = COALESCE(plpgsql_sv_var2 + $1, $1);
+  RETURN plpgsql_sv_var2;
+END;
+$$ LANGUAGE plpgsql;
+SELECT inc_var_num(1.0);
+ inc_var_num 
+-------------
+           1
+(1 row)
+
+SELECT inc_var_num(1.0);
+ inc_var_num 
+-------------
+           2
+(1 row)
+
+SELECT inc_var_num(1.0);
+ inc_var_num 
+-------------
+           3
+(1 row)
+
+SELECT inc_var_num(1.0) FROM generate_series(1,10);
+ inc_var_num 
+-------------
+           4
+           5
+           6
+           7
+           8
+           9
+          10
+          11
+          12
+          13
+(10 rows)
+
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2;
+DROP FUNCTION inc_var_int;
+DROP FUNCTION inc_var_num;
+-- plpgsql variables are preferred against session variables
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+DO $$
+<<myblock>>
+DECLARE plpgsql_sv_var1 int;
+BEGIN
+  LET plpgsql_sv_var1 = 100;
+
+  plpgsql_sv_var1 := 1000;
+
+  -- print 100;
+  RAISE NOTICE 'session variable is %', public.plpgsql_sv_var1;
+
+  -- print 1000
+  RAISE NOTICE 'plpgsql variable is %', myblock.plpgsql_sv_var1;
+
+  -- print 1000
+  RAISE NOTICE 'variable is %', plpgsql_sv_var1;
+END;
+$$;
+NOTICE:  session variable is 100
+NOTICE:  plpgsql variable is 1000
+NOTICE:  variable is 1000
+DROP VARIABLE plpgsql_sv_var1;
+-- the value should not be corrupted
+CREATE VARIABLE plpgsql_sv_v text;
+LET plpgsql_sv_v = 'abc';
+CREATE FUNCTION ffunc()
+RETURNS text AS $$
+BEGIN
+  RETURN gfunc(plpgsql_sv_v);
+END
+$$ LANGUAGE plpgsql;
+CREATE FUNCTION gfunc(t text)
+RETURNS text AS $$
+BEGIN
+  LET plpgsql_sv_v = 'BOOM!';
+  RETURN t;
+END;
+$$ LANGUAGE plpgsql;
+select ffunc();
+ ffunc 
+-------
+ abc
+(1 row)
+
+DROP FUNCTION ffunc();
+DROP FUNCTION gfunc(text);
+DROP VARIABLE plpgsql_sv_v;
diff --git a/src/pl/plpgsql/src/meson.build b/src/pl/plpgsql/src/meson.build
index 3dd734b776b..3905c0b6d45 100644
--- a/src/pl/plpgsql/src/meson.build
+++ b/src/pl/plpgsql/src/meson.build
@@ -88,6 +88,7 @@ tests += {
       'plpgsql_trap',
       'plpgsql_trigger',
       'plpgsql_varprops',
+      'plpgsql_session_variable',
     ],
   },
 }
diff --git a/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql b/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql
new file mode 100644
index 00000000000..398e7fe2a7c
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql
@@ -0,0 +1,289 @@
+-- test of session variables
+CREATE VARIABLE plpgsql_sv_var AS numeric;
+
+LET plpgsql_sv_var = pi();
+
+-- passing parameters to DO block
+DO $$
+BEGIN
+  RAISE NOTICE 'value of session variable is %', plpgsql_sv_var;
+END;
+$$;
+
+-- passing output from DO block;
+DO $$
+BEGIN
+  LET plpgsql_sv_var = 2 * pi();
+END
+$$;
+
+SELECT plpgsql_sv_var AS "pi_multiply_2";
+
+DROP VARIABLE plpgsql_sv_var;
+
+-- test access from PL/pgSQL
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS numeric;
+CREATE VARIABLE plpgsql_sv_var3 AS varchar;
+
+CREATE OR REPLACE FUNCTION writer_func()
+RETURNS void AS $$
+BEGIN
+  LET plpgsql_sv_var1 = 10;
+  LET plpgsql_sv_var2 = pi();
+  -- very long value
+  LET plpgsql_sv_var3 = format('(%s)', repeat('*', 10000));
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION updater_func()
+RETURNS void AS $$
+BEGIN
+  LET plpgsql_sv_var1 = plpgsql_sv_var1 + 100;
+  LET plpgsql_sv_var2 = plpgsql_sv_var2 + 100000000000;
+  -- very long value
+  LET plpgsql_sv_var3 = plpgsql_sv_var3 || format('(%s)', repeat('*', 10000));
+END;
+$$ LANGUAGE plpgsql;
+
+
+CREATE OR REPLACE FUNCTION reader_func()
+RETURNS void AS $$
+BEGIN
+  RAISE NOTICE 'var1 = %', plpgsql_sv_var1;
+  RAISE NOTICE 'var2 = %', plpgsql_sv_var2;
+  RAISE NOTICE 'length of var3 = %', length(plpgsql_sv_var3);
+END;
+$$ LANGUAGE plpgsql;
+
+-- execute under transaction
+BEGIN;
+SELECT writer_func();
+SELECT reader_func();
+SELECT updater_func();
+SELECT reader_func();
+END;
+
+-- execute out of transaction
+SELECT writer_func();
+SELECT reader_func();
+SELECT updater_func();
+SELECT reader_func();
+
+-- execute inside PL/pgSQL block
+DO $$
+BEGIN
+  PERFORM writer_func();
+  PERFORM reader_func();
+  PERFORM updater_func();
+  PERFORM reader_func();
+END;
+$$;
+
+-- plan caches should be correctly invalidated
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2, plpgsql_sv_var3;
+
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS numeric;
+CREATE VARIABLE plpgsql_sv_var3 AS varchar;
+
+-- should to work again
+DO $$
+BEGIN
+  PERFORM writer_func();
+  PERFORM reader_func();
+  PERFORM updater_func();
+  PERFORM reader_func();
+END;
+$$;
+
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2, plpgsql_sv_var3;
+
+DROP FUNCTION writer_func;
+DROP FUNCTION reader_func;
+DROP FUNCTION updater_func;
+
+-- another check of correct plan cache invalidation
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS int[];
+
+CREATE OR REPLACE FUNCTION test_func()
+RETURNS void AS $$
+DECLARE v int[] DEFAULT '{}';
+BEGIN
+  LET plpgsql_sv_var1 = 1;
+  v[plpgsql_sv_var1] = 100;
+  RAISE NOTICE '%', v;
+  LET plpgsql_sv_var2 = v;
+  LET plpgsql_sv_var2[plpgsql_sv_var1] = -1;
+  RAISE NOTICE '%', plpgsql_sv_var2;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT test_func();
+
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2;
+
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS int[];
+
+SELECT test_func();
+
+DROP FUNCTION test_func();
+
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2;
+
+-- check secure access
+CREATE ROLE regress_var_owner_role;
+CREATE ROLE regress_var_reader_role;
+CREATE ROLE regress_var_exec_role;
+
+GRANT ALL ON SCHEMA public TO regress_var_owner_role, regress_var_reader_role, regress_var_exec_role;
+
+SET ROLE TO regress_var_owner_role;
+
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+LET plpgsql_sv_var1 = 10;
+
+SET ROLE TO DEFAULT;
+
+SET ROLE TO regress_var_reader_role;
+
+CREATE OR REPLACE FUNCTION var_read_func()
+RETURNS void AS $$
+BEGIN
+  RAISE NOTICE '%', plpgsql_sv_var1;
+END;
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+
+SET ROLE TO DEFAULT;
+
+SET ROLE TO regress_var_exec_role;
+
+-- should to fail
+SELECT var_read_func();
+
+SET ROLE TO DEFAULT;
+
+SET ROLE TO regress_var_owner_role;
+GRANT SELECT ON VARIABLE plpgsql_sv_var1 TO regress_var_reader_role;
+
+SET ROLE TO DEFAULT;
+
+SET ROLE TO regress_var_exec_role;
+
+-- should be ok
+SELECT var_read_func();
+
+SET ROLE TO DEFAULT;
+
+SET ROLE TO regress_var_owner_role;
+
+DROP VARIABLE plpgsql_sv_var1;
+
+SET ROLE TO DEFAULT;
+
+SET ROLE TO regress_var_exec_role;
+
+-- should to fail, but not crash
+SELECT var_read_func();
+
+SET ROLE TO DEFAULT;
+
+DROP FUNCTION var_read_func;
+
+REVOKE ALL ON SCHEMA public FROM regress_var_owner_role, regress_var_reader_role, regress_var_exec_role;
+
+DROP ROLE regress_var_owner_role;
+DROP ROLE regress_var_reader_role;
+DROP ROLE regress_var_exec_role;
+
+-- returns updated value
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+
+CREATE OR REPLACE FUNCTION inc_var_int(int)
+RETURNS int AS $$
+BEGIN
+  LET plpgsql_sv_var1 = COALESCE(plpgsql_sv_var1 + $1, $1);
+  RETURN plpgsql_sv_var1;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT inc_var_int(1);
+SELECT inc_var_int(1);
+SELECT inc_var_int(1);
+
+SELECT inc_var_int(1) FROM generate_series(1,10);
+
+CREATE VARIABLE plpgsql_sv_var2 AS numeric;
+
+LET plpgsql_sv_var2 = 0.0;
+
+CREATE OR REPLACE FUNCTION inc_var_num(numeric)
+RETURNS int AS $$
+BEGIN
+  LET plpgsql_sv_var2 = COALESCE(plpgsql_sv_var2 + $1, $1);
+  RETURN plpgsql_sv_var2;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT inc_var_num(1.0);
+SELECT inc_var_num(1.0);
+SELECT inc_var_num(1.0);
+
+SELECT inc_var_num(1.0) FROM generate_series(1,10);
+
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2;
+
+DROP FUNCTION inc_var_int;
+DROP FUNCTION inc_var_num;
+
+-- plpgsql variables are preferred against session variables
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+
+DO $$
+<<myblock>>
+DECLARE plpgsql_sv_var1 int;
+BEGIN
+  LET plpgsql_sv_var1 = 100;
+
+  plpgsql_sv_var1 := 1000;
+
+  -- print 100;
+  RAISE NOTICE 'session variable is %', public.plpgsql_sv_var1;
+
+  -- print 1000
+  RAISE NOTICE 'plpgsql variable is %', myblock.plpgsql_sv_var1;
+
+  -- print 1000
+  RAISE NOTICE 'variable is %', plpgsql_sv_var1;
+END;
+$$;
+
+DROP VARIABLE plpgsql_sv_var1;
+
+-- the value should not be corrupted
+CREATE VARIABLE plpgsql_sv_v text;
+LET plpgsql_sv_v = 'abc';
+
+CREATE FUNCTION ffunc()
+RETURNS text AS $$
+BEGIN
+  RETURN gfunc(plpgsql_sv_v);
+END
+$$ LANGUAGE plpgsql;
+
+CREATE FUNCTION gfunc(t text)
+RETURNS text AS $$
+BEGIN
+  LET plpgsql_sv_v = 'BOOM!';
+  RETURN t;
+END;
+$$ LANGUAGE plpgsql;
+
+select ffunc();
+
+DROP FUNCTION ffunc();
+DROP FUNCTION gfunc(text);
+
+DROP VARIABLE plpgsql_sv_v;
-- 
2.45.2

From 3dc59ddce2836b61564c947abb722b70170856f6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

Inside an query the session variables can be shadowed. This behaviour is by design,
because this ensuring so new variables doesn't break existing queries. But this
behaviour can be confusing, because shadowing is quiet.

When new GUC session_variables_ambiguity_warning is on, then the warning
is displayed, when some session variable can be used, but it is not used
due shadowing. This feature should to help with investigation of unexpected
behaviour.

Note: PLpgSQL has an option that controls priority of identifier's spaces
(SQL or PLpgSQL). Default behaviour doesn't allows collisions. I believe
this default is the best. I cannot to implement similar very strict
behaviour to session variables, because one unhappy named session variable
can breaks an applications. The problems related to badly named PLpgSQL's
varible is limitted just to only one routine.
---
 doc/src/sgml/config.sgml                      | 60 +++++++++++++++++++
 doc/src/sgml/ddl.sgml                         |  6 +-
 src/backend/parser/parse_expr.c               | 48 +++++++++++++--
 src/backend/utils/misc/guc_tables.c           |  9 +++
 src/backend/utils/misc/postgresql.conf.sample |  1 +
 src/include/parser/parse_expr.h               |  1 +
 .../src/expected/plpgsql_session_variable.out | 33 ++++++++++
 .../src/sql/plpgsql_session_variable.sql      | 29 +++++++++
 .../regress/expected/session_variables.out    | 22 +++++++
 src/test/regress/sql/session_variables.sql    | 20 +++++++
 10 files changed, 224 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 3dec0b7cfeb..9f14ad36f82 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10685,6 +10685,66 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-session-variables-ambiguity-warning" xreflabel="session_variables_ambiguity_warning">
+      <term><varname>session_variables_ambiguity_warning</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>session_variables_ambiguity_warning</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        When on, a warning is raised when any identifier in a query could be
+        used as both a column identifier, routine variable or a session
+        variable identifier. The default is <literal>off</literal>.
+       </para>
+       <para>
+        Session variables can be shadowed by column references in a query, this
+        is an expected behavior.  Previously working queries shouldn't error out
+        by creating any session variable, so session variables are always shadowed
+        if an identifier is ambiguous.  Variables should be referenced using
+        anunambiguous identifier without any possibility for a collision with
+        identifier of other database objects (column names or record fields names).
+        The warning messages emitted when enabling <varname>session_variables_ambiguity_warning</varname>
+        can help finding such identifier collision.
+<programlisting>
+CREATE TABLE foo(a int);
+INSERT INTO foo VALUES(10);
+CREATE VARIABLE a int;
+LET a = 100;
+SELECT a FROM foo;
+</programlisting>
+
+<screen>
+ a
+----
+ 10
+(1 row)
+</screen>
+
+<programlisting>
+SET session_variables_ambiguity_warning TO on;
+SELECT a FROM foo;
+</programlisting>
+
+<screen>
+WARNING:  session variable "a" is shadowed
+LINE 1: SELECT a FROM foo;
+               ^
+DETAIL:  Session variables can be shadowed by columns, routine's variables and routine's arguments with the same name.
+ a
+----
+ 10
+(1 row)
+</screen>
+       </para>
+       <para>
+        This feature can significantly increase log size, so it's disabled by
+        default.  For testing or development environments it's recommended to
+        enable it if you use session variables.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-standard-conforming-strings" xreflabel="standard_conforming_strings">
       <term><varname>standard_conforming_strings</varname> (<type>boolean</type>)
       <indexterm><primary>strings</primary><secondary>standard conforming</secondary></indexterm>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 92730799f40..9d5082dd3ce 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5353,7 +5353,11 @@ SELECT current_user_id;
     The session variables can be shadowed by column references in a query. When
     a query contains identifiers or qualified identifiers that could be used as
     both a session variable identifiers and as column identifier, then the
-    column identifier is preferred every time.
+    column identifier is preferred every time. Warnings can be emitted when
+    this situation happens by enabling configuration parameter <xref
+    linkend="guc-session-variables-ambiguity-warning"/>. User can explicitly
+    qualify the source object by syntax <literal>table.column</literal> or
+    <literal>schema.variable</literal>.
    </para>
   </sect1>
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 73be2a8e10a..f0ebbcf5beb 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -47,6 +47,7 @@
 
 /* GUC parameters */
 bool		Transform_null_equals = false;
+bool		session_variables_ambiguity_warning = false;
 
 static Node *transformExprRecurse(ParseState *pstate, Node *expr);
 static Node *transformParamRef(ParseState *pstate, ParamRef *pref);
@@ -943,7 +944,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	 * question is if we want to identify session's variables in these
 	 * contexts? The code can be more simple, when we don't do it, but then we
 	 * cannot to raise maybe useful message like "you cannot to use session
-	 * variables here".
+	 * variables here". On second hand, in this case the warnings about
+	 * session's variable shadowing can be messy.
 	 */
 	if (expr_kind_allows_session_variables(pstate->p_expr_kind))
 	{
@@ -962,10 +964,48 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		 * SELECT foo.a, foo.b, foo.c FROM foo;
 		 *
 		 * This case can be messy and then we disallow it. When we know that a
-		 * possible variable will be shadowed, we don't try to identify
-		 * variable.
+		 * possible variable will be shadowed, we try to identify variable
+		 * only when session_variables_ambiguity_warning is requested.
 		 */
-		if (!node && !(relname && crerr == CRERR_NO_COLUMN))
+		if (node || (relname && crerr == CRERR_NO_COLUMN))
+		{
+			/*
+			 * In this path we just try (if it is wanted) detect if session
+			 * variable is shadowed.
+			 */
+			if (session_variables_ambiguity_warning)
+			{
+				/*
+				 * The AccessShareLock is created on related session variable.
+				 * The lock will be kept for the whole transaction.
+				 */
+				varid = IdentifyVariable(cref->fields, &attrname, &not_unique, true);
+
+				if (OidIsValid(varid))
+				{
+					/* This path will ending by WARNING. Unlock variable first */
+					UnlockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+					if (node)
+						ereport(WARNING,
+								(errcode(ERRCODE_AMBIGUOUS_COLUMN),
+								 errmsg("session variable \"%s\" is shadowed",
+										NameListToString(cref->fields)),
+								 errdetail("Session variables can be shadowed by columns, routine's variables and routine's arguments with the same name."),
+								 parser_errposition(pstate, cref->location)));
+					else
+						/* session variable is shadowed by RTE */
+						ereport(WARNING,
+								(errcode(ERRCODE_AMBIGUOUS_COLUMN),
+								 errmsg("session variable \"%s.%s\" is shadowed",
+										get_namespace_name(get_session_variable_namespace(varid)),
+										get_session_variable_name(varid)),
+								 errdetail("Session variables can be shadowed by tables or table's aliases with the same name."),
+								 parser_errposition(pstate, cref->location)));
+				}
+			}
+		}
+		else
 		{
 			/*
 			 * The AccessShareLock is created on related session variable. The
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f6fcdebb031..e8b442dc5d6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -1534,6 +1534,15 @@ struct config_bool ConfigureNamesBool[] =
 		false,
 		NULL, NULL, NULL
 	},
+	{
+		{"session_variables_ambiguity_warning", PGC_USERSET, CLIENT_CONN_OTHER,
+			gettext_noop("Raise a warning when reference to a session variable is ambiguous."),
+			NULL
+		},
+		&session_variables_ambiguity_warning,
+		false,
+		NULL, NULL, NULL
+	},
 	{
 		{"default_transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default read-only status of new transactions."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 9ec9f97e926..ebf6f016d2d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -713,6 +713,7 @@
 #default_transaction_read_only = off
 #default_transaction_deferrable = off
 #session_replication_role = 'origin'
+#session_variables_ambiguity_warning = off
 #statement_timeout = 0				# in milliseconds, 0 is disabled
 #transaction_timeout = 0			# in milliseconds, 0 is disabled
 #lock_timeout = 0				# in milliseconds, 0 is disabled
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index 9b46dfd9ecc..0d64b300f8a 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -17,6 +17,7 @@
 
 /* GUC parameters */
 extern PGDLLIMPORT bool Transform_null_equals;
+extern PGDLLIMPORT bool session_variables_ambiguity_warning;
 
 extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKind);
 
diff --git a/src/pl/plpgsql/src/expected/plpgsql_session_variable.out b/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
index 6e8b4134c06..ec07f1cbae9 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
@@ -354,6 +354,39 @@ $$;
 NOTICE:  session variable is 100
 NOTICE:  plpgsql variable is 1000
 NOTICE:  variable is 1000
+-- againt with session_variables_ambiguity_warning(on)
+SET session_variables_ambiguity_warning TO on;
+DO $$
+<<myblock>>
+DECLARE plpgsql_sv_var1 int;
+BEGIN
+  LET plpgsql_sv_var1 = 100;
+
+  -- should be ok without warning
+  plpgsql_sv_var1 := 1000;
+
+  -- should be ok without warning
+  -- print 100;
+  RAISE NOTICE 'session variable is %', public.plpgsql_sv_var1;
+
+  -- should be ok without warning
+  -- print 1000
+  RAISE NOTICE 'plpgsql variable is %', myblock.plpgsql_sv_var1;
+
+  -- should to print plpgsql variable with warning
+  -- print 1000
+  RAISE NOTICE 'variable is %', plpgsql_sv_var1;
+END;
+$$;
+NOTICE:  session variable is 100
+NOTICE:  plpgsql variable is 1000
+WARNING:  session variable "plpgsql_sv_var1" is shadowed
+LINE 1: plpgsql_sv_var1
+        ^
+DETAIL:  Session variables can be shadowed by columns, routine's variables and routine's arguments with the same name.
+QUERY:  plpgsql_sv_var1
+NOTICE:  variable is 1000
+SET session_variables_ambiguity_warning TO off;
 DROP VARIABLE plpgsql_sv_var1;
 -- the value should not be corrupted
 CREATE VARIABLE plpgsql_sv_v text;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql b/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql
index 398e7fe2a7c..ef705a4cc87 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql
@@ -260,6 +260,35 @@ BEGIN
 END;
 $$;
 
+-- againt with session_variables_ambiguity_warning(on)
+
+SET session_variables_ambiguity_warning TO on;
+
+DO $$
+<<myblock>>
+DECLARE plpgsql_sv_var1 int;
+BEGIN
+  LET plpgsql_sv_var1 = 100;
+
+  -- should be ok without warning
+  plpgsql_sv_var1 := 1000;
+
+  -- should be ok without warning
+  -- print 100;
+  RAISE NOTICE 'session variable is %', public.plpgsql_sv_var1;
+
+  -- should be ok without warning
+  -- print 1000
+  RAISE NOTICE 'plpgsql variable is %', myblock.plpgsql_sv_var1;
+
+  -- should to print plpgsql variable with warning
+  -- print 1000
+  RAISE NOTICE 'variable is %', plpgsql_sv_var1;
+END;
+$$;
+
+SET session_variables_ambiguity_warning TO off;
+
 DROP VARIABLE plpgsql_sv_var1;
 
 -- the value should not be corrupted
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index fca21f4137d..2f903243767 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -1248,3 +1248,25 @@ SELECT var1;
 (1 row)
 
 DROP VARIABLE var1, var2;
+-- test session_variables_ambiguity_warning
+CREATE SCHEMA xxtab;
+CREATE VARIABLE xxtab.avar int;
+CREATE TABLE public.xxtab(avar int);
+INSERT INTO public.xxtab VALUES(1);
+LET xxtab.avar = 20;
+SET session_variables_ambiguity_warning TO on;
+--- should to raise warning, show 1
+SELECT xxtab.avar FROM public.xxtab;
+WARNING:  session variable "xxtab.avar" is shadowed
+LINE 1: SELECT xxtab.avar FROM public.xxtab;
+               ^
+DETAIL:  Session variables can be shadowed by columns, routine's variables and routine's arguments with the same name.
+ avar 
+------
+    1
+(1 row)
+
+SET session_variables_ambiguity_warning TO off;
+DROP TABLE public.xxtab;
+DROP SCHEMA xxtab CASCADE;
+NOTICE:  drop cascades to session variable xxtab.avar
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 5186cd9dc9f..a5d3af6ade1 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -841,3 +841,23 @@ BEGIN; DROP VARIABLE var1; ROLLBACK;
 SELECT var1;
 
 DROP VARIABLE var1, var2;
+
+-- test session_variables_ambiguity_warning
+CREATE SCHEMA xxtab;
+
+CREATE VARIABLE xxtab.avar int;
+
+CREATE TABLE public.xxtab(avar int);
+
+INSERT INTO public.xxtab VALUES(1);
+
+LET xxtab.avar = 20;
+
+SET session_variables_ambiguity_warning TO on;
+--- should to raise warning, show 1
+SELECT xxtab.avar FROM public.xxtab;
+
+SET session_variables_ambiguity_warning TO off;
+
+DROP TABLE public.xxtab;
+DROP SCHEMA xxtab CASCADE;
-- 
2.45.2

From ee1120816b682397bc06e45e3461f59df63c183f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

In current code base it requires just small changes in parser. It is nice
to have to have possibility to use prepared LET statements mainly due reduction
of security risks (SQL injection), and implementation is almost cheap.

Explicit preparing is not necessity for support of LET statements in PL, because
PL uses SPI for query execution, but it is nice to have this possibility (completenees, ...)
---
 doc/src/sgml/ref/prepare.sgml                 |  4 +-
 src/backend/parser/gram.y                     |  3 +-
 src/backend/parser/parse_cte.c                |  7 ++
 src/bin/psql/tab-complete.c                   |  4 +-
 .../regress/expected/session_variables.out    | 81 ++++++++++++++++++-
 src/test/regress/sql/session_variables.sql    | 57 +++++++++++++
 6 files changed, 149 insertions(+), 7 deletions(-)

diff --git a/doc/src/sgml/ref/prepare.sgml b/doc/src/sgml/ref/prepare.sgml
index 8ee9439f611..45d72b1d52b 100644
--- a/doc/src/sgml/ref/prepare.sgml
+++ b/doc/src/sgml/ref/prepare.sgml
@@ -116,8 +116,8 @@ PREPARE <replaceable class="parameter">name</replaceable> [ ( <replaceable class
     <listitem>
      <para>
       Any <command>SELECT</command>, <command>INSERT</command>, <command>UPDATE</command>,
-      <command>DELETE</command>, <command>MERGE</command>, or <command>VALUES</command>
-      statement.
+      <command>DELETE</command>, <command>MERGE</command>, <command>VALUES</command>,
+      or <command>LET</command> statement.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index fcacef276cf..c17a71f6656 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -12163,7 +12163,8 @@ PreparableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt
-			| MergeStmt						/* by default all are $$=$1 */
+			| MergeStmt
+			| LetStmt						/* by default all are $$=$1 */
 		;
 
 /*****************************************************************************
diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c
index de9ae9b4834..b4b9b5b61c8 100644
--- a/src/backend/parser/parse_cte.c
+++ b/src/backend/parser/parse_cte.c
@@ -126,6 +126,13 @@ transformWithClause(ParseState *pstate, WithClause *withClause)
 		CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
 		ListCell   *rest;
 
+		/* LET is allowed by parser, but not supported. Reject for now */
+		if (IsA(cte->ctequery, LetStmt))
+			ereport(ERROR,
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("LET not supported in WITH query"),
+					parser_errposition(pstate, cte->location));
+
 		for_each_cell(rest, withClause->ctes, lnext(withClause->ctes, lc))
 		{
 			CommonTableExpr *cte2 = (CommonTableExpr *) lfirst(rest);
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index df4c9cb4eef..474dd1d569b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4288,9 +4288,9 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("VALUES") && !TailMatches("DEFAULT", "VALUES"))
 		COMPLETE_WITH("(");
 
-/* LET */
+/* LET, EXPLAIN LET, PREPARE LET */
 	/* If prev. word is LET suggest a list of variables */
-	else if (Matches("LET"))
+	else if (TailMatches("LET"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
 	/* Complete LET <variable> with "=" */
 	else if (TailMatches("LET", MatchAny))
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 11eaea669db..358502fa31c 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -428,9 +428,9 @@ DROP VARIABLE var1, var2;
 -- the LET statement should be disallowed in CTE
 CREATE VARIABLE var1 AS int;
 WITH x AS (LET var1 = 100) SELECT * FROM x;
-ERROR:  syntax error at or near "LET"
+ERROR:  LET not supported in WITH query
 LINE 1: WITH x AS (LET var1 = 100) SELECT * FROM x;
-                   ^
+             ^
 -- should be ok
 LET var1 = generate_series(1, 1);
 -- should fail
@@ -1308,5 +1308,82 @@ SELECT var1;
    10
 (1 row)
 
+SET plan_cache_mode TO force_generic_plan;
+PREPARE p1 AS LET var1 = (SELECT count(*) FROM var_tab_test_table);
+LET var1 = NULL;
+EXPLAIN (COSTS OFF) EXECUTE p1;
+                  QUERY PLAN                  
+----------------------------------------------
+ SET SESSION VARIABLE
+ Result
+   InitPlan 1
+     ->  Aggregate
+           ->  Seq Scan on var_tab_test_table
+(5 rows)
+
+-- should be NULL
+SELECT var1;
+ var1 
+------
+     
+(1 row)
+
+EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE, SUMMARY OFF) EXECUTE p1;
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ SET SESSION VARIABLE
+ Result (actual rows=1 loops=1)
+   InitPlan 1
+     ->  Aggregate (actual rows=1 loops=1)
+           ->  Seq Scan on var_tab_test_table (actual rows=10 loops=1)
+(5 rows)
+
+-- should be 10
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+SET plan_cache_mode TO DEFAULT;
+DEALLOCATE p1;
 DROP VARIABLE var1;
 DROP TABLE var_tab_test_table;
+CREATE VARIABLE var1 numeric;
+SET plan_cache_mode TO force_generic_plan;
+PREPARE p1(numeric) AS LET var1 = $1;
+PREPARE p2 AS SELECT var1;
+EXECUTE p1(pi() + 100);
+EXECUTE p2;
+      var1       
+-----------------
+ 103.14159265359
+(1 row)
+
+-- prepared plan cache invalidation test
+DROP VARIABLE var1;
+CREATE VARIABLE var1 numeric;
+-- should be NULL
+EXECUTE p2;
+ var1 
+------
+     
+(1 row)
+
+DEALLOCATE p1;
+DEALLOCATE p2;
+DROP VARIABLE var1;
+SET plan_cache_mode TO force_generic_plan;
+CREATE VARIABLE var1 numeric[];
+PREPARE p1(int, numeric) AS LET var1[$1] = $2;
+LET var1 = '{}'::numeric[];
+EXECUTE p1(1, 10.2);
+EXECUTE p1(2, 10.3);
+SELECT var1;
+    var1     
+-------------
+ {10.2,10.3}
+(1 row)
+
+DEALLOCATE p1;
+DROP VARIABLE var1;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index fa0a57fc77e..3436b57e8c7 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -880,5 +880,62 @@ EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE, SUMMARY OFF) LET var1 = (SELECT count(*
 -- should be 10
 SELECT var1;
 
+SET plan_cache_mode TO force_generic_plan;
+
+PREPARE p1 AS LET var1 = (SELECT count(*) FROM var_tab_test_table);
+
+LET var1 = NULL;
+
+EXPLAIN (COSTS OFF) EXECUTE p1;
+
+-- should be NULL
+SELECT var1;
+
+EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE, SUMMARY OFF) EXECUTE p1;
+
+-- should be 10
+SELECT var1;
+
+SET plan_cache_mode TO DEFAULT;
+
+DEALLOCATE p1;
+
 DROP VARIABLE var1;
 DROP TABLE var_tab_test_table;
+
+CREATE VARIABLE var1 numeric;
+
+SET plan_cache_mode TO force_generic_plan;
+
+PREPARE p1(numeric) AS LET var1 = $1;
+PREPARE p2 AS SELECT var1;
+
+EXECUTE p1(pi() + 100);
+EXECUTE p2;
+
+-- prepared plan cache invalidation test
+DROP VARIABLE var1;
+CREATE VARIABLE var1 numeric;
+
+-- should be NULL
+EXECUTE p2;
+
+DEALLOCATE p1;
+DEALLOCATE p2;
+
+DROP VARIABLE var1;
+
+SET plan_cache_mode TO force_generic_plan;
+
+CREATE VARIABLE var1 numeric[];
+
+PREPARE p1(int, numeric) AS LET var1[$1] = $2;
+
+LET var1 = '{}'::numeric[];
+EXECUTE p1(1, 10.2);
+EXECUTE p1(2, 10.3);
+
+SELECT var1;
+
+DEALLOCATE p1;
+DROP VARIABLE var1;
-- 
2.45.2

From 8c8f88a1c6bf9ae737ad31eb54e3246521debebe Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

This is simple patch - just add special flag to session variable memory entry.
The entries with active this flag are removed from sessionvars hash table
at transaction end. The "TRANSACTION END" is synonyms for "COMMIT ROLLBACK"
but the "TRANSACTION END" is more illustrative and less confusing then "COMMIT ROLLBACK".
---
 doc/src/sgml/catalogs.sgml                    |  3 +-
 doc/src/sgml/ref/create_variable.sgml         | 13 ++++-
 src/backend/commands/session_variable.c       | 51 +++++++++++++++++++
 src/backend/parser/gram.y                     |  1 +
 src/bin/pg_dump/pg_dump.c                     | 11 ++++
 src/bin/pg_dump/pg_dump.h                     |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl              | 18 +++++++
 src/bin/psql/describe.c                       |  1 +
 src/include/catalog/pg_variable.h             |  1 +
 .../isolation/expected/session-variable.out   |  4 +-
 .../isolation/specs/session-variable.spec     |  4 +-
 .../regress/expected/session_variables.out    | 34 +++++++++++++
 src/test/regress/sql/session_variables.sql    | 20 ++++++++
 13 files changed, 158 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 81b629a9f41..850060aac9f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9838,7 +9838,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
       <para>
        Action performed at end of transaction:
-       <literal>n</literal> = no action, <literal>d</literal> = drop the variable.
+       <literal>n</literal> = no action, <literal>d</literal> = drop the variable,
+       <literal>r</literal> = reset the variable to its default value.
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index 4d37611a1fe..214f565f172 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -27,7 +27,7 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
-    [ ON COMMIT DROP ]
+    [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
 </synopsis>
  </refsynopsisdiv>
  <refsect1>
@@ -121,6 +121,17 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="p
     </listitem>
    </varlistentry>
 
+   <varlistentry id="sql-createvariable-on-transaction-end-reset">
+    <term><literal>ON TRANSACTION END RESET</literal></term>
+    <listitem>
+     <para>
+      The <literal>ON TRANSACTION END RESET</literal> clause causes the session
+      variable to be reset to its default value when the transaction is committed
+      or rolled back.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
  </refsect1>
 
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 3a3489ec743..d65a92d85a4 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -91,6 +91,8 @@ typedef struct SVariableData
 	void	   *domain_check_extra;
 	LocalTransactionId domain_check_extra_lxid;
 
+	bool		reset_at_eox;
+
 	/*
 	 * Top level local transaction id of the last transaction that dropped the
 	 * variable if any.  We need this information to avoid freeing memory for
@@ -118,6 +120,12 @@ static MemoryContext SVariableMemoryContext = NULL;
 /* true after accepted sinval message */
 static bool needs_validation = false;
 
+/*
+ * true, when some used session variable has ON COMMIT DROP
+ * or ON TRANSACTION END RESET clauses
+ */
+static bool has_session_variables_with_reset_at_eox = false;
+
 /*
  * The content of session variables is not removed immediately. When it
  * is possible we do this at the transaction end. But when the transaction failed,
@@ -398,6 +406,32 @@ remove_invalid_session_variables(bool atEOX)
 	}
 }
 
+/*
+ * remove entries marked as "reset_at_eox"
+ */
+static void
+remove_session_variables_with_reset_at_eox(void)
+{
+	HASH_SEQ_STATUS status;
+	SVariable	svar;
+
+	if (!sessionvars)
+		return;
+
+	/* leave quckly, when there are not that variables */
+	if (!has_session_variables_with_reset_at_eox)
+		return;
+
+	hash_seq_init(&status, sessionvars);
+	while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+	{
+		if (svar->reset_at_eox)
+			hash_search(sessionvars, &svar->varid, HASH_REMOVE, NULL);
+	}
+
+	has_session_variables_with_reset_at_eox = false;
+}
+
 /*
   * Perform ON COMMIT DROP for temporary session variables,
   * and remove all dropped variables from memory.
@@ -405,6 +439,8 @@ remove_invalid_session_variables(bool atEOX)
 void
 AtPreEOXact_SessionVariables(bool isCommit)
 {
+	remove_session_variables_with_reset_at_eox();
+
 	if (isCommit)
 	{
 		if (xact_drop_items)
@@ -516,6 +552,21 @@ setup_session_variable(SVariable svar, Oid varid)
 	svar->domain_check_extra = NULL;
 	svar->domain_check_extra_lxid = InvalidLocalTransactionId;
 
+	/*
+	 * We don't need to explicitly reset variables marked ON COMMIT DROP. It
+	 * can be done by sinval message processing. But this processing can be
+	 * postponed due aborted transaction. On second hand there is not a
+	 * reason, why don't do it at transaction end immediately.
+	 */
+	if (varform->varxactendaction == VARIABLE_XACTEND_RESET ||
+		varform->varxactendaction == VARIABLE_XACTEND_DROP)
+	{
+		svar->reset_at_eox = true;
+		has_session_variables_with_reset_at_eox = true;
+	}
+	else
+		svar->reset_at_eox = false;
+
 	svar->drop_lxid = InvalidTransactionId;
 
 	svar->isnull = true;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 48523e6c3b5..cd95b2bb97d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5251,6 +5251,7 @@ CreateSessionVarStmt:
  * transaction end like tables.
  */
 XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
+			| ON TRANSACTION END_P RESET			{ $$ = VARIABLE_XACTEND_RESET; }
 			| /*EMPTY*/								{ $$ = VARIABLE_XACTEND_NOOP; }
 		;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 64a6336a897..5296a28078b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5330,6 +5330,7 @@ getVariables(Archive *fout)
 	int			i_varnamespace;
 	int			i_vartype;
 	int			i_vartypname;
+	int			i_varxactendaction;
 	int			i_varowner;
 	int			i_varcollation;
 	int			i_varacl;
@@ -5347,6 +5348,7 @@ getVariables(Archive *fout)
 	/* Get the variables in current database. */
 	appendPQExpBuffer(query,
 					  "SELECT v.tableoid, v.oid, v.varname,\n"
+					  "v.varxactendaction,\n"
 					  "v.varnamespace,\n"
 					  "v.vartype,\n"
 					  "pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n"
@@ -5369,6 +5371,7 @@ getVariables(Archive *fout)
 	i_varnamespace = PQfnumber(res, "varnamespace");
 	i_vartype = PQfnumber(res, "vartype");
 	i_vartypname = PQfnumber(res, "vartypname");
+	i_varxactendaction = PQfnumber(res, "varxactendaction");
 	i_varcollation = PQfnumber(res, "varcollation");
 
 	i_varowner = PQfnumber(res, "varowner");
@@ -5392,6 +5395,9 @@ getVariables(Archive *fout)
 
 		varinfo[i].vartype = atooid(PQgetvalue(res, i, i_vartype));
 		varinfo[i].vartypname = pg_strdup(PQgetvalue(res, i, i_vartypname));
+		varinfo[i].varxactendaction =
+			pg_strdup(PQgetvalue(res, i, i_varxactendaction));
+
 		varinfo[i].varcollation = atooid(PQgetvalue(res, i, i_varcollation));
 
 		varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl));
@@ -5435,6 +5441,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	PQExpBuffer query;
 	char	   *qualvarname;
 	const char *vartypname;
+	const char *varxactendaction;
 	Oid			varcollation;
 
 	/* Skip if not to be dumped */
@@ -5446,6 +5453,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
 	qualvarname = pg_strdup(fmtQualifiedDumpable(varinfo));
 	vartypname = varinfo->vartypname;
+	varxactendaction = varinfo->varxactendaction;
 	varcollation = varinfo->varcollation;
 
 	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
@@ -5464,6 +5472,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (strcmp(varxactendaction, "r") == 0)
+		appendPQExpBuffer(query, " ON TRANSACTION END RESET");
+
 	appendPQExpBuffer(query, ";\n");
 
 	if (varinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 36a69a08324..0910507d9ac 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -702,6 +702,7 @@ typedef struct _VariableInfo
 	DumpableAcl dacl;
 	Oid			vartype;
 	char	   *vartypname;
+	char	   *varxactendaction;
 	char	   *varacl;
 	char	   *rvaracl;
 	char	   *initvaracl;
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d62db242287..71f8c713f68 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3941,6 +3941,24 @@ my %tests = (
 		},
 	},
 
+	'CREATE VARIABLE test_variable ON TRANSACTION END RESET' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE VARIABLE dump_test.variable2 AS integer ON TRANSACTION END RESET;',
+		regexp => qr/^
+			\QCREATE VARIABLE dump_test.variable2 AS integer ON TRANSACTION END RESET;\E/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_measurement    => 1,
+		},
+	},
+
 	'CREATE VIEW test_view' => {
 		create_order => 61,
 		create_sql => 'CREATE VIEW dump_test.test_view
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f6aa8bc5f6c..50bae539113 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5182,6 +5182,7 @@ listVariables(const char *pattern, bool verbose)
 					  "  pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n"
 					  "  CASE v.varxactendaction\n"
 					  "    WHEN 'd' THEN 'ON COMMIT DROP'\n"
+					  "    WHEN 'r' THEN 'ON TRANSACTION END RESET'\n"
 					  "  END as \"%s\"\n",
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
index e3aca285a2b..0fc824cfe15 100644
--- a/src/include/catalog/pg_variable.h
+++ b/src/include/catalog/pg_variable.h
@@ -73,6 +73,7 @@ typedef enum VariableXactEndAction
 {
 	VARIABLE_XACTEND_NOOP = 'n',	/* NOOP */
 	VARIABLE_XACTEND_DROP = 'd',	/* ON COMMIT DROP */
+	VARIABLE_XACTEND_RESET = 'r',	/* ON TRANSACTION END RESET */
 }			VariableXactEndAction;
 
 /* ----------------
diff --git a/src/test/isolation/expected/session-variable.out b/src/test/isolation/expected/session-variable.out
index 9fbbf9057a6..19a2e5a8f64 100644
--- a/src/test/isolation/expected/session-variable.out
+++ b/src/test/isolation/expected/session-variable.out
@@ -85,11 +85,12 @@ myvar
 
 step sr1: ROLLBACK;
 
-starting permutation: create3 let3 s3 o_c_d create4 let4 drop4 drop3 inval3 discard sc3 state
+starting permutation: create3 let3 s3 o_c_d o_eox_r create4 let4 drop4 drop3 inval3 discard sc3 clean state
 step create3: CREATE VARIABLE myvar3 AS text;
 step let3: LET myvar3 = 'test';
 step s3: BEGIN;
 step o_c_d: CREATE TEMP VARIABLE myvar_o_c_d AS text ON COMMIT DROP;
+step o_eox_r: CREATE VARIABLE myvar_o_eox_r AS text ON TRANSACTION END RESET; LET myvar_o_eox_r = 'test';
 step create4: CREATE VARIABLE myvar4 AS text;
 step let4: LET myvar4 = 'test';
 step drop4: DROP VARIABLE myvar4;
@@ -102,6 +103,7 @@ t
 
 step discard: DISCARD VARIABLES;
 step sc3: COMMIT;
+step clean: DROP VARIABLE myvar_o_eox_r;
 step state: SELECT varname FROM pg_variable;
 varname
 -------
diff --git a/src/test/isolation/specs/session-variable.spec b/src/test/isolation/specs/session-variable.spec
index 45e65d4085d..5d089c8a4ed 100644
--- a/src/test/isolation/specs/session-variable.spec
+++ b/src/test/isolation/specs/session-variable.spec
@@ -25,12 +25,14 @@ session s3
 step s3			{ BEGIN; }
 step let3		{ LET myvar3 = 'test'; }
 step o_c_d		{ CREATE TEMP VARIABLE myvar_o_c_d AS text ON COMMIT DROP; }
+step o_eox_r	{ CREATE VARIABLE myvar_o_eox_r AS text ON TRANSACTION END RESET; LET myvar_o_eox_r = 'test'; }
 step create4	{ CREATE VARIABLE myvar4 AS text; }
 step let4		{ LET myvar4 = 'test'; }
 step drop4		{ DROP VARIABLE myvar4; }
 step inval3		{ SELECT COUNT(*) >= 0 FROM pg_foreign_table; }
 step discard	{ DISCARD VARIABLES; }
 step sc3		{ COMMIT; }
+step clean		{ DROP VARIABLE myvar_o_eox_r; }
 step state		{ SELECT varname FROM pg_variable; }
 
 session s4
@@ -48,4 +50,4 @@ permutation let val dbg drop create dbg val
 # calling the dbg step after the concurrent drop
 permutation let val s1 dbg drop create dbg val sr1
 # test for DISCARD ALL when all internal queues have actions registered
-permutation create3 let3 s3 o_c_d create4 let4 drop4 drop3 inval3 discard sc3 state
+permutation create3 let3 s3 o_c_d o_eox_r create4 let4 drop4 drop3 inval3 discard sc3 clean state
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 5c81b3e3435..e3fe8285dfa 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -1507,3 +1507,37 @@ SELECT count(*) FROM pg_session_variables();
      0
 (1 row)
 
+CREATE VARIABLE var1 AS int ON TRANSACTION END RESET;
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+COMMIT;
+-- should be NULL;
+SELECT var1 IS NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+ROLLBACK;
+-- should be NULL
+SELECT var1 IS NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP VARIABLE var1;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 1f2ba8111f4..2221f17d55d 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -1005,3 +1005,23 @@ COMMIT;
 SELECT count(*) FROM pg_variable WHERE varname = 'var1';
 -- should be zero
 SELECT count(*) FROM pg_session_variables();
+
+CREATE VARIABLE var1 AS int ON TRANSACTION END RESET;
+
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+COMMIT;
+
+-- should be NULL;
+SELECT var1 IS NULL;
+
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+ROLLBACK;
+
+-- should be NULL
+SELECT var1 IS NULL;
+
+DROP VARIABLE var1;
-- 
2.45.2

From 7a29723603208770a1b63eabe5a71cf3e6b035d4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

When the evaluation of default expression fails, we remove related entry from sessionvars
hash table. Then sessionvars can contain only sucessfully initialized values. Then we don't
need special flag for badly initialized session variables.
---
 doc/src/sgml/catalogs.sgml                    |  8 ++
 doc/src/sgml/ddl.sgml                         | 13 ++-
 doc/src/sgml/ref/create_variable.sgml         | 21 +++--
 doc/src/sgml/ref/discard.sgml                 |  3 +-
 src/backend/catalog/pg_variable.c             | 27 ++++++
 src/backend/commands/session_variable.c       | 87 ++++++++++++++++++-
 src/backend/parser/gram.y                     | 15 +++-
 src/backend/parser/parse_agg.c                |  2 +
 src/backend/parser/parse_expr.c               |  4 +
 src/backend/parser/parse_func.c               |  1 +
 src/bin/pg_dump/pg_dump.c                     | 14 +++
 src/bin/pg_dump/pg_dump.h                     |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl              | 36 ++++++++
 src/bin/psql/describe.c                       |  4 +-
 src/include/catalog/pg_variable.h             |  3 +
 src/include/nodes/parsenodes.h                |  1 +
 src/include/parser/parse_node.h               |  1 +
 src/test/regress/expected/psql.out            | 36 ++++----
 .../regress/expected/session_variables.out    | 73 ++++++++++++++--
 src/test/regress/sql/session_variables.sql    | 48 ++++++++++
 20 files changed, 353 insertions(+), 45 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 850060aac9f..18975a9e58c 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9866,6 +9866,14 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vardefexpr</structfield> <type>pg_node_tree</type>
+      </para>
+      <para>
+       The internal representation of the variable default value
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index eb499e4118f..d3f0d08cd99 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5341,13 +5341,12 @@ SELECT current_user_id;
 
    <para>
     The value of a session variable is local to the current session. Retrieving
-    a variable's value returns a <literal>NULL</literal>, unless its value has
-    been set to something else in the current session using the
-    <command>LET</command> command. The content of a variable is not
-    transactional. This is the same as regular variables in PL languages.
-    The session variables can be persistent or can be temporary. In both cases,
-    the content of session variables is temporary and not shared (like the
-    content of temporary tables).
+    a variable's value returns either a <literal>NULL</literal> or a default
+    value, unless its value has been set to something else in the current
+    session using the <command>LET</command> command. The content of a variable
+    is not transactional. This is the same as regular variables in PL languages.
+    The session variables are persistent, but the content of session variables
+    is temporary and not shared (like the content of temporary tables).
    </para>
 
    <para>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index 214f565f172..d61940172b1 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -27,7 +27,7 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
-    [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
+    [ DEFAULT <replaceable class="parameter">default_expr</replaceable> ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
 </synopsis>
  </refsynopsisdiv>
  <refsect1>
@@ -42,10 +42,10 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="p
 
   <para>
    The value of a session variable is local to the current session. Retrieving
-   a session variable's value returns either a NULL, unless its value is set to
-   something else in the current session with a LET command. The content of
-   a session variable is not transactional. This is the  same as regular
-   variables in procedural languages.
+   a session variable's value returns either a NULL or a default value, unless
+   its value is set to something else in the current session with a LET
+   command. The content of a session variable is not transactional. This is the
+   same as regular variables in procedural languages.
   </para>
 
   <para>
@@ -109,6 +109,17 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="p
     </listitem>
    </varlistentry>
 
+   <varlistentry id="sql-createvariable-default">
+    <term><literal>DEFAULT <replaceable>default_expr</replaceable></literal></term>
+    <listitem>
+     <para>
+      The <literal>DEFAULT</literal> clause can be used to assign a default
+      value to a session variable. This expression is evaluated when the session
+      variable is first accessed for reading and had not yet been assigned a value.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createvariable-on-commit-drop">
     <term><literal>ON COMMIT DROP</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml
index 61b967f9c9b..6b0cb95034f 100644
--- a/doc/src/sgml/ref/discard.sgml
+++ b/doc/src/sgml/ref/discard.sgml
@@ -71,7 +71,8 @@ DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP | VARIABLES }
     <listitem>
      <para>
       Resets the value of all session variables. If a variable
-      is later reused, it is re-initialized to <literal>NULL</literal>.
+      is later reused, it is re-initialized to either
+      <literal>NULL</literal> or its default value.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
index 70b622aa672..94dfeac4f47 100644
--- a/src/backend/catalog/pg_variable.c
+++ b/src/backend/catalog/pg_variable.c
@@ -24,6 +24,9 @@
 #include "catalog/pg_variable.h"
 #include "commands/session_variable.h"
 #include "miscadmin.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
 #include "parser/parse_type.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -37,6 +40,7 @@ static ObjectAddress create_variable(const char *varName,
 									 Oid varOwner,
 									 Oid varCollation,
 									 bool if_not_exists,
+									 Node *varDefexpr,
 									 VariableXactEndAction varXactEndAction);

@@ -51,6 +55,7 @@ create_variable(const char *varName,
 				Oid varOwner,
 				Oid varCollation,
 				bool if_not_exists,
+				Node *varDefexpr,
 				VariableXactEndAction varXactEndAction)
 {
 	Acl		   *varacl;
@@ -114,6 +119,11 @@ create_variable(const char *varName,
 	values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation);
 	values[Anum_pg_variable_varxactendaction - 1] = CharGetDatum(varXactEndAction);
 
+	if (varDefexpr)
+		values[Anum_pg_variable_vardefexpr - 1] = CStringGetTextDatum(nodeToString(varDefexpr));
+	else
+		nulls[Anum_pg_variable_vardefexpr - 1] = true;
+
 	varacl = get_user_default_acl(OBJECT_VARIABLE, varOwner,
 								  varNamespace);
 	if (varacl != NULL)
@@ -150,6 +160,11 @@ create_variable(const char *varName,
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
 	free_object_addresses(addrs);
 
+	/* dependency on default expr */
+	if (varDefexpr)
+		recordDependencyOnExpr(&myself, (Node *) varDefexpr,
+							   NIL, DEPENDENCY_NORMAL);
+
 	/* dependency on owner */
 	recordDependencyOnOwner(VariableRelationId, varid, varOwner);
 
@@ -185,6 +200,7 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 	Oid			collation;
 	Oid			typcollation;
 	ObjectAddress variable;
+	Node	   *cooked_default = NULL;
 
 	/* Check consistency of arguments */
 	if (stmt->XactEndAction == VARIABLE_XACTEND_DROP
@@ -226,6 +242,16 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 						format_type_be(typid)),
 				 parser_errposition(pstate, stmt->collClause->location)));
 
+	if (stmt->defexpr)
+	{
+		cooked_default = transformExpr(pstate, stmt->defexpr,
+									   EXPR_KIND_VARIABLE_DEFAULT);
+
+		cooked_default = coerce_to_specific_type(pstate,
+												 cooked_default, typid, "DEFAULT");
+		assign_expr_collations(pstate, cooked_default);
+	}
+
 	variable = create_variable(stmt->variable->relname,
 							   namespaceid,
 							   typid,
@@ -233,6 +259,7 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 							   varowner,
 							   collation,
 							   stmt->if_not_exists,
+							   cooked_default,
 							   stmt->XactEndAction);
 
 	elog(DEBUG1, "record for session variable \"%s\" (oid:%d) was created in pg_variable",
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index d65a92d85a4..37fe0fecd50 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -22,6 +22,7 @@
 #include "executor/svariableReceiver.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "optimizer/optimizer.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -523,11 +524,68 @@ AtEOSubXact_SessionVariables(bool isCommit,
 	}
 }
 
+/*
+ * evaluate an expression
+ */
+static void
+eval_assign_defexpr(SVariable svar, HeapTuple tup)
+{
+	Datum		defexpr_value;
+	bool		isnull;
+
+	Assert(svar);
+	Assert(svar->is_valid);
+	Assert(HeapTupleIsValid(tup));
+
+	defexpr_value = SysCacheGetAttr(VARIABLEOID,
+									tup,
+									Anum_pg_variable_vardefexpr,
+									&isnull);
+
+	if (!isnull)
+	{
+		EState	   *estate;
+		ExprState  *defexprs;
+		Expr	   *defexpr;
+		char	   *defexpr_str;
+		Datum		value;
+		MemoryContext oldcxt;
+
+		estate = CreateExecutorState();
+
+		defexpr_str = TextDatumGetCString(defexpr_value);
+		defexpr = (Expr *) stringToNode(defexpr_str);
+
+		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		defexpr = expression_planner((Expr *) defexpr);
+		defexprs = ExecInitExpr(defexpr, NULL);
+
+		value = ExecEvalExprSwitchContext(defexprs,
+										  GetPerTupleExprContext(estate),
+										  &isnull);
+
+		MemoryContextSwitchTo(oldcxt);
+
+		if (!isnull)
+		{
+			oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+
+			svar->value = datumCopy(value, svar->typbyval, svar->typlen);
+			svar->isnull = false;
+
+			MemoryContextSwitchTo(oldcxt);
+		}
+
+		FreeExecutorState(estate);
+	}
+}
+
 /*
  * Update attributes cached in svar
  */
 static void
-setup_session_variable(SVariable svar, Oid varid)
+setup_session_variable(SVariable svar, Oid varid, bool is_write)
 {
 	HeapTuple	tup;
 	Form_pg_variable varform;
@@ -577,6 +635,9 @@ setup_session_variable(SVariable svar, Oid varid)
 	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
 											ObjectIdGetDatum(varid));
 
+	if (!is_write)
+		eval_assign_defexpr(svar, tup);
+
 	ReleaseSysCache(tup);
 }
 
@@ -606,7 +667,7 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	 */
 	if (!svar->is_valid)
 	{
-		setup_session_variable(&locsvar, svar->varid);
+		setup_session_variable(&locsvar, svar->varid, false);
 		_svar = &locsvar;
 	}
 	else
@@ -748,7 +809,25 @@ get_session_variable(Oid varid)
 	 */
 	if (!svar->is_valid)
 	{
-		setup_session_variable(svar, varid);
+		/* in this case we want to use defexp if it is defined */
+		PG_TRY();
+		{
+			/*
+			 * In this case, the setup can execute default expression. When
+			 * the execution of default expression fails, then we need to
+			 * remove entry from session vars.
+			 */
+			setup_session_variable(svar, varid, false);
+		}
+		PG_CATCH();
+		{
+			/* This entry cannot be valid, remove from sessionvars */
+			hash_search(sessionvars, &varid, HASH_REMOVE, NULL);
+
+			/* propagate the error */
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
 
 		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by READ)",
 			 get_namespace_name(get_session_variable_namespace(varid)),
@@ -812,7 +891,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 
 	if (!found)
 	{
-		setup_session_variable(svar, varid);
+		setup_session_variable(svar, varid, true);
 
 		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by WRITE)",
 			 get_namespace_name(get_session_variable_namespace(svar->varid)),
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index cd95b2bb97d..48293093518 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -653,6 +653,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>		partitions_list
 %type <list>		hash_partbound
 %type <defelt>		hash_partbound_elem
+%type <node>		OptSessionVarDefExpr
 
 %type <node>	json_format_clause
 				json_format_clause_opt
@@ -5222,30 +5223,36 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
-			CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause XactEndActionOption
+			CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause OptSessionVarDefExpr XactEndActionOption
 				{
 					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
 					$4->relpersistence = $2;
 					n->variable = $4;
 					n->typeName = $6;
 					n->collClause = (CollateClause *) $7;
-					n->XactEndAction = $8;
+					n->defexpr = $8;
+					n->XactEndAction = $9;
 					n->if_not_exists = false;
 					$$ = (Node *) n;
 				}
-			| CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause XactEndActionOption
+			| CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OptSessionVarDefExpr XactEndActionOption
 				{
 					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
 					$7->relpersistence = $2;
 					n->variable = $7;
 					n->typeName = $9;
 					n->collClause = (CollateClause *) $10;
-					n->XactEndAction = $11;
+					n->defexpr = $11;
+					n->XactEndAction = $12;
 					n->if_not_exists = true;
 					$$ = (Node *) n;
 				}
 		;
 
+OptSessionVarDefExpr: DEFAULT b_expr					{ $$ = $2; }
+			| /* EMPTY */							{ $$ = NULL; }
+		;
+
 /*
  * Temporary session variables can be dropped on successful
  * transaction end like tables.
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b494c885e9f..cb57203e185 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -485,6 +485,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 
 			if (isAgg)
 				err = _("aggregate functions are not allowed in DEFAULT expressions");
@@ -934,6 +935,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("window functions are not allowed in DEFAULT expressions");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f0ebbcf5beb..6007655630d 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -583,6 +583,7 @@ expr_kind_allows_session_variables(ParseExprKind p_expr_kind)
 		case EXPR_KIND_JOIN_USING:
 		case EXPR_KIND_CYCLE_MARK:
 		case EXPR_KIND_ASSIGN_TARGET:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			result = false;
 			break;
 	}
@@ -668,6 +669,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_CYCLE_MARK:
 		case EXPR_KIND_ASSIGN_TARGET:
 		case EXPR_KIND_LET_TARGET:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			/* okay */
 			break;
 
@@ -2079,6 +2081,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("cannot use subquery in DEFAULT expression");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
@@ -3429,6 +3432,7 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "CHECK";
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			return "DEFAULT";
 		case EXPR_KIND_INDEX_EXPRESSION:
 			return "index expression";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 9aa4f60768b..fcac694455d 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2620,6 +2620,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("set-returning functions are not allowed in DEFAULT expressions");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5296a28078b..9f71950c60f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5330,6 +5330,7 @@ getVariables(Archive *fout)
 	int			i_varnamespace;
 	int			i_vartype;
 	int			i_vartypname;
+	int			i_vardefexpr;
 	int			i_varxactendaction;
 	int			i_varowner;
 	int			i_varcollation;
@@ -5354,6 +5355,7 @@ getVariables(Archive *fout)
 					  "pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n"
 					  "CASE WHEN v.varcollation <> t.typcollation "
 					  "THEN v.varcollation ELSE 0 END AS varcollation,\n"
+					  "pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n"
 					  "v.varowner,\n"
 					  "v.varacl,\n"
 					  "acldefault('V', v.varowner) AS acldefault\n"
@@ -5371,6 +5373,7 @@ getVariables(Archive *fout)
 	i_varnamespace = PQfnumber(res, "varnamespace");
 	i_vartype = PQfnumber(res, "vartype");
 	i_vartypname = PQfnumber(res, "vartypname");
+	i_vardefexpr = PQfnumber(res, "vardefexpr");
 	i_varxactendaction = PQfnumber(res, "varxactendaction");
 	i_varcollation = PQfnumber(res, "varcollation");
 
@@ -5409,6 +5412,11 @@ getVariables(Archive *fout)
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(varinfo[i].dobj), fout);
 
+		if (PQgetisnull(res, i, i_vardefexpr))
+			varinfo[i].vardefexpr = NULL;
+		else
+			varinfo[i].vardefexpr = pg_strdup(PQgetvalue(res, i, i_vardefexpr));
+
 		/* Do not try to dump ACL if no ACL exists. */
 		if (!PQgetisnull(res, i, i_varacl))
 			varinfo[i].dobj.components |= DUMP_COMPONENT_ACL;
@@ -5441,6 +5449,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	PQExpBuffer query;
 	char	   *qualvarname;
 	const char *vartypname;
+	const char *vardefexpr;
 	const char *varxactendaction;
 	Oid			varcollation;
 
@@ -5453,6 +5462,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
 	qualvarname = pg_strdup(fmtQualifiedDumpable(varinfo));
 	vartypname = varinfo->vartypname;
+	vardefexpr = varinfo->vardefexpr;
 	varxactendaction = varinfo->varxactendaction;
 	varcollation = varinfo->varcollation;
 
@@ -5472,6 +5482,10 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (vardefexpr)
+		appendPQExpBuffer(query, " DEFAULT %s",
+						  vardefexpr);
+
 	if (strcmp(varxactendaction, "r") == 0)
 		appendPQExpBuffer(query, " ON TRANSACTION END RESET");
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0910507d9ac..e2cb06cc883 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -702,6 +702,7 @@ typedef struct _VariableInfo
 	DumpableAcl dacl;
 	Oid			vartype;
 	char	   *vartypname;
+	char	   *vardefexpr;
 	char	   *varxactendaction;
 	char	   *varacl;
 	char	   *rvaracl;
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 71f8c713f68..ff5c01020e4 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3959,6 +3959,42 @@ my %tests = (
 		},
 	},
 
+	'CREATE VARIABLE test_variable DEFAULT' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE VARIABLE dump_test.variable3 AS integer DEFAULT 10;',
+		regexp => qr/^
+			\QCREATE VARIABLE dump_test.variable3 AS integer DEFAULT 10;\E/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_measurement    => 1,
+		},
+	},
+
+	'CREATE VARIABLE test_variable DEFAULT ON TRANSACTION END RESET' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE VARIABLE dump_test.variable4 AS integer DEFAULT 10 ON TRANSACTION END RESET',
+		regexp => qr/^
+			\QCREATE VARIABLE dump_test.variable4 AS integer DEFAULT 10 ON TRANSACTION END RESET;\E/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_measurement    => 1,
+		},
+	},
+
 	'CREATE VIEW test_view' => {
 		create_order => 61,
 		create_sql => 'CREATE VIEW dump_test.test_view
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 50bae539113..566559316ee 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5159,7 +5159,7 @@ listVariables(const char *pattern, bool verbose)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, false, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false};
 
 	if (pset.sversion < 180000)
 	{
@@ -5180,6 +5180,7 @@ listVariables(const char *pattern, bool verbose)
 					  "  (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type bt\n"
 					  "   WHERE c.oid = v.varcollation AND bt.oid = v.vartype AND v.varcollation <> bt.typcollation) as \"%s\",\n"
 					  "  pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n"
+					  "  pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\",\n"
 					  "  CASE v.varxactendaction\n"
 					  "    WHEN 'd' THEN 'ON COMMIT DROP'\n"
 					  "    WHEN 'r' THEN 'ON TRANSACTION END RESET'\n"
@@ -5189,6 +5190,7 @@ listVariables(const char *pattern, bool verbose)
 					  gettext_noop("Type"),
 					  gettext_noop("Collation"),
 					  gettext_noop("Owner"),
+					  gettext_noop("Default"),
 					  gettext_noop("Transactional end action"));
 
 	if (verbose)
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
index 0fc824cfe15..0a972aa5d04 100644
--- a/src/include/catalog/pg_variable.h
+++ b/src/include/catalog/pg_variable.h
@@ -66,6 +66,9 @@ CATALOG(pg_variable,9222,VariableRelationId)
 	/* access permissions */
 	aclitem		varacl[1] BKI_DEFAULT(_null_);
 
+	/* list of expression trees for variable default (NULL if none) */
+	pg_node_tree vardefexpr BKI_DEFAULT(_null_);
+
 #endif
 } FormData_pg_variable;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 56e284dc09c..ed0a214ffe4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3455,6 +3455,7 @@ typedef struct CreateSessionVarStmt
 	TypeName   *typeName;		/* the type of variable */
 	CollateClause *collClause;
 	bool		if_not_exists;	/* do nothing if it already exists */
+	Node	   *defexpr;		/* default expression */
 	char		XactEndAction;	/* on transaction end action */
 } CreateSessionVarStmt;
 
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6d936919659..baba22ed840 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -84,6 +84,7 @@ typedef enum ParseExprKind
 	EXPR_KIND_CYCLE_MARK,		/* cycle mark value */
 	EXPR_KIND_ASSIGN_TARGET,	/* PL/pgSQL assignment target */
 	EXPR_KIND_LET_TARGET,		/* LET target */
+	EXPR_KIND_VARIABLE_DEFAULT, /* default value for session variable */
 } ParseExprKind;

diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index ad7cd1ca390..ca131e568eb 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5833,20 +5833,20 @@ CREATE ROLE regress_variable_owner;
 SET ROLE TO regress_variable_owner;
 CREATE VARIABLE var1 AS varchar COLLATE "C";
 \dV+ var1
-                                                          List of variables
- Schema | Name |       Type        | Collation |         Owner          | Transactional end action | Access privileges | Description 
---------+------+-------------------+-----------+------------------------+--------------------------+-------------------+-------------
- public | var1 | character varying | C         | regress_variable_owner |                          |                   | 
+                                                               List of variables
+ Schema | Name |       Type        | Collation |         Owner          | Default | Transactional end action | Access privileges | Description 
+--------+------+-------------------+-----------+------------------------+---------+--------------------------+-------------------+-------------
+ public | var1 | character varying | C         | regress_variable_owner |         |                          |                   | 
 (1 row)
 
 GRANT SELECT ON VARIABLE var1 TO PUBLIC;
 COMMENT ON VARIABLE var1 IS 'some description';
 \dV+ var1
-                                                                            List of variables
- Schema | Name |       Type        | Collation |         Owner          | Transactional end action |                Access privileges                 |   Description    
---------+------+-------------------+-----------+------------------------+--------------------------+--------------------------------------------------+------------------
- public | var1 | character varying | C         | regress_variable_owner |                          | regress_variable_owner=rw/regress_variable_owner+| some description
-        |      |                   |           |                        |                          | =r/regress_variable_owner                        | 
+                                                                                 List of variables
+ Schema | Name |       Type        | Collation |         Owner          | Default | Transactional end action |                Access privileges                 |   Description    
+--------+------+-------------------+-----------+------------------------+---------+--------------------------+--------------------------------------------------+------------------
+ public | var1 | character varying | C         | regress_variable_owner |         |                          | regress_variable_owner=rw/regress_variable_owner+| some description
+        |      |                   |           |                        |         |                          | =r/regress_variable_owner                        | 
 (1 row)
 
 DROP VARIABLE var1;
@@ -6314,9 +6314,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-                          List of variables
- Schema | Name | Type | Collation | Owner | Transactional end action 
---------+------+------+-----------+-------+--------------------------
+                               List of variables
+ Schema | Name | Type | Collation | Owner | Default | Transactional end action 
+--------+------+------+-----------+-------+---------+--------------------------
 (0 rows)
 
 -- again, but with dotted schema qualifications.
@@ -6489,9 +6489,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 \dy "no.such.schema"."no.such.event.trigger"
 improper qualified name (too many dotted names): "no.such.schema"."no.such.event.trigger"
 \dV "no.such.schema"."no.such.variable"
-                          List of variables
- Schema | Name | Type | Collation | Owner | Transactional end action 
---------+------+------+-----------+-------+--------------------------
+                               List of variables
+ Schema | Name | Type | Collation | Owner | Default | Transactional end action 
+--------+------+------+-----------+-------+---------+--------------------------
 (0 rows)
 
 -- again, but with current database and dotted schema qualifications.
@@ -6628,9 +6628,9 @@ List of text search templates
 (0 rows)
 
 \dV regression."no.such.schema"."no.such.variable"
-                          List of variables
- Schema | Name | Type | Collation | Owner | Transactional end action 
---------+------+------+-----------+-------+--------------------------
+                               List of variables
+ Schema | Name | Type | Collation | Owner | Default | Transactional end action 
+--------+------+------+-----------+-------+---------+--------------------------
 (0 rows)
 
 -- again, but with dotted database and dotted schema qualifications.
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index e3fe8285dfa..9c42f8e4e09 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -45,11 +45,11 @@ SET ROLE TO regress_variable_owner;
 CREATE VARIABLE svartest.var1 AS int;
 SET ROLE TO DEFAULT;
 \dV+ svartest.var1
-                                                                     List of variables
-  Schema  | Name |  Type   | Collation |         Owner          | Transactional end action |                Access privileges                 | Description 
-----------+------+---------+-----------+------------------------+--------------------------+--------------------------------------------------+-------------
- svartest | var1 | integer |           | regress_variable_owner |                          | regress_variable_owner=rw/regress_variable_owner+| 
-          |      |         |           |                        |                          | regress_variable_reader=r/regress_variable_owner | 
+                                                                          List of variables
+  Schema  | Name |  Type   | Collation |         Owner          | Default | Transactional end action |                Access privileges                 | Description 
+----------+------+---------+-----------+------------------------+---------+--------------------------+--------------------------------------------------+-------------
+ svartest | var1 | integer |           | regress_variable_owner |         |                          | regress_variable_owner=rw/regress_variable_owner+| 
+          |      |         |           |                        |         |                          | regress_variable_reader=r/regress_variable_owner | 
 (1 row)
 
 DROP VARIABLE svartest.var1;
@@ -1541,3 +1541,66 @@ SELECT var1 IS NULL;
 (1 row)
 
 DROP VARIABLE var1;
+CREATE OR REPLACE FUNCTION vartest_fx()
+RETURNS int AS $$
+BEGIN
+  RAISE NOTICE 'vartest_fx executed';
+  RETURN 0;
+END;
+$$ LANGUAGE plpgsql;
+CREATE VARIABLE var1 AS int DEFAULT vartest_fx();
+-- vartest_fx should be protected by dep, should fail
+DROP FUNCTION vartest_fx();
+ERROR:  cannot drop function vartest_fx() because other objects depend on it
+DETAIL:  session variable var1 depends on function vartest_fx()
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- should be ok
+SELECT var1;
+NOTICE:  vartest_fx executed
+ var1 
+------
+    0
+(1 row)
+
+-- the defexpr should be evaluated only once
+SELECT var1;
+ var1 
+------
+    0
+(1 row)
+
+DISCARD VARIABLES;
+-- in this case, the defexpr should not be evaluated
+LET var1 = 100;
+SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+DISCARD VARIABLES;
+CREATE OR REPLACE FUNCTION vartest_fx()
+RETURNS int AS $$
+BEGIN
+  RAISE EXCEPTION 'vartest_fx is executing';
+  RETURN 0;
+END;
+$$ LANGUAGE plpgsql;
+-- should to fail, but not to crash
+SELECT var1;
+ERROR:  vartest_fx is executing
+CONTEXT:  PL/pgSQL function vartest_fx() line 3 at RAISE
+-- again
+SELECT var1;
+ERROR:  vartest_fx is executing
+CONTEXT:  PL/pgSQL function vartest_fx() line 3 at RAISE
+-- but we can write
+LET var1 = 100;
+SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+DROP VARIABLE var1;
+DROP FUNCTION vartest_fx();
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 2221f17d55d..8b432177980 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -1025,3 +1025,51 @@ ROLLBACK;
 SELECT var1 IS NULL;
 
 DROP VARIABLE var1;
+
+CREATE OR REPLACE FUNCTION vartest_fx()
+RETURNS int AS $$
+BEGIN
+  RAISE NOTICE 'vartest_fx executed';
+  RETURN 0;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE VARIABLE var1 AS int DEFAULT vartest_fx();
+
+-- vartest_fx should be protected by dep, should fail
+DROP FUNCTION vartest_fx();
+
+-- should be ok
+SELECT var1;
+
+-- the defexpr should be evaluated only once
+SELECT var1;
+
+DISCARD VARIABLES;
+
+-- in this case, the defexpr should not be evaluated
+LET var1 = 100;
+SELECT var1;
+
+DISCARD VARIABLES;
+
+CREATE OR REPLACE FUNCTION vartest_fx()
+RETURNS int AS $$
+BEGIN
+  RAISE EXCEPTION 'vartest_fx is executing';
+  RETURN 0;
+END;
+$$ LANGUAGE plpgsql;
+
+-- should to fail, but not to crash
+SELECT var1;
+
+-- again
+SELECT var1;
+
+-- but we can write
+LET var1 = 100;
+SELECT var1;
+
+DROP VARIABLE var1;
+DROP FUNCTION vartest_fx();
-- 
2.45.2

From 562f0e1706a78bc279ea69e2b95cc6f4b17cadc1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

Almost trivial patch - psql, pg_dump support
---
 doc/src/sgml/catalogs.sgml                    |  20 +++
 doc/src/sgml/plpgsql.sgml                     |   6 +-
 doc/src/sgml/ref/create_variable.sgml         |  32 +++-
 src/backend/catalog/pg_variable.c             |   9 +-
 src/backend/commands/session_variable.c       |  69 +++++++-
 src/backend/parser/gram.y                     |  41 +++--
 src/bin/pg_dump/pg_dump.c                     |  19 ++-
 src/bin/pg_dump/pg_dump.h                     |   2 +
 src/bin/pg_dump/t/002_pg_dump.pl              |  36 ++++
 src/bin/psql/describe.c                       |   6 +-
 src/bin/psql/tab-complete.c                   |  11 +-
 src/include/catalog/pg_variable.h             |   6 +
 src/include/nodes/parsenodes.h                |   2 +
 src/test/regress/expected/psql.out            |  36 ++--
 .../regress/expected/session_variables.out    | 155 +++++++++++++++++-
 src/test/regress/sql/session_variables.sql    | 122 ++++++++++++++
 16 files changed, 523 insertions(+), 49 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 18975a9e58c..96cc88e6cf6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9834,6 +9834,26 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
 
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varnotnull</structfield> <type>boolean</type>
+      </para>
+      <para>
+       True if the session variable doesn't allow null values. The default value is false.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varisimmutable</structfield> <type>boolean</type>
+      </para>
+      <para>
+       True if the variable is <link linkend="sql-createvariable-immutable">immutable</link> (cannot be modified).
+       The default value is false.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vareoxaction</structfield> <type>char</type>
        <structfield>varxactendaction</structfield> <type>char</type>
       </para>
       <para>
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index d6d4ebd1a03..b7575a5673a 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -6041,9 +6041,9 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE;
     <para>
      The <application>PL/pgSQL</application> language has no packages, and
      therefore no package variables or package constants.
-     <productname>PostgreSQL</productname> has session variables.
-     Session variables can be created by <command>CREATE VARIABLE</command>,
-     as described in <xref linkend="sql-createvariable"/>.
+     <productname>PostgreSQL</productname> has session variables and immutable
+     session variables. Session variables can be created by <command>CREATE
+     VARIABLE</command>, as described in <xref linkend="sql-createvariable"/>.
     </para>
    </sect3>
   </sect2>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index d61940172b1..cb20d1dc68c 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -26,8 +26,8 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
-    [ DEFAULT <replaceable class="parameter">default_expr</replaceable> ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
+CREATE [ { TEMPORARY | TEMP } ] [ IMMUTABLE ] VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
+    [ NOT NULL ] [ DEFAULT <replaceable class="parameter">default_expr</replaceable> ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
 </synopsis>
  </refsynopsisdiv>
  <refsect1>
@@ -69,6 +69,22 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="p
 
   <variablelist>
 
+   <varlistentry id="sql-createvariable-immutable">
+    <term><literal>IMMUTABLE</literal></term>
+    <listitem>
+     <para>
+      The assigned value of the session variable can not be changed.
+      Only if the session variable doesn't have a default value, a single
+      initialization is allowed using the <command>LET</command> command. Once
+      done, no further change is allowed until end of transaction
+      if the session variable was created with clause <literal>ON TRANSACTION
+      END RESET</literal>, or until reset of all session variables by
+      <command>DISCARD VARIABLES</command>, or until reset of all session
+      objects by command <command>DISCARD ALL</command>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry  id="sql-createvariable-if-not-exists">
     <term><literal>IF NOT EXISTS</literal></term>
     <listitem>
@@ -109,6 +125,18 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="p
     </listitem>
    </varlistentry>
 
+   <varlistentry id="sql-createvariable-not-null">
+    <term><literal>NOT NULL</literal></term>
+    <listitem>
+     <para>
+      The <literal>NOT NULL</literal> clause forbids setting the session
+      variable to a null value. A session variable created as NOT NULL
+      should not to have a declared default value, and if the variable
+      has not assigned value, then the reading of this variable fails.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createvariable-default">
     <term><literal>DEFAULT <replaceable>default_expr</replaceable></literal></term>
     <listitem>
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
index 94dfeac4f47..a89c0eccd6a 100644
--- a/src/backend/catalog/pg_variable.c
+++ b/src/backend/catalog/pg_variable.c
@@ -40,6 +40,8 @@ static ObjectAddress create_variable(const char *varName,
 									 Oid varOwner,
 									 Oid varCollation,
 									 bool if_not_exists,
+									 bool not_null,
+									 bool is_immutable,
 									 Node *varDefexpr,
 									 VariableXactEndAction varXactEndAction);
 
@@ -55,6 +57,8 @@ create_variable(const char *varName,
 				Oid varOwner,
 				Oid varCollation,
 				bool if_not_exists,
+				bool not_null,
+				bool is_immutable,
 				Node *varDefexpr,
 				VariableXactEndAction varXactEndAction)
 {
@@ -117,6 +121,8 @@ create_variable(const char *varName,
 	values[Anum_pg_variable_vartypmod - 1] = Int32GetDatum(varTypmod);
 	values[Anum_pg_variable_varowner - 1] = ObjectIdGetDatum(varOwner);
 	values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation);
+	values[Anum_pg_variable_varnotnull - 1] = BoolGetDatum(not_null);
+	values[Anum_pg_variable_varisimmutable - 1] = BoolGetDatum(is_immutable);
 	values[Anum_pg_variable_varxactendaction - 1] = CharGetDatum(varXactEndAction);
 
 	if (varDefexpr)
@@ -202,7 +208,6 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 	ObjectAddress variable;
 	Node	   *cooked_default = NULL;
 
-	/* Check consistency of arguments */
 	if (stmt->XactEndAction == VARIABLE_XACTEND_DROP
 		&& stmt->variable->relpersistence != RELPERSISTENCE_TEMP)
 		ereport(ERROR,
@@ -259,6 +264,8 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 							   varowner,
 							   collation,
 							   stmt->if_not_exists,
+							   stmt->not_null,
+							   stmt->is_immutable,
 							   cooked_default,
 							   stmt->XactEndAction);
 
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 37fe0fecd50..8c997ff2b51 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -92,6 +92,9 @@ typedef struct SVariableData
 	void	   *domain_check_extra;
 	LocalTransactionId domain_check_extra_lxid;
 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*
@@ -110,6 +113,9 @@ typedef struct SVariableData
 	bool		is_valid;
 
 	uint32		hashvalue;		/* used for pairing sinval message */
+
+	/* true, when the value is already set, and cannot be changed more */
+	bool		protect_value;
 } SVariableData;
 
 typedef SVariableData *SVariable;
@@ -576,6 +582,23 @@ eval_assign_defexpr(SVariable svar, HeapTuple tup)
 
 			MemoryContextSwitchTo(oldcxt);
 		}
+		else
+		{
+			/*
+			 * Raise an error if this is a NOT NULL variable but the result of
+			 * DEFAULT expression is NULL.
+			 */
+			if (svar->not_null)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+								get_namespace_name(get_session_variable_namespace(svar->varid)),
+								get_session_variable_name(svar->varid)),
+						 errdetail("The result of DEFAULT expression is NULL.")));
+		}
+
+		if (svar->is_immutable)
+			svar->protect_value = true;
 
 		FreeExecutorState(estate);
 	}
@@ -606,6 +629,9 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 
 	get_typlenbyval(svar->typid, &svar->typlen, &svar->typbyval);
 
+	svar->not_null = varform->varnotnull;
+	svar->is_immutable = varform->varisimmutable;
+
 	svar->is_domain = (get_typtype(varform->vartype) == TYPTYPE_DOMAIN);
 	svar->domain_check_extra = NULL;
 	svar->domain_check_extra_lxid = InvalidLocalTransactionId;
@@ -635,9 +661,31 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
 											ObjectIdGetDatum(varid));
 
-	if (!is_write)
+	svar->protect_value = false;
+
+	/*
+	 * When the variable is marked as IMMUTABLE, we prefer to evaluate
+	 * possible DEFAULT before write op. In this case we want to protect
+	 * default value against any overwrite.
+	 */
+	if (!is_write ||
+		svar->is_immutable)
+	{
 		eval_assign_defexpr(svar, tup);
 
+		/*
+		 * Raise an error if this is a NOT NULL variable without default
+		 * expression.
+		 */
+		if (svar->isnull && svar->not_null)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+							get_namespace_name(get_session_variable_namespace(varid)),
+							get_session_variable_name(varid)),
+					 errdetail("The session variable was not initialized yet.")));
+	}
+
 	ReleaseSysCache(tup);
 }
 
@@ -657,6 +705,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	Assert(svar);
 	Assert(!isnull || value == (Datum) 0);
 
+	if (svar->protect_value)
+		ereport(ERROR,
+				(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
+				 errmsg("session variable \"%s.%s\" is declared IMMUTABLE",
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
+
+	/* don't allow assignment of null to NOT NULL variable */
+	if (isnull && svar->not_null)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
+
 	/*
 	 * Use typbyval, typbylen from session variable only when they are
 	 * trustable (the invalidation message was not accepted for this variable).
@@ -693,6 +756,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	svar->value = newval;
 	svar->isnull = isnull;
 
+	/* don't allow more changes of value when variable is IMMUTABLE */
+	if (svar->is_immutable)
+		svar->protect_value = true;
+
 	/*
 	 * XXX While unlikely, an error here is possible. It wouldn't leak memory
 	 * as the allocated chunk has already been correctly assigned to the
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 48293093518..7c6e077a46a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -683,6 +683,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
+%type <boolean>		OptNotNull OptImmutable
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -5223,27 +5224,31 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
-			CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause OptSessionVarDefExpr XactEndActionOption
+			CREATE OptTemp OptImmutable VARIABLE qualified_name opt_as Typename opt_collate_clause OptNotNull OptSessionVarDefExpr XactEndActionOption
 				{
 					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
-					$4->relpersistence = $2;
-					n->variable = $4;
-					n->typeName = $6;
-					n->collClause = (CollateClause *) $7;
-					n->defexpr = $8;
-					n->XactEndAction = $9;
+					$5->relpersistence = $2;
+					n->is_immutable = $3;
+					n->variable = $5;
+					n->typeName = $7;
+					n->collClause = (CollateClause *) $8;
+					n->not_null = $9;
+					n->defexpr = $10;
+					n->XactEndAction = $11;
 					n->if_not_exists = false;
 					$$ = (Node *) n;
 				}
-			| CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OptSessionVarDefExpr XactEndActionOption
+			| CREATE OptTemp OptImmutable VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OptNotNull OptSessionVarDefExpr XactEndActionOption
 				{
 					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
-					$7->relpersistence = $2;
-					n->variable = $7;
-					n->typeName = $9;
-					n->collClause = (CollateClause *) $10;
-					n->defexpr = $11;
-					n->XactEndAction = $12;
+					$8->relpersistence = $2;
+					n->is_immutable = $3;
+					n->variable = $8;
+					n->typeName = $10;
+					n->collClause = (CollateClause *) $11;
+					n->not_null = $12;
+					n->defexpr = $13;
+					n->XactEndAction = $14;
 					n->if_not_exists = true;
 					$$ = (Node *) n;
 				}
@@ -5263,6 +5268,14 @@ XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
 		;

+OptNotNull: NOT NULL_P								{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
+OptImmutable: IMMUTABLE								{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 9f71950c60f..49c33e52def 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5334,6 +5334,8 @@ getVariables(Archive *fout)
 	int			i_varxactendaction;
 	int			i_varowner;
 	int			i_varcollation;
+	int			i_varnotnull;
+	int			i_varisimmutable;
 	int			i_varacl;
 	int			i_acldefault;
 	int			i,
@@ -5355,6 +5357,8 @@ getVariables(Archive *fout)
 					  "pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n"
 					  "CASE WHEN v.varcollation <> t.typcollation "
 					  "THEN v.varcollation ELSE 0 END AS varcollation,\n"
+					  "v.varnotnull,\n"
+					  "v.varisimmutable,\n"
 					  "pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n"
 					  "v.varowner,\n"
 					  "v.varacl,\n"
@@ -5376,6 +5380,8 @@ getVariables(Archive *fout)
 	i_vardefexpr = PQfnumber(res, "vardefexpr");
 	i_varxactendaction = PQfnumber(res, "varxactendaction");
 	i_varcollation = PQfnumber(res, "varcollation");
+	i_varnotnull = PQfnumber(res, "varnotnull");
+	i_varisimmutable = PQfnumber(res, "varisimmutable");
 
 	i_varowner = PQfnumber(res, "varowner");
 	i_varacl = PQfnumber(res, "varacl");
@@ -5402,6 +5408,8 @@ getVariables(Archive *fout)
 			pg_strdup(PQgetvalue(res, i, i_varxactendaction));
 
 		varinfo[i].varcollation = atooid(PQgetvalue(res, i, i_varcollation));
+		varinfo[i].varnotnull = *(PQgetvalue(res, i, i_varnotnull)) == 't';
+		varinfo[i].varisimmutable = *(PQgetvalue(res, i, i_varisimmutable)) == 't';
 
 		varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl));
 		varinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault));
@@ -5451,7 +5459,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	const char *vartypname;
 	const char *vardefexpr;
 	const char *varxactendaction;
+	const char *varisimmutable;
 	Oid			varcollation;
+	bool		varnotnull;
 
 	/* Skip if not to be dumped */
 	if (!varinfo->dobj.dump || dopt->dataOnly)
@@ -5465,12 +5475,14 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	vardefexpr = varinfo->vardefexpr;
 	varxactendaction = varinfo->varxactendaction;
 	varcollation = varinfo->varcollation;
+	varnotnull = varinfo->varnotnull;
+	varisimmutable = varinfo->varisimmutable ? "IMMUTABLE " : "";
 
 	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
 					  qualvarname);
 
-	appendPQExpBuffer(query, "CREATE VARIABLE %s AS %s",
-					  qualvarname, vartypname);
+	appendPQExpBuffer(query, "CREATE %sVARIABLE %s AS %s",
+					  varisimmutable, qualvarname, vartypname);
 
 	if (OidIsValid(varcollation))
 	{
@@ -5482,6 +5494,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (varnotnull)
+		appendPQExpBuffer(query, " NOT NULL");
+
 	if (vardefexpr)
 		appendPQExpBuffer(query, " DEFAULT %s",
 						  vardefexpr);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e2cb06cc883..cbb06b66f74 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -710,6 +710,8 @@ typedef struct _VariableInfo
 	char	   *initrvaracl;
 	Oid			varcollation;
 	const char *rolname;		/* name of owner, or empty string */
+	bool		varnotnull;
+	bool		varisimmutable;
 } VariableInfo;
 
 /*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index ff5c01020e4..a0707c7db16 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3995,6 +3995,42 @@ my %tests = (
 		},
 	},
 
+	'CREATE IMMUTABLE VARIABLE test_variable' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE IMMUTABLE VARIABLE dump_test.variable5 AS integer',
+		regexp => qr/^
+			\QCREATE IMMUTABLE VARIABLE dump_test.variable5 AS integer;\E/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_measurement    => 1,
+		},
+	},
+
+	'CREATE IMMUTABLE VARIABLE test_variable NOT NULL DEFAULT' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE IMMUTABLE VARIABLE dump_test.variable7 AS integer NOT NULL DEFAULT 10',
+		regexp => qr/^
+			\QCREATE IMMUTABLE VARIABLE dump_test.variable7 AS integer NOT NULL DEFAULT 10;\E/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_measurement    => 1,
+		},
+	},
+
 	'CREATE VIEW test_view' => {
 		create_order => 61,
 		create_sql => 'CREATE VIEW dump_test.test_view
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 566559316ee..15ca777996a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5159,7 +5159,7 @@ listVariables(const char *pattern, bool verbose)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false, false, false};
 
 	if (pset.sversion < 180000)
 	{
@@ -5180,6 +5180,8 @@ listVariables(const char *pattern, bool verbose)
 					  "  (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type bt\n"
 					  "   WHERE c.oid = v.varcollation AND bt.oid = v.vartype AND v.varcollation <> bt.typcollation) as \"%s\",\n"
 					  "  pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n"
+					  "  NOT v.varnotnull as \"%s\",\n"
+					  "  NOT v.varisimmutable as \"%s\",\n"
 					  "  pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\",\n"
 					  "  CASE v.varxactendaction\n"
 					  "    WHEN 'd' THEN 'ON COMMIT DROP'\n"
@@ -5190,6 +5192,8 @@ listVariables(const char *pattern, bool verbose)
 					  gettext_noop("Type"),
 					  gettext_noop("Collation"),
 					  gettext_noop("Owner"),
+					  gettext_noop("Nullable"),
+					  gettext_noop("Mutable"),
 					  gettext_noop("Default"),
 					  gettext_noop("Transactional end action"));
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 1ad930a2b1e..a9cc36a642f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1252,6 +1252,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"IMMUTABLE VARIABLE", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE IMMUTABLE
+																					 * VARIABLE ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -3277,7 +3279,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
-		COMPLETE_WITH("SEQUENCE", "TABLE", "VARIABLE", "VIEW");
+		COMPLETE_WITH("IMMUTABLE VARIABLE", "SEQUENCE", "TABLE", "VARIABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE, SEQUENCE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 	{
@@ -3600,7 +3602,8 @@ psql_completion(const char *text, int start, int end)
 /* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VARIABLE <name> with AS */
 	else if (TailMatches("CREATE", "VARIABLE", MatchAny) ||
-			 TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny))
+			 TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny) ||
+			 TailMatches("IMMUTABLE", "VARIABLE", MatchAny))
 		COMPLETE_WITH("AS");
 	else if (TailMatches("VARIABLE", MatchAny, "AS"))
 		/* Complete CREATE VARIABLE <name> with AS types */
@@ -4248,6 +4251,10 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("FROM", "SERVER", MatchAny, "INTO", MatchAny))
 		COMPLETE_WITH("OPTIONS (");
 
+/* IMMUTABLE -- can be expend to IMMUTABLE VARIABLE */
+	else if (TailMatches("CREATE", "IMMUTABLE"))
+		COMPLETE_WITH("VARIABLE");
+
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete NOT MATCHED THEN INSERT */
 	else if (TailMatches("NOT", "MATCHED", "THEN", "INSERT"))
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
index 0a972aa5d04..ad17b78d6a8 100644
--- a/src/include/catalog/pg_variable.h
+++ b/src/include/catalog/pg_variable.h
@@ -54,6 +54,12 @@ CATALOG(pg_variable,9222,VariableRelationId)
 	/* typmod for variable's type */
 	int32		vartypmod BKI_DEFAULT(-1);
 
+	/* don't allow NULL */
+	bool		varnotnull BKI_DEFAULT(f);
+
+	/* don't allow changes */
+	bool		varisimmutable BKI_DEFAULT(f);
+
 	/* action on transaction end */
 	char		varxactendaction BKI_DEFAULT(n);
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ed0a214ffe4..76d7656d7f7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3455,6 +3455,8 @@ typedef struct CreateSessionVarStmt
 	TypeName   *typeName;		/* the type of variable */
 	CollateClause *collClause;
 	bool		if_not_exists;	/* do nothing if it already exists */
+	bool		not_null;		/* disallow nulls */
+	bool		is_immutable;	/* don't allow changes */
 	Node	   *defexpr;		/* default expression */
 	char		XactEndAction;	/* on transaction end action */
 } CreateSessionVarStmt;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index ca131e568eb..f7082639605 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5833,20 +5833,20 @@ CREATE ROLE regress_variable_owner;
 SET ROLE TO regress_variable_owner;
 CREATE VARIABLE var1 AS varchar COLLATE "C";
 \dV+ var1
-                                                               List of variables
- Schema | Name |       Type        | Collation |         Owner          | Default | Transactional end action | Access privileges | Description 
---------+------+-------------------+-----------+------------------------+---------+--------------------------+-------------------+-------------
- public | var1 | character varying | C         | regress_variable_owner |         |                          |                   | 
+                                                                         List of variables
+ Schema | Name |       Type        | Collation |         Owner          | Nullable | Mutable | Default | Transactional end action | Access privileges | Description 
+--------+------+-------------------+-----------+------------------------+----------+---------+---------+--------------------------+-------------------+-------------
+ public | var1 | character varying | C         | regress_variable_owner | t        | t       |         |                          |                   | 
 (1 row)
 
 GRANT SELECT ON VARIABLE var1 TO PUBLIC;
 COMMENT ON VARIABLE var1 IS 'some description';
 \dV+ var1
-                                                                                 List of variables
- Schema | Name |       Type        | Collation |         Owner          | Default | Transactional end action |                Access privileges                 |   Description    
---------+------+-------------------+-----------+------------------------+---------+--------------------------+--------------------------------------------------+------------------
- public | var1 | character varying | C         | regress_variable_owner |         |                          | regress_variable_owner=rw/regress_variable_owner+| some description
-        |      |                   |           |                        |         |                          | =r/regress_variable_owner                        | 
+                                                                                           List of variables
+ Schema | Name |       Type        | Collation |         Owner          | Nullable | Mutable | Default | Transactional end action |                Access privileges                 |   Description    
+--------+------+-------------------+-----------+------------------------+----------+---------+---------+--------------------------+--------------------------------------------------+------------------
+ public | var1 | character varying | C         | regress_variable_owner | t        | t       |         |                          | regress_variable_owner=rw/regress_variable_owner+| some description
+        |      |                   |           |                        |          |         |         |                          | =r/regress_variable_owner                        | 
 (1 row)
 
 DROP VARIABLE var1;
@@ -6314,9 +6314,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-                               List of variables
- Schema | Name | Type | Collation | Owner | Default | Transactional end action 
---------+------+------+-----------+-------+---------+--------------------------
+                                         List of variables
+ Schema | Name | Type | Collation | Owner | Nullable | Mutable | Default | Transactional end action 
+--------+------+------+-----------+-------+----------+---------+---------+--------------------------
 (0 rows)
 
 -- again, but with dotted schema qualifications.
@@ -6489,9 +6489,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 \dy "no.such.schema"."no.such.event.trigger"
 improper qualified name (too many dotted names): "no.such.schema"."no.such.event.trigger"
 \dV "no.such.schema"."no.such.variable"
-                               List of variables
- Schema | Name | Type | Collation | Owner | Default | Transactional end action 
---------+------+------+-----------+-------+---------+--------------------------
+                                         List of variables
+ Schema | Name | Type | Collation | Owner | Nullable | Mutable | Default | Transactional end action 
+--------+------+------+-----------+-------+----------+---------+---------+--------------------------
 (0 rows)
 
 -- again, but with current database and dotted schema qualifications.
@@ -6628,9 +6628,9 @@ List of text search templates
 (0 rows)
 
 \dV regression."no.such.schema"."no.such.variable"
-                               List of variables
- Schema | Name | Type | Collation | Owner | Default | Transactional end action 
---------+------+------+-----------+-------+---------+--------------------------
+                                         List of variables
+ Schema | Name | Type | Collation | Owner | Nullable | Mutable | Default | Transactional end action 
+--------+------+------+-----------+-------+----------+---------+---------+--------------------------
 (0 rows)
 
 -- again, but with dotted database and dotted schema qualifications.
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 9c42f8e4e09..6ef1a6f88c1 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -45,11 +45,11 @@ SET ROLE TO regress_variable_owner;
 CREATE VARIABLE svartest.var1 AS int;
 SET ROLE TO DEFAULT;
 \dV+ svartest.var1
-                                                                          List of variables
-  Schema  | Name |  Type   | Collation |         Owner          | Default | Transactional end action |                Access privileges                 | Description 
-----------+------+---------+-----------+------------------------+---------+--------------------------+--------------------------------------------------+-------------
- svartest | var1 | integer |           | regress_variable_owner |         |                          | regress_variable_owner=rw/regress_variable_owner+| 
-          |      |         |           |                        |         |                          | regress_variable_reader=r/regress_variable_owner | 
+                                                                                     List of variables
+  Schema  | Name |  Type   | Collation |         Owner          | Nullable | Mutable | Default | Transactional end action |                Access privileges                 | Description 
+----------+------+---------+-----------+------------------------+----------+---------+---------+--------------------------+--------------------------------------------------+-------------
+ svartest | var1 | integer |           | regress_variable_owner | t        | t       |         |                          | regress_variable_owner=rw/regress_variable_owner+| 
+          |      |         |           |                        |          |         |         |                          | regress_variable_reader=r/regress_variable_owner | 
 (1 row)
 
 DROP VARIABLE svartest.var1;
@@ -1604,3 +1604,148 @@ SELECT var1;
 
 DROP VARIABLE var1;
 DROP FUNCTION vartest_fx();
+-- test NOT NULL
+-- should be ok
+CREATE VARIABLE var1 AS int NOT NULL;
+-- should to fail
+SELECT var1;
+ERROR:  null value is not allowed for NOT NULL session variable "public.var1"
+DETAIL:  The session variable was not initialized yet.
+--should be ok
+LET var1 = 10;
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+DROP VARIABLE var1;
+-- should be ok
+CREATE VARIABLE var1 AS int NOT NULL DEFAULT 0;
+--should be ok
+SELECT var1;
+ var1 
+------
+    0
+(1 row)
+
+-- should be ok
+LET var1 = 10;
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+DISCARD VARIABLES;
+-- should to fail
+LET var1 = NULL;
+ERROR:  null value is not allowed for NOT NULL session variable "public.var1"
+DROP VARIABLE var1;
+-- test NOT NULL
+CREATE OR REPLACE FUNCTION vartest_fx()
+RETURNS int AS $$
+BEGIN
+  RETURN NULL;
+END;
+$$ LANGUAGE plpgsql;
+CREATE VARIABLE var1 AS int NOT NULL DEFAULT vartest_fx();
+-- should to fail
+SELECT var1;
+ERROR:  null value is not allowed for NOT NULL session variable "public.var1"
+DETAIL:  The result of DEFAULT expression is NULL.
+DISCARD VARIABLES;
+-- should be ok
+LET var1 = 10;
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+CREATE OR REPLACE FUNCTION vartest_fx()
+RETURNS int AS $$
+BEGIN
+  RETURN 0;
+END;
+$$ LANGUAGE plpgsql;
+DISCARD VARIABLES;
+-- should be ok
+SELECT var1;
+ var1 
+------
+    0
+(1 row)
+
+DROP VARIABLE var1;
+DROP FUNCTION vartest_fx();
+-- test IMMUTBLE
+CREATE IMMUTABLE VARIABLE var1 AS int;
+-- should be ok
+SELECT var1;
+ var1 
+------
+     
+(1 row)
+
+-- first write should ok
+-- should be ok
+LET var1 = 10;
+-- should fail
+LET var1 = 20;
+ERROR:  session variable "public.var1" is declared IMMUTABLE
+DISCARD VARIABLES;
+-- should be ok
+LET var1 = 10;
+-- should fail
+LET var1 = 20;
+ERROR:  session variable "public.var1" is declared IMMUTABLE
+DISCARD VARIABLES;
+-- should be ok
+SELECT var1;
+ var1 
+------
+     
+(1 row)
+
+-- should be ok
+LET var1 = NULL;
+-- should fail
+LET var1 = 20;
+ERROR:  session variable "public.var1" is declared IMMUTABLE
+DROP VARIABLE var1;
+CREATE IMMUTABLE VARIABLE var1 AS int DEFAULT 10;
+-- don't allow change when variable has DEFAULT value
+-- should to fail
+LET var1 = 20;
+ERROR:  session variable "public.var1" is declared IMMUTABLE
+DISCARD VARIABLES;
+-- should be ok
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+-- should fail
+LET var1 = 20;
+ERROR:  session variable "public.var1" is declared IMMUTABLE
+DROP VARIABLE var1;
+-- should be ok
+CREATE IMMUTABLE VARIABLE var1 AS INT NOT NULL DEFAULT 10;
+-- should to fail
+LET var1 = 10;
+ERROR:  session variable "public.var1" is declared IMMUTABLE
+LET var1 = 20;
+ERROR:  session variable "public.var1" is declared IMMUTABLE
+-- should be ok
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+-- should to fail
+LET var1 = 30;
+ERROR:  session variable "public.var1" is declared IMMUTABLE
+DROP VARIABLE var1;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 8b432177980..d0222f80a49 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -1073,3 +1073,125 @@ SELECT var1;
 
 DROP VARIABLE var1;
 DROP FUNCTION vartest_fx();
+
+-- test NOT NULL
+-- should be ok
+CREATE VARIABLE var1 AS int NOT NULL;
+-- should to fail
+SELECT var1;
+
+--should be ok
+LET var1 = 10;
+SELECT var1;
+
+DROP VARIABLE var1;
+
+-- should be ok
+CREATE VARIABLE var1 AS int NOT NULL DEFAULT 0;
+
+--should be ok
+SELECT var1;
+
+-- should be ok
+LET var1 = 10;
+SELECT var1;
+
+DISCARD VARIABLES;
+
+-- should to fail
+LET var1 = NULL;
+
+DROP VARIABLE var1;
+
+-- test NOT NULL
+CREATE OR REPLACE FUNCTION vartest_fx()
+RETURNS int AS $$
+BEGIN
+  RETURN NULL;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE VARIABLE var1 AS int NOT NULL DEFAULT vartest_fx();
+
+-- should to fail
+SELECT var1;
+
+DISCARD VARIABLES;
+
+-- should be ok
+LET var1 = 10;
+SELECT var1;
+
+CREATE OR REPLACE FUNCTION vartest_fx()
+RETURNS int AS $$
+BEGIN
+  RETURN 0;
+END;
+$$ LANGUAGE plpgsql;
+
+DISCARD VARIABLES;
+
+-- should be ok
+SELECT var1;
+
+DROP VARIABLE var1;
+DROP FUNCTION vartest_fx();
+
+-- test IMMUTBLE
+CREATE IMMUTABLE VARIABLE var1 AS int;
+
+-- should be ok
+SELECT var1;
+-- first write should ok
+-- should be ok
+LET var1 = 10;
+-- should fail
+LET var1 = 20;
+
+DISCARD VARIABLES;
+
+-- should be ok
+LET var1 = 10;
+-- should fail
+LET var1 = 20;
+
+DISCARD VARIABLES;
+
+-- should be ok
+SELECT var1;
+-- should be ok
+LET var1 = NULL;
+-- should fail
+LET var1 = 20;
+
+DROP VARIABLE var1;
+
+CREATE IMMUTABLE VARIABLE var1 AS int DEFAULT 10;
+
+-- don't allow change when variable has DEFAULT value
+-- should to fail
+LET var1 = 20;
+
+DISCARD VARIABLES;
+
+-- should be ok
+SELECT var1;
+-- should fail
+LET var1 = 20;
+
+DROP VARIABLE var1;
+
+-- should be ok
+CREATE IMMUTABLE VARIABLE var1 AS INT NOT NULL DEFAULT 10;
+
+-- should to fail
+LET var1 = 10;
+LET var1 = 20;
+
+-- should be ok
+SELECT var1;
+
+-- should to fail
+LET var1 = 30;
+
+DROP VARIABLE var1;
-- 
2.45.2

From 4d4f46829d6db0487253cba8d8ccd6d15989b410 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

The temporary variables are created inside temp schema and they are dropped by dropping
this schema. For consistency with temp tables, the CREATE TEMP VARIABLE command supports
ON COMMIT DROP clause. The temporary variables with this clause are collected in xact_drop_items
list. This list should be carefully maintained and can contain only valid entries (not
yet dropped). From this reasons now the subcommit and subaborting have to be handled.
---
 doc/src/sgml/catalogs.sgml                    |  10 +
 doc/src/sgml/ddl.sgml                         |   5 +-
 doc/src/sgml/ref/create_variable.sgml         |  15 +-
 src/backend/access/transam/xact.c             |   7 +-
 src/backend/catalog/pg_variable.c             |  24 ++-
 src/backend/commands/session_variable.c       | 190 +++++++++++++++++-
 src/backend/commands/view.c                   |   2 +-
 src/backend/parser/analyze.c                  |   4 +-
 src/backend/parser/gram.y                     |  30 ++-
 src/backend/parser/parse_relation.c           |  22 +-
 src/bin/psql/describe.c                       |  10 +-
 src/bin/psql/tab-complete.c                   |   3 +-
 src/include/catalog/pg_variable.h             |   9 +
 src/include/commands/session_variable.h       |   7 +-
 src/include/nodes/parsenodes.h                |   1 +
 src/include/nodes/primnodes.h                 |   4 +-
 src/include/parser/parse_relation.h           |   2 +-
 .../isolation/expected/session-variable.out   |   3 +-
 .../isolation/specs/session-variable.spec     |   3 +-
 src/test/regress/expected/psql.out            |  36 ++--
 .../regress/expected/session_variables.out    | 130 +++++++++++-
 src/test/regress/sql/session_variables.sql    |  66 ++++++
 src/tools/pgindent/typedefs.list              |   1 +
 23 files changed, 520 insertions(+), 64 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 5d495554429..81b629a9f41 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9832,6 +9832,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varxactendaction</structfield> <type>char</type>
+      </para>
+      <para>
+       Action performed at end of transaction:
+       <literal>n</literal> = no action, <literal>d</literal> = drop the variable.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>varcollation</structfield> <type>oid</type>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 9d5082dd3ce..eb499e4118f 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5345,8 +5345,9 @@ SELECT current_user_id;
     been set to something else in the current session using the
     <command>LET</command> command. The content of a variable is not
     transactional. This is the same as regular variables in PL languages.
-    The session variables are persistent, but the content of session variables is
-    temporary and not shared (like the content of temporary tables).
+    The session variables can be persistent or can be temporary. In both cases,
+    the content of session variables is temporary and not shared (like the
+    content of temporary tables).
    </para>
 
    <para>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index c95bd32742f..4d37611a1fe 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -26,7 +26,8 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
+CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
+    [ ON COMMIT DROP ]
 </synopsis>
  </refsynopsisdiv>
  <refsect1>
@@ -108,6 +109,18 @@ CREATE VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceab
     </listitem>
    </varlistentry>
 
+   <varlistentry id="sql-createvariable-on-commit-drop">
+    <term><literal>ON COMMIT DROP</literal></term>
+    <listitem>
+     <para>
+      The <literal>ON COMMIT DROP</literal> clause specifies the behaviour of a
+      temporary session variable at transaction commit. With this clause, the
+      session variable is dropped at commit time. The clause is only allowed
+      for temporary variables.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
  </refsect1>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2d3ed8607ab..d877e18f96e 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2307,7 +2307,7 @@ CommitTransaction(void)
 	smgrDoPendingSyncs(true, is_parallel_worker);
 
 	/* Remove values of dropped session variables from memory */
-	AtPreEOXact_SessionVariables();
+	AtPreEOXact_SessionVariables(true);
 
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
@@ -2899,6 +2899,7 @@ AbortTransaction(void)
 	AtAbort_Portals();
 	smgrDoPendingSyncs(false, is_parallel_worker);
 	AtEOXact_LargeObject(false);
+	AtPreEOXact_SessionVariables(false);
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
@@ -5175,6 +5176,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,
@@ -5339,6 +5342,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/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
index 0b21204bc47..70b622aa672 100644
--- a/src/backend/catalog/pg_variable.c
+++ b/src/backend/catalog/pg_variable.c
@@ -36,7 +36,8 @@ static ObjectAddress create_variable(const char *varName,
 									 int32 varTypmod,
 									 Oid varOwner,
 									 Oid varCollation,
-									 bool if_not_exists);
+									 bool if_not_exists,
+									 VariableXactEndAction varXactEndAction);

 /*
@@ -49,7 +50,8 @@ create_variable(const char *varName,
 				int32 varTypmod,
 				Oid varOwner,
 				Oid varCollation,
-				bool if_not_exists)
+				bool if_not_exists,
+				VariableXactEndAction varXactEndAction)
 {
 	Acl		   *varacl;
 	NameData	varname;
@@ -110,6 +112,7 @@ create_variable(const char *varName,
 	values[Anum_pg_variable_vartypmod - 1] = Int32GetDatum(varTypmod);
 	values[Anum_pg_variable_varowner - 1] = ObjectIdGetDatum(varOwner);
 	values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation);
+	values[Anum_pg_variable_varxactendaction - 1] = CharGetDatum(varXactEndAction);
 
 	varacl = get_user_default_acl(OBJECT_VARIABLE, varOwner,
 								  varNamespace);
@@ -183,6 +186,13 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 	Oid			typcollation;
 	ObjectAddress variable;
 
+	/* Check consistency of arguments */
+	if (stmt->XactEndAction == VARIABLE_XACTEND_DROP
+		&& stmt->variable->relpersistence != RELPERSISTENCE_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("ON COMMIT DROP can only be used on temporary variables")));
+
 	namespaceid =
 		RangeVarGetAndCheckCreationNamespace(stmt->variable, NoLock, NULL);
 
@@ -222,7 +232,8 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 							   typmod,
 							   varowner,
 							   collation,
-							   stmt->if_not_exists);
+							   stmt->if_not_exists,
+							   stmt->XactEndAction);
 
 	elog(DEBUG1, "record for session variable \"%s\" (oid:%d) was created in pg_variable",
 		 stmt->variable->relname, variable.objectId);
@@ -230,6 +241,8 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 	/* We want SessionVariableCreatePostprocess to see the catalog changes. */
 	CommandCounterIncrement();
 
+	SessionVariableCreatePostprocess(variable.objectId, stmt->XactEndAction);
+
 	return variable;
 }
 
@@ -242,6 +255,7 @@ DropVariableById(Oid varid)
 {
 	Relation	rel;
 	HeapTuple	tup;
+	char		XactEndAction;
 
 	rel = table_open(VariableRelationId, RowExclusiveLock);
 
@@ -250,6 +264,8 @@ DropVariableById(Oid varid)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for variable %u", varid);
 
+	XactEndAction = ((Form_pg_variable) GETSTRUCT(tup))->varxactendaction;
+
 	CatalogTupleDelete(rel, &tup->t_self);
 
 	ReleaseSysCache(tup);
@@ -257,5 +273,5 @@ DropVariableById(Oid varid)
 	table_close(rel, RowExclusiveLock);
 
 	/* Do the necessary cleanup if needed in local memory */
-	SessionVariableDropPostprocess(varid);
+	SessionVariableDropPostprocess(varid, XactEndAction);
 }
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index d9169820a7f..3a3489ec743 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -16,6 +16,8 @@
 
 #include "access/xact.h"
 #include "catalog/pg_variable.h"
+#include "catalog/dependency.h"
+#include "catalog/namespace.h"
 #include "commands/session_variable.h"
 #include "executor/svariableReceiver.h"
 #include "funcapi.h"
@@ -31,6 +33,19 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
+typedef struct SVariableXActDropItem
+{
+	Oid			varid;			/* varid of session variable */
+
+	/*
+	 * creating_subid is the ID of the creating subxact. If the action was
+	 * unregistered during the current transaction, deleting_subid is the ID
+	 * of the deleting subxact, otherwise InvalidSubTransactionId.
+	 */
+	SubTransactionId creating_subid;
+	SubTransactionId deleting_subid;
+} SVariableXActDropItem;
+
 /*
  * Values of session variables are stored in the backend local memory
  * inside sessionvars hash table in binary format inside a dedicated memory
@@ -113,6 +128,12 @@ static bool needs_validation = false;
  */
 static LocalTransactionId validated_lxid = InvalidLocalTransactionId;
 
+/* list holds fields of SVariableXActDropItem type */
+static List *xact_drop_items = NIL;
+
+static void register_session_variable_xact_drop(Oid varid);
+static void unregister_session_variable_xact_drop(Oid varid);
+
 /*
  * Callback function for session variable invalidation.
  */
@@ -151,16 +172,45 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
 	}
 }
 
+/*
+ * Do the necessary work to setup local memory management of a new
+ * variable.
+ *
+ * Caller should already have created the necessary entry in catalog
+ * and made them visible.
+ */
+void
+SessionVariableCreatePostprocess(Oid varid, char XactEndAction)
+{
+	/*
+	 * For temporary variables, we need to create a new end of xact action to
+	 * ensure deletion from catalog.
+	 */
+	if (XactEndAction == VARIABLE_XACTEND_DROP)
+	{
+		Assert(isTempNamespace(get_session_variable_namespace(varid)));
+
+		register_session_variable_xact_drop(varid);
+	}
+}
+
 /*
  * Handle the local memory cleanup for a DROP VARIABLE command.
  *
  * Caller should take care of removing the pg_variable entry first.
  */
 void
-SessionVariableDropPostprocess(Oid varid)
+SessionVariableDropPostprocess(Oid varid, char XactEndAction)
 {
 	Assert(LocalTransactionIdIsValid(MyProc->vxid.lxid));
 
+	if (XactEndAction == VARIABLE_XACTEND_DROP)
+	{
+		Assert(isTempNamespace(get_session_variable_namespace(varid)));
+
+		unregister_session_variable_xact_drop(varid);
+	}
+
 	if (sessionvars)
 	{
 		bool		found;
@@ -183,6 +233,57 @@ SessionVariableDropPostprocess(Oid varid)
 	}
 }
 
+/*
+ * Registration of actions to be executed on session variables at transaction
+ * end time. We want to drop temporary session variables with clause ON COMMIT
+ * DROP.
+ */
+
+/*
+ * Register a session variable xact action.
+ */
+static void
+register_session_variable_xact_drop(Oid varid)
+{
+	SVariableXActDropItem *xact_ai;
+	MemoryContext oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+	xact_ai = (SVariableXActDropItem *)
+		palloc(sizeof(SVariableXActDropItem));
+
+	xact_ai->varid = varid;
+
+	xact_ai->creating_subid = GetCurrentSubTransactionId();
+	xact_ai->deleting_subid = InvalidSubTransactionId;
+
+	xact_drop_items = lcons(xact_ai, xact_drop_items);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Unregister an id of a given session variable from drop list. In this
+ * moment, the action is just marked as deleted by setting deleting_subid. The
+ * calling even might be rollbacked, in which case we should not lose this
+ * action.
+ */
+static void
+unregister_session_variable_xact_drop(Oid varid)
+{
+	ListCell   *l;
+
+	foreach(l, xact_drop_items)
+	{
+		SVariableXActDropItem *xact_ai =
+			(SVariableXActDropItem *) lfirst(l);
+
+		if (xact_ai->varid == varid)
+			xact_ai->deleting_subid = GetCurrentSubTransactionId();
+	}
+}
+
 /*
  * Release stored value, free memory
  */
@@ -298,15 +399,92 @@ remove_invalid_session_variables(bool atEOX)
 }
 
 /*
- * Remove values of dropped session variables from memory.
+  * Perform ON COMMIT DROP for temporary session variables,
+  * and remove all dropped variables from memory.
  */
 void
-AtPreEOXact_SessionVariables(void)
+AtPreEOXact_SessionVariables(bool isCommit)
 {
-	/* We cannot to do it when transaction is aborted */
-	Assert(IsTransactionState());
+	if (isCommit)
+	{
+		if (xact_drop_items)
+		{
+			ListCell   *l;
+
+			foreach(l, xact_drop_items)
+			{
+				SVariableXActDropItem *xact_ai =
+					(SVariableXActDropItem *) lfirst(l);
+
+				/* Iterate only over entries that are still pending */
+				if (xact_ai->deleting_subid == InvalidSubTransactionId)
+				{
+					ObjectAddress object;
+
+					object.classId = VariableRelationId;
+					object.objectId = xact_ai->varid;
+					object.objectSubId = 0;
+
+					/*
+					 * Since this is an automatic drop, rather than one
+					 * directly initiated by the user, we pass the
+					 * PERFORM_DELETION_INTERNAL flag.
+					 */
+					elog(DEBUG1, "session variable (oid:%u) will be deleted (forced by ON COMMIT DROP clause)",
+						 xact_ai->varid);
+
+					performDeletion(&object, DROP_CASCADE,
+									PERFORM_DELETION_INTERNAL |
+									PERFORM_DELETION_QUIETLY);
+				}
+			}
+		}
 
-	remove_invalid_session_variables(true);
+		remove_invalid_session_variables(true);
+	}
+
+	/*
+	 * We have to clean xact_drop_items. All related variables are dropped
+	 * now, or lost inside aborted transaction.
+	 */
+	list_free_deep(xact_drop_items);
+	xact_drop_items = NULL;
+}
+
+/*
+ * Post-subcommit or post-subabort cleanup of xact drop list.
+ *
+ * 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)
+{
+	ListCell   *cur_item;
+
+	foreach(cur_item, xact_drop_items)
+	{
+		SVariableXActDropItem *xact_ai =
+			(SVariableXActDropItem *) lfirst(cur_item);
+
+		if (!isCommit && xact_ai->creating_subid == mySubid)
+		{
+			/* cur_item must be removed */
+			xact_drop_items = foreach_delete_current(xact_drop_items, cur_item);
+			pfree(xact_ai);
+		}
+		else
+		{
+			/* cur_item must be preserved */
+			if (xact_ai->creating_subid == mySubid)
+				xact_ai->creating_subid = parentSubid;
+			if (xact_ai->deleting_subid == mySubid)
+				xact_ai->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId;
+		}
+	}
 }
 
 /*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index fdad8338324..6f74f14f6ae 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -484,7 +484,7 @@ DefineView(ViewStmt *stmt, const char *queryString,
 	 */
 	view = copyObject(stmt->view);	/* don't corrupt original command */
 	if (view->relpersistence == RELPERSISTENCE_PERMANENT
-		&& isQueryUsingTempRelation(viewParse))
+		&& isQueryUsingTempObject(viewParse))
 	{
 		view->relpersistence = RELPERSISTENCE_TEMP;
 		ereport(NOTICE,
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 2c0a66fb2cc..47074136e6b 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -3303,10 +3303,10 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 		 * creation query. It would be hard to refresh data or incrementally
 		 * maintain it if a source disappeared.
 		 */
-		if (isQueryUsingTempRelation(query))
+		if (isQueryUsingTempObject(query))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("materialized views must not use temporary tables or views")));
+					 errmsg("materialized views must not use temporary tables, views or session variables")));
 
 		/*
 		 * A materialized view would either need to save parameters for use in
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c17a71f6656..48523e6c3b5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -480,6 +480,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	 OptTemp
 %type <ival>	 OptNoLog
 %type <oncommit> OnCommitOption
+%type <ival>	 XactEndActionOption
 
 %type <ival>	for_locking_strength
 %type <node>	for_locking_item
@@ -5221,26 +5222,39 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
-			CREATE VARIABLE qualified_name opt_as Typename opt_collate_clause
+			CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause XactEndActionOption
 				{
 					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
-					n->variable = $3;
-					n->typeName = $5;
-					n->collClause = (CollateClause *) $6;
+					$4->relpersistence = $2;
+					n->variable = $4;
+					n->typeName = $6;
+					n->collClause = (CollateClause *) $7;
+					n->XactEndAction = $8;
 					n->if_not_exists = false;
 					$$ = (Node *) n;
 				}
-			| CREATE VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause
+			| CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause XactEndActionOption
 				{
 					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
-					n->variable = $6;
-					n->typeName = $8;
-					n->collClause = (CollateClause *) $9;
+					$7->relpersistence = $2;
+					n->variable = $7;
+					n->typeName = $9;
+					n->collClause = (CollateClause *) $10;
+					n->XactEndAction = $11;
 					n->if_not_exists = true;
 					$$ = (Node *) n;
 				}
 		;
 
+/*
+ * Temporary session variables can be dropped on successful
+ * transaction end like tables.
+ */
+XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
+			| /*EMPTY*/								{ $$ = VARIABLE_XACTEND_NOOP; }
+		;
+
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 2f64eaf0e37..bde5b1118c5 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -101,7 +101,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 static int	specialAttNum(const char *attname);
 static bool rte_visible_if_lateral(ParseState *pstate, RangeTblEntry *rte);
 static bool rte_visible_if_qualified(ParseState *pstate, RangeTblEntry *rte);
-static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool isQueryUsingTempObject_walker(Node *node, void *context);

 /*
@@ -3821,13 +3821,13 @@ rte_visible_if_qualified(ParseState *pstate, RangeTblEntry *rte)
  * the query is a temporary relation (table, view, or materialized view).
  */
 bool
-isQueryUsingTempRelation(Query *query)
+isQueryUsingTempObject(Query *query)
 {
-	return isQueryUsingTempRelation_walker((Node *) query, NULL);
+	return isQueryUsingTempObject_walker((Node *) query, NULL);
 }
 
 static bool
-isQueryUsingTempRelation_walker(Node *node, void *context)
+isQueryUsingTempObject_walker(Node *node, void *context)
 {
 	if (node == NULL)
 		return false;
@@ -3853,13 +3853,23 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 		}
 
 		return query_tree_walker(query,
-								 isQueryUsingTempRelation_walker,
+								 isQueryUsingTempObject_walker,
 								 context,
 								 QTW_IGNORE_JOINALIASES);
 	}
+	else if (IsA(node, Param))
+	{
+		Param	   *p = (Param *) node;
+
+		if (p->paramkind == PARAM_VARIABLE)
+		{
+			if (isAnyTempNamespace(get_session_variable_namespace(p->paramvarid)))
+				return true;
+		}
+	}
 
 	return expression_tree_walker(node,
-								  isQueryUsingTempRelation_walker,
+								  isQueryUsingTempObject_walker,
 								  context);
 }
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index adf83ed1970..f6aa8bc5f6c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5159,7 +5159,7 @@ listVariables(const char *pattern, bool verbose)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, false, false, false, false};
 
 	if (pset.sversion < 180000)
 	{
@@ -5179,12 +5179,16 @@ listVariables(const char *pattern, bool verbose)
 					  "  pg_catalog.format_type(v.vartype, v.vartypmod) as \"%s\",\n"
 					  "  (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type bt\n"
 					  "   WHERE c.oid = v.varcollation AND bt.oid = v.vartype AND v.varcollation <> bt.typcollation) as \"%s\",\n"
-					  "  pg_catalog.pg_get_userbyid(v.varowner) as \"%s\"\n",
+					  "  pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n"
+					  "  CASE v.varxactendaction\n"
+					  "    WHEN 'd' THEN 'ON COMMIT DROP'\n"
+					  "  END as \"%s\"\n",
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("Type"),
 					  gettext_noop("Collation"),
-					  gettext_noop("Owner"));
+					  gettext_noop("Owner"),
+					  gettext_noop("Transactional end action"));
 
 	if (verbose)
 	{
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 474dd1d569b..1ad930a2b1e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3599,7 +3599,8 @@ psql_completion(const char *text, int start, int end)
 	}
 /* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VARIABLE <name> with AS */
-	else if (TailMatches("CREATE", "VARIABLE", MatchAny))
+	else if (TailMatches("CREATE", "VARIABLE", MatchAny) ||
+			 TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny))
 		COMPLETE_WITH("AS");
 	else if (TailMatches("VARIABLE", MatchAny, "AS"))
 		/* Complete CREATE VARIABLE <name> with AS types */
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
index 1f4112790af..e3aca285a2b 100644
--- a/src/include/catalog/pg_variable.h
+++ b/src/include/catalog/pg_variable.h
@@ -54,6 +54,9 @@ CATALOG(pg_variable,9222,VariableRelationId)
 	/* typmod for variable's type */
 	int32		vartypmod BKI_DEFAULT(-1);
 
+	/* action on transaction end */
+	char		varxactendaction BKI_DEFAULT(n);
+
 	/* variable collation */
 	Oid			varcollation BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_collation);
 
@@ -66,6 +69,12 @@ CATALOG(pg_variable,9222,VariableRelationId)
 #endif
 } FormData_pg_variable;
 
+typedef enum VariableXactEndAction
+{
+	VARIABLE_XACTEND_NOOP = 'n',	/* NOOP */
+	VARIABLE_XACTEND_DROP = 'd',	/* ON COMMIT DROP */
+}			VariableXactEndAction;
+
 /* ----------------
  *		Form_pg_variable corresponds to a pointer to a tuple with
  *		the format of pg_variable relation.
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 0d4c635ae7b..dc83296b1ba 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -21,8 +21,11 @@
 #include "tcop/cmdtag.h"
 #include "utils/queryenvironment.h"
 
-extern void SessionVariableDropPostprocess(Oid varid);
-extern void AtPreEOXact_SessionVariables(void);
+extern void SessionVariableCreatePostprocess(Oid varid, char XactEndAction);
+extern void SessionVariableDropPostprocess(Oid varid, char XactEndAction);
+extern void AtPreEOXact_SessionVariables(bool isCommit);
+extern void AtEOSubXact_SessionVariables(bool isCommit, SubTransactionId mySubid,
+										 SubTransactionId parentSubid);
 
 extern void SetSessionVariable(Oid varid, Datum value, bool isNull);
 extern void SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fbab338a938..56e284dc09c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3455,6 +3455,7 @@ typedef struct CreateSessionVarStmt
 	TypeName   *typeName;		/* the type of variable */
 	CollateClause *collClause;
 	bool		if_not_exists;	/* do nothing if it already exists */
+	char		XactEndAction;	/* on transaction end action */
 } CreateSessionVarStmt;

diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 722df699537..18527d0f85b 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -51,7 +51,9 @@ typedef struct Alias
 	List	   *colnames;		/* optional list of column aliases */
 } Alias;
 
-/* What to do at commit time for temporary relations */
+/*
+ * What to do at commit time for temporary relations or session variables.
+ */
 typedef enum OnCommitAction
 {
 	ONCOMMIT_NOOP,				/* No ON COMMIT clause (do nothing) */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index bea2da54961..585eadf6524 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -124,6 +124,6 @@ extern int	attnameAttNum(Relation rd, const char *attname, bool sysColOK);
 extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
-extern bool isQueryUsingTempRelation(Query *query);
+extern bool isQueryUsingTempObject(Query *query);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/test/isolation/expected/session-variable.out b/src/test/isolation/expected/session-variable.out
index 2968cce0895..9fbbf9057a6 100644
--- a/src/test/isolation/expected/session-variable.out
+++ b/src/test/isolation/expected/session-variable.out
@@ -85,10 +85,11 @@ myvar
 
 step sr1: ROLLBACK;
 
-starting permutation: create3 let3 s3 create4 let4 drop4 drop3 inval3 discard sc3 state
+starting permutation: create3 let3 s3 o_c_d create4 let4 drop4 drop3 inval3 discard sc3 state
 step create3: CREATE VARIABLE myvar3 AS text;
 step let3: LET myvar3 = 'test';
 step s3: BEGIN;
+step o_c_d: CREATE TEMP VARIABLE myvar_o_c_d AS text ON COMMIT DROP;
 step create4: CREATE VARIABLE myvar4 AS text;
 step let4: LET myvar4 = 'test';
 step drop4: DROP VARIABLE myvar4;
diff --git a/src/test/isolation/specs/session-variable.spec b/src/test/isolation/specs/session-variable.spec
index c864fee4006..45e65d4085d 100644
--- a/src/test/isolation/specs/session-variable.spec
+++ b/src/test/isolation/specs/session-variable.spec
@@ -24,6 +24,7 @@ step create		{ CREATE VARIABLE myvar AS text; }
 session s3
 step s3			{ BEGIN; }
 step let3		{ LET myvar3 = 'test'; }
+step o_c_d		{ CREATE TEMP VARIABLE myvar_o_c_d AS text ON COMMIT DROP; }
 step create4	{ CREATE VARIABLE myvar4 AS text; }
 step let4		{ LET myvar4 = 'test'; }
 step drop4		{ DROP VARIABLE myvar4; }
@@ -47,4 +48,4 @@ permutation let val dbg drop create dbg val
 # calling the dbg step after the concurrent drop
 permutation let val s1 dbg drop create dbg val sr1
 # test for DISCARD ALL when all internal queues have actions registered
-permutation create3 let3 s3 create4 let4 drop4 drop3 inval3 discard sc3 state
+permutation create3 let3 s3 o_c_d create4 let4 drop4 drop3 inval3 discard sc3 state
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index c89067490f7..ad7cd1ca390 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5833,20 +5833,20 @@ CREATE ROLE regress_variable_owner;
 SET ROLE TO regress_variable_owner;
 CREATE VARIABLE var1 AS varchar COLLATE "C";
 \dV+ var1
-                                            List of variables
- Schema | Name |       Type        | Collation |         Owner          | Access privileges | Description 
---------+------+-------------------+-----------+------------------------+-------------------+-------------
- public | var1 | character varying | C         | regress_variable_owner |                   | 
+                                                          List of variables
+ Schema | Name |       Type        | Collation |         Owner          | Transactional end action | Access privileges | Description 
+--------+------+-------------------+-----------+------------------------+--------------------------+-------------------+-------------
+ public | var1 | character varying | C         | regress_variable_owner |                          |                   | 
 (1 row)
 
 GRANT SELECT ON VARIABLE var1 TO PUBLIC;
 COMMENT ON VARIABLE var1 IS 'some description';
 \dV+ var1
-                                                              List of variables
- Schema | Name |       Type        | Collation |         Owner          |                Access privileges                 |   Description    
---------+------+-------------------+-----------+------------------------+--------------------------------------------------+------------------
- public | var1 | character varying | C         | regress_variable_owner | regress_variable_owner=rw/regress_variable_owner+| some description
-        |      |                   |           |                        | =r/regress_variable_owner                        | 
+                                                                            List of variables
+ Schema | Name |       Type        | Collation |         Owner          | Transactional end action |                Access privileges                 |   Description    
+--------+------+-------------------+-----------+------------------------+--------------------------+--------------------------------------------------+------------------
+ public | var1 | character varying | C         | regress_variable_owner |                          | regress_variable_owner=rw/regress_variable_owner+| some description
+        |      |                   |           |                        |                          | =r/regress_variable_owner                        | 
 (1 row)
 
 DROP VARIABLE var1;
@@ -6314,9 +6314,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-            List of variables
- Schema | Name | Type | Collation | Owner 
---------+------+------+-----------+-------
+                          List of variables
+ Schema | Name | Type | Collation | Owner | Transactional end action 
+--------+------+------+-----------+-------+--------------------------
 (0 rows)
 
 -- again, but with dotted schema qualifications.
@@ -6489,9 +6489,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 \dy "no.such.schema"."no.such.event.trigger"
 improper qualified name (too many dotted names): "no.such.schema"."no.such.event.trigger"
 \dV "no.such.schema"."no.such.variable"
-            List of variables
- Schema | Name | Type | Collation | Owner 
---------+------+------+-----------+-------
+                          List of variables
+ Schema | Name | Type | Collation | Owner | Transactional end action 
+--------+------+------+-----------+-------+--------------------------
 (0 rows)
 
 -- again, but with current database and dotted schema qualifications.
@@ -6628,9 +6628,9 @@ List of text search templates
 (0 rows)
 
 \dV regression."no.such.schema"."no.such.variable"
-            List of variables
- Schema | Name | Type | Collation | Owner 
---------+------+------+-----------+-------
+                          List of variables
+ Schema | Name | Type | Collation | Owner | Transactional end action 
+--------+------+------+-----------+-------+--------------------------
 (0 rows)
 
 -- again, but with dotted database and dotted schema qualifications.
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 358502fa31c..5c81b3e3435 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -45,11 +45,11 @@ SET ROLE TO regress_variable_owner;
 CREATE VARIABLE svartest.var1 AS int;
 SET ROLE TO DEFAULT;
 \dV+ svartest.var1
-                                                        List of variables
-  Schema  | Name |  Type   | Collation |         Owner          |                Access privileges                 | Description 
-----------+------+---------+-----------+------------------------+--------------------------------------------------+-------------
- svartest | var1 | integer |           | regress_variable_owner | regress_variable_owner=rw/regress_variable_owner+| 
-          |      |         |           |                        | regress_variable_reader=r/regress_variable_owner | 
+                                                                     List of variables
+  Schema  | Name |  Type   | Collation |         Owner          | Transactional end action |                Access privileges                 | Description 
+----------+------+---------+-----------+------------------------+--------------------------+--------------------------------------------------+-------------
+ svartest | var1 | integer |           | regress_variable_owner |                          | regress_variable_owner=rw/regress_variable_owner+| 
+          |      |         |           |                        |                          | regress_variable_reader=r/regress_variable_owner | 
 (1 row)
 
 DROP VARIABLE svartest.var1;
@@ -1387,3 +1387,123 @@ SELECT var1;
 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables
+CREATE TEMP VARIABLE var1 AS int;
+-- this view should be temporary
+CREATE VIEW var_test_view AS SELECT var1;
+NOTICE:  view "var_test_view" will be a temporary view
+DROP VARIABLE var1 CASCADE;
+NOTICE:  drop cascades to view var_test_view
+BEGIN;
+  CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+COMMIT;
+-- should be zero
+SELECT count(*) FROM pg_variable WHERE varname = 'var1';
+ count 
+-------
+     0
+(1 row)
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+ count 
+-------
+     0
+(1 row)
+
+BEGIN;
+  CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+ROLLBACK;
+-- should be zero
+SELECT count(*) FROM pg_variable WHERE varname = 'var1';
+ count 
+-------
+     0
+(1 row)
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+ count 
+-------
+     0
+(1 row)
+
+BEGIN;
+  CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP;
+  LET var1 = 100;
+  DROP VARIABLE var1;
+COMMIT;
+-- should be zero
+SELECT count(*) FROM pg_variable WHERE varname = 'var1';
+ count 
+-------
+     0
+(1 row)
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+ count 
+-------
+     0
+(1 row)
+
+BEGIN;
+  CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP;
+  LET var1 = 100;
+  DROP VARIABLE var1;
+ROLLBACK;
+-- should be zero
+SELECT count(*) FROM pg_variable WHERE varname = 'var1';
+ count 
+-------
+     0
+(1 row)
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+ count 
+-------
+     0
+(1 row)
+
+BEGIN;
+  CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP;
+  LET var1 = 100;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  ROLLBACK TO s1;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+COMMIT;
+-- should be zero
+SELECT count(*) FROM pg_variable WHERE varname = 'var1';
+ count 
+-------
+     0
+(1 row)
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+ count 
+-------
+     0
+(1 row)
+
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 3436b57e8c7..1f2ba8111f4 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -813,6 +813,7 @@ BEGIN;
   DROP VARIABLE var1;
   SELECT var2;
 ROLLBACK;
+
 -- should be ok
 SELECT var1;
 
@@ -939,3 +940,68 @@ SELECT var1;
 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
+CREATE TEMP VARIABLE var1 AS int;
+-- this view should be temporary
+CREATE VIEW var_test_view AS SELECT var1;
+
+DROP VARIABLE var1 CASCADE;
+
+BEGIN;
+  CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP;
+  LET var1 = 100;
+  SELECT var1;
+COMMIT;
+
+-- should be zero
+SELECT count(*) FROM pg_variable WHERE varname = 'var1';
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+
+BEGIN;
+  CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP;
+  LET var1 = 100;
+  SELECT var1;
+ROLLBACK;
+
+-- should be zero
+SELECT count(*) FROM pg_variable WHERE varname = 'var1';
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+
+BEGIN;
+  CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP;
+  LET var1 = 100;
+  DROP VARIABLE var1;
+COMMIT;
+
+-- should be zero
+SELECT count(*) FROM pg_variable WHERE varname = 'var1';
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+
+BEGIN;
+  CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP;
+  LET var1 = 100;
+  DROP VARIABLE var1;
+ROLLBACK;
+
+-- should be zero
+SELECT count(*) FROM pg_variable WHERE varname = 'var1';
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
+
+BEGIN;
+  CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP;
+  LET var1 = 100;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  ROLLBACK TO s1;
+  SELECT var1;
+COMMIT;
+
+-- should be zero
+SELECT count(*) FROM pg_variable WHERE varname = 'var1';
+-- should be zero
+SELECT count(*) FROM pg_session_variables();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bb9d831481d..91a963f9570 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2787,6 +2787,7 @@ SupportRequestWFuncMonotonic
 SVariable
 SVariableData
 SVariableState
+SVariableXActDropItem
 Syn
 SyncOps
 SyncRepConfigData
-- 
2.45.2

From a58a43dc29272319ff9bc989edebe04c8528bf50 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

The expression executor can be called directly (not inside query executor)
when expression is evaluated as simple expression in plpgsql or when expression
is an argument of CALL statement. For these cases we need to allow direct
access to content of session variables from executor. We can do it, because
we know so the expression is not evaluated under query and cannot be
evaluated inside parallel worker.

This patch enables session variables for simple expression evaluation.

This patch enables usage session variables in arguments of CALL stmt.
---
 src/backend/executor/execExpr.c               | 88 +++++++++++++------
 src/backend/executor/execExprInterp.c         | 16 ++++
 src/backend/jit/llvm/llvmjit_expr.c           |  6 ++
 src/backend/parser/analyze.c                  |  8 --
 src/include/executor/execExpr.h               | 12 ++-
 .../src/expected/plpgsql_session_variable.out |  3 +-
 src/pl/plpgsql/src/pl_exec.c                  |  3 +-
 .../regress/expected/session_variables.out    | 15 ++--
 src/test/regress/sql/session_variables.sql    |  7 +-
 9 files changed, 108 insertions(+), 50 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 972233ec25b..15aefdc313b 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1001,31 +1001,69 @@ ExecInitExprRec(Expr *node, ExprState *state,
 								es_num_session_variables = state->parent->state->es_num_session_variables;
 							}
 
-							Assert(es_session_variables);
-
-							/*
-							 * Use buffered session variables when the
-							 * buffer with copied values is avaiable
-							 * (standard query executor mode)
-							 */
-
-							/* Parameter sanity checks. */
-							if (param->paramid >= es_num_session_variables)
-								elog(ERROR, "paramid of PARAM_VARIABLE param is out of range");
-
-							var = &es_session_variables[param->paramid];
-
-							if (var->typid != param->paramtype)
-								elog(ERROR, "type of buffered value is different than PARAM_VARIABLE type");
-
-							/*
-							 * In this case, pass the value like a
-							 * constant.
-							 */
-							scratch.opcode = EEOP_CONST;
-							scratch.d.constval.value = var->value;
-							scratch.d.constval.isnull = var->isnull;
-							ExprEvalPushStep(state, &scratch);
+							if (es_session_variables)
+							{
+								/*
+								 * This path is used when expression is
+								 * evaluated inside query evaluation. For
+								 * ensuring of immutability of session variable
+								 * inside query we use special buffer with copy
+								 * of used session variables. This method helps
+								 * with parallel execution too.
+								 */
+
+								/* Parameter sanity checks. */
+								if (param->paramid >= es_num_session_variables)
+									elog(ERROR, "paramid of PARAM_VARIABLE param is out of range");
+
+								var = &es_session_variables[param->paramid];
+
+								if (var->typid != param->paramtype)
+									elog(ERROR, "type of buffered value is different than PARAM_VARIABLE type");
+
+								/*
+								 * In this case, pass the value like a
+								 * constant.
+								 */
+								scratch.opcode = EEOP_CONST;
+								scratch.d.constval.value = var->value;
+								scratch.d.constval.isnull = var->isnull;
+								ExprEvalPushStep(state, &scratch);
+							}
+							else
+							{
+								AclResult	aclresult;
+								Oid			varid = param->paramvarid;
+								Oid			vartype = param->paramtype;
+
+								Assert(!IsParallelWorker());
+
+								/*
+								 * In some cases (plpgsql's simple expression
+								 * evaluation or evaluation of CALL arguments),
+								 * the expression executor is called directly.
+								 * We can allow direct access to session
+								 * variables (copy only), because we know, so
+								 * outside is not any query (and expression
+								 * cannot be executed parallel).
+								 *
+								 * In this case we should to do aclcheck,
+								 * because usual aclcheck from
+								 * standard_ExecutorStart is not executed in
+								 * this case. Fortunately it is just once per
+								 * transaction.
+								 */
+								aclresult = object_aclcheck(VariableRelationId, varid,
+															GetUserId(), ACL_SELECT);
+								if (aclresult != ACLCHECK_OK)
+									aclcheck_error(aclresult, OBJECT_VARIABLE,
+												   get_session_variable_name(varid));
+
+								scratch.opcode = EEOP_PARAM_VARIABLE;
+								scratch.d.vparam.varid = varid;
+								scratch.d.vparam.vartype = vartype;
+								ExprEvalPushStep(state, &scratch);
+							}
 						}
 						break;
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index d8735286c4d..80a862b2843 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -59,6 +59,7 @@
 #include "access/heaptoast.h"
 #include "catalog/pg_type.h"
 #include "commands/sequence.h"
+#include "commands/session_variable.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -450,6 +451,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_PARAM_EXEC,
 		&&CASE_EEOP_PARAM_EXTERN,
 		&&CASE_EEOP_PARAM_CALLBACK,
+		&&CASE_EEOP_PARAM_VARIABLE,
 		&&CASE_EEOP_CASE_TESTVAL,
 		&&CASE_EEOP_MAKE_READONLY,
 		&&CASE_EEOP_IOCOERCE,
@@ -1093,6 +1095,20 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_PARAM_VARIABLE)
+		{
+			/*
+			 * Direct access to session variable (without buffering). Because
+			 * returned value can be used (without an assignement) after the
+			 * referenced session variables is updated, we have to use an copy
+			 * of stored value every time.
+			 */
+			*op->resvalue = GetSessionVariableWithTypeCheck(op->d.vparam.varid,
+															op->resnull,
+															op->d.vparam.vartype);
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_CASE_TESTVAL)
 		{
 			/*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index cbd9ed7cc4b..7af83f5a8e6 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -1145,6 +1145,12 @@ llvm_compile_expr(ExprState *state)
 					break;
 				}
 
+			case EEOP_PARAM_VARIABLE:
+				build_EvalXFunc(b, mod, "ExecEvalParamVariable",
+								v_state, op, v_econtext);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
 			case EEOP_SBSREF_SUBSCRIPTS:
 				{
 					int			jumpdone = op->d.sbsref_subscript.jumpdone;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 47074136e6b..d3661a9ad48 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -3383,14 +3383,6 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 							 true,
 							 stmt->funccall->location);
 
-	/*
-	 * The arguments of CALL statement are evaluated by direct
-	 * expression executor call. This path is unsupported yet,
-	 * so block it. It will be enabled by some other patch.
-	 */
-	if (pstate->p_hasSessionVariables)
-		elog(ERROR, "session variable cannot be used as an argument");
-
 	assign_expr_collations(pstate, node);
 
 	fexpr = castNode(FuncExpr, node);
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 55337d49166..0477723fa2e 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -156,10 +156,11 @@ typedef enum ExprEvalOp
 	EEOP_BOOLTEST_IS_FALSE,
 	EEOP_BOOLTEST_IS_NOT_FALSE,
 
-	/* evaluate PARAM_EXEC/EXTERN parameters */
+	/* evaluate PARAM_EXEC/EXTERN/VARIABLE parameters */
 	EEOP_PARAM_EXEC,
 	EEOP_PARAM_EXTERN,
 	EEOP_PARAM_CALLBACK,
+	EEOP_PARAM_VARIABLE,
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -400,6 +401,13 @@ typedef struct ExprEvalStep
 			Oid			paramtype;	/* OID of parameter's datatype */
 		}			cparam;
 
+		/* for EEOP_PARAM_VARIABLE */
+		struct
+		{
+			Oid			varid;		/* OID of assigned variable */
+			Oid			vartype;	/* OID of parameter's datatype */
+		}			vparam;
+
 		/* for EEOP_CASE_TESTVAL/DOMAIN_TESTVAL */
 		struct
 		{
@@ -798,6 +806,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
 							  ExprContext *econtext);
 extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
 								ExprContext *econtext);
+extern void ExecEvalParamVariable(ExprState *state, ExprEvalStep *op,
+								  ExprContext *econtext);
 extern void ExecEvalCoerceViaIOSafe(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/pl/plpgsql/src/expected/plpgsql_session_variable.out b/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
index ec07f1cbae9..789c3123b0c 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
@@ -211,8 +211,7 @@ SET ROLE TO regress_var_exec_role;
 -- should to fail
 SELECT var_read_func();
 ERROR:  permission denied for session variable plpgsql_sv_var1
-CONTEXT:  SQL expression "plpgsql_sv_var1"
-PL/pgSQL function var_read_func() line 3 at RAISE
+CONTEXT:  PL/pgSQL function var_read_func() line 3 at RAISE
 SET ROLE TO DEFAULT;
 SET ROLE TO regress_var_owner_role;
 GRANT SELECT ON VARIABLE plpgsql_sv_var1 TO regress_var_reader_role;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index a9e1e323ad1..239b3250a95 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8101,8 +8101,7 @@ exec_is_simple_query(PLpgSQL_expr *expr)
 		query->sortClause ||
 		query->limitOffset ||
 		query->limitCount ||
-		query->setOperations ||
-		query->hasSessionVariables)
+		query->setOperations)
 		return false;
 
 	/*
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 6ef1a6f88c1..05e5952405e 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -107,8 +107,7 @@ ERROR:  permission denied for session variable var1
 CONTEXT:  SQL function "sqlfx" statement 1
 SELECT plpgsqlfx(20);
 ERROR:  permission denied for session variable var1
-CONTEXT:  SQL expression "$1 + var1"
-PL/pgSQL function plpgsqlfx(integer) line 1 at RETURN
+CONTEXT:  PL/pgSQL function plpgsqlfx(integer) line 1 at RETURN
 -- should be ok
 SELECT sqlfx_sd(20);
  sqlfx_sd 
@@ -278,17 +277,15 @@ $$;
 NOTICE:  result array: {1,2,3,4,5,6,7,8,9,10}
 DROP VARIABLE var1;
 DROP VARIABLE var2;
--- CALL statement is not supported yet
--- requires direct access to session variable from expression executor
+-- CALL statement is supported
 CREATE VARIABLE v int;
+LET v = 1;
 CREATE PROCEDURE p(arg int) AS $$ BEGIN RAISE NOTICE '%', arg; END $$ LANGUAGE plpgsql;
--- should not crash (but is not supported yet)
+-- should to work
 CALL p(v);
-ERROR:  session variable cannot be used as an argument
+NOTICE:  1
 DO $$ BEGIN CALL p(v); END $$;
-ERROR:  session variable cannot be used as an argument
-CONTEXT:  SQL statement "CALL p(v)"
-PL/pgSQL function inline_code_block line 1 at CALL
+NOTICE:  1
 DROP PROCEDURE p(int);
 DROP VARIABLE v;
 -- test search path
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index d0222f80a49..074619c4a0e 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -207,13 +207,14 @@ $$;
 DROP VARIABLE var1;
 DROP VARIABLE var2;
 
--- CALL statement is not supported yet
--- requires direct access to session variable from expression executor
+-- CALL statement is supported
 CREATE VARIABLE v int;
 
+LET v = 1;
+
 CREATE PROCEDURE p(arg int) AS $$ BEGIN RAISE NOTICE '%', arg; END $$ LANGUAGE plpgsql;
 
--- should not crash (but is not supported yet)
+-- should to work
 CALL p(v);
 
 DO $$ BEGIN CALL p(v); END $$;
-- 
2.45.2

From b7f6c7599762cd78f09795dff6901ceae237e438 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---
 src/backend/executor/execMain.c               |  14 +-
 src/backend/executor/execParallel.c           | 147 +++++++++++++++++-
 src/backend/optimizer/util/clauses.c          |  18 +--
 .../regress/expected/session_variables.out    |  12 +-
 4 files changed, 171 insertions(+), 20 deletions(-)

diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index f2fb680defc..4fbe7c71840 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -203,7 +203,19 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	 * related session variables are copied to dedicated array, and this array
 	 * is passed to executor.
 	 */
-	if (queryDesc->plannedstmt->sessionVariables)
+	if (queryDesc->num_session_variables > 0)
+	{
+		/*
+		 * When a parallel query needs to access query parameters (including
+		 * related session variables), then related session variables are
+		 * restored (deserialized) in queryDesc already. So just push pointer
+		 * of this array to executor's estate.
+		 */
+		Assert(IsParallelWorker());
+		estate->es_session_variables = queryDesc->session_variables;
+		estate->es_num_session_variables = queryDesc->num_session_variables;
+	}
+	else if (queryDesc->plannedstmt->sessionVariables)
 	{
 		ListCell   *lc;
 		int			nSessionVariables;
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index bfb3419efb7..cb434e2768e 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -12,8 +12,9 @@
  * workers and ensuring that their state generally matches that of the
  * leader; see src/backend/access/transam/README.parallel for details.
  * However, we must save and restore relevant executor state, such as
- * any ParamListInfo associated with the query, buffer/WAL usage info, and
- * the actual plan to be passed down to the worker.
+ * any ParamListInfo associated with the query, buffer/WAL usage info,
+ * session variables buffer, and the actual plan to be passed down to
+ * the worker.
  *
  * IDENTIFICATION
  *	  src/backend/executor/execParallel.c
@@ -64,6 +65,7 @@
 #define PARALLEL_KEY_QUERY_TEXT		UINT64CONST(0xE000000000000008)
 #define PARALLEL_KEY_JIT_INSTRUMENTATION UINT64CONST(0xE000000000000009)
 #define PARALLEL_KEY_WAL_USAGE			UINT64CONST(0xE00000000000000A)
+#define PARALLEL_KEY_SESSION_VARIABLES	UINT64CONST(0xE00000000000000B)
 
 #define PARALLEL_TUPLE_QUEUE_SIZE		65536
 
@@ -138,6 +140,12 @@ static bool ExecParallelRetrieveInstrumentation(PlanState *planstate,
 /* Helper function that runs in the parallel worker. */
 static DestReceiver *ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc);
 
+/* Helper functions that can pass values of session variables */
+static Size EstimateSessionVariables(EState *estate);
+static void SerializeSessionVariables(EState *estate, char **start_address);
+static SessionVariableValue *RestoreSessionVariables(char **start_address,
+													 int *num_session_variables);
+
 /*
  * Create a serialized representation of the plan to be sent to each worker.
  */
@@ -596,6 +604,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	char	   *pstmt_data;
 	char	   *pstmt_space;
 	char	   *paramlistinfo_space;
+	char	   *session_variables_space;
 	BufferUsage *bufusage_space;
 	WalUsage   *walusage_space;
 	SharedExecutorInstrumentation *instrumentation = NULL;
@@ -605,6 +614,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	int			instrumentation_len = 0;
 	int			jit_instrumentation_len = 0;
 	int			instrument_offset = 0;
+	int			session_variables_len = 0;
 	Size		dsa_minsize = dsa_minimum_size();
 	char	   *query_string;
 	int			query_len;
@@ -660,6 +670,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	shm_toc_estimate_chunk(&pcxt->estimator, paramlistinfo_len);
 	shm_toc_estimate_keys(&pcxt->estimator, 1);
 
+	/* Estimate space for serialized session variables. */
+	session_variables_len = EstimateSessionVariables(estate);
+	shm_toc_estimate_chunk(&pcxt->estimator, session_variables_len);
+	shm_toc_estimate_keys(&pcxt->estimator, 1);
+
 	/*
 	 * Estimate space for BufferUsage.
 	 *
@@ -761,6 +776,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMLISTINFO, paramlistinfo_space);
 	SerializeParamList(estate->es_param_list_info, &paramlistinfo_space);
 
+	/* Store serialized session variables. */
+	session_variables_space = shm_toc_allocate(pcxt->toc, session_variables_len);
+	shm_toc_insert(pcxt->toc, PARALLEL_KEY_SESSION_VARIABLES, session_variables_space);
+	SerializeSessionVariables(estate, &session_variables_space);
+
 	/* Allocate space for each worker's BufferUsage; no need to initialize. */
 	bufusage_space = shm_toc_allocate(pcxt->toc,
 									  mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -1411,6 +1431,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 	SharedJitInstrumentation *jit_instrumentation;
 	int			instrument_options = 0;
 	void	   *area_space;
+	char	   *sessionvariable_space;
 	dsa_area   *area;
 	ParallelWorkerContext pwcxt;
 
@@ -1436,6 +1457,14 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 	area_space = shm_toc_lookup(toc, PARALLEL_KEY_DSA, false);
 	area = dsa_attach_in_place(area_space, seg);
 
+	/* Reconstruct session variables. */
+	sessionvariable_space = shm_toc_lookup(toc,
+										   PARALLEL_KEY_SESSION_VARIABLES,
+										   false);
+	queryDesc->session_variables =
+		RestoreSessionVariables(&sessionvariable_space,
+								&queryDesc->num_session_variables);
+
 	/* Start up the executor */
 	queryDesc->plannedstmt->jitFlags = fpes->jit_flags;
 	ExecutorStart(queryDesc, fpes->eflags);
@@ -1504,3 +1533,117 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 	FreeQueryDesc(queryDesc);
 	receiver->rDestroy(receiver);
 }
+
+/*
+ * Estimate the amount of space required to serialize a session variable.
+ */
+static Size
+EstimateSessionVariables(EState *estate)
+{
+	int			i;
+	Size		sz = sizeof(int);
+
+	if (estate->es_session_variables == NULL)
+		return sz;
+
+	for (i = 0; i < estate->es_num_session_variables; i++)
+	{
+		SessionVariableValue *svarval;
+		Oid			typeOid;
+		int16		typLen;
+		bool		typByVal;
+
+		svarval = &estate->es_session_variables[i];
+
+		typeOid = svarval->typid;
+
+		sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+		/* space for datum/isnull */
+		Assert(OidIsValid(typeOid));
+		get_typlenbyval(typeOid, &typLen, &typByVal);
+
+		sz = add_size(sz,
+					  datumEstimateSpace(svarval->value, svarval->isnull, typByVal, typLen));
+	}
+
+	return sz;
+}
+
+/*
+ * Serialize a session variables buffer into caller-provided storage.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn.  The details for each parameter
+ * consist of a 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().  The caller is responsible for ensuring that there is
+ * enough storage to store the number of bytes that will be written; use
+ * EstimateSessionVariables to find out how many will be needed.
+ * *start_address is updated to point to the byte immediately following those
+ * written.
+ *
+ * RestoreSessionVariables can be used to recreate a session variable buffer
+ * based on the serialized representation;
+ */
+static void
+SerializeSessionVariables(EState *estate, char **start_address)
+{
+	int			nparams;
+	int			i;
+
+	/* Write number of parameters. */
+	nparams = estate->es_num_session_variables;
+	memcpy(*start_address, &nparams, sizeof(int));
+	*start_address += sizeof(int);
+
+	/* Write each parameter in turn. */
+	for (i = 0; i < nparams; i++)
+	{
+		SessionVariableValue *svarval;
+		Oid			typeOid;
+		int16		typLen;
+		bool		typByVal;
+
+		svarval = &estate->es_session_variables[i];
+		typeOid = svarval->typid;
+
+		/* Write type OID. */
+		memcpy(*start_address, &typeOid, sizeof(Oid));
+		*start_address += sizeof(Oid);
+
+		Assert(OidIsValid(typeOid));
+		get_typlenbyval(typeOid, &typLen, &typByVal);
+
+		datumSerialize(svarval->value, svarval->isnull, typByVal, typLen,
+					   start_address);
+	}
+}
+
+static SessionVariableValue *
+RestoreSessionVariables(char **start_address, int *num_session_variables)
+{
+	SessionVariableValue *session_variables;
+	int			i;
+	int			nparams;
+
+	memcpy(&nparams, *start_address, sizeof(int));
+	*start_address += sizeof(int);
+
+	*num_session_variables = nparams;
+	session_variables = (SessionVariableValue *)
+		palloc(nparams * sizeof(SessionVariableValue));
+
+	for (i = 0; i < nparams; i++)
+	{
+		SessionVariableValue *svarval = &session_variables[i];
+
+		/* Read type OID. */
+		memcpy(&svarval->typid, *start_address, sizeof(Oid));
+		*start_address += sizeof(Oid);
+
+		/* Read datum/isnull. */
+		svarval->value = datumRestore(start_address, &svarval->isnull);
+	}
+
+	return session_variables;
+}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 1b1fb6ecf81..092ed2353ac 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -924,25 +924,19 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 
 	/*
 	 * We can't pass Params to workers at the moment either, so they are also
-	 * parallel-restricted, unless they are PARAM_EXTERN Params or are
-	 * PARAM_EXEC Params listed in safe_param_ids, meaning they could be
-	 * either generated within workers or can be computed by the leader and
-	 * then their value can be passed to workers.
+	 * parallel-restricted, unless they are PARAM_EXTERN or PARAM_VARIABLE
+	 * Params or are PARAM_EXEC Params listed in safe_param_ids, meaning they
+	 * could be either generated within workers or can be computed by the
+	 * leader and then their value can be passed to workers.
 	 */
 	else if (IsA(node, Param))
 	{
 		Param	   *param = (Param *) node;
 
-		if (param->paramkind == PARAM_EXTERN)
+		if (param->paramkind == PARAM_EXTERN ||
+			param->paramkind == PARAM_VARIABLE)
 			return false;
 
-		/* We doesn't support passing session variables to workers */
-		if (param->paramkind == PARAM_VARIABLE)
-		{
-			if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
-				return true;
-		}
-
 		if (param->paramkind != PARAM_EXEC ||
 			!list_member_int(context->safe_param_ids, param->paramid))
 		{
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 05e5952405e..cbff3863c10 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -629,12 +629,14 @@ SELECT count(*) FROM svar_test WHERE a%10 = zero;
 
 -- parallel execution is not supported yet
 EXPLAIN (COSTS OFF) SELECT count(*) FROM svar_test WHERE a%10 = zero;
-            QUERY PLAN             
------------------------------------
+                 QUERY PLAN                 
+--------------------------------------------
  Aggregate
-   ->  Seq Scan on svar_test
-         Filter: ((a % 10) = zero)
-(3 rows)
+   ->  Gather
+         Workers Planned: 2
+         ->  Parallel Seq Scan on svar_test
+               Filter: ((a % 10) = zero)
+(5 rows)
 
 LET zero = (SELECT count(*) FROM svar_test);
 -- result should be 1000
-- 
2.45.2

From 07ac901fb7446807965d6245cd3dd831c40965a9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

There is not an reason why session variables should to block inlining.
(of SQL functions). I can imagine some use cases like wrapping, and
inlining significantly reduces an overhead of SQL functions.
---
 src/backend/optimizer/util/clauses.c          | 37 ++++++++++++++-----
 .../regress/expected/session_variables.out    |  7 ++--
 2 files changed, 30 insertions(+), 14 deletions(-)

diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 092ed2353ac..d98cf22daef 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4732,8 +4732,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 		querytree->limitOffset ||
 		querytree->limitCount ||
 		querytree->setOperations ||
-		(list_length(querytree->targetList) != 1) ||
-		querytree->hasSessionVariables)
+		(list_length(querytree->targetList) != 1))
 		goto fail;
 
 	/* If the function result is composite, resolve it */
@@ -4937,21 +4936,39 @@ substitute_actual_parameters_mutator(Node *node,
 {
 	if (node == NULL)
 		return NULL;
+
+
+	/*
+	 * SQL functions can contain two different kind of params. The nodes with
+	 * paramkind PARAM_EXTERN are related to function's arguments (and should
+	 * be replaced in this step), because this is how we apply the function's
+	 * arguments for an expression.
+	 *
+	 * The nodes with paramkind PARAM_VARIABLE are related to usage of session
+	 * variables. The values of session variables are not passed to expression
+	 * by expression arguments, so it should not be replaced here by
+	 * function's arguments.
+	 */
 	if (IsA(node, Param))
 	{
 		Param	   *param = (Param *) node;
 
-		if (param->paramkind != PARAM_EXTERN)
+		if (param->paramkind != PARAM_EXTERN &&
+			param->paramkind != PARAM_VARIABLE)
 			elog(ERROR, "unexpected paramkind: %d", (int) param->paramkind);
-		if (param->paramid <= 0 || param->paramid > context->nargs)
-			elog(ERROR, "invalid paramid: %d", param->paramid);
 
-		/* Count usage of parameter */
-		context->usecounts[param->paramid - 1]++;
+		if (param->paramkind == PARAM_EXTERN)
+		{
+			if (param->paramid <= 0 || param->paramid > context->nargs)
+				elog(ERROR, "invalid paramid: %d", param->paramid);
 
-		/* Select the appropriate actual arg and replace the Param with it */
-		/* We don't need to copy at this time (it'll get done later) */
-		return list_nth(context->args, param->paramid - 1);
+			/* Count usage of parameter */
+			context->usecounts[param->paramid - 1]++;
+
+			/* Select the appropriate actual arg and replace the Param with it */
+			/* We don't need to copy at this time (it'll get done later) */
+			return list_nth(context->args, param->paramid - 1);
+		}
 	}
 	return expression_tree_mutator(node, substitute_actual_parameters_mutator,
 								   (void *) context);
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index cbff3863c10..a75bc69ee8b 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -104,7 +104,6 @@ SELECT var1;
 ERROR:  permission denied for session variable var1
 SELECT sqlfx(20);
 ERROR:  permission denied for session variable var1
-CONTEXT:  SQL function "sqlfx" statement 1
 SELECT plpgsqlfx(20);
 ERROR:  permission denied for session variable var1
 CONTEXT:  PL/pgSQL function plpgsqlfx(integer) line 1 at RETURN
@@ -211,10 +210,10 @@ SELECT sqlfx1(sqlfx2('Hello'));
 
 -- inlining is blocked
 EXPLAIN (COSTS OFF, VERBOSE) SELECT sqlfx1(sqlfx2('Hello'));
-                      QUERY PLAN                      
-------------------------------------------------------
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
  Result
-   Output: sqlfx1(sqlfx2('Hello'::character varying))
+   Output: ((var1 || ', '::text) || ((var2 || ', '::text) || 'Hello'::text))
 (2 rows)
 
 DROP FUNCTION sqlfx1(varchar);
-- 
2.45.2

From 62aac1c8adc9b11c590fab1c98214188e07cb452 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

PLpgSQL allows to call expression executor directly (for simple expression).
This possibility strongly reduces overhead related to execution. Reimplementation
of LET statement (inside PLpgSQL) allows to use this possibility, and strongly
increase performance:

CREATE VARIABLE svar int;

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

From 120ms to 8ms (with assertions) (this is best case, but without it, the LET statement
can be bottle neck).

An alternative can be reimplementation of LET statement inside expression
executor, and then SQL LET command can be executed in simple expression
execution, but this increase an complexity of executor, but still the
benefits is only inside plpgsql, so it is better to this optimization
inside plpgsql.
---
 src/backend/executor/spi.c              |  3 ++
 src/backend/parser/analyze.c            | 12 ++++++
 src/backend/parser/gram.y               |  8 ++++
 src/backend/parser/parser.c             |  1 +
 src/include/nodes/parsenodes.h          |  2 +
 src/include/parser/parser.h             |  4 ++
 src/pl/plpgsql/src/pl_exec.c            | 55 +++++++++++++++++++++++++
 src/pl/plpgsql/src/pl_funcs.c           | 24 +++++++++++
 src/pl/plpgsql/src/pl_gram.y            | 28 ++++++++++++-
 src/pl/plpgsql/src/pl_reserved_kwlist.h |  1 +
 src/pl/plpgsql/src/plpgsql.h            | 12 ++++++
 src/tools/pgindent/typedefs.list        |  1 +
 12 files changed, 150 insertions(+), 1 deletion(-)

diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index e516c0a67c6..c3191fdc432 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2983,6 +2983,9 @@ _SPI_error_callback(void *arg)
 			case RAW_PARSE_PLPGSQL_ASSIGN3:
 				errcontext("PL/pgSQL assignment \"%s\"", query);
 				break;
+			case RAW_PARSE_PLPGSQL_LET:
+				errcontext("LET statement \"%s\"", query);
+				break;
 			default:
 				errcontext("SQL statement \"%s\"", query);
 				break;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index d3661a9ad48..762e058a497 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -1928,6 +1928,18 @@ transformLetStmt(ParseState *pstate, LetStmt *stmt)
 
 	stmt->query = (Node *) query;
 
+	/*
+	 * Inside PL/pgSQL we don't want to execute LET statement as utility
+	 * command, because it disallow to execute expression as simple
+	 * expression. So for PL/pgSQL we have extra path, and we return SELECT.
+	 * Then it can be executed by exec_eval_expr. Result is dirrectly assigned
+	 * to target session variable inside PL/pgSQL LET statement handler. This
+	 * is extra code, extra path, but possibility to get faster execution is
+	 * too attractive.
+	 */
+	if (stmt->plpgsql_mode)
+		return query;
+
 	/* represent the command as a utility Query */
 	result = makeNode(Query);
 	result->commandType = CMD_UTILITY;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7c6e077a46a..3bbc59eca2e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -832,6 +832,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %token		MODE_PLPGSQL_ASSIGN1
 %token		MODE_PLPGSQL_ASSIGN2
 %token		MODE_PLPGSQL_ASSIGN3
+%token		MODE_PLPGSQL_LET

 /* Precedence: lowest to highest */
@@ -963,6 +964,13 @@ parse_toplevel:
 				pg_yyget_extra(yyscanner)->parsetree =
 					list_make1(makeRawStmt((Node *) n, 0));
 			}
+			| MODE_PLPGSQL_LET LetStmt
+			{
+				LetStmt *n = (LetStmt *) $2;
+				n->plpgsql_mode = true;
+				pg_yyget_extra(yyscanner)->parsetree =
+					list_make1(makeRawStmt((Node *) n, 0));
+			}
 		;
 
 /*
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 118488c3f30..f9430a32919 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -62,6 +62,7 @@ raw_parser(const char *str, RawParseMode mode)
 			[RAW_PARSE_PLPGSQL_ASSIGN1] = MODE_PLPGSQL_ASSIGN1,
 			[RAW_PARSE_PLPGSQL_ASSIGN2] = MODE_PLPGSQL_ASSIGN2,
 			[RAW_PARSE_PLPGSQL_ASSIGN3] = MODE_PLPGSQL_ASSIGN3,
+			[RAW_PARSE_PLPGSQL_LET] = MODE_PLPGSQL_LET,
 		};
 
 		yyextra.have_lookahead = true;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 76d7656d7f7..0cc560d538f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2109,6 +2109,8 @@ typedef struct LetStmt
 	NodeTag		type;
 	List	   *target;			/* target variable */
 	Node	   *query;			/* source expression */
+	bool		plpgsql_mode;	/* true, when command will be executed
+								 * (parsed) by plpgsql runtime */
 	int			location;
 } LetStmt;
 
diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
index be184ec5066..efbc76f0ad4 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -33,6 +33,9 @@
  * RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement,
  * and return a one-element List containing a RawStmt node.  "n"
  * gives the number of dotted names comprising the target ColumnRef.
+ *
+ * RAW_PARSE_PLPGSQL_LET: parse a LET statement, and return a
+ * one-element List containing a RawStmt node.
  */
 typedef enum
 {
@@ -42,6 +45,7 @@ typedef enum
 	RAW_PARSE_PLPGSQL_ASSIGN1,
 	RAW_PARSE_PLPGSQL_ASSIGN2,
 	RAW_PARSE_PLPGSQL_ASSIGN3,
+	RAW_PARSE_PLPGSQL_LET,
 } RawParseMode;
 
 /* Values for the backslash_quote GUC */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 239b3250a95..80d512d30a0 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -24,6 +24,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
+#include "commands/session_variable.h"
 #include "executor/execExpr.h"
 #include "executor/spi.h"
 #include "executor/tstoreReceiver.h"
@@ -328,6 +329,8 @@ static int	exec_stmt_commit(PLpgSQL_execstate *estate,
 							 PLpgSQL_stmt_commit *stmt);
 static int	exec_stmt_rollback(PLpgSQL_execstate *estate,
 							   PLpgSQL_stmt_rollback *stmt);
+static int	exec_stmt_let(PLpgSQL_execstate *estate,
+						  PLpgSQL_stmt_let *let);
 
 static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
 								 PLpgSQL_function *func,
@@ -2120,6 +2123,10 @@ exec_stmts(PLpgSQL_execstate *estate, List *stmts)
 				rc = exec_stmt_rollback(estate, (PLpgSQL_stmt_rollback *) stmt);
 				break;
 
+			case PLPGSQL_STMT_LET:
+				rc = exec_stmt_let(estate, (PLpgSQL_stmt_let *) stmt);
+				break;
+
 			default:
 				/* point err_stmt to parent, since this one seems corrupt */
 				estate->err_stmt = save_estmt;
@@ -5001,6 +5008,54 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt)
 	return PLPGSQL_RC_OK;
 }
 
+/* ----------
+ * exec_stmt_let			Evaluate an expression and
+ *					put the result into a session variable.
+ * ----------
+ */
+static int
+exec_stmt_let(PLpgSQL_execstate *estate, PLpgSQL_stmt_let *stmt)
+{
+	bool		isNull;
+	Oid			valtype;
+	int32		valtypmod;
+	Datum		value;
+	Oid			varid;
+
+	List	   *plansources;
+	CachedPlanSource *plansource;
+
+	value = exec_eval_expr(estate,
+						   stmt->expr,
+						   &isNull,
+						   &valtype,
+						   &valtypmod);
+
+	/*
+	 * Oid of target session variable is stored in Query structure. It is
+	 * safer to read this value directly from the plan than to hold this value
+	 * in the plpgsql context, because it's not necessary to handle
+	 * invalidation of the cached value. Next operations are read only without
+	 * any allocations, so we can expect that retrieving varid from Query
+	 * should be fast.
+	 */
+	plansources = SPI_plan_get_plan_sources(stmt->expr->plan);
+	if (list_length(plansources) != 1)
+		elog(ERROR, "unexpected length of plansources of query for LET statement");
+
+	plansource = (CachedPlanSource *) linitial(plansources);
+	if (list_length(plansource->query_list) != 1)
+		elog(ERROR, "unexpected length of plansource of query for LET statement");
+
+	varid = linitial_node(Query, plansource->query_list)->resultVariable;
+	if (!OidIsValid(varid))
+		elog(ERROR, "oid of target session variable is not valid");
+
+	SetSessionVariableWithSecurityCheck(varid, value, isNull);
+
+	return PLPGSQL_RC_OK;
+}
+
 /* ----------
  * exec_assign_expr			Put an expression's result into a variable.
  * ----------
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index eeb7c4d7c04..1b39351db7f 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -288,6 +288,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
 			return "COMMIT";
 		case PLPGSQL_STMT_ROLLBACK:
 			return "ROLLBACK";
+		case PLPGSQL_STMT_LET:
+			return "LET";
 	}
 
 	return "unknown";
@@ -370,6 +372,7 @@ static void free_perform(PLpgSQL_stmt_perform *stmt);
 static void free_call(PLpgSQL_stmt_call *stmt);
 static void free_commit(PLpgSQL_stmt_commit *stmt);
 static void free_rollback(PLpgSQL_stmt_rollback *stmt);
+static void free_let(PLpgSQL_stmt_let *stmt);
 static void free_expr(PLpgSQL_expr *expr);

@@ -459,6 +462,9 @@ free_stmt(PLpgSQL_stmt *stmt)
 		case PLPGSQL_STMT_ROLLBACK:
 			free_rollback((PLpgSQL_stmt_rollback *) stmt);
 			break;
+		case PLPGSQL_STMT_LET:
+			free_let((PLpgSQL_stmt_let *) stmt);
+			break;
 		default:
 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
 			break;
@@ -713,6 +719,12 @@ free_getdiag(PLpgSQL_stmt_getdiag *stmt)
 {
 }
 
+static void
+free_let(PLpgSQL_stmt_let *stmt)
+{
+	free_expr(stmt->expr);
+}
+
 static void
 free_expr(PLpgSQL_expr *expr)
 {
@@ -815,6 +827,7 @@ static void dump_perform(PLpgSQL_stmt_perform *stmt);
 static void dump_call(PLpgSQL_stmt_call *stmt);
 static void dump_commit(PLpgSQL_stmt_commit *stmt);
 static void dump_rollback(PLpgSQL_stmt_rollback *stmt);
+static void dump_let(PLpgSQL_stmt_let *stmt);
 static void dump_expr(PLpgSQL_expr *expr);

@@ -914,6 +927,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
 		case PLPGSQL_STMT_ROLLBACK:
 			dump_rollback((PLpgSQL_stmt_rollback *) stmt);
 			break;
+		case PLPGSQL_STMT_LET:
+			dump_let((PLpgSQL_stmt_let *) stmt);
+			break;
 		default:
 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
 			break;
@@ -1590,6 +1606,14 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
 	printf("\n");
 }
 
+static void
+dump_let(PLpgSQL_stmt_let *stmt)
+{
+	dump_ind();
+	dump_expr(stmt->expr);
+	printf("\n");
+}
+
 static void
 dump_expr(PLpgSQL_expr *expr)
 {
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 97be9239e37..669741733fb 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -195,7 +195,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %type <stmt>	stmt_return stmt_raise stmt_assert stmt_execsql
 %type <stmt>	stmt_dynexecute stmt_for stmt_perform stmt_call stmt_getdiag
 %type <stmt>	stmt_open stmt_fetch stmt_move stmt_close stmt_null
-%type <stmt>	stmt_commit stmt_rollback
+%type <stmt>	stmt_commit stmt_rollback stmt_let
 %type <stmt>	stmt_case stmt_foreach_a
 
 %type <list>	proc_exceptions
@@ -302,6 +302,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_INTO
 %token <keyword>	K_IS
 %token <keyword>	K_LAST
+%token <keyword>	K_LET
 %token <keyword>	K_LOG
 %token <keyword>	K_LOOP
 %token <keyword>	K_MERGE
@@ -871,6 +872,8 @@ proc_stmt		: pl_block ';'
 						{ $$ = $1; }
 				| stmt_rollback
 						{ $$ = $1; }
+				| stmt_let
+						{ $$ = $1; }
 				;
 
 stmt_perform	: K_PERFORM
@@ -987,6 +990,29 @@ stmt_assign		: T_DATUM
 					}
 				;
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
+						pmode = RAW_PARSE_PLPGSQL_LET;
+
+						new = palloc0(sizeof(PLpgSQL_stmt_let));
+						new->cmd_type = PLPGSQL_STMT_LET;
+						new->lineno   = plpgsql_location_to_lineno(@1);
+						new->stmtid = ++plpgsql_curr_compile->nstatements;
+
+						/* Push back the head name to include it in the stmt */
+						plpgsql_push_back_token(K_LET);
+						new->expr = read_sql_construct(';', 0, 0, ";",
+													   pmode,
+													   false, true,
+													   NULL, NULL);
+
+						$$ = (PLpgSQL_stmt *)new;
+					}
+				;
+
 stmt_getdiag	: K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
 					{
 						PLpgSQL_stmt_getdiag *new;
diff --git a/src/pl/plpgsql/src/pl_reserved_kwlist.h b/src/pl/plpgsql/src/pl_reserved_kwlist.h
index d338e9f6374..be94aa751a4 100644
--- a/src/pl/plpgsql/src/pl_reserved_kwlist.h
+++ b/src/pl/plpgsql/src/pl_reserved_kwlist.h
@@ -40,6 +40,7 @@ PG_KEYWORD("from", K_FROM)
 PG_KEYWORD("if", K_IF)
 PG_KEYWORD("in", K_IN)
 PG_KEYWORD("into", K_INTO)
+PG_KEYWORD("let", K_LET)
 PG_KEYWORD("loop", K_LOOP)
 PG_KEYWORD("not", K_NOT)
 PG_KEYWORD("null", K_NULL)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 50c3b28472b..bb400629adb 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -128,6 +128,7 @@ typedef enum PLpgSQL_stmt_type
 	PLPGSQL_STMT_CALL,
 	PLPGSQL_STMT_COMMIT,
 	PLPGSQL_STMT_ROLLBACK,
+	PLPGSQL_STMT_LET,
 } PLpgSQL_stmt_type;
 
 /*
@@ -520,6 +521,17 @@ typedef struct PLpgSQL_stmt_assign
 	PLpgSQL_expr *expr;
 } PLpgSQL_stmt_assign;
 
+/*
+ * Let statement
+ */
+typedef struct PLpgSQL_stmt_let
+{
+	PLpgSQL_stmt_type cmd_type;
+	int			lineno;
+	unsigned int stmtid;
+	PLpgSQL_expr *expr;
+} PLpgSQL_stmt_let;
+
 /*
  * PERFORM statement
  */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 91a963f9570..d39dfde174a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1886,6 +1886,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
-- 
2.45.2

From 61d485e7f51b3f83bc99c559b50d2f856e2dc919 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 17:06:04 +0100
Subject: [PATCH 18/20] this patch changes error message "column doesn't exist"
 to message "column or variable doesn't exist"

The error message will be more correct. Today, missing PL/pgSQL variable can be reported. The change has
impact on lot of regress tests not directly related to session variables, and then it is distributed as separate patch
---
 src/backend/parser/parse_expr.c               |  2 +-
 src/backend/parser/parse_relation.c           | 24 +++++++++++---
 src/backend/parser/parse_target.c             |  8 +++--
 src/include/parser/parse_expr.h               |  2 ++
 src/pl/plpgsql/src/expected/plpgsql_array.out |  2 +-
 .../plpgsql/src/expected/plpgsql_record.out   |  4 +--
 .../src/expected/plpgsql_session_variable.out |  2 +-
 src/pl/tcl/expected/pltcl_queries.out         | 12 +++----
 .../isolation/expected/session-variable.out   |  4 +--
 src/test/regress/expected/alter_table.out     | 32 +++++++++----------
 src/test/regress/expected/copy2.out           |  2 +-
 src/test/regress/expected/errors.out          |  8 ++---
 src/test/regress/expected/join.out            | 12 +++----
 src/test/regress/expected/namespace.out       |  2 +-
 src/test/regress/expected/numerology.out      |  2 +-
 src/test/regress/expected/plpgsql.out         | 12 +++----
 src/test/regress/expected/psql.out            |  2 +-
 src/test/regress/expected/rules.out           |  2 +-
 .../regress/expected/session_variables.out    |  6 ++--
 src/test/regress/expected/transactions.out    |  4 +--
 src/test/regress/expected/union.out           |  2 +-
 21 files changed, 83 insertions(+), 63 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6007655630d..42acd124a01 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -521,7 +521,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
  * When we are in an expression where session variables cannot be used, we raise
  * the first form of error message.
  */
-static bool
+bool
 expr_kind_allows_session_variables(ParseExprKind p_expr_kind)
 {
 	bool		result = false;
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index bde5b1118c5..93a5898c6c3 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -27,6 +27,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_enr.h"
+#include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_type.h"
 #include "parser/parsetree.h"
@@ -3655,6 +3656,19 @@ errorMissingRTE(ParseState *pstate, RangeVar *relation)
 				 parser_errposition(pstate, relation->location)));
 }
 
+/*
+ * set message "column does not exist" or "column or variable does not exist"
+ * in dependency if expression context allows session variables.
+ */
+static int
+column_or_variable_does_not_exists(ParseState *pstate, const char *colname)
+{
+	if (expr_kind_allows_session_variables(pstate->p_expr_kind))
+		return errmsg("column or variable \"%s\" does not exist", colname);
+	else
+		return errmsg("column \"%s\" does not exist", colname);
+}
+
 /*
  * Generate a suitable error about a missing column.
  *
@@ -3689,7 +3703,7 @@ errorMissingColumn(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 relname ?
 					 errmsg("column %s.%s does not exist", relname, colname) :
-					 errmsg("column \"%s\" does not exist", colname),
+					 column_or_variable_does_not_exists(pstate, colname),
 					 errdetail("There are columns named \"%s\", but they are in tables that cannot be referenced from this part of the query.",
 							   colname),
 					 !relname ? errhint("Try using a table-qualified name.") : 0,
@@ -3699,7 +3713,7 @@ errorMissingColumn(ParseState *pstate,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 relname ?
 				 errmsg("column %s.%s does not exist", relname, colname) :
-				 errmsg("column \"%s\" does not exist", colname),
+				 column_or_variable_does_not_exists(pstate, colname),
 				 errdetail("There is a column named \"%s\" in table \"%s\", but it cannot be referenced from this part of the query.",
 						   colname, state->rexact1->eref->aliasname),
 				 rte_visible_if_lateral(pstate, state->rexact1) ?
@@ -3717,14 +3731,14 @@ errorMissingColumn(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 relname ?
 					 errmsg("column %s.%s does not exist", relname, colname) :
-					 errmsg("column \"%s\" does not exist", colname),
+					 column_or_variable_does_not_exists(pstate, colname),
 					 parser_errposition(pstate, location)));
 		/* Handle case where we have a single alternative spelling to offer */
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 relname ?
 				 errmsg("column %s.%s does not exist", relname, colname) :
-				 errmsg("column \"%s\" does not exist", colname),
+				 column_or_variable_does_not_exists(pstate, colname),
 				 errhint("Perhaps you meant to reference the column \"%s.%s\".",
 						 state->rfirst->eref->aliasname,
 						 strVal(list_nth(state->rfirst->eref->colnames,
@@ -3738,7 +3752,7 @@ errorMissingColumn(ParseState *pstate,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 relname ?
 				 errmsg("column %s.%s does not exist", relname, colname) :
-				 errmsg("column \"%s\" does not exist", colname),
+				 column_or_variable_does_not_exists(pstate, colname),
 				 errhint("Perhaps you meant to reference the column \"%s.%s\" or the column \"%s.%s\".",
 						 state->rfirst->eref->aliasname,
 						 strVal(list_nth(state->rfirst->eref->colnames,
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index ee6fcd0503a..e7dcc16b5dc 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -776,7 +776,9 @@ transformAssignmentIndirection(ParseState *pstate,
 			if (!typrelid)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("cannot assign to field \"%s\" of column \"%s\" because its type %s is not a composite type",
+						 errmsg(expr_kind_allows_session_variables(pstate->p_expr_kind) ?
+								 "cannot assign to field \"%s\" of column or variable \"%s\" because its type %s is not a composite type" :
+								 "cannot assign to field \"%s\" of column \"%s\" because its type %s is not a composite type",
 								strVal(n), targetName,
 								format_type_be(targetTypeId)),
 						 parser_errposition(pstate, location)));
@@ -785,7 +787,9 @@ transformAssignmentIndirection(ParseState *pstate,
 			if (attnum == InvalidAttrNumber)
 				ereport(ERROR,
 						(errcode(ERRCODE_UNDEFINED_COLUMN),
-						 errmsg("cannot assign to field \"%s\" of column \"%s\" because there is no such column in data type %s",
+						 errmsg(expr_kind_allows_session_variables(pstate->p_expr_kind) ?
+								 "cannot assign to field \"%s\" of column or variable \"%s\" because there is no such column in data type %s" :
+								 "cannot assign to field \"%s\" of column \"%s\" because there is no such column in data type %s",
 								strVal(n), targetName,
 								format_type_be(targetTypeId)),
 						 parser_errposition(pstate, location)));
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index 0d64b300f8a..e5aebf99b4a 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -23,4 +23,6 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern bool expr_kind_allows_session_variables(ParseExprKind p_expr_kind);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index ad60e0e8be3..c1ab9fd7ed9 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -41,7 +41,7 @@ NOTICE:  a = {"(,11)"}, a[1].i = 11
 -- perhaps this ought to work, but for now it doesn't:
 do $$ declare a complex[];
 begin a[1:2].i := array[11,12]; raise notice 'a = %', a; end$$;
-ERROR:  cannot assign to field "i" of column "a" because its type complex[] is not a composite type
+ERROR:  cannot assign to field "i" of column or variable "a" because its type complex[] is not a composite type
 LINE 1: a[1:2].i := array[11,12]
         ^
 QUERY:  a[1:2].i := array[11,12]
diff --git a/src/pl/plpgsql/src/expected/plpgsql_record.out b/src/pl/plpgsql/src/expected/plpgsql_record.out
index a9b5b778efc..07abc2e84d4 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_record.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_record.out
@@ -135,7 +135,7 @@ ERROR:  record "c" has no field "x"
 CONTEXT:  PL/pgSQL assignment "c.x.q1 = 1"
 PL/pgSQL function inline_code_block line 1 at assignment
 do $$ declare c nested_int8s; begin c.c2.x = 1; end $$;
-ERROR:  cannot assign to field "x" of column "c" because there is no such column in data type two_int8s
+ERROR:  cannot assign to field "x" of column or variable "c" because there is no such column in data type two_int8s
 LINE 1: c.c2.x = 1
         ^
 QUERY:  c.c2.x = 1
@@ -157,7 +157,7 @@ ERROR:  record "c" has no field "x"
 CONTEXT:  PL/pgSQL assignment "b.c.x.q1 = 1"
 PL/pgSQL function inline_code_block line 1 at assignment
 do $$ <<b>> declare c nested_int8s; begin b.c.c2.x = 1; end $$;
-ERROR:  cannot assign to field "x" of column "b" because there is no such column in data type two_int8s
+ERROR:  cannot assign to field "x" of column or variable "b" because there is no such column in data type two_int8s
 LINE 1: b.c.c2.x = 1
         ^
 QUERY:  b.c.c2.x = 1
diff --git a/src/pl/plpgsql/src/expected/plpgsql_session_variable.out b/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
index 789c3123b0c..f006db4faa8 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
@@ -232,7 +232,7 @@ SET ROLE TO DEFAULT;
 SET ROLE TO regress_var_exec_role;
 -- should to fail, but not crash
 SELECT var_read_func();
-ERROR:  column "plpgsql_sv_var1" does not exist
+ERROR:  column or variable "plpgsql_sv_var1" does not exist
 LINE 1: plpgsql_sv_var1
         ^
 QUERY:  plpgsql_sv_var1
diff --git a/src/pl/tcl/expected/pltcl_queries.out b/src/pl/tcl/expected/pltcl_queries.out
index 35cc6e62aad..497cd38189b 100644
--- a/src/pl/tcl/expected/pltcl_queries.out
+++ b/src/pl/tcl/expected/pltcl_queries.out
@@ -450,12 +450,12 @@ CONTEXT:  while executing
 "__PLTcl_proc_tcl_eval_text spi_prepare\ a\ \"b\ \{\""
 in PL/Tcl function tcl_eval(text)
 select tcl_error_handling_test($tcl$spi_prepare "select moo" []$tcl$);
-       tcl_error_handling_test        
---------------------------------------
- SQLSTATE: 42703                     +
- condition: undefined_column         +
- cursor_position: 8                  +
- message: column "moo" does not exist+
+             tcl_error_handling_test              
+--------------------------------------------------
+ SQLSTATE: 42703                                 +
+ condition: undefined_column                     +
+ cursor_position: 8                              +
+ message: column or variable "moo" does not exist+
  statement: select moo
 (1 row)
 
diff --git a/src/test/isolation/expected/session-variable.out b/src/test/isolation/expected/session-variable.out
index 19a2e5a8f64..1c4149f6ef1 100644
--- a/src/test/isolation/expected/session-variable.out
+++ b/src/test/isolation/expected/session-variable.out
@@ -10,7 +10,7 @@ test
 
 step drop: DROP VARIABLE myvar;
 step val: SELECT myvar;
-ERROR:  column "myvar" does not exist
+ERROR:  column or variable "myvar" does not exist
 
 starting permutation: let val s1 drop val sr1
 step let: LET myvar = 'test';
@@ -23,7 +23,7 @@ test
 step s1: BEGIN;
 step drop: DROP VARIABLE myvar;
 step val: SELECT myvar;
-ERROR:  column "myvar" does not exist
+ERROR:  column or variable "myvar" does not exist
 step sr1: ROLLBACK;
 
 starting permutation: let val dbg drop create dbg val
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 79cf82b5aed..eb310d4e192 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1309,19 +1309,19 @@ select * from atacc1;
 (1 row)
 
 select * from atacc1 order by a;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: select * from atacc1 order by a;
                                       ^
 select * from atacc1 order by "........pg.dropped.1........";
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: select * from atacc1 order by "........pg.dropped.1........"...
                                       ^
 select * from atacc1 group by a;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: select * from atacc1 group by a;
                                       ^
 select * from atacc1 group by "........pg.dropped.1........";
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: select * from atacc1 group by "........pg.dropped.1........"...
                                       ^
 select atacc1.* from atacc1;
@@ -1331,7 +1331,7 @@ select atacc1.* from atacc1;
 (1 row)
 
 select a from atacc1;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: select a from atacc1;
                ^
 select atacc1.a from atacc1;
@@ -1345,15 +1345,15 @@ select b,c,d from atacc1;
 (1 row)
 
 select a,b,c,d from atacc1;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: select a,b,c,d from atacc1;
                ^
 select * from atacc1 where a = 1;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: select * from atacc1 where a = 1;
                                    ^
 select "........pg.dropped.1........" from atacc1;
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: select "........pg.dropped.1........" from atacc1;
                ^
 select atacc1."........pg.dropped.1........" from atacc1;
@@ -1361,11 +1361,11 @@ ERROR:  column atacc1.........pg.dropped.1........ does not exist
 LINE 1: select atacc1."........pg.dropped.1........" from atacc1;
                ^
 select "........pg.dropped.1........",b,c,d from atacc1;
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: select "........pg.dropped.1........",b,c,d from atacc1;
                ^
 select * from atacc1 where "........pg.dropped.1........" = 1;
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: select * from atacc1 where "........pg.dropped.1........" = ...
                                    ^
 -- UPDATEs
@@ -1374,7 +1374,7 @@ ERROR:  column "a" of relation "atacc1" does not exist
 LINE 1: update atacc1 set a = 3;
                           ^
 update atacc1 set b = 2 where a = 3;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: update atacc1 set b = 2 where a = 3;
                                       ^
 update atacc1 set "........pg.dropped.1........" = 3;
@@ -1382,7 +1382,7 @@ ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exis
 LINE 1: update atacc1 set "........pg.dropped.1........" = 3;
                           ^
 update atacc1 set b = 2 where "........pg.dropped.1........" = 3;
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: update atacc1 set b = 2 where "........pg.dropped.1........"...
                                       ^
 -- INSERTs
@@ -1430,11 +1430,11 @@ LINE 1: insert into atacc1 ("........pg.dropped.1........",b,c,d) va...
                             ^
 -- DELETEs
 delete from atacc1 where a = 3;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: delete from atacc1 where a = 3;
                                  ^
 delete from atacc1 where "........pg.dropped.1........" = 3;
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: delete from atacc1 where "........pg.dropped.1........" = 3;
                                  ^
 delete from atacc1;
@@ -1720,7 +1720,7 @@ select f1 from c1;
 
 alter table c1 drop column f1;
 select f1 from c1;
-ERROR:  column "f1" does not exist
+ERROR:  column or variable "f1" does not exist
 LINE 1: select f1 from c1;
                ^
 HINT:  Perhaps you meant to reference the column "c1.f2".
@@ -1734,7 +1734,7 @@ ERROR:  cannot drop inherited column "f1"
 alter table p1 drop column f1;
 -- c1.f1 is dropped now, since there is no local definition for it
 select f1 from c1;
-ERROR:  column "f1" does not exist
+ERROR:  column or variable "f1" does not exist
 LINE 1: select f1 from c1;
                ^
 HINT:  Perhaps you meant to reference the column "c1.f2".
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index 931542f2689..b48970e56ac 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -144,7 +144,7 @@ LINE 1: COPY x TO stdout WHERE a = 1;
 COPY x from stdin WHERE a = 50004;
 COPY x from stdin WHERE a > 60003;
 COPY x from stdin WHERE f > 60003;
-ERROR:  column "f" does not exist
+ERROR:  column or variable "f" does not exist
 LINE 1: COPY x from stdin WHERE f > 60003;
                                 ^
 COPY x from stdin WHERE a = max(x.b);
diff --git a/src/test/regress/expected/errors.out b/src/test/regress/expected/errors.out
index 8c527474daf..e53ae451dfc 100644
--- a/src/test/regress/expected/errors.out
+++ b/src/test/regress/expected/errors.out
@@ -27,7 +27,7 @@ LINE 1: select * from nonesuch;
                       ^
 -- bad name in target list
 select nonesuch from pg_database;
-ERROR:  column "nonesuch" does not exist
+ERROR:  column or variable "nonesuch" does not exist
 LINE 1: select nonesuch from pg_database;
                ^
 -- empty distinct list isn't OK
@@ -37,17 +37,17 @@ LINE 1: select distinct from pg_database;
                         ^
 -- bad attribute name on lhs of operator
 select * from pg_database where nonesuch = pg_database.datname;
-ERROR:  column "nonesuch" does not exist
+ERROR:  column or variable "nonesuch" does not exist
 LINE 1: select * from pg_database where nonesuch = pg_database.datna...
                                         ^
 -- bad attribute name on rhs of operator
 select * from pg_database where pg_database.datname = nonesuch;
-ERROR:  column "nonesuch" does not exist
+ERROR:  column or variable "nonesuch" does not exist
 LINE 1: ...ect * from pg_database where pg_database.datname = nonesuch;
                                                               ^
 -- bad attribute name in select distinct on
 select distinct on (foobar) * from pg_database;
-ERROR:  column "foobar" does not exist
+ERROR:  column or variable "foobar" does not exist
 LINE 1: select distinct on (foobar) * from pg_database;
                             ^
 -- grouping with FOR UPDATE
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 53f70d72ed6..661bb1a30de 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -6230,13 +6230,13 @@ LINE 1: select t2.uunique1 from
 HINT:  Perhaps you meant to reference the column "t2.unique1".
 select uunique1 from
   tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, suggest both at once
-ERROR:  column "uunique1" does not exist
+ERROR:  column or variable "uunique1" does not exist
 LINE 1: select uunique1 from
                ^
 HINT:  Perhaps you meant to reference the column "t1.unique1" or the column "t2.unique1".
 select ctid from
   tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, need qualification
-ERROR:  column "ctid" does not exist
+ERROR:  column or variable "ctid" does not exist
 LINE 1: select ctid from
                ^
 DETAIL:  There are columns named "ctid", but they are in tables that cannot be referenced from this part of the query.
@@ -7333,7 +7333,7 @@ lateral (select * from int8_tbl t1,
 
 -- test some error cases where LATERAL should have been used but wasn't
 select f1,g from int4_tbl a, (select f1 as g) ss;
-ERROR:  column "f1" does not exist
+ERROR:  column or variable "f1" does not exist
 LINE 1: select f1,g from int4_tbl a, (select f1 as g) ss;
                                              ^
 DETAIL:  There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
@@ -7345,7 +7345,7 @@ LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
 DETAIL:  There is an entry for table "a", but it cannot be referenced from this part of the query.
 HINT:  To reference that table, you must mark this subquery with LATERAL.
 select f1,g from int4_tbl a cross join (select f1 as g) ss;
-ERROR:  column "f1" does not exist
+ERROR:  column or variable "f1" does not exist
 LINE 1: select f1,g from int4_tbl a cross join (select f1 as g) ss;
                                                        ^
 DETAIL:  There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
@@ -7382,7 +7382,7 @@ LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
 create temp table xx1 as select f1 as x1, -f1 as x2 from int4_tbl;
 -- error, can't do this:
 update xx1 set x2 = f1 from (select * from int4_tbl where f1 = x1) ss;
-ERROR:  column "x1" does not exist
+ERROR:  column or variable "x1" does not exist
 LINE 1: ... set x2 = f1 from (select * from int4_tbl where f1 = x1) ss;
                                                                 ^
 DETAIL:  There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query.
@@ -7402,7 +7402,7 @@ update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1)
 ERROR:  table name "xx1" specified more than once
 -- also errors:
 delete from xx1 using (select * from int4_tbl where f1 = x1) ss;
-ERROR:  column "x1" does not exist
+ERROR:  column or variable "x1" does not exist
 LINE 1: ...te from xx1 using (select * from int4_tbl where f1 = x1) ss;
                                                                 ^
 DETAIL:  There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query.
diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out
index dbbda72d395..d98793f92a7 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -23,7 +23,7 @@ BEGIN;
 SET search_path to public, test_ns_schema_1;
 CREATE SCHEMA test_ns_schema_2
        CREATE VIEW abc_view AS SELECT c FROM abc;
-ERROR:  column "c" does not exist
+ERROR:  column or variable "c" does not exist
 LINE 2:        CREATE VIEW abc_view AS SELECT c FROM abc;
                                               ^
 COMMIT;
diff --git a/src/test/regress/expected/numerology.out b/src/test/regress/expected/numerology.out
index 717a237df98..385c4b1b4cc 100644
--- a/src/test/regress/expected/numerology.out
+++ b/src/test/regress/expected/numerology.out
@@ -314,7 +314,7 @@ NOTICE:  i = 1002
 NOTICE:  i = 1003
 -- error cases
 SELECT _100;
-ERROR:  column "_100" does not exist
+ERROR:  column or variable "_100" does not exist
 LINE 1: SELECT _100;
                ^
 SELECT 100_;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 074af8f33a8..0a7a9feb6e2 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -2598,7 +2598,7 @@ end; $$ language plpgsql;
 -- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION
 -- blocks
 select excpt_test1();
-ERROR:  column "sqlstate" does not exist
+ERROR:  column or variable "sqlstate" does not exist
 LINE 1: sqlstate
         ^
 QUERY:  sqlstate
@@ -2613,7 +2613,7 @@ begin
 end; $$ language plpgsql;
 -- should fail
 select excpt_test2();
-ERROR:  column "sqlstate" does not exist
+ERROR:  column or variable "sqlstate" does not exist
 LINE 1: sqlstate
         ^
 QUERY:  sqlstate
@@ -4675,7 +4675,7 @@ BEGIN
         RAISE NOTICE '%, %', r.roomno, r.comment;
     END LOOP;
 END$$;
-ERROR:  column "foo" does not exist
+ERROR:  column or variable "foo" does not exist
 LINE 1: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomn...
                                         ^
 QUERY:  SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno
@@ -4717,7 +4717,7 @@ begin
   raise notice 'x = %', x;
 end;
 $$;
-ERROR:  column "x" does not exist
+ERROR:  column or variable "x" does not exist
 LINE 1: x + 1
         ^
 QUERY:  x + 1
@@ -4729,7 +4729,7 @@ begin
   raise notice 'x = %, y = %', x, y;
 end;
 $$;
-ERROR:  column "x" does not exist
+ERROR:  column or variable "x" does not exist
 LINE 1: x + 1
         ^
 QUERY:  x + 1
@@ -5769,7 +5769,7 @@ ALTER TABLE alter_table_under_transition_tables
   DROP column name;
 UPDATE alter_table_under_transition_tables
   SET id = id;
-ERROR:  column "name" does not exist
+ERROR:  column or variable "name" does not exist
 LINE 1: (SELECT string_agg(id || '=' || name, ',') FROM d)
                                         ^
 QUERY:  (SELECT string_agg(id || '=' || name, ',') FROM d)
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index f7082639605..451fdde31ce 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -120,7 +120,7 @@ SELECT $1, $2 \bind 'foo' 'bar' \g
 -- errors
 -- parse error
 SELECT foo \bind \g
-ERROR:  column "foo" does not exist
+ERROR:  column or variable "foo" does not exist
 LINE 1: SELECT foo 
                ^
 -- tcop error
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 4c789279e5e..e172fa1404a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1191,7 +1191,7 @@ drop rule rules_foorule on rules_foo;
 -- this should fail because f1 is not exposed for unqualified reference:
 create rule rules_foorule as on insert to rules_foo where f1 < 100
 do instead insert into rules_foo2 values (f1);
-ERROR:  column "f1" does not exist
+ERROR:  column or variable "f1" does not exist
 LINE 2: do instead insert into rules_foo2 values (f1);
                                                   ^
 DETAIL:  There are columns named "f1", but they are in tables that cannot be referenced from this part of the query.
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index a75bc69ee8b..415cf2c82ba 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -296,7 +296,7 @@ ERROR:  session variable "var1" doesn't exist
 LINE 1: LET var1 = pi();
             ^
 SELECT var1;
-ERROR:  column "var1" does not exist
+ERROR:  column or variable "var1" does not exist
 LINE 1: SELECT var1;
                ^
 -- should be ok
@@ -507,7 +507,7 @@ SELECT v1;
 
 -- should fail, attribute doesn't exist
 LET v1.x = 10;
-ERROR:  cannot assign to field "x" of column "v1" because there is no such column in data type t1
+ERROR:  cannot assign to field "x" of column or variable "v1" because there is no such column in data type t1
 LINE 1: LET v1.x = 10;
             ^
 -- should fail, don't allow multi column query
@@ -1054,7 +1054,7 @@ BEGIN;
 DROP VARIABLE var1;
 -- should fail
 SELECT var1;
-ERROR:  column "var1" does not exist
+ERROR:  column or variable "var1" does not exist
 LINE 1: SELECT var1;
                ^
 ROLLBACK;
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 7f5757e89c4..b05b16d94bf 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -256,7 +256,7 @@ SELECT * FROM trans_barbaz;	-- should have 1
 BEGIN;
 	SAVEPOINT one;
 		SELECT trans_foo;
-ERROR:  column "trans_foo" does not exist
+ERROR:  column or variable "trans_foo" does not exist
 LINE 1: SELECT trans_foo;
                ^
 	ROLLBACK TO SAVEPOINT one;
@@ -305,7 +305,7 @@ BEGIN;
 	SAVEPOINT one;
 		INSERT INTO savepoints VALUES (5);
 		SELECT trans_foo;
-ERROR:  column "trans_foo" does not exist
+ERROR:  column or variable "trans_foo" does not exist
 LINE 1: SELECT trans_foo;
                ^
 COMMIT;
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 0fd0e1c38b3..f86987dbdc9 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -921,7 +921,7 @@ ORDER BY q2,q1;
 
 -- This should fail, because q2 isn't a name of an EXCEPT output column
 SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
-ERROR:  column "q2" does not exist
+ERROR:  column or variable "q2" does not exist
 LINE 1: ... int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1...
                                                              ^
 DETAIL:  There is a column named "q2" in table "*SELECT* 2", but it cannot be referenced from this part of the query.
-- 
2.45.2

From eeb6fde3a2c3931ec34edfc7681ce3ff99e4b35f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

This commit implements transactional variables. The content of transactional
session variables is sensitive to transactions and subtransactions. Any
transactional variable holds a history of values necessary for revert. This history
is cleaned (purged) when a) we read the variable, b) when the transaction is finished.

We don't try to purge, when the last modification of a variable is from
current subtransaction, or when purge was processed in current subtransaction.

This patch is based on my work from Feb 2020 and it is related to discussion
about features related to session variables. Now, when the all features are
separated to isolated patches I can revitalize this patch, because it doesn't
increase complexity of basic patches.

Unlike other patches, this patch is without any review at this moment (Feb 2024),
but the feature should be fully functional for people who are interested about
this feature (for testing).
---
 doc/src/sgml/catalogs.sgml                    |  11 +
 doc/src/sgml/ref/create_variable.sgml         |  10 +-
 src/backend/catalog/pg_variable.c             |   4 +
 src/backend/commands/session_variable.c       | 301 +++++++++++++++++-
 src/backend/parser/gram.y                     |  50 +--
 src/bin/pg_dump/pg_dump.c                     |  10 +-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_dump/t/002_pg_dump.pl              |  36 +++
 src/bin/psql/describe.c                       |   4 +-
 src/bin/psql/tab-complete.c                   |   5 +
 src/include/catalog/pg_variable.h             |   3 +
 src/include/nodes/parsenodes.h                |   1 +
 src/include/parser/kwlist.h                   |   1 +
 src/test/regress/expected/psql.out            |  36 +--
 .../regress/expected/session_variables.out    | 110 ++++++-
 src/test/regress/sql/session_variables.sql    |  58 ++++
 16 files changed, 591 insertions(+), 50 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 96cc88e6cf6..31fac4261df 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9851,6 +9851,17 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry><structfield>varistransact</structfield></entry>
+      <entry><type>boolean</type></entry>
+      <entry></entry>
+      <entry>
+       True, when the variable is "transactional". In case of transaction
+       rollback, transactional variables are reset to their content at the
+       transaction start. The default value is false.
+      </entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>vareoxaction</structfield> <type>char</type>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index cb20d1dc68c..c9df71709d9 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ { TEMPORARY | TEMP } ] [ IMMUTABLE ] VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
+CREATE [ { TEMPORARY | TEMP } ] [ { TRANSACTIONAL | TRANSACTION } ] [ IMMUTABLE ] VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
     [ NOT NULL ] [ DEFAULT <replaceable class="parameter">default_expr</replaceable> ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
 </synopsis>
  </refsynopsisdiv>
@@ -48,6 +48,14 @@ CREATE [ { TEMPORARY | TEMP } ] [ IMMUTABLE ] VARIABLE [ IF NOT EXISTS ] <replac
    same as regular variables in procedural languages.
   </para>
 
+  <para>
+   When a schema variable is created with a
+   <command>CREATE TRANSACTIONAL VARIABLE</command> command, the variables
+   content changes are transactional: in case of rollback, they are reset to
+   their value at the beginning of the transaction or the latest subtransaction.
+   The variable content is only hold in memory, and thus is not persistent.
+  </para>
+
   <para>
    Session variables are retrieved by the <command>SELECT</command>
    command.  Their value is set with the <command>LET</command> command.
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
index a89c0eccd6a..b99c6dc1e48 100644
--- a/src/backend/catalog/pg_variable.c
+++ b/src/backend/catalog/pg_variable.c
@@ -42,6 +42,7 @@ static ObjectAddress create_variable(const char *varName,
 									 bool if_not_exists,
 									 bool not_null,
 									 bool is_immutable,
+									 bool is_transact,
 									 Node *varDefexpr,
 									 VariableXactEndAction varXactEndAction);
 
@@ -59,6 +60,7 @@ create_variable(const char *varName,
 				bool if_not_exists,
 				bool not_null,
 				bool is_immutable,
+				bool is_transact,
 				Node *varDefexpr,
 				VariableXactEndAction varXactEndAction)
 {
@@ -123,6 +125,7 @@ create_variable(const char *varName,
 	values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation);
 	values[Anum_pg_variable_varnotnull - 1] = BoolGetDatum(not_null);
 	values[Anum_pg_variable_varisimmutable - 1] = BoolGetDatum(is_immutable);
+	values[Anum_pg_variable_varistransact - 1] = BoolGetDatum(is_transact);
 	values[Anum_pg_variable_varxactendaction - 1] = CharGetDatum(varXactEndAction);
 
 	if (varDefexpr)
@@ -266,6 +269,7 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 							   stmt->if_not_exists,
 							   stmt->not_null,
 							   stmt->is_immutable,
+							   stmt->is_transact,
 							   cooked_default,
 							   stmt->XactEndAction);
 
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 8c997ff2b51..9acfcbacc0b 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -47,6 +47,19 @@ typedef struct SVariableXActDropItem
 	SubTransactionId deleting_subid;
 } SVariableXActDropItem;
 
+/*
+ * Used for transactional variables. Holds prev version.
+ */
+typedef struct PrevValue
+{
+	Datum		value;
+	bool		isnull;
+
+	SubTransactionId modify_subid;
+
+	struct PrevValue *prev_value;
+} PrevValue;
+
 /*
  * Values of session variables are stored in the backend local memory
  * inside sessionvars hash table in binary format inside a dedicated memory
@@ -75,6 +88,25 @@ typedef struct SVariableData
 	bool		isnull;
 	Datum		value;
 
+	/*
+	 * We don't need stack versions modified in same subtransaction.
+	 * Used by transactional variables only. The value of transactional
+	 * variable can be returned immediately when modify_subid is same
+	 * like current subid.
+	 */
+	SubTransactionId modify_subid;
+
+	/*
+	 * When the modify_subid is different than current subid, then
+	 * we need to recheck versions and throw versions related to
+	 * reverted transactions. When purge_subid is same like current subid
+	 * we can return the value of transaction variable without this
+	 * recheck.
+	 */
+	SubTransactionId purge_subid;
+
+	PrevValue  *prev_value;
+
 	Oid			typid;
 	int16		typlen;
 	bool		typbyval;
@@ -94,6 +126,7 @@ typedef struct SVariableData
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;
 
@@ -133,6 +166,9 @@ static bool needs_validation = false;
  */
 static bool has_session_variables_with_reset_at_eox = false;
 
+/* true, when transactional variables was modified */
+static bool has_modified_transactional_variables = false;
+
 /*
  * The content of session variables is not removed immediately. When it
  * is possible we do this at the transaction end. But when the transaction failed,
@@ -148,6 +184,7 @@ static List *xact_drop_items = NIL;
 
 static void register_session_variable_xact_drop(Oid varid);
 static void unregister_session_variable_xact_drop(Oid varid);
+static bool purge_session_variable(SVariable svar);
 
 /*
  * Callback function for session variable invalidation.
@@ -303,8 +340,25 @@ unregister_session_variable_xact_drop(Oid varid)
  * Release stored value, free memory
  */
 static void
-free_session_variable_value(SVariable svar)
+free_session_variable_value(SVariable svar, bool deep_free)
 {
+	if (deep_free)
+	{
+		PrevValue *prev_value = svar->prev_value;
+		PrevValue *next_value;
+
+		while (prev_value)
+		{
+			if (!svar->typbyval)
+				pfree(DatumGetPointer(prev_value->value));
+
+			next_value = prev_value->prev_value;
+			pfree(prev_value);
+
+			prev_value = next_value;
+		}
+	}
+
 	/* Clean current value */
 	if (!svar->isnull)
 	{
@@ -403,7 +457,7 @@ remove_invalid_session_variables(bool atEOX)
 			{
 				Oid			varid = svar->varid;
 
-				free_session_variable_value(svar);
+				free_session_variable_value(svar, true);
 				hash_search(sessionvars, &varid, HASH_REMOVE, NULL);
 				svar = NULL;
 			}
@@ -439,6 +493,94 @@ remove_session_variables_with_reset_at_eox(void)
 	has_session_variables_with_reset_at_eox = false;
 }
 
+/*
+ * remove prev values at eox
+ */
+static void
+remove_prev_values_at_eox(bool isCommit)
+{
+	HASH_SEQ_STATUS status;
+	SVariable	svar;
+
+	if (!sessionvars)
+		return;
+
+	/* leave quckly, when there are not that variables */
+	if (!has_modified_transactional_variables)
+		return;
+
+	hash_seq_init(&status, sessionvars);
+	while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+	{
+		if (svar->is_transact && svar->modify_subid != InvalidSubTransactionId)
+		{
+			if (isCommit)
+			{
+				if (purge_session_variable(svar))
+				{
+					PrevValue *prev_value = svar->prev_value;
+
+					while (prev_value)
+					{
+						PrevValue *current_pv = prev_value;
+
+						if (!svar->typbyval && !current_pv->isnull)
+							pfree(DatumGetPointer(current_pv->value));
+
+						prev_value = current_pv->prev_value;
+						pfree(current_pv);
+					}
+				}
+				else
+				{
+					hash_search(sessionvars, &svar->varid, HASH_REMOVE, NULL);
+					svar = NULL;
+				}
+			}
+			else
+			{
+				PrevValue *prev_value = svar->prev_value;
+
+				while (prev_value)
+				{
+					PrevValue *current_pv = prev_value;
+
+					if (current_pv->modify_subid == InvalidSubTransactionId)
+						break;
+
+					if (!svar->typbyval && !current_pv->isnull)
+						pfree(DatumGetPointer(current_pv->value));
+
+					prev_value = current_pv->prev_value;
+					pfree(current_pv);
+				}
+
+				if (prev_value)
+				{
+					svar->value = prev_value->value;
+					svar->isnull = prev_value->isnull;
+
+					pfree(prev_value);
+				}
+				else
+				{
+					hash_search(sessionvars, &svar->varid, HASH_REMOVE, NULL);
+					svar = NULL;
+				}
+			}
+
+			/* When svar is still valid (not removed from sessionvars */
+			if (svar)
+			{
+				svar->modify_subid = InvalidSubTransactionId;
+				svar->prev_value = NULL;
+			}
+		}
+	}
+
+	has_modified_transactional_variables = false;
+}
+
 /*
   * Perform ON COMMIT DROP for temporary session variables,
   * and remove all dropped variables from memory.
@@ -486,6 +628,8 @@ AtPreEOXact_SessionVariables(bool isCommit)
 		remove_invalid_session_variables(true);
 	}
 
+	remove_prev_values_at_eox(isCommit);
+
 	/*
 	 * We have to clean xact_drop_items. All related variables are dropped
 	 * now, or lost inside aborted transaction.
@@ -631,6 +775,7 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 
 	svar->not_null = varform->varnotnull;
 	svar->is_immutable = varform->varisimmutable;
+	svar->is_transact = varform->varistransact;
 
 	svar->is_domain = (get_typtype(varform->vartype) == TYPTYPE_DOMAIN);
 	svar->domain_check_extra = NULL;
@@ -656,6 +801,17 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	svar->isnull = true;
 	svar->value = (Datum) 0;
 
+	if (svar->is_transact)
+	{
+		svar->modify_subid = GetCurrentSubTransactionId();
+		has_modified_transactional_variables = true;
+	}
+	else
+		svar->modify_subid = InvalidSubTransactionId;
+
+	svar->purge_subid = InvalidSubTransactionId;
+	svar->prev_value = NULL;
+
 	svar->is_valid = true;
 
 	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
@@ -689,6 +845,69 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	ReleaseSysCache(tup);
 }
 
+/*
+ * Try to remove all previous versions related to reverted transactions.
+ * Returns true, when valid version was found.
+ */
+static bool
+purge_session_variable(SVariable svar)
+{
+	SubTransactionId current_subid;
+	PrevValue	   *prev_value;
+	bool			found = true;
+
+	Assert(svar->is_transact);
+
+	if (svar->modify_subid == InvalidSubTransactionId)
+		return true;
+
+	current_subid = GetCurrentSubTransactionId();
+
+	if (svar->modify_subid == current_subid)
+		return true;
+
+	if (svar->purge_subid == current_subid)
+		return true;
+
+	if (SubTransactionIsActive(svar->modify_subid))
+	{
+		svar->purge_subid = current_subid;
+		return true;
+	}
+
+	prev_value = svar->prev_value;
+
+	while (prev_value)
+	{
+		PrevValue *current_pv = prev_value;
+
+		if (current_pv->modify_subid == InvalidSubTransactionId ||
+			SubTransactionIsActive(current_pv->modify_subid))
+		{
+			svar->value = current_pv->value;
+			svar->isnull = current_pv->isnull;
+			svar->modify_subid = current_pv->modify_subid;
+
+			prev_value = current_pv->prev_value;
+			pfree(current_pv);
+
+			found = true;
+			break;
+		}
+
+		if (!svar->typbyval && !current_pv->isnull)
+			pfree(DatumGetPointer(current_pv->value));
+
+		prev_value = current_pv->prev_value;
+		pfree(current_pv);
+	}
+
+	svar->prev_value = prev_value;
+	svar->purge_subid = current_subid;
+
+	return found;
+}
+
 /*
  * Assign some content to the session variable. It's copied to
  * SVariableMemoryContext if necessary.
@@ -701,6 +920,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	Datum		newval;
 	SVariableData locsvar,
 			   *_svar;
+	SubTransactionId current_subid = GetCurrentSubTransactionId();
+	SubTransactionId prev_purge_subid = InvalidSubTransactionId;
+	bool		save_prev_value;
+	PrevValue  *prev_value;
 
 	Assert(svar);
 	Assert(!isnull || value == (Datum) 0);
@@ -736,6 +959,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	else
 		_svar = svar;
 
+	if (_svar->is_transact && _svar->create_lsn == svar->create_lsn)
+	{
+		Assert(svar->typid == _svar->typid);
+		Assert(svar->typbyval == _svar->typbyval);
+		Assert(svar->typlen == _svar->typlen);
+
+		save_prev_value = svar->modify_subid != current_subid;
+		prev_value = svar->prev_value;
+	}
+	else
+	{
+		save_prev_value = false;
+		prev_value = NULL;
+	}
+
 	if (!isnull)
 	{
 		MemoryContext oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
@@ -747,7 +985,37 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	else
 		newval = value;
 
-	free_session_variable_value(svar);
+	if (save_prev_value)
+	{
+		volatile PrevValue *new_prev_value;
+
+		PG_TRY();
+		{
+			new_prev_value = MemoryContextAlloc(SVariableMemoryContext,
+												sizeof(PrevValue));
+		}
+		PG_CATCH();
+		{
+			/* release mem from persistent content */
+			if (newval != value)
+				pfree(DatumGetPointer(newval));
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+
+		new_prev_value->value = svar->value;
+		new_prev_value->isnull = svar->isnull;
+		new_prev_value->modify_subid = svar->modify_subid;
+		new_prev_value->prev_value = prev_value;
+
+		prev_value = (PrevValue *) new_prev_value;
+		prev_purge_subid = svar->purge_subid;
+
+		has_modified_transactional_variables = true;
+
+	}
+	else
+		free_session_variable_value(svar, prev_value == NULL);
 
 	/* We can overwrite old variable now. No error expected */
 	if (svar != _svar)
@@ -755,6 +1023,9 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	svar->value = newval;
 	svar->isnull = isnull;
+	svar->modify_subid = current_subid;
+	svar->purge_subid = prev_purge_subid;
+	svar->prev_value = prev_value;
 
 	/* don't allow more changes of value when variable is IMMUTABLE */
 	if (svar->is_immutable)
@@ -870,6 +1141,8 @@ get_session_variable(Oid varid)
 	else
 		svar->is_valid = false;
 
+reinit:
+
 	/*
 	 * Force setup for not yet initialized variables or variables that cannot
 	 * be validated.
@@ -902,6 +1175,28 @@ get_session_variable(Oid varid)
 			 varid);
 	}
 
+	/*
+	 * Transactional variables should be purged before (remove
+	 * versions created by possibly reverted subtransactions).
+	 */
+	if (svar->is_transact &&
+		svar->modify_subid != GetCurrentSubTransactionId() &&
+		svar->modify_subid != InvalidSubTransactionId)
+	{
+		if (!purge_session_variable(svar))
+		{
+			/* force reinit */
+			svar->is_valid = false;
+
+			/*
+			 * In next iteration modify_subid should be
+			 * InvalidSubTransactionId or current subid,
+			 * so there is not risk of infinity cycle.
+			 */
+			goto reinit;
+		}
+	}
+
 	/* Ensure so returned data is still correct domain */
 	if (svar->is_domain)
 	{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3bbc59eca2e..55c201aea22 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -683,7 +683,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-%type <boolean>		OptNotNull OptImmutable
+%type <boolean>		OptNotNull OptImmutable OptTransactional
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -788,7 +788,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TARGET TEMP TEMPLATE TEMPORARY TEXT_P THEN
-	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
+	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSACTIONAL TRANSFORM
 	TREAT TRIGGER TRIM TRUE_P
 	TRUNCATE TRUSTED TYPE_P TYPES_P
 
@@ -5232,31 +5232,33 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
-			CREATE OptTemp OptImmutable VARIABLE qualified_name opt_as Typename opt_collate_clause OptNotNull OptSessionVarDefExpr XactEndActionOption
+			CREATE OptTemp OptTransactional OptImmutable VARIABLE qualified_name opt_as Typename opt_collate_clause OptNotNull OptSessionVarDefExpr XactEndActionOption
 				{
 					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
-					$5->relpersistence = $2;
-					n->is_immutable = $3;
-					n->variable = $5;
-					n->typeName = $7;
-					n->collClause = (CollateClause *) $8;
-					n->not_null = $9;
-					n->defexpr = $10;
-					n->XactEndAction = $11;
+					$6->relpersistence = $2;
+					n->is_immutable = $4;
+					n->is_transact = $3;
+					n->variable = $6;
+					n->typeName = $8;
+					n->collClause = (CollateClause *) $9;
+					n->not_null = $10;
+					n->defexpr = $11;
+					n->XactEndAction = $12;
 					n->if_not_exists = false;
 					$$ = (Node *) n;
 				}
-			| CREATE OptTemp OptImmutable VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OptNotNull OptSessionVarDefExpr XactEndActionOption
+			| CREATE OptTemp OptTransactional OptImmutable VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OptNotNull OptSessionVarDefExpr XactEndActionOption
 				{
 					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
-					$8->relpersistence = $2;
-					n->is_immutable = $3;
-					n->variable = $8;
-					n->typeName = $10;
-					n->collClause = (CollateClause *) $11;
-					n->not_null = $12;
-					n->defexpr = $13;
-					n->XactEndAction = $14;
+					$9->relpersistence = $2;
+					n->is_immutable = $4;
+					n->is_transact = $3;
+					n->variable = $9;
+					n->typeName = $11;
+					n->collClause = (CollateClause *) $12;
+					n->not_null = $13;
+					n->defexpr = $14;
+					n->XactEndAction = $15;
 					n->if_not_exists = true;
 					$$ = (Node *) n;
 				}
@@ -5284,6 +5286,12 @@ OptImmutable: IMMUTABLE								{ $$ = true; }
 			| /* EMPTY */							{ $$ = false; }
 		;
 
+OptTransactional:
+			TRANSACTION								{ $$ = true; }
+			| TRANSACTIONAL							{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
@@ -18045,6 +18053,7 @@ unreserved_keyword:
 			| TEXT_P
 			| TIES
 			| TRANSACTION
+			| TRANSACTIONAL
 			| TRANSFORM
 			| TRIGGER
 			| TRUNCATE
@@ -18695,6 +18704,7 @@ bare_label_keyword:
 			| TIMESTAMP
 			| TRAILING
 			| TRANSACTION
+			| TRANSACTIONAL
 			| TRANSFORM
 			| TREAT
 			| TRIGGER
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 49c33e52def..24753d5921d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5336,6 +5336,7 @@ getVariables(Archive *fout)
 	int			i_varcollation;
 	int			i_varnotnull;
 	int			i_varisimmutable;
+	int			i_varistransact;
 	int			i_varacl;
 	int			i_acldefault;
 	int			i,
@@ -5359,6 +5360,7 @@ getVariables(Archive *fout)
 					  "THEN v.varcollation ELSE 0 END AS varcollation,\n"
 					  "v.varnotnull,\n"
 					  "v.varisimmutable,\n"
+					  "v.varistransact,\n"
 					  "pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n"
 					  "v.varowner,\n"
 					  "v.varacl,\n"
@@ -5382,6 +5384,7 @@ getVariables(Archive *fout)
 	i_varcollation = PQfnumber(res, "varcollation");
 	i_varnotnull = PQfnumber(res, "varnotnull");
 	i_varisimmutable = PQfnumber(res, "varisimmutable");
+	i_varistransact = PQfnumber(res, "varistransact");
 
 	i_varowner = PQfnumber(res, "varowner");
 	i_varacl = PQfnumber(res, "varacl");
@@ -5410,6 +5413,7 @@ getVariables(Archive *fout)
 		varinfo[i].varcollation = atooid(PQgetvalue(res, i, i_varcollation));
 		varinfo[i].varnotnull = *(PQgetvalue(res, i, i_varnotnull)) == 't';
 		varinfo[i].varisimmutable = *(PQgetvalue(res, i, i_varisimmutable)) == 't';
+		varinfo[i].varistransact = *(PQgetvalue(res, i, i_varistransact)) == 't';
 
 		varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl));
 		varinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault));
@@ -5460,6 +5464,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	const char *vardefexpr;
 	const char *varxactendaction;
 	const char *varisimmutable;
+	const char *varistransact;
 	Oid			varcollation;
 	bool		varnotnull;
 
@@ -5477,12 +5482,13 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	varcollation = varinfo->varcollation;
 	varnotnull = varinfo->varnotnull;
 	varisimmutable = varinfo->varisimmutable ? "IMMUTABLE " : "";
+	varistransact = varinfo->varistransact ? "TRANSACTIONAL " : "";
 
 	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
 					  qualvarname);
 
-	appendPQExpBuffer(query, "CREATE %sVARIABLE %s AS %s",
-					  varisimmutable, qualvarname, vartypname);
+	appendPQExpBuffer(query, "CREATE %s%sVARIABLE %s AS %s",
+					  varistransact, varisimmutable, qualvarname, vartypname);
 
 	if (OidIsValid(varcollation))
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index cbb06b66f74..22bcace330e 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -712,6 +712,7 @@ typedef struct _VariableInfo
 	const char *rolname;		/* name of owner, or empty string */
 	bool		varnotnull;
 	bool		varisimmutable;
+	bool		varistransact;
 } VariableInfo;
 
 /*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index a0707c7db16..46dc023f909 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4031,6 +4031,42 @@ my %tests = (
 		},
 	},
 
+	'CREATE TRANSACTIONAL VARIABLE test_variable' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE TRANSACTIONAL VARIABLE dump_test.variable8 AS integer',
+		regexp => qr/^
+			\QCREATE TRANSACTIONAL VARIABLE dump_test.variable8 AS integer;\E/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_measurement    => 1,
+		},
+	},
+
+	'CREATE TRANSACTIONAL IMMUTABLE VARIABLE test_variable' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE TRANSACTIONAL IMMUTABLE VARIABLE dump_test.variable9 AS integer',
+		regexp => qr/^
+			\QCREATE TRANSACTIONAL IMMUTABLE VARIABLE dump_test.variable9 AS integer;\E/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_measurement    => 1,
+		},
+	},
+
 	'CREATE VIEW test_view' => {
 		create_order => 61,
 		create_sql => 'CREATE VIEW dump_test.test_view
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 15ca777996a..ce074a00b8b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5159,7 +5159,7 @@ listVariables(const char *pattern, bool verbose)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false, false, false, false};
 
 	if (pset.sversion < 180000)
 	{
@@ -5182,6 +5182,7 @@ listVariables(const char *pattern, bool verbose)
 					  "  pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n"
 					  "  NOT v.varnotnull as \"%s\",\n"
 					  "  NOT v.varisimmutable as \"%s\",\n"
+					  "  v.varistransact as \"%s\",\n"
 					  "  pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\",\n"
 					  "  CASE v.varxactendaction\n"
 					  "    WHEN 'd' THEN 'ON COMMIT DROP'\n"
@@ -5194,6 +5195,7 @@ listVariables(const char *pattern, bool verbose)
 					  gettext_noop("Owner"),
 					  gettext_noop("Nullable"),
 					  gettext_noop("Mutable"),
+					  gettext_noop("Transactional"),
 					  gettext_noop("Default"),
 					  gettext_noop("Transactional end action"));
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a9cc36a642f..5d491a852d2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1284,6 +1284,8 @@ static const pgsql_thing_t words_after_create[] = {
 																			 * TABLE ... */
 	{"TEXT SEARCH", NULL, NULL, NULL},
 	{"TRANSFORM", NULL, NULL, NULL, NULL, THING_NO_ALTER},
+	{"TRANSACTIONAL", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE TRANSACTIONAL
+																				* VARIABLE ... */
 	{"TRIGGER", "SELECT tgname FROM pg_catalog.pg_trigger WHERE tgname LIKE '%s' AND NOT tgisinternal"},
 	{"TYPE", NULL, NULL, &Query_for_list_of_datatypes},
 	{"UNIQUE", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE UNIQUE
@@ -3601,8 +3603,11 @@ psql_completion(const char *text, int start, int end)
 	}
 /* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VARIABLE <name> with AS */
+	else if (Matches("CREATE", "TRANSACTION|TRANSACTIONAL"))
+		COMPLETE_WITH("IMMUTABLE", "VARIABLE");
 	else if (TailMatches("CREATE", "VARIABLE", MatchAny) ||
 			 TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny) ||
+			 TailMatches("TRANSACTION|TRANSACTIONAL", "VARIABLE", MatchAny) ||
 			 TailMatches("IMMUTABLE", "VARIABLE", MatchAny))
 		COMPLETE_WITH("AS");
 	else if (TailMatches("VARIABLE", MatchAny, "AS"))
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
index ad17b78d6a8..31e02113e9d 100644
--- a/src/include/catalog/pg_variable.h
+++ b/src/include/catalog/pg_variable.h
@@ -60,6 +60,9 @@ CATALOG(pg_variable,9222,VariableRelationId)
 	/* don't allow changes */
 	bool		varisimmutable BKI_DEFAULT(f);
 
+	/* supports transactions */
+	bool		varistransact BKI_DEFAULT(f);
+
 	/* action on transaction end */
 	char		varxactendaction BKI_DEFAULT(n);
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0cc560d538f..1f981ba8c85 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3459,6 +3459,7 @@ typedef struct CreateSessionVarStmt
 	bool		if_not_exists;	/* do nothing if it already exists */
 	bool		not_null;		/* disallow nulls */
 	bool		is_immutable;	/* don't allow changes */
+	bool		is_transact;	/* supports transactions */
 	Node	   *defexpr;		/* default expression */
 	char		XactEndAction;	/* on transaction end action */
 } CreateSessionVarStmt;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 2e88d7d9508..753cf360db4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -457,6 +457,7 @@ PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("to", TO, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("trailing", TRAILING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("transaction", TRANSACTION, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("transactional", TRANSACTIONAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("transform", TRANSFORM, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("treat", TREAT, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("trigger", TRIGGER, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 451fdde31ce..75f173d2333 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5833,20 +5833,20 @@ CREATE ROLE regress_variable_owner;
 SET ROLE TO regress_variable_owner;
 CREATE VARIABLE var1 AS varchar COLLATE "C";
 \dV+ var1
-                                                                         List of variables
- Schema | Name |       Type        | Collation |         Owner          | Nullable | Mutable | Default | Transactional end action | Access privileges | Description 
---------+------+-------------------+-----------+------------------------+----------+---------+---------+--------------------------+-------------------+-------------
- public | var1 | character varying | C         | regress_variable_owner | t        | t       |         |                          |                   | 
+                                                                                 List of variables
+ Schema | Name |       Type        | Collation |         Owner          | Nullable | Mutable | Transactional | Default | Transactional end action | Access privileges | Description 
+--------+------+-------------------+-----------+------------------------+----------+---------+---------------+---------+--------------------------+-------------------+-------------
+ public | var1 | character varying | C         | regress_variable_owner | t        | t       | f             |         |                          |                   | 
 (1 row)
 
 GRANT SELECT ON VARIABLE var1 TO PUBLIC;
 COMMENT ON VARIABLE var1 IS 'some description';
 \dV+ var1
-                                                                                           List of variables
- Schema | Name |       Type        | Collation |         Owner          | Nullable | Mutable | Default | Transactional end action |                Access privileges                 |   Description    
---------+------+-------------------+-----------+------------------------+----------+---------+---------+--------------------------+--------------------------------------------------+------------------
- public | var1 | character varying | C         | regress_variable_owner | t        | t       |         |                          | regress_variable_owner=rw/regress_variable_owner+| some description
-        |      |                   |           |                        |          |         |         |                          | =r/regress_variable_owner                        | 
+                                                                                                   List of variables
+ Schema | Name |       Type        | Collation |         Owner          | Nullable | Mutable | Transactional | Default | Transactional end action |                Access privileges                 |   Description    
+--------+------+-------------------+-----------+------------------------+----------+---------+---------------+---------+--------------------------+--------------------------------------------------+------------------
+ public | var1 | character varying | C         | regress_variable_owner | t        | t       | f             |         |                          | regress_variable_owner=rw/regress_variable_owner+| some description
+        |      |                   |           |                        |          |         |               |         |                          | =r/regress_variable_owner                        | 
 (1 row)
 
 DROP VARIABLE var1;
@@ -6314,9 +6314,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-                                         List of variables
- Schema | Name | Type | Collation | Owner | Nullable | Mutable | Default | Transactional end action 
---------+------+------+-----------+-------+----------+---------+---------+--------------------------
+                                                 List of variables
+ Schema | Name | Type | Collation | Owner | Nullable | Mutable | Transactional | Default | Transactional end action 
+--------+------+------+-----------+-------+----------+---------+---------------+---------+--------------------------
 (0 rows)
 
 -- again, but with dotted schema qualifications.
@@ -6489,9 +6489,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 \dy "no.such.schema"."no.such.event.trigger"
 improper qualified name (too many dotted names): "no.such.schema"."no.such.event.trigger"
 \dV "no.such.schema"."no.such.variable"
-                                         List of variables
- Schema | Name | Type | Collation | Owner | Nullable | Mutable | Default | Transactional end action 
---------+------+------+-----------+-------+----------+---------+---------+--------------------------
+                                                 List of variables
+ Schema | Name | Type | Collation | Owner | Nullable | Mutable | Transactional | Default | Transactional end action 
+--------+------+------+-----------+-------+----------+---------+---------------+---------+--------------------------
 (0 rows)
 
 -- again, but with current database and dotted schema qualifications.
@@ -6628,9 +6628,9 @@ List of text search templates
 (0 rows)
 
 \dV regression."no.such.schema"."no.such.variable"
-                                         List of variables
- Schema | Name | Type | Collation | Owner | Nullable | Mutable | Default | Transactional end action 
---------+------+------+-----------+-------+----------+---------+---------+--------------------------
+                                                 List of variables
+ Schema | Name | Type | Collation | Owner | Nullable | Mutable | Transactional | Default | Transactional end action 
+--------+------+------+-----------+-------+----------+---------+---------------+---------+--------------------------
 (0 rows)
 
 -- again, but with dotted database and dotted schema qualifications.
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 415cf2c82ba..f13078c9191 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -45,11 +45,11 @@ SET ROLE TO regress_variable_owner;
 CREATE VARIABLE svartest.var1 AS int;
 SET ROLE TO DEFAULT;
 \dV+ svartest.var1
-                                                                                     List of variables
-  Schema  | Name |  Type   | Collation |         Owner          | Nullable | Mutable | Default | Transactional end action |                Access privileges                 | Description 
-----------+------+---------+-----------+------------------------+----------+---------+---------+--------------------------+--------------------------------------------------+-------------
- svartest | var1 | integer |           | regress_variable_owner | t        | t       |         |                          | regress_variable_owner=rw/regress_variable_owner+| 
-          |      |         |           |                        |          |         |         |                          | regress_variable_reader=r/regress_variable_owner | 
+                                                                                             List of variables
+  Schema  | Name |  Type   | Collation |         Owner          | Nullable | Mutable | Transactional | Default | Transactional end action |                Access privileges                 | Description 
+----------+------+---------+-----------+------------------------+----------+---------+---------------+---------+--------------------------+--------------------------------------------------+-------------
+ svartest | var1 | integer |           | regress_variable_owner | t        | t       | f             |         |                          | regress_variable_owner=rw/regress_variable_owner+| 
+          |      |         |           |                        |          |         |               |         |                          | regress_variable_reader=r/regress_variable_owner | 
 (1 row)
 
 DROP VARIABLE svartest.var1;
@@ -1747,3 +1747,103 @@ SELECT var1;
 LET var1 = 30;
 ERROR:  session variable "public.var1" is declared IMMUTABLE
 DROP VARIABLE var1;
+-- test transactional variables
+CREATE TRANSACTION VARIABLE tv AS int DEFAULT 0;
+BEGIN;
+  LET tv = 100;
+  SELECT tv;
+ tv  
+-----
+ 100
+(1 row)
+
+ROLLBACK;
+SELECT tv;
+ tv 
+----
+  0
+(1 row)
+
+LET tv = 100;
+BEGIN;
+  LET tv = 1000;
+COMMIT;
+SELECT tv;
+  tv  
+------
+ 1000
+(1 row)
+
+BEGIN;
+  LET tv = 0;
+  SELECT tv;
+ tv 
+----
+  0
+(1 row)
+
+ROLLBACK;
+SELECT tv;
+  tv  
+------
+ 1000
+(1 row)
+
+-- test subtransactions
+BEGIN;
+  LET tv = 1;
+SAVEPOINT x1;
+  LET tv = 2;
+SAVEPOINT x2;
+  LET tv = 3;
+ROLLBACK TO x2;
+  SELECT tv;
+ tv 
+----
+  2
+(1 row)
+
+  LET tv = 10;
+ROLLBACK TO x1;
+  SELECT tv;
+ tv 
+----
+  1
+(1 row)
+
+ROLLBACK;
+SELECT tv;
+  tv  
+------
+ 1000
+(1 row)
+
+BEGIN;
+  LET tv = 1;
+SAVEPOINT x1;
+  LET tv = 2;
+SAVEPOINT x2;
+  LET tv = 3;
+ROLLBACK TO x2;
+  SELECT tv;
+ tv 
+----
+  2
+(1 row)
+
+  LET tv = 10;
+ROLLBACK TO x1;
+  SELECT tv;
+ tv 
+----
+  1
+(1 row)
+
+COMMIT;
+SELECT tv;
+ tv 
+----
+  1
+(1 row)
+
+DROP VARIABLE tv;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 074619c4a0e..c71b90dc85f 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -1196,3 +1196,61 @@ SELECT var1;
 LET var1 = 30;
 
 DROP VARIABLE var1;
+
+-- test transactional variables
+CREATE TRANSACTION VARIABLE tv AS int DEFAULT 0;
+
+BEGIN;
+  LET tv = 100;
+  SELECT tv;
+ROLLBACK;
+
+SELECT tv;
+
+LET tv = 100;
+BEGIN;
+  LET tv = 1000;
+COMMIT;
+
+SELECT tv;
+
+BEGIN;
+  LET tv = 0;
+  SELECT tv;
+ROLLBACK;
+
+SELECT tv;
+
+-- test subtransactions
+
+BEGIN;
+  LET tv = 1;
+SAVEPOINT x1;
+  LET tv = 2;
+SAVEPOINT x2;
+  LET tv = 3;
+ROLLBACK TO x2;
+  SELECT tv;
+  LET tv = 10;
+ROLLBACK TO x1;
+  SELECT tv;
+ROLLBACK;
+
+SELECT tv;
+
+BEGIN;
+  LET tv = 1;
+SAVEPOINT x1;
+  LET tv = 2;
+SAVEPOINT x2;
+  LET tv = 3;
+ROLLBACK TO x2;
+  SELECT tv;
+  LET tv = 10;
+ROLLBACK TO x1;
+  SELECT tv;
+COMMIT;
+
+SELECT tv;
+
+DROP VARIABLE tv;
-- 
2.45.2

From cc1b52681f1ae021d9e2a81fc60ecdad6b0fa5b4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

possibility to restore session variable specified by name
---
 doc/src/sgml/ref/pg_restore.sgml | 11 +++++++++++
 src/bin/pg_dump/pg_restore.c     |  9 ++++++++-
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index 2e3ba802581..952f52b1f9d 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -106,6 +106,17 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-A <replaceable class="parameter">session_variable</replaceable></option></term>
+      <term><option>--variable=<replaceable class="parameter">session_variable</replaceable></option></term>
+      <listitem>
+       <para>
+        Restore a named session variable only.  Multiple session variables may
+        be specified with multiple <option>-A</option> switches.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-c</option></term>
       <term><option>--clean</option></term>
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index df119591cca..a7c3ae9bfd1 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -105,6 +105,7 @@ main(int argc, char **argv)
 		{"trigger", 1, NULL, 'T'},
 		{"use-list", 1, NULL, 'L'},
 		{"username", 1, NULL, 'U'},
+		{"variable", 1, NULL, 'A'},
 		{"verbose", 0, NULL, 'v'},
 		{"single-transaction", 0, NULL, '1'},
 
@@ -155,7 +156,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1",
+	while ((c = getopt_long(argc, argv, "A:acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1",
 							cmdopts, NULL)) != -1)
 	{
 		switch (c)
@@ -163,6 +164,11 @@ main(int argc, char **argv)
 			case 'a':			/* Dump data only */
 				opts->dataOnly = 1;
 				break;
+			case 'A':			/* vAriable */
+				opts->selTypes = 1;
+				opts->selVariable = 1;
+				simple_string_list_append(&opts->variableNames, optarg);
+				break;
 			case 'c':			/* clean (i.e., drop) schema prior to create */
 				opts->dropSchema = 1;
 				break;
@@ -463,6 +469,7 @@ usage(const char *progname)
 
 	printf(_("\nOptions controlling the restore:\n"));
 	printf(_("  -a, --data-only              restore only the data, no schema\n"));
+	printf(_("  -A, --variable=NAME          restore named session variable\n"));
 	printf(_("  -c, --clean                  clean (drop) database objects before recreating\n"));
 	printf(_("  -C, --create                 create the target database\n"));
 	printf(_("  -e, --exit-on-error          exit on error, default is to continue\n"));
-- 
2.45.2

---

From: Laurenz Albe <[email-masked]>
Date: Thu, 25 Jul 2024 15:52:47 +0200

Thanks for the updated patch set.

I found a problem in 0019-transactional-variables.patch:

+     <row>

That's messed up; it should be

     <row>
      <entry role="catalog_table_entry"><para role="column_definition">
       <structfield>varistransact</structfield> <type>boolean</type>
      </para>
      <para>
       True, when the variable is <quote>transactional</quote>. In the case
       of a transaction rollback, transactional variables are reset to the
       value they had when the transaction started. The default value is
       <literal>false</literal>.
      </para></entry>
     </row>

I have started reading through the first patch, and so far I have only found
language problems.

I wonder how I should go about this.  At first, I intended to send an edited
version of the first patch, but as later patches depend on earlier patches,
that would mess up the patch set.

I can send my suggested modifications in text, but then you have to copy and
paste them all, which is cumbersome.

What would be best for you?

Thinking further, I wondered about the order of patches.
If some committer eventually takes mercy on this patch set, I expect that
only a part of the functionality will go in as a first step.
Does the order of the patches in the patch set match such a process?

I'd guess that temporary session variables or ON TRANSACTION END RESET
would be things that can be committed later on, but PL/pgSQL support
should be in the first commit.

What is your approach to that?

Yours,
Laurenz Albe

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 25 Jul 2024 18:32:24 +0200

Hi

>        <literal>false</literal>.
changed

> What would be best for you?

send me it in any form - probably the diff of patches are best. I'll do
necessary fixes.

There are not good tool for maintaining chronologically incremental
patchset

> Does the order of the patches in the patch set match such a process?

yes

we talk so first seven patches are mandatory - others are optional and
introduce new functionality or increase performance

> should be in the first commit.

The plpgsql is supported by a second patch, because variables are
accessible by queries. But patch 16 introduces the
PLpgSQL LET command, and then allows us to evaluate an expression as a
simple expression, which is significantly faster.

> What is your approach to that?

The first six, seven patches are fundamental.  For others we can talk about
priorities. I see performance related patches to be more important, but
they are a little bit more technically difficult or maybe related to a not
too cool area now (like PL/pgSQL - I like it, but almost all people's
stored procedures aren't used). Implementation of new features is almost
done by simple patches. And again for somebody temp variables are not
important (usage temp variables in PLpgSQL is +/- zero), but for others can
be very important (it can be very interesting for writing psql scripts,
because it allows parametrization of DO commands, and it is cheaper than
temp tables for passing result).

So others patches have some order, because there has to be some order, but
it is not final or immutable. I can imagine throwing these patches to
different patchset. On the second hand, the size of these patches is less
than the first two, and I think it is interesting, because from my
perspective, these patches cover this area completely. But again, the order
of these patches is important for some dependencies in files (regress
tests), but it doesn't draw the priority of implemented features. Current
state is the result of my work or work of other peoples that did review of
these patches. I am open to changing this order, if somebody would do it.

Regards

Pavel

> Laurenz Albe
From 46eaa8946db6c5cec3a261040ed06c21fa71f307 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 

 

 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+
 			/*
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 
-

index d14a94b9873..f53472524a6 100644

 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du

From a1d1f0a5ef7f32c696d958df1f13c464719142f8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*
index f53472524a6..168d1ec4de0 100644

From aed34ca6db147062862127235044885aed60538d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 

 
 
+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

 	if (sessionvars)

 -- should be zero again

From e855543e09a552d2f53720c4935f1476792f7c5d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+

From 8e9ee6542b8881f26b415b819fd643a0221f953a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 
+     <row>
 
  <sect1 id="ddl-others">

 

+/*

 
 
 						/*
 
+	/*
 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*

 

 
 
 			/*
 
 /* GUC parameters */
 
-

 /*
 
+/*

+	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */

 

 
 
 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*

From 17ff2769673c69329c2e7c3b98e42146e95b9bdf Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From 1068b5e15545181d50ac44a6b926dc8b73b3b632 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

 

 /* GUC parameters */

 /* GUC parameters */

 

 

From 1e98f621e600841f15127916e41924d363190c40 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

 

-/* LET */

From b5b7653f4d3e580a6a9d515d3b554dbf92ebf747 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

 

 DROP TABLE public.xxtab;

From 2a45a8dfb67d3f09c11c6ea138df2f92787bfea8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
 
 		/*

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables

From 0b5bec7288b490e7d2542354f316bb9d40f56f3b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;

 

 

 

 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From 6466e6d93c879f683351e5baa9a9ff1d78c6381a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{

 
 CreateSessionVarStmt:

 			if (isAgg)

 

 
 
+	if (vardefexpr)

 
 
 	if (verbose)

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From 141aaa1a5aaa618396f10f3105d22cc369be4784 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 

 /*
 
 CreateSessionVarStmt:

 

 

 
+	if (varnotnull)
 
 /*

 

+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From 8105f2f98173a8f54ceca6b7f2b68d64b4eb29f4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 

 
 
-	/*

 

 	/*

+LET v = 1;
+

From 63b4ef186124a13cc6d48fd66ac779e91dee7b23 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

 
 	/*

 

From eae5d09de79075f6c2ede7df5c54b9a2bef9e396 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*

 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void
index 0671ff78722..4f98261ac69 100644
@@ -199,7 +199,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 
 %type <list>	proc_exceptions
@@ -306,6 +306,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_INTO
 %token <keyword>	K_IS
 %token <keyword>	K_LAST
+%token <keyword>	K_LET
 %token <keyword>	K_LOG
 %token <keyword>	K_LOOP
 %token <keyword>	K_MERGE
@@ -875,6 +876,8 @@ proc_stmt		: pl_block ';'
 						{ $$ = $1; }
 				| stmt_rollback
 						{ $$ = $1; }
+				| stmt_let
+						{ $$ = $1; }
 				;
 
 stmt_perform	: K_PERFORM
@@ -991,6 +994,29 @@ stmt_assign		: T_DATUM
 					}
 				;
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */

From 1a149c1bcda6a8d7ff90503caa059ec3d7e54e95 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From 4a0642cf8794f95b8c119d480bbfcd96573d7bf4 Mon Sep 17 00:00:00 2001

 
+/*

 

 

 select a from atacc1;

 
index 52012806699..0089ce1a79b 100644

From beb4268345d91b8c01a5bee8e24df7fe863d74d5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 doc/src/sgml/catalogs.sgml                    |  12 +
 16 files changed, 592 insertions(+), 50 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 96cc88e6cf6..765e4fa2889 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9851,6 +9851,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry><structfield>varistransact</structfield></entry>
+      <entry><type>boolean</type></entry>
+      <entry></entry>
+      <entry>
+       True, when the variable is <quote>transactional</quote>. In the case
+       of a transaction rollback, transactional variables are reset to the
+       value they had when the transaction started. The default value is
+       <literal>false</literal>.
+      </entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>vareoxaction</structfield> <type>char</type>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index cb20d1dc68c..c9df71709d9 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:

 

 
 
 /*

 

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+

From 97cbbb9ee1eafcb15165d90c549c0015c9fb0795 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

---

From: Laurenz Albe <[email-masked]>
Date: Sat, 27 Jul 2024 16:19:23 +0200

I went through the v20240724-2-0001 patch and mostly changed some documentation
and the comments.  Attached is my updated version.

Comments:

> [...]
> [...]

I cannot imagine that we'll ever have session variables in "pg_catalog".
Did you put that there as defensive coding?

> [...]

That function should get documented in functions-info.html#FUNCTIONS-INFO-SCHEMA-TABLE
I have added an entry in the attached patch.

> [...]

I have slightly rewritten the text and moved it to doc/src/sgml/ddl.sgml.
I felt that to be a better place for generic information about session variable's
behavior.  Also, the single paragraph in doc/src/sgml/ddl.sgml felt lonely.
I left the note in CREATE VARIABLE, but it is now just a link to ddl.sgml.

> [...]
> [...]
> [...]

One of the two selectDumpableObject() calls seems redundant.
I removed the first one; I hope that's OK.

> [...]

I guess that belongs in the second patch, but I didn't change it.
Since the feature cannot be committed without LET, it doesn't really matter in
which of the two patches it is.

> [...]

That's a weird test - why the pipe?  Is there something I don't get?
There is already another test for cross-database references.

> +cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.variable"

And another one.  Do we need so many tests for that?

I hope I didn't create too many conflicts with the rest of the patch set.

I plan to review the other patches too, but I'll go on vacations for three weeks
in a week, and fall promises to be busy.  So it might be a while.

Yours,
Laurenz Albe
From 70fbc2b6097ebae7a53e095b6f9c63360f600599 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Sat, 27 Jul 2024 16:07:34 +0200
Subject: [PATCH v20240727] Catalog, CREATE/ALTER/DROP, privileges, pg_dump and
 psql support for session variables

This patch is not short, but the code is simple.
---
 doc/src/sgml/catalogs.sgml                    | 120 +++++++++++
 doc/src/sgml/ddl.sgml                         |  31 +++
 doc/src/sgml/event-trigger.sgml               |  24 +++
 doc/src/sgml/func.sgml                        |  13 ++
 doc/src/sgml/glossary.sgml                    |  15 ++
 doc/src/sgml/ref/allfiles.sgml                |   3 +
 .../sgml/ref/alter_default_privileges.sgml    |  26 ++-
 doc/src/sgml/ref/comment.sgml                 |   1 +
 doc/src/sgml/ref/create_schema.sgml           |   7 +-
 doc/src/sgml/ref/grant.sgml                   |   6 +
 doc/src/sgml/ref/revoke.sgml                  |   7 +
 doc/src/sgml/reference.sgml                   |   3 +
 src/backend/catalog/Makefile                  |   1 +
 src/backend/catalog/aclchk.c                  |  77 +++++++
 src/backend/catalog/dependency.c              |   6 +
 src/backend/catalog/meson.build               |   1 +
 src/backend/catalog/namespace.c               | 135 ++++++++++++
 src/backend/catalog/objectaddress.c           | 121 ++++++++++-
 src/backend/catalog/pg_shdepend.c             |   2 +
 src/backend/commands/alter.c                  |   9 +
 src/backend/commands/dropcmds.c               |   4 +
 src/backend/commands/event_trigger.c          |   4 +
 src/backend/commands/seclabel.c               |   1 +
 src/backend/commands/tablecmds.c              |  41 ++++
 src/backend/parser/gram.y                     | 105 +++++++++-
 src/backend/parser/parse_utilcmd.c            |  12 ++
 src/backend/tcop/utility.c                    |  16 ++
 src/backend/utils/adt/acl.c                   |   7 +
 src/backend/utils/cache/lsyscache.c           | 113 ++++++++++
 src/bin/pg_dump/common.c                      |   3 +
 src/bin/pg_dump/dumputils.c                   |   6 +
 src/bin/pg_dump/pg_backup.h                   |   2 +
 src/bin/pg_dump/pg_backup_archiver.c          |   9 +
 src/bin/pg_dump/pg_dump.c                     | 195 +++++++++++++++++-
 src/bin/pg_dump/pg_dump.h                     |  19 ++
 src/bin/pg_dump/pg_dump_sort.c                |   6 +
 src/bin/pg_dump/t/002_pg_dump.pl              |  64 ++++++
 src/bin/psql/command.c                        |   3 +
 src/bin/psql/describe.c                       |  96 +++++++++
 src/bin/psql/describe.h                       |   3 +
 src/bin/psql/help.c                           |   1 +
 src/bin/psql/tab-complete.c                   |  44 +++-
 src/include/catalog/Makefile                  |   3 +-
 src/include/catalog/meson.build               |   1 +
 src/include/catalog/namespace.h               |   4 +
 src/include/catalog/pg_default_acl.h          |   1 +
 src/include/catalog/pg_proc.dat               |   3 +
 src/include/nodes/parsenodes.h                |  16 ++
 src/include/parser/kwlist.h                   |   2 +
 src/include/tcop/cmdtaglist.h                 |   3 +
 src/include/utils/acl.h                       |   1 +
 src/include/utils/lsyscache.h                 |   9 +
 src/test/regress/expected/dependency.out      |  17 ++
 src/test/regress/expected/oidjoins.out        |   4 +
 src/test/regress/expected/psql.out            |  50 +++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/dependency.sql           |  14 ++
 src/test/regress/sql/psql.sql                 |  21 ++
 src/tools/pgindent/typedefs.list              |   4 +
 59 files changed, 1490 insertions(+), 27 deletions(-)

index b654fae1b2..de122e543f 100644
 
+   The catalog <structname>pg_variable</structname> stores information about
index c5e11a6699..432184b4e5 100644
@@ -5298,6 +5298,37 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>
 
+  <sect1 id="ddl-session-variables">
+   <title>Session Variables</title>
+
+   <indexterm zone="ddl-session-variables">
+    <primary>Session variables</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>session variable</primary>
+   </indexterm>
+
+   <para>
+    Session variables are database objects that can hold a value.
+    Session variables, like relations, exist within a schema and their access
+    is controlled via <command>GRANT</command> and <command>REVOKE</command>
+    commands.  A session variable can be created by the <command>CREATE
+    VARIABLE</command> command.
+   </para>
+
+   <para>
+    Inside a query or an expression, a session variable can be
+    <quote>shadowed</quote> by a column with the same name.  Similarly, the
+    name of a function or procedure argument or a PL/pgSQL variable (see
+    <xref linkend="plpgsql-declarations"/>) can shadow a session variable
+    in the routine's body.  Such collisions of identifiers can be resolved
+    by using qualified identifiers: Session variables can be qualified with
+    the schema name, columns can use table aliases, routine variables can use
+    block labels, and routine arguments can use the routine name.
+   </para>
+  </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>
 
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 8e009cca05..15b2af99dc 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -424,6 +424,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>ALTER VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>ALTER VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
@@ -712,6 +720,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>CREATE VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>CREATE VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
@@ -1000,6 +1016,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>DROP VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>DROP VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3f93c61aa3..ec3f5cbdb5 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25641,6 +25641,19 @@ SELECT relname FROM pg_class WHERE pg_table_is_visible(oid);
         Is type (or domain) visible in search path?
        </para></entry>
       </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_variable_is_visible</primary>
+        </indexterm>
+        <function>pg_variable_is_visible</function> ( <parameter>variable</parameter> <type>oid</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is session variable visible in search path?
+       </para></entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index 405fe6dc8b..d5f4bcc6e0 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -1654,6 +1654,21 @@
    </glossdef>
   </glossentry>
 
+     value is private to each session and is released when the session ends.
+     Read or write access to session variables is controlled by privileges,
+     similar to other database objects.
index f5be638867..2f67de3e21 100644
index 89aacec4fa..041c78d432 100644

+   tables), sequences, functions, types (including domains), and session
 
   <para>
index 5b43c56b13..21cd80818f 100644
 
index ed69298ccc..a834c876bc 100644
index 999f657d5c..78ff10fcf5 100644

index 8df492281a..626c0231d0 100644
 
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83f..25578f3946 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -75,6 +75,7 @@
    &alterType;
    &alterUser;
    &alterUserMapping;
+   &alterVariable;
    &alterView;
    &analyze;
    &begin;
@@ -127,6 +128,7 @@
    &createType;
    &createUser;
    &createUserMapping;
+   &createVariable;
    &createView;
    &deallocate;
    &declare;
@@ -175,6 +177,7 @@
    &dropType;
    &dropUser;
    &dropUserMapping;
+   &dropVariable;
    &dropView;
    &end;
    &execute;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1589a75fd5..7a90fcfc45 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -45,6 +45,7 @@ OBJS = \
 	pg_shdepend.o \
 	pg_subscription.o \
 	pg_type.o \
+	pg_variable.o \
 	storage.o \
 	toasting.o
 
index a44ccee3b6..6968b1e6eb 100644
 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:
index 0489cbabcb..c9d686665d 100644
 
index 2f3ded8a0e..bfdb4da330 100644
index 43b707699d..8752e1646e 100644
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
@@ -3274,6 +3338,66 @@ TSConfigIsVisibleExt(Oid cfgid, bool *is_missing)
 	return visible;
 }
 
+/*
+		/* if nspname is a known namespace, the variable must be there */
+		/* iterate over the schemas on the search_path */
 
 /*
  * DeconstructQualifiedName
@@ -5075,3 +5199,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)
 
index 85a7b7e641..ee70eeeed4 100644
 
 /*

 
 	/*
 
+/*

 
 				if (objname)
 
index 753afb8845..1ea3d6db05 100644
index 4f99ebb447..63fa99fad3 100644
 
index 85eec7e394..3cce17e92a 100644
 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
index 7a5ed6b985..6d41aa7ec3 100644
index 5607273bf9..c026ab5dcb 100644
 
 			/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 721d24783b..590cfa2406 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -51,6 +51,7 @@
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
@@ -6723,6 +6724,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
  * (possibly nested several levels deep in composite types, arrays, etc!).
  * Eventually, we'd like to propagate the check or rewrite operation
  * into such tables, but for now, just error out if we find any.
+ * Also, check if "typeOid" is used as type of some session variable.
  *
  * Caller should provide either the associated relation of a rowtype,
  * or a type name (not both) for use in the error message, if any.
@@ -6786,6 +6788,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
+		/* check if the type is used as type of some session variable */
index a043fd4c66..3f2c590ce3 100644
+		CreateSchemaStmt CreateSeqStmt CreateSessionVarStmt CreateStmt CreateStatsStmt

 

 
 /*

 
 opt_column: COLUMN

index d5c2b2ff0b..2fba90c95a 100644

 	/*

 	return result;
 }
index fa66b8017e..ce8db7cda4 100644

index d7b39140b3..5f80af4f5e 100644
index 48a280d089..2eed9489f9 100644
 
 	return subname;
 }
+
+ * Returns the type of the given session variable.
index c323b5bd3d..4079ad603a 100644

index 5649859aa1..50de5d0ea2 100644
 
index fbf5f1c515..e81cfde274 100644
 
 	int			useDB;
index 68e321212d..76846cc763 100644
index b8b1888bd3..70e6e0c909 100644
@@ -5313,6 +5314,190 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
+					  "       v.varnamespace, v.vartype,\n"
+					  "       pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n"
+					  "       CASE WHEN v.varcollation <> t.typcollation "
+					  "            THEN v.varcollation\n"
+					  "            ELSE 0\n"
+					  "       END AS varcollation,\n"
+					  "       v.varowner, v.varacl,\n"
+					  "       acldefault('V', v.varowner) AS acldefault\n"
@@ -9875,7 +10060,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -10474,6 +10660,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION_REL:
 			dumpSubscriptionTable(fout, (const SubRelInfo *) dobj);
 			break;
+		case DO_VARIABLE:
+			dumpVariable(fout, (VariableInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -14923,6 +15112,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
 		case DEFACLOBJ_NAMESPACE:
 			type = "SCHEMAS";
 			break;
+		case DEFACLOBJ_VARIABLE:
+			type = "VARIABLES";
+			break;
 		default:
 			/* shouldn't get here */
 			pg_fatal("unrecognized object type in default privileges: %d",
@@ -18370,6 +18562,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_CONVERSION:
 			case DO_TABLE:
 			case DO_TABLE_ATTACH:
+			case DO_VARIABLE:
 			case DO_ATTRDEF:
 			case DO_PROCLANG:
 			case DO_CAST:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 4b2e5870a9..36a69a0832 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -52,6 +52,7 @@ typedef enum
 	DO_TABLE,
 	DO_TABLE_ATTACH,
 	DO_ATTRDEF,
+	DO_VARIABLE,
 	DO_INDEX,
 	DO_INDEX_ATTACH,
 	DO_STATSEXT,
@@ -692,6 +693,23 @@ typedef struct _SubRelInfo
 	char	   *srsublsn;
 } SubRelInfo;
 
+/*
 
 #endif							/* PG_DUMP_H */
index 4cb754caa5..ebb9bf037c 100644
index d3dd8784d6..d62db24228 100644

 
 
index 180781ecd0..26a9a3fac7 100644
index 7c9a1f234c..adf83ed197 100644
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
index 273f974538..4e15a180ea 100644
 
+/* \dV */
index 6f58a11074..bdc327ffe3 100644
index d453e224d9..a1aecf20b1 100644

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*
index 167f91a6e3..507f380821 100644

index f70d1daba5..6eec79d2ee 100644
 
index 8d434d48d5..18ea6ac83a 100644
 
@@ -169,6 +171,8 @@ extern SearchPathMatcher *GetSearchPathMatcher(MemoryContext context);
 
index d272cdf08b..529d53c6bb 100644

index 73d9cf8582..644beb9043 100644
 
index 85a62b538e..80ea5928ee 100644

+
index f7fe834cf4..c884cd5c46 100644
index 7fdcec6dd9..9465df7b2f 100644
index 731d84b2a9..98aab98229 100644
 
index 20446f6f83..8a3684e711 100644
 
index 74d9ff2998..c336f67a59 100644
@@ -151,3 +151,20 @@ owner of type deptest_t
+-- clean up
index 215eb899be..d995332140 100644
index 3bbe4c5f97..c89067490f 100644
 
+\dV "no.such.variable"
+            List of variables

index 2429ec2bba..ed6893d479 100644
 
 # ----------
index 8d74ed7122..6c18b7f840 100644
@@ -114,3 +114,17 @@ DROP USER regress_dep_user2;
+-- clean up
index 3b3c6f6e29..b80d56a27a 100644

 

 
 -- check \drg and \du
index b4d7f9217c..a18e81125b 100644

---

From: Laurenz Albe <[email-masked]>
Date: Mon, 29 Jul 2024 11:56:46 +0200

> and the comments.  Attached is my updated version.

Sorry; I forgot the new files.  Here is an updated replacement for your first patch.

I'll start working on the second patch now.

Yours,
Laurenz Albe
From f2b7654d3b9dc758316fd7cb2de05e78ceeee737 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Mon, 29 Jul 2024 11:54:25 +0200
Subject: [PATCH v20240729] Enhancing catalog for support session variables and
 related support

 doc/src/sgml/func.sgml                        |  13 +
 doc/src/sgml/ref/create_variable.sgml         | 151 +++++++++++
 src/backend/catalog/namespace.c               | 135 ++++++++++
 src/backend/commands/tablecmds.c              |  41 +++
 src/bin/pg_dump/pg_dump.c                     | 195 +++++++++++++-
 src/test/regress/sql/dependency.sql           |  14 +
 66 files changed, 2390 insertions(+), 27 deletions(-)

 

 

   <para>
index 0000000000..d87570e7d8
 
index 0000000000..ec165ab96f
@@ -0,0 +1,151 @@
+   controlled via the commands <command>GRANT</command> and <command>REVOKE</command>.
+   a session variable's value returns NULL, unless its value is set to
+   something else in the current session with a <command>LET</command> command.
+   The content of a session variable is not transactional. This is the same as
+   regular variables in procedural languages.
+    Session variables can be <quote>shadowed</quote> by other identifiers.
+    For details, see <xref linkend="ddl-session-variables"/>.
index 0000000000..5bdb3560f0

 

+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)
 
index 0000000000..f65121440a

 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 

index 0000000000..3810e040f2
+ *		the format of the pg_variable relation.

+

 
+\dV "no.such.variable"
+            List of variables

index 0000000000..aca766042d
 
 # ----------

 

 
 -- check \drg and \du
index 0000000000..42dfb95a02

---

From: Laurenz Albe <[email-masked]>
Date: Tue, 30 Jul 2024 21:46:46 +0200

This is my review of the second patch in the series.

Again, I mostly changed code comments and documentation.

Noteworthy remarks:

- A general reminder: single line comments should start with a lower case
  letter and have to period in the end:

  /* this is a typical single line comment */

- Variable as parameter:

  CREATE VARIABLE var AS date;
  LET var = current_date;
  PREPARE stmt(date) AS SELECT $1;
  EXECUTE stmt(var);
  ERROR:  paramid of PARAM_VARIABLE param is out of range

  Is that working as intended?  I don't understand the error message.

  Perhaps there is a bug in src/backend/executor/execExpr.c.

- IdentifyVariable

  > + * IdentifyVariable - try to find variable identified by list of names.

  The function comment doesn't make clear to me what the function does.
  Perhaps that is so confusing because the function seems to serve several
  purposes (during parsing, in ANALYZE, and to identify shadowed variables
  in a later patch).

  I rewrote the function comment, but didn't touch the code or code comments.

  Perhaps part of the reason why this function is so complicated is that
  you support things like this:

    CREATE SCHEMA sch;
    CREATE TYPE cp AS (a integer, b integer);
    CREATE VARIABLE sch.v AS cp;
    LET sch.v = (1, 2);
    SELECT sch.v.b;
     b 
    ═══
     2
    (1 row)

    test=# SELECT test.sch.v.b;
     b 
    ═══
     2
    (1 row)

  We don't support that for tables:

    CREATE TABLE tab (c cp);
    INSERT INTO tab VALUES (ROW(42, 43));
    SELECT tab.c.b FROM tab;
    ERROR:  cross-database references are not implemented: tab.c.b

  You have to use extra parentheses:

    SELECT (tab.c).b FROM tab;
     b  
    ════
     43
    (1 row)

  I'd say that this should be the same for session variables.
  What do you think?

  Code comments:

  - There are lots of variables declared at the beginning, but they are only
    used in code blocks further down.  For clarity, you should declare a
    variable in the code block where it is used.

  - +                   /*

    There needn't be an "else", since the ereport() will never return.
    Less indentation is better.

- src/backend/commands/session_variable.c

  > +   return copy_session_variable_value(svar, isNull);

- src/backend/executor/execMain.c

  > +    * is passed to executor.

  Why?  Is that a performance measure?  The comment should explain that.

- parallel safety

  > +           if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))

  Even if a later patch alleviates that restriction, this patch should document that
  session variables imply "parallel restricted".  I have added that to doc/src/sgml/parallel.sgml.

Attached are the first two patches with my edits (the first patch is unchanged;
I just add it again to keep the cfbot happy.

I hope to get to review the other patches at some later time.

Yours,
Laurenz Albe
From 4705db0de3f9f5031fa8238908421d217a77e291 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Tue, 30 Jul 2024 21:39:12 +0200
Subject: [PATCH v20240730 1/2] Enhancing catalog for support session variables
 and related support

 
index 626d35514c..7531de17fd 100644

index b39f97dc8d..c3c4fbb3d9 100644
@@ -25647,6 +25647,19 @@ SELECT relname FROM pg_class WHERE pg_table_is_visible(oid);

 
 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*
index 0b2a52463f..f3564e6017 100644

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
index 8c20c263c4..6f012a1b55 100644
index 2b02148559..d8c74f4e2e 100644
 
+/*
+ * getVariables
 
@@ -18432,6 +18624,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
+/*
 
 #endif							/* PG_DUMP_H */
index 5bcc2244d5..0d3765eed4 100644

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 024469474d..873a83fa5a 100644

 /*
 	/* Complete "CREATE UNLOGGED" with TABLE or SEQUENCE */
 		COMPLETE_WITH("TABLE", "SEQUENCE");
@@ -3580,6 +3591,13 @@ psql_completion(const char *text, int start, int end)
 
@@ -3843,6 +3861,12 @@ psql_completion(const char *text, int start, int end)
 
+	/* DROP VARIABLE */
@@ -4046,7 +4070,7 @@ psql_completion(const char *text, int start, int end)
@@ -4068,7 +4092,8 @@ psql_completion(const char *text, int start, int end)
@@ -4076,7 +4101,8 @@ psql_completion(const char *text, int start, int end)
@@ -4112,6 +4138,8 @@ psql_completion(const char *text, int start, int end)
@@ -4415,7 +4443,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
@@ -4883,6 +4911,8 @@ psql_completion(const char *text, int start, int end)

 

 
 
index 06b2f4ba66..837d8dfae1 100644
@@ -6472,6 +6472,9 @@

 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du
index 3deb6113b8..837e5b83a3 100644

From 4d30bddd4c2f8b0ef9a1b4b6ab7175c67dd849ea Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Tue, 30 Jul 2024 21:42:40 +0200
Subject: [PATCH v20240730 2/2] Storage for session variables and SQL interface

Session variables are stored in session memory in a dedicated hash table.
They are set by the LET command and read by the SELECT command.
The access rights should be checked.

The identifiers of session variables should always be shadowed by possible
column identifiers: we don't want to break an application by creating some
badly named session variable.

 doc/src/sgml/ddl.sgml                         |  32 +
 doc/src/sgml/parallel.sgml                    |   6 +
 doc/src/sgml/plpgsql.sgml                     |  14 +
 src/backend/catalog/namespace.c               | 317 ++++++
 src/backend/optimizer/plan/planner.c          |   9 +
 src/backend/optimizer/plan/setrefs.c          | 135 ++-
 src/backend/parser/analyze.c                  | 264 ++++-
 src/backend/parser/parse_expr.c               | 220 ++++-
 src/include/catalog/pg_variable.h             |   8 +
 src/include/nodes/execnodes.h                 |  16 +
 53 files changed, 2974 insertions(+), 23 deletions(-)

index de122e543f..ac3b04ff91 100644
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varcreate_lsn</structfield> <type>XLogRecPtr</type>
+      </para>
+      <para>
+       LSN of the transaction where the variable was created.
+       <structfield>varcreate_lsn</structfield> and
+       <structfield>oid</structfield> together form the all-time unique
+       identifier (<structfield>oid</structfield> alone is not enough, since
+       object identifiers can get reused).
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>varname</structfield> <type>name</type>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 7531de17fd..574540ca6f 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5317,6 +5317,38 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     VARIABLE</command> command.
    </para>
 
+   <para>
+    The value of a session variable is set with the SQL statement
+    <command>LET</command>.  The value of a session variable can be retrieved
+    with the SQL statement <command>SELECT</command>.
+<programlisting>
+CREATE VARIABLE var1 AS date;
+LET var1 = current_date;
+SELECT var1;
+</programlisting>
+
+    or
+
+<programlisting>
+CREATE VARIABLE public.current_user_id AS integer;
+GRANT READ ON VARIABLE public.current_user_id TO PUBLIC;
+LET current_user_id = (SELECT id FROM users WHERE usename = session_user);
+SELECT current_user_id;
+</programlisting>
+   </para>
+
+   <para>
+    The value of a session variable is local to the current session. Retrieving
+    a variable's value returns a <literal>NULL</literal>, unless its value has
+    been set to something else in the current session using the
+    <command>LET</command> command.  Session variables are not transactional:
+    any changes made to the value of a session variable in a transaction won't
+    be undone if the transaction is rolled back (just like variables in
+    procedural languages).  Session variables themselves are persistent, but
+    their values are neither persistent nor shared (like the content of
+    temporary tables).
+   </para>
+
    <para>
     Inside a query or an expression, a session variable can be
     <quote>shadowed</quote> by a column with the same name.  Similarly, the
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 15b2af99dc..578b97afee 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -432,6 +432,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>ALTER VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>ALTER VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
@@ -728,6 +736,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>CREATE VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>CREATE VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
@@ -1024,6 +1040,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>DROP VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>DROP VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
diff --git a/doc/src/sgml/parallel.sgml b/doc/src/sgml/parallel.sgml
index 590cb385dd..a5cef4cd79 100644
--- a/doc/src/sgml/parallel.sgml
+++ b/doc/src/sgml/parallel.sgml
@@ -521,6 +521,12 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
         Plan nodes that reference a correlated <literal>SubPlan</literal>.
       </para>
     </listitem>
+
+    <listitem>
+      <para>
+        Plan nodes that use a session variable.
+      </para>
+    </listitem>
   </itemizedlist>
 
  <sect2 id="parallel-labeling">
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index a675923867..c1196c7107 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -6034,6 +6034,20 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE;
 </programlisting>
     </para>
    </sect3>
+
+   <sect3 id="plpgsql-porting-package-variables">
+    <title><command>Packages and package variables</command></title>
+
+    <para>
+     The <application>PL/pgSQL</application> language has no packages, and
+     therefore no package variables or package constants.
+     You can consider translating an Oracle package into a schema in
+     <productname>PostgreSQL</productname>.  Package functions and procedures
+     would then become functions and procedures in that schema, and package
+     variables could be translated to session variables in that schema.
+     (see <xref linkend="ddl-session-variables"\>).
+    </para>
+   </sect3>
   </sect2>
 
index 2f67de3e21..cc3bd5ab54 100644
index d87570e7d8..d2036351e5 100644
index ec165ab96f..05bf67e341 100644
@@ -145,6 +145,7 @@ SELECT var1;
 
index 5bdb3560f0..67988b5fcd 100644
 
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 25578f3946..13e4adc5df 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -186,6 +186,7 @@
    &grant;
    &importForeignSchema;
    &insert;
+   &let;
    &listen;
    &load;
    &lock;
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index c9d686665d..148719e490 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1864,6 +1864,11 @@ find_expr_references_walker(Node *node,
 	{
 		Param	   *param = (Param *) node;
 
+		/* a variable parameter depends on the session variable */
+		if (param->paramkind == PARAM_VARIABLE)
+			add_object_address(VariableRelationId, param->paramvarid, 0,
+							   context->addrs);
+
 		/* A parameter must depend on the parameter's datatype */
 		add_object_address(TypeRelationId, param->paramtype, 0,
 						   context->addrs);
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 8752e1646e..2e128ef7ec 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -3399,6 +3399,323 @@ LookupVariable(const char *nspname,
 	return varoid;
 }
 
+/*
+/* -----
+ * IdentifyVariable - try to find a variable from a list of identifiers
+ * Returns the OID of the variable found, or InvalidOid.
+ * "names" is a list of up to four identifiers; possible meanings are:
+ * - variable  (searched on the search_path)
+ * - schema.variable
+ * - variable.attribute  (searched on the search_path)
+ * - schema.variable.attribute
+ * - database.schema.variable
+ * - database.schema.variable.attribute
+ * If there is more than one way to identify a variable, "not_unique" will be
+ * set to true.
+ * Unless "noerror" is true, an error is raised if there are more than four
+ *  identifiers in the list, or if the named database is not the current one.
+ * This is useful if we want to identify a shadowed variable.
+ * If an attribute is identified, it is stored in "attrname", otherwise the
+ * parameter is set to NULL.
+ * The identified session variable will be locked with an AccessShareLock.
+ * -----
index f65121440a..1c73181c3d 100644

index 48f7348f91..1cfaeca51e 100644
index 6dd00a4abd..ca621be5ec 100644
index 11118d0ce0..71248a34f2 100644
 
index 9b52bab52f..3fdd9cb630 100644
@@ -988,6 +989,47 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 						/*
index 4d7c92d63c..f2fb680def 100644
 
+	/*
index b511a429ad..b6662c6e8f 100644
index d2e2af4f81..07fe05deaf 100644
index 948afd9094..e5024f33a5 100644
 
 	/*
 
@@ -727,6 +729,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root,
 	 */
 	pull_up_subqueries(root);
 
+	/*
+	 * Check if some subquery uses a session variable.  The flag
+	 * hasSessionVariables should be true if the query or some subquery uses a
+	 * session variable.
+	 */
+	pull_up_has_session_variables(root);
+
 	/*
 	 * If this is a simple UNION ALL query, flatten it into an appendrel. We
 	 * do this now because it requires applying pull_up_subqueries to the leaf
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 7aed84584c..95cda4726c 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -209,6 +209,9 @@ static List *set_returning_clause_references(PlannerInfo *root,
 static List *set_windowagg_runcondition_references(PlannerInfo *root,
 												   List *runcondition,
 												   Plan *plan);
+static bool pull_up_has_session_variables_walker(Node *node,
+												 PlannerInfo *root);
+static void record_plan_variable_dependency(PlannerInfo *root, Oid varid);

 /*****************************************************************************
@@ -1308,6 +1311,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 	return plan;
 }
 
+/*
@@ -1956,8 +2003,9 @@ copyVar(Var *var)
@@ -2047,6 +2095,13 @@ fix_expr_common(PlannerInfo *root, Node *node)
 
 /*
@@ -2056,6 +2111,10 @@ fix_expr_common(PlannerInfo *root, Node *node)
+ * the list root->glob->sessionVariable.  Also, assign the parameter's
+ * "paramid" to the parameter's position in that list.
@@ -2074,6 +2133,41 @@ fix_param_node(PlannerInfo *root, Param *p)
 
@@ -2135,7 +2229,10 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
  * replacing Aggref nodes that should be replaced by initplan output Params,
  * choosing the best implementation for AlternativeSubPlans,
  * looking up operator opcode info for OpExpr and related nodes,
- * and adding OIDs from regclass Const nodes into root->glob->relationOids.
+ * adding OIDs from regclass Const nodes into root->glob->relationOids,
+ * assigning paramvarid to PARAM_VARIABLE params, and collecting the
+ * OIDs of session variables in the root->glob->sessionVariables list
+ * (paramvarid is the position of the session variable in this list).
  *
  * 'node': the expression to be modified
  * 'rtoffset': how much to increment varnos by
@@ -2157,7 +2254,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
 		root->multiexpr_params != NIL ||
 		root->glob->lastPHId != 0 ||
 		root->minmax_aggs != NIL ||
-		root->hasAlternativeSubPlans)
+		root->hasAlternativeSubPlans ||
+		root->hasSessionVariables)
 	{
 		return fix_scan_expr_mutator(node, &context);
 	}
@@ -3520,6 +3618,25 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
 	}
 }
 
+/*
+ * session variable in an expression list, or as the target of a LET statement.
@@ -3605,9 +3722,9 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 			}
 
 			/*
@@ -3630,6 +3747,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 
+		/* record dependency on the target variable of a LET command */
index 969e257f70..061ac2dc7c 100644
 
+	/* if the subquery had session variables, the main query does too */
index b4e085e9d4..0063b2b632 100644
 
+		/* we don't support passing session variables to workers */
 
 				/*
 
index 28fed9d87f..0630503694 100644

 
+		case T_LetStmt:
 
 			/*

 

 
 
 	foreach(l, stmt->lockingClause)
 	{
@@ -1679,12 +1695,235 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+	/* locks the variable with an AccessShareLock */
+	/* we need to postpone conversion of "unknown" to text */
+	/* the grammar should have produced a SELECT */
+	 * Generate an expression list for the LET that selects all the non-resjunk
+	 * columns from the subquery.
+		/* now we can read the type of the expression */
+	/* generate query's target list using the computed list of expressions */
+	/* this is top-level query */
+	 * Save target session variable ID.  This value is used multiple times: by
+	 * the query rewriter (for getting related defexpr), by planner (for
+	 * acquiring an AccessShareLock on target variable), and by the executor
+	 * (we need to know the target variable ID).
@@ -1930,6 +2169,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 
@@ -2404,6 +2644,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)

@@ -2471,6 +2712,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)

 
@@ -2658,9 +2900,15 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 	/*
 	 * Transform the target reference.  Typically we will get back a Param
 	 * node, but there's no reason to be too picky about its type.
+	 *
+	 * Session variables should not be used as target of a PL/pgSQL assign
+	 * statement.  So we should use a dedicated expression kind and disallow
+	 * session variables there.  The dedicated context allows to eliminate
+	 * undesirable warnings about the possibility of a target PL/pgSQL variable
+	 * shadowing a session variable.
 	 */
 	target = transformExpr(pstate, (Node *) cref,
-						   EXPR_KIND_UPDATE_TARGET);
+						   EXPR_KIND_ASSIGN_TARGET);
 	targettype = exprType(target);
 	targettypmod = exprTypmod(target);
 	targetcollation = exprCollation(target);
@@ -2702,6 +2950,10 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 	 */
 	type_id = exprType((Node *) tle->expr);
 
+	/*
+	 * For indirection processing and additional casts we can use expr_kind
+	 * EXPR_KIND_UPDATE_TARGET.
+	 */
 	pstate->p_expr_kind = EXPR_KIND_UPDATE_TARGET;
 
 	if (indirection)
@@ -2844,6 +3096,8 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 							   (LockingClause *) lfirst(l), false);
 	}

 	/* this must be done after collations, for reliable comparison of exprs */
@@ -3117,6 +3371,14 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 							 true,
 							 stmt->funccall->location);
 
+	/*
+	 * The arguments of CALL statement are evaluated by a direct expression
+	 * executor call.  This path is unsupported yet, so block it.  It will be
+	 * enabled later.
+	 */
+	if (pstate->p_hasSessionVariables)
+		elog(ERROR, "session variable cannot be used as an argument");
+
 	assign_expr_collations(pstate, node);
 
index 3f2c590ce3..5787402d38 100644

 
 
+ *				LET STATEMENT
index bee7d8346a..b494c885e9 100644

 			/*
index d2db69a3f9..b50ac801a9 100644
@@ -34,12 +35,14 @@
 
 /* GUC parameters */
@@ -108,6 +111,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,

 /*
@@ -501,6 +507,89 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	return result;
 }
 
+/*
+ * Returns true if the given expression kind is valid for session variables.
+ * The second usage of this function is to decide whether a "column does not
+ * exist" or a "column or variable does not exist" error message should be
+ * printed.  When we are in an expression where session variables cannot be
+ * used, we raise the first form of error message.
+		/* session variables allowed */
+		/* session variables not allowed */
@@ -577,6 +666,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
@@ -849,8 +940,61 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	}
 
 	/*
-	 * Throw error if no translation found.
+	 * There are contexts where session variables are not allowed.  We don't
+	 * need to identify session variables in such a context, but identifying
+	 * them allows us to raise meaningful error messages like "you cannot use
+	 * session variables here".
 	 */
+	if (expr_kind_allows_session_variables(pstate->p_expr_kind))
+	{
+		Oid			varid = InvalidOid;
+		char	   *attrname = NULL;
+		bool		not_unique;
+
+		/* -----
+		 * Session variables are shadowed by columns, routine variables or
+		 * routine arguments.  We certainly don't want to use a session variable
+		 * when it is exactly shadowed, but a RTE like this is conceivable:
+		 *
+		 * CREATE TYPE t AS (c int);
+		 * CREATE VARIABLE foo AS t;
+		 * CREATE TABLE foo(a int, b int);
+		 *
+		 * SELECT foo.a, foo.b, foo.c FROM foo;
+		 *
+		 * However, that is very confusing, so we disallow it.  We don't try to
+		 * identify a variable if we know that it would be shadowed.
+		 * -----
+		 */
+		if (!node && !(relname && crerr == CRERR_NO_COLUMN))
+		{
+			/* takes an AccessShareLock on the session variable */
+			varid = IdentifyVariable(cref->fields, &attrname, &not_unique, false);
+
+			if (OidIsValid(varid))
+			{
+				Oid			typid;
+				int32		typmod;
+				Oid			collid;
+
+				if (not_unique)
+					ereport(ERROR,
+							(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+							 errmsg("session variable reference \"%s\" is ambiguous",
+									NameListToString(cref->fields)),
+							 parser_errposition(pstate, cref->location)));
+
+				get_session_variable_type_typmod_collid(varid, &typid, &typmod,
+														&collid);
+
+				node = makeParamSessionVariable(pstate,
+												varid, typid, typmod, collid,
+												attrname, cref->location);
+			}
+		}
+	}
+
+	/* throw an error if no translation was found */
 	if (node == NULL)
 	{
 		switch (crerr)
@@ -882,6 +1026,72 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
@@ -1816,6 +2026,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
@@ -1859,6 +2070,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 
 			/*
@@ -3196,6 +3410,10 @@ ParseExprKindName(ParseExprKind exprKind)
 
 			/*
index 9b23344a3b..9aa4f60768 100644
 
index 96f80b3046..dac78ba5cc 100644

 
index 0c45fcf318..85b809cb07 100644
 
index ce8db7cda4..662dbcd1dd 100644
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index 653685bffc..a0edd5e049 100644

+/*
index 5af1a168ec..7e1e9e28f1 100644

 /*
 
 	/*
 	 * Recurse into sublink subqueries, too.  But we already did the ones in
-	 * the rtable and cteList.
+	 * the rtable and cteList.  We need to force a recursive call for session
+	 * variables too, to find and lock variables used in the query (see
+	 * ScanQueryWalker).
 	 */
-	if (parsetree->hasSubLinks)
+	if (parsetree->hasSubLinks ||
+		parsetree->hasSessionVariables)
 	{
 		query_tree_walker(parsetree, ScanQueryWalker,
 						  (void *) &acquire,
 						  QTW_IGNORE_RC_SUBQUERIES);
 	}
+
+	/* process session variables */
+	if (OidIsValid(parsetree->resultVariable))
+	{
+		if (acquire)
+			LockDatabaseObject(VariableRelationId, parsetree->resultVariable,
+							   0, AccessShareLock);
+		else
+			UnlockDatabaseObject(VariableRelationId, parsetree->resultVariable,
+								 0, AccessShareLock);
+	}
 }
 
 /*
 
 	/*
 
 /*
+ *		OPEROID, AMOPOPID, FOREIGNSERVEROID, FOREIGNDATAWRAPPEROID and
+ *		VARIABLEOID caches.
index e48a86be54..eaae0c5a93 100644
 
 	return false;
 }
index 873a83fa5a..a642926180 100644
@@ -4282,6 +4282,14 @@ psql_completion(const char *text, int start, int end)
 
+/* LET */
index 18ea6ac83a..def691dafb 100644

 extern Oid	get_collation_oid(List *collname, bool missing_ok);
 extern Oid	get_conversion_oid(List *conname, bool missing_ok);
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
index 3810e040f2..db593cd5be 100644
--- a/src/include/catalog/pg_variable.h
+++ b/src/include/catalog/pg_variable.h
@@ -35,6 +35,14 @@ CATALOG(pg_variable,9222,VariableRelationId)
 	/* OID of entry in pg_type for variable's type */
 	Oid			vartype BKI_LOOKUP(pg_type);
 
+	/*
+	 * Used for identity check [oid, create_lsn].
+	 *
+	 * This column of the 8-byte XlogRecPtr type should be at an address that
+	 * is divisible by 8, but before any column of type NameData.
+	 */
+	XLogRecPtr	varcreate_lsn;
+
 	/* variable name */
 	NameData	varname;
 
index 0a7274e26c..d1b2f59e0c 100644

index cac684d9b3..449050f7fe 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+	Oid			varid;
+	Oid			typid;
+	bool		isnull;
+	Datum		value;
+} SessionVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -661,6 +673,10 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
 
+	/* Session variables info: */
+	int			es_num_session_variables;		/* number of used variables */
+	SessionVariableValue *es_session_variables;	/* array of copies of values */
+
 	QueryEnvironment *es_queryEnv;	/* query environment */
 
index 80ea5928ee..74bf5af776 100644

 
index 14ccfc1ac1..c16385512f 100644

 
 	/*
index 1aeeaec95e..6b9b9904fd 100644
 
 	NodeTag		type;

 

index ea47652adb..5f7b83d13f 100644
+ *		PARAM_VARIABLE:  The parameter is a reference to a session variable
+ *				(paramid holds the variable's OID).
 
 typedef struct Param
index aafc173792..de471692e8 100644
 
index c884cd5c46..2e88d7d950 100644
index 5b781d87a9..6d93691965 100644

 
index 9465df7b2f..a921af2486 100644
index a3d521b6f9..38cc87f4e7 100644
+	DestVariable,				/* results sent to session variable */
 } CommandDest;
 
 /* ----------------
index 239b3250a9..a9e1e323ad 100644
 
 	/*
index aca766042d..287628ede6 100644
+-- access from cached plans should work
+-- dependency (plan invalidation) should work
+-- usage LET statement in plpgsql should work
+-- the expression should remain "unknown"
+--It should work in plpgsql too
+-- we should see table
+-- we should see table
+-- we should see table
index 42dfb95a02..db5ba176db 100644
 
+-- access from cached plans should work
+-- dependency (plan invalidation) should work
+-- usage LET statement in plpgsql should work
+-- the expression should remain "unknown"
+--It should work in plpgsql too
+-- we should see table
+-- we should see table
+-- we should see table
index 837e5b83a3..bc3dbd7260 100644

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 30 Jul 2024 22:56:19 +0200

Hi

> Did you put that there as defensive coding?

No, I just used a pattern for relations. I removed the test against
PG_CATALOG_NAMESPACE and wrote comment

<-->/*
<--> * Quick check: if it ain't in the path at all, it ain't visible. We
<--> * don't expect usage of session variables in the system namespace.
<--> */

> That function should get documented in
> functions-info.html#FUNCTIONS-INFO-SCHEMA-TABLE

merged

> I left the note in CREATE VARIABLE, but it is now just a link to ddl.sgml.

merged

> I removed the first one; I hope that's OK.

sure

> That's a weird test - why the pipe?  Is there something I don't get?

This test is not related to pipe, but usage of invalid multipart names

These tests look weird, but when you look at the psql.out file, then it is
consistent with others.

> > +cross-database references are not implemented:
> "no.such.database"."no.such.schema"."no.such.variable"
>

It is crash psql parser test - these tests are designed like \dt or \di

> in a week, and fall promises to be busy.  So it might be a while.

I am sending patches with merged your changes (related to first patch)

Regards

Pavel

> Laurenz Albe
From b5d54f2b0b39b979084d0f2f62e3c95f925812ec Mon Sep 17 00:00:00 2001

index a765307bfe6..b0beead68f0 100644
 
+/*

 

 

 select a from atacc1;

 

From 8dad04185e3e1a5fc3c552b79b1d8161369d230c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

index bda7df93caa..31e8731ee71 100644
 
+     <row>
index 00938743c0d..b73f032ddbb 100644
 
  <refsynopsisdiv>
 <synopsis>
@@ -47,6 +47,14 @@ CREATE [ { TEMPORARY | TEMP } ] [ IMMUTABLE ] VARIABLE [ IF NOT EXISTS ] <replac
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*
index 2c27dad8d13..a12b0890f48 100644

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:
+			TRANSACTION								{ $$ = true; }
+			| TRANSACTIONAL							{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
@@ -18045,6 +18053,7 @@ unreserved_keyword:
 			| TEXT_P
 			| TIES
 			| TRANSACTION
+			| TRANSACTIONAL
 			| TRANSFORM
 			| TRIGGER
 			| TRUNCATE
@@ -18695,6 +18704,7 @@ bare_label_keyword:
 			| TIMESTAMP
 			| TRAILING
 			| TRANSACTION
+			| TRANSACTIONAL
 			| TRANSFORM
 			| TREAT
 			| TRIGGER
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4c22ce9553b..d9fc9b2d5c8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5336,6 +5336,7 @@ getVariables(Archive *fout)
 	int			i_varcollation;
 	int			i_varnotnull;
 	int			i_varisimmutable;
+	int			i_varistransact;
 	int			i_varacl;
 	int			i_acldefault;
 	int			i,
@@ -5360,6 +5361,7 @@ getVariables(Archive *fout)
 					  "       END AS varcollation,\n"
 					  "       v.varnotnull,\n"
 					  "       v.varisimmutable,\n"
+					  "       v.varistransact,\n"
 					  "       pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n"
 					  "       v.varowner, v.varacl,\n"
 					  "       acldefault('V', v.varowner) AS acldefault\n"
@@ -5382,6 +5384,7 @@ getVariables(Archive *fout)
 	i_varcollation = PQfnumber(res, "varcollation");
 	i_varnotnull = PQfnumber(res, "varnotnull");
 	i_varisimmutable = PQfnumber(res, "varisimmutable");
+	i_varistransact = PQfnumber(res, "varistransact");

@@ -5457,6 +5461,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5474,12 +5479,13 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 
 
 /*
index 9ebfd6dba72..869e09fb3e5 100644

 
index 4d6a943712f..02c641acd1f 100644
@@ -3596,8 +3598,11 @@ psql_completion(const char *text, int start, int end)
index 58b573fae49..d3fe8cf6550 100644

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+

From d13bb18637cdecdf8822d6e80be9cc64ee5a3225 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*
index a1125dc627b..2c27dad8d13 100644

 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index b59afd79202..bf91e6047ce 100644

From f26d645a023aa542721afe569af05c9a2f19e2ff Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From 751f463f049e26b3dfaa295fe76bd9e3199b5e98 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From 34e3730a351189f230f8a824c23a2aeaa4f8125c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

 
 	/*

 

From 630463e904db5f3190c3eef6416e8cdbf72238ba Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 3fdd9cb6303..8665e6b2b49 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1002,31 +1002,69 @@ ExecInitExprRec(Expr *node, ExprState *state,
 								es_num_session_variables = state->parent->state->es_num_session_variables;
 							}

index 1633ea83731..7a793b57d40 100644

 
-	/*
 
index d983a9a7fed..bd875e544f0 100644

 
@@ -802,6 +810,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
 
 	/*

+LET v = 1;
+

From 7ff02a41c10da225a282e612d0416319c3d09525 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

index 385bf3a5f4c..7bf7233b2e9 100644
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vardefexpr</structfield> <type>pg_node_tree</type>
+      </para>
+      <para>
+       The internal representation of the variable default value
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index b8ec02a85ed..18955bf289c 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5352,13 +5352,12 @@ SELECT current_user_id;
 
    <para>
 
    <para>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index e1c2940d4ca..8187c18e28b 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -27,7 +27,7 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
-    [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
+    [ DEFAULT <replaceable class="parameter">default_expr</replaceable> ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
 </synopsis>
  </refsynopsisdiv>
  <refsect1>
@@ -41,10 +41,10 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="p
 
   <para>
    The value of a session variable is local to the current session.  Retrieving
-   a session variable's value returns NULL, unless its value is set to
-   something else in the current session with a <command>LET</command> command.
-   The content of a session variable is not transactional. This is the same as
-   regular variables in procedural languages.
+   a session variable's value returns NULL or a default value, unless its value
+   is set to something else in the current session with a <command>LET</command>
+   command.  The content of a session variable is not transactional.  This is the
+   same as regular variables in procedural languages.
   </para>
 
   <para>
@@ -105,6 +105,17 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="p
     </listitem>
    </varlistentry>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{
 
index 656aed604c9..78359bc1afc 100644

 CreateSessionVarStmt:

 			if (isAgg)
index 48dfa3734f8..a765307bfe6 100644
 
@@ -2079,6 +2081,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("cannot use subquery in DEFAULT expression");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
@@ -3429,6 +3432,7 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "CHECK";
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			return "DEFAULT";
 		case EXPR_KIND_INDEX_EXPRESSION:
 			return "index expression";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 9aa4f60768b..fcac694455d 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2620,6 +2620,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("set-returning functions are not allowed in DEFAULT expressions");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a796b8c557b..6a71a7b0571 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5330,6 +5330,7 @@ getVariables(Archive *fout)
 	int			i_varnamespace;
 	int			i_vartype;
 	int			i_vartypname;
+	int			i_vardefexpr;
 	int			i_varxactendaction;
 	int			i_varowner;
 	int			i_varcollation;
@@ -5355,6 +5356,7 @@ getVariables(Archive *fout)
 					  "            THEN v.varcollation\n"
 					  "            ELSE 0\n"
 					  "       END AS varcollation,\n"
+					  "       pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n"
 					  "       v.varowner, v.varacl,\n"
 					  "       acldefault('V', v.varowner) AS acldefault\n"
 					  "FROM pg_catalog.pg_variable v\n"
@@ -5371,6 +5373,7 @@ getVariables(Archive *fout)
 	i_varnamespace = PQfnumber(res, "varnamespace");
 	i_vartype = PQfnumber(res, "vartype");
 	i_vartypname = PQfnumber(res, "vartypname");
+	i_vardefexpr = PQfnumber(res, "vardefexpr");
 	i_varxactendaction = PQfnumber(res, "varxactendaction");
 	i_varcollation = PQfnumber(res, "varcollation");
 
@@ -5406,6 +5409,11 @@ getVariables(Archive *fout)
 		varinfo[i].dacl.initprivs = NULL;
 		varinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_varowner));
 
@@ -5438,6 +5446,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5450,6 +5459,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

@@ -5469,6 +5479,10 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (vardefexpr)
 
index 08ea791420a..04fe9843ae6 100644

 
 	if (verbose)
index f3531bd45d4..3b9667b247b 100644

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From d64fc37c558eee8bb543ced8e86fb7cdb8e70916 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

index 5e25d37b709..385bf3a5f4c 100644
 
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index 642346186f0..e1c2940d4ca 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -27,7 +27,7 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
-    [ ON COMMIT DROP ]
+    [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
 </synopsis>
  </refsynopsisdiv>
  <refsect1>
@@ -117,6 +117,17 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="p
     </listitem>
    </varlistentry>

 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;
index 172ee1f45d1..656aed604c9 100644
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d8c74f4e2e4..a796b8c557b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5330,6 +5330,7 @@ getVariables(Archive *fout)
 	int			i_varnamespace;
 	int			i_vartype;
 	int			i_vartypname;
+	int			i_varxactendaction;
 	int			i_varowner;
 	int			i_varcollation;
 	int			i_varacl;
@@ -5347,6 +5348,7 @@ getVariables(Archive *fout)
 	/* Get the variables in current database. */
 	appendPQExpBuffer(query,
 					  "SELECT v.tableoid, v.oid, v.varname,\n"
+					  "       v.varxactendaction,\n"
 					  "       v.varnamespace, v.vartype,\n"
 					  "       pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n"
 					  "       CASE WHEN v.varcollation <> t.typcollation "
@@ -5369,6 +5371,7 @@ getVariables(Archive *fout)
 	i_varnamespace = PQfnumber(res, "varnamespace");
 	i_vartype = PQfnumber(res, "vartype");
 	i_vartypname = PQfnumber(res, "vartypname");
+	i_varxactendaction = PQfnumber(res, "varxactendaction");
 	i_varcollation = PQfnumber(res, "varcollation");

 
 		varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl));
@@ -5432,6 +5438,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	PQExpBuffer query;
 	char	   *qualvarname;
 	const char *vartypname;
+	const char *varxactendaction;
 	Oid			varcollation;
 
 	/* Skip if not to be dumped */
@@ -5443,6 +5450,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
@@ -5461,6 +5469,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}

index 0d3765eed4f..08ea791420a 100644
 
index 3a4a979d349..f3531bd45d4 100644
 
 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From ee595613958ff958f8b7f6b24afb9b0bdad157ac Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 16 files changed, 524 insertions(+), 49 deletions(-)

index 7bf7233b2e9..bda7df93caa 100644
 
      <row>
index 8187c18e28b..00938743c0d 100644
 
  <refsynopsisdiv>
 <synopsis>
@@ -65,6 +65,22 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="p
 
   <variablelist>
 
@@ -105,6 +121,18 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="p
     </listitem>
    </varlistentry>

 
 	if (varDefexpr)

 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
index 78359bc1afc..a1125dc627b 100644

 /*
 
 CreateSessionVarStmt:

+OptNotNull: NOT NULL_P								{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
+OptImmutable: IMMUTABLE								{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 6a71a7b0571..4c22ce9553b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5334,6 +5334,8 @@ getVariables(Archive *fout)
 	int			i_varxactendaction;
 	int			i_varowner;
 	int			i_varcollation;
+	int			i_varnotnull;
+	int			i_varisimmutable;
 	int			i_varacl;
 	int			i_acldefault;
 	int			i,
@@ -5356,6 +5358,8 @@ getVariables(Archive *fout)
 					  "            THEN v.varcollation\n"
 					  "            ELSE 0\n"
 					  "       END AS varcollation,\n"
+					  "       v.varnotnull,\n"
+					  "       v.varisimmutable,\n"
 					  "       pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n"
 					  "       v.varowner, v.varacl,\n"
 					  "       acldefault('V', v.varowner) AS acldefault\n"
@@ -5376,6 +5380,8 @@ getVariables(Archive *fout)
 	i_vardefexpr = PQfnumber(res, "vardefexpr");
 	i_varxactendaction = PQfnumber(res, "varxactendaction");
 	i_varcollation = PQfnumber(res, "varcollation");
+	i_varnotnull = PQfnumber(res, "varnotnull");
+	i_varisimmutable = PQfnumber(res, "varisimmutable");

 
@@ -5448,7 +5456,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5462,12 +5472,14 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 
 	if (OidIsValid(varcollation))
 	{
@@ -5479,6 +5491,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (varnotnull)
 
 /*
index 04fe9843ae6..9ebfd6dba72 100644

 
index ec54abb9a73..4d6a943712f 100644
@@ -3277,7 +3279,8 @@ psql_completion(const char *text, int start, int end)
+		COMPLETE_WITH("IMMUTABLE VARIABLE", "SEQUENCE", "TABLE", "VARIABLE",
+					  "VIEW");
@@ -3594,7 +3597,8 @@ psql_completion(const char *text, int start, int end)
@@ -4242,6 +4246,10 @@ psql_completion(const char *text, int start, int end)
 
index 3b9667b247b..58b573fae49 100644
 
+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From fc0336e0b3230e3229018bdaa26962bf1440211a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

index 189ac43a88a..77dff90f6b3 100644

index 0f2ffad3049..da764033dba 100644
@@ -4282,9 +4282,9 @@ psql_completion(const char *text, int start, int end)
 
-/* LET */

From 4d9102eb1b51d1ede595fb165782c3b8e6ef2e93 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 src/bin/psql/tab-complete.c                   |   5 +-
 23 files changed, 521 insertions(+), 65 deletions(-)

index 3e5b702b016..5e25d37b709 100644
 
+     <row>
index 65a03a98e1a..b8ec02a85ed 100644
@@ -5356,8 +5356,9 @@ SELECT current_user_id;
 
    <para>
index 05bf67e3411..642346186f0 100644
 
  <refsynopsisdiv>
 <synopsis>
-CREATE VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
+CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
+    [ ON COMMIT DROP ]
 </synopsis>
  </refsynopsisdiv>
  <refsect1>
@@ -104,6 +105,18 @@ CREATE VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceab
     </listitem>
    </varlistentry>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
 
 		/*
index 77dff90f6b3..172ee1f45d1 100644

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{
index da764033dba..ec54abb9a73 100644
@@ -3277,7 +3277,7 @@ psql_completion(const char *text, int start, int end)
@@ -3593,7 +3593,8 @@ psql_completion(const char *text, int start, int end)
index cf06e139ac8..3a4a979d349 100644

 
  *		the format of the pg_variable relation.

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index bc3dbd72604..b59afd79202 100644

From d1e9886e0551b765848c35ceab0aef037aa3031f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

index f9fe2892295..189ac43a88a 100644

 
 DROP TABLE public.xxtab;

From f560f06e5e43bebeacca238bfd9c82a75c42a008 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

index 57cd7bb9727..722c987e485 100644
 
index 595eeba4c3b..65a03a98e1a 100644
@@ -5364,7 +5364,11 @@ SELECT current_user_id;
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f6b2e19a0e2..48dfa3734f8 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -47,6 +47,7 @@
 
 /* GUC parameters */
 
index 6a623f5f342..a105e984fca 100644
@@ -1535,6 +1535,15 @@ struct config_bool ConfigureNamesBool[] =
 
 /* GUC parameters */

 

 

From d2c4c55306623b710162c78c1e98eba1c6ec5fd9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From 69c536fcbbeb7b4ad762abc31c1cd900ffe5274b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 

 
 
+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

 	if (sessionvars)

 -- should be zero again

From 44bcb9e5dddd053feb373533f4454495ed590aaa Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*
index 837d8dfae19..b66c3f34340 100644
@@ -12238,4 +12238,12 @@

From 0c0d3d34913e8b5e3510c966d57e672f8e73fcc4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*
index 36fe4c1ec82..f9fe2892295 100644

index b4df037965e..0f2ffad3049 100644
@@ -3734,7 +3734,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+

From e740b07f44de4b5e1508df046ab8e8b903d8af57 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

index de122e543f7..3e5b702b016 100644
 
+     <row>
index 7531de17fd3..595eeba4c3b 100644
@@ -5327,6 +5327,45 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     the schema name, columns can use table aliases, routine variables can use
     block labels, and routine arguments can use the routine name.
 
  <sect1 id="ddl-others">
 
index ec165ab96fc..05bf67e3411 100644

index c9d686665de..0556189f97a 100644
 
index c4642b75bbe..df1e8794336 100644
@@ -3397,6 +3397,332 @@ LookupVariable(const char *nspname,
 	return varoid;
 }
 
+/*

 
index 9b52bab52fc..3fdd9cb6303 100644
 
 						/*
 
+	/*
 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*
 
index 3f2c590ce32..36fe4c1ec82 100644

 

 
 			/*
index d2db69a3f90..f6b2e19a0e2 100644
 
 /* GUC parameters */
 
-

 /*
 
+/*

+	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
index 7cc1fc57188..b4df037965e 100644
 
+/* LET */
index 18ea6ac83a5..def691dafb3 100644

index 3810e040f28..cf06e139ac8 100644

 

+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */

 

 
 
 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*
 
index 837e5b83a3f..bc3dbd72604 100644

From 5bb1aa7850412a9a58d2a03a526be84cdd548ea6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 src/backend/catalog/namespace.c               | 133 +++++++++
 src/bin/psql/tab-complete.c                   |  42 ++-
 66 files changed, 2387 insertions(+), 26 deletions(-)

index b654fae1b2f..de122e543f7 100644
 
index 626d35514cc..7531de17fd3 100644

index b39f97dc8de..c3c4fbb3d9b 100644
index 405fe6dc8b9..d5f4bcc6e08 100644
 
index 89aacec4fab..041c78d432f 100644

 
   <para>
 
index 00000000000..ec165ab96fc

 

+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:
index 0489cbabcb8..c9d686665de 100644
 
index 43b707699d7..c4642b75bbe 100644
@@ -971,6 +972,67 @@ RelationIsVisibleExt(Oid relid, bool *is_missing)
 	return visible;
 }
 
+/*
+ * VariableIsVisible
+	 * Quick check: if it ain't in the path at all, it ain't visible. We
+	 * don't expect usage of session variables in the system namespace.
+	if (!list_member_oid(activeSearchPath, varnamespace))
 
 /*
  * TypenameGetTypid
@@ -3274,6 +3336,66 @@ TSConfigIsVisibleExt(Oid cfgid, bool *is_missing)
 	return visible;
 }
 
+/*
 
 /*
  * DeconstructQualifiedName
@@ -5075,3 +5197,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*
index 0b2a52463f1..f3564e60173 100644
 
index a043fd4c669..3f2c590ce32 100644

 

 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

index 48a280d089b..2eed9489f9d 100644
 
 	return subname;
 }
+

 
 
 	int			useDB;
index 8c20c263c4b..6f012a1b558 100644
index 2b02148559b..d8c74f4e2e4 100644
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */
index 5bcc2244d58..0d3765eed4f 100644

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 024469474da..7cc1fc57188 100644

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 
index 8d434d48d57..18ea6ac83a5 100644

 
 
index 06b2f4ba66c..837d8dfae19 100644
 
index 00000000000..3810e040f28

+

index 74d9ff2998d..c336f67a596 100644
 
+\dV "no.such.variable"
+            List of variables

 
 # ----------
index 8d74ed7122c..6c18b7f840a 100644

 

 
 -- check \drg and \du
index 3deb6113b80..837e5b83a3f 100644

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 31 Jul 2024 08:41:50 +0200

> I just add it again to keep the cfbot happy.

Probably you didn't attach new files - the second patch is not complete. Or
you didn't make changes there?

Regards

Pavel

---

From: Laurenz Albe <[email-masked]>
Date: Wed, 31 Jul 2024 08:57:31 +0200

On Wed, 2024-07-31 at 08:41 +0200, Pavel Stehule wrote:
> Probably you didn't attach new files - the second patch is not complete. Or you didn't make changes there?

Hm.  What is missing?

Yours,
Laurenz Albe

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 31 Jul 2024 09:04:08 +0200

> Or you didn't make changes there?

let.sgml,
session_variable.c
svariable_receiver.c
session_variable.h
...

Regards

Pavel

---

From: Laurenz Albe <[email-masked]>
Date: Wed, 31 Jul 2024 11:45:00 +0200

On Wed, 2024-07-31 at 09:04 +0200, Pavel Stehule wrote:
> st 31. 7. 2024 v 8:57 odesílatel Laurenz Albe <[email-masked]> napsal:

Argh, I forgit the new files, sorry.

The attached patches should be complete.

Yours,
Laurenz Albe
From 4705db0de3f9f5031fa8238908421d217a77e291 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Tue, 30 Jul 2024 21:39:12 +0200
Subject: [PATCH v20240731 1/2] Enhancing catalog for support session variables
 and related support

 

 

 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 

 
 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du

From 5bb113483c85f5ebfc8f244288e0b0e9da3caf2b Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 31 Jul 2024 11:42:39 +0200
Subject: [PATCH v20240731 2/2] Storage for session variables and SQL interface

 doc/src/sgml/ref/let.sgml                     |  96 ++
 src/backend/commands/session_variable.c       | 574 +++++++++++
 58 files changed, 3899 insertions(+), 23 deletions(-)

+     <row>
 
+   <para>

 
 
index 0000000000..f6640576be
@@ -0,0 +1,96 @@
+      An arbitrary SQL expression.  The result must be of a data type that can
+      be cast to the type of the session variable in an assignment.

+/*

 	values[Anum_pg_variable_oid - 1] = ObjectIdGetDatum(varid);
+	values[Anum_pg_variable_varcreate_lsn - 1] = LSNGetDatum(GetXLogInsertRecPtr());
 	values[Anum_pg_variable_varname - 1] = NameGetDatum(&varname);
 	values[Anum_pg_variable_varnamespace - 1] = ObjectIdGetDatum(varNamespace);
 	values[Anum_pg_variable_vartype - 1] = ObjectIdGetDatum(varType);
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91..1cfaeca51e 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -50,6 +50,7 @@ OBJS = \
 	schemacmds.o \
 	seclabel.o \
 	sequence.o \
+	session_variable.o \
 	statscmds.o \
 	subscriptioncmds.o \
 	tablecmds.o \
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 6dd00a4abd..ca621be5ec 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -38,6 +38,7 @@ backend_sources += files(
   'schemacmds.c',
   'seclabel.c',
   'sequence.c',
+  'session_variable.c',
   'statscmds.c',
   'subscriptioncmds.c',
   'tablecmds.c',
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
new file mode 100644
index 0000000000..23e7fa99f7
--- /dev/null
+++ b/src/backend/commands/session_variable.c
@@ -0,0 +1,574 @@
+/*-------------------------------------------------------------------------
+ *
+ * session_variable.c
+ *	  session variable creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/session_variable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
+#include "executor/svariableReceiver.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "rewrite/rewriteHandler.h"
+#include "storage/lmgr.h"
+#include "storage/proc.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+/*
+ * The values of session variables are stored in the backend's private memory
+ * in the dedicated memory context SVariableMemoryContext in binary format.
+ * They are stored in the "sessionvars" hash table, whose key is the OID of the
+ * variable.  However, the OID is not good enough to identify a session
+ * variable: concurrent sessions could drop the session variable and create a
+ * new one, which could be assigned the same OID.  To ensure that the values
+ * stored in memory and the catalog definition match, we also keep track of
+ * the "create_lsn".  Before any access to the variable values, we need to
+ * check if the LSN stored in memory matches the LSN in the catalog.  If there
+ * is a mismatch between the LSNs, or if the OID is not present in pg_variable
+ * at all, the value stored in memory is released.
+ */
+typedef struct SVariableData
+{
+	Oid			varid;			/* pg_variable OID of the variable (hash key) */
+	XLogRecPtr	create_lsn;
+
+	bool		isnull;
+	Datum		value;
+
+	Oid			typid;
+	int16		typlen;
+	bool		typbyval;
+
+	bool		is_domain;
+
+	/*
+	 * domain_check_extra holds cached domain metadata.  This "extra" is
+	 * usually stored in fn_mcxt.  We do not have access to that memory context
+	 * for session variables, but we can use TopTransactionContext instead.
+	 * A fresh value is forced when we detect we are in a different transaction
+	 * (the local transaction ID differs from domain_check_extra_lxid).
+	 */
+	void	   *domain_check_extra;
+	LocalTransactionId domain_check_extra_lxid;
+
+	/*
+	 * Stored value and type description can be outdated when we receive a
+	 * sinval message.  We then have to check if the stored data are still
+	 * trustworthy.
+	 */
+	bool		is_valid;
+
+	uint32		hashvalue;		/* used for pairing sinval message */
+} SVariableData;
+
+typedef SVariableData *SVariable;
+
+static HTAB *sessionvars = NULL;	/* hash table for session variables */
+
+static MemoryContext SVariableMemoryContext = NULL;
+
+/*
+ * Callback function for session variable invalidation.
+ */
+static void
+pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
+{
+	HASH_SEQ_STATUS status;
+	SVariable	svar;
+
+	elog(DEBUG1, "pg_variable_cache_callback %u %u", cacheid, hashvalue);
+
+	Assert(sessionvars);
+
+	/*
+	 * If the hashvalue is not specified, we have to recheck all currently
+	 * used session variables.  Since we can't tell the exact session variable
+	 * from its hashvalue, we have to iterate over all items in the hash bucket.
+	 */
+	hash_seq_init(&status, sessionvars);
+
+	while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+	{
+		if (hashvalue == 0 || svar->hashvalue == hashvalue)
+		{
+			svar->is_valid = false;
+		}
+	}
+}
+
+/*
+ * Release stored value, free memory
+ */
+static void
+free_session_variable_value(SVariable svar)
+{
+	/* clean the current value */
+	if (!svar->isnull)
+	{
+		if (!svar->typbyval)
+			pfree(DatumGetPointer(svar->value));
+
+		svar->isnull = true;
+	}
+
+	svar->value = (Datum) 0;
+}
+
+/*
+ * Returns true when the entry in pg_variable is consistent with the given
+ * session variable.
+ */
+static bool
+is_session_variable_valid(SVariable svar)
+{
+	HeapTuple	tp;
+	bool		result = false;
+
+	Assert(OidIsValid(svar->varid));
+
+	tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+	if (HeapTupleIsValid(tp))
+	{
+		/*
+		 * The OID alone is not enough as an unique identifier, because OID
+		 * values get recycled, and a new session variable could have got
+		 * the same OID.  We do a second check against the 64-bit LSN when
+		 * the variable was created.
+		 */
+		if (svar->create_lsn == ((Form_pg_variable) GETSTRUCT(tp))->varcreate_lsn)
+			result = true;
+
+		ReleaseSysCache(tp);
+	}
+
+	return result;
+}
+
+/*
+ * Initialize attributes cached in "svar"
+ */
+static void
+setup_session_variable(SVariable svar, Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+
+	Assert(OidIsValid(varid));
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	svar->varid = varid;
+	svar->create_lsn = varform->varcreate_lsn;
+
+	svar->typid = varform->vartype;
+
+	get_typlenbyval(svar->typid, &svar->typlen, &svar->typbyval);
+
+	svar->is_domain = (get_typtype(varform->vartype) == TYPTYPE_DOMAIN);
+	svar->domain_check_extra = NULL;
+	svar->domain_check_extra_lxid = InvalidLocalTransactionId;
+
+	svar->isnull = true;
+	svar->value = (Datum) 0;
+
+	svar->is_valid = true;
+
+	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
+											ObjectIdGetDatum(varid));
+
+	ReleaseSysCache(tup);
+}
+
+/*
+ * Assign a new value to the session variable.  It is copied to
+ * SVariableMemoryContext if necessary.
+ *
+ * If any error happens, the existing value won't be modified.
+ */
+static void
+set_session_variable(SVariable svar, Datum value, bool isnull)
+{
+	Datum		newval;
+	SVariableData locsvar,
+			   *_svar;
+
+	Assert(svar);
+	Assert(!isnull || value == (Datum) 0);
+
+	/*
+	 * Use typbyval, typbylen from session variable only when they are
+	 * trustworthy (the invalidation message was not accepted for this
+	 * variable).  If the variable might be invalid, force setup.
+	 *
+	 * Do not overwrite the passed session variable until we can be certain
+	 * that no error can be thrown.
+	 */
+	if (!svar->is_valid)
+	{
+		setup_session_variable(&locsvar, svar->varid);
+		_svar = &locsvar;
+	}
+	else
+		_svar = svar;
+
+	if (!isnull)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+
+		newval = datumCopy(value, _svar->typbyval, _svar->typlen);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else
+		newval = value;
+
+	free_session_variable_value(svar);
+
+	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new value",
+		 get_namespace_name(get_session_variable_namespace(svar->varid)),
+		 get_session_variable_name(svar->varid),
+		 svar->varid);
+
+	/* no more error expected, so we can overwrite the old variable now */
+	if (svar != _svar)
+		memcpy(svar, _svar, sizeof(SVariableData));
+
+	svar->value = newval;
+	svar->isnull = isnull;
+}
+
+/*
+ * Create the hash table for storing session variables.
+ */
+static void
+create_sessionvars_hashtables(void)
+{
+	HASHCTL		vars_ctl;
+
+	Assert(!sessionvars);
+
+	if (!SVariableMemoryContext)
+	{
+		/* read sinval messages */
+		CacheRegisterSyscacheCallback(VARIABLEOID,
+									  pg_variable_cache_callback,
+									  (Datum) 0);
+
+		/* we need our own long-lived memory context */
+		SVariableMemoryContext =
+			AllocSetContextCreate(TopMemoryContext,
+								  "session variables",
+								  ALLOCSET_START_SMALL_SIZES);
+	}
+
+	memset(&vars_ctl, 0, sizeof(vars_ctl));
+	vars_ctl.keysize = sizeof(Oid);
+	vars_ctl.entrysize = sizeof(SVariableData);
+	vars_ctl.hcxt = SVariableMemoryContext;
+
+	sessionvars = hash_create("Session variables", 64, &vars_ctl,
+							  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+/*
+ * Search a session variable in the hash table given its OID.  If it
+ * doesn't exist, then insert it there.
+ *
+ * The caller is responsible for doing permission checks.
+ *
+ * As a side effect, this function acquires a AccessShareLock on the
+ * session variable until the end of the transaction.
+ */
+static SVariable
+get_session_variable(Oid varid)
+{
+	SVariable	svar;
+	bool		found;
+
+	/* protect the used session variable against DROP */
+	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	if (!sessionvars)
+		create_sessionvars_hashtables();
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+								   HASH_ENTER, &found);
+
+	if (found)
+	{
+		if (!svar->is_valid)
+		{
+			/*
+			 * If there was an invalidation message, the variable might still be
+			 * valid, but we have to check with the system catalog.
+			 */
+			if (is_session_variable_valid(svar))
+				svar->is_valid = true;
+			else
+				/* if the value cannot be validated, we have to discard it */
+				free_session_variable_value(svar);
+		}
+	}
+	else
+		svar->is_valid = false;
+
+	/*
+	 * Force setup for not yet initialized variables or variables that cannot
+	 * be validated.
+	 */
+	if (!svar->is_valid)
+	{
+		setup_session_variable(svar, varid);
+
+		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by READ)",
+			 get_namespace_name(get_session_variable_namespace(varid)),
+			 get_session_variable_name(varid),
+			 varid);
+	}
+
+	/* ensure the returned data is still of the correct domain */
+	if (svar->is_domain)
+	{
+		/*
+		 * Store "extra" for domain_check() in TopTransactionContext.  When we
+		 * are in a new transaction, domain_check_extra cache is not valid any
+		 * more.
+		 */
+		if (svar->domain_check_extra_lxid != MyProc->vxid.lxid)
+			svar->domain_check_extra = NULL;
+
+		domain_check(svar->value, svar->isnull,
+					 svar->typid, &svar->domain_check_extra,
+					 TopTransactionContext);
+
+		svar->domain_check_extra_lxid = MyProc->vxid.lxid;
+	}
+
+	return svar;
+}
+
+/*
+ * Store the given value in a session variable in the cache.
+ *
+ * The caller is responsible for doing permission checks.
+ *
+ * As a side effect, this function acquires a AccessShareLock on the session
+ * variable until the end of the transaction.
+ */
+void
+SetSessionVariable(Oid varid, Datum value, bool isNull)
+{
+	SVariable	svar;
+	bool		found;
+
+	/* protect used session variable against DROP */
+	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	if (!sessionvars)
+		create_sessionvars_hashtables();
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+								   HASH_ENTER, &found);
+
+	if (!found)
+	{
+		setup_session_variable(svar, varid);
+
+		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by WRITE)",
+			 get_namespace_name(get_session_variable_namespace(svar->varid)),
+			 get_session_variable_name(svar->varid),
+			 varid);
+	}
+
+	/* if this fails, it won't change the stored value */
+	set_session_variable(svar, value, isNull);
+}
+
+/*
+ * Wrapper around SetSessionVariable with permission checks.
+ */
+void
+SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull)
+{
+	AclResult	aclresult;
+
+	/* is the caller allowed to update the session variable? */
+	aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));
+
+	SetSessionVariable(varid, value, isNull);
+}
+
+/*
+ * Returns a copy of the value stored in a variable.
+ */
+static inline Datum
+copy_session_variable_value(SVariable svar, bool *isNull)
+{
+	Datum		value;
+
+	/* force copy of non NULL value */
+	if (!svar->isnull)
+	{
+		value = datumCopy(svar->value, svar->typbyval, svar->typlen);
+		*isNull = false;
+	}
+	else
+	{
+		value = (Datum) 0;
+		*isNull = true;
+	}
+
+	return value;
+}
+
+/*
+ * Returns a copy of the value of the session variable (in the current memory
+ * context).  The caller is responsible for permission checks.
+ */
+Datum
+GetSessionVariable(Oid varid, bool *isNull, Oid *typid)
+{
+	SVariable	svar;
+
+	svar = get_session_variable(varid);
+
+	/*
+	 * Although svar is freshly validated in this point, the svar->is_valid can
+	 * be false, due possible accepting invalidation message inside domain
+	 * check. Now, the validation is done after lock, that can also accept
+	 * invalidation message, so validation should be trustful.
+	 *
+	 * For now, we don't need to repeat validation. Only svar should be valid
+	 * pointer.
+	 */
+	Assert(svar);
+
+	*typid = svar->typid;
+
+	return copy_session_variable_value(svar, isNull);
+}
+
+/*
+ * Returns a copy of the value of the session variable after checking if the
+ * type is the same as "expected_typid".  The caller is responsible for
+ * permission checks.
+ */
+Datum
+GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
+{
+	SVariable	svar;
+
+	svar = get_session_variable(varid);
+
+	Assert(svar && svar->is_valid);
+
+	if (expected_typid != svar->typid)
+		elog(ERROR, "type of variable \"%s.%s\" is different than expected",
+			 get_namespace_name(get_session_variable_namespace(varid)),
+			 get_session_variable_name(varid));
+
+	return copy_session_variable_value(svar, isNull);
+}
+
+/*
+ * Assign the result of the evaluated expression to the session variable
+ */
+void
+ExecuteLetStmt(ParseState *pstate,
+			   LetStmt *stmt,
+			   ParamListInfo params,
+			   QueryEnvironment *queryEnv,
+			   QueryCompletion *qc)
+{
+	Query	   *query = castNode(Query, stmt->query);
+	List	   *rewritten;
+	DestReceiver *dest;
+	AclResult	aclresult;
+	PlannedStmt *plan;
+	QueryDesc  *queryDesc;
+	Oid			varid = query->resultVariable;
+
+	Assert(OidIsValid(varid));
+
+	/* do we have permission to write to the session variable? */
+	aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));
+
+	/* create a dest receiver for LET */
+	dest = CreateVariableDestReceiver(varid);
+
+	/* run the query rewriter */
+	query = copyObject(query);
+
+	rewritten = QueryRewrite(query);
+
+	Assert(list_length(rewritten) == 1);
+
+	query = linitial_node(Query, rewritten);
+	Assert(query->commandType == CMD_SELECT);
+
+	/* plan the query */
+	plan = pg_plan_query(query, pstate->p_sourcetext,
+						 CURSOR_OPT_PARALLEL_OK, params);
+
+	/*
+	 * Use a snapshot with an updated command ID to ensure this query sees the
+	 * results of any previously executed queries.  (This could only matter if
+	 * the planner executed an allegedly-stable function that changed the
+	 * database contents, but let's do it anyway to be parallel to the EXPLAIN
+	 * code path.)
+	 */
+	PushCopiedSnapshot(GetActiveSnapshot());
+	UpdateActiveSnapshotCommandId();
+
+	/* create a QueryDesc, redirecting output to our tuple receiver */
+	queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
+								GetActiveSnapshot(), InvalidSnapshot,
+								dest, params, queryEnv, 0);
+
+	/* call ExecutorStart to prepare the plan for execution */
+	ExecutorStart(queryDesc, 0);
+
+	/*
+	 * Run the plan to completion.  The result should be only one row.  To
+	 * check if there are too many result rows, we try to fetch two.
+	 */
+	ExecutorRun(queryDesc, ForwardScanDirection, 2L, true);
+
+	/* save the rowcount if we're given a QueryCompletion to fill */
+	if (qc)
+		SetQueryCompletion(qc, CMDTAG_LET, queryDesc->estate->es_processed);
+
+	/* and clean up */
+	ExecutorFinish(queryDesc);
+	ExecutorEnd(queryDesc);
+
+	FreeQueryDesc(queryDesc);
+
+	PopActiveSnapshot();
+}
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 11118d0ce0..71248a34f2 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -76,6 +76,7 @@ OBJS = \
 	nodeWindowAgg.o \
 	nodeWorktablescan.o \
 	spi.o \
+	svariableReceiver.o \
 	tqueue.o \
 	tstoreReceiver.o

 						/*
 
+	/*
index 0000000000..c4cff44ecf
+ * This DestReceiver is used by the LET command for storing the result to a
+ * non-deleted attribute.  The row counter (field "rows") is incremented
+ * after receiving a row, and an error is raised when there are no rows or
+ * there are more than one received rows.  Because a received tuple can have
+ * deleted attributes, we need to find the first non-deleted attribute
+ * (field "slot_offset").  The value is detoasted before storing it in the
+	bool		need_detoast;		/* do we need to detoast the attribute? */
+	int			slot_offset;		/* position of non-deleted attribute */
+		 * Double check - the type and typmod of target variable should be the
+		 * same as the type and typmod of assignment expression.  The
+		 * expression should be wrapped by a cast to the target type/typmod.
+ * Receive a tuple from the executor and store it in the session variable.
+	/* make sure the tuple is fully deconstructed */
+ * Clean up at end of the executor run
+ * Destroy the receiver when we are done with it
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*

 

 
 
 			/*
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*
 
index 0000000000..b3f03c6582

index 0000000000..db44d8b94c
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 1 Aug 2024 00:05:54 +0200

Hi

st 31. 7. 2024 v 11:45 odesílatel Laurenz Albe <[email-masked]>
napsal:

> > > > Probably you didn't attach new files - the second patch is not
> complete. Or you didn't make changes there?
> > >
> > > Hm.  What is missing?
> >
> > let.sgml,
> > session_variable.c
> > svariable_receiver.c
> > session_variable.h
> > ...
>

merged Laurenz's patches

Regards

Pavel

> Laurenz Albe
From ccb684e1d074a41b04172800ffdf27224ada0659 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

index 4512c4525bf..9e0420f7cf2 100644
 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)
 
index fa5adf631b8..4b62c01f149 100644
 
+/*
+ * Used for transactional variables. Holds prev version.
+ */
+typedef struct PrevValue
+{
+	Datum		value;
+	bool		isnull;
+
+	SubTransactionId modify_subid;
+
+	struct PrevValue *prev_value;
+} PrevValue;
+
 /*
  * The values of session variables are stored in the backend's private memory
  * in the dedicated memory context SVariableMemoryContext in binary format.
@@ -68,6 +81,25 @@ typedef struct SVariableData
 	bool		isnull;
 	Datum		value;
 
+	/*
@@ -86,6 +118,7 @@ typedef struct SVariableData
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;
 
@@ -125,6 +158,9 @@ static bool needs_validation = false;
  */
 static bool has_session_variables_with_reset_at_eox = false;
 
@@ -140,6 +176,7 @@ static List *xact_drop_items = NIL;

 /*
  * Callback function for session variable invalidation.
@@ -294,8 +331,25 @@ unregister_session_variable_xact_drop(Oid varid)
  * Release stored value, free memory
  */
 static void
-free_session_variable_value(SVariable svar)
+free_session_variable_value(SVariable svar, bool deep_free)
 {
+	if (deep_free)
+	{
+		PrevValue *prev_value = svar->prev_value;
+		PrevValue *next_value;
+
+		while (prev_value)
+		{
+			if (!svar->typbyval)
+				pfree(DatumGetPointer(prev_value->value));
+
+			next_value = prev_value->prev_value;
+			pfree(prev_value);
+
+			prev_value = next_value;
+		}
+	}
+
 	/* clean the current value */
 	if (!svar->isnull)
 	{
@@ -394,7 +448,7 @@ remove_invalid_session_variables(bool atEOX)
 			{
 				Oid			varid = svar->varid;
 
@@ -430,6 +484,94 @@ remove_session_variables_with_reset_at_eox(void)
 
+/*
@@ -477,6 +619,8 @@ AtPreEOXact_SessionVariables(bool isCommit)
 
+	remove_prev_values_at_eox(isCommit);
+
 	/*
 	 * We have to clean xact_drop_items. All related variables are dropped
 	 * now, or lost inside aborted transaction.
@@ -622,6 +766,7 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)

 	svar->is_domain = (get_typtype(varform->vartype) == TYPTYPE_DOMAIN);
 	svar->domain_check_extra = NULL;
@@ -647,6 +792,17 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	svar->isnull = true;
 	svar->value = (Datum) 0;
 
+	if (svar->is_transact)
+	{
 
 	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
@@ -680,6 +836,69 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	ReleaseSysCache(tup);
 }
 
+/*
  * Assign a new value to the session variable.  It is copied to
@@ -692,6 +911,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	Assert(svar);
 	Assert(!isnull || value == (Datum) 0);
@@ -727,6 +950,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	else
 		_svar = svar;
 
@@ -738,7 +976,37 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	else
 		newval = value;

 	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new value",
 		 get_namespace_name(get_session_variable_namespace(svar->varid)),
@@ -751,6 +1019,9 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	svar->value = newval;
 	svar->isnull = isnull;
 
 	/* don't allow more changes of value when variable is IMMUTABLE */
 	if (svar->is_immutable)
@@ -854,6 +1125,8 @@ get_session_variable(Oid varid)
 	else
 		svar->is_valid = false;
 
+reinit:
+
 	/*
 	 * Force setup for not yet initialized variables or variables that cannot
 	 * be validated.
@@ -886,6 +1159,28 @@ get_session_variable(Oid varid)
 			 varid);
 	}
 
+	/*
 	/* ensure the returned data is still of the correct domain */
index ebc0babbd94..60502df488a 100644

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:

 

 
 
 /*

 
index 68bc49a0e27..1dc44edf6e5 100644
@@ -61,6 +61,9 @@ CATALOG(pg_variable,9222,VariableRelationId)

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 
 
index 2d485944660..a920a529ac3 100644
 
index 66df1121d68..2ab78aeca25 100644
 
 DROP VARIABLE var1;
+

From 8aeb1b6b5767ad273b11998c28c5843ed1d5c37a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

index b8b27e1719e..7a3a4eabe25 100644
 
+     <varlistentry>

 

From a8923b7511ed1959f8cbff09bf5f581a70ba2a55 Mon Sep 17 00:00:00 2001

index 3370e2fff9a..3caa085d151 100644
@@ -522,7 +522,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
  * printed.  When we are in an expression where session variables cannot be
  * used, we raise the first form of error message.
 
+/*

 

 

 select a from atacc1;

 
index c8f7da3ae24..2d485944660 100644

From 9f64fae284152e8e900de156bb753f3a98666451 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

index 79c3023e95d..2fa06b00ef7 100644
@@ -1916,6 +1916,18 @@ transformLetStmt(ParseState *pstate, LetStmt *stmt)

+	/*
index aae22b45673..ebc0babbd94 100644

 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */

From cf37ba35c6699caa4d746fd8265a5bbb190eb45f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
index 52d4e9b4b54..c8f7da3ae24 100644
 
 -- inlining is blocked

From f3cec52aeefd37f7b246da0fd8cf4ef2585225ea Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

index 0063b2b6322..092ed2353ac 100644
 
 	/*

-		/* we don't support passing session variables to workers */
index 1baaf564bef..52d4e9b4b54 100644

From 8e3637d97ba5a2a6adb048376748bb945252d103 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 

 
index db75c6e2d88..79c3023e95d 100644
@@ -3371,14 +3371,6 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 
-	/*
-	 * The arguments of CALL statement are evaluated by a direct expression
-	 * executor call.  This path is unsupported yet, so block it.  It will be
-	 * enabled later.
-	 */
-	if (pstate->p_hasSessionVariables)
-		elog(ERROR, "session variable cannot be used as an argument");
-
 	assign_expr_collations(pstate, node);

 

 	/*
index 14d971ac27f..1baaf564bef 100644
index 5a3588ad368..66df1121d68 100644

+LET v = 1;
+

From 8d8989941b67df6b8154f0b2a807d9c8f6bbec66 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 doc/src/sgml/ddl.sgml                         | 16 ++--
 20 files changed, 355 insertions(+), 46 deletions(-)

index 30dfe877572..9d3942bdb4b 100644
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vardefexpr</structfield> <type>pg_node_tree</type>
+      </para>
+      <para>
+       The internal representation of the variable default value
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index bec8214ea07..aae18492a9c 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5339,14 +5339,14 @@ SELECT current_user_id;
 
    <para>
     The value of a session variable is local to the current session. Retrieving
-    a variable's value returns a <literal>NULL</literal>, unless its value has
-    been set to something else in the current session using the
-    <command>LET</command> command.  Session variables are not transactional:
-    any changes made to the value of a session variable in a transaction won't
-    be undone if the transaction is rolled back (just like variables in
-    procedural languages).  Session variables themselves can be persistent
-    or temporary, but their values are neither persistent nor shared (like the
-    content of temporary tables).
+    a variable's value returns a <literal>NULL</literal> or a default value,
+    unless its value has  been set to something else in the current session
+    using the <command>LET</command> command.  Session variables are not
+    transactional: any changes made to the value of a session variable in
+    a transaction won't be undone if the transaction is rolled back (just like
+    variables in procedural languages).  Session variables themselves can be
+    persistent or temporary, but their values are neither persistent nor shared
+    (like the content of temporary tables).
    </para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{
 
index 43b1eb50034..58a6d21f9aa 100644
@@ -514,11 +515,68 @@ AtEOSubXact_SessionVariables(bool isCommit,
 	}
 }
 
+/*
  * Initialize attributes cached in "svar"
@@ -568,6 +626,9 @@ setup_session_variable(SVariable svar, Oid varid)
 
+	if (!is_write)
 
@@ -597,7 +658,7 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	 */
 	if (!svar->is_valid)
 	{
@@ -732,7 +793,25 @@ get_session_variable(Oid varid)
 
 		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by READ)",
 			 get_namespace_name(get_session_variable_namespace(varid)),
@@ -796,7 +875,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 
 	if (!found)
 	{
 
index 257d67af675..9865c651f67 100644

 CreateSessionVarStmt:

 			if (isAgg)
index e0181a8b952..3370e2fff9a 100644
@@ -584,6 +584,7 @@ expr_kind_allows_session_variables(ParseExprKind p_expr_kind)
@@ -669,6 +670,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
@@ -2076,6 +2078,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;
@@ -3426,6 +3429,7 @@ ParseExprKindName(ParseExprKind exprKind)

 

 
+	if (vardefexpr)

 
 
 	if (verbose)
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
index 60291ef7ac2..e8bfeebed11 100644
--- a/src/include/catalog/pg_variable.h
+++ b/src/include/catalog/pg_variable.h
@@ -67,6 +67,9 @@ CATALOG(pg_variable,9222,VariableRelationId)
 	/* access permissions */
 	aclitem		varacl[1] BKI_DEFAULT(_null_);

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 
 
index a3156254ad0..f291eaa156c 100644

 DROP VARIABLE var1;
index 540b317cfef..521ae48eda2 100644
 
 DROP VARIABLE var1;
+

From fb4ba830c38ced35f37692997cc3a56fe138df29 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 doc/src/sgml/plpgsql.sgml                     |   3 +-
 16 files changed, 523 insertions(+), 47 deletions(-)

index 9d3942bdb4b..4512c4525bf 100644
 
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varnotnull</structfield> <type>boolean</type>
+      </para>
+      <para>
+       True if the session variable doesn't allow null values. The default value is false.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varisimmutable</structfield> <type>boolean</type>
+      </para>
+      <para>
+       True if the variable is <link linkend="sql-createvariable-immutable">immutable</link> (cannot be modified).
+       The default value is false.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vareoxaction</structfield> <type>char</type>
        <structfield>varxactendaction</structfield> <type>char</type>
       </para>
       <para>
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index c1196c7107d..d646e13fb23 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -6044,7 +6044,8 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE;
      You can consider translating an Oracle package into a schema in
      <productname>PostgreSQL</productname>.  Package functions and procedures
      would then become functions and procedures in that schema, and package
-     variables could be translated to session variables in that schema.
+     variables could be translated to session variables or immutable session
+     variables in that schema.
      (see <xref linkend="ddl-session-variables"\>).
     </para>
    </sect3>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index 8187c18e28b..00938743c0d 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -26,8 +26,8 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 58a6d21f9aa..fa5adf631b8 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -84,6 +84,9 @@ typedef struct SVariableData
 	void	   *domain_check_extra;
 	LocalTransactionId domain_check_extra_lxid;
 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*
@@ -102,6 +105,9 @@ typedef struct SVariableData
 	bool		is_valid;

 typedef SVariableData *SVariable;
@@ -567,6 +573,23 @@ eval_assign_defexpr(SVariable svar, HeapTuple tup)

 		FreeExecutorState(estate);
 	}
@@ -597,6 +620,9 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)

@@ -626,9 +652,31 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 
-	if (!is_write)
 
+		/*
 
@@ -648,6 +696,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	Assert(svar);
 	Assert(!isnull || value == (Datum) 0);
 
 	 * trustworthy (the invalidation message was not accepted for this
@@ -688,6 +751,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*
index 9865c651f67..aae22b45673 100644

 /*
 
 CreateSessionVarStmt:

 

 

 
+	if (varnotnull)
 
 /*

 
 
index e8bfeebed11..68bc49a0e27 100644
@@ -55,6 +55,12 @@ CATALOG(pg_variable,9222,VariableRelationId)
 
+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 
 
index f291eaa156c..14d971ac27f 100644

 DROP VARIABLE var1;
index 521ae48eda2..5a3588ad368 100644
 
 DROP VARIABLE var1;

From 24a6f3e4835c4c0b247a99f78985a66b600e9ec9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

index 67a54aa3e64..30dfe877572 100644

 
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index db55ca4b1c3..43b1eb50034 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -83,6 +83,8 @@ typedef struct SVariableData
 	void	   *domain_check_extra;
 	LocalTransactionId domain_check_extra_lxid;
 
+	bool		reset_at_eox;
+
 	/*
@@ -110,6 +112,12 @@ static MemoryContext SVariableMemoryContext = NULL;
 
+/*
@@ -389,6 +397,32 @@ remove_invalid_session_variables(bool atEOX)
 	}
 }
 
+/*
@@ -396,6 +430,8 @@ remove_invalid_session_variables(bool atEOX)
@@ -507,6 +543,21 @@ setup_session_variable(SVariable svar, Oid varid)
 
+	/*
 
 	svar->isnull = true;
index 9f7562bc311..257d67af675 100644

 

 

 
 
index 1e6dd98d415..60291ef7ac2 100644
@@ -74,6 +74,7 @@ typedef enum VariableXactEndAction
 
 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4
index b8a1ce6e7ef..a3156254ad0 100644
 
index cd44a0b4a0c..540b317cfef 100644

From 55b75853cb2ccb36902b1d583f1795a9a2e43ea7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 23 files changed, 521 insertions(+), 66 deletions(-)

index ac3b04ff912..67a54aa3e64 100644
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varxactendaction</structfield> <type>char</type>
+      </para>
+      <para>
+       Action performed at end of transaction:
+       <literal>n</literal> = no action, <literal>d</literal> = drop the variable.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>varcollation</structfield> <type>oid</type>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 6165fa74449..bec8214ea07 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5344,9 +5344,9 @@ SELECT current_user_id;
     <command>LET</command> command.  Session variables are not transactional:
     any changes made to the value of a session variable in a transaction won't
     be undone if the transaction is rolled back (just like variables in
-    procedural languages).  Session variables themselves are persistent, but
-    their values are neither persistent nor shared (like the content of
-    temporary tables).
+    procedural languages).  Session variables themselves can be persistent
+    or temporary, but their values are neither persistent nor shared (like the
+    content of temporary tables).
    </para>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);
 
index bd639d0b483..db55ca4b1c3 100644
 
 #include "access/xact.h"
 
+typedef struct SVariableXActDropItem
+{
+	Oid			varid;			/* varid of session variable */
+
+	/*
+	 * creating_subid is the ID of the creating subxact. If the action was
+	 * unregistered during the current transaction, deleting_subid is the ID
+	 * of the deleting subxact, otherwise InvalidSubTransactionId.
+	 */
+	SubTransactionId creating_subid;
+	SubTransactionId deleting_subid;
+} SVariableXActDropItem;
+
 /*
  * The values of session variables are stored in the backend's private memory
  * in the dedicated memory context SVariableMemoryContext in binary format.
@@ -105,6 +120,12 @@ static bool needs_validation = false;
  */
 static LocalTransactionId validated_lxid = InvalidLocalTransactionId;
 
@@ -142,16 +163,45 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
 	}
 }
 
+/*
 
+	if (XactEndAction == VARIABLE_XACTEND_DROP)
+	{
+		Assert(isTempNamespace(get_session_variable_namespace(varid)));
+
+		unregister_session_variable_xact_drop(varid);
+	}
+
 	if (sessionvars)
 	{
 		bool		found;
@@ -174,6 +224,57 @@ SessionVariableDropPostprocess(Oid varid)
 	}
 }
 
+/*
@@ -289,15 +390,92 @@ remove_invalid_session_variables(bool atEOX)
 }
 
 /*

 /*
index 06305036944..db75c6e2d88 100644
@@ -3291,10 +3291,10 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 
 		/*
index ea5a65b3ff7..9f7562bc311 100644

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{
index db593cd5bed..1e6dd98d415 100644
@@ -55,6 +55,9 @@ CATALOG(pg_variable,9222,VariableRelationId)

@@ -67,6 +70,12 @@ CATALOG(pg_variable,9222,VariableRelationId)
 #endif
 } FormData_pg_variable;

 

index 5f7b83d13fa..dd8ed3965b7 100644

 
 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 
 
index 44cfe7ed2d6..b8a1ce6e7ef 100644

 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables
index e7e16c02bc4..cd44a0b4a0c 100644

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables

From c1ec13f1679fb5717fd5b667ccce4da030d43fd1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

index 3bc391eff10..ea5a65b3ff7 100644

 
-/* LET */
index c8c52162c96..44cfe7ed2d6 100644
 
index 1811299df31..e7e16c02bc4 100644

From bb5c4d9024be8d8f9387c88f7dabcf2b0af7d766 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

 doc/src/sgml/ddl.sgml                         | 10 ++++
 src/backend/parser/parse_expr.c               | 49 +++++++++++++--
 10 files changed, 230 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 4e77adc7f17..ae418a9c3ff 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10693,6 +10693,66 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>
 
index 574540ca6fe..6165fa74449 100644
@@ -5359,6 +5359,16 @@ SELECT current_user_id;
+    When a query contains identifiers or qualified identifiers that could be
+    used as both a session variable identifiers and as column identifier,
+    then the column identifier is preferred every time.  Warnings can be
+    emitted when this situation happens by enabling configuration parameter <xref
 
  <sect1 id="ddl-others">
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index b50ac801a91..e0181a8b952 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -47,6 +47,7 @@
 
 /* GUC parameters */

@@ -962,11 +963,51 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
-		 * However, that is very confusing, so we disallow it.  We don't try to
-		 * identify a variable if we know that it would be shadowed.
-		 * -----
+		 * However, that is very confusing, so we disallow it.
+		 * When session_variables_ambiguity_warning is requested, then we
+		 * need to identify a variable although we know, so this variable
+		 * would be shadowed.
 			/* takes an AccessShareLock on the session variable */
 			varid = IdentifyVariable(cref->fields, &attrname, &not_unique, false);
 
 /* GUC parameters */

 
 
index 92fe0dc8d79..29d9145119a 100644
 
index 6e198b3640d..1b1f9b3d279 100644

From 32b141465284f052eb5bb9f77aefb33d714f561d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From bc65881ea80b4932797fab1ec3a01f6c0a5a049a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

index 9492ec7a6b0..3bc391eff10 100644

index 29d9145119a..c8c52162c96 100644
index 1b1f9b3d279..1811299df31 100644
 
 DROP TABLE public.xxtab;

From c019177ede0ca59e34c2168933f978dbd8fe89c9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
 
index b1d414ca966..9e5a4ad45de 100644
@@ -94,7 +94,13 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)

-	Assert(sessionvars);
+	/*
 
 	/*
 	 * If the hashvalue is not specified, we have to recheck all currently
@@ -674,3 +680,23 @@ pg_session_variables(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
+
+/*
index 5787402d387..9492ec7a6b0 100644

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

index 287628ede61..25fe57d6351 100644
index db5ba176db2..8d420a0a31f 100644
 
 RESET search_path;
+

From 40b535ae10dba6a50846a751e903c9197ebfc16e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 src/backend/commands/session_variable.c       | 173 +++++++++++++-
 9 files changed, 682 insertions(+), 2 deletions(-)

 
 
index 9e5a4ad45de..bd639d0b483 100644
 
@@ -67,6 +68,14 @@ typedef struct SVariableData
 
+	/*
+	 * Top level local transaction id of the last transaction that dropped the
+	 * variable if any.  We need this information to avoid freeing memory for
+	 * variable dropped by the local backend that may be eventually
+	 * rollbacked.
+	 */
+	LocalTransactionId drop_lxid;
+
 	/*
 	 * Stored value and type description can be outdated when we receive a
 	 * sinval message.  We then have to check if the stored data are still
@@ -83,6 +92,19 @@ static HTAB *sessionvars = NULL;	/* hash table for session variables */

@@ -114,6 +136,40 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
@@ -167,6 +223,83 @@ is_session_variable_valid(SVariable svar)
 	return result;
 }
 
+/*
  * Initialize attributes cached in "svar"
@@ -196,6 +329,8 @@ setup_session_variable(SVariable svar, Oid varid)

@@ -319,22 +454,44 @@ get_session_variable(Oid varid)
 	if (!sessionvars)
 		create_sessionvars_hashtables();

 	if (found)
 	{
+		/*
+		 * The session variable can be dropped by DROP VARIABLE command,
+		 * but effect of this command can be reverted by ROLLBACK to savepoint,
+		 * so we can work here with readable value in variable marked as invalid.
+		 */
 		if (!svar->is_valid)
 		{
 			/*
 			 * If there was an invalidation message, the variable might still be
 			 * valid, but we have to check with the system catalog.
+			 *
+			 * If we access this session variable, then the variable should be
+			 * possibly validated. The oid should be valid, because related
+			 * session variable is locked already, and remove_invalid_session_variables
+			 * should to remove variables dropped by other transactions.
 			 */
 			if (is_session_variable_valid(svar))
 				svar->is_valid = true;
 			else
-				/* if the value cannot be validated, we have to discard it */
-				free_session_variable_value(svar);
+			{
+				/* We don't expect it */
+				elog(ERROR, "unexpected state of session variable %u", varid);
+			}
 		}
 	}
 	else
@@ -395,6 +552,16 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 	if (!sessionvars)
 		create_sessionvars_hashtables();

@@ -594,6 +761,8 @@ pg_session_variables(PG_FUNCTION_ARGS)

 
 	if (sessionvars)
 
index 25fe57d6351..92fe0dc8d79 100644
index 8d420a0a31f..6e198b3640d 100644
 
 -- should be zero again

From 88f03243c5921248b02b93e2679b8501870f5b87 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 23e7fa99f77..b1d414ca966 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -572,3 +572,105 @@ ExecuteLetStmt(ParseState *pstate,
 
 	PopActiveSnapshot();
 }
+
+/*

From 6485f88748dbdb3dfed7ba2413c423900ac23935 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 

 

 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 

 
 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du

From 52abb225980605f75905c11dc3abcbfc5238f8aa Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

index de122e543f7..ac3b04ff912 100644
 
+     <row>
index 7531de17fd3..574540ca6fe 100644
 
+   <para>
index 590cb385ddf..a5cef4cd79b 100644
 
index a6759238672..c1196c7107d 100644

 
index 00000000000..f6640576bee
index c9d686665de..148719e490e 100644
 
index c4642b75bbe..9e018aae800 100644
@@ -3397,6 +3397,323 @@ LookupVariable(const char *nspname,
 	return varoid;
 }
 
+/*

index 00000000000..23e7fa99f77

 						/*
 
+	/*
index 00000000000..c4cff44ecf4
index 948afd90948..e5024f33a59 100644
 
 	/*

+	/*
index 7aed84584c6..95cda4726c1 100644

+/*
 
 /*

+/*
 
 			/*
 
index 969e257f70b..061ac2dc7cd 100644
 
index b4e085e9d4b..0063b2b6322 100644

 				/*
 
index 28fed9d87f6..06305036944 100644

 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*
 
index 3f2c590ce32..5787402d387 100644

 

 
 			/*
index d2db69a3f90..b50ac801a91 100644
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*
index 5af1a168ec2..7e1e9e28f12 100644

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

index 3810e040f28..db593cd5bed 100644
 
+	/*

 
index cac684d9b3a..449050f7fe5 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

index ea47652adb8..5f7b83d13fa 100644
 
 typedef struct Param

 
 
index a3d521b6f97..38cc87f4e7a 100644
 
 /* ----------------
 
 	/*
index aca766042db..287628ede61 100644
index 42dfb95a022..db5ba176db2 100644

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 1 Aug 2024 08:12:13 +0200

Hi

fresh rebase + fix format in doc

Regards

Pavel
From 26d0cde537c0d87d93e80805e795a663acfb784f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From c870db0ad87fa5a2547ce07ad98e1b0461912a47 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*

 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index 0ab9ef134d0..9424bef2baa 100644

From dd5f1b3e5acb6664bb47059a70b7abe4a21207e5 Mon Sep 17 00:00:00 2001

 
+/*

 

 

 select a from atacc1;

 

From 74eecca7ba5f4557f546f5bf8afe649da96f695c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From b919f7fbd81594cc81533ad6f5c29a60a3a938a6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:
index 62577feb053..a67ece9fbce 100644
@@ -5376,6 +5376,7 @@ getVariables(Archive *fout)
@@ -5400,6 +5401,7 @@ getVariables(Archive *fout)
@@ -5422,6 +5424,7 @@ getVariables(Archive *fout)
 
@@ -5450,6 +5453,7 @@ getVariables(Archive *fout)
 
@@ -5497,6 +5501,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5514,12 +5519,13 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 
 
 /*

 

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+

From ae605343573838f930443493a35a9e1854d02979 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

 
 	/*

 

From 572ab99f75141079474b60ffd21a45a1fcc9cb1a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 512bd4ecb46..eefd0c2958c 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1005,31 +1005,69 @@ ExecInitExprRec(Expr *node, ExprState *state,
 								es_num_session_variables = state->parent->state->es_num_session_variables;
 							}

index 1535fd6b984..f3d5082f0c5 100644
 		&&CASE_EEOP_PARAM_SET,
@@ -1094,6 +1096,20 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
 		EEO_CASE(EEOP_PARAM_SET)
 			/* out of line, unlikely to matter performancewise */
index 27f94f90070..4aa2d02f56d 100644
 
 			case EEOP_PARAM_SET:
 				build_EvalXFunc(b, mod, "ExecEvalParamSet",
 								v_state, op, v_econtext);
 
-	/*
 
index 845f3422dea..c60f6173f57 100644
 
-	/* evaluate PARAM_EXEC/EXTERN parameters */
+	/* evaluate PARAM_EXEC/EXTERN/VARIABLE parameters */
 	EEOP_PARAM_EXEC,
 	EEOP_PARAM_EXTERN,
 	EEOP_PARAM_CALLBACK,
+	EEOP_PARAM_VARIABLE,
 	/* set PARAM_EXEC value */
 	EEOP_PARAM_SET,
 
@@ -402,6 +403,13 @@ typedef struct ExprEvalStep
 			Oid			paramtype;	/* OID of parameter's datatype */
 		}			cparam;
 
@@ -806,6 +814,8 @@ extern void ExecEvalParamSet(ExprState *state, ExprEvalStep *op,
 
 	/*

+LET v = 1;
+

From cf7d5b5fc2473c9078772806e5bf948753e21dbc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7e7cc3d077f..07260cda792 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5370,6 +5370,7 @@ getVariables(Archive *fout)
 	int			i_varnamespace;
 	int			i_vartype;
 	int			i_vartypname;
+	int			i_varxactendaction;
 	int			i_varowner;
 	int			i_varcollation;
 	int			i_varacl;
@@ -5387,6 +5388,7 @@ getVariables(Archive *fout)
 	/* Get the variables in current database. */
 	appendPQExpBuffer(query,
 					  "SELECT v.tableoid, v.oid, v.varname,\n"
+					  "       v.varxactendaction,\n"
 					  "       v.varnamespace, v.vartype,\n"
 					  "       pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n"
 					  "       CASE WHEN v.varcollation <> t.typcollation "
@@ -5409,6 +5411,7 @@ getVariables(Archive *fout)
 	i_varnamespace = PQfnumber(res, "varnamespace");
 	i_vartype = PQfnumber(res, "vartype");
 	i_vartypname = PQfnumber(res, "vartypname");
+	i_varxactendaction = PQfnumber(res, "varxactendaction");
 	i_varcollation = PQfnumber(res, "varcollation");
 
 	i_varowner = PQfnumber(res, "varowner");
@@ -5432,6 +5435,9 @@ getVariables(Archive *fout)

 		varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl));
@@ -5472,6 +5478,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	PQExpBuffer query;
 	char	   *qualvarname;
 	const char *vartypname;
+	const char *varxactendaction;
 	Oid			varcollation;
 
 	/* Skip if not to be dumped */
@@ -5483,6 +5490,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
@@ -5501,6 +5509,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}

 
 
 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From 6d2afba3fd60d984c1b1543e0866ce9cb2fb4e34 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{

 
 CreateSessionVarStmt:

 			if (isAgg)
 
index 07260cda792..7fe4d89d132 100644
@@ -5370,6 +5370,7 @@ getVariables(Archive *fout)
@@ -5395,6 +5396,7 @@ getVariables(Archive *fout)
@@ -5411,6 +5413,7 @@ getVariables(Archive *fout)
 
@@ -5446,6 +5449,11 @@ getVariables(Archive *fout)
 		varinfo[i].dacl.initprivs = NULL;
 		varinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_varowner));
 
@@ -5478,6 +5486,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5490,6 +5499,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

@@ -5509,6 +5519,10 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (vardefexpr)

 
 
 	if (verbose)

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From 21f0570fbb8737a830d85cdf20b3645507942f50 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
index 8aa979ecb25..8bdf9127f0b 100644
      (see <xref linkend="ddl-session-variables"/>).
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*

 /*
 
 CreateSessionVarStmt:

index 7fe4d89d132..62577feb053 100644
@@ -5374,6 +5374,8 @@ getVariables(Archive *fout)
@@ -5396,6 +5398,8 @@ getVariables(Archive *fout)
@@ -5416,6 +5420,8 @@ getVariables(Archive *fout)
 
 	i_varowner = PQfnumber(res, "varowner");
 	i_varacl = PQfnumber(res, "varacl");
@@ -5442,6 +5448,8 @@ getVariables(Archive *fout)
 			pg_strdup(PQgetvalue(res, i, i_varxactendaction));

@@ -5488,7 +5496,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5502,12 +5512,14 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 
 	if (OidIsValid(varcollation))
 	{
@@ -5519,6 +5531,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (varnotnull)
 
 /*

 

+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From c9fecbc1d00e6b1bb04e87d98f60335978aefb60 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
 
 		/*

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index 5e2e5bcd806..0ab9ef134d0 100644
@@ -2789,6 +2789,7 @@ SupportRequestWFuncMonotonic
 SVariable
 SVariableData
 SVariableState
+SVariableXActDropItem
 Syn
 SyncOps
 SyncRepConfigData
-- 
2.45.2

From 16f96fce8d60fb1f0626d0484f66be0a36feddf6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

 

-/* LET */

From ac090a812d88fa8f768dae6242fccc154a9eacfc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

 

 DROP TABLE public.xxtab;

From b027b0353cd330b064b9634f8308e953d43c2f8e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

 
 
  <sect1 id="ddl-others">
 
 /* GUC parameters */

 /* GUC parameters */

 

 

From f9654e0ebf4df629657ddf996119738a83df316e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From 366e823a7e2d149cb986187412d3d8135c4c9af4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 

 
 
+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

 	if (sessionvars)

 -- should be zero again

From 318c5900446a85fae238de00a48a502cd24e2e4a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*

index 42231c6ab26..a8d979eac73 100644
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+

From 5b0f9702bd690c9ed1edba8ca0d4379d4d5f174d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*
index 2ea095f6bb2..6943902fa3b 100644
@@ -12244,4 +12244,12 @@

From 1a73644e245f30e869a5d44089d497aa400ef514 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 
+     <row>
 
+   <para>
 
index a6759238672..8aa979ecb25 100644
+     (see <xref linkend="ddl-session-variables"/>).
+    </para>
+   </sect3>
   </sect2>

 

+/*

 
index 66dda8e5e69..512bd4ecb46 100644
@@ -991,6 +992,47 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 						/*
 
+	/*
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*

 

 
 
 			/*
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

index 312287eca6e..42231c6ab26 100644
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
index c3670f7158c..9c8c8a4c72c 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*
 
index ce299a4be13..5e2e5bcd806 100644
@@ -2592,6 +2593,7 @@ SerializedTransactionState
@@ -2784,6 +2786,9 @@ SupportRequestRows

From b30310168412e99a6f51b7b91c664b139be85243 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 

 

 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }
index 702a6c3a0b0..312287eca6e 100644

 
 	return subname;
 }
+

 
 
 	int			useDB;
index 0d025162738..7e7cc3d077f 100644
@@ -361,6 +361,7 @@ static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo);
@@ -5353,6 +5354,190 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
@@ -9915,7 +10100,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -10514,6 +10700,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
@@ -14963,6 +15152,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -18574,6 +18766,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 
 
index 54b50ee5d61..2ea095f6bb2 100644
@@ -6478,6 +6478,9 @@

 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du
index 8de9978ad8d..ce299a4be13 100644
@@ -3063,6 +3066,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.45.2

---

From: Erik Rijkers <[email-masked]>
Date: Thu, 1 Aug 2024 09:45:34 +0200

doc-build fails: a slash tumbled backwards in doc/src/sgml:

$ grep 'xref.*.\\>'  *.sgml
plpgsql.sgml:     (see <xref linkend="ddl-session-variables"\>).

That should simply be a forward slash, of course.

Thanks,

Erik Rijkers

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 1 Aug 2024 09:48:16 +0200

> That should simply be a forward slash, of course.

should be fixed in my today patchset

---

From: Laurenz Albe <[email-masked]>
Date: Thu, 01 Aug 2024 13:22:01 +0200

> fresh rebase + fix format in doc

Thanks!

I'm curious, but too lazy to build the patch now, so I'm asking:
what did you do about this error?

> ERROR:  paramid of PARAM_VARIABLE param is out of range

Or does a later patch take care of that?

Yours,
Laurenz Albe

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 1 Aug 2024 13:24:58 +0200

> what did you do about this error?

I try to investigate this issue now.

The patchset is just merging of your work

> Or does a later patch take care of that?

This is a clear bug, and I have to fix it. I hope so I'll do this today

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 2 Aug 2024 00:02:27 +0200

> Or does a later patch take care of that?

It is working with patch "allow read an value of session variable directly
from  expression executor"

It looks very similar to evaluation of parameters of CALL statements.
Without the mentioned patch, the CALL statements don't allow variables as
an argument. I blocked usage of session variables as EXECUTE parameters
until direct access to session variables in expression evaluation is
supported.

Regards

Pavel

> Laurenz Albe
From 73282660b2767d9a562bc02aea6092508e5cff21 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 18/21] expression with session variables can be inlined

 

 
index 6466a247328..1829a2a7eda 100644
 
 -- inlining is blocked

From f51f3ff72e7044c10072bf14d990d091f1d26656 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 17/21] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

index f630ef07ffc..ad5550b6f67 100644
@@ -1922,6 +1922,18 @@ transformLetStmt(ParseState *pstate, LetStmt *stmt)

+	/*

 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */

From 7763c4820f0571c3c89bf4104053be27a5be4dee Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 21/21] pg_restore -A, --variable

 
+     <varlistentry>

 

From ce8e0cd4246cdacbf0853de76b84f87e0605894f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 20/21] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:
index be7cf8f10b4..3f3a18d8cf5 100644

 

 
 
 /*

 

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 
 
index a23a957ee41..4184b6158bf 100644
 
 DROP VARIABLE svartest.var1;
@@ -1760,3 +1760,103 @@ SELECT var1;
 LET var1 = 30;
 ERROR:  session variable "public.var1" is declared IMMUTABLE
 DROP VARIABLE var1;
+-- test transactional variables
+CREATE TRANSACTION VARIABLE tv AS int DEFAULT 0;
+BEGIN;
+  LET tv = 100;
+  SELECT tv;
+ tv  
+-----
+ 100
+(1 row)
+
+ROLLBACK;
+SELECT tv;
+ tv 
+----
+  0
+(1 row)
+
+LET tv = 100;
+BEGIN;
+  LET tv = 1000;
+COMMIT;
+SELECT tv;
+  tv  
+------
+ 1000
+(1 row)
+
+BEGIN;
+  LET tv = 0;
+  SELECT tv;
+ tv 
+----
+  0
+(1 row)
+
+ROLLBACK;
+SELECT tv;
+  tv  
+------
+ 1000
+(1 row)
+
+-- test subtransactions
+BEGIN;
+  LET tv = 1;
+SAVEPOINT x1;
+  LET tv = 2;
+SAVEPOINT x2;
+  LET tv = 3;
+ROLLBACK TO x2;
+  SELECT tv;
+ tv 
+----
+  2
+(1 row)
+
+  LET tv = 10;
+ROLLBACK TO x1;
+  SELECT tv;
+ tv 
+----
+  1
+(1 row)
+
+ROLLBACK;
+SELECT tv;
+  tv  
+------
+ 1000
+(1 row)
+
+BEGIN;
+  LET tv = 1;
+SAVEPOINT x1;
+  LET tv = 2;
+SAVEPOINT x2;
+  LET tv = 3;
+ROLLBACK TO x2;
+  SELECT tv;
+ tv 
+----
+  2
+(1 row)
+
+  LET tv = 10;
+ROLLBACK TO x1;
+  SELECT tv;
+ tv 
+----
+  1
+(1 row)
+
+COMMIT;
+SELECT tv;
+ tv 
+----
+  1
+(1 row)
+
+DROP VARIABLE tv;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index eb3da1f35f7..bca9a92ec48 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -1207,3 +1207,61 @@ SELECT var1;
 LET var1 = 30;
 
 DROP VARIABLE var1;
+

From fe23d39e3a9df88cc80034cca8b83db8a8d66d4f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 17:06:04 +0100
Subject: [PATCH 19/21] this patch changes error message "column doesn't exist"
 to message "column or variable doesn't exist"

 
+/*

 

 

 select a from atacc1;

 
index 1829a2a7eda..a23a957ee41 100644
@@ -309,7 +309,7 @@ ERROR:  session variable "var1" doesn't exist
@@ -520,7 +520,7 @@ SELECT v1;
 
@@ -1067,7 +1067,7 @@ BEGIN;

From 30b78f2441b6180886c5c137f95575e4988a37b1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 16/21] allow parallel execution queries with session variables

---

 

 

 
 	/*

index d8afa7ae4e0..6466a247328 100644
@@ -642,12 +642,14 @@ SELECT count(*) FROM svar_test WHERE a%10 = zero;

From 94407c43a03a58d9d8fca9a7d2483605d1faaf22 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 15/21] allow read an value of session variable directly from
 expression executor

This patch enables usage session variables in arguments of CALL stmt.
---
 src/backend/commands/prepare.c                |  8 --
 src/backend/executor/execExpr.c               | 88 +++++++++++++------
 src/backend/executor/execExprInterp.c         | 16 ++++
 src/backend/jit/llvm/llvmjit_expr.c           |  6 ++
 src/backend/parser/analyze.c                  |  8 --
 src/include/executor/execExpr.h               | 12 ++-
 .../src/expected/plpgsql_session_variable.out |  3 +-
 src/pl/plpgsql/src/pl_exec.c                  |  3 +-
 .../regress/expected/session_variables.out    | 26 +++---
 src/test/regress/sql/session_variables.sql    | 12 +--
 10 files changed, 117 insertions(+), 65 deletions(-)

diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 2046fa8a543..364df777ea7 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -338,14 +338,6 @@ EvaluateParams(ParseState *pstate, PreparedStatement *pstmt, List *params,
 		i++;
 	}
 
-	/*
-	 * The arguments of EXECUTE are evaluated by a direct expression
-	 * executor call.  This mode doesn't support session variables yet.
-	 * It will be enabled later.
-	 */
-	if (pstate->p_hasSessionVariables)
-		elog(ERROR, "session variable cannot be used as an argument");
-
 	/* Prepare the expressions for execution */
 	exprstates = ExecPrepareExprList(params, estate);

 

index 5030857863c..f630ef07ffc 100644
@@ -3377,14 +3377,6 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 
-	/*

 

 	/*
index de229ba301b..d8afa7ae4e0 100644
@@ -278,27 +277,28 @@ $$;
--- EXECUTE statement is not supported yet
+-- EXECUTE statement
 LET v = 20;
 PREPARE ptest(int) AS SELECT $1;
--- should fail
 EXECUTE ptest(v);
 DEALLOCATE ptest;
index d0e6e7ce262..eb3da1f35f7 100644

+LET v = 1;
+

 DO $$ BEGIN CALL p(v); END $$;
@@ -221,13 +222,12 @@ DO $$ BEGIN CALL p(v); END $$;
 DROP PROCEDURE p(int);
 DROP VARIABLE v;
 
--- EXECUTE statement is not supported yet
--- requires direct access to session variable from expression executor
+-- EXECUTE statement
 CREATE VARIABLE v int;
 LET v = 20;
 PREPARE ptest(int) AS SELECT $1;
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.45.2

From 750419913a427d8bdc04ec7c14fc028969da0ba9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 14/21] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*

 /*
 
 CreateSessionVarStmt:

index 11357382b0b..be7cf8f10b4 100644

 

 

+	if (varnotnull)
 
 /*

 

+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 
 
index f2df2395b18..de229ba301b 100644
 
 DROP VARIABLE svartest.var1;
@@ -1614,3 +1614,148 @@ SELECT var1;
 
 DROP VARIABLE var1;
index a93e30c1498..d0e6e7ce262 100644
@@ -1085,3 +1085,125 @@ SELECT var1;
 
 DROP VARIABLE var1;

From 0ff6714df8e183446a0c68f792b2bec4f56f65f9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 13/21] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{

 
 CreateSessionVarStmt:

 			if (isAgg)
 
index da9bc23ae09..11357382b0b 100644

 

 
+	if (vardefexpr)

 
 
 	if (verbose)

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 
 
index 280b27e7054..f2df2395b18 100644
 
 DROP VARIABLE svartest.var1;
@@ -1551,3 +1551,66 @@ SELECT var1 IS NULL;
 (1 row)
 
 DROP VARIABLE var1;
index 829293ddcc4..a93e30c1498 100644
@@ -1037,3 +1037,51 @@ ROLLBACK;
 SELECT var1 IS NULL;
 
 DROP VARIABLE var1;
+

From 881a6b36a09e9d09a9007f205c7eda563b09e132 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 12/21] Implementation ON TRANSACTION END RESET clause

 

 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;
 
index 099137711f6..da9bc23ae09 100644

 

 

 
 
 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4
index a8932ff3632..280b27e7054 100644
@@ -1517,3 +1517,37 @@ SELECT count(*) FROM pg_session_variables();
      0
 (1 row)
 
+CREATE VARIABLE var1 AS int ON TRANSACTION END RESET;
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+COMMIT;
+-- should be NULL;
+SELECT var1 IS NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+ROLLBACK;
+-- should be NULL
+SELECT var1 IS NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP VARIABLE var1;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 63b51f9a34a..829293ddcc4 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -1017,3 +1017,23 @@ COMMIT;
 SELECT count(*) FROM pg_variable WHERE varname = 'var1';
 -- should be zero
 SELECT count(*) FROM pg_session_variables();
+
+CREATE VARIABLE var1 AS int ON TRANSACTION END RESET;
+
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+COMMIT;
+
+-- should be NULL;
+SELECT var1 IS NULL;
+
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+ROLLBACK;
+
+-- should be NULL
+SELECT var1 IS NULL;
+
+DROP VARIABLE var1;
-- 
2.45.2

From a93bd3d01cc5f00444cc1046fef1e04b4018ccae Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 10/21] PREPARE LET support

 

-/* LET */
index 8c5d8a5eb29..0cc92c22e16 100644
@@ -438,9 +438,9 @@ DROP VARIABLE var1, var2;
@@ -1318,5 +1318,82 @@ SELECT var1;
    10
 (1 row)
 
index 3649fc1316b..0b4a53ce107 100644
@@ -892,5 +892,62 @@ EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE, SUMMARY OFF) LET var1 = (SELECT count(*
 -- should be 10
 SELECT var1;

From 231e09e5605d3d75e7c8ba3f0b34c5c38e7d30e0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 09/21] EXPLAIN LET support

 
 
index 719e63fe823..2046fa8a543 100644
@@ -663,8 +663,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,

 
index 660423d5de8..8c5d8a5eb29 100644
@@ -1280,3 +1280,43 @@ SET session_variables_ambiguity_warning TO off;
index 391c1349ffa..3649fc1316b 100644
@@ -873,3 +873,24 @@ SET session_variables_ambiguity_warning TO off;
 
 DROP TABLE public.xxtab;

From f449fa648f0d0f4ff1e96289a07de4fccf4f4e21 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 11/21] implementation of temporary session variables

 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
index f75be33a071..5030857863c 100644
@@ -3297,10 +3297,10 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 
 		/*

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 
 
index 0cc92c22e16..a8932ff3632 100644
 
 DROP VARIABLE svartest.var1;
@@ -1397,3 +1397,123 @@ SELECT var1;
 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables
index 0b4a53ce107..63b51f9a34a 100644
@@ -825,6 +825,7 @@ BEGIN;
   DROP VARIABLE var1;
   SELECT var2;
 ROLLBACK;
+
 -- should be ok
 SELECT var1;
 
@@ -951,3 +952,68 @@ SELECT var1;
 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables

From 05c31c46c18dd10a4e92a00dcc619be1b313995a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 08/21] GUC session_variables_ambiguity_warning

index a65839a6709..10e7a9a3923 100644

  <sect1 id="ddl-others">
 
 /* GUC parameters */

index 74a38bb2458..bc9ca7b981a 100644
@@ -1594,6 +1594,15 @@ struct config_bool ConfigureNamesBool[] =
 
 /* GUC parameters */

 
 
 -- the value should not be corrupted
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 7e4d41de3e6..660423d5de8 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -1258,3 +1258,25 @@ SELECT var1;
 (1 row)
 
index b3f3d6f16a1..391c1349ffa 100644
@@ -853,3 +853,23 @@ BEGIN; DROP VARIABLE var1; ROLLBACK;
 SELECT var1;

From 0d382fa413425a34113ffa4f5c16169f483b8be3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 07/21] plpgsql tests

---

From b4389cdd01f3cf39edbc31d1a1b4e5796c5fa572 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 06/21] memory cleaning after DROP VARIABLE

 

 
 
+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

 	if (sessionvars)
 
index 38e0360156f..7e4d41de3e6 100644
@@ -1039,3 +1039,222 @@ SELECT count(*) FROM pg_session_variables();
index d4165f34ffa..b3f3d6f16a1 100644
@@ -732,3 +732,124 @@ DISCARD VARIABLES;
 
 -- should be zero again

From c33146e06079f764808eebb11e7b282cdebdead8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 05/21] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

index 16e1d01ddc6..38e0360156f 100644
@@ -977,3 +977,65 @@ DROP VARIABLE :"DBNAME".:"DBNAME".b;
index d5bb5dc83be..d4165f34ffa 100644
@@ -696,3 +696,39 @@ DROP VARIABLE :"DBNAME".:"DBNAME".:"DBNAME";
 DROP SCHEMA :"DBNAME";
 
 RESET search_path;
+

From 65f39ee14f2ca6536ffda8b47671f9d2e9ff1d0d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 04/21] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*

From aeeb9a6fd6dceaf0e4ba144f7ecd1fc57f6e7ebc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 1 Aug 2024 23:12:29 +0200
Subject: [PATCH 03/21] regress tests

---
 src/test/regress/expected/session_variables.out | 10 ++++++++++
 src/test/regress/sql/session_variables.sql      | 12 ++++++++++++
 2 files changed, 22 insertions(+)

diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 287628ede61..16e1d01ddc6 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -291,6 +291,16 @@ CONTEXT:  SQL statement "CALL p(v)"
 PL/pgSQL function inline_code_block line 1 at CALL
 DROP PROCEDURE p(int);
 DROP VARIABLE v;
+-- EXECUTE statement is not supported yet
+-- requires direct access to session variable from expression executor
+CREATE VARIABLE v int;
+LET v = 20;
+PREPARE ptest(int) AS SELECT $1;
+-- should fail
+EXECUTE ptest(v);
+ERROR:  session variable cannot be used as an argument
+DEALLOCATE ptest;
+DROP VARIABLE v;
 -- test search path
 CREATE SCHEMA svartest;
 CREATE VARIABLE svartest.var1 AS numeric;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index db5ba176db2..d5bb5dc83be 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -221,6 +221,18 @@ DO $$ BEGIN CALL p(v); END $$;
 DROP PROCEDURE p(int);
 DROP VARIABLE v;
 
+-- EXECUTE statement is not supported yet
+-- requires direct access to session variable from expression executor
+CREATE VARIABLE v int;
+LET v = 20;
+PREPARE ptest(int) AS SELECT $1;
+
+-- should fail
+EXECUTE ptest(v);
+
+DEALLOCATE ptest;
+DROP VARIABLE v;
+
 -- test search path
 CREATE SCHEMA svartest;
 CREATE VARIABLE svartest.var1 AS numeric;
-- 
2.45.2

From fe3ef458d94bc091a501352e8c1a2e1183979d41 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/21] Storage for session variables and SQL interface

- EXECUTE statement is not supported (usage of direct access to express executor)

 src/backend/commands/prepare.c                |   8 +
 59 files changed, 3907 insertions(+), 23 deletions(-)

+     <row>
 
+   <para>

 

 
+/*

index 07257d4db94..719e63fe823 100644
@@ -338,6 +338,14 @@ EvaluateParams(ParseState *pstate, PreparedStatement *pstmt, List *params,
 		i++;
 	}
 
+	/*
+	 * The arguments of EXECUTE are evaluated by a direct expression
+	 * executor call.  This mode doesn't support session variables yet.
+	 * It will be enabled later.
+	 */
+	if (pstate->p_hasSessionVariables)
+		elog(ERROR, "session variable cannot be used as an argument");
+
 	/* Prepare the expressions for execution */
 	exprstates = ExecPrepareExprList(params, estate);

 
 						/*
 
+	/*
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*
 
index e901203424d..f75be33a071 100644
 
@@ -51,6 +54,7 @@
 
@@ -83,6 +87,8 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 										 CreateTableAsStmt *stmt);
 static Query *transformCallStmt(ParseState *pstate,
 								CallStmt *stmt);
+static Query *transformLetStmt(ParseState *pstate,
+							   LetStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 								   LockingClause *lc, bool pushedDown);
 #ifdef DEBUG_NODE_TESTS_ENABLED
@@ -330,6 +336,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
 			case T_UpdateStmt:
 			case T_DeleteStmt:
 			case T_MergeStmt:
+			case T_LetStmt:
 				(void) test_raw_expression_coverage(parseTree, NULL);
 				break;
 			default:
@@ -409,6 +416,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
 									   (CallStmt *) parseTree);
 			break;
 
+		case T_LetStmt:
 
 			/*
@@ -460,6 +472,7 @@ stmt_requires_parse_analysis(RawStmt *parseTree)
 		case T_SelectStmt:
 		case T_ReturnStmt:
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			result = true;
 			break;
 
@@ -568,6 +581,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)

@@ -994,6 +1008,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)

 
@@ -1459,6 +1474,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
 	foreach(l, stmt->lockingClause)
 	{
@@ -1685,12 +1701,235 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
@@ -1936,6 +2175,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 
@@ -2410,6 +2650,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)

@@ -2477,6 +2718,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)

 
@@ -2664,9 +2906,15 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 	/*
@@ -2708,6 +2956,10 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 
+	/*
 
 	if (indirection)
@@ -2850,6 +3102,8 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 							   (LockingClause *) lfirst(l), false);
 	}

 	/* this must be done after collations, for reliable comparison of exprs */
@@ -3123,6 +3377,14 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 							 true,
 							 stmt->funccall->location);
 
+	/*

 

 
 
 			/*
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*

From 190cce26b83bb4cfa8abc3794de405dcb6822b95 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/21] Enhancing catalog for support session variables and
 related support

 

 

 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
index 79190470f71..099137711f6 100644
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 

 
 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 3 Aug 2024 07:40:00 +0200

Hi

fix build plpgsql

Regards

Pavel
From 2aca6766e9f601c9d974cfb507224a8e09f087a8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 17/21] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

An alternative can be reimplementation of LET statement inside expression
executor, and then SQL LET command can be executed in simple expression
execution, but this increase an complexity of executor, but still the
benefits is only inside plpgsql, so it is better to this optimization
inside plpgsql.
---
 src/backend/executor/spi.c                |  3 ++
 src/backend/parser/analyze.c              | 12 +++++
 src/backend/parser/gram.y                 |  8 ++++
 src/backend/parser/parser.c               |  1 +
 src/include/nodes/parsenodes.h            |  2 +
 src/include/parser/parser.h               |  4 ++
 src/pl/plpgsql/src/pl_exec.c              | 55 +++++++++++++++++++++++
 src/pl/plpgsql/src/pl_funcs.c             | 24 ++++++++++
 src/pl/plpgsql/src/pl_gram.y              | 28 +++++++++++-
 src/pl/plpgsql/src/pl_reserved_kwlist.h   |  1 +
 src/pl/plpgsql/src/pl_unreserved_kwlist.h |  1 +
 src/pl/plpgsql/src/plpgsql.h              | 12 +++++
 src/tools/pgindent/typedefs.list          |  1 +
 13 files changed, 151 insertions(+), 1 deletion(-)

 
+	/*

 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void
index 8182ce28aa1..923542e86f1 100644
@@ -200,7 +200,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 
 %type <list>	proc_exceptions
@@ -307,6 +307,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_INTO
 %token <keyword>	K_IS
 %token <keyword>	K_LAST
+%token <keyword>	K_LET
 %token <keyword>	K_LOG
 %token <keyword>	K_LOOP
 %token <keyword>	K_MERGE
@@ -876,6 +877,8 @@ proc_stmt		: pl_block ';'
 						{ $$ = $1; }
 				| stmt_rollback
 						{ $$ = $1; }
+				| stmt_let
+						{ $$ = $1; }
 				;
 
 stmt_perform	: K_PERFORM
@@ -992,6 +995,29 @@ stmt_assign		: T_DATUM
 					}
 				;
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
+						pmode = RAW_PARSE_PLPGSQL_LET;
+
+						new = palloc0(sizeof(PLpgSQL_stmt_let));
+						new->cmd_type = PLPGSQL_STMT_LET;
+						new->lineno   = plpgsql_location_to_lineno(@1);
+						new->stmtid = ++plpgsql_curr_compile->nstatements;
+
+						/* Push back the head name to include it in the stmt */
+						plpgsql_push_back_token(K_LET);
+						new->expr = read_sql_construct(';', 0, 0, ";",
+													   pmode,
+													   false, true,
+													   NULL, NULL);
+
+						$$ = (PLpgSQL_stmt *)new;
+					}
+				;
+
 stmt_getdiag	: K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
 					{
 						PLpgSQL_stmt_getdiag *new;
diff --git a/src/pl/plpgsql/src/pl_reserved_kwlist.h b/src/pl/plpgsql/src/pl_reserved_kwlist.h
index d338e9f6374..be94aa751a4 100644
--- a/src/pl/plpgsql/src/pl_reserved_kwlist.h
+++ b/src/pl/plpgsql/src/pl_reserved_kwlist.h
@@ -40,6 +40,7 @@ PG_KEYWORD("from", K_FROM)
 PG_KEYWORD("if", K_IF)
 PG_KEYWORD("in", K_IN)
 PG_KEYWORD("into", K_INTO)
+PG_KEYWORD("let", K_LET)
 PG_KEYWORD("loop", K_LOOP)
 PG_KEYWORD("not", K_NOT)
 PG_KEYWORD("null", K_NULL)
diff --git a/src/pl/plpgsql/src/pl_unreserved_kwlist.h b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
index 670b4cf0c1e..602628d7d2a 100644
--- a/src/pl/plpgsql/src/pl_unreserved_kwlist.h
+++ b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
@@ -69,6 +69,7 @@ PG_KEYWORD("info", K_INFO)
 PG_KEYWORD("insert", K_INSERT)
 PG_KEYWORD("is", K_IS)
 PG_KEYWORD("last", K_LAST)
+PG_KEYWORD("let", K_LET)
 PG_KEYWORD("log", K_LOG)
 PG_KEYWORD("merge", K_MERGE)
 PG_KEYWORD("message", K_MESSAGE)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 50c3b28472b..bb400629adb 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -128,6 +128,7 @@ typedef enum PLpgSQL_stmt_type
 	PLPGSQL_STMT_CALL,
 	PLPGSQL_STMT_COMMIT,
 	PLPGSQL_STMT_ROLLBACK,
+	PLPGSQL_STMT_LET,
 } PLpgSQL_stmt_type;
 
 /*
 
+/*
+ * Let statement
+ */
index 7db193fa58c..76fb6207952 100644

From e1d50174a1e66c82ba9ea04b1aab8afd28183604 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 18/21] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From afc73bddf6f07bd0b47d0cb38b251c95760ee48f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 20/21] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:

 

 
 
 /*

 

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+

From ead5d03ae18fbf350f0a75631c0a64f8834a9748 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 21/21] pg_restore -A, --variable

 
+     <varlistentry>

 

From 529674ef4451614f3134fdff984cad3ed8d51151 Mon Sep 17 00:00:00 2001

 
+/*

 

 

 select a from atacc1;

 

From 1e4480a824ae7deaba3370d1a2cce1d1279c75d6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 16/21] allow parallel execution queries with session variables

---

 

 

 
 	/*

 

From bef3e4c0ba6e11bb2d8a69220c5593bdc137ba67 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 14/21] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*

 /*
 
 CreateSessionVarStmt:

 

 

 
+	if (varnotnull)
 
 /*

 

+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From 85dbd5ddf496bee36d6bdd04a4a47fa42bf54e59 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 15/21] allow read an value of session variable directly from
 expression executor

is an argument of CALL or EXECUTE statements. For these cases we need to allow direct

 
-	/*

 

 
-	/*

 

 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.45.2

From 0fea467f0f7c7ddd36b80919328b1393bcd4d0ad Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 13/21] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{

 
 CreateSessionVarStmt:

 			if (isAgg)

 

 
 
+	if (vardefexpr)

 
 
 	if (verbose)

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From 8cc5038a675d79ba05cd0d358bc2dae5fcfea12a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 12/21] Implementation ON TRANSACTION END RESET clause

 

 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;

 

 

 

 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From 5fd1d766c7ca87ba5b134ffc4feb5398a56088bf Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 11/21] implementation of temporary session variables

 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index ace27ab49cd..7427caafdd1 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2308,7 +2308,7 @@ CommitTransaction(void)
 	smgrDoPendingSyncs(true, is_parallel_worker);

@@ -2905,6 +2905,7 @@ AbortTransaction(void)
@@ -5181,6 +5182,8 @@ CommitSubTransaction(void)
@@ -5345,6 +5348,8 @@ AbortSubTransaction(void)

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
 
 		/*

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index 0ebc4594472..7db193fa58c 100644

From a5339a811d13e6c2dd00045db6c8b3a4f837ae5a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 10/21] PREPARE LET support

 

-/* LET */

From d2a0782ac19d0af0fcbde38aeda79def0ee8d215 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 08/21] GUC session_variables_ambiguity_warning

 
 
  <sect1 id="ddl-others">
 
 /* GUC parameters */

 /* GUC parameters */

 

 

From d2d95b342b11314e19ebae1a2a40ff86993feb87 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 07/21] plpgsql tests

---

From 0834a4516a2f0ba4f4ed50ff9ce58389bb0ded73 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 09/21] EXPLAIN LET support

 

 

 DROP TABLE public.xxtab;

From b31f09495d1870bc66cab889285cab02acac15a2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 06/21] memory cleaning after DROP VARIABLE

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index dfc8cf2dcf2..ace27ab49cd 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -36,6 +36,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/session_variable.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "commands/waitlsn.h"
@@ -2306,6 +2307,9 @@ CommitTransaction(void)
 	 */
 	smgrDoPendingSyncs(true, is_parallel_worker);

 

+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

 	if (sessionvars)

 -- should be zero again

From 108cfb27b626cff69e05dc15cb0a715caba746f8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 1 Aug 2024 23:12:29 +0200
Subject: [PATCH 03/21] regress tests

---

From 5b5715bbc5418179fdb2dfb12c804b04ccd9c270 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 05/21] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+

From 086e330ad64a6ea4fb9d83cff51d793eb8630bef Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 04/21] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*
index a3bb22d6c27..6e655bbdc6e 100644
@@ -12250,4 +12250,12 @@

From 041e3795481fb9d1d56491459f45ac74dd15a062 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/21] Storage for session variables and SQL interface

 
+     <row>
 
+   <para>

 

 
+/*

index cede90c3b98..59a6340a85e 100644
index 7549be5dc3b..86da8105146 100644
 
+	/*

 
 						/*
 
+	/*
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*

 

 
 
 			/*
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 
 
index a1f8d03db1e..ef31b1881b1 100644

 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*
 
index e7bc3036a17..0ebc4594472 100644

From 20fcd2055afb7beeb1e541a6109006e46276f14a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/21] Enhancing catalog for support session variables and
 related support

 

index 3cf896b22fa..62f9015fdd1 100644

 
 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 
 
index d36f6001bb1..a3bb22d6c27 100644

 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du
index 75fc05093cc..e7bc3036a17 100644

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 7 Aug 2024 12:00:14 +0200

Hi

rebase + usage hash_seq_init_with_hash_value

Attention - regress tests fails due problem with
hash_seq_init_with_hash_value

Regards

Pavel
From a466f6541d9e11d9713875c68378958351279a59 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From 0b7a2aeeba347a7f09a7b23c545dbb37b3d6744b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*

 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index 93b628d31c7..457ae6c2284 100644

From 68429cee3e32ae07bcdcc4d4924aeeb2007f0a38 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)
 
index 6d0371b334f..1153ea2678c 100644
 
+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*
@@ -295,8 +332,25 @@ unregister_session_variable_xact_drop(Oid varid)
@@ -395,7 +449,7 @@ remove_invalid_session_variables(bool atEOX)
 			{
 				Oid			varid = svar->varid;
 
@@ -431,6 +485,94 @@ remove_session_variables_with_reset_at_eox(void)
 
+/*
@@ -478,6 +620,8 @@ AtPreEOXact_SessionVariables(bool isCommit)
 
+	remove_prev_values_at_eox(isCommit);
+
 	/*
 	 * We have to clean xact_drop_items. All related variables are dropped
 	 * now, or lost inside aborted transaction.
@@ -623,6 +767,7 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)

 	svar->is_domain = (get_typtype(varform->vartype) == TYPTYPE_DOMAIN);
 	svar->domain_check_extra = NULL;
@@ -648,6 +793,17 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	svar->isnull = true;
 	svar->value = (Datum) 0;
 
+	if (svar->is_transact)
+	{
 
 	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
@@ -681,6 +837,69 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	ReleaseSysCache(tup);
 }
 
+/*
@@ -693,6 +912,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	Assert(svar);
 	Assert(!isnull || value == (Datum) 0);
@@ -728,6 +951,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	else
 		_svar = svar;
 
@@ -739,7 +977,37 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	else
 		newval = value;

 	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new value",
 		 get_namespace_name(get_session_variable_namespace(svar->varid)),
@@ -752,6 +1020,9 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	svar->value = newval;
 	svar->isnull = isnull;
 
 	/* don't allow more changes of value when variable is IMMUTABLE */
 	if (svar->is_immutable)
@@ -855,6 +1126,8 @@ get_session_variable(Oid varid)
 	else
 		svar->is_valid = false;
 
+reinit:
+
 	/*
 	 * Force setup for not yet initialized variables or variables that cannot
 	 * be validated.
@@ -887,6 +1160,28 @@ get_session_variable(Oid varid)
 			 varid);
 	}
 
+	/*

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:
index a036c0d06ee..42599eac81d 100644
@@ -5423,6 +5423,7 @@ getVariables(Archive *fout)
@@ -5447,6 +5448,7 @@ getVariables(Archive *fout)
@@ -5469,6 +5471,7 @@ getVariables(Archive *fout)
 
@@ -5497,6 +5500,7 @@ getVariables(Archive *fout)
 
@@ -5544,6 +5548,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5561,12 +5566,13 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 
 
 /*

 

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+

From efe722cf74b8fc435841178741f36828316f350d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From d69b4da52bf549358258dfdbc53fd1894797b921 Mon Sep 17 00:00:00 2001

 
+/*

 

 

 select a from atacc1;

 

From c52891010df78694377e5d9d6cc6c115645b3ad1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

 
 	/*

 

From 82d5063ed427f50779613d10ad178075fc74fe6f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*

 

 
-	/*

 

 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.45.2

From 4e71186ee45b9e2e7efe05bb9a3bde79a5c58efe Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{
 
index a96d5dc85ba..c2307d3bd23 100644
@@ -515,11 +516,68 @@ AtEOSubXact_SessionVariables(bool isCommit,
 	}
 }
 
+/*
@@ -569,6 +627,9 @@ setup_session_variable(SVariable svar, Oid varid)
 
+	if (!is_write)
 
@@ -598,7 +659,7 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	 */
 	if (!svar->is_valid)
 	{
@@ -733,7 +794,25 @@ get_session_variable(Oid varid)
 
 		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by READ)",
 			 get_namespace_name(get_session_variable_namespace(varid)),
@@ -797,7 +876,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 
 	if (!found)
 	{

 
 CreateSessionVarStmt:

 			if (isAgg)
 
index 1e771f4beae..2a2ed1fe6f2 100644
@@ -5417,6 +5417,7 @@ getVariables(Archive *fout)
@@ -5442,6 +5443,7 @@ getVariables(Archive *fout)
@@ -5458,6 +5460,7 @@ getVariables(Archive *fout)
 
@@ -5493,6 +5496,11 @@ getVariables(Archive *fout)
 		varinfo[i].dacl.initprivs = NULL;
 		varinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_varowner));
 
@@ -5525,6 +5533,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5537,6 +5546,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

@@ -5556,6 +5566,10 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (vardefexpr)

 
 
 	if (verbose)

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From 8c5226e9699cc7eacd4a74025f7e3d813583a4a7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

index c2307d3bd23..6d0371b334f 100644
 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 typedef SVariableData *SVariable;
@@ -568,6 +574,23 @@ eval_assign_defexpr(SVariable svar, HeapTuple tup)

 		FreeExecutorState(estate);
 	}
@@ -598,6 +621,9 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)

@@ -627,9 +653,31 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 
-	if (!is_write)
 
+		/*
 
@@ -649,6 +697,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	Assert(svar);
 	Assert(!isnull || value == (Datum) 0);
 
@@ -689,6 +752,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*

 /*
 
 CreateSessionVarStmt:

index 2a2ed1fe6f2..a036c0d06ee 100644
@@ -5421,6 +5421,8 @@ getVariables(Archive *fout)
@@ -5443,6 +5445,8 @@ getVariables(Archive *fout)
@@ -5463,6 +5467,8 @@ getVariables(Archive *fout)
 
 	i_varowner = PQfnumber(res, "varowner");
 	i_varacl = PQfnumber(res, "varacl");
@@ -5489,6 +5495,8 @@ getVariables(Archive *fout)
 			pg_strdup(PQgetvalue(res, i, i_varxactendaction));

@@ -5535,7 +5543,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5549,12 +5559,14 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 
 	if (OidIsValid(varcollation))
 	{
@@ -5566,6 +5578,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (varnotnull)
 
 /*

 

+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From e6ab4465bdcf7606de1e014c342ba916bfdd84b1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

index 55cb893acc2..a96d5dc85ba 100644
 
+	bool		reset_at_eox;
+
 	/*
 
+/*
@@ -390,6 +398,32 @@ remove_invalid_session_variables(bool atEOX)
 	}
 }
 
+/*
@@ -397,6 +431,8 @@ remove_invalid_session_variables(bool atEOX)
@@ -508,6 +544,21 @@ setup_session_variable(SVariable svar, Oid varid)
 
+	/*
 
 	svar->isnull = true;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index dd217b69e54..1e771f4beae 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5417,6 +5417,7 @@ getVariables(Archive *fout)
 	int			i_varnamespace;
 	int			i_vartype;
 	int			i_vartypname;
+	int			i_varxactendaction;
 	int			i_varowner;
 	int			i_varcollation;
 	int			i_varacl;
@@ -5434,6 +5435,7 @@ getVariables(Archive *fout)
 	/* Get the variables in current database. */
 	appendPQExpBuffer(query,
 					  "SELECT v.tableoid, v.oid, v.varname,\n"
+					  "       v.varxactendaction,\n"
 					  "       v.varnamespace, v.vartype,\n"
 					  "       pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n"
 					  "       CASE WHEN v.varcollation <> t.typcollation "
@@ -5456,6 +5458,7 @@ getVariables(Archive *fout)
 	i_varnamespace = PQfnumber(res, "varnamespace");
 	i_vartype = PQfnumber(res, "vartype");
 	i_vartypname = PQfnumber(res, "vartypname");
+	i_varxactendaction = PQfnumber(res, "varxactendaction");
 	i_varcollation = PQfnumber(res, "varcollation");
 
 	i_varowner = PQfnumber(res, "varowner");
@@ -5479,6 +5482,9 @@ getVariables(Archive *fout)

 		varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl));
@@ -5519,6 +5525,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	PQExpBuffer query;
 	char	   *qualvarname;
 	const char *vartypname;
+	const char *varxactendaction;
 	Oid			varcollation;
 
 	/* Skip if not to be dumped */
@@ -5530,6 +5537,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
@@ -5548,6 +5556,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}

 
 
 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From 420f7e36297d4a6cf220087f123cfd36e60f4635 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

 

-/* LET */

From 2b24efc7ce5235630de1a3070502df3215e8da75 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);
 
index 542624c8000..55cb893acc2 100644
 
 #include "access/xact.h"

@@ -143,16 +164,45 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
 	}
 }
 
+/*
 
+	if (XactEndAction == VARIABLE_XACTEND_DROP)
+	{
+		Assert(isTempNamespace(get_session_variable_namespace(varid)));
+
+		unregister_session_variable_xact_drop(varid);
+	}
+
 	if (sessionvars)
 	{
 		bool		found;
@@ -175,6 +225,57 @@ SessionVariableDropPostprocess(Oid varid)
 	}
 }
 
+/*
@@ -290,15 +391,92 @@ remove_invalid_session_variables(bool atEOX)
 }
 
 /*

 /*
 
 		/*

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index 7e13ae6b01e..93b628d31c7 100644
@@ -2793,6 +2793,7 @@ SupportRequestWFuncMonotonic
 SVariable
 SVariableData
 SVariableState
+SVariableXActDropItem
 Syn
 SyncOps
 SyncRepConfigData
-- 
2.45.2

From 97621a0cdffa34fc5793656706309f34b2730f7b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

 

 DROP TABLE public.xxtab;

From 0042d1e87b89e47811f17db2741acd5c88834cb6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From 9f0d2add2893eab4c5eab52c588fddc472498bfc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index a1a1d58a436..57a3eab842c 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10710,6 +10710,66 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>

  <sect1 id="ddl-others">
 
 /* GUC parameters */

index c0a52cdcc3e..f834c6452bd 100644
@@ -1595,6 +1595,15 @@ struct config_bool ConfigureNamesBool[] =
 
 /* GUC parameters */

 

 

From ab29ad7c3498af46c1fcabfe445931168a8142f6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 src/backend/commands/session_variable.c       | 172 +++++++++++++-
 9 files changed, 681 insertions(+), 2 deletions(-)

 
 
index 866566da32e..542624c8000 100644

+	/*

@@ -117,6 +139,39 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
 		Assert(hashvalue == 0 || svar->hashvalue == hashvalue);
 
 		svar->is_valid = false;
 
@@ -169,6 +224,83 @@ is_session_variable_valid(SVariable svar)
 	return result;
 }
 
+/*
@@ -198,6 +330,8 @@ setup_session_variable(SVariable svar, Oid varid)

@@ -321,22 +455,44 @@ get_session_variable(Oid varid)
 	if (!sessionvars)
 		create_sessionvars_hashtables();

 	if (found)
 	{
+		/*
@@ -397,6 +553,16 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)

@@ -596,6 +762,8 @@ pg_session_variables(PG_FUNCTION_ARGS)

 
 	if (sessionvars)

 -- should be zero again

From 80956b034fc4d26fa6837ba54436eb0ee3815c8e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
 
index 1fad24d1fb1..866566da32e 100644

-	Assert(sessionvars);
+	/*
 
 	/*
 	 * If the hashvalue is not specified, we have to recheck all currently
@@ -676,3 +682,23 @@ pg_session_variables(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
+
+/*

index bdac691af13..84415f0caa1 100644
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+

From ece09d8da5cc82b5266a75aa754e46efe5a023a8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 9fa169f53dc..1fad24d1fb1 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -574,3 +574,105 @@ ExecuteLetStmt(ParseState *pstate,
 
 	PopActiveSnapshot();
 }
+
+/*

From dc59cf4dd8ad2a2605f4ab05bab56985e0f8e7ee Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 src/backend/commands/session_variable.c       | 576 +++++++++++
 .../regress/expected/session_variables.out    | 922 ++++++++++++++++++
 src/test/regress/sql/session_variables.sql    | 637 ++++++++++++
 59 files changed, 3931 insertions(+), 23 deletions(-)

+     <row>
 
+   <para>

 

 
+/*

 
+	/*
 
index 00000000000..9fa169f53dc
@@ -0,0 +1,576 @@
+	if (hashvalue == 0)
+		hash_seq_init_with_hash_value(&status, sessionvars, hashvalue);
+		Assert(hashvalue == 0 || svar->hashvalue == hashvalue);

 						/*
 
+	/*
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*

 

 
 
 			/*
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

index 45af3877ea9..bdac691af13 100644
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*
index aca766042db..16e1d01ddc6 100644
@@ -55,3 +55,925 @@ SET ROLE TO DEFAULT;
index 42dfb95a022..d5bb5dc83be 100644
@@ -59,3 +59,640 @@ DROP VARIABLE svartest.var1;
 DROP SCHEMA svartest;
 
index fa5d533b4da..7e13ae6b01e 100644
@@ -2596,6 +2597,7 @@ SerializedTransactionState
@@ -2788,6 +2790,9 @@ SupportRequestRows

From 64b1fb449f9be84fea902844a662eccf839f9119 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 

index 0f7154b76ab..af8eb371094 100644

 
 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*
index 1f94f4fdbbc..b0c75b3ea66 100644

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }
index b2ea8125c92..45af3877ea9 100644

 
 	return subname;
 }
+

 
 
 	int			useDB;
index b6e01d3d292..dd217b69e54 100644
@@ -5400,6 +5401,190 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
@@ -9962,7 +10147,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -10561,6 +10747,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
@@ -15010,6 +15199,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -18621,6 +18813,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 

 
 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du
index 547d14b3e7c..fa5d533b4da 100644
@@ -3067,6 +3070,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.45.2

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 8 Aug 2024 07:17:00 +0200

Hi

fresh rebase + revert usage of hash_seq_init_with_hash_value

regards

Pavel
From ef525034027ff8cb23a1b6e2b8f4ea0c61d88b01 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From 2716ee28ac3f3475f33e3e806587fb05faa24e33 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)
 
index 02da70a7390..facd37f161f 100644
 
+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*
@@ -293,8 +330,25 @@ unregister_session_variable_xact_drop(Oid varid)
@@ -393,7 +447,7 @@ remove_invalid_session_variables(bool atEOX)
 			{
 				Oid			varid = svar->varid;
 
@@ -429,6 +483,94 @@ remove_session_variables_with_reset_at_eox(void)
 
+/*
@@ -476,6 +618,8 @@ AtPreEOXact_SessionVariables(bool isCommit)
 
+	remove_prev_values_at_eox(isCommit);
+
 	/*
 	 * We have to clean xact_drop_items. All related variables are dropped
 	 * now, or lost inside aborted transaction.
@@ -621,6 +765,7 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)

 	svar->is_domain = (get_typtype(varform->vartype) == TYPTYPE_DOMAIN);
 	svar->domain_check_extra = NULL;
@@ -646,6 +791,17 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	svar->isnull = true;
 	svar->value = (Datum) 0;
 
+	if (svar->is_transact)
+	{
 
 	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
@@ -679,6 +835,69 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	ReleaseSysCache(tup);
 }
 
+/*
@@ -691,6 +910,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	Assert(svar);
 	Assert(!isnull || value == (Datum) 0);
@@ -726,6 +949,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	else
 		_svar = svar;
 
@@ -737,7 +975,37 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	else
 		newval = value;

 	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new value",
 		 get_namespace_name(get_session_variable_namespace(svar->varid)),
@@ -750,6 +1018,9 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	svar->value = newval;
 	svar->isnull = isnull;
 
 	/* don't allow more changes of value when variable is IMMUTABLE */
 	if (svar->is_immutable)
@@ -853,6 +1124,8 @@ get_session_variable(Oid varid)
 	else
 		svar->is_valid = false;
 
+reinit:
+
 	/*
 	 * Force setup for not yet initialized variables or variables that cannot
 	 * be validated.
@@ -885,6 +1158,28 @@ get_session_variable(Oid varid)
 			 varid);
 	}
 
+	/*

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:

 

 
 
 /*

 

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+

From 5c72838174394ed7e8332405d38522a321e9fd80 Mon Sep 17 00:00:00 2001

index 3891b475325..e44c6112575 100644
 
+/*

 

 

 select a from atacc1;

 

From 5cdfb024aa85138739d068e07ab04f38a8204c8f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From 306e0a033394bef5e77c2bceca414f1fa430154d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

index d6516b1bca3..7defca33be2 100644
@@ -2985,6 +2985,9 @@ _SPI_error_callback(void *arg)

+	/*

 /*

 
index ea9740e3f89..34e6029b289 100644

+			case PLPGSQL_STMT_LET:
+				rc = exec_stmt_let(estate, (PLpgSQL_stmt_let *) stmt);
+				break;
+
 			default:
 				/* point err_stmt to parent, since this one seems corrupt */
 				estate->err_stmt = save_estmt;
@@ -5003,6 +5010,54 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt)
 	return PLPGSQL_RC_OK;
 }
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */

From 54261fe7a2d98993ea8c8bbe3e44c98620a06a6d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

 
 	/*

 

From c0fb8039d3744ab79275e74150f9e180c63674bc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*

 
index ea47c4d6f9c..4866e7dbcdf 100644

 
-	/*

 
 
index f76deb56d5a..ea9740e3f89 100644
@@ -8103,8 +8103,7 @@ exec_is_simple_query(PLpgSQL_expr *expr)
 
 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.45.2

From bc5e11a6776b7cfac94229b60139a3bd54430760 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

index 2bea532eff3..02da70a7390 100644
 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 typedef SVariableData *SVariable;
@@ -566,6 +572,23 @@ eval_assign_defexpr(SVariable svar, HeapTuple tup)

 		FreeExecutorState(estate);
 	}
@@ -596,6 +619,9 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)

@@ -625,9 +651,31 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 
-	if (!is_write)
 
+		/*
 
@@ -647,6 +695,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	Assert(svar);
 	Assert(!isnull || value == (Datum) 0);
 
@@ -687,6 +750,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*

 /*
 
 CreateSessionVarStmt:

 

 

 
+	if (varnotnull)
 
 /*

 

+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From 87767f78c02fa48956de35858c93e34f3b089886 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{
 
index bcb5c9bd643..2bea532eff3 100644
@@ -513,11 +514,68 @@ AtEOSubXact_SessionVariables(bool isCommit,
 	}
 }
 
+/*
@@ -567,6 +625,9 @@ setup_session_variable(SVariable svar, Oid varid)
 
+	if (!is_write)
 
@@ -596,7 +657,7 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	 */
 	if (!svar->is_valid)
 	{
@@ -731,7 +792,25 @@ get_session_variable(Oid varid)
 
 		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by READ)",
 			 get_namespace_name(get_session_variable_namespace(varid)),
@@ -795,7 +874,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 
 	if (!found)
 	{

 
 CreateSessionVarStmt:

 			if (isAgg)
index fc16ed3e60b..3891b475325 100644
 
@@ -2077,6 +2079,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;

 

 
+	if (vardefexpr)

 
 
 	if (verbose)

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From 178e28e24038b094590090e9a1b7fb1b53c0812f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

index a50fc733102..bcb5c9bd643 100644
 
+	bool		reset_at_eox;
+
 	/*
 
+/*
@@ -388,6 +396,32 @@ remove_invalid_session_variables(bool atEOX)
 	}
 }
 
+/*
@@ -395,6 +429,8 @@ remove_invalid_session_variables(bool atEOX)
@@ -506,6 +542,21 @@ setup_session_variable(SVariable svar, Oid varid)
 
+	/*
 
 	svar->isnull = true;

 

 

 

 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From e07c63c9869e3c068eca0a6dbcc3a69390ab18d8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

 

-/* LET */

From 6e215e94c79f47c2e107c46553632b259defc620 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);
 
index 78e6f005714..a50fc733102 100644
 
 #include "access/xact.h"

@@ -141,16 +162,45 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
 	}
 }
 
+/*
 
+	if (XactEndAction == VARIABLE_XACTEND_DROP)
+	{
+		Assert(isTempNamespace(get_session_variable_namespace(varid)));
+
+		unregister_session_variable_xact_drop(varid);
+	}
+
 	if (sessionvars)
 	{
 		bool		found;
@@ -173,6 +223,57 @@ SessionVariableDropPostprocess(Oid varid)
 	}
 }
 
+/*
@@ -288,15 +389,92 @@ remove_invalid_session_variables(bool atEOX)
 }
 
 /*

 /*
 
 		/*

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables

From e1f7fe1270853c0e09c0ebf1355afc8008021d92 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

 

 DROP TABLE public.xxtab;

From a64d5b3199a110d3011d526385a633948f40178d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

 
 
  <sect1 id="ddl-others">
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 32c93f2c6c2..fc16ed3e60b 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -47,6 +47,7 @@
 
 /* GUC parameters */

 /* GUC parameters */

 

 

From ae288d2acdf94e9ab217754725d8a67a7b99c8df Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From 947885f15584a32b2888cd739ea989ff02d3da9c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 

index 9e5a4ad45de..78e6f005714 100644

+	/*

@@ -114,6 +136,39 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
@@ -167,6 +222,83 @@ is_session_variable_valid(SVariable svar)
 	return result;
 }
 
+/*
@@ -196,6 +328,8 @@ setup_session_variable(SVariable svar, Oid varid)

@@ -319,22 +453,44 @@ get_session_variable(Oid varid)
 	if (!sessionvars)
 		create_sessionvars_hashtables();

 	if (found)
 	{
+		/*
@@ -395,6 +551,16 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)

@@ -594,6 +760,8 @@ pg_session_variables(PG_FUNCTION_ARGS)

 
 	if (sessionvars)

 -- should be zero again

From 04ec553ce81493a5a328d4d85c20d8e11a23a4d8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*

From 4fad9680aa16ab2b8e7745aa7ef14a9f890f8ba0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+

From 2645b194dc356dde369d316f013136ad707a5443 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 59 files changed, 3929 insertions(+), 23 deletions(-)

+     <row>
 
+   <para>

 

 
+/*

 
+	/*

 
 						/*
 
+	/*
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*

 

 
 
 			/*
index 56e413da9f5..32c93f2c6c2 100644
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
@@ -1817,6 +2027,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
@@ -1860,6 +2071,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 
 			/*
@@ -3199,6 +3413,10 @@ ParseExprKindName(ParseExprKind exprKind)
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index ea9740e3f89..f76deb56d5a 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8103,7 +8103,8 @@ exec_is_simple_query(PLpgSQL_expr *expr)
 		query->sortClause ||
 		query->limitOffset ||
 		query->limitCount ||
-		query->setOperations)
+		query->setOperations ||
+		query->hasSessionVariables)
 		return false;
 
 	/*

From 0c12548a1486ce37dc65395d5b9f18328951b05e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 

 

 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 

 
 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 15 Aug 2024 07:55:15 +0200

>   letter and have to period in the end:

Should it be "have not to period in the end" ?

>   /* this is a typical single line comment */

I fixed almost all without parts related to psql and executor - there
almost all current comments break the mentioned rule. So I use the style
used in these files.  I can fix it (if you like it) - or it can be fixed
generally in a separated patch set.

>   Is that working as intended?  I don't understand the error message.

fixed in 0002 patch (variables cannot be used as EXECUTE argument) and 0014
(enable usage variables as an argument of EXECUTE)

merged

>   I'd say that this should be the same for session variables.

I prefer the current state, but I don't have a very strong opinion about
it. It can save 115 lines of almost trivial code, but I think the user
experience can be much worse.  Using composite types in tables is not too
common a pattern (and when I read some recommendations for Oracle, it is an
antipattern), but usage of composite variables is common (it can hold a
row). Moreover, we talked long about possible identifier's collisions, and
the pattern schema.variable is very good protection against possible
collisions. Probably the pattern catalog.schema.variable.field is not too
interesting but the support has 50 lines.

More, the plpgsql supports pattern label.variable.field, so can be little
bit unfriendly if session variables doesn't support "similar" pattern

create type t as (x int, y int);
do $$
<<lab1>>declare v t;
begin
  v := (20,20); raise notice '%', lab1.v.x;
end $$;
NOTICE:  20
DO

>     variable in the code block where it is used.

moved

>     Less indentation is better.
done

>   > +    * For now, we don't need to repeat validation. Only svar should

This comment is related to assertions. Before I had there
`Assert(svar->is_valid)`, because I expected it. But it was not always
true. And although it is true, we don't need to validate a variable,
because at this moment, the variable should be locked, and then we can
return content safely.

>   Why?  Is that a performance measure?  The comment should explain that.

Session variables are volatile internally - some function that uses LET
statements can be called more times inside a query. This behavior is not
consistent with plpgsql's variables or external parameters. And if we want
to support parallel queries, then volatile session variables can be pretty
messy (and unusable). If we want the same behaviour for queries with or
without parallel support, then the session variables should be "stable"
(like stable functions). Simple implementation is using "snapshot" of
values of used session variables when query is started. This "snapshot" is
immutable, so we don't need to make a copy more times.

I changed this comment

<-->/*
<--> * The executor doesn't work with session variables directly. Values of
<--> * related session variables are copied to a dedicated array, and this
array
<--> * is passed to the executor. This array is stable "snapshot" of values
of used
<--> * session variables. There are three benefits of this strategy:
<--> *
<--> * - consistency with external parameters and plpgsql variables,
<--> *
<--> * - session variables can be parallel safe,
<--> *
<--> * - we don't need make fresh copy for any read of session variable
<--> *    (this is necessary because the internally the session variable can
<--> *    be changed inside query execution time, and then a reference to
<--> *    previously returned value can be corrupted).
<--> */

> doc/src/sgml/parallel.sgml.

merged (and removed in 0015)

> I hope to get to review the other patches at some later time.

Attached new patchset

Regards

Pavel
From b8db256632f45a146aec3322382c7ce9b6b3a07b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 src/backend/catalog/namespace.c               | 315 ++++++
 src/backend/executor/execExpr.c               |  36 +
 src/backend/executor/execMain.c               |  57 ++

+     <row>
 
+   <para>

 

index fd994c92d4f..375fea5dae8 100644
@@ -3397,6 +3397,321 @@ LookupVariable(const char *nspname,
 	return varoid;
 }
 
+/*
+					/* the last field of list can be star too */
index e3a861c8cba..ef89594a268 100644

 
+	/*

index 66dda8e5e69..dd4a779233a 100644
@@ -991,6 +992,41 @@ ExecInitExprRec(Expr *node, ExprState *state,
+							/* parameter sanity checks */
 
 						/*
index 4d7c92d63c1..ef51a0aca8f 100644
@@ -196,6 +198,61 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 
+	/*
+	 * related session variables are copied to a dedicated array, and this array
+	 * is passed to the executor. This array is stable "snapshot" of values of
+	 * used session variables. There are three benefits of this strategy:
+	 * - consistency with external parameters and plpgsql variables,
+	 * - session variables can be parallel safe,
+	 * - we don't need make fresh copy for any read of session variable
+	 *    (this is necessary because the internally the session variable can
+	 *    be changed inside query execution time, and then a reference to
+	 *    previously returned value can be corrupted).
+		/* create the array used for passing values of used session variables */
+		/* fill the array */
 
 	/*

+	/*
index 7aed84584c6..d84adfdcced 100644

+/*
+		/* recurse into subselects */
 
 /*
+		/* we will modify object */

+/*
+	/* append this variable to global, register dependency */
 
 			/*

 
 
 				/*
 
index e901203424d..f53653aa368 100644

 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+	/* there can't be any outer WITH to worry about */

 

 
 
+	/*
 
 	if (indirection)

 
+	/*
 
index 0a24f21ba35..b328cde99fe 100644

 
@@ -12750,6 +12752,47 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 
+					/* create target list for implicit query */
@@ -17781,6 +17824,7 @@ unreserved_keyword:
@@ -18393,6 +18437,7 @@ bare_label_keyword:

 			/*
index 56e413da9f5..763b7b77975 100644
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
+				/* success, so generate a FieldSelect expression */
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index 4039ee0df73..ef4c0990f9a 100644
@@ -8375,6 +8377,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
@@ -13155,6 +13165,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
index 87f1519ec65..8c384788aa8 100644
@@ -611,6 +611,18 @@ typedef struct AsyncRequest
 								 * tuples) */
 } AsyncRequest;
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+	Oid			varid;
+	Oid			typid;
+	bool		isnull;
+	Datum		value;
+} SessionVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -663,6 +675,10 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param
 
index 6ac612299f3..0f48bb77d35 100644

 
 
 /* ----------------
 
 	/*
 
2.46.0

From 76b65907421676d5751994c853b3a190975bea1d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

index 23e7fa99f77..6ad81e37f9d 100644
 
 	PopActiveSnapshot();
 }
+
+/*
+				/* when we see data in catalog */
index ade84e23dea..89e087f6973 100644
@@ -12258,4 +12258,12 @@
 
2.46.0

From 5ad4ce056a9e5b9f2c77111a20077e37877f66c3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
 
index 6ad81e37f9d..6cef627afde 100644

-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*
+	/* destroy hash table and reset related memory context */
+	/* release memory allocated by session variables */
index b328cde99fe..3fe80424e95 100644

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+
2.46.0

From 1c786f84f80d11ae2c43e00a898653fa4fffbb1b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

index dfc8cf2dcf2..51ef21e596c 100644
 
+	/* remove values of dropped session variables from memory */
+	AtPreEOXact_SessionVariables();
+
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
index ef89594a268..0fb8c7f22a4 100644
 
 	table_close(rel, RowExclusiveLock);
+
+	/* do the necessary cleanup if needed in local memory */
+	SessionVariableDropPostprocess(varid);
 }
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 6cef627afde..7602fd24efa 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"

+	/*

 
+/*
+	/* we cannot to do it when transaction is aborted */

 
+	if (validated_lxid == InvalidLocalTransactionId ||
+		validated_lxid != MyProc->vxid.lxid)
+	{
+		/* throw invalid entries, skip entries dropped by this transaction. */
+		remove_invalid_session_variables(false);
+
+		/* don't repeat it in this transaction */
+		validated_lxid = MyProc->vxid.lxid;
+	}
+
 	svar = (SVariable) hash_search(sessionvars, &varid,
 								   HASH_ENTER, &found);
 
 	if (found)
 	{
+		/*
+				/* we don't expect it */
 
+	if (validated_lxid == InvalidLocalTransactionId ||
+		validated_lxid != MyProc->vxid.lxid)
+	{
+		/* throw invalid entries, skip entries dropped by this transaction */
+		remove_invalid_session_variables(false);
+
+		/* don't repeat it in this transaction */
+		validated_lxid = MyProc->vxid.lxid;
+	}
+
 	svar = (SVariable) hash_search(sessionvars, &varid,
 								   HASH_ENTER, &found);

 
 
 	if (sessionvars)

 -- should be zero again
2.46.0

From 02498fcdc385de23fc6e9ad57dd47050f96650ea Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 

index 5dd95d73a1a..e52bc984aa9 100644
@@ -25681,6 +25681,19 @@ SELECT relname FROM pg_class WHERE pg_table_is_visible(oid);

 
 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:
 
index 43b707699d7..fd994c92d4f 100644
 
+/*
+ * VariableIsVisible
+				/* found it first in path */
+				/* found something else first in path */
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)
 
index 00000000000..e3a861c8cba
+	/* post creation hook for new function */
+	/* disallow pseudotypes */
+	/* complain if COLLATE is applied to an uncollatable type */
+	/* we want SessionVariableCreatePostprocess to see the catalog changes. */

 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*
index 7a36db6af6d..fc1345181d4 100644
 
index c3f25582c38..0a24f21ba35 100644

 

@@ -6988,6 +7019,7 @@ object_type_any_name:
 
 /*
@@ -7864,6 +7896,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7909,6 +7949,14 @@ privilege_target:

@@ -8106,6 +8154,7 @@ defacl_privilege_target:

@@ -9888,6 +9937,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10249,6 +10316,24 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10530,6 +10615,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17859,6 +17952,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18513,6 +18608,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P

 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
index b6e01d3d292..5d1cac709a1 100644
 
+/*
+ * getVariables
+	/* get the variables in current database */
+		/* do not try to dump ACL if no ACL exists */
+		/* decide whether we want to dump it */
+	/* skip if not to be dumped */
+	/* dump comment if any */
+	/* dump ACL if any */

+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 
 
index 4abc6d95262..ade84e23dea 100644

 
+
index bb191b1f469..6ac612299f3 100644
@@ -485,6 +485,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du
2.46.0

From 638a4c73f413dabe913eeada2262ef59a0ebd5bc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

2.46.0

From e23feafbc6770573f551b24e4549bc2190b0c44f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

index 2937384b001..82d3a892c36 100644

  <sect1 id="ddl-others">
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 763b7b77975..c9be3d6b38e 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -47,6 +47,7 @@
 
 /* GUC parameters */

+					/* this path will ending by WARNING. Unlock variable first */
index 79ecaa4c4c2..90a01213209 100644
index 667e0dc40a2..3eae1c15160 100644
 
 /* GUC parameters */

 

 
2.46.0

From ad5fea56da9c3f2e0cc24ea4cf8f0e54feffd29a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

index 8d749d61f12..30c1212ac13 100644
@@ -12145,7 +12145,8 @@ PreparableStmt:

 
-/* LET */

2.46.0

From 3809ed3ae68f0450f3a18d1df58e21d907da8df5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

index 3fe80424e95..8d749d61f12 100644
@@ -12114,7 +12114,8 @@ ExplainableStmt:

 
 DROP TABLE public.xxtab;
2.46.0

From 5756da513482df1b482605aa4dfc51bbe10a453a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

index 51ef21e596c..3b2ad93026b 100644
 
 	/* remove values of dropped session variables from memory */
-	AtPreEOXact_SessionVariables();
+	AtPreEOXact_SessionVariables(true);
 
index 0fb8c7f22a4..d17b2756165 100644

 /*

+	/* check consistency of arguments */

 	/* we want SessionVariableCreatePostprocess to see the catalog changes. */

 

 
 	ReleaseSysCache(tup);
 
 	/* do the necessary cleanup if needed in local memory */
-	SessionVariableDropPostprocess(varid);
+	SessionVariableDropPostprocess(varid, XactEndAction);
 }
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 7602fd24efa..67f3de89ccc 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -16,6 +16,8 @@
 
 #include "access/xact.h"

 
+/*

+/*
 
 /*
-	/* we cannot to do it when transaction is aborted */
+				/* iterate only over entries that are still pending */

 /*
index f53653aa368..bd1b99dc8a7 100644
 
 		/*
index 30c1212ac13..8055675a7be 100644

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
2.46.0

From 3e287517117c4a522a1098db31212b70fe456129 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

index 67f3de89ccc..8e9049561d5 100644
 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;
index 8055675a7be..5af2e8a0651 100644
 
index 5d1cac709a1..abd9a55dcd6 100644
 	/* get the variables in current database */

 
 
 	/* skip if not to be dumped */
@@ -5530,6 +5537,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 

 
 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4
 
2.46.0

From e5ddf956babc2bbeb0c8999fcb3a858a8336158a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>
 
index d17b2756165..781587f9913 100644

+	if (varDefexpr)

 
 	/* check consistency of arguments */
 	if (stmt->XactEndAction == VARIABLE_XACTEND_DROP
@@ -226,6 +242,16 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 						format_type_be(typid)),
 				 parser_errposition(pstate, stmt->collClause->location)));
 
+	if (stmt->defexpr)
+	{
 
index 8e9049561d5..6d5aa9d0182 100644
 
+/*
 
+	if (!is_write)
 
+			/* this entry cannot be valid, remove from sessionvars */

 	if (!found)
 	{
 
index 5af2e8a0651..9ee6ee94a11 100644

 CreateSessionVarStmt:

 			if (isAgg)
index c9be3d6b38e..68751fcb086 100644
 
index abd9a55dcd6..be893b50209 100644

 		/* do not try to dump ACL if no ACL exists */

 
 
+	if (vardefexpr)

 
 
 	if (verbose)

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+
2.46.0

From 4cc6dee2f385c580f949289495cc2f4b3315ced0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 src/backend/catalog/pg_variable.c             |   8 +
 16 files changed, 523 insertions(+), 46 deletions(-)

      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

index 781587f9913..34b0b971516 100644

 	if (varDefexpr)
@@ -259,6 +265,8 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 							   varowner,
 							   collation,
 							   stmt->if_not_exists,
+							   stmt->not_null,
+							   stmt->is_immutable,
 							   cooked_default,
 							   stmt->XactEndAction);
 
index 6d5aa9d0182..9d4112c07b3 100644
 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*
index 9ee6ee94a11..ecda13e9af5 100644

 /*
 
 CreateSessionVarStmt:

index be893b50209..e8c1e841836 100644

 
 
 	/* skip if not to be dumped */

 
 
+	if (varnotnull)
 
 /*

 

+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
2.46.0

From d86f77a7f52d87c286cb28440ccfeb11597b8a50 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 src/backend/executor/execExpr.c               | 82 ++++++++++++++-----
 10 files changed, 117 insertions(+), 59 deletions(-)

-	/*
 
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index dd4a779233a..2c6f7051ec8 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1005,25 +1005,69 @@ ExecInitExprRec(Expr *node, ExprState *state,
 								es_num_session_variables = state->parent->state->es_num_session_variables;
 							}
 
-							/* parameter sanity checks */
+								/* parameter sanity checks */

 
index bd1b99dc8a7..46ce68e8ad7 100644
 
-	/*

 

 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.46.0

From 117f0aa838fa3a55ace2d564f75490a81064bb6b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---
 doc/src/sgml/parallel.sgml                    |   6 -
 src/backend/executor/execMain.c               |  14 +-
 src/backend/executor/execParallel.c           | 147 +++++++++++++++++-
 src/backend/optimizer/util/clauses.c          |  18 +--
 .../regress/expected/session_variables.out    |  12 +-
 5 files changed, 171 insertions(+), 26 deletions(-)

diff --git a/doc/src/sgml/parallel.sgml b/doc/src/sgml/parallel.sgml
index a5cef4cd79b..590cb385ddf 100644
--- a/doc/src/sgml/parallel.sgml
+++ b/doc/src/sgml/parallel.sgml
@@ -521,12 +521,6 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
         Plan nodes that reference a correlated <literal>SubPlan</literal>.
       </para>
     </listitem>
-
-    <listitem>
-      <para>
-        Plan nodes that use a session variable.
-      </para>
-    </listitem>
   </itemizedlist>
 
index ef51a0aca8f..58bcb357b5c 100644
@@ -213,7 +213,19 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	 *    be changed inside query execution time, and then a reference to
 	 *    previously returned value can be corrupted).

 

 

 	/*

 
 
2.46.0

From d4c68b6ce79c2977160607bfeb508fbab8c0c28f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

index 46ce68e8ad7..1de9c762cd5 100644

+	/*
index ecda13e9af5..b29ccf43e06 100644

 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void
index 8182ce28aa1..3f155c4f8a4 100644

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
+						/* push back the head name to include it in the stmt */
 
 /*
 
+/*
+ * Let statement
+ */
2.46.0

From 8d9fa781d2f657538a604fd6c3adf2ab69b98b6c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked
 
2.46.0

From c619c91b5ed8b1450fc3abd853f20386b1a5e277 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>
index 34b0b971516..f9a5281ecc5 100644

 	if (varDefexpr)
@@ -267,6 +270,7 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 							   stmt->if_not_exists,
 							   stmt->not_null,
 							   stmt->is_immutable,
+							   stmt->is_transact,
 							   cooked_default,
 							   stmt->XactEndAction);
 
index 9d4112c07b3..c871f2fbdad 100644
 
+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*
+			/* when svar is still valid (not removed from sessionvars */

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*
index b29ccf43e06..de4221a07e4 100644

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:
@@ -18026,6 +18034,7 @@ unreserved_keyword:
@@ -18675,6 +18684,7 @@ bare_label_keyword:
index e8c1e841836..dd114262c4c 100644

 

 
 
 /*

 

index 0f48bb77d35..27d33644f96 100644
@@ -456,6 +456,7 @@ PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD, BARE_LABEL)

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+
2.46.0

From 78b445be0eb8a5a971eb200bb3e82c19ee3ad4dd Mon Sep 17 00:00:00 2001

index 68751fcb086..c40ea6b066d 100644
 
+/*

 

 

 select a from atacc1;

index e913f683a68..f40a93b6c8c 100644
 
index 862433ee52b..96ba1923c1d 100644

2.46.0

From d271ff228ee00237258445fa681b612d60cf5b82 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 
2.46.0

---

From: Laurenz Albe <[email-masked]>
Date: Tue, 27 Aug 2024 08:15:42 +0200

On Thu, 2024-08-15 at 07:55 +0200, Pavel Stehule wrote:
> út 30. 7. 2024 v 21:46 odesílatel Laurenz Albe <[email-masked]> napsal:

I made a typo.  I mean "have *no* period in the end".

> I fixed almost all without parts related to psql and executor - there almost all
> current comments break the mentioned rule. So I use the style used in these files.
> I can fix it (if you like it) - or it can be fixed generally in a separated patch set.

Thanks.  I also noticed that a lot of existing comments break that rule,
so I think that it is fine if you stick with the way that the surrounding
code does it.

> fixed in 0002 patch (variables cannot be used as EXECUTE argument) and 0014 (enable usage variables as an argument of EXECUTE)

Thanks.

> >   > --- a/src/backend/catalog/namespace.c
> >   > +++ b/src/backend/catalog/namespace.c
> >   > [...]
> >   > +/*
> >   > + * IdentifyVariable - try to find variable identified by list of names.
> >   > [...]
> > 
> I prefer the current state, but I don't have a very strong opinion about it.
> It can save 115 lines of almost trivial code, but I think the user experience
> can be much worse.  Using composite types in tables is not too common a
> pattern (and when I read some recommendations for Oracle, it is an antipattern),
> but usage of composite variables is common (it can hold a row). Moreover,
> we talked long about possible identifier's collisions, and the pattern
> schema.variable is very good protection against possible collisions.
> Probably the pattern catalog.schema.variable.field is not too interesting
> but the support has 50 lines.
> 
> More, the plpgsql supports pattern label.variable.field, so can be little bit
> unfriendly if session variables doesn't support "similar" pattern

I see your point, and I'm fine with leaving it as it is.

> >   > +Datum
> >   > +GetSessionVariable(Oid varid, bool *isNull, Oid *typid)
> >   > +{
> >   > +   SVariable   svar;
> >   > +
> >   > +   svar = get_session_variable(varid);
> >   > +
> >   > +   /*
> >   > +    * Although svar is freshly validated in this point, the svar->is_valid can
> >   > +    * be false, due possible accepting invalidation message inside domain
> >   > +    * check. Now, the validation is done after lock, that can also accept
> >   > +    * invalidation message, so validation should be trustful.
> >   > +    *
> >   > +    * For now, we don't need to repeat validation. Only svar should be valid
> >   > +    * pointer.
> >   > +    */
> 
> This comment is related to assertions. Before I had there `Assert(svar->is_valid)`,
> because I expected it. But it was not always true. And although it is true,
> we don't need to validate a variable, because at this moment, the variable
> should be locked, and then we can return content safely.

I guess my main problem is the word "trustful".  I don't recognize that word.
Perhaps you can reword the comment along the lines of your above explanation.

> >   > @@ -196,6 +198,51 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
> >   >     Assert(queryDesc->sourceText != NULL);
> >   >     estate->es_sourceText = queryDesc->sourceText;
> >   >
> >   > +   /*
> >   > +    * The executor doesn't work with session variables directly. Values of
> >   > +    * related session variables are copied to dedicated array, and this array
> >   > +    * is passed to executor.
> >   > +    */
> > 
> Session variables are volatile internally - some function that uses LET statements
> can be called more times inside a query. This behavior is not consistent with
> plpgsql's variables or external parameters. And if we want to support parallel
> queries, then volatile session variables can be pretty messy (and unusable).
> If we want the same behaviour for queries with or without parallel support,
> then the session variables should be "stable" (like stable functions).
> Simple implementation is using "snapshot" of values of used session variables
> when query is started. This "snapshot" is immutable, so we don't need to make
> a copy more times.
> 
> I changed this comment

Thanks.

> > - parallel safety
> > 
> >   > --- a/src/backend/optimizer/util/clauses.c
> >   > +++ b/src/backend/optimizer/util/clauses.c
> >   > @@ -935,6 +936,13 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
> >   >         if (param->paramkind == PARAM_EXTERN)
> >   >             return false;
> >   >
> >   > +       /* We doesn't support passing session variables to workers */
> >   > +       if (param->paramkind == PARAM_VARIABLE)
> >   > +       {
> >   > +           if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
> >   > +               return true;
> >   > +       }
> > 

Thanks.

I hope I can do some more review at some point in the future...

I sincerely hope that this patch set gets merged at some point.
The big obstacle is that it is so large.  That's probably because it is pretty
feature-complete (but, as we have found, not totally free of bugs).

Judging from the amount of time I put into my review so far, I guess that this
patch set would keep a committer busy for several weeks.  Perhaps the only way to
get this done would be to make you a committer...

Yours,
Laurenz Albe

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 27 Aug 2024 08:52:27 +0200

Hi

út 27. 8. 2024 v 8:15 odesílatel Laurenz Albe <[email-masked]>
napsal:

> almost all
> > fixed in 0002 patch (variables cannot be used as EXECUTE argument) and
> 0014 (enable usage variables as an argument of EXECUTE)
>
> Thanks.
>
> it.
> `Assert(svar->is_valid)`,

I'll try to change it

> statements

Any feature that builds its own system catalog cannot be short. The first
two patches have 300KB, like all others and just basic support for reading
and writing. All other features have 300KB. On second hand almost all code
is simple, and there are no changes in critical parts of Postgres. The size
and complexity of JSONPath is level higher. I can throw 200KB from another
300KB patch set which can be better for review, but it can be harder to
maintain this patch set. I'll try it, and I'll send a reduced version.

> get this done would be to make you a committer...

:-)

Unfortunately, I am very bad at languages, I had a lot of problems in basic
school just with Czech lang, Russian and English was more terrible) - so I
very appreciate your work on this patch.

Thank you very much

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 29 Aug 2024 19:33:46 +0200

>> Perhaps you can reword the comment along the lines of your above

is this better

<-->/*
<--> * Although svar is freshly validated in this point, the svar->is_valid
can
<--> * be false, due possible accepting invalidation message inside domain
<--> * check. But now, the variable, and all dependencies are locked, so we
<--> * don't need to repeat validation.
<--> */

> Unfortunately, I am very bad at languages, I had a lot of problems in
> basic school just with Czech lang, Russian and English was more terrible) -
> so I very appreciate your work on this patch.
>
> Thank you very much
>
> Pavel
>

Regards

Pavel

>> Laurenz Albe
From 2b4e31537de2fc902a2307d585013ba7d2dfd2f9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From 0075e77dc48eb7ad2d9c56924ea2ec339181b713 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)
 
index a84c52a5cd0..c6e4d524a86 100644
 
+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*
index 136cd89c586..447cb68552c 100644
@@ -680,7 +680,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -785,7 +785,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER

@@ -5180,31 +5180,33 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5232,6 +5234,12 @@ OptImmutable: IMMUTABLE								{ $$ = true; }
 
+OptTransactional:
@@ -17972,6 +17980,7 @@ unreserved_keyword:
@@ -18619,6 +18628,7 @@ bare_label_keyword:

 

 
 
 /*

 
index 72b65558e69..c0cbcdf0619 100644
@@ -3582,8 +3584,11 @@ psql_completion(const char *text, int start, int end)

index 7cf2deaca46..1686e45750a 100644
@@ -3444,6 +3444,7 @@ typedef struct CreateSessionVarStmt
index f90d3d43f7f..63dbe51b724 100644
@@ -454,6 +454,7 @@ PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD, BARE_LABEL)
index 604bd657aac..65995b21bfe 100644
@@ -5888,20 +5888,20 @@ CREATE ROLE regress_variable_owner;

 DROP VARIABLE var1;
@@ -6369,9 +6369,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-                                         List of variables
 
@@ -6544,9 +6544,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
 -- again, but with current database and dotted schema qualifications.
@@ -6683,9 +6683,9 @@ List of text search templates
 (0 rows)

 
 
 DROP VARIABLE var1;
+

From 815ddae06e4d4db35fcbabca1f966eb927929f56 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From 34cb0320804ac28d20202f367a0afe522f416bed Mon Sep 17 00:00:00 2001

 
+/*

 

 

 select a from atacc1;

index 61a19cdc4ca..aba341897fe 100644
index 31fb7d142eb..a74693af187 100644
 
index c110c70d5ad..604bd657aac 100644
@@ -167,7 +167,7 @@ SELECT $1, $2 \bind 'foo' 'bar' \g
 
index 0456d48c93a..dadd0c94359 100644
@@ -922,7 +922,7 @@ ORDER BY q2,q1;

From cc4c3467610c4f06d034956685ce1fd3a0bc304e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*
index 27e31a4c2cb..136cd89c586 100644
@@ -829,6 +829,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -960,6 +961,13 @@ parse_toplevel:
 
 /*
 
index 027e391969c..7cf2deaca46 100644
@@ -2096,6 +2096,8 @@ typedef struct LetStmt

 
 
+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
+typedef struct PLpgSQL_stmt_let
+{
+	PLpgSQL_stmt_type cmd_type;
+	int			lineno;
+	unsigned int stmtid;
+	PLpgSQL_expr *expr;
+} PLpgSQL_stmt_let;
+
 /*
  * PERFORM statement
  */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 318c67b58d5..9f3af3079a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1887,6 +1887,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
-- 
2.46.0

From fd6dc5fd44e4dbb713f4425cc341a7d8a69c743e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*
 
index bfdf15a5071..036dbfc58f8 100644

index 77394e76c37..5e0ba0eb6aa 100644
@@ -1099,6 +1101,20 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
index 48ccdb942a2..6c74487f8fe 100644

-	/*
 
index eec0aa699e5..f31be25669e 100644

@@ -409,6 +410,13 @@ typedef struct ExprEvalStep
 			Oid			paramtype;	/* OID of parameter's datatype */
 		}			cparam;
 
@@ -830,6 +838,8 @@ extern void ExecEvalParamSet(ExprState *state, ExprEvalStep *op,
 
 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.46.0

From ed4d89d73bf4f741d8f86467c36e9a3518dc0f14 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

index 67fad7eeb83..d74e0d9bc02 100644
@@ -208,7 +208,19 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)

 

 

 	/*

 

From e24666bede75a00389018082528823a8552fd895 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
index bfbff9ab74e..e704e05a991 100644
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)
 
index 925b123f33f..a84c52a5cd0 100644
 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 18953e6a4ad..27e31a4c2cb 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -680,6 +680,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt

 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -5171,27 +5172,31 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5211,6 +5216,14 @@ XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
 		;

 

 

 
+	if (varnotnull)
 
 /*

 
index 04c49e41774..72b65558e69 100644
@@ -3263,7 +3265,8 @@ psql_completion(const char *text, int start, int end)
@@ -3580,7 +3583,8 @@ psql_completion(const char *text, int start, int end)
@@ -4228,6 +4232,10 @@ psql_completion(const char *text, int start, int end)

+	/* don't allow NULL */
 
index bffba5b259f..027e391969c 100644
@@ -3440,6 +3440,8 @@ typedef struct CreateSessionVarStmt
index a740bae3473..c110c70d5ad 100644
@@ -5888,20 +5888,20 @@ CREATE ROLE regress_variable_owner;

 DROP VARIABLE var1;
@@ -6369,9 +6369,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-                               List of variables
 
@@ -6544,9 +6544,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
 -- again, but with current database and dotted schema qualifications.
@@ -6683,9 +6683,9 @@ List of text search templates
 (0 rows)

 
 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From 66ee5255c9dd7e05f1e013b009a40fa8f51e6705 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vardefexpr</structfield> <type>pg_node_tree</type>
+      </para>
+      <para>
+       The internal representation of the variable default value
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 56dee89a906..6758ac5a2e0 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5301,14 +5301,14 @@ SELECT current_user_id;
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{
 
index 7881ee813a5..925b123f33f 100644
 
+/*
 
+	if (!is_write)

 
 	if (!found)
 	{
 
 		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by WRITE)",
 			 get_namespace_name(get_session_variable_namespace(svar->varid)),
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ea6d4d94685..18953e6a4ad 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -650,6 +650,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <partboundspec> PartitionBoundSpec
 %type <list>		hash_partbound
 %type <defelt>		hash_partbound_elem
+%type <node>		OptSessionVarDefExpr
 
 %type <node>	json_format_clause
 				json_format_clause_opt
@@ -5170,30 +5171,36 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:

 			if (isAgg)

 

 
 
+	if (vardefexpr)

 
 
 	if (verbose)

index 22ade2147a8..bffba5b259f 100644
@@ -3440,6 +3440,7 @@ typedef struct CreateSessionVarStmt

index 58d6feb2823..a740bae3473 100644
@@ -5888,20 +5888,20 @@ CREATE ROLE regress_variable_owner;

 DROP VARIABLE var1;
@@ -6369,9 +6369,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-                          List of variables
 
@@ -6544,9 +6544,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
 -- again, but with current database and dotted schema qualifications.
@@ -6683,9 +6683,9 @@ List of text search templates
 (0 rows)

 
 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From d38057fb1fa9767404c1379aaeb29e4fbb68705a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

index c19ea1e3e3f..7881ee813a5 100644
 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9be5c75b076..ea6d4d94685 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5199,6 +5199,7 @@ CreateSessionVarStmt:
  * transaction end like tables.
  */
 XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
+			| ON TRANSACTION END_P RESET			{ $$ = VARIABLE_XACTEND_RESET; }
 			| /*EMPTY*/								{ $$ = VARIABLE_XACTEND_NOOP; }
 		;

 

 

 

 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From f91707ad391d8dc6258c6a06424c19de3ccf1f77 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
index 06328352ec1..56dee89a906 100644
@@ -5306,9 +5306,9 @@ SELECT current_user_id;
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

index 1cb37433303..c89e3c907bd 100644

 /*

 

 

 
 
 	ReleaseSysCache(tup);
 
index 25181bde28e..c19ea1e3e3f 100644
 
 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
 
 		/*
 		 * A materialized view would either need to save parameters for use in
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0b8886e731b..9be5c75b076 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -479,6 +479,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	 OptTemp
 %type <ival>	 OptNoLog
 %type <oncommit> OnCommitOption
+%type <ival>	 XactEndActionOption
 
 %type <ival>	for_locking_strength
 %type <node>	for_locking_item
@@ -5169,26 +5170,39 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{
index 2e91ad41f34..04c49e41774 100644
@@ -3263,7 +3263,7 @@ psql_completion(const char *text, int start, int end)
@@ -3579,7 +3579,8 @@ psql_completion(const char *text, int start, int end)

 

index cdd1d9ee26a..22ade2147a8 100644
@@ -3440,6 +3440,7 @@ typedef struct CreateSessionVarStmt

 
 
 step sr1: ROLLBACK;
 
index c70c614091e..58d6feb2823 100644
@@ -5888,20 +5888,20 @@ CREATE ROLE regress_variable_owner;

 DROP VARIABLE var1;
@@ -6369,9 +6369,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-            List of variables
 
@@ -6544,9 +6544,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
 -- again, but with current database and dotted schema qualifications.
@@ -6683,9 +6683,9 @@ List of text search templates
 (0 rows)

 
 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index 7b2456b9e89..318c67b58d5 100644

From 0495618688c52ddec53ba24010f0ef23d9733b5b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

index 48bdcab2ad5..0b8886e731b 100644
@@ -12093,7 +12093,8 @@ PreparableStmt:

index 43fb65f8e38..2e91ad41f34 100644
@@ -4268,9 +4268,9 @@ psql_completion(const char *text, int start, int end)
 
-/* LET */

From d1478a2f10586367c79cc900abe07145e00b6007 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

index 11df4a04d43..ef986c5a705 100644

 
index 3d62cfb0f6f..48bdcab2ad5 100644
@@ -12062,7 +12062,8 @@ ExplainableStmt:

 
 DROP TABLE public.xxtab;

From ec7c3c65a4c525892ce3ab086ea87a0a0ca516cb Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From 48acc39b04aa5b745a7ba397b8a5424f9e0bbfec Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 12feac60874..dc474a8cbfd 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10706,6 +10706,66 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>
 
index 7eee2a8e803..06328352ec1 100644
@@ -5321,6 +5321,16 @@ SELECT current_user_id;
 
  <sect1 id="ddl-others">
 
 /* GUC parameters */

index 880d76aae0f..0d703528389 100644
 
 /* GUC parameters */

 

 

From 5f2d1a214276b322721da6abf7a6c6ec3ac10aeb Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

index 0fe1630fca8..1cb37433303 100644

 
index 67611c517f3..25181bde28e 100644

+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

@@ -591,6 +757,8 @@ pg_session_variables(PG_FUNCTION_ARGS)

 
 	if (sessionvars)
 
index 143109aa4da..7453685d046 100644
@@ -115,3 +115,4 @@ test: serializable-parallel-2
 
 -- should be zero again

From 33389a390fb52f3e853ef1eabe800aed28d528e7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 1d1b33ac12c..74e9a6dce93 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -569,3 +569,105 @@ ExecuteLetStmt(ParseState *pstate,
 
 	PopActiveSnapshot();
 }
+
+/*

From 828f9e120738b9d7c8e5b403ae57805ac4d39a59 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
 
index 74e9a6dce93..67611c517f3 100644

-	Assert(sessionvars);
+	/*
 
 	/*
 	 * If the hashvalue is not specified, we have to recheck all currently
@@ -671,3 +677,23 @@ pg_session_variables(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
+
+/*
index be1e70e3ebf..3d62cfb0f6f 100644
@@ -2071,7 +2071,13 @@ DiscardStmt:

index db0e9cd3618..43fb65f8e38 100644
@@ -3720,7 +3720,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+extern void ResetSessionVariables(void);
+
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6cb40a5f170..cdd1d9ee26a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3950,6 +3950,7 @@ typedef enum DiscardMode
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
 	DISCARD_TEMP,
+	DISCARD_VARIABLES,
 } DiscardMode;

 RESET search_path;
+

From 5414215d7d4d5723190b4d1635090e42e1682c6d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 src/backend/commands/session_variable.c       | 571 +++++++++++
 59 files changed, 3928 insertions(+), 23 deletions(-)

+     <row>
index df6e1b424c7..7eee2a8e803 100644
@@ -5279,6 +5279,38 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
+   <para>
 
index 78e4983139b..bfbff9ab74e 100644

 

+/*

 
+	/*
 
index 00000000000..1d1b33ac12c
@@ -0,0 +1,571 @@
+	 * check. But now, the variable, and all dependencies are locked, so we
+	 * don't need to repeat validation.
 
index 63289ee35ee..bfdf15a5071 100644
 
 						/*
index 29e186fa73d..67fad7eeb83 100644
@@ -191,6 +193,61 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 
+	/*
index b5827d39808..344eaba6532 100644
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*
 
index 51b926ebdc0..be1e70e3ebf 100644
@@ -306,7 +306,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -456,6 +456,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
@@ -746,7 +747,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEEP KEY KEYS

 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
@@ -1097,6 +1098,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -12698,6 +12700,47 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -17729,6 +17772,7 @@ unreserved_keyword:
@@ -18339,6 +18383,7 @@ bare_label_keyword:

 			/*
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index 00eda1b34c0..87ec57a721c 100644

+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
index baae5965133..db0e9cd3618 100644
@@ -4268,6 +4268,14 @@ psql_completion(const char *text, int start, int end)
 
+/* LET */

 
+	/*

 
index af7d8fd1e72..aa3bca24bde 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index 847b3eac6dc..6cb40a5f170 100644

@@ -2082,6 +2087,18 @@ typedef struct MergeStmt
 	WithClause *withClause;		/* WITH clause */
 } MergeStmt;
 
index 540d021592e..215b69b3eaf 100644

 
 	/*
index 62cd6a6666e..18493b2c4e9 100644
 
 	NodeTag		type;

 

 
 typedef struct Param
 
index 964e27b577f..f90d3d43f7f 100644

 
 
 /* ----------------
 
 	/*
 
index 237df1c21a9..7b2456b9e89 100644
@@ -1495,6 +1495,7 @@ LargeObjectDesc
@@ -2598,6 +2599,7 @@ SerializedTransactionState

From aeededbcc5a8bce4223ac949b3eec8c0f765771c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 
index b671858627b..df6e1b424c7 100644
@@ -5260,6 +5260,37 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>

index 461fc3f437c..22a63b53e58 100644
@@ -25692,6 +25692,19 @@ SELECT relname FROM pg_class WHERE pg_table_is_visible(oid);

 
 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*
index b3cc6f8f690..c41e9521d01 100644
@@ -6661,6 +6662,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -6724,6 +6726,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
index 84cef57a707..51b926ebdc0 100644
@@ -294,8 +295,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -787,8 +788,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 
@@ -1058,6 +1059,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1602,6 +1604,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;

@@ -5150,6 +5153,34 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6936,6 +6967,7 @@ object_type_any_name:
 
 /*
@@ -7812,6 +7844,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7857,6 +7897,14 @@ privilege_target:

@@ -8054,6 +8102,7 @@ defacl_privilege_target:

@@ -9836,6 +9885,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10197,6 +10264,24 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10478,6 +10563,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17805,6 +17898,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18457,6 +18552,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 79cad4ab30c..35f0794134c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -104,6 +104,7 @@ typedef struct
 	List	   *indexes;		/* CREATE INDEX items */
 	List	   *triggers;		/* CREATE TRIGGER items */
 	List	   *grants;			/* GRANT items */
+	List	   *variables;		/* CREATE VARIABLE items */
 } CreateSchemaStmtContext;

@@ -3791,6 +3792,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 	cxt.indexes = NIL;
 	cxt.triggers = NIL;
 	cxt.grants = NIL;
+	cxt.variables = NIL;
 
 	/*
 	 * Run through each schema element in the schema element list. Separate
@@ -3859,6 +3861,15 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				cxt.grants = lappend(cxt.grants, element);
 				break;
 
@@ -3872,6 +3883,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 
 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 
 
index 4dfc7b2d857..2ee402d9e3d 100644
@@ -1081,6 +1081,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 19d20c58781..8ea5c4a8f2f 100644
@@ -268,6 +268,7 @@ slashUsage(unsigned short int pager)
index a7ccde6d7df..baae5965133 100644

 /*
@@ -2790,7 +2801,7 @@ psql_completion(const char *text, int start, int end)
@@ -3566,6 +3577,13 @@ psql_completion(const char *text, int start, int end)
 
@@ -3829,6 +3847,12 @@ psql_completion(const char *text, int start, int end)
 
+	/* DROP VARIABLE */
@@ -4032,7 +4056,7 @@ psql_completion(const char *text, int start, int end)
@@ -4054,7 +4078,8 @@ psql_completion(const char *text, int start, int end)
@@ -4062,7 +4087,8 @@ psql_completion(const char *text, int start, int end)
@@ -4098,6 +4124,8 @@ psql_completion(const char *text, int start, int end)
@@ -4401,7 +4429,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
@@ -4869,6 +4897,8 @@ psql_completion(const char *text, int start, int end)

 

 

index 124d853e499..847b3eac6dc 100644
@@ -2296,6 +2296,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW,
 } ObjectType;
 
@@ -3410,6 +3411,21 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+
index f8659078ced..964e27b577f 100644
@@ -483,6 +483,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)

index 6aeb7cb9636..c70c614091e 100644
@@ -5883,6 +5883,30 @@ COMMIT;
@@ -6104,6 +6128,12 @@ cross-database references are not implemented: nonesuch.public.func_deps_stat
@@ -6338,6 +6368,12 @@ List of schemas
 
+\dV "no.such.variable"
+            List of variables
@@ -6507,6 +6543,12 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
@@ -6640,6 +6682,12 @@ List of text search templates
 
@@ -6687,6 +6735,8 @@ cross-database references are not implemented: "no.such.database"."no.such.schem
index 7a5a910562e..72f28e7597b 100644
 
 # ----------
index 0a2f8b46922..38e0f685587 100644
@@ -1617,6 +1617,19 @@ COMMIT;
 
@@ -1728,6 +1741,9 @@ DROP FUNCTION psql_error;
 
@@ -1769,6 +1785,8 @@ DROP FUNCTION psql_error;
 
@@ -1809,6 +1827,7 @@ DROP FUNCTION psql_error;
 
@@ -1833,6 +1852,7 @@ DROP FUNCTION psql_error;
 
@@ -1858,6 +1878,7 @@ DROP FUNCTION psql_error;
 
 -- check \drg and \du
index 9e951a9e6f3..237df1c21a9 100644
@@ -534,6 +534,7 @@ CreateRoleStmt
@@ -873,6 +874,7 @@ FormData_pg_ts_parser
@@ -932,6 +934,7 @@ Form_pg_ts_parser

---

From: Laurenz Albe <[email-masked]>
Date: Mon, 02 Sep 2024 16:00:51 +0200

On Thu, 2024-08-29 at 19:33 +0200, Pavel Stehule wrote:
> <--> * Although svar is freshly validated in this point, the svar->is_valid can

Much better.

Here is an improved version:

  Although "svar" is freshly validated in this point, svar->is_valid can
  be false, if an invalidation message ws processed during the domain check.
  But the variable and all its dependencies are locked now, so we don't need
  to repeat the validation.

Yours,
Laurenz Albe

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 3 Sep 2024 13:41:31 +0200

Hi

merged

thank you

Regards

Pavel

> Laurenz Albe
From 11c6cf4d8d6bbee5058e3de86a1471c7b1c83944 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From 8f685361e8f7f042f156c8ae47c2cec5f15ec010 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From cb87bb6364eba3a08d460fc7f013dcfcc75a613e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)
 
index 2af93eecea8..b7a74d1772b 100644
 
+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:
index 16832c4d000..d59a92df473 100644

 

 
 	if (OidIsValid(varcollation))
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 8e955eda4ef..15373050811 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -713,6 +713,7 @@ typedef struct _VariableInfo
 	const char *rolname;		/* name of owner, or empty string */
 	bool		varnotnull;
 	bool		varisimmutable;
+	bool		varistransact;
 } VariableInfo;
 
 /*

 

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+

From 875cb3021dc6a6700b9b3a3377c6f8a4401a67cd Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*

 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */

From 4644cfe98695c848175f1e449381786db11f0c93 Mon Sep 17 00:00:00 2001

 
+/*

 

 

 select a from atacc1;

 

From 23dcc384853fd1604ac79758624991e969f5ff12 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

index 3936a62c5f9..4b3df7c2f8d 100644

 

 

 
 	/*

 

From 3f8689ffdb696a3ce64da66ccfa85227cc9c6fb6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*

 
index a6c47f61e0d..4fc484e3b74 100644
 
 			/* out of line, unlikely to matter performance-wise */

-	/*

 

 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.46.0

From 5cb46b941f07505dee2b08f8678210eba711ef92 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)
 
index e0edfdd2a28..2af93eecea8 100644
 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*

 /*
 
 CreateSessionVarStmt:

index 1ca1addec55..16832c4d000 100644

 

 

+	if (varnotnull)
+		appendPQExpBuffer(query, " NOT NULL");
+
 	if (vardefexpr)
 		appendPQExpBuffer(query, " DEFAULT %s",
 						  vardefexpr);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 3cedb1de3f2..8e955eda4ef 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -711,6 +711,8 @@ typedef struct _VariableInfo
 	char	   *initrvaracl;
 	Oid			varcollation;
 	const char *rolname;		/* name of owner, or empty string */
+	bool		varnotnull;
+	bool		varisimmutable;
 } VariableInfo;
 
 /*

 

+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From 2cbf45f5e8110f555938e2b6de485c639b6d829b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{
 
index 7318c1d3185..e0edfdd2a28 100644
 
+/*
 
+	if (!is_write)

 
 	if (!found)
 	{

 
 CreateSessionVarStmt:

 			if (isAgg)
 
index fe352ec556d..1ca1addec55 100644

 

 
+	if (vardefexpr)
 
index 994ea48c3e5..3cedb1de3f2 100644
@@ -703,6 +703,7 @@ typedef struct _VariableInfo

 
 	if (verbose)

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From 55502e9ffa0f550b2833626d3235d65a2144d484 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

index d3c9156dd42..7318c1d3185 100644
 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;
 
index 9ae7bb4a499..fe352ec556d 100644

 

 

index 5be6c1969a3..994ea48c3e5 100644
@@ -703,6 +703,7 @@ typedef struct _VariableInfo

 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From c39f255e85ed72864a2435f86230b324e107927b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

 

-/* LET */

From 0d623b1a8d730a53815dfb2d8f0401d94a27431e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

 

 DROP TABLE public.xxtab;

From 84a97fbfeac18ac1bb28bca5b8d01ee3d540d943 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);
 
index dec689bf248..d3c9156dd42 100644
 
 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
 
 		/*

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables

From 932d01b4e0d55a4eb84944e3b74c4d3076fd5093 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

index 0aec11f4432..7578b078623 100644

  <sect1 id="ddl-others">
 
 /* GUC parameters */

index 521ec5591c8..64609d5ffa2 100644
 
 /* GUC parameters */

 

 

From 7bb092ef4c5ebc605178f31f3a7a4c20cac1df6a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From af360735f6b482375ea1ebff5181f099ef297446 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 

index 3d92aaa0fad..dec689bf248 100644

+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

 	if (sessionvars)

 -- should be zero again

From c69089e7fe389059d9e4c28803cbe6182a72b340 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
 
index f1399ac9bd7..3d92aaa0fad 100644

-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+

From 3446caac2e29cddb27ae560cc29ce97015ab7320 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

index 548d9835c0d..f1399ac9bd7 100644
 
 	PopActiveSnapshot();
 }
+
+/*
index 0aebcd23a88..c37f193d17b 100644

From 35182975c2a537f30f4c37776822a7e027b640cb Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 
+     <row>
 
+   <para>
index 4b3df7c2f8d..3936a62c5f9 100644

 

 
+/*

 
+	/*
 
index 00000000000..548d9835c0d
+	 * Although "svar" is freshly validated in this point, svar->is_valid can
+	 * be false, if an invalidation message ws processed during the domain check.
+	 * But the variable and all its dependencies are locked now, so we don't need
+	 * to repeat the validation.

 						/*
 
+	/*
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*
 
index 34fbf8ee237..1647d2c7815 100644

 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*

 

 
 
 			/*
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index b31be31321d..d3303527eb8 100644
@@ -512,6 +513,7 @@ static char *generate_function_name(Oid funcid, int nargs,
@@ -8425,6 +8427,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
@@ -13207,6 +13217,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*

From a0814b172f1b61a61c563969f6c1feea9d4999e0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 

 

 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
index dacb033e989..9ae7bb4a499 100644
 
+/*
+ * getVariables
@@ -9958,7 +10143,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -10557,6 +10743,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION_REL:
 			dumpSubscriptionTable(fout, (const SubRelInfo *) dobj);
 			break;
+		case DO_VARIABLE:
+			dumpVariable(fout, (VariableInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -15006,6 +15195,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
 		case DEFACLOBJ_NAMESPACE:
 			type = "SCHEMAS";
 			break;
+		case DEFACLOBJ_VARIABLE:
+			type = "VARIABLES";
+			break;
 		default:
 			/* shouldn't get here */
 			pg_fatal("unrecognized object type in default privileges: %d",
@@ -18617,6 +18809,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_CONVERSION:
 			case DO_TABLE:
 			case DO_TABLE_ATTACH:
+			case DO_VARIABLE:
 			case DO_ATTRDEF:
 			case DO_PROCLANG:
 			case DO_CAST:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0b7d21b2e9b..5be6c1969a3 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -52,6 +52,7 @@ typedef enum
 	DO_TABLE,
 	DO_TABLE_ATTACH,
 	DO_ATTRDEF,
+	DO_VARIABLE,
 	DO_INDEX,
 	DO_INDEX_ATTACH,
 	DO_STATSEXT,
@@ -693,6 +694,23 @@ typedef struct _SubRelInfo
 	char	   *srsublsn;
 } SubRelInfo;
 
+/*
@@ -776,5 +794,6 @@ extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 
 
index 85f42be1b3c..0aebcd23a88 100644

 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 12 Sep 2024 07:15:34 +0200

Hi

fresh rebase

regards

Pavel
From 6c69c9f3ccc151b6c603c431f0b0f5a75947e0b5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From 00082a1a890f3adf757d42bba8930353d305ec6e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From 9fbc9bcbbfceebc7682398ef26fbab8f1f09f17f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:
index 9a8f6a4fd38..6dd67855031 100644

 

 
 
 /*

 

index c2c0213574e..5cb34f3cbf0 100644
@@ -3453,6 +3453,7 @@ typedef struct CreateSessionVarStmt

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+

From eee74b1b9f77644ed23d4c95e80dafce40f7857f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

index 90d98345764..1b1fa292cac 100644

+	/*

 /*
 
index f158d7a1dad..c2c0213574e 100644
@@ -2105,6 +2105,8 @@ typedef struct LetStmt

 
 
+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
+typedef struct PLpgSQL_stmt_let
+{
+	PLpgSQL_stmt_type cmd_type;
+	int			lineno;
+	unsigned int stmtid;
+	PLpgSQL_expr *expr;
+} PLpgSQL_stmt_let;
+
 /*
  * PERFORM statement
  */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index da20dd2cd64..3cc4cf208e2 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1888,6 +1888,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
-- 
2.46.0

From f7495807b0cc001a37edea71ae4dadac5d84720c Mon Sep 17 00:00:00 2001

index 2f0d484a5c4..1dee0da6809 100644
index 95ae2108044..fa9a30bd138 100644
@@ -3730,6 +3731,19 @@ errorMissingRTE(ParseState *pstate, RangeVar *relation)
 
+/*
@@ -3764,7 +3778,7 @@ errorMissingColumn(ParseState *pstate,
@@ -3774,7 +3788,7 @@ errorMissingColumn(ParseState *pstate,
@@ -3792,14 +3806,14 @@ errorMissingColumn(ParseState *pstate,
@@ -3813,7 +3827,7 @@ errorMissingColumn(ParseState *pstate,
index 76bf88c3ca2..6af6b801ad4 100644
@@ -779,7 +779,9 @@ transformAssignmentIndirection(ParseState *pstate,
@@ -788,7 +790,9 @@ transformAssignmentIndirection(ParseState *pstate,

index 6974c8f4a44..3970ac721c9 100644

 

 
 select a from atacc1;

 
index 9e23166fed1..672012e1461 100644
index 0a6945581bd..5c599ba312e 100644
index a1626f3fae9..b22e2e07544 100644

From 292e0afff7a90244a22056deb098aae95d034b22 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

 
 
 	/*

 

From c893c31368c13131d1b4e20e07c31643ef0e4435 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*

 /*
 
 CreateSessionVarStmt:

index 3318f7bc5cc..9a8f6a4fd38 100644

 

 

+	if (varnotnull)
 
 /*

 

+	/* don't allow NULL */
 
index 6d1343b083a..f158d7a1dad 100644
@@ -3449,6 +3449,8 @@ typedef struct CreateSessionVarStmt

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From 72576ba525f701403a00d7477c1b3d1379d86e8c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*
 
index b1a5a167b10..ace7fd994a5 100644

index 9fd988cc992..43cd1f3ee1d 100644

 
-	/*

 

 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.46.0

From 7baccced27e30d5a20ea27d6ef4ebf1307a418e6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{

 
 CreateSessionVarStmt:
 
index a7343001f21..ddfbcf42b74 100644
@@ -488,6 +488,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 
 			if (isAgg)
@@ -937,6 +938,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
index 468dc03e275..2f0d484a5c4 100644
 
index 22b5a870509..3318f7bc5cc 100644

 

 
+	if (vardefexpr)

 
 
 	if (verbose)

index cd147534139..6d1343b083a 100644
@@ -3449,6 +3449,7 @@ typedef struct CreateSessionVarStmt
 
index d284d38b668..7e7e6bdc917 100644

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From f2950b877e2c33f72fd0b413f8a2a90b64c07eb8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;
 
index 929cb1b9f1c..22b5a870509 100644

 

 

 
 
 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From 861bb4d9d9d98d22b9283b1326ae5dbf408fc967 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

 

-/* LET */

From 8eea405d0f7f8c0f6fd4f24a6da58b884a337e37 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

index fa8df43cbb8..1b5c98c9fb6 100644

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
 
 		/*

 CreateSessionVarStmt:
 
+/*
index 8075b1b8a1b..95ae2108044 100644

 /*
@@ -3896,13 +3896,13 @@ rte_visible_if_qualified(ParseState *pstate, RangeTblEntry *rte)
 
 static bool
-isQueryUsingTempRelation_walker(Node *node, void *context)
+isQueryUsingTempObject_walker(Node *node, void *context)
 {
 	if (node == NULL)
 		return false;
@@ -3928,13 +3928,23 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 		}

 

 	if (verbose)
 	{

 

index aa61a5e27bf..cd147534139 100644
@@ -3449,6 +3449,7 @@ typedef struct CreateSessionVarStmt

index 91fd8e243b5..a8117bcae3f 100644
@@ -126,6 +126,6 @@ extern int	attnameAttNum(Relation rd, const char *attname, bool sysColOK);

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index 9cd6c332a75..da20dd2cd64 100644
@@ -2794,6 +2794,7 @@ SupportRequestWFuncMonotonic
 SVariable
 SVariableData
 SVariableState
+SVariableXActDropItem
 Syn
 SyncOps
 SyncRepConfigData
-- 
2.46.0

From 3a86d00d39b1cf48c1cbb54cf6e2848b414a56f7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

index 2819e479f82..2176b4c3e2c 100644

 
 
 /*****************************************************************************
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index 3ab0aae78f7..e1f04c0d00d 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -103,7 +103,8 @@ extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
 							  ExplainState *es, const char *queryString,
 							  ParamListInfo params, QueryEnvironment *queryEnv);

 DROP TABLE public.xxtab;

From 68f1679f7283f3ab398e384a1f1c17ac68d32b03 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From aa7556e560770d7b6cf3120a483c63e3eaca035f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

 
 
  <sect1 id="ddl-others">
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 8760876c686..468dc03e275 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -47,6 +47,7 @@
 
 /* GUC parameters */

index 686309db58b..b68bbe94fec 100644
 
 /* GUC parameters */

 

 

From 727970258af6c7646b79a02cc43e3dcb3363ffa5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

index 87700c7c5c7..fa8df43cbb8 100644

 

+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

 	if (sessionvars)

 -- should be zero again

From a694756371c0ca4812087710cf72f97674105a28 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+extern void ResetSessionVariables(void);
+
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7c1ad5146d0..aa61a5e27bf 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3959,6 +3959,7 @@ typedef enum DiscardMode
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
 	DISCARD_TEMP,
+	DISCARD_VARIABLES,
 } DiscardMode;

 RESET search_path;
+

From 64e3d07a7a90b5f45bc60d60a12d9d66abd17694 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*
index 7a15ab6c5e6..75af55a0f41 100644

From 3fcd83072f205c7d94453c806a78925cd4658dde Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 
+     <row>
 
+   <para>

 

 
+/*

 
+	/*

index c8077aa57bd..b1a5a167b10 100644
 
 						/*
 
+	/*
index 0d00e029f32..210d911963b 100644
@@ -4342,6 +4342,16 @@ raw_expression_tree_walker_impl(Node *node,
index df35d1ff9c4..1d10509c93f 100644
@@ -325,6 +325,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/*
@@ -559,6 +560,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
@@ -729,6 +731,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root,
 	 */
 	pull_up_subqueries(root);
 
+	/*
index 91c7c4fe2fe..1fa2ecf5c20 100644
@@ -210,6 +210,9 @@ static List *set_returning_clause_references(PlannerInfo *root,

 /*****************************************************************************
@@ -1310,6 +1313,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 	return plan;
 }
 
+/*
@@ -1958,8 +2005,9 @@ copyVar(Var *var)
@@ -2049,6 +2097,13 @@ fix_expr_common(PlannerInfo *root, Node *node)
 
 /*
@@ -2058,6 +2113,10 @@ fix_expr_common(PlannerInfo *root, Node *node)
@@ -2076,6 +2135,41 @@ fix_param_node(PlannerInfo *root, Param *p)
 
@@ -2137,7 +2231,10 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
@@ -2159,7 +2256,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
@@ -3544,6 +3642,25 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
 	}
 }
 
+/*
@@ -3629,9 +3746,9 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 			}
 
 			/*
@@ -3654,6 +3771,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 
index a70404558ff..aa3ce219cd7 100644
@@ -1315,6 +1315,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,

 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*

 

index efa730c1676..a7343001f21 100644
@@ -580,6 +580,11 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			errkind = true;
 			break;
 
@@ -970,6 +975,10 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 
 			/*
index 36c1b7a88f2..8760876c686 100644
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index badbf111ee0..f90467d684b 100644
@@ -528,6 +529,7 @@ static char *generate_function_name(Oid funcid, int nargs,
@@ -8590,6 +8592,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
@@ -13372,6 +13382,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
index 516b9487435..82847fa6e5a 100644
@@ -613,6 +613,18 @@ typedef struct AsyncRequest
 								 * tuples) */
 } AsyncRequest;
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+	Oid			varid;
+	Oid			typid;
+	bool		isnull;
+	Datum		value;
+} SessionVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -665,6 +677,10 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */

index 6ea52818897..7c1ad5146d0 100644
 
+	/* target variable of LET statement */
+	Oid			resultVariable;
+
 	/* has aggregates in tlist or havingQual */
 	bool		hasAggs pg_node_attr(query_jumble_ignore);
 	/* has window functions in tlist */
@@ -162,6 +165,8 @@ typedef struct Query
 	bool		hasRowSecurity pg_node_attr(query_jumble_ignore);
 	/* parser has added an RTE_GROUP RTE */
 	bool		hasGroupRTE pg_node_attr(query_jumble_ignore);
+	/* uses session variables */
+	bool		hasSessionVariables pg_node_attr(query_jumble_ignore);
 	/* is a RETURN statement */
 	bool		isReturn pg_node_attr(query_jumble_ignore);
 
@@ -2091,6 +2096,18 @@ typedef struct MergeStmt
 	WithClause *withClause;		/* WITH clause */
 } MergeStmt;
 
index 07e2415398e..7a90ade4282 100644

 
 	/*
 	 * The rangetable index for the RTE_GROUP RTE, or 0 if there is no
 
 	NodeTag		type;

 

 
 typedef struct Param
 
index 543df568147..d284d38b668 100644

@@ -228,6 +230,7 @@ struct ParseState
 	bool		p_hasTargetSRFs;
 	bool		p_hasSubLinks;
 	bool		p_hasModifyingCTE;
+	bool		p_hasSessionVariables;

 
 /* ----------------
 
 	/*
 
index 20c3b77a3d7..9cd6c332a75 100644
@@ -1496,6 +1496,7 @@ LargeObjectDesc
@@ -2599,6 +2600,7 @@ SerializedTransactionState
@@ -2789,6 +2791,9 @@ SupportRequestRows

From 813c3c5511a1b47234e168657586401dc7b7d645 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 

index 1bde4091ca6..889327791ca 100644
@@ -25696,6 +25696,19 @@ SELECT relname FROM pg_class WHERE pg_table_is_visible(oid);

 
 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
index 546e7e4ce1a..929cb1b9f1c 100644
 
+/*
+ * getVariables
@@ -9961,7 +10146,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -10560,6 +10746,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
@@ -15009,6 +15198,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -18620,6 +18812,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 
 
index ff5436acacf..7a15ab6c5e6 100644
 
index d6f7e795fe1..6ea52818897 100644
@@ -2305,6 +2305,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW,
 } ObjectType;
 
@@ -3419,6 +3420,21 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du
index e9ebddde24d..20c3b77a3d7 100644
@@ -535,6 +535,7 @@ CreateRoleStmt
@@ -874,6 +875,7 @@ FormData_pg_ts_parser
@@ -933,6 +935,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
+Form_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
@@ -3068,6 +3071,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.46.0

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 18 Sep 2024 08:21:44 +0200

Hi

fresh rebase

regards

Pavel
From 444db224725374d48e197a3c61bfd1c072baf58e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked
 
2.46.1

From bcd5d8e595e12b775ae045ffd28607473b47bd98 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 
2.46.1

From 4f3f094248da0cd597a205642686096b3ca92ec0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index edf4882bb64..03914d2395f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9862,6 +9862,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*
index 5b68c083c18..5c685b55ecc 100644
@@ -681,7 +681,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -786,7 +786,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER

@@ -5215,31 +5215,33 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5267,6 +5269,12 @@ OptImmutable: IMMUTABLE								{ $$ = true; }
 
+OptTransactional:
@@ -18008,6 +18016,7 @@ unreserved_keyword:
@@ -18656,6 +18665,7 @@ bare_label_keyword:
index ecd0871fcf0..068f264ef6b 100644

 

 
 	if (OidIsValid(varcollation))
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 544b7259623..2539f411ed8 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -714,6 +714,7 @@ typedef struct _VariableInfo
 	const char *rolname;		/* name of owner, or empty string */
 	bool		varnotnull;
 	bool		varisimmutable;
+	bool		varistransact;
 } VariableInfo;
 
 /*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index cdcd0637d6b..680bd19d094 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4067,6 +4067,42 @@ my %tests = (
 		},
 	},
 
index 2ca2688413f..705fb1f2703 100644
@@ -5167,7 +5167,7 @@ listVariables(const char *pattern, bool verbose)
 
@@ -5190,6 +5190,7 @@ listVariables(const char *pattern, bool verbose)
@@ -5202,6 +5203,7 @@ listVariables(const char *pattern, bool verbose)

 
index 9dfc50174f9..e3f0bc8794d 100644
@@ -3457,6 +3457,7 @@ typedef struct CreateSessionVarStmt
index aefe51f335b..125eb819b76 100644
@@ -455,6 +455,7 @@ PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD, BARE_LABEL)

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+
2.46.1

From 95d4a890d7c73f4e72f27c9eb3b985bbacff0438 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*
index 83dc89a2541..5b68c083c18 100644
@@ -830,6 +830,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -961,6 +962,13 @@ parse_toplevel:
 
 /*
 
index 9a7b0b46040..9dfc50174f9 100644

 
 
+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index 08ea60eae71..c3542ffaa00 100644
2.46.1

From 98a656eebf2e4586b263e56fefe3bdde90fc6382 Mon Sep 17 00:00:00 2001

 
+/*

 

 

 select a from atacc1;

 

2.46.1

From e04db69d31ee4a8cc34174d5b286a48f087bedf1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

index 0f6a8c571bc..3be5a99374f 100644
@@ -210,7 +210,19 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)

 

 

 	/*

 
 
2.46.1

From 4e6103d0407e3cfc7a2b920fc220ac9cd556366a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*

 

 
-	/*

 

 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.46.1

From f3d88aae6f71c55fb9283940220651a4fecd50aa Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 995c75bc6da..181b9601c44 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9877,6 +9877,14 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{
 
index c5f75eec33e..2fabb5761ee 100644
@@ -651,6 +651,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	json_format_clause
 				json_format_clause_opt
@@ -5205,30 +5206,36 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:

 			if (isAgg)
 
index dc25e83279a..b75cd3fb07e 100644

 

 
+	if (vardefexpr)
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 94ca13e48c0..aa2c7152f09 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -704,6 +704,7 @@ typedef struct _VariableInfo
 	DumpableAcl dacl;
 	Oid			vartype;
 	char	   *vartypname;
+	char	   *vardefexpr;
 	char	   *varxactendaction;
 	char	   *varacl;
 	char	   *rvaracl;
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index e478383a25b..d05a32e2ae9 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3995,6 +3995,42 @@ my %tests = (
 		},
 	},
 
index e5011342dbe..93fdeeb6c1b 100644
@@ -5167,7 +5167,7 @@ listVariables(const char *pattern, bool verbose)
 
@@ -5188,6 +5188,7 @@ listVariables(const char *pattern, bool verbose)
@@ -5197,6 +5198,7 @@ listVariables(const char *pattern, bool verbose)
 
 	if (verbose)

index 6648c6af715..4b2503fc2c8 100644

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+
2.46.1

From 8665691e32484bb9d3f8b1811f5b7a1959c68d55 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0eab32c991c..995c75bc6da 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9849,7 +9849,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
       <para>
        Action performed at end of transaction:
-       <literal>n</literal> = no action, <literal>d</literal> = drop the variable.
+       <literal>n</literal> = no action, <literal>d</literal> = drop the variable,
+       <literal>r</literal> = reset the variable to its default value.
       </para></entry>
      </row>

 
 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8fc88f8b440..c5f75eec33e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5234,6 +5234,7 @@ CreateSessionVarStmt:
  * transaction end like tables.
  */
 XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
+			| ON TRANSACTION END_P RESET			{ $$ = VARIABLE_XACTEND_RESET; }
 			| /*EMPTY*/								{ $$ = VARIABLE_XACTEND_NOOP; }
 		;
 
index f0a75007a77..dc25e83279a 100644

 

 

 	if (varinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index af15b3f2188..94ca13e48c0 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -704,6 +704,7 @@ typedef struct _VariableInfo
 	DumpableAcl dacl;
 	Oid			vartype;
 	char	   *vartypname;
+	char	   *varxactendaction;
 	char	   *varacl;
 	char	   *rvaracl;
 	char	   *initvaracl;
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index ef8ca0c60cf..e478383a25b 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3977,6 +3977,24 @@ my %tests = (
 		},
 	},
 
index f71c2546161..e5011342dbe 100644
@@ -5190,6 +5190,7 @@ listVariables(const char *pattern, bool verbose)
 
 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4
 
2.46.1

From 7398f5594bfa44c4968a2c6fcb444b6ddc8d1d2d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 181b9601c44..edf4882bb64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9845,6 +9845,26 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2fabb5761ee..83dc89a2541 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -681,6 +681,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt

 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -5206,27 +5207,31 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5246,6 +5251,14 @@ XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
 		;

index b75cd3fb07e..ecd0871fcf0 100644

 

 

+	if (varnotnull)
+		appendPQExpBuffer(query, " NOT NULL");
+
 	if (vardefexpr)
 		appendPQExpBuffer(query, " DEFAULT %s",
 						  vardefexpr);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index aa2c7152f09..544b7259623 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -712,6 +712,8 @@ typedef struct _VariableInfo
 	char	   *initrvaracl;
 	Oid			varcollation;
 	const char *rolname;		/* name of owner, or empty string */
+	bool		varnotnull;
+	bool		varisimmutable;
 } VariableInfo;
 
 /*
index d05a32e2ae9..cdcd0637d6b 100644
 
index 93fdeeb6c1b..2ca2688413f 100644
@@ -5167,7 +5167,7 @@ listVariables(const char *pattern, bool verbose)
 
@@ -5188,6 +5188,8 @@ listVariables(const char *pattern, bool verbose)
@@ -5198,6 +5200,8 @@ listVariables(const char *pattern, bool verbose)

 
+	/* don't allow NULL */
 
index 4b2503fc2c8..9a7b0b46040 100644
@@ -3453,6 +3453,8 @@ typedef struct CreateSessionVarStmt

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
2.46.1

From 21ece58d8d893a9e30613fcec97e73bb807f7619 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

index 72ae3b11994..31d467ee4e7 100644
@@ -12128,7 +12128,8 @@ PreparableStmt:

 
-/* LET */

2.46.1

From 63e7c652829441b7b185be29824f37a56f5f790d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 783ecdb59a4..0eab32c991c 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9843,6 +9843,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
 
 		/*
index 31d467ee4e7..8fc88f8b440 100644
 
 %type <ival>	for_locking_strength
 %type <node>	for_locking_item
@@ -5204,26 +5205,39 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 302221f443a..f71c2546161 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5167,7 +5167,7 @@ listVariables(const char *pattern, bool verbose)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, false, false, false, false};
 
@@ -5187,12 +5187,16 @@ listVariables(const char *pattern, bool verbose)
 
 	if (verbose)
 	{

 

index c30b7ed0359..6648c6af715 100644

 
 
 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index e06ee9cc630..08ea60eae71 100644
2.46.1

From 870edbf19646535fd78fde9a2c4de2a7198bd030 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

index aaec439892c..1dc4dc406cf 100644
@@ -509,8 +510,9 @@ standard_ExplainOneQuery(Query *query, int cursorOptions,
 	}

@@ -594,6 +596,25 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
 		else
@@ -617,8 +638,8 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
@@ -667,6 +688,8 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
 
index 9c43b280258..72ae3b11994 100644
@@ -12097,7 +12097,8 @@ ExplainableStmt:

 
 DROP TABLE public.xxtab;
2.46.1

From 722c297337fab06515de107f085f84e77949c2db Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

2.46.1

From 33ff33fc8546600adf9a8267be110b5a5dd88124 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

 
 
  <sect1 id="ddl-others">
 
 /* GUC parameters */

 /* GUC parameters */

 

 
2.46.1

From 281eb03ff5f96622c76706bcfa4f8498fa24ae1a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 

 
 
+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

 	if (sessionvars)

 -- should be zero again
2.46.1

From b1898a33425f16e5a12285ec37a8a157cd95c262 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*
index 6a001750905..cc63cc59756 100644
@@ -12319,4 +12319,12 @@
   proargtypes => 'int2',
   prosrc => 'gist_stratnum_identity' },
 
+# Session variables support
2.46.1

From 39a530325b8782b3314fcda898f59229a513c03b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*
index 4e622bc38c4..9c43b280258 100644
@@ -2072,7 +2072,13 @@ DiscardStmt:

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+extern void ResetSessionVariables(void);
+
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 80f7d56404c..c30b7ed0359 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3963,6 +3963,7 @@ typedef enum DiscardMode
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
 	DISCARD_TEMP,
+	DISCARD_VARIABLES,
 } DiscardMode;

 RESET search_path;
+
2.46.1

From 19797658501aeb8067d7a4e10189d6576bbcef9b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c2cf290cb33..783ecdb59a4 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9788,6 +9788,19 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
 
+   <para>

 

 
+/*

 
+	/*

 
 						/*
index 7042ca6c600..0f6a8c571bc 100644
@@ -193,6 +195,61 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 
+	/*
index d92d43a17ea..0cd8a24ba42 100644
 
 	/*
@@ -565,6 +566,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
@@ -735,6 +737,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root,
 	 */
 	pull_up_subqueries(root);
 
+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*
 
index 7cbb686fae8..4e622bc38c4 100644
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
@@ -747,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEEP KEY KEYS

 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
@@ -1098,6 +1099,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -12733,6 +12735,47 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -17764,6 +17807,7 @@ unreserved_keyword:
@@ -18375,6 +18419,7 @@ bare_label_keyword:

 			/*
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index 2177d17e278..8f472f0fd3c 100644
@@ -8595,6 +8597,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
@@ -13377,6 +13387,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
index 88467977f89..972220fe816 100644
@@ -614,6 +614,18 @@ typedef struct AsyncRequest
 								 * tuples) */
 } AsyncRequest;
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+	Oid			varid;
+	Oid			typid;
+	bool		isnull;
+	Datum		value;
+} SessionVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -666,6 +678,10 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */

index eb4ba2c4215..80f7d56404c 100644

 

 
 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param
 
index 56670e65b11..aefe51f335b 100644

 
 
 /* ----------------
 
 	/*
 
index ce2b61d7cf8..e06ee9cc630 100644
2.46.1

From ff2efe1c00650f7615f11d9076426699981cd85f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bfb97865e18..c2cf290cb33 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -369,6 +369,11 @@
       <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
       <entry>mappings of users to foreign servers</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+      <entry>session variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -9737,4 +9742,119 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </table>
  </sect1>

 
index 6f75bd0c7d2..9132c046eef 100644
@@ -25713,6 +25713,19 @@ SELECT relname FROM pg_class WHERE pg_table_is_visible(oid);

 
 
   <para>

 

index d2abc48fd8d..f2bb3b66676 100644
 
+		case OBJECT_VARIABLE:
@@ -2808,6 +2873,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
@@ -2919,6 +2987,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
@@ -3067,6 +3138,8 @@ pg_aclmask(ObjectType objtype, Oid object_oid, AttrNumber attnum, Oid roleid,
@@ -4334,6 +4407,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*
index 022ddf172a3..58633fdbe1c 100644
@@ -52,6 +52,7 @@
@@ -6668,6 +6669,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -6731,6 +6733,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
index ab304ca989d..7cbb686fae8 100644
@@ -788,8 +789,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 
@@ -1059,6 +1060,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1603,6 +1605,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;

@@ -5185,6 +5188,34 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6971,6 +7002,7 @@ object_type_any_name:
 
 /*
@@ -7847,6 +7879,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7892,6 +7932,14 @@ privilege_target:

@@ -8089,6 +8137,7 @@ defacl_privilege_target:

@@ -9871,6 +9920,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10232,6 +10299,24 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10513,6 +10598,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17841,6 +17934,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18494,6 +18589,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1e15ce10b48..6295468f849 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -104,6 +104,7 @@ typedef struct
 	List	   *indexes;		/* CREATE INDEX items */
 	List	   *triggers;		/* CREATE TRIGGER items */
 	List	   *grants;			/* GRANT items */
+	List	   *variables;		/* CREATE VARIABLE items */
 } CreateSchemaStmtContext;

@@ -3895,6 +3896,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 	cxt.indexes = NIL;
 	cxt.triggers = NIL;
 	cxt.grants = NIL;
+	cxt.variables = NIL;
 
 	/*
 	 * Run through each schema element in the schema element list. Separate
@@ -3963,6 +3965,15 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				cxt.grants = lappend(cxt.grants, element);
 				break;
 
@@ -3976,6 +3987,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 
 	return result;
 }

index 2a716cc6b7f..98522a45612 100644
@@ -851,6 +851,10 @@ acldefault(ObjectType objtype, Oid ownerId)
@@ -948,6 +952,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
 
 	return subname;
 }
+

 
 
 	int			useDB;
index 130b80775db..f0a75007a77 100644
 
+/*
+ * getVariables
@@ -9971,7 +10156,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -10570,6 +10756,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION_REL:
 			dumpSubscriptionTable(fout, (const SubRelInfo *) dobj);
 			break;
+		case DO_VARIABLE:
+			dumpVariable(fout, (VariableInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -15019,6 +15208,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
 		case DEFACLOBJ_NAMESPACE:
 			type = "SCHEMAS";
 			break;
+		case DEFACLOBJ_VARIABLE:
+			type = "VARIABLES";
+			break;
 		default:
 			/* shouldn't get here */
 			pg_fatal("unrecognized object type in default privileges: %d",
@@ -18633,6 +18825,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_CONVERSION:
 			case DO_TABLE:
 			case DO_TABLE_ATTACH:
+			case DO_VARIABLE:
 			case DO_ATTRDEF:
 			case DO_PROCLANG:
 			case DO_CAST:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 9f907ed5ad4..af15b3f2188 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -52,6 +52,7 @@ typedef enum
 	DO_TABLE,
 	DO_TABLE_ATTACH,
 	DO_ATTRDEF,
+	DO_VARIABLE,
 	DO_INDEX,
 	DO_INDEX_ATTACH,
 	DO_STATSEXT,
@@ -694,6 +695,23 @@ typedef struct _SubRelInfo
 	char	   *srsublsn;
 } SubRelInfo;
 
+/*
@@ -777,5 +795,6 @@ extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
 
 #endif							/* PG_DUMP_H */
index ab6c8304913..ef8ca0c60cf 100644
 
@@ -1674,6 +1684,23 @@ my %tests = (
 		},
 	},
 
@@ -3932,6 +3959,24 @@ my %tests = (
 		},
 	},
 
@@ -4394,6 +4439,25 @@ my %tests = (
 		like => {},
 	},
 
index faabecbc76f..302221f443a 100644
@@ -5156,6 +5156,102 @@ error_return:
 	return false;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 
 
index 43f608d7a0a..6a001750905 100644
@@ -6533,6 +6533,9 @@
 
index e62ce1b7536..eb4ba2c4215 100644
 
@@ -3423,6 +3424,21 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+
index 899d64ad55f..56670e65b11 100644
@@ -484,6 +484,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)

 
+\dV "no.such.variable"
+            List of variables

index 4f38104ba01..be981ef22eb 100644
 
 # ----------

 

 
 -- check \drg and \du
index ace5414fa5b..ce2b61d7cf8 100644
@@ -3073,6 +3076,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.46.1

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 22 Sep 2024 10:43:22 +0200

Hi

fix assertion failure after
https://github.com/postgres/postgres/commit/24f5205948093a96edf8213294b3d585ac3fe1fb

the analyze routine for LetStmt was fixed

<-->/*
<--> * The query is executed as utility command by nested executor call.
<--> * Assigned queryId is required in this case.
<--> */
<-->if (IsQueryIdEnabled())

Regards

Pavel
From 9162f453eec404b7180109cd903c26e2039047cd Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From 288d09fbe4c98843243f2bf369c6500d9d2be365 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From 907e88cfebc8182cfa86565663c888895bab9cc7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:

 

 
 
 /*

 

index 0233b0d88d9..788bda04eb1 100644
@@ -5935,20 +5935,20 @@ CREATE ROLE regress_variable_owner;

 DROP VARIABLE var1;
@@ -6416,9 +6416,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-                                         List of variables
 
@@ -6591,9 +6591,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
 -- again, but with current database and dotted schema qualifications.
@@ -6730,9 +6730,9 @@ List of text search templates
 (0 rows)

 
 
 DROP VARIABLE var1;
+

From 8c34af7406c7f78631470a3b11c7d1bed6bd5992 Mon Sep 17 00:00:00 2001

 
+/*

 

 

 select a from atacc1;

 
index c5f1f53e70e..0233b0d88d9 100644
@@ -214,7 +214,7 @@ select $1::int as col \bind 1 \g \bind 2 \g

From dda8428aed2409ecc65f6fdd021a354806a89be5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

index e313aad2e72..04831da00db 100644
@@ -1929,6 +1929,18 @@ transformLetStmt(ParseState *pstate, LetStmt *stmt)

+	/*

 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index 37b05021657..c0384f0281c 100644

From 23419d7c1357d917400058cc4ad4e4986bb08282 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*

 

index be677b04115..e313aad2e72 100644
@@ -3384,14 +3384,6 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 
-	/*

 

 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.46.1

From 6fd7b79133f33163b722396fe8eef4db9ade8f39 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

index 8ad4a25cf93..cc09bf7decd 100644
@@ -211,7 +211,19 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)

 

 

 	/*

 

From e4d3aa96b7f771a49581cd1b8a6b592b62a56bbf Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*

 /*
 
 CreateSessionVarStmt:

 

 

 
+	if (varnotnull)
 
 /*

 

+	/* don't allow NULL */
 
index 247195b2531..c5f1f53e70e 100644
@@ -5935,20 +5935,20 @@ CREATE ROLE regress_variable_owner;

 DROP VARIABLE var1;
@@ -6416,9 +6416,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-                               List of variables
 
@@ -6591,9 +6591,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
 -- again, but with current database and dotted schema qualifications.
@@ -6730,9 +6730,9 @@ List of text search templates
 (0 rows)

 
 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From b01c6b72b8fba0c19b57074042bc08584ae25c78 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{

 
 CreateSessionVarStmt:

 			if (isAgg)

 

 
 
+	if (vardefexpr)

 
 
 	if (verbose)

 

index ccf29514d9a..247195b2531 100644
@@ -5935,20 +5935,20 @@ CREATE ROLE regress_variable_owner;

 DROP VARIABLE var1;
@@ -6416,9 +6416,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-                          List of variables
 
@@ -6591,9 +6591,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
 -- again, but with current database and dotted schema qualifications.
@@ -6730,9 +6730,9 @@ List of text search templates
 (0 rows)

 
 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From 4b9b3aafcca614a7863ca15d7e0099072ed8f1c5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;

 

 

 

 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From b79ea358fc5ba92522355a8bbd711833c907b03e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
index 58652815889..be677b04115 100644
@@ -3304,10 +3304,10 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 
 		/*

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;
 
index 969c13de606..ccf29514d9a 100644
@@ -5935,20 +5935,20 @@ CREATE ROLE regress_variable_owner;

 DROP VARIABLE var1;
@@ -6416,9 +6416,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-            List of variables
 
@@ -6591,9 +6591,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
 -- again, but with current database and dotted schema qualifications.
@@ -6730,9 +6730,9 @@ List of text search templates
 (0 rows)

 
 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index fc019b829bb..37b05021657 100644

From 037d57032437d6ab9e7f45505c924bb8f1018651 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

 

 DROP TABLE public.xxtab;

From d994f5ebd4a52b93b0d48d50643825850943aca6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

 
 
  <sect1 id="ddl-others">
 
 /* GUC parameters */

 /* GUC parameters */

 

 

From e0acf56008afb6dac2d51b0559c3903416d38032 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From 5d60aaddcbb57d2c51a8aee63c0d8f859f73f1c7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

 

-/* LET */

From 59fea7fb96fc256f2bfe3b71c4ea07e99c0d8a1f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+

From a908c8d9aeafc5bdaa84dfd406e322bab0c37840 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*

From 3d5e4880b15a2eea09f0b0f2b6506a0e64f13290 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 

 
 
+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

 	if (sessionvars)

 -- should be zero again

From 78cf3926ed9059e060a500c342599fc893c1e7f2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 

index e39d524b6bd..3a45c9aa057 100644
@@ -25717,6 +25717,19 @@ SELECT relname FROM pg_class WHERE pg_table_is_visible(oid);

 
 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*
index 2d703aa22e2..527b0d73010 100644
@@ -6713,6 +6714,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -6776,6 +6778,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 
 
index 2bb87897505..eeab5a5cb3b 100644
@@ -1080,6 +1080,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 

 
 
+

index 3819bf5e254..969c13de606 100644
@@ -5930,6 +5930,30 @@ COMMIT;
@@ -6151,6 +6175,12 @@ cross-database references are not implemented: nonesuch.public.func_deps_stat
@@ -6385,6 +6415,12 @@ List of schemas
 
+\dV "no.such.variable"
+            List of variables
@@ -6554,6 +6590,12 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
@@ -6687,6 +6729,12 @@ List of text search templates
 
@@ -6734,6 +6782,8 @@ cross-database references are not implemented: "no.such.database"."no.such.schem
 
 # ----------
index c5021fc0b13..666eddb632d 100644
@@ -1631,6 +1631,19 @@ COMMIT;
 
@@ -1742,6 +1755,9 @@ DROP FUNCTION psql_error;
 
@@ -1783,6 +1799,8 @@ DROP FUNCTION psql_error;
 
@@ -1823,6 +1841,7 @@ DROP FUNCTION psql_error;
 
@@ -1847,6 +1866,7 @@ DROP FUNCTION psql_error;
 
@@ -1872,6 +1892,7 @@ DROP FUNCTION psql_error;
 
 -- check \drg and \du
index b6135f03479..3d189a91bb7 100644
@@ -3074,6 +3077,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.46.1

From 4583de7b4a56b9e38362b2a314e586d3e7298f33 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 src/backend/parser/analyze.c                  | 271 ++++-
 59 files changed, 3935 insertions(+), 23 deletions(-)

+     <row>
 
+   <para>

 

 
+/*

 
+	/*

 
 						/*
index 713cf3e802c..8ad4a25cf93 100644
@@ -194,6 +196,61 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 
+	/*
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*
 
index e901203424d..58652815889 100644

 
+		case T_LetStmt:
 
 			/*

 

 
 
 	foreach(l, stmt->lockingClause)
 	{
@@ -1685,12 +1701,242 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+	 * The query is executed as utility command by nested executor call.
+	 * Assigned queryId is required in this case.
+	if (IsQueryIdEnabled())
+		JumbleQuery(query);
@@ -1936,6 +2182,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 
@@ -2410,6 +2657,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)

@@ -2477,6 +2725,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)

 
@@ -2664,9 +2913,15 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 	/*
@@ -2708,6 +2963,10 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 
+	/*
 
 	if (indirection)
@@ -2850,6 +3109,8 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 							   (LockingClause *) lfirst(l), false);
 	}

 	/* this must be done after collations, for reliable comparison of exprs */
@@ -3123,6 +3384,14 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 							 true,
 							 stmt->funccall->location);
 
+	/*

 

 
 
 			/*
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*
 
index 3d189a91bb7..fc019b829bb 100644

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 9 Oct 2024 06:24:56 +0200

Hi

fresh rebase

Regards

Pavel
From a2474d1d1d4b0809267105b0b471e5df561e1e0c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 
2.46.2

From dbbd31d8e6789766c448aedd0ae73a71ce770058 Mon Sep 17 00:00:00 2001

 
+/*

 

 
index 143ae7c09c0..cf75916f45f 100644

 select a from atacc1;

index ab449fa7b80..4217dd5f5c3 100644
@@ -148,7 +148,7 @@ LINE 1: COPY x TO stdout WHERE a = 1;
index 12abd3a0e7a..d5676d88d44 100644
@@ -6240,13 +6240,13 @@ LINE 1: select t2.uunique1 from
@@ -7343,7 +7343,7 @@ lateral (select * from int8_tbl t1,
 
@@ -7355,7 +7355,7 @@ LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
@@ -7392,7 +7392,7 @@ LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
@@ -7412,7 +7412,7 @@ update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1)
index 2b47013f113..2e9db29690d 100644

2.46.2

From 156aa7a8260bba545212bba0673216a5f9a4e520 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*
index 89f417cfbe4..bc19bf92b35 100644

 /*
 
index c79b17e43c5..93aba4b5367 100644

 
 
+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
+typedef struct PLpgSQL_stmt_let
+{
+	PLpgSQL_stmt_type cmd_type;
+	int			lineno;
+	unsigned int stmtid;
+	PLpgSQL_expr *expr;
+} PLpgSQL_stmt_let;
+
 /*
  * PERFORM statement
  */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 65b31ea6c43..3338e9b6dda 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1889,6 +1889,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
-- 
2.46.2

From 79d1469aedd5dfe899f3b0630215c4d0ca369e21 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 src/bin/psql/tab-complete.in.c                |   5 +

index 9c2d9043f59..e4005dd2afd 100644
 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*
index bc19bf92b35..64af450b37e 100644

 /*

@@ -5239,31 +5239,33 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5291,6 +5293,12 @@ OptImmutable: IMMUTABLE								{ $$ = true; }
 
+OptTransactional:
+			TRANSACTION								{ $$ = true; }
+			| TRANSACTIONAL							{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
@@ -18032,6 +18040,7 @@ unreserved_keyword:
 			| TEXT_P
 			| TIES
 			| TRANSACTION
+			| TRANSACTIONAL
 			| TRANSFORM
 			| TRIGGER
 			| TRUNCATE
@@ -18680,6 +18689,7 @@ bare_label_keyword:
 			| TIMESTAMP
 			| TRAILING
 			| TRANSACTION
+			| TRANSACTIONAL
 			| TRANSFORM
 			| TREAT
 			| TRIGGER
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3adb849613f..c15dba130c9 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5426,6 +5426,7 @@ getVariables(Archive *fout)
 	int			i_varcollation;
 	int			i_varnotnull;
 	int			i_varisimmutable;
+	int			i_varistransact;
 	int			i_varacl;
 	int			i_acldefault;
 	int			i,
@@ -5450,6 +5451,7 @@ getVariables(Archive *fout)
 					  "       END AS varcollation,\n"
 					  "       v.varnotnull,\n"
 					  "       v.varisimmutable,\n"
+					  "       v.varistransact,\n"
 					  "       pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n"
 					  "       v.varowner, v.varacl,\n"
 					  "       acldefault('V', v.varowner) AS acldefault\n"
@@ -5472,6 +5474,7 @@ getVariables(Archive *fout)
 	i_varcollation = PQfnumber(res, "varcollation");
 	i_varnotnull = PQfnumber(res, "varnotnull");
 	i_varisimmutable = PQfnumber(res, "varisimmutable");
+	i_varistransact = PQfnumber(res, "varistransact");
 
@@ -5500,6 +5503,7 @@ getVariables(Archive *fout)
 
@@ -5547,6 +5551,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5564,12 +5569,13 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 
 
 /*
 
index 89b54182f98..23c8c3ce272 100644

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 17d44b1e9cd..f1e99c09846 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -1305,6 +1305,8 @@ static const pgsql_thing_t words_after_create[] = {
 																			 * TABLE ... */
 	{"TEXT SEARCH", NULL, NULL, NULL},
 	{"TRANSFORM", NULL, NULL, NULL, NULL, THING_NO_ALTER},
+	{"TRANSACTIONAL", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE TRANSACTIONAL
+																				* VARIABLE ... */
 	{"TRIGGER", "SELECT tgname FROM pg_catalog.pg_trigger WHERE tgname LIKE '%s' AND NOT tgisinternal"},
 	{"TYPE", NULL, NULL, &Query_for_list_of_datatypes},
 	{"UNIQUE", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE UNIQUE
@@ -3947,8 +3949,11 @@ match_previous_words(int pattern_id,
 	}
 /* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VARIABLE <name> with AS */
+	else if (Matches("CREATE", "TRANSACTION|TRANSACTIONAL"))
+		COMPLETE_WITH("IMMUTABLE", "VARIABLE");
 	else if (TailMatches("CREATE", "VARIABLE", MatchAny) ||
 			 TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny) ||
+			 TailMatches("TRANSACTION|TRANSACTIONAL", "VARIABLE", MatchAny) ||
 			 TailMatches("IMMUTABLE", "VARIABLE", MatchAny))
 		COMPLETE_WITH("AS");
 	else if (TailMatches("VARIABLE", MatchAny, "AS"))
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
index 68bc49a0e27..1dc44edf6e5 100644
--- a/src/include/catalog/pg_variable.h
+++ b/src/include/catalog/pg_variable.h
@@ -61,6 +61,9 @@ CATALOG(pg_variable,9222,VariableRelationId)
 	/* don't allow changes */
 	bool		varisimmutable BKI_DEFAULT(f);

index 93aba4b5367..3db948d18ed 100644
@@ -3472,6 +3472,7 @@ typedef struct CreateSessionVarStmt

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+
2.46.2

From a1dab1afa134e36f7c6aa00c16e6d8b687b0b7bf Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked
 
2.46.2

From 4ff0b4e17a3618807930a3709f44607365035a6a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

diff --git a/doc/src/sgml/parallel.sgml b/doc/src/sgml/parallel.sgml
index 683dede6adc..1ce9abf86f5 100644
--- a/doc/src/sgml/parallel.sgml
+++ b/doc/src/sgml/parallel.sgml
@@ -515,12 +515,6 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
         Plan nodes that reference a correlated <literal>SubPlan</literal>.
       </para>
     </listitem>
-
-    <listitem>
-      <para>
-        Plan nodes that use a session variable.
-      </para>
-    </listitem>
   </itemizedlist>
 
index ba6e38263cc..b926d37af17 100644

 

 

 	/*

 
 
2.46.2

From 91d9e736f7da0919b35833e422387fb449dac163 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*

 

 
-	/*

 

 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.46.2

From 9a0fe275fdcccda919433c13d01804dc06405a69 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

index 752b895de45..40fb03fe265 100644
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vardefexpr</structfield> <type>pg_node_tree</type>
+      </para>
+      <para>
+       The internal representation of the variable default value
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 38c24240b6b..b88078a4d3a 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5304,14 +5304,14 @@ SELECT current_user_id;
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{
 
index 3dc3b7e1642..749874eb6d9 100644
 
 %type <node>	json_format_clause
 				json_format_clause_opt
@@ -5229,30 +5230,36 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:

 			if (isAgg)
 
index a3e7d0e2a8c..e0cee1dd66f 100644
@@ -5420,6 +5420,7 @@ getVariables(Archive *fout)
@@ -5445,6 +5446,7 @@ getVariables(Archive *fout)
@@ -5461,6 +5463,7 @@ getVariables(Archive *fout)
 
@@ -5496,6 +5499,11 @@ getVariables(Archive *fout)
 		varinfo[i].dacl.initprivs = NULL;
 		varinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_varowner));
 
@@ -5528,6 +5536,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5540,6 +5549,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

@@ -5559,6 +5569,10 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (vardefexpr)

index f6982fc7a7f..e805f223e56 100644

 	if (verbose)

index 8beeff36173..e0c864f081f 100644
@@ -3468,6 +3468,7 @@ typedef struct CreateSessionVarStmt

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+
2.46.2

From 613210aadfacd5dbb641b1814e2621598f7337ef Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

index f4a226e1685..752b895de45 100644

 
 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0c4f3b54e20..3dc3b7e1642 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5258,6 +5258,7 @@ CreateSessionVarStmt:
  * transaction end like tables.
  */
 XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
+			| ON TRANSACTION END_P RESET			{ $$ = VARIABLE_XACTEND_RESET; }
 			| /*EMPTY*/								{ $$ = VARIABLE_XACTEND_NOOP; }
 		;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b6d50b94db6..a3e7d0e2a8c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5420,6 +5420,7 @@ getVariables(Archive *fout)
 	int			i_varnamespace;
 	int			i_vartype;
 	int			i_vartypname;
+	int			i_varxactendaction;
 	int			i_varowner;
 	int			i_varcollation;
 	int			i_varacl;
@@ -5437,6 +5438,7 @@ getVariables(Archive *fout)
 	/* get the variables in current database */
 	appendPQExpBuffer(query,
 					  "SELECT v.tableoid, v.oid, v.varname,\n"
+					  "       v.varxactendaction,\n"
 					  "       v.varnamespace, v.vartype,\n"
 					  "       pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n"
 					  "       CASE WHEN v.varcollation <> t.typcollation "
@@ -5459,6 +5461,7 @@ getVariables(Archive *fout)
 	i_varnamespace = PQfnumber(res, "varnamespace");
 	i_vartype = PQfnumber(res, "vartype");
 	i_vartypname = PQfnumber(res, "vartypname");
+	i_varxactendaction = PQfnumber(res, "varxactendaction");
 	i_varcollation = PQfnumber(res, "varcollation");
 
 	i_varowner = PQfnumber(res, "varowner");
@@ -5482,6 +5485,9 @@ getVariables(Archive *fout)

 		varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl));
@@ -5522,6 +5528,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	PQExpBuffer query;
 	char	   *qualvarname;
 	const char *vartypname;
+	const char *varxactendaction;
 	Oid			varcollation;
 
 	/* skip if not to be dumped */
@@ -5533,6 +5540,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
@@ -5551,6 +5559,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}

 
index d83dbeca969..f6982fc7a7f 100644
 
 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4
 
2.46.2

From 45bc634c25090010e8d1b3a95b4a1bb81d041f53 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 src/bin/psql/tab-complete.in.c                |  12 +-

index 40fb03fe265..9c2d9043f59 100644
 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*
index 749874eb6d9..89f417cfbe4 100644

 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -5230,27 +5231,31 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5270,6 +5275,14 @@ XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
 		;

index e0cee1dd66f..3adb849613f 100644
@@ -5424,6 +5424,8 @@ getVariables(Archive *fout)
@@ -5446,6 +5448,8 @@ getVariables(Archive *fout)
@@ -5466,6 +5470,8 @@ getVariables(Archive *fout)
 
 	i_varowner = PQfnumber(res, "varowner");
 	i_varacl = PQfnumber(res, "varacl");
@@ -5492,6 +5498,8 @@ getVariables(Archive *fout)
 			pg_strdup(PQgetvalue(res, i, i_varxactendaction));

@@ -5538,7 +5546,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5552,12 +5562,14 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 
 	if (OidIsValid(varcollation))
 	{
@@ -5569,6 +5581,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (varnotnull)
 
 /*
 
index e805f223e56..89b54182f98 100644

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 82c1eabdc71..17d44b1e9cd 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -1273,6 +1273,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"IMMUTABLE VARIABLE", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE IMMUTABLE
+																					 * VARIABLE ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -3593,7 +3595,8 @@ match_previous_words(int pattern_id,
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
-		COMPLETE_WITH("SEQUENCE", "TABLE", "VARIABLE", "VIEW");
+		COMPLETE_WITH("IMMUTABLE VARIABLE", "SEQUENCE", "TABLE", "VARIABLE",
+					  "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or SEQUENCE */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "SEQUENCE");
@@ -3945,7 +3948,8 @@ match_previous_words(int pattern_id,
 /* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VARIABLE <name> with AS */
 	else if (TailMatches("CREATE", "VARIABLE", MatchAny) ||
-			 TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny))
+			 TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny) ||
+			 TailMatches("IMMUTABLE", "VARIABLE", MatchAny))
 		COMPLETE_WITH("AS");
 	else if (TailMatches("VARIABLE", MatchAny, "AS"))
 		/* Complete CREATE VARIABLE <name> with AS types */
@@ -4589,6 +4593,10 @@ match_previous_words(int pattern_id,
 	else if (TailMatches("FROM", "SERVER", MatchAny, "INTO", MatchAny))
 		COMPLETE_WITH("OPTIONS (");

+	/* don't allow NULL */
 
index e0c864f081f..c79b17e43c5 100644
@@ -3468,6 +3468,8 @@ typedef struct CreateSessionVarStmt

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
2.46.2

From bd9c9160d8f0b5fda1dc306afc4591f5047e010c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

index ee1bcb84e28..752436d1d27 100644
@@ -515,8 +516,9 @@ standard_ExplainOneQuery(Query *query, int cursorOptions,
 	}

@@ -600,6 +602,25 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
 		else
@@ -623,8 +644,8 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
@@ -673,6 +694,8 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
 
index 929e340ac48..f3e48a68dac 100644
@@ -12121,7 +12121,8 @@ ExplainableStmt:

 
 DROP TABLE public.xxtab;
2.46.2

From bd4ef67967fab0cc436aeb2f3a8d89c4013df281 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

 src/bin/psql/tab-complete.in.c                |  4 +-

index f3e48a68dac..f9914917ac8 100644
@@ -12152,7 +12152,8 @@ PreparableStmt:

+		/* LET is allowed by parser, but not supported. Reject for now */
+		if (IsA(cte->ctequery, LetStmt))
+			ereport(ERROR,
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("LET not supported in WITH query"),
+					parser_errposition(pstate, cte->location));
+
 		for_each_cell(rest, withClause->ctes, lnext(withClause->ctes, lc))
 		{
 			CommonTableExpr *cte2 = (CommonTableExpr *) lfirst(rest);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 5ca87ec83e9..05b25a3a452 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -4629,9 +4629,9 @@ match_previous_words(int pattern_id,
 	else if (TailMatches("VALUES") && !TailMatches("DEFAULT", "VALUES"))
 		COMPLETE_WITH("(");
 
-/* LET */

2.46.2

From 6de5e420836717e1c05b9af799759bed41dbd974 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 src/bin/psql/tab-complete.in.c                |   5 +-

index 263987dacca..f4a226e1685 100644
 
+     <row>
index 1837193a7e7..38c24240b6b 100644
@@ -5309,9 +5309,9 @@ SELECT current_user_id;
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
 
 		/*
index f9914917ac8..0c4f3b54e20 100644
 
 %type <ival>	for_locking_strength
 %type <node>	for_locking_item
@@ -5228,26 +5229,39 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 
index 02d96401346..d83dbeca969 100644

 	if (verbose)
 	{
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 05b25a3a452..82c1eabdc71 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3593,7 +3593,7 @@ match_previous_words(int pattern_id,
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
-		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+		COMPLETE_WITH("SEQUENCE", "TABLE", "VARIABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or SEQUENCE */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "SEQUENCE");
@@ -3944,7 +3944,8 @@ match_previous_words(int pattern_id,
 	}
 /* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VARIABLE <name> with AS */
-	else if (TailMatches("CREATE", "VARIABLE", MatchAny))
+	else if (TailMatches("CREATE", "VARIABLE", MatchAny) ||
+			 TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny))
 		COMPLETE_WITH("AS");
 	else if (TailMatches("VARIABLE", MatchAny, "AS"))
 		/* Complete CREATE VARIABLE <name> with AS types */
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
index db593cd5bed..1e6dd98d415 100644
--- a/src/include/catalog/pg_variable.h
+++ b/src/include/catalog/pg_variable.h
@@ -55,6 +55,9 @@ CATALOG(pg_variable,9222,VariableRelationId)
 	/* typmod for variable's type */
 	int32		vartypmod BKI_DEFAULT(-1);

 

index df0e69d5942..8beeff36173 100644
@@ -3468,6 +3468,7 @@ typedef struct CreateSessionVarStmt

 
 
 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index c65e52744b9..65b31ea6c43 100644
2.46.2

From 066df00d5ae0363d76efe12279d4e3cc3496a4c5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

index 9707d5238da..4f04c2ab4a9 100644
 
index 12784c245c9..1837193a7e7 100644
@@ -5324,6 +5324,16 @@ SELECT current_user_id;
 
  <sect1 id="ddl-others">
 
 /* GUC parameters */

 /* GUC parameters */

 

 
2.46.2

From db20e4b1f936efef81e877f58ac89610a6aca0d2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

2.46.2

From fb3ca00ab7b054d094da93c47ee3ffe468330a0a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 src/bin/psql/tab-complete.in.c                |  2 +-

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*
index eeffa561731..929e340ac48 100644
@@ -2096,7 +2096,13 @@ DiscardStmt:

diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index bdac691af13..84415f0caa1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -2959,6 +2959,9 @@ CreateCommandTag(Node *parsetree)
 				case DISCARD_SEQUENCES:
 					tag = CMDTAG_DISCARD_SEQUENCES;
 					break;
+				case DISCARD_VARIABLES:
+					tag = CMDTAG_DISCARD_VARIABLES;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index af752e6af3a..5ca87ec83e9 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -4083,7 +4083,7 @@ match_previous_words(int pattern_id,
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+extern void ResetSessionVariables(void);
+
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f68280cd68a..df0e69d5942 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3978,6 +3978,7 @@ typedef enum DiscardMode
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
 	DISCARD_TEMP,
+	DISCARD_VARIABLES,
 } DiscardMode;

 RESET search_path;
+
2.46.2

From 2c8d2b62dbba92bbaaa7c495cb44c327245c8778 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 

 
 
+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

 	if (sessionvars)

 -- should be zero again
2.46.2

From 73343682a683bac7408ba0c84bf618c558651736 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 src/bin/psql/tab-complete.in.c                |  42 ++-

index 964c819a02d..2e892604288 100644
 
index 8ab0ddb112f..06230508e0f 100644
@@ -5263,6 +5263,37 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>

index d0306390346..66c61da56fb 100644
@@ -429,6 +429,14 @@
@@ -717,6 +725,14 @@
@@ -1005,6 +1021,14 @@
index b26db3b04b0..3b3a5ea4807 100644
@@ -25719,6 +25719,19 @@ SELECT relname FROM pg_class WHERE pg_table_is_visible(oid);
index f54f25c1c6b..d0cb53763aa 100644
@@ -1664,6 +1664,21 @@
    </glossdef>
   </glossentry>

 
 
   <para>

 

index 95eb0b12277..0d7e16f708a 100644
@@ -291,6 +292,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
@@ -535,6 +539,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
@@ -640,6 +648,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
@@ -830,6 +841,18 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
@@ -919,6 +942,32 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
@@ -1078,6 +1127,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
@@ -1269,6 +1322,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
 
+		case OBJECT_VARIABLE:
+			objtype = DEFACLOBJ_VARIABLE;
+			if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
+				this_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) iacls->objtype);
@@ -1512,6 +1571,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case DEFACLOBJ_NAMESPACE:
 				iacls.objtype = OBJECT_SCHEMA;
 				break;
+			case DEFACLOBJ_VARIABLE:
+				iacls.objtype = OBJECT_VARIABLE;
+				break;
 			default:
 				/* Shouldn't get here */
 				elog(ERROR, "unexpected default ACL type: %d",
@@ -1572,6 +1634,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case ParameterAclRelationId:
 				istmt.objtype = OBJECT_PARAMETER_ACL;
 				break;
+			case VariableRelationId:
+				istmt.objtype = OBJECT_VARIABLE;
+				break;
 			default:
 				elog(ERROR, "unexpected object class %u", classid);
 				break;
@@ -2813,6 +2878,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("permission denied for type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("permission denied for session variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("permission denied for view %s");
 						break;
@@ -2924,6 +2992,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("must be owner of type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("must be owner of session variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("must be owner of view %s");
 						break;
@@ -3072,6 +3143,8 @@ pg_aclmask(ObjectType objtype, Oid object_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return object_aclmask(TypeRelationId, object_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return object_aclmask(VariableRelationId, object_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) objtype);
@@ -4339,6 +4412,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 			defaclobjtype = DEFACLOBJ_NAMESPACE;
 			break;
 
+		case OBJECT_VARIABLE:
 
index 30807f91904..86e2258657d 100644
@@ -986,6 +987,67 @@ RelationIsVisibleExt(Oid relid, bool *is_missing)
 	return visible;
 }
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
@@ -3289,6 +3351,66 @@ TSConfigIsVisibleExt(Oid cfgid, bool *is_missing)
 	return visible;
 }
 
+/*
 
 /*
  * DeconstructQualifiedName
@@ -5085,3 +5207,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 05a6de68ba3..ef71d7b94a7 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -2154,6 +2154,8 @@ stringify_grant_objtype(ObjectType objtype)
 			return "TABLESPACE";
 		case OBJECT_TYPE:
 			return "TYPE";
+		case OBJECT_VARIABLE:
+			return "VARIABLE";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
@@ -2237,6 +2239,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 			return "TABLESPACES";
 		case OBJECT_TYPE:
 			return "TYPES";
+		case OBJECT_VARIABLE:
+			return "VARIABLES";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5607273bf9f..c026ab5dcb8 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -92,6 +92,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_USER_MAPPING:
+		case OBJECT_VARIABLE:
 			return false;
 
 			/*
index af8c05b91f1..21b5b5a3491 100644
@@ -6720,6 +6721,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -6783,6 +6785,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
index 4aa8646af7b..50993bb2325 100644

 

@@ -5209,6 +5212,34 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6995,6 +7026,7 @@ object_type_any_name:
 
 /*
@@ -7871,6 +7903,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7916,6 +7956,14 @@ privilege_target:

@@ -8113,6 +8161,7 @@ defacl_privilege_target:

@@ -9895,6 +9944,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10256,6 +10323,24 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10537,6 +10622,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17865,6 +17958,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18518,6 +18613,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P

 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
index 68ae2970ade..3fa61090c64 100644
@@ -134,12 +134,14 @@ typedef struct _restoreOptions
 
 	int			useDB;
index 1b47c388ced..b6d50b94db6 100644
@@ -5403,6 +5404,190 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
@@ -9977,7 +10162,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -10576,6 +10762,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
@@ -15025,6 +15214,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -18644,6 +18836,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
+/*
 
 #endif							/* PG_DUMP_H */

 
 
index 6a36c910833..02d96401346 100644
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
+extern bool listVariables(const char *pattern, bool varbose);
+
 #endif							/* DESCRIBE_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 19d20c58781..8ea5c4a8f2f 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -268,6 +268,7 @@ slashUsage(unsigned short int pager)
 	HELP0("  \\dT[S+] [PATTERN]      list data types\n");
 	HELP0("  \\du[S+] [PATTERN]      list roles\n");
 	HELP0("  \\dv[S+] [PATTERN]      list views\n");
+	HELP0("  \\dV     [PATTERN]      list session variables\n");
 	HELP0("  \\dx[+]  [PATTERN]      list extensions\n");
 	HELP0("  \\dX     [PATTERN]      list extended statistics\n");
 	HELP0("  \\dy[+]  [PATTERN]      list event triggers\n");
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index a9f4d205e14..4539b97225a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -970,6 +970,13 @@ static const SchemaQuery Query_for_trigger_of_table = {
 	.refnamespace = "c1.relnamespace",
 };

 /*
@@ -1304,6 +1311,7 @@ static const pgsql_thing_t words_after_create[] = {
@@ -1865,7 +1873,7 @@ psql_completion(const char *text, int start, int end)
@@ -2556,6 +2564,9 @@ match_previous_words(int pattern_id,
@@ -3121,7 +3132,7 @@ match_previous_words(int pattern_id,
@@ -3931,6 +3942,13 @@ match_previous_words(int pattern_id,
 
@@ -4193,6 +4211,12 @@ match_previous_words(int pattern_id,
 
+	/* DROP VARIABLE */
@@ -4394,7 +4418,7 @@ match_previous_words(int pattern_id,
@@ -4416,7 +4440,8 @@ match_previous_words(int pattern_id,
@@ -4424,7 +4449,8 @@ match_previous_words(int pattern_id,
@@ -4460,6 +4486,8 @@ match_previous_words(int pattern_id,
@@ -4762,7 +4790,7 @@ match_previous_words(int pattern_id,
 
 /* PREPARE xx AS */
 
 /*
@@ -5228,6 +5256,8 @@ match_previous_words(int pattern_id,

 

 
 
index 3ae31a614ce..3530de99dae 100644
@@ -6553,6 +6553,9 @@
 
index 1c314cd9074..b5f13cb09c0 100644
 
@@ -3438,6 +3439,21 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du
index a65e1c07c5d..c2375a76d8d 100644
@@ -3075,6 +3078,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.46.2

From 622f38fade377e8964d93501c3abea83a7837444 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*
index 3530de99dae..58227db933a 100644
@@ -12345,4 +12345,12 @@
 
2.46.2

From 2fb59be9a3bd416c819927544cc3759e827d25d5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 src/bin/psql/tab-complete.in.c                |  12 +-

index 2e892604288..263987dacca 100644
 
+     <row>
index 06230508e0f..12784c245c9 100644
@@ -5282,6 +5282,38 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
+   <para>
index 66c61da56fb..ac878cf4dac 100644
@@ -437,6 +437,14 @@
@@ -733,6 +741,14 @@
@@ -1029,6 +1045,14 @@
index 1ce9abf86f5..683dede6adc 100644
@@ -515,6 +515,12 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';

 

index 86e2258657d..487353ef2eb 100644
@@ -3412,6 +3412,321 @@ LookupVariable(const char *nspname,
 	return varoid;
 }
 
+/*

 
+	/*

 
 						/*
index cc9a594cba5..ba6e38263cc 100644
 
+	/*
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*
 
index 4d7f972caf1..271fb0ccc49 100644
@@ -1410,6 +1410,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,

 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*
 
index 50993bb2325..eeffa561731 100644

 
@@ -12757,6 +12759,47 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 
@@ -17788,6 +17831,7 @@ unreserved_keyword:
@@ -18399,6 +18443,7 @@ bare_label_keyword:

 			/*
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 4539b97225a..af752e6af3a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -1214,8 +1214,8 @@ static const char *const sql_commands[] = {
 	"ABORT", "ALTER", "ANALYZE", "BEGIN", "CALL", "CHECKPOINT", "CLOSE", "CLUSTER",
 	"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
 	"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
-	"FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LISTEN", "LOAD", "LOCK",
-	"MERGE INTO", "MOVE", "NOTIFY", "PREPARE",
+	"FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LET",
+	"LISTEN", "LOAD", "LOCK", "MERGE INTO", "MOVE", "NOTIFY", "PREPARE",
 	"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
 	"RESET", "REVOKE", "ROLLBACK",
 	"SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
@@ -4629,6 +4629,14 @@ match_previous_words(int pattern_id,
 	else if (TailMatches("VALUES") && !TailMatches("DEFAULT", "VALUES"))
 		COMPLETE_WITH("(");
 
+/* LET */

 
+	/*

 
index e4698a28c4f..7aa5f90abd5 100644
@@ -617,6 +617,18 @@ typedef struct AsyncRequest
 								 * tuples) */
 } AsyncRequest;
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+	Oid			varid;
+	Oid			typid;
+	bool		isnull;
+	Datum		value;
+} SessionVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -669,6 +681,10 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */

index b5f13cb09c0..f68280cd68a 100644

 

 
 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param
index 93137261e48..61341c50cf7 100644
@@ -124,4 +124,6 @@ extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);

 

 /* ----------------
 
 	/*
 
index c2375a76d8d..c65e52744b9 100644
@@ -1497,6 +1497,7 @@ LargeObjectDesc
2.46.2

---

From: Laurenz Albe <[email-masked]>
Date: Wed, 23 Oct 2024 07:11:08 +0300

I have gone over patch 3 from the set and worked on the comments.

> +	elog(DEBUG1, "pg_session_variables start");

I don't think that message is necessary, particularly with DEBUG1.
I have removed this message and the "end" message as well.

> +			memset(nulls, 0, sizeof(nulls));

Instead of explicitly zeroing out the arrays, I have used an empty initializer
in the definition, like

  bool		nulls[NUM_PG_SESSION_VARIABLES_ATTS] = {};

That should have the same effect.
If you don't like that, I have no real problem with your original code.

> +			values[3] = ObjectIdGetDatum(svar->typid);

You are using the type ID without checking if it exists in the catalog.
I think that is a bug.

There is a dependency between the variable and the type, but if a concurrent
session drops both the variable and the data type, the non-existing type ID
would still show up in the function output.
Even worse, the OID could have been reused for a different type since.

I am attaching just patch number 3 and leave you to adapt the patch set,
but I don't think any of the other patches should be affected.

Yours,
Laurenz Albe
From 422d0b6793b88951cd24a56ab45f5d7699e70c6b Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 23 Oct 2024 06:56:02 +0300
Subject: [PATCH vv20241023] function pg_session_variables for cleaning tests

This is a function designed for testing and debugging.  It returns the
content of sessionvars as-is, and can therefore display entries about
session variables that were dropped but for which this backend didn't
process the shared invalidations yet.
---
 src/backend/commands/session_variable.c | 89 +++++++++++++++++++++++++
 src/include/catalog/pg_proc.dat         |  8 +++
 2 files changed, 97 insertions(+)

diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 548d9835c0d..19f772a9fb6 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -569,3 +569,92 @@ ExecuteLetStmt(ParseState *pstate,
 
 	PopActiveSnapshot();
 }
+
+/*
+ * pg_session_variables - designed for testing
+ *
+ * This is a function designed for testing and debugging.  It returns the
+ * content of session variables as-is, and can therefore display data about
+ * session variables that were dropped, but for which this backend didn't
+ * process the shared invalidations yet.
+ */
+Datum
+pg_session_variables(PG_FUNCTION_ARGS)
+{
+#define NUM_PG_SESSION_VARIABLES_ATTS 8
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	if (sessionvars)
+	{
+		ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+		HASH_SEQ_STATUS status;
+		SVariable	svar;
+
+		hash_seq_init(&status, sessionvars);
+
+		while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+		{
+			Datum		values[NUM_PG_SESSION_VARIABLES_ATTS] = {};
+			bool		nulls[NUM_PG_SESSION_VARIABLES_ATTS] = {};
+			HeapTuple	tp;
+			bool		var_is_valid = false;
+
+			values[0] = ObjectIdGetDatum(svar->varid);
+			values[3] = ObjectIdGetDatum(svar->typid);
+
+			/*
+			 * It is possible that the variable has been dropped from the
+			 * catalog, but not yet purged from the hash table.
+			 */
+			tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+			if (HeapTupleIsValid(tp))
+			{
+				Form_pg_variable varform = (Form_pg_variable) GETSTRUCT(tp);
+
+				/*
+				 * It is also possible that a variable has been dropped and
+				 * someone created a new variable with the same object ID.  Use
+				 * the catalog information only if that is not the case.
+				 */
+				if (svar->create_lsn == varform->varcreate_lsn)
+				{
+					values[1] = CStringGetTextDatum(
+													get_namespace_name(varform->varnamespace));
+
+					values[2] = CStringGetTextDatum(NameStr(varform->varname));
+					values[4] = CStringGetTextDatum(format_type_be(svar->typid));
+					values[5] = BoolGetDatum(false);
+
+					values[6] = BoolGetDatum(
+											 object_aclcheck(VariableRelationId, svar->varid,
+															 GetUserId(), ACL_SELECT) == ACLCHECK_OK);
+
+					values[7] = BoolGetDatum(
+											 object_aclcheck(VariableRelationId, svar->varid,
+															 GetUserId(), ACL_UPDATE) == ACLCHECK_OK);
+
+					var_is_valid = true;
+				}
+
+				ReleaseSysCache(tp);
+			}
+
+			/* if there is no matching catalog entry, return null values */
+			if (!var_is_valid)
+			{
+				nulls[1] = true;
+				nulls[2] = true;
+				nulls[4] = true;
+				values[5] = BoolGetDatum(true);
+				nulls[6] = true;
+				nulls[7] = true;
+			}
+
+			tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+		}
+	}
+
+	return (Datum) 0;
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 01a118239ce..f60df4db58f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12366,4 +12366,12 @@
   proargtypes => 'int2',
   prosrc => 'gist_stratnum_identity' },
 
2.47.0

---

From: Laurenz Albe <[email-masked]>
Date: Thu, 24 Oct 2024 11:29:02 +0300

... and here is a review for patch 4

I didn't change any code, just added the odd article to a comment.

While running the regression tests with "make installcheck", I noticed two problems:

  --- /home/laurenz/postgresql/src/test/regress/expected/session_variables.out    2024-10-24 11:14:06.717663613 +0300
  +++ /home/laurenz/postgresql/src/test/regress/results/session_variables.out 2024-10-24 11:15:37.999286228 +0300
  @@ -30,6 +30,7 @@
   GRANT ALL ON SCHEMA svartest TO regress_variable_owner;
   CREATE VARIABLE svartest.var1 AS int;
   CREATE ROLE regress_variable_reader;
  +ERROR:  role "regress_variable_reader" already exists

I suggest that patch 0001 should drop role "regress_variable_reader" again.

  @@ -107,7 +108,7 @@
   CONTEXT:  SQL function "sqlfx" statement 1
   SELECT plpgsqlfx(20);
   ERROR:  permission denied for session variable var1
  -CONTEXT:  SQL expression "$1 + var1"
  +CONTEXT:  PL/pgSQL expression "$1 + var1"

That looks like bit rot from your commit 4af123ad45.

Yours,
Laurenz Albe
From 027fb062ecc0840bc5c2d135ebbc8ddc6b046f96 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Thu, 24 Oct 2024 11:17:12 +0300
Subject: [PATCH v20241024] DISCARD VARIABLES

 .../regress/expected/session_variables.out    | 63 +++++++++++++++++++
 11 files changed, 158 insertions(+), 3 deletions(-)

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
 
index 19f772a9fb6..eedce691bc0 100644

-	Assert(sessionvars);
+	/*
+	 * There is no guarantee of session variables being initialized, even when
+	 * receiving an invalidation callback, as DISCARD [ ALL | VARIABLES ]
+	 * destroys the hash table entirely.
+	 */
+	if (!sessionvars)
+		return;
 
 	/*
 	 * If the hashvalue is not specified, we have to recheck all currently
@@ -658,3 +664,23 @@ pg_session_variables(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
+
+/*
+ * Fast drop of the complete content of the session variables hash table, and
+ * This is used by the DISCARD VARIABLES (and DISCARD ALL) command.

index 172a37ed760..deb9563a598 100644
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+extern void ResetSessionVariables(void);
+
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 20ed8394c78..781db0bb303 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3977,6 +3977,7 @@ typedef enum DiscardMode
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
 	DISCARD_TEMP,
+	DISCARD_VARIABLES,
 } DiscardMode;
 
index 16e1d01ddc6..bcc8c8d847d 100644
@@ -977,3 +977,66 @@ DROP VARIABLE :"DBNAME".:"DBNAME".b;
 
 RESET search_path;
+

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 25 Oct 2024 07:21:34 +0200

removed

> If you don't like that, I have no real problem with your original code.

I prefer the original way - minimally it is a common pattern. I didn't find
any usage of `= {} ` in code

> You are using the type ID without checking if it exists in the catalog.

The original idea was using typid as hint identification of deleted
variables. The possibility that this id will not be consistent for the
current catalogue was expected. And it
is a reason why the result type is just Oid and not regtype. Without it,
pg_session_variables shows just empty rows (except oid) for dropped not yet
purged variables.

> Even worse, the OID could have been reused for a different type since.

I agree so usage `select typid::regtype, ... from pg_session_variables(.. `

can be dangerous, but it is the reason why we have there typname column.

Showing typid has some information value, but I don't think it is
absolutely necessary. I see some possible changes:

1. no change
2. remove typid column
3. show typid only when variable is valid, and using regtype as output
type, remove typname

What do you prefer?

I'll send the attachment with merging changes for patch 4

Regards

Pavel

> but I don't think any of the other patches should be affected.

The comments from your patch are merged

---

From: Laurenz Albe <[email-masked]>
Date: Fri, 25 Oct 2024 10:41:40 +0300

Thanks.

> I prefer the original way - minimally it is a common pattern. I didn't find any usage of `= {} ` in code

That's alright by me.

> The original idea was using typid as hint identification of deleted variables. The possibility
> that this id will not be consistent for the current catalogue was expected. And it
> is a reason why the result type is just Oid and not regtype. Without it, pg_session_variables
> shows just empty rows (except oid) for dropped not yet purged variables.

I see your point.  It is for testing and debugging only.

> 
> owing typid has some information value, but I don't think it is absolutely necessary. I see some possible changes:
> 
> 1. no change
> 2. remove typid column
> 3. show typid only when variable is valid, and using regtype as output type, remove typname
> 
> What do you prefer?

I'd say leave it as it is.  I agree that it is not dangerous, and if it is intentional that
non-existing type IDs might be displayed, I have no problem with it.

Yours,
Laurenz Albe

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 25 Oct 2024 22:38:01 +0200

Hi

čt 24. 10. 2024 v 10:29 odesílatel Laurenz Albe <[email-masked]>
napsal:

> /home/laurenz/postgresql/src/test/regress/expected/session_variables.out
> 2024-10-24 11:14:06.717663613 +0300
>   +++
> /home/laurenz/postgresql/src/test/regress/results/session_variables.out
> 2024-10-24 11:15:37.999286228 +0300
>   @@ -30,6 +30,7 @@

> I suggest that patch 0001 should drop role "regress_variable_reader" again.

I did it,

fixed

merged your changes of comments

> Laurenz Albe
From 1dba9814937770d99ec268c999f6c25111ca5ba5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From 6e1312b604aa8b9f472ef4be152f4d3f8e05cc19 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 2fb2e73604e..5ae25d07ad7 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2991,6 +2991,9 @@ _SPI_error_callback(void *arg)
 			case RAW_PARSE_PLPGSQL_ASSIGN3:
 				errcontext("PL/pgSQL assignment \"%s\"", query);
 				break;
+			case RAW_PARSE_PLPGSQL_LET:
+				errcontext("LET statement \"%s\"", query);
+				break;
 			default:
 				errcontext("SQL statement \"%s\"", query);
 				break;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 496d75a494a..2edc51cfee9 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2014,6 +2014,18 @@ transformLetStmt(ParseState *pstate, LetStmt *stmt)

+	/*
index a58f1abb5a7..0f5087bf8ea 100644
@@ -817,6 +817,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 /* Precedence: lowest to highest */
@@ -948,6 +949,13 @@ parse_toplevel:
 				pg_yyget_extra(yyscanner)->parsetree =
 					list_make1(makeRawStmt((Node *) n, @2));
 			}
+			| MODE_PLPGSQL_LET LetStmt
+			{
+				LetStmt *n = (LetStmt *) $2;
+				n->plpgsql_mode = true;
+				pg_yyget_extra(yyscanner)->parsetree =
+					list_make1(makeRawStmt((Node *) n, 0));
+			}
 		;
 
 /*
 
index c807fd19825..7de1a194e19 100644
@@ -2114,6 +2114,8 @@ typedef struct LetStmt

index e69559b980e..560f1ec5bdb 100644
 
 static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
 								 PLpgSQL_function *func,
@@ -2121,6 +2124,10 @@ exec_stmts(PLpgSQL_execstate *estate, List *stmts)
 				rc = exec_stmt_rollback(estate, (PLpgSQL_stmt_rollback *) stmt);
 				break;
 
+			case PLPGSQL_STMT_LET:
+				rc = exec_stmt_let(estate, (PLpgSQL_stmt_let *) stmt);
+				break;
+
 			default:
 				/* point err_stmt to parent, since this one seems corrupt */
 				estate->err_stmt = save_estmt;
@@ -5004,6 +5011,54 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt)
 	return PLPGSQL_RC_OK;
 }
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index cba711682f4..c8c252825c2 100644

From 06a173b9adeeb2637eb2acc407563a326b374e66 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

index 8cee732561d..f79937980ee 100644
@@ -4744,8 +4744,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 
@@ -4949,21 +4948,39 @@ substitute_actual_parameters_mutator(Node *node,

 
index b5e38206071..dde2baa8fd2 100644
@@ -105,7 +105,6 @@ SELECT var1;
@@ -212,10 +211,10 @@ SELECT sqlfx1(sqlfx2('Hello'));
 
 -- inlining is blocked

From 07658cbfef78ec801f66cea83d88b5e7d26df60a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)
 
index 3cebf4786b1..c81a66061d9 100644
 
+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*
index 0f5087bf8ea..fc6a36d54fd 100644
@@ -668,7 +668,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -773,7 +773,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER

@@ -5221,31 +5221,33 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5273,6 +5275,12 @@ OptImmutable: IMMUTABLE								{ $$ = true; }
 
+OptTransactional:
@@ -18035,6 +18043,7 @@ unreserved_keyword:
@@ -18683,6 +18692,7 @@ bare_label_keyword:

 

 
 
 /*
 
index c330d1081ab..ae0b517aff6 100644
@@ -5163,7 +5163,7 @@ listVariables(const char *pattern, bool verbose)
 
@@ -5186,6 +5186,7 @@ listVariables(const char *pattern, bool verbose)
@@ -5198,6 +5199,7 @@ listVariables(const char *pattern, bool verbose)
 
index 6e43e95eaca..001cad319be 100644

index 7de1a194e19..1668aad5263 100644
@@ -3481,6 +3481,7 @@ typedef struct CreateSessionVarStmt

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 
 
index 80248ef1004..e82d5b5a61e 100644
 
 DROP VARIABLE svartest.var1;
@@ -1764,3 +1764,103 @@ SELECT var1;
 LET var1 = 30;
 ERROR:  session variable "public.var1" is declared IMMUTABLE
 DROP VARIABLE var1;
+-- test transactional variables
+CREATE TRANSACTION VARIABLE tv AS int DEFAULT 0;
+BEGIN;
+  LET tv = 100;
+  SELECT tv;
+ tv  
+-----
+ 100
+(1 row)
+
+ROLLBACK;
+SELECT tv;
+ tv 
+----
+  0
+(1 row)
+
+LET tv = 100;
+BEGIN;
+  LET tv = 1000;
+COMMIT;
+SELECT tv;
+  tv  
+------
+ 1000
+(1 row)
+
+BEGIN;
+  LET tv = 0;
+  SELECT tv;
+ tv 
+----
+  0
+(1 row)
+
+ROLLBACK;
+SELECT tv;
+  tv  
+------
+ 1000
+(1 row)
+
+-- test subtransactions
+BEGIN;
+  LET tv = 1;
+SAVEPOINT x1;
+  LET tv = 2;
+SAVEPOINT x2;
+  LET tv = 3;
+ROLLBACK TO x2;
+  SELECT tv;
+ tv 
+----
+  2
+(1 row)
+
+  LET tv = 10;
+ROLLBACK TO x1;
+  SELECT tv;
+ tv 
+----
+  1
+(1 row)
+
+ROLLBACK;
+SELECT tv;
+  tv  
+------
+ 1000
+(1 row)
+
+BEGIN;
+  LET tv = 1;
+SAVEPOINT x1;
+  LET tv = 2;
+SAVEPOINT x2;
+  LET tv = 3;
+ROLLBACK TO x2;
+  SELECT tv;
+ tv 
+----
+  2
+(1 row)
+
+  LET tv = 10;
+ROLLBACK TO x1;
+  SELECT tv;
+ tv 
+----
+  1
+(1 row)
+
+COMMIT;
+SELECT tv;
+ tv 
+----
+  1
+(1 row)
+
+DROP VARIABLE tv;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index c5d6c850bdc..3c037635ddf 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -1209,3 +1209,61 @@ SELECT var1;
 LET var1 = 30;
 
 DROP VARIABLE var1;
+

From 8d10faf73f4955b90331c230785fc245b7cb253f Mon Sep 17 00:00:00 2001

index bf30da04b5e..98bb39fb09b 100644
 
+/*

 

 

 select a from atacc1;

index 64ea33aeae8..6874f5ae0d1 100644
@@ -160,7 +160,7 @@ LINE 1: COPY x TO stdout WHERE a = 1;
index 9b2973694ff..d9f16aeb978 100644
@@ -6310,13 +6310,13 @@ LINE 1: select t2.uunique1 from
@@ -7413,7 +7413,7 @@ lateral (select * from int8_tbl t1,
 
@@ -7425,7 +7425,7 @@ LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
@@ -7462,7 +7462,7 @@ LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
@@ -7482,7 +7482,7 @@ update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1)
index dde2baa8fd2..80248ef1004 100644
@@ -310,7 +310,7 @@ ERROR:  session variable "var1" doesn't exist
@@ -522,7 +522,7 @@ SELECT v1;
 
@@ -1071,7 +1071,7 @@ BEGIN;
index c73631a9a1d..5c0eacc5f15 100644

From e1882349f999a8aeba838d5dc61af5bea798378d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

 
index a5452c0c9e4..8cee732561d 100644
 
 	/*

index 40e3ea8a5dc..b5e38206071 100644
@@ -644,12 +644,14 @@ SELECT count(*) FROM svar_test WHERE a%10 = zero;

From 3163eea6d012d5f050ad0d45d257748faa27b281 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
index 7ea8359cf1b..124083b46b3 100644
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{
 
index 9c8662f322d..b1d0eea7d49 100644
 
+/*
 
+	if (!is_write)

 
 	if (!found)
 	{
 
index 412700a3897..a26bfb1d703 100644
@@ -638,6 +638,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	json_format_clause
 				json_format_clause_opt
@@ -5211,30 +5212,36 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:

 			if (isAgg)
index 90cacf9e3f2..bf30da04b5e 100644

 

 
 
+	if (vardefexpr)

index c0fbdc43158..ce9a0547c28 100644
@@ -5163,7 +5163,7 @@ listVariables(const char *pattern, bool verbose)
 
@@ -5184,6 +5184,7 @@ listVariables(const char *pattern, bool verbose)
@@ -5193,6 +5194,7 @@ listVariables(const char *pattern, bool verbose)
 
 	if (verbose)

index 9a8b04e0361..be0a5d2cbbc 100644
@@ -3477,6 +3477,7 @@ typedef struct CreateSessionVarStmt
 
index c11fdb3d970..fa566b194d1 100644

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 
 
index bfbd241f525..4cf4e07f4af 100644
 
 DROP VARIABLE svartest.var1;
@@ -1555,3 +1555,66 @@ SELECT var1 IS NULL;
 (1 row)
 
 DROP VARIABLE var1;
index a44989ef0b5..4c5bbecdbe2 100644
@@ -1039,3 +1039,51 @@ ROLLBACK;
 SELECT var1 IS NULL;
 
 DROP VARIABLE var1;
+

From 7af6365991db4b6f839b4256336ec3b788c50ba8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)
 
index b1d0eea7d49..3cebf4786b1 100644
 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a26bfb1d703..a58f1abb5a7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -668,6 +668,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt

 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -5212,27 +5213,31 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5252,6 +5257,14 @@ XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
 		;

 

 

 
+	if (varnotnull)
 
 /*
 
index ce9a0547c28..c330d1081ab 100644
@@ -5163,7 +5163,7 @@ listVariables(const char *pattern, bool verbose)
 
@@ -5184,6 +5184,8 @@ listVariables(const char *pattern, bool verbose)
@@ -5194,6 +5196,8 @@ listVariables(const char *pattern, bool verbose)
 
index bdd52431661..6e43e95eaca 100644

+	/* don't allow NULL */
 
index be0a5d2cbbc..c807fd19825 100644
@@ -3477,6 +3477,8 @@ typedef struct CreateSessionVarStmt

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 
 
index 4cf4e07f4af..407b90a0a16 100644
 
 DROP VARIABLE svartest.var1;
@@ -1618,3 +1618,148 @@ SELECT var1;
 
 DROP VARIABLE var1;
index 4c5bbecdbe2..3c2ef190fd9 100644
@@ -1087,3 +1087,125 @@ SELECT var1;
 
 DROP VARIABLE var1;

From 01276e63fa4c5aa5c1e46ccaed7beeff52b18437 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

index db747a9da81..9c8662f322d 100644
 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index fab3d339378..412700a3897 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5240,6 +5240,7 @@ CreateSessionVarStmt:
  * transaction end like tables.
  */
 XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
+			| ON TRANSACTION END_P RESET			{ $$ = VARIABLE_XACTEND_RESET; }
 			| /*EMPTY*/								{ $$ = VARIABLE_XACTEND_NOOP; }
 		;

 

 

 
 
index ed446aa91d3..c0fbdc43158 100644
@@ -5186,6 +5186,7 @@ listVariables(const char *pattern, bool verbose)
 
 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4
index 963ffaa3b1e..bfbd241f525 100644
@@ -1521,3 +1521,37 @@ SELECT count(*) FROM pg_session_variables();
      0
 (1 row)
 
+CREATE VARIABLE var1 AS int ON TRANSACTION END RESET;
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+COMMIT;
+-- should be NULL;
+SELECT var1 IS NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+ROLLBACK;
+-- should be NULL
+SELECT var1 IS NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP VARIABLE var1;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index bc1f0930d38..a44989ef0b5 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -1019,3 +1019,23 @@ COMMIT;
 SELECT count(*) FROM pg_variable WHERE varname = 'var1';
 -- should be zero
 SELECT count(*) FROM pg_session_variables();
+
+CREATE VARIABLE var1 AS int ON TRANSACTION END RESET;
+
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+COMMIT;
+
+-- should be NULL;
+SELECT var1 IS NULL;
+
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+ROLLBACK;
+
+-- should be NULL
+SELECT var1 IS NULL;
+
+DROP VARIABLE var1;
-- 
2.47.0

From af10bea162e010961cd4452905288e2150ecf0bd Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*
 
index 11891d97c16..f977536846a 100644

index 6a7f18f6ded..f9a13b63eab 100644
 
index 0b3b5748ea2..1ccb9c5f4a0 100644
 
index 9dfd435128a..496d75a494a 100644
@@ -3470,14 +3470,6 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 
-	/*
 
index cd97dfa0624..72b54b3a33f 100644

 
@@ -831,6 +839,8 @@ extern void ExecEvalParamSet(ExprState *state, ExprEvalStep *op,
index cdc257c609b..e69559b980e 100644
@@ -8104,8 +8104,7 @@ exec_is_simple_query(PLpgSQL_expr *expr)
 
 	/*
index 407b90a0a16..40e3ea8a5dc 100644
@@ -108,8 +108,7 @@ ERROR:  permission denied for session variable var1
-CONTEXT:  PL/pgSQL expression "$1 + var1"
@@ -279,27 +278,28 @@ $$;
index 3c2ef190fd9..c5d6c850bdc 100644

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.47.0

From c34fe4dba58191eac7867092380017e6f9e43347 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
index c4f39bf795d..7ea8359cf1b 100644
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index ce88470c5af..311d2934186 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2309,7 +2309,7 @@ CommitTransaction(void)
 	smgrDoPendingSyncs(true, is_parallel_worker);

@@ -2912,6 +2912,7 @@ AbortTransaction(void)
@@ -5190,6 +5191,8 @@ CommitSubTransaction(void)
@@ -5355,6 +5358,8 @@ AbortSubTransaction(void)

 /*

 

 

 
 
 	ReleaseSysCache(tup);
 
index eab91e1460c..db747a9da81 100644
 
 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
index 81b8b0a633d..9dfd435128a 100644
@@ -3390,10 +3390,10 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 
 		/*
 		 * A materialized view would either need to save parameters for use in
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9bb8346939f..fab3d339378 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -466,6 +466,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	 OptTemp
 %type <ival>	 OptNoLog
 %type <oncommit> OnCommitOption
+%type <ival>	 XactEndActionOption
 
 %type <ival>	for_locking_strength
 %type <node>	for_locking_item
@@ -5210,26 +5211,39 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index dc29f210925..ed446aa91d3 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5163,7 +5163,7 @@ listVariables(const char *pattern, bool verbose)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, false, false, false, false};
 
@@ -5183,12 +5183,16 @@ listVariables(const char *pattern, bool verbose)
 
 	if (verbose)
 	{
index d6b7f47e35a..bdd52431661 100644

 

index e63079d9345..9a8b04e0361 100644
@@ -3477,6 +3477,7 @@ typedef struct CreateSessionVarStmt

index 4b2424d6d34..6174a5562c5 100644

 
 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 
 
index 8a32baab784..963ffaa3b1e 100644
 
 DROP VARIABLE svartest.var1;
@@ -1401,3 +1401,123 @@ SELECT var1;
 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables
index b6322018ce9..bc1f0930d38 100644
@@ -827,6 +827,7 @@ BEGIN;
   DROP VARIABLE var1;
   SELECT var2;
 ROLLBACK;
+
 -- should be ok
 SELECT var1;
 
@@ -953,3 +954,68 @@ SELECT var1;
 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index ee518a43df6..cba711682f4 100644
@@ -2795,6 +2795,7 @@ SupportRequestWFuncMonotonic
 SVariable
 SVariableData
 SVariableState
+SVariableXActDropItem
 Syn
 SyncOps
 SyncRepConfigData
-- 
2.47.0

From 4df290bfd475882eaac350115d10c0cc26a64a82 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

index 200757dc7a9..9bb8346939f 100644
@@ -12134,7 +12134,8 @@ PreparableStmt:

index deb9563a598..d6b7f47e35a 100644
 
-/* LET */
index 9f4800f0b06..8a32baab784 100644
@@ -439,9 +439,9 @@ DROP VARIABLE var1, var2;
@@ -1322,5 +1322,82 @@ SELECT var1;
    10
 (1 row)
 
index aca2a06280f..b6322018ce9 100644
@@ -894,5 +894,62 @@ EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE, SUMMARY OFF) LET var1 = (SELECT count(*
 -- should be 10
 SELECT var1;

From 62c3fbb5a86aa8471debac35a6073e4308c86ec5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

index 18a5af6b919..ce3e41baf95 100644

 
index 112c3343f09..200757dc7a9 100644
@@ -12103,7 +12103,8 @@ ExplainableStmt:

index 73d3f58b6e8..9f4800f0b06 100644
@@ -1284,3 +1284,43 @@ SET session_variables_ambiguity_warning TO off;
index 886c2f93e5e..aca2a06280f 100644
@@ -875,3 +875,24 @@ SET session_variables_ambiguity_warning TO off;
 
 DROP TABLE public.xxtab;

From d4f08d9901116753d77efbbc236ac699926f5bb8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index dc401087dc6..88c6a9f5b9b 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10733,6 +10733,66 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>
 
index ed4a005c8dc..c4f39bf795d 100644
 
  <sect1 id="ddl-others">
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 1c3bfb12393..90cacf9e3f2 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -47,6 +47,7 @@
 
 /* GUC parameters */

index 8a67f01200c..efb65b02380 100644
index 39a3ac23127..992dda3c644 100644
@@ -714,6 +714,7 @@
 
 /* GUC parameters */

 
 
 -- the value should not be corrupted
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index ed3b3b642ac..73d3f58b6e8 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -1262,3 +1262,25 @@ SELECT var1;
 (1 row)
 
index 529864d4e16..886c2f93e5e 100644
@@ -855,3 +855,23 @@ BEGIN; DROP VARIABLE var1; ROLLBACK;
 SELECT var1;

From 7c37df772f6c0fb83abab33c8d68988b3f20c74f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From afac62d87687c809d1a8bc9d354ae706f911e97c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 .../regress/expected/session_variables.out    | 218 ++++++++++++++++++
 9 files changed, 680 insertions(+), 2 deletions(-)

index d8f6c658420..ce88470c5af 100644
@@ -2307,6 +2308,9 @@ CommitTransaction(void)

 
index 668b8189a6c..eab91e1460c 100644

+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

@@ -589,6 +755,8 @@ pg_session_variables(PG_FUNCTION_ARGS)
 {
 #define NUM_PG_SESSION_VARIABLES_ATTS 8

 	if (sessionvars)
 
index 2c4760fb687..ed3b3b642ac 100644
@@ -1044,3 +1044,221 @@ SELECT count(*) FROM pg_session_variables();
      0
 (1 row)
 
index 979b9029e58..529864d4e16 100644
@@ -734,3 +734,124 @@ DISCARD VARIABLES;
 
 -- should be zero again

From 3db9ab430ccb9b5f601b4c2f41de93aa8ca3c7c3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
 
index 13ab9575fa9..668b8189a6c 100644

-	Assert(sessionvars);
+	/*
 
 	/*
 	 * If the hashvalue is not specified, we have to recheck all currently
@@ -661,3 +667,23 @@ pg_session_variables(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
+
+/*
index 90227454155..112c3343f09 100644
@@ -2077,7 +2077,13 @@ DiscardStmt:

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+extern void ResetSessionVariables(void);
+
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 85eb29bebc1..e63079d9345 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3987,6 +3987,7 @@ typedef enum DiscardMode
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
 	DISCARD_TEMP,
+	DISCARD_VARIABLES,
 } DiscardMode;
 
index a2cb35e3d7d..2c4760fb687 100644
@@ -981,3 +981,66 @@ DROP VARIABLE :"DBNAME".:"DBNAME".b;
index 6445479d22d..979b9029e58 100644
@@ -698,3 +698,39 @@ DROP VARIABLE :"DBNAME".:"DBNAME".:"DBNAME";
 DROP SCHEMA :"DBNAME";
 
 RESET search_path;
+

From 22e0f18ab498c87317d0be51e2e42af4f8598426 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

This is a function designed for testing and debugging.  It returns the
content of sessionvars as-is, and can therefore display entries about
session variables that were dropped but for which this backend didn't
process the shared invalidations yet.
---
 src/backend/commands/session_variable.c | 92 +++++++++++++++++++++++++
 src/include/catalog/pg_proc.dat         |  8 +++
 2 files changed, 100 insertions(+)

diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 548d9835c0d..13ab9575fa9 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -569,3 +569,95 @@ ExecuteLetStmt(ParseState *pstate,
 
 	PopActiveSnapshot();
 }
+
+/*
index 48b95986729..09480ee5146 100644
@@ -12403,4 +12403,12 @@

From a9cba74694f2e2e0ae4bbe42e2cc6dbd5f35f343 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 .../regress/expected/session_variables.out    | 925 ++++++++++++++++++
 src/test/regress/sql/session_variables.sql    | 639 ++++++++++++
 59 files changed, 3940 insertions(+), 23 deletions(-)

+     <row>
index 10bf71f840a..ed4a005c8dc 100644
 
+   <para>

 

 
+/*

 
+	/*

index 45954d979f6..11891d97c16 100644
 
 						/*
 
+	/*
index f76072228c9..9c13c0015b1 100644
index 0f423e96847..600dfbf7d43 100644
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

index 8e39795e245..a5452c0c9e4 100644

 				/*
@@ -4718,7 +4750,8 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 
index 506e0631615..81b8b0a633d 100644

@@ -414,6 +420,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
@@ -493,6 +500,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
 									   (CallStmt *) parseTree);
 			break;
 
+		case T_LetStmt:
 
 			/*
@@ -545,6 +557,7 @@ stmt_requires_parse_analysis(RawStmt *parseTree)
 		case T_SelectStmt:
 		case T_ReturnStmt:
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			result = true;
 			break;
 
@@ -653,6 +666,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)

@@ -1079,6 +1093,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)

 
@@ -1544,6 +1559,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
 	foreach(l, stmt->lockingClause)
 	{
@@ -1770,12 +1786,242 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
@@ -2021,6 +2267,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 
@@ -2496,6 +2743,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)

@@ -2563,6 +2811,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)

 
@@ -2750,9 +2999,15 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 	/*
@@ -2794,6 +3049,10 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 
+	/*
 
 	if (indirection)
@@ -2936,6 +3195,8 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 							   (LockingClause *) lfirst(l), false);
 	}

 	/* this must be done after collations, for reliable comparison of exprs */
@@ -3209,6 +3470,14 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 							 true,
 							 stmt->funccall->location);
 
+	/*
 
index 21768cc1356..90227454155 100644
@@ -293,7 +293,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -443,6 +443,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
@@ -734,7 +735,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEEP KEY KEYS

 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
@@ -1081,6 +1082,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -12743,6 +12745,47 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -17791,6 +17834,7 @@ unreserved_keyword:
@@ -18402,6 +18446,7 @@ bare_label_keyword:

 			/*
index ef0b560f5e3..1c3bfb12393 100644
 
 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
index ebb04cdeaea..172a37ed760 100644
 
+/* LET */

 
+	/*

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index 2b819fc1cc6..85eb29bebc1 100644

@@ -2100,6 +2105,18 @@ typedef struct MergeStmt
 	ParseLoc	stmt_len;		/* length in bytes; 0 means "rest of string" */
 } MergeStmt;
 
index add0f9e45fc..ea1f76d0fbb 100644

 
 	/*
index 52f29bcdb69..4549366cf59 100644
 
 	NodeTag		type;

 

index b0ef1952e8f..4b2424d6d34 100644
 
 typedef struct Param
 
index 2375e95c107..c11fdb3d970 100644

@@ -244,6 +246,7 @@ struct ParseState
 	bool		p_hasTargetSRFs;
 	bool		p_hasSubLinks;
 	bool		p_hasModifyingCTE;
+	bool		p_hasSessionVariables;

 
 /* ----------------
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index e69559b980e..cdc257c609b 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8104,7 +8104,8 @@ exec_is_simple_query(PLpgSQL_expr *expr)
 		query->sortClause ||
 		query->limitOffset ||
 		query->limitCount ||
-		query->setOperations)
+		query->setOperations ||
+		query->hasSessionVariables)
 		return false;
 
 	/*
index d0aff56a01d..a2cb35e3d7d 100644
@@ -56,3 +56,928 @@ DROP VARIABLE svartest.var1;
 DROP ROLE regress_variable_reader;
+DROP TYPE sv_xyz;
+DROP VARIABLE v_ab.a;
+DROP SCHEMA v_ab;
index 82679cbf48e..6445479d22d 100644
@@ -59,3 +59,642 @@ DROP VARIABLE svartest.var1;
 DROP ROLE regress_variable_reader;
+DROP TYPE sv_xyz;
+DROP VARIABLE v_ab.a;
+DROP SCHEMA v_ab;
index a29fc61864e..ee518a43df6 100644
@@ -2600,6 +2601,7 @@ SerializedTransactionState
@@ -2790,6 +2792,9 @@ SupportRequestRows

From 336868c03bf43dacf2f261f922106a99f5dc1c4e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 .../regress/expected/session_variables.out    |  58 ++++
 66 files changed, 2388 insertions(+), 26 deletions(-)

index f6344b3b79a..10bf71f840a 100644

index 7be0324ac87..a6bb2b2760b 100644

 
 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*
index e14bc0c0548..42f21a543dc 100644
@@ -6738,6 +6739,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -6801,6 +6803,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
index baca4059d2e..21768cc1356 100644
@@ -281,8 +282,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -775,8 +776,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 
@@ -1042,6 +1043,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1584,6 +1586,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;

@@ -5191,6 +5194,34 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6977,6 +7008,7 @@ object_type_any_name:
 
 /*
@@ -7853,6 +7885,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7898,6 +7938,14 @@ privilege_target:

@@ -8095,6 +8143,7 @@ defacl_privilege_target:

@@ -9877,6 +9926,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10238,6 +10305,24 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10519,6 +10604,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17868,6 +17961,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18521,6 +18616,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P

 	/*

 	return result;
 }

index a85dc0d891f..b464a7f090a 100644
 
 	return subname;
 }
+

 
 
 	int			useDB;
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 
 
index 328d78c73f9..454ee93d008 100644
index 363a66e7185..dc29f210925 100644
@@ -5152,6 +5152,102 @@ error_return:
 	return false;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 1be0056af73..ebb04cdeaea 100644

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 
 
index 1ec0d6f6b5f..48b95986729 100644
 
index b40b661ec8a..2b819fc1cc6 100644
@@ -2316,6 +2316,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW,
 } ObjectType;
 
@@ -3447,6 +3448,21 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+

 
+\dV "no.such.variable"
+            List of variables

index 00000000000..d0aff56a01d
@@ -0,0 +1,58 @@
+DROP ROLE regress_variable_reader;
index 81e4222d26a..05eb784f62b 100644
 
 # ----------

 

 
 -- check \drg and \du
index 00000000000..82679cbf48e
+DROP ROLE regress_variable_reader;
index 110089695e1..a29fc61864e 100644
@@ -3076,6 +3079,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.47.0

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 28 Oct 2024 08:01:26 +0100

Hi

fresh rebase

Regards

Pavel
From 66aac2a21a00560dfdd5ab464088b4d8fb9cf60f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From b5314999eb80d158e0750315f292d3784b92b2fd Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*
index d9bf61a59db..3126b5eae9f 100644
@@ -664,7 +664,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -769,7 +769,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER

@@ -5217,31 +5217,33 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5269,6 +5271,12 @@ OptImmutable: IMMUTABLE								{ $$ = true; }
 
+OptTransactional:
+			TRANSACTION								{ $$ = true; }
+			| TRANSACTIONAL							{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
@@ -18031,6 +18039,7 @@ unreserved_keyword:
 			| TEXT_P
 			| TIES
 			| TRANSACTION
+			| TRANSACTIONAL
 			| TRANSFORM
 			| TRIGGER
 			| TRUNCATE
@@ -18679,6 +18688,7 @@ bare_label_keyword:
 			| TIMESTAMP
 			| TRAILING
 			| TRANSACTION
+			| TRANSACTIONAL
 			| TRANSFORM
 			| TREAT
 			| TRIGGER
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dda33fe4ef..024c167b2a3 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5428,6 +5428,7 @@ getVariables(Archive *fout)
 	int			i_varcollation;
 	int			i_varnotnull;
 	int			i_varisimmutable;
+	int			i_varistransact;
 	int			i_varacl;
 	int			i_acldefault;
 	int			i,
@@ -5452,6 +5453,7 @@ getVariables(Archive *fout)
 					  "       END AS varcollation,\n"
 					  "       v.varnotnull,\n"
 					  "       v.varisimmutable,\n"
+					  "       v.varistransact,\n"
 					  "       pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n"
 					  "       v.varowner, v.varacl,\n"
 					  "       acldefault('V', v.varowner) AS acldefault\n"
@@ -5474,6 +5476,7 @@ getVariables(Archive *fout)
 	i_varcollation = PQfnumber(res, "varcollation");
 	i_varnotnull = PQfnumber(res, "varnotnull");
 	i_varisimmutable = PQfnumber(res, "varisimmutable");
+	i_varistransact = PQfnumber(res, "varistransact");
 
@@ -5502,6 +5505,7 @@ getVariables(Archive *fout)
 
@@ -5549,6 +5553,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5566,12 +5571,13 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 
 
 /*
index 4d48e68402d..764540f35c8 100644

 

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+

From ae0e2ed4be0b4b2b60d12f46d224124ca221289d Mon Sep 17 00:00:00 2001

index bcbb1f564af..16bf52c9854 100644
@@ -520,7 +520,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
+/*

 

 

 select a from atacc1;

 

From 8a5cf51edbf878119924bff5e6155a40b5742cd2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*
index 4ea09c9b282..d9bf61a59db 100644
@@ -813,6 +813,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -944,6 +945,13 @@ parse_toplevel:
 
 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
+typedef struct PLpgSQL_stmt_let
+{
+	PLpgSQL_stmt_type cmd_type;
+	int			lineno;
+	unsigned int stmtid;
+	PLpgSQL_expr *expr;
+} PLpgSQL_stmt_let;
+
 /*
  * PERFORM statement
  */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6d89d48f6c6..f0f96e67383 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1890,6 +1890,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
-- 
2.47.0

From c759ea9debef7ecc8a212ae98be285cd05f04687 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From d08e93c4f3516855488c3ab591ab315b744c0543 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

 
 
 	/*

 

From eb7b62b2db6e9704e8ad75cecebfcf668757678c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

index 3735b5554e0..e8c375df198 100644
 
-	/*

 

 
-	/*

 

 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.47.0

From 7585bd0603f7f4cbadb5edf6e604056771ca345b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e53ea6e169a..4ea09c9b282 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -664,6 +664,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt

 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -5208,27 +5209,31 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5248,6 +5253,14 @@ XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
 		;

index 22d53f4bdde..5dda33fe4ef 100644
@@ -5426,6 +5426,8 @@ getVariables(Archive *fout)
@@ -5448,6 +5450,8 @@ getVariables(Archive *fout)
@@ -5468,6 +5472,8 @@ getVariables(Archive *fout)
 
 	i_varowner = PQfnumber(res, "varowner");
 	i_varacl = PQfnumber(res, "varacl");
@@ -5494,6 +5500,8 @@ getVariables(Archive *fout)
 			pg_strdup(PQgetvalue(res, i, i_varxactendaction));

@@ -5540,7 +5548,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5554,12 +5564,14 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 
 	if (OidIsValid(varcollation))
 	{
@@ -5571,6 +5583,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (varnotnull)
 
 /*
index b0904316159..4d48e68402d 100644

 

+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From 100e6732e9760853d445b086c3c284dd97cbe69f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{
 
index f265afd8e9e..e53ea6e169a 100644
@@ -634,6 +634,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	json_format_clause
 				json_format_clause_opt
@@ -5207,30 +5208,36 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:

 			if (isAgg)
index 3602c3572d7..bcbb1f564af 100644
@@ -582,6 +582,7 @@ expr_kind_allows_session_variables(ParseExprKind p_expr_kind)
@@ -667,6 +668,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
@@ -2075,6 +2077,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;
@@ -3427,6 +3430,7 @@ ParseExprKindName(ParseExprKind exprKind)
index 4d620a65613..22d53f4bdde 100644
@@ -5422,6 +5422,7 @@ getVariables(Archive *fout)
@@ -5463,6 +5465,7 @@ getVariables(Archive *fout)
 
@@ -5498,6 +5501,11 @@ getVariables(Archive *fout)
 		varinfo[i].dacl.initprivs = NULL;
 		varinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_varowner));
 
@@ -5530,6 +5538,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5542,6 +5551,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

@@ -5561,6 +5571,10 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (vardefexpr)
 
index cfae33748ac..b0904316159 100644

 
 	if (verbose)

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From 3314214ad3f3e1e74a435c0a27465001d6ee12bb Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 732d9cbe460..f265afd8e9e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5236,6 +5236,7 @@ CreateSessionVarStmt:
  * transaction end like tables.
  */
 XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
+			| ON TRANSACTION END_P RESET			{ $$ = VARIABLE_XACTEND_RESET; }
 			| /*EMPTY*/								{ $$ = VARIABLE_XACTEND_NOOP; }
 		;
 
index 64cb03db02e..4d620a65613 100644
@@ -5422,6 +5422,7 @@ getVariables(Archive *fout)
@@ -5439,6 +5440,7 @@ getVariables(Archive *fout)
 
 	i_varowner = PQfnumber(res, "varowner");
@@ -5484,6 +5487,9 @@ getVariables(Archive *fout)

 		varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl));
@@ -5524,6 +5530,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	PQExpBuffer query;
 	char	   *qualvarname;
 	const char *vartypname;
+	const char *varxactendaction;
 	Oid			varcollation;
 
 	/* skip if not to be dumped */
@@ -5535,6 +5542,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
@@ -5553,6 +5561,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}

index 0a2295f0521..cfae33748ac 100644

 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From e6e056d202d20d0723366e8027f09af305faaf44 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index ba4966288ac..8e2bd66d247 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2319,7 +2319,7 @@ CommitTransaction(void)
 	smgrDoPendingSyncs(true, is_parallel_worker);

@@ -2922,6 +2922,7 @@ AbortTransaction(void)
@@ -5200,6 +5201,8 @@ CommitSubTransaction(void)
@@ -5365,6 +5368,8 @@ AbortSubTransaction(void)

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
 
 		/*
 		 * A materialized view would either need to save parameters for use in
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 13aa3fd84c4..732d9cbe460 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -462,6 +462,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	 OptTemp
 %type <ival>	 OptNoLog
 %type <oncommit> OnCommitOption
+%type <ival>	 XactEndActionOption
 
 %type <ival>	for_locking_strength
 %type <node>	for_locking_item
@@ -5206,26 +5207,39 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index 21e12b09a28..6d89d48f6c6 100644
@@ -2796,6 +2796,7 @@ SupportRequestWFuncMonotonic
 SVariable
 SVariableData
 SVariableState
+SVariableXActDropItem
 Syn
 SyncOps
 SyncRepConfigData
-- 
2.47.0

From 589b5dd8b00a9653849d45121fd4a55ecbf1ac10 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

index 9e234f4cb6a..13aa3fd84c4 100644
@@ -12130,7 +12130,8 @@ PreparableStmt:

 
-/* LET */

From d97f0deb07f2cabab958c8dd98bdef29260dec9f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

index 7c0fd63b2f0..d41516454f5 100644
@@ -512,8 +513,9 @@ standard_ExplainOneQuery(Query *query, int cursorOptions,
 	}

@@ -611,6 +613,25 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
 		else
+						pstate, params);
@@ -634,8 +655,8 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
@@ -684,6 +705,8 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
index 455a03cce63..3735b5554e0 100644
@@ -662,8 +662,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 
 		if (pstmt->commandType != CMD_UTILITY)
-			ExplainOnePlan(pstmt, into, es, query_string, paramLI, pstate->p_queryEnv,
-						   &planduration, (es->buffers ? &bufusage : NULL),
+			ExplainOnePlan(pstmt, into, InvalidOid, es, query_string, paramLI,
+						   pstate->p_queryEnv, &planduration,
+						   (es->buffers ? &bufusage : NULL),
 						   es->memory ? &mem_counters : NULL);
 		else
 			ExplainOneUtility(pstmt->utilityStmt, into, es, pstate, paramLI);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31cb91cf2b0..9e234f4cb6a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -12099,7 +12099,8 @@ ExplainableStmt:
 			| CreateAsStmt
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
-			| ExecuteStmt					/* by default all are $$=$1 */
+			| ExecuteStmt
+			| LetStmt						/* by default all are $$=$1 */
 		;
 
 /*****************************************************************************
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index aa5872bc154..f5b08857ae8 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -103,7 +103,8 @@ extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
 							  ExplainState *es, ParseState *pstate,
 							  ParamListInfo params);

 DROP TABLE public.xxtab;

From 96fa76700fae6d2a5923de590db6ff8d82427bf4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

 
 
  <sect1 id="ddl-others">
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index aba5259e027..3602c3572d7 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -45,6 +45,7 @@
 
 /* GUC parameters */

@@ -960,11 +961,51 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
 /* GUC parameters */

 

 

From 4e08235661c949935ecb9c3f9791cd97fbbb5a77 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From 58fe756996a1146ee195ea404350a76d1eb67b03 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

index 004f7e10e55..ba4966288ac 100644
@@ -2317,6 +2318,9 @@ CommitTransaction(void)

 

+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 
 
 	if (sessionvars)

 
 -- should be zero again

From 4140b1d21adf8cb7448735e0a5e57129fbce33b3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*
index 9bf68f42b0e..31cb91cf2b0 100644
@@ -2073,7 +2073,13 @@ DiscardStmt:

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+

From e8b36a8c0e2ced3235449bd395ead45bcb16f674 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*

From 20a62cb7804e360ead81bcce582ed41da7be218a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 
+     <row>
 
+   <para>

 

 
+/*

index a93f970a292..455a03cce63 100644
 
+	/*

 
 						/*
 
+	/*
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*
 
index 00f07552135..9bf68f42b0e 100644
@@ -289,7 +289,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -439,6 +439,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
@@ -730,7 +731,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEEP KEY KEYS

 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
@@ -1077,6 +1078,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -12739,6 +12741,47 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -17787,6 +17830,7 @@ unreserved_keyword:
@@ -18398,6 +18442,7 @@ bare_label_keyword:

 			/*
index c2806297aa4..aba5259e027 100644
 
@@ -33,11 +34,13 @@
 
 /* GUC parameters */
@@ -106,6 +109,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,

 /*
@@ -499,6 +505,89 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	return result;
 }
 
+/*
@@ -575,6 +664,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
@@ -847,8 +938,61 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	}
 
 	/*
@@ -880,6 +1024,72 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
@@ -1815,6 +2025,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
@@ -1858,6 +2069,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 
 			/*
@@ -3197,6 +3411,10 @@ ParseExprKindName(ParseExprKind exprKind)
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
index b67d5186a2d..914f5a79ca9 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*
index 50e1db0e5c3..21e12b09a28 100644
@@ -1498,6 +1498,7 @@ LargeObjectDesc
@@ -2601,6 +2602,7 @@ SerializedTransactionState
@@ -2791,6 +2793,9 @@ SupportRequestRows

From c743631d867e58f6944edaeb30622c940fd44aeb Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 

 

 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index a586d246ece..2cba190f652 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -2149,6 +2149,8 @@ stringify_grant_objtype(ObjectType objtype)
 			return "TABLESPACE";
 		case OBJECT_TYPE:
 			return "TYPE";
+		case OBJECT_VARIABLE:
+			return "VARIABLE";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
@@ -2232,6 +2234,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 			return "TABLESPACES";
 		case OBJECT_TYPE:
 			return "TYPES";
+		case OBJECT_VARIABLE:
+			return "VARIABLES";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5607273bf9f..c026ab5dcb8 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -92,6 +92,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_USER_MAPPING:
+		case OBJECT_VARIABLE:
 			return false;
 
 			/*
 
index dd458182f02..00f07552135 100644
@@ -277,8 +278,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -771,8 +772,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 
@@ -1038,6 +1039,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1580,6 +1582,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;

@@ -5187,6 +5190,34 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6973,6 +7004,7 @@ object_type_any_name:
 
 /*
@@ -7849,6 +7881,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7894,6 +7934,14 @@ privilege_target:

@@ -8091,6 +8139,7 @@ defacl_privilege_target:

@@ -9873,6 +9922,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10234,6 +10301,24 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10515,6 +10600,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17864,6 +17957,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18517,6 +18612,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P

 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
index d8c6330732e..64cb03db02e 100644
@@ -5405,6 +5406,190 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
@@ -9979,7 +10164,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -10578,6 +10764,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
@@ -15027,6 +15216,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -18646,6 +18838,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
+/*
 
 #endif							/* PG_DUMP_H */
index ac60829d686..0a2295f0521 100644

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 

 
 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du
index 171a7dd5d2b..50e1db0e5c3 100644
@@ -3077,6 +3080,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.47.0

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 29 Oct 2024 08:16:42 +0100

Hi

again, necessary rebase

Regards

Pavel
From 5159fcadb764a872686dbca8dcd62469aaef7cd7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From db10fe6280141ef59fa6ec1532e77810355623df Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:

 

 
 
 /*

 

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+

From fe17703c91217f83315f39a21836675806472e6a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From be1c341793754c3f90852843c9afb63892ac1974 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*

 /*

 
index 86c5bd324a9..b5cb1bee223 100644
 #include "access/tupconvert.h"
@@ -323,6 +324,8 @@ static int	exec_stmt_commit(PLpgSQL_execstate *estate,
 
 static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
 								 PLpgSQL_function *func,
@@ -2116,6 +2119,10 @@ exec_stmts(PLpgSQL_execstate *estate, List *stmts)
 				rc = exec_stmt_rollback(estate, (PLpgSQL_stmt_rollback *) stmt);
 				break;
 
+			case PLPGSQL_STMT_LET:
+				rc = exec_stmt_let(estate, (PLpgSQL_stmt_let *) stmt);
+				break;
+
 			default:
 				/* point err_stmt to parent, since this one seems corrupt */
 				estate->err_stmt = save_estmt;
@@ -4999,6 +5006,54 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt)
 	return PLPGSQL_RC_OK;
 }
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */

From 84b3547cfad13d0ace92414d298237cbdf587b4b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

 
 
 	/*

 

From 389703a7b78ce3b39872e9ac52039fe7e2e83111 Mon Sep 17 00:00:00 2001

 
+/*

 

 

 select a from atacc1;

 

From fc7c96870e692cb4bb62291dbda2853db52782a2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*

 /*
 
 CreateSessionVarStmt:

 

 

 
+	if (varnotnull)
 
 /*

 

+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From 4ea5641501650fbb640d82eaf73ba1b9c0046b97 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*

 

 
-	/*

 
 
index 1361a8a8480..86c5bd324a9 100644
@@ -8099,8 +8099,7 @@ exec_is_simple_query(PLpgSQL_expr *expr)
 
 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.47.0

From 394d032948ed08e76c4540954e20bb0045e21a4a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{

 
 CreateSessionVarStmt:

 			if (isAgg)

 

 
 
+	if (vardefexpr)

 
 
 	if (verbose)

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From 93d035e7c30bd386a3a2f1d48dfae4465c280972 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

 

-/* LET */

From 04558dc55d7fb5f704563949caeb90f4c81fbfea Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;

 

 

 

 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From 20c19c2c71126f6a6621948c06835784e28bf7b8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
 
 		/*

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables

From 2c780d353c760ac4ab0169c0d7fa20de4b8676a7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

 

 DROP TABLE public.xxtab;

From e883ab1fc242b45ff0dc86ba6c262b98c43e66c5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index d54f9049569..de1b7acd889 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10736,6 +10736,66 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>

  <sect1 id="ddl-others">
 
 /* GUC parameters */

 /* GUC parameters */

 

 

From 11a4e0eab4d10b176cb099b842e92f76921e2974 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From 1b7f06ae78974ac01e955dc2cdd704924fd00129 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 

 
 
+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 
 
 	if (sessionvars)

 
 -- should be zero again

From 3f2cdd9e31aba1af3d4329aa5fe855f287c54d37 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+

From ea37cb966bb662aefaa1e1726fb2d7b7cfe7b1f5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*

From ab861889b75399931d99ef46754006fe20542784 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 
+     <row>
 
+   <para>

 

 
+/*

 
+	/*

 
 						/*
 
+	/*
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*

 

 
 
 			/*

 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 86c5bd324a9..1361a8a8480 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8099,7 +8099,8 @@ exec_is_simple_query(PLpgSQL_expr *expr)
 		query->sortClause ||
 		query->limitOffset ||
 		query->limitCount ||
-		query->setOperations)
+		query->setOperations ||
+		query->hasSessionVariables)
 		return false;
 
 	/*

From 59b4d5736999ec64782092730d81d448f5f365f5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 

 

 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 

 
 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du

---

From: Laurenz Albe <[email-masked]>
Date: Sat, 02 Nov 2024 06:46:12 +0100

> again, necessary rebase

I have started looking at patch 5, and I have some questions and comments.

- The commit message is headed "memory cleaning after DROP VARIABLE", but
  the rest of the commit message speaks of sinval messages.  These two
  things are independent, aren't they?  And both lead to the need to validate
  the variables, right?

  Then this code comment would for example be wrong:

  It also becomes "true" after DROP VARIABLE, right?
  I am happy to fix the comment, but I want to understand the patch first.

- I see that the patch adds cleanup of invalid session variable to each
  COMMIT.  Is that a good idea?  I'd expect that it is good enough to clean
  up whenever session variables are accessed.
  Calling remove_invalid_session_variables() during each COMMIT will affect
  all transactions, and I don't see the benefit.

  Also, do we need to call it during pg_session_variables()?

Yours,
Laurenz Albe

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 2 Nov 2024 08:36:30 +0100

>   things are independent, aren't they?  And both lead to the need to

Maybe it can be formulated differently, but it is true. There are a lot of
sinval messages, but in this case
only sinval messages related to DROP VARIABLE are interesting.

>   I am happy to fix the comment, but I want to understand the patch first.

sinval message can be raised by any operation over the pg_variable table.

 <-><-->if (hashvalue == 0 || svar->hashvalue == hashvalue)
 <-><-->{
 <-><--><-->svar->is_valid = false;
+<-><--><-->needs_validation = true;
+<-><-->}
+<->}

When I execute DROP VARIABLE, then the hash value is specified, but the
hash can be zero for some massive cleaning, and there are other events that
can send sinval message. I think an ANALYZE does this. So the comment /*
true after accepted sinval message */ is more accurate than /* true after
DROP VARIABLE */.

>   all transactions, and I don't see the benefit.

If I remember well, there were two reasons why I did it.

1. Minimize the unwanted surprises for users that will check memory usage -
So if you drop the variables, then the allocated space is released in
possibly near time. The rule - allocated space is released, when in the
next transaction you use any session variable looks a little bit crazy
(although I think so there will not be real significant difference in
functionality). Correct me, if I am wrong, but I don't remember any memory
(or resource) cleaning in Postgres, that is delayed to second transactions.
I agree, there is overhead of cleaning, but this can be very fast when the
user doesn't use session variables, because the hash table with session
variables is not initialized. I can imagine some usage some hooks there as
alternative

2. The main reason why it is implemented is implementation of temporal
variables with RESET or DROP on transaction end. Related code should be
triggered at commit time, it cannot be delayed.

>   Also, do we need to call it during pg_session_variables()?

I think it can be removed. Originally pg_session_variables showed only
valid variables, but it is not true now.

Regards

Pavel

---

From: Laurenz Albe <[email-masked]>
Date: Mon, 04 Nov 2024 10:24:09 +0100

On Sat, 2024-11-02 at 08:36 +0100, Pavel Stehule wrote:
> so 2. 11. 2024 v 6:46 odesílatel Laurenz Albe <[email-masked]> napsal:
> Maybe it can be formulated differently, but it is true. There are a lot of sinval messages, but in this case

Okay...

> sinval message can be raised by any operation over the pg_variable table.

I set a watchpoint on "needs_validation", and when I run DROP VARIABLE, it is called
directly from the command:

Hardware watchpoint 2: needs_validation

Old value = false
New value = true
0x00000000006ad44c in SessionVariableDropPostprocess (varid=<optimized out>) at session_variable.c:169
169				svar->drop_lxid = MyProc->vxid.lxid;
(gdb) where
#0  0x00000000006ad44c in SessionVariableDropPostprocess (varid=<optimized out>) at session_variable.c:169
#1  0x0000000000625dec in DropVariableById (varid=<optimized out>) at pg_variable.c:259
#2  0x00000000005fec57 in doDeletion (object=0x4ac85e8, flags=0) at dependency.c:1450
...
#8  0x00000000008bb7e0 in standard_ProcessUtility (pstmt=0x49ac700, queryString=0x49abbb0 "DROP VARIABLE a;", readOnlyTree=<optimized out>, 
    context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x49acac0, qc=0x7ffe19d2db60) at utility.c:1076
...
#12 0x00000000008b6742 in exec_simple_query (query_string=0x49abbb0 "DROP VARIABLE a;") at postgres.c:1283

Later, there is a sinval message and "pg_variable_cache_callback()" is called, which sets
"needs_validation" again.

Perhaps my confustion is as follows: if DROP VARIABLE sends an invalidation message,
and the handler sets "needs_validation", why is it necessary to set "needs_validation"
in SessionVariableDropPostprocess() too?

If we didn't set "needs_validation" in SessionVariableDropPostprocess(), the comment
would be true.

> 1. Minimize the unwanted surprises for users that will check memory usage - So if you
>    drop the variables, then the allocated space is released in possibly near time.
>    The rule - allocated space is released, when in the next transaction you use any
>    session variable looks a little bit crazy (although I think so there will not be real
>    significant difference in functionality).
>    Correct me, if I am wrong, but I don't remember any memory (or resource) cleaning
>    in Postgres, that is delayed to second transactions. I agree, there is overhead of cleaning,
>    but this can be very fast when the user doesn't use session variables, because the hash table
>    with session variables is not initialized. I can imagine some usage some hooks there as alternative

I don't think that is a good enough reason.

Memory used by an idle backend is not totally predictable for other reasons as well:
- the catalog cache
- memory that PostgreSQL freed, but that is kept in the malloc arena so that
  it can be reused for the next malloc() call

I believe that the disadvantage of keeping some memory allocated across transaction
is not as bad as the pain of going through all variables on each COMMIT.
If you have set one or two session variables and run a lot of statements in
autocommit mode, that is an unnecessary overhead.

> 2. The main reason why it is implemented is implementation of temporal variables
>    with RESET or DROP on transaction end. Related code should be triggered at
>    commit time, it cannot be delayed.

That makes sense.

If I remember right, temporary variables are an optional feature.
So I suggest that you move AtPreEOXact_SessionVariables() to the patch that adds
temporary session variables.

> I think it can be removed. Originally pg_session_variables showed only valid variables, but it is not true now.

Right.

I'll wait for your reaction to my above suggestions before I start working on the comments.

Yours,
Laurenz Albe

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 6 Nov 2024 19:24:20 +0100

Hi

only fresh rebase

Regards

Pavel
From 1adb925bab4d24c1e5cd99ff9d9db65554622488 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*
index 5ae0804c493..2a6b46e39eb 100644

 /*

@@ -5223,31 +5223,33 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5275,6 +5277,12 @@ OptImmutable: IMMUTABLE								{ $$ = true; }
 
+OptTransactional:
@@ -18067,6 +18075,7 @@ unreserved_keyword:
@@ -18715,6 +18724,7 @@ bare_label_keyword:
index b82e3d6f42a..72462b12ce6 100644

 

 
 
 /*
 
index 866985b879a..24843d394e7 100644
@@ -5162,7 +5162,7 @@ listVariables(const char *pattern, bool verbose)
 
@@ -5185,6 +5185,7 @@ listVariables(const char *pattern, bool verbose)

 
index 677ceb4946f..9dc1b5cb438 100644

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+

From 2a5c7459776f10895e78de332ad91f0406af659d Mon Sep 17 00:00:00 2001

 
+/*

 

 

 select a from atacc1;

 

From 0d6849693fad6bd70a1bde2ff156b7756deafa6f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*
index e18568884ff..5ae0804c493 100644

 /*
 
index c163c952445..677ceb4946f 100644

 
 
+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index 27e352fa341..9685bc1bcb9 100644

From 690aa348305d8268e244386bed0725d346fc34cd Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>
index f2c1020d053..f916736d761 100644
@@ -104,6 +104,7 @@ main(int argc, char **argv)
 
@@ -154,7 +155,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1",
+	while ((c = getopt_long(argc, argv, "A:acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1",
 							cmdopts, NULL)) != -1)
 	{
 		switch (c)
@@ -162,6 +163,11 @@ main(int argc, char **argv)
 			case 'a':			/* Dump data only */
 				opts->dataOnly = 1;
 				break;
+			case 'A':			/* vAriable */
+				opts->selTypes = 1;
+				opts->selVariable = 1;
+				simple_string_list_append(&opts->variableNames, optarg);
+				break;
 			case 'c':			/* clean (i.e., drop) schema prior to create */
 				opts->dropSchema = 1;
 				break;
@@ -462,6 +468,7 @@ usage(const char *progname)

From 1d57d0a5a973e0d7f8b856a17dcc31a874ff9691 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From d0ae54e5c53082b6234d2d814b94cbf79cb3782f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

 
 
 	/*

 

From 8fca358a597a928070c4035248c68fa2b397bef0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*
index 481055203d1..e18568884ff 100644

 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -5214,27 +5215,31 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5254,6 +5259,14 @@ XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
 		;

index b9165e4fa8d..b82e3d6f42a 100644

 

 

+	if (varnotnull)
 
 /*
 
index 45b3839a87e..866985b879a 100644
@@ -5162,7 +5162,7 @@ listVariables(const char *pattern, bool verbose)
 
@@ -5183,6 +5183,8 @@ listVariables(const char *pattern, bool verbose)
@@ -5193,6 +5195,8 @@ listVariables(const char *pattern, bool verbose)

 
+	/* don't allow NULL */
 
index 7a7a75dce79..c163c952445 100644

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From e7811c4e729e887025c0acbe7718387f9d366beb Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*
 
index 6dc8c562bec..845bdd1ae85 100644

index 30c5a19aad6..0a154b97d6e 100644

 
-	/*

 

 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.47.0

From eebacfe2ec201e4a6560e9b5ba9196d0ed56b120 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vardefexpr</structfield> <type>pg_node_tree</type>
+      </para>
+      <para>
+       The internal representation of the variable default value
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index a17b646a0de..639eaab7f14 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5305,14 +5305,14 @@ SELECT current_user_id;
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{
 
index 0f13810e92f..481055203d1 100644
 
 %type <node>	json_format_clause
 				json_format_clause_opt
@@ -5213,30 +5214,36 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:

 			if (isAgg)
 
index 00d070bd4a2..b9165e4fa8d 100644

 

 
+	if (vardefexpr)

index e0272c51000..45b3839a87e 100644
@@ -5162,7 +5162,7 @@ listVariables(const char *pattern, bool verbose)
 
@@ -5183,6 +5183,7 @@ listVariables(const char *pattern, bool verbose)
@@ -5192,6 +5193,7 @@ listVariables(const char *pattern, bool verbose)
 
 	if (verbose)

index 4a9b749b9a1..7a7a75dce79 100644

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From 24aee9b2a94fa7695145dad07a7cc6dedc80742d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

 

 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index bdec8cf1462..0f13810e92f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5242,6 +5242,7 @@ CreateSessionVarStmt:
  * transaction end like tables.
  */
 XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
+			| ON TRANSACTION END_P RESET			{ $$ = VARIABLE_XACTEND_RESET; }
 			| /*EMPTY*/								{ $$ = VARIABLE_XACTEND_NOOP; }
 		;
 
index 47a859ac365..00d070bd4a2 100644

 

 

 
index d8572171f0d..e0272c51000 100644
@@ -5185,6 +5185,7 @@ listVariables(const char *pattern, bool verbose)
 
 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From 53befdd49a8edae85131eb1b3ac4dc797f6e4061 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

index 3904b8fd512..5d774b69473 100644
@@ -12144,7 +12144,8 @@ PreparableStmt:

 
-/* LET */

From a5696af818d5d4a35983a9e040489417f77e1bfc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

index 62511700196..3904b8fd512 100644
@@ -12113,7 +12113,8 @@ ExplainableStmt:

 
 DROP TABLE public.xxtab;

From 3c99d3024b7bcd0b9337e6243f5ef9508ab904bf Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

 
index d3bf0f60fce..3415d279903 100644
@@ -5325,6 +5325,16 @@ SELECT current_user_id;
 
  <sect1 id="ddl-others">
 
 /* GUC parameters */

 /* GUC parameters */

 

 

From d3b804cced9d38a90e6d2ef2d1cc7e49a94e92d5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 21:36:54 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 
+     <row>
index 3415d279903..a17b646a0de 100644
@@ -5310,9 +5310,9 @@ SELECT current_user_id;
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e3f65dc66c3..01e3d469306 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2318,7 +2318,7 @@ CommitTransaction(void)
 	smgrDoPendingSyncs(true, is_parallel_worker);

@@ -2916,6 +2916,7 @@ AbortTransaction(void)
@@ -5194,6 +5195,8 @@ CommitSubTransaction(void)
@@ -5359,6 +5362,8 @@ AbortSubTransaction(void)

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 
+/*

+/*
 
 /*

 /*
index 2bd49eb55e6..c736c5c46f3 100644
 
 		/*
index 5d774b69473..bdec8cf1462 100644
 
 %type <ival>	for_locking_strength
 %type <node>	for_locking_item
@@ -5212,26 +5213,39 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e811cceaae5..d8572171f0d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5162,7 +5162,7 @@ listVariables(const char *pattern, bool verbose)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, false, false, false, false};
 
@@ -5182,12 +5182,16 @@ listVariables(const char *pattern, bool verbose)
 
 	if (verbose)
 	{

 

index a3acd2d8664..4a9b749b9a1 100644

 
 
 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index bbfaab57083..27e352fa341 100644

From c13512bc5717112e3add5432391c4e1127a78e93 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From aa46754ddf4b55f3ec712470822874083a30030f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

index b7ebcc2a557..e3f65dc66c3 100644
@@ -2316,6 +2317,9 @@ CommitTransaction(void)

 

+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 
 
 	if (sessionvars)

 
 -- should be zero again

From e51506d1b4db71d163a42c56d6322d6534209657 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*
index f7e4c27b550..62511700196 100644

index 69d2f7e1ced..970451460c2 100644
@@ -2962,6 +2962,9 @@ CreateCommandTag(Node *parsetree)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+extern void ResetSessionVariables(void);
+
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4eaf9c70948..a3acd2d8664 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3988,6 +3988,7 @@ typedef enum DiscardMode
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
 	DISCARD_TEMP,
+	DISCARD_VARIABLES,
 } DiscardMode;

 RESET search_path;
+

From 692cfbdc6346bd585fda11fbad55281a8f15ef91 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*
index d676ee83e41..89a372c65ca 100644
@@ -12395,4 +12395,12 @@

From df0c5cb0e97ffc9f0a8890adf19eac32f27df17c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

 58 files changed, 3916 insertions(+), 23 deletions(-)

+     <row>
index 950079073d9..d3bf0f60fce 100644
@@ -5283,6 +5283,38 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
+   <para>

 

 
+/*

 
+	/*

index 8f7a5340059..6dc8c562bec 100644
 
 						/*
 
+	/*
index 3060847b133..8fc67f08d76 100644
@@ -4341,6 +4341,16 @@ raw_expression_tree_walker_impl(Node *node,
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*
 
index 33b10d72cb5..9f698a46d87 100644

 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*
 
index 59da3b50ab9..f7e4c27b550 100644

 
@@ -12753,6 +12755,47 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 
@@ -17823,6 +17866,7 @@ unreserved_keyword:
@@ -18434,6 +18478,7 @@ bare_label_keyword:

 			/*

 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

index 217c6028497..69d2f7e1ced 100644
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 				 * Commands inside the DO block or the called procedure might
@@ -1067,6 +1069,11 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_LetStmt:
@@ -2206,6 +2213,10 @@ UtilityContainsQuery(Node *parsetree)
 
+		case T_LetStmt:
+			qry = castNode(Query, ((LetStmt *) parsetree)->query);
+			return qry;
+
 		default:
 			return NULL;
 	}
@@ -2404,6 +2415,10 @@ CreateCommandTag(Node *parsetree)
 			tag = CMDTAG_SELECT;
 			break;
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -3289,6 +3304,7 @@ GetCommandLogLevel(Node *parsetree)
 			break;
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
index 182a6956bb0..b4464edc22a 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index 8ee1cc5a131..4eaf9c70948 100644

 

 
 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*
index 5b1e772f63d..bbfaab57083 100644

From b113c409b6c65cf77fd94709fccae8d80e171047 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 65 files changed, 2364 insertions(+), 26 deletions(-)

index f02f67d7b86..950079073d9 100644
@@ -5264,6 +5264,37 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>

index 73979f20fff..56caa1051a1 100644
@@ -25736,6 +25736,19 @@ SELECT relname FROM pg_class WHERE pg_table_is_visible(oid);

 
 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
index dcfc1dbaffd..d03cb947e67 100644
 
 			/*
index 4345b96de5e..d37ec43bcd3 100644
 
index 89fdb94c237..59da3b50ab9 100644

 

@@ -5193,6 +5196,34 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6981,6 +7012,7 @@ object_type_any_name:
 
 /*
@@ -7857,6 +7889,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7902,6 +7942,14 @@ privilege_target:

@@ -8099,6 +8147,7 @@ defacl_privilege_target:

@@ -9887,6 +9936,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10248,6 +10315,24 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10529,6 +10614,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17900,6 +17993,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18553,6 +18648,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P

 	/*

 	return result;
 }
index f28bf371059..217c6028497 100644
@@ -1389,6 +1391,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				}
 				break;
 
+			case T_CreateSessionVarStmt:
+				address = CreateVariable(pstate, (CreateSessionVarStmt *) parsetree);
+				break;
+
 				/*
 				 * ************* object creation / destruction **************
 				 */
@@ -2341,6 +2347,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_STATISTIC_EXT:
 			tag = CMDTAG_ALTER_STATISTICS;
 			break;
+		case OBJECT_VARIABLE:
+			tag = CMDTAG_ALTER_VARIABLE;
+			break;
 		default:
 			tag = CMDTAG_UNKNOWN;
 			break;
@@ -2649,6 +2658,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_STATISTIC_EXT:
 					tag = CMDTAG_DROP_STATISTICS;
 					break;
+				case OBJECT_VARIABLE:
+					tag = CMDTAG_DROP_VARIABLE;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
@@ -3225,6 +3237,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;

 	return subname;
 }
+
index 9b2d34e2815..7018ec260c7 100644
@@ -245,6 +245,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)

 
 
 	int			useDB;
index b2f4eb2c6de..47a859ac365 100644
@@ -359,6 +359,7 @@ static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo);
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */
index 3c8f2eb808d..ca323171aa8 100644
@@ -77,6 +77,7 @@ enum dbObjectTypePriorities
@@ -118,6 +119,7 @@ static const int dbObjectTypePriority[] =
@@ -1500,6 +1502,10 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)

 
 
index 1f3cbb11f7c..7594f1010a6 100644
@@ -1077,6 +1077,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
index 37b43fb1221..e811cceaae5 100644
@@ -5151,6 +5151,102 @@ error_return:
 	return false;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 02fe5d151e0..317bc2eab6e 100644

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 
 
index f23321a41f1..d676ee83e41 100644
@@ -6556,6 +6556,9 @@
 
index 0d96db56386..8ee1cc5a131 100644

+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du
index 1847bbfa95c..5b1e772f63d 100644

---

From: Dmitry Dolgov <[email-masked]>
Date: Sun, 10 Nov 2024 16:24:19 +0100

Hi folks,

Thanks for continuing this work. As a side note, I would like to mention
how strange the situation in this CF item is. If I understand correctly,
there are two hackers threads discussing the same patch, with recent
patches posted in both of them. This is obviously confusing, e.g. the
main concern from another thread, about names clashing, wasn't even
mentioned in this one. Is it possible to reconcile development in one
thread?

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 10 Nov 2024 17:19:09 +0100

> mentioned in this one. Is it possible to reconcile development in one

This is probably my error. I don't try to organize threads, just I'll try
to reply in the thread where I got a question.

Regards

Pavel

---

From: Dmitry Dolgov <[email-masked]>
Date: Sun, 10 Nov 2024 18:41:07 +0100

> On Sun, Nov 10, 2024 at 05:19:09PM GMT, Pavel Stehule wrote:

It's fine. I just would appreciate some clarity about which patch to
look at. To confirm, the series in this thread contains everything from
the other one, plus some improvements, right?

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 10 Nov 2024 18:51:40 +0100

> to reply in the thread where I got a question.

I thought a lot of time about better solutions for identifier collisions
and I really don't think so there is some consistent user friendly syntax.
Personally I think there is an easy already implemented solution -
convention - just use a dedicated schema for variables and this schema
should not be in the search path. Or use secondary convention - like using
prefix "__" for session variables. Common convention is using "_" for
PLpgSQL variables. I searched how this issue is solved in other databases,
or in standard, and I found nothing special. The Oracle and SQL/PSM has a
concept of visibility - the variables are not visible outside packages or
modules, but Postgres has nothing similar. It can be emulated by a
dedicated schema without inserting a search path, but it is less strong.

I think we can introduce an alternative syntax, that will not be user
friendly or readable friendly, but it can be without collisions - or can
decrease possible risks.

It is nothing new - SQL does it with old, "new" syntax of inner joins, or
in Postgres we can

where salary < 40000

or

where pg_catalog.int4lt(salary, 40000);

or some like we use for operators OPERATOR(*schema*.*operatorname*)

So introducing VARIABLE(schema.variablename) syntax as an alternative
syntax for accessing variables I really like. I strongly prefer to use this
as only alternative (secondary) syntax, because I don't think it is
friendly syntax or writing friendly, but it is safe, and I can imagine
tools that can replace generic syntax to this special, or that detects
generic syntax and shows some warning. Then users can choose what they
prefer. Two syntaxes - generic and special can be good enough for all - and
this can be perfectly consistent with current Postgres.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 10 Nov 2024 19:04:34 +0100

> the other one, plus some improvements, right?

I don't remember any change that can be visible for users in this thread.
Laurens does very precious code review (thank you again) - there are bigger
changes in comments, and less changes in code - some parts of code are
moved between patches, some lines were redundant and removed, some lines
were artefacts of some git work and were cleaned. Some redundant tests were
removed. There is no new code.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 10 Nov 2024 19:09:04 +0100

> dedicated schema without inserting a search path, but it is less strong.

There can be more collisions in Oracle, because the functions without
arguments don't need parentheses. Postgres is safer, because this syntax is
not allowed.

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 10 Nov 2024 23:41:31 +0100

Hi

po 4. 11. 2024 v 10:24 odesílatel Laurenz Albe <[email-masked]>
napsal:

> #8  0x00000000008bb7e0 in standard_ProcessUtility (pstmt=0x49ac700,
> queryString=0x49abbb0 "DROP VARIABLE a;", readOnlyTree=<optimized out>,
>     context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0,
> dest=0x49acac0, qc=0x7ffe19d2db60) at utility.c:1076
> ...

I thought about it, and it  is redundant. I removed it. All tests passed
still.

> variables

moved

removed

> I'll wait for your reaction to my above suggestions before I start working

new patch set attached

Regards

Pavel

> Laurenz Albe
From 752fbf2fd1e82445a2015824872f3c665a1d6bd8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
index 20bf282b3f2..a66c8df415d 100644
 
 -- inlining is blocked

From dbf31733dbb58437f2e8c70ca346d7cae55c9225 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From ea51ff3fe6acb2edb6663693375c2d9dabba8fe4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 22:40:55 +0200
Subject: [PATCH 19/20] transactional variables

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 72110e05f58..1ba396b05b6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9858,6 +9858,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)
 
index 1df4a4e9bc5..4d4f1ad1e0a 100644
 
+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*
@@ -291,8 +328,25 @@ unregister_session_variable_xact_drop(Oid varid)
@@ -391,7 +445,7 @@ remove_invalid_session_variables(bool atEOX)
 			{
 				Oid			varid = svar->varid;
 
@@ -427,6 +481,94 @@ remove_session_variables_with_reset_at_eox(void)
 
+/*
@@ -474,6 +616,8 @@ AtPreEOXact_SessionVariables(bool isCommit)
 
+	remove_prev_values_at_eox(isCommit);
+
 	/*
 	 * We have to clean xact_drop_items. All related variables are dropped
 	 * now, or lost inside aborted transaction.
@@ -619,6 +763,7 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)

 	svar->is_domain = (get_typtype(varform->vartype) == TYPTYPE_DOMAIN);
 	svar->domain_check_extra = NULL;
@@ -644,6 +789,17 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	svar->isnull = true;
 	svar->value = (Datum) 0;
 
+	if (svar->is_transact)
+	{
 
 	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
@@ -677,6 +833,69 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	ReleaseSysCache(tup);
 }
 
+/*
@@ -689,6 +908,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	Assert(svar);
 	Assert(!isnull || value == (Datum) 0);
@@ -724,6 +947,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	else
 		_svar = svar;
 
@@ -735,7 +973,37 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	else
 		newval = value;

 	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new value",
 		 get_namespace_name(get_session_variable_namespace(svar->varid)),
@@ -748,6 +1016,9 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	svar->value = newval;
 	svar->isnull = isnull;
 
 	/* don't allow more changes of value when variable is IMMUTABLE */
 	if (svar->is_immutable)
@@ -851,6 +1122,8 @@ get_session_variable(Oid varid)
 	else
 		svar->is_valid = false;
 
+reinit:
+
 	/*
 	 * Force setup for not yet initialized variables or variables that cannot
 	 * be validated.
@@ -883,6 +1156,28 @@ get_session_variable(Oid varid)
 			 varid);
 	}
 
+	/*
index bc8e25b1933..af9d640c0e2 100644

 /*

@@ -5240,31 +5240,33 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5292,6 +5294,12 @@ OptImmutable: IMMUTABLE								{ $$ = true; }
 
+OptTransactional:
+			TRANSACTION								{ $$ = true; }
+			| TRANSACTIONAL							{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
@@ -18084,6 +18092,7 @@ unreserved_keyword:
 			| TEXT_P
 			| TIES
 			| TRANSACTION
+			| TRANSACTIONAL
 			| TRANSFORM
 			| TRIGGER
 			| TRUNCATE
@@ -18732,6 +18741,7 @@ bare_label_keyword:
 			| TIMESTAMP
 			| TRAILING
 			| TRANSACTION
+			| TRANSACTIONAL
 			| TRANSFORM
 			| TREAT
 			| TRIGGER
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 59f4103ca4f..762cd020ea9 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5439,6 +5439,7 @@ getVariables(Archive *fout)
 	int			i_varcollation;
 	int			i_varnotnull;
 	int			i_varisimmutable;
+	int			i_varistransact;
 	int			i_varacl;
 	int			i_acldefault;
 	int			i,
@@ -5463,6 +5464,7 @@ getVariables(Archive *fout)
 					  "       END AS varcollation,\n"
 					  "       v.varnotnull,\n"
 					  "       v.varisimmutable,\n"
+					  "       v.varistransact,\n"
 					  "       pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n"
 					  "       v.varowner, v.varacl,\n"
 					  "       acldefault('V', v.varowner) AS acldefault\n"
@@ -5485,6 +5487,7 @@ getVariables(Archive *fout)
 	i_varcollation = PQfnumber(res, "varcollation");
 	i_varnotnull = PQfnumber(res, "varnotnull");
 	i_varisimmutable = PQfnumber(res, "varisimmutable");
+	i_varistransact = PQfnumber(res, "varistransact");
 
@@ -5513,6 +5516,7 @@ getVariables(Archive *fout)
 
@@ -5560,6 +5564,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5577,12 +5582,13 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 
 	if (OidIsValid(varcollation))
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f7921f942d0..310701f09e1 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -719,6 +719,7 @@ typedef struct _VariableInfo
 	const char *rolname;		/* name of owner, or empty string */
 	bool		varnotnull;
 	bool		varisimmutable;
+	bool		varistransact;
 } VariableInfo;
 
 /*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 47cb59356e0..b39bddd3e40 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4077,6 +4077,42 @@ my %tests = (
 		},
 	},
 
index cc69f07f1f9..8862539d76e 100644
@@ -5206,7 +5206,7 @@ listVariables(const char *pattern, bool verbose)
 
@@ -5229,6 +5229,7 @@ listVariables(const char *pattern, bool verbose)
@@ -5241,6 +5242,7 @@ listVariables(const char *pattern, bool verbose)
 
index b653dda1fe8..e955522d2cb 100644

index bf820828dd9..a4372f184df 100644
index e1635f686c2..da9f4a7030a 100644

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 
 
index e3dcb714607..7568f24ada4 100644
 
index 27c13b487b0..6e8831d7193 100644
 
 DROP VARIABLE var1;
+

From 0fad39d34bb6c434d77785585b9cd7761ad075f4 Mon Sep 17 00:00:00 2001

 
+/*

 
diff --git a/src/test/isolation/expected/session-variable.out b/src/test/isolation/expected/session-variable.out
index e9a254bcd12..d8848d555cd 100644
--- a/src/test/isolation/expected/session-variable.out
+++ b/src/test/isolation/expected/session-variable.out
@@ -10,7 +10,7 @@ test

 
 starting permutation: let val dbg drop create dbg val
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 2212c8dbb59..35b3a3bd302 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1295,19 +1295,19 @@ select * from atacc1;
 (1 row)
 
@@ -1317,7 +1317,7 @@ select atacc1.* from atacc1;
 (1 row)
 
 select a from atacc1;
@@ -1331,15 +1331,15 @@ select b,c,d from atacc1;
 (1 row)
 
@@ -1347,11 +1347,11 @@ ERROR:  column atacc1.........pg.dropped.1........ does not exist
@@ -1360,7 +1360,7 @@ ERROR:  column "a" of relation "atacc1" does not exist
@@ -1368,7 +1368,7 @@ ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exis
@@ -1416,11 +1416,11 @@ LINE 1: insert into atacc1 ("........pg.dropped.1........",b,c,d) va...
@@ -1706,7 +1706,7 @@ select f1 from c1;
 
@@ -1720,7 +1720,7 @@ ERROR:  cannot drop inherited column "f1"
 
index 77a7bf45c8a..e1635f686c2 100644
index a66c8df415d..e3dcb714607 100644

From 28cb7ebf011378714334ddd73672f9a54a473fbc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*
index 6aea3bd35b2..bc8e25b1933 100644

 /*
 
index 7308c323574..bf820828dd9 100644

 
 
+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */

From 5c3e6810a32310d9cc11f39163b1e46725ceb2c6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 07:57:29 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

 
 
 	/*

index 388c8ee8e72..20bf282b3f2 100644

From 479e4e273938c5c4319d43fd8b055586d03b08d6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 14:40:37 +0200
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 789f774770b..72110e05f58 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9841,6 +9841,26 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)
 
index 9a92bc5e2e3..1df4a4e9bc5 100644
 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 typedef SVariableData *SVariable;
@@ -564,6 +570,23 @@ eval_assign_defexpr(SVariable svar, HeapTuple tup)

 		FreeExecutorState(estate);
 	}
@@ -594,6 +617,9 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)

@@ -623,9 +649,31 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 
-	if (!is_write)
 
+		/*
 
@@ -645,6 +693,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	Assert(svar);
 	Assert(!isnull || value == (Datum) 0);
 
@@ -685,6 +748,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*
index b8e33ed19db..6aea3bd35b2 100644

 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -5231,27 +5232,31 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
@@ -5271,6 +5276,14 @@ XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
 		;

index c138994304a..59f4103ca4f 100644
@@ -5437,6 +5437,8 @@ getVariables(Archive *fout)
@@ -5459,6 +5461,8 @@ getVariables(Archive *fout)
@@ -5479,6 +5483,8 @@ getVariables(Archive *fout)
 
 	i_varowner = PQfnumber(res, "varowner");
 	i_varacl = PQfnumber(res, "varacl");
@@ -5505,6 +5511,8 @@ getVariables(Archive *fout)
 			pg_strdup(PQgetvalue(res, i, i_varxactendaction));

@@ -5551,7 +5559,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5565,12 +5575,14 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 
 	if (OidIsValid(varcollation))
 	{
@@ -5582,6 +5594,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (varnotnull)
+		appendPQExpBuffer(query, " NOT NULL");
+
 	if (vardefexpr)
 		appendPQExpBuffer(query, " DEFAULT %s",
 						  vardefexpr);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 8ed83979ec9..f7921f942d0 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -717,6 +717,8 @@ typedef struct _VariableInfo
 	char	   *initrvaracl;
 	Oid			varcollation;
 	const char *rolname;		/* name of owner, or empty string */
+	bool		varnotnull;
+	bool		varisimmutable;
 } VariableInfo;
 
 /*
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index f58ede5334a..47cb59356e0 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4041,6 +4041,42 @@ my %tests = (
 		},
 	},
 
index dc18da350eb..cc69f07f1f9 100644
@@ -5206,7 +5206,7 @@ listVariables(const char *pattern, bool verbose)
 
@@ -5227,6 +5227,8 @@ listVariables(const char *pattern, bool verbose)
@@ -5237,6 +5239,8 @@ listVariables(const char *pattern, bool verbose)
 
index 3fb2c4e9248..b653dda1fe8 100644

+	/* don't allow NULL */
 
index 60404413c49..7308c323574 100644
index bc7b60a97ea..77a7bf45c8a 100644

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 
 
index 48855136489..36c73dadc27 100644

 DROP VARIABLE var1;
index b8d56edf46c..3fb6267adb4 100644
 
 DROP VARIABLE var1;

From d1d04c6b7521c35d0066fcc96a7d3e4016a3358b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 06:31:48 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*

 

 
-	/*

 
 
index ddb099bbbb1..789c3123b0c 100644
-CONTEXT:  PL/pgSQL expression "plpgsql_sv_var1"
 
 	/*
index 36c73dadc27..388c8ee8e72 100644
index 3fb6267adb4..27c13b487b0 100644

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.47.0

From 190c9c555da180e1a692c274ec861d8fd3a93dcd Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 10:44:20 +0200
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 76eff813b43..789f774770b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9873,6 +9873,14 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vardefexpr</structfield> <type>pg_node_tree</type>
+      </para>
+      <para>
+       The internal representation of the variable default value
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 05c9a0aebcc..a20db14af78 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5338,14 +5338,14 @@ SELECT current_user_id;
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{
 
index f69fc9f9b57..9a92bc5e2e3 100644
@@ -511,11 +512,68 @@ AtEOSubXact_SessionVariables(bool isCommit,
 	}
 }
 
+/*
@@ -565,6 +623,9 @@ setup_session_variable(SVariable svar, Oid varid)
 
+	if (!is_write)
 
@@ -594,7 +655,7 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	 */
 	if (!svar->is_valid)
 	{
@@ -729,7 +790,25 @@ get_session_variable(Oid varid)
 
 		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by READ)",
 			 get_namespace_name(get_session_variable_namespace(varid)),
@@ -793,7 +872,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 
 	if (!found)
 	{
 
index d8de8acf944..b8e33ed19db 100644
 
 %type <node>	json_format_clause
 				json_format_clause_opt
@@ -5230,30 +5231,36 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:

 			if (isAgg)
 
index 2ec6a22a266..c138994304a 100644
@@ -5433,6 +5433,7 @@ getVariables(Archive *fout)
@@ -5458,6 +5459,7 @@ getVariables(Archive *fout)
 
@@ -5509,6 +5512,11 @@ getVariables(Archive *fout)
 		varinfo[i].dacl.initprivs = NULL;
 		varinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_varowner));
 
@@ -5541,6 +5549,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
@@ -5553,6 +5562,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

@@ -5572,6 +5582,10 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (vardefexpr)
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 7c0c03749e8..8ed83979ec9 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -709,6 +709,7 @@ typedef struct _VariableInfo
 	DumpableAcl dacl;
 	Oid			vartype;
 	char	   *vartypname;
+	char	   *vardefexpr;
 	char	   *varxactendaction;
 	char	   *varacl;
 	char	   *rvaracl;
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 419ea39727b..f58ede5334a 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4005,6 +4005,42 @@ my %tests = (
 		},
 	},
 
index d0f69f8c8e9..dc18da350eb 100644
@@ -5206,7 +5206,7 @@ listVariables(const char *pattern, bool verbose)
 
@@ -5227,6 +5227,7 @@ listVariables(const char *pattern, bool verbose)
@@ -5236,6 +5237,7 @@ listVariables(const char *pattern, bool verbose)
 
 	if (verbose)

index 7a519e08429..60404413c49 100644

index 61077a52b16..bc7b60a97ea 100644

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 
 
index 20f37171ea2..48855136489 100644

 DROP VARIABLE var1;
index 8d5751f1b2e..b8d56edf46c 100644
 
 DROP VARIABLE var1;
+

From 568210968a6b58f7b7f9d3e9bc3b833ed574e673 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 20 Mar 2024 08:37:15 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 68811a7a3b2..76eff813b43 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9845,7 +9845,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
       <para>
        Action performed at end of transaction:
-       <literal>n</literal> = no action, <literal>d</literal> = drop the variable.
+       <literal>n</literal> = no action, <literal>d</literal> = drop the variable,
+       <literal>r</literal> = reset the variable to its default value.
       </para></entry>
      </row>

 
index 3d4e6eee6b2..f69fc9f9b57 100644
 
+	bool		reset_at_eox;
+
 	/*
 
+/*
@@ -386,6 +394,32 @@ remove_invalid_session_variables(bool atEOX)
 	}
 }
 
+/*
@@ -393,6 +427,8 @@ remove_invalid_session_variables(bool atEOX)
@@ -504,6 +540,21 @@ setup_session_variable(SVariable svar, Oid varid)
 
+	/*
 
 	svar->isnull = true;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 76655aa8aeb..d8de8acf944 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5259,6 +5259,7 @@ CreateSessionVarStmt:
  * transaction end like tables.
  */
 XactEndActionOption:  ON COMMIT DROP				{ $$ = VARIABLE_XACTEND_DROP; }
+			| ON TRANSACTION END_P RESET			{ $$ = VARIABLE_XACTEND_RESET; }
 			| /*EMPTY*/								{ $$ = VARIABLE_XACTEND_NOOP; }
 		;
 
index b8d839ece41..2ec6a22a266 100644
@@ -5433,6 +5433,7 @@ getVariables(Archive *fout)
 
 	i_varowner = PQfnumber(res, "varowner");
@@ -5495,6 +5498,9 @@ getVariables(Archive *fout)

 		varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl));
@@ -5535,6 +5541,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	PQExpBuffer query;
 	char	   *qualvarname;
 	const char *vartypname;
+	const char *varxactendaction;
 	Oid			varcollation;
 
 	/* skip if not to be dumped */
@@ -5546,6 +5553,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)

 	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
@@ -5564,6 +5572,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}

 	if (varinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f2f558b2961..7c0c03749e8 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -709,6 +709,7 @@ typedef struct _VariableInfo
 	DumpableAcl dacl;
 	Oid			vartype;
 	char	   *vartypname;
+	char	   *varxactendaction;
 	char	   *varacl;
 	char	   *rvaracl;
 	char	   *initvaracl;
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index dd8c054a6a6..419ea39727b 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3987,6 +3987,24 @@ my %tests = (
 		},
 	},
 
index d02f6416f0e..d0f69f8c8e9 100644
@@ -5229,6 +5229,7 @@ listVariables(const char *pattern, bool verbose)
 
 /* ----------------
diff --git a/src/test/isolation/expected/session-variable.out b/src/test/isolation/expected/session-variable.out
index a609797dc5d..e9a254bcd12 100644
--- a/src/test/isolation/expected/session-variable.out
+++ b/src/test/isolation/expected/session-variable.out
@@ -86,11 +86,12 @@ myvar
 
 step sr1: ROLLBACK;
 
@@ -103,6 +104,7 @@ t

 session s4
index 999397af26b..20f37171ea2 100644
 
index 1d2eb3c4281..8d5751f1b2e 100644

From b80a7a3431014637d58f569bdd8b561b514c97f1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 10 Nov 2024 22:13:22 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

 src/backend/access/transam/xact.c             |   8 +
 src/backend/commands/session_variable.c       | 200 +++++++++++++++++-
 src/include/commands/session_variable.h       |   6 +-
 23 files changed, 534 insertions(+), 63 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index aa796876e97..68811a7a3b2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9839,6 +9839,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
index 1f6c88cf556..05c9a0aebcc 100644
@@ -5343,9 +5343,9 @@ SELECT current_user_id;
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 47d8a849ef7..01e3d469306 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2317,6 +2317,9 @@ CommitTransaction(void)
 	 */
 	smgrDoPendingSyncs(true, is_parallel_worker);

@@ -2913,6 +2916,7 @@ AbortTransaction(void)
 	AtAbort_Portals();
@@ -5191,6 +5195,8 @@ CommitSubTransaction(void)
@@ -5356,6 +5362,8 @@ AbortSubTransaction(void)

 /*

 

 

 
 
 	ReleaseSysCache(tup);
 
index f5d19228e34..3d4e6eee6b2 100644
 
 #include "access/xact.h"

 
+/*
 
+	if (XactEndAction == VARIABLE_XACTEND_DROP)
+	{
+		Assert(isTempNamespace(get_session_variable_namespace(varid)));
+
+		unregister_session_variable_xact_drop(varid);
+	}
+
 	if (sessionvars)
 	{
 		bool		found;
@@ -171,6 +221,57 @@ SessionVariableDropPostprocess(Oid varid)
 	}
 }
 
+/*
@@ -238,7 +339,7 @@ is_session_variable_valid(SVariable svar)
  * until the end of the transaction (where the variable was dropped).
-remove_invalid_session_variables(void)
 	HASH_SEQ_STATUS status;
 	SVariable	svar;
@@ -265,7 +366,7 @@ remove_invalid_session_variables(void)
-			if (svar->drop_lxid == MyProc->vxid.lxid)
 				needs_validation = true;
 				continue;
@@ -285,6 +386,95 @@ remove_invalid_session_variables(void)
 	}
 }
 
+/*
@@ -443,7 +633,7 @@ get_session_variable(Oid varid)
 		validated_lxid != MyProc->vxid.lxid)
 		/* throw invalid entries, skip entries dropped by this transaction. */
-		remove_invalid_session_variables();
 
 		/* don't repeat it in this transaction */
 		validated_lxid = MyProc->vxid.lxid;
@@ -541,7 +731,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 		validated_lxid != MyProc->vxid.lxid)
 	{
 		/* throw invalid entries, skip entries dropped by this transaction */
-		remove_invalid_session_variables();
+		remove_invalid_session_variables(false);
 
 		/* don't repeat it in this transaction */
 		validated_lxid = MyProc->vxid.lxid;
 
 		/*
index af511d12aa1..76655aa8aeb 100644
 
 %type <ival>	for_locking_strength
 %type <node>	for_locking_item
@@ -5229,26 +5230,39 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index c52c8fd147f..d02f6416f0e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5206,7 +5206,7 @@ listVariables(const char *pattern, bool verbose)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, false, false, false, false};
 
@@ -5226,12 +5226,16 @@ listVariables(const char *pattern, bool verbose)
 
 	if (verbose)
 	{
index df3e2539270..3fb2c4e9248 100644

 
index 3dab8ae2a48..dc83296b1ba 100644
@@ -21,7 +21,11 @@

index 04ab22bd492..7a519e08429 100644

 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/test/isolation/expected/session-variable.out b/src/test/isolation/expected/session-variable.out
index 0a5579dc7ce..a609797dc5d 100644
--- a/src/test/isolation/expected/session-variable.out
+++ b/src/test/isolation/expected/session-variable.out
@@ -86,10 +86,11 @@ myvar
 
 step sr1: ROLLBACK;
 
index 88e21194711..61077a52b16 100644

 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 
 
index 1e28b9461fd..999397af26b 100644

 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables
index 1c39f70883c..1d2eb3c4281 100644

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables

From 055be690e79e71417b821f3d4280e7ad46e3a2a9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

index def7dde2330..af511d12aa1 100644
@@ -12161,7 +12161,8 @@ PreparableStmt:

index 3ef4776d98a..df3e2539270 100644
 
-/* LET */
index aa8ce67a543..1e28b9461fd 100644
 
index 046aa5a5d22..1c39f70883c 100644

From 3ccdf5f9d9d4fdb99e4c55858ed737b4f83e171c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

index aad47305f20..def7dde2330 100644
@@ -12130,7 +12130,8 @@ ExplainableStmt:

index 582d0b8d034..aa8ce67a543 100644
index ef9fffd1d4b..046aa5a5d22 100644
 
 DROP TABLE public.xxtab;

From 078e5fb07da63452622f33f6a4917112ad7f8939 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

 
index d4639b27795..1f6c88cf556 100644
@@ -5358,6 +5358,16 @@ SELECT current_user_id;
 
  <sect1 id="ddl-others">
 
 /* GUC parameters */

 /* GUC parameters */

index 593d232d260..ddb099bbbb1 100644

index 3239f45e8e4..582d0b8d034 100644
 
index a77fd785339..ef9fffd1d4b 100644

From acd0c1e562a8e52aca2a5b657f88ac123fec9518 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 06/20] plpgsql tests

---

index 00000000000..593d232d260
+CONTEXT:  PL/pgSQL expression "plpgsql_sv_var1"

From 325afcfd4ed0023f2f31209e532cc77324d63872 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:58:23 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

Accept of sinval message invalidates entries in sessionvars hash table. These entries
are validated before any read or write operations on session variables. When the entry
cannot be validated, then it is removed. The removing can be delayed when variable
is dropped inside current transaction. This transaction can be aborted and in this case
we want to preserve content of the variable.
---
 src/backend/access/transam/xact.c             |   1 +
 src/backend/catalog/pg_variable.c             |   4 +
 src/backend/commands/session_variable.c       | 156 ++++++++++++-
 src/include/commands/session_variable.h       |   2 +
 .../isolation/expected/session-variable.out   | 110 +++++++++
 src/test/isolation/isolation_schedule         |   1 +
 .../isolation/specs/session-variable.spec     |  50 ++++
 .../regress/expected/session_variables.out    | 218 ++++++++++++++++++
 src/test/regress/sql/session_variables.sql    | 121 ++++++++++
 9 files changed, 661 insertions(+), 2 deletions(-)
 create mode 100644 src/test/isolation/expected/session-variable.out
 create mode 100644 src/test/isolation/specs/session-variable.spec

index b7ebcc2a557..47d8a849ef7 100644
 
index 668b8189a6c..f5d19228e34 100644

+	/*

@@ -114,6 +136,37 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
@@ -167,6 +220,71 @@ is_session_variable_valid(SVariable svar)
 	return result;
 }
 
+/*
+remove_invalid_session_variables(void)
+			if (svar->drop_lxid == MyProc->vxid.lxid)
@@ -196,6 +314,8 @@ setup_session_variable(SVariable svar, Oid varid)

@@ -319,22 +439,44 @@ get_session_variable(Oid varid)
 	if (!sessionvars)
 		create_sessionvars_hashtables();
 
+		remove_invalid_session_variables();
 
 	if (found)
 	{
+		/*
@@ -395,6 +537,16 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 
+		remove_invalid_session_variables();
 
index 443afbafd4a..3dab8ae2a48 100644
@@ -21,6 +21,8 @@
 
index 00000000000..0a5579dc7ce
@@ -0,0 +1,110 @@
+      |    |t      
index 2c4760fb687..3239f45e8e4 100644
 
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
index 979b9029e58..a77fd785339 100644
 
 -- should be zero again
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;

From 19cd69cb9f3504d0d9bf68d470c3456ecfa100e2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*
index 8ae368c513c..aad47305f20 100644

index ec572815c94..3ef4776d98a 100644
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
index 778db6ad4f2..04ab22bd492 100644

 RESET search_path;
+

From a0210f5f45ec28d135c7654fae5af47c0fdd6031 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*

From cef94a6d42e938c503234fbd6f35784722551235 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 5709c9cdbd4..aa796876e97 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9784,6 +9784,19 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
index 6ae36f832f7..d4639b27795 100644
@@ -5316,6 +5316,38 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
+   <para>

 

 
+/*

 
+	/*

 
 						/*
 
+	/*
index 1f78dc3d530..aee3a0d1ad4 100644
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*
 
index 89fa23c2dd4..8ae368c513c 100644

 
@@ -12770,6 +12772,47 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 
@@ -17840,6 +17883,7 @@ unreserved_keyword:
@@ -18451,6 +18495,7 @@ bare_label_keyword:

 			/*

 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index a39068d1bf1..e7f74947cde 100644
@@ -8617,6 +8619,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
@@ -13399,6 +13409,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
index 293648e30c7..ec572815c94 100644
 
+/* LET */

 
+	/*

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index 0e08296b438..778db6ad4f2 100644

 

 
 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*

From 5cbaf816ed8133ece60814acf2d7767ff1b62037 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c180ed7abbc..5709c9cdbd4 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -369,6 +369,11 @@
       <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
       <entry>mappings of users to foreign servers</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+      <entry>session variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -9733,4 +9738,119 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </table>
  </sect1>
 
index 3c56610d2ac..6ae36f832f7 100644
@@ -5297,6 +5297,37 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>

 

 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*
index ccd9645e7d2..b7c1e280a00 100644
@@ -6768,6 +6769,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -6831,6 +6833,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
index 67eb96396af..89fa23c2dd4 100644

 

@@ -5210,6 +5213,34 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6998,6 +7029,7 @@ object_type_any_name:
 
 /*
@@ -7874,6 +7906,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7919,6 +7959,14 @@ privilege_target:

@@ -8116,6 +8164,7 @@ defacl_privilege_target:

@@ -9904,6 +9953,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10265,6 +10332,24 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10546,6 +10631,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17917,6 +18010,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18570,6 +18665,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0f324ee4e31..4eaed1f8439 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -105,6 +105,7 @@ typedef struct
 	List	   *indexes;		/* CREATE INDEX items */
 	List	   *triggers;		/* CREATE TRIGGER items */
 	List	   *grants;			/* GRANT items */
+	List	   *variables;		/* CREATE VARIABLE items */
 } CreateSchemaStmtContext;

@@ -4039,6 +4040,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 	cxt.indexes = NIL;
 	cxt.triggers = NIL;
 	cxt.grants = NIL;
+	cxt.variables = NIL;
 
 	/*
 	 * Run through each schema element in the schema element list. Separate
@@ -4107,6 +4109,15 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				cxt.grants = lappend(cxt.grants, element);
 				break;
 
@@ -4120,6 +4131,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 
 	return result;
 }

 
 	return subname;
 }
+
index 33d323085f5..b0451c1b903 100644
@@ -246,6 +246,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)

 
 
 	int			useDB;
index a8c141b689d..b8d839ece41 100644
@@ -363,6 +363,7 @@ static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo);
@@ -5416,6 +5417,190 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
@@ -10140,7 +10325,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -10739,6 +10925,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION_REL:
 			dumpSubscriptionTable(fout, (const SubRelInfo *) dobj);
 			break;
+		case DO_VARIABLE:
+			dumpVariable(fout, (VariableInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -15188,6 +15377,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
 		case DEFACLOBJ_NAMESPACE:
 			type = "SCHEMAS";
 			break;
+		case DEFACLOBJ_VARIABLE:
+			type = "VARIABLES";
+			break;
 		default:
 			/* shouldn't get here */
 			pg_fatal("unrecognized object type in default privileges: %d",
@@ -18898,6 +19090,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_CONVERSION:
 			case DO_TABLE:
 			case DO_TABLE_ATTACH:
+			case DO_VARIABLE:
 			case DO_ATTRDEF:
 			case DO_PROCLANG:
 			case DO_CAST:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index d65f5585651..f2f558b2961 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -52,6 +52,7 @@ typedef enum
 	DO_TABLE,
 	DO_TABLE_ATTACH,
 	DO_ATTRDEF,
+	DO_VARIABLE,
 	DO_INDEX,
 	DO_INDEX_ATTACH,
 	DO_STATSEXT,
@@ -699,6 +700,23 @@ typedef struct _SubRelInfo
 	char	   *srsublsn;
 } SubRelInfo;
 
+/*
@@ -782,5 +800,6 @@ extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
 
 #endif							/* PG_DUMP_H */
index aa1564cd450..dd8c054a6a6 100644

@@ -3942,6 +3969,24 @@ my %tests = (
 		},
 	},
 
@@ -4404,6 +4449,25 @@ my %tests = (
 		like => {},
 	},
 
index 5bfebad64d5..c52c8fd147f 100644
@@ -5195,6 +5195,102 @@ error_return:
 	return false;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index fad2277991d..293648e30c7 100644

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 

index 0f9462493e3..0e08296b438 100644

+

index 36dc31c16c4..88e21194711 100644
 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du

---

From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:24:02 +0100

> +#include "commands/session_variable.h"

You probably forgot to move that to the patch for temporary variables.
I did that.

> +static LocalTransactionId validated_lxid = InvalidLocalTransactionId;

I have moved the reference to the transaction end to the temporary variable
patch.

I have gone over the comments in patch 0005 and 0006.
I hope I got everything right.  Attached is an updated patch set.

Yours,
Laurenz Albe
From e365daf920d512a7aef14f0e2b21c26caab8896f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH v20241113 01/20] Enhancing catalog for support session
 variables and related support

 

 

 
   <para>

 

 
+		case OBJECT_VARIABLE:
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*

 
 				if (objname)

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 
 
index cbbe8acd382..41f1121d19f 100644
@@ -6566,6 +6566,9 @@

 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du

From f3a6ba7ebc8c5c5e47a0df93ac76505a70d686ff Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH v20241113 02/20] Storage for session variables and SQL
 interface

 
+     <row>
 
+   <para>

 

 
+/*

 
+	/*

 
 						/*
index 5ca856fd279..21e938a6227 100644
@@ -195,6 +197,61 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 
+	/*
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*

 

 
 
 			/*

 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*

From f62ecfa275b3debf9a781889f5cc2d7a4eec02f7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH v20241113 03/20] function pg_session_variables for cleaning
 tests

 
 	PopActiveSnapshot();
 }
+
+/*
index 41f1121d19f..e943a74846f 100644
@@ -12405,4 +12405,12 @@

From 159735e9779ad8c9ae08986201b043de54cb62f9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH v20241113 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+

From 2654afbbcc33e5b4de5f826fdc867325ad19654a Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 09:31:28 +0100
Subject: [PATCH v20241113 05/20] memory cleaning after DROP VARIABLE

Accepting a sinval message invalidates entries in the "sessionvars" hash table.
These entries are validated before any read or write operations on session variables.
When the entry cannot be validated, it is removed.  Removal will be delayed when
the variable was dropped by the current transaction, which could still be rolled back.
---
 src/backend/catalog/pg_variable.c             |   4 +
 src/backend/commands/session_variable.c       | 153 +++++++++++-
 src/include/commands/session_variable.h       |   2 +
 .../isolation/expected/session-variable.out   | 110 +++++++++
 src/test/isolation/isolation_schedule         |   1 +
 .../isolation/specs/session-variable.spec     |  50 ++++
 .../regress/expected/session_variables.out    | 217 ++++++++++++++++++
 src/test/regress/sql/session_variables.sql    | 120 ++++++++++
 8 files changed, 653 insertions(+), 4 deletions(-)
 create mode 100644 src/test/isolation/expected/session-variable.out
 create mode 100644 src/test/isolation/specs/session-variable.spec

index ef89594a268..c7369e2b2ac 100644
 
 	table_close(rel, RowExclusiveLock);
+
+	/* do the necessary cleanup in local memory, if needed */
+	SessionVariableDropPostprocess(varid);
 }
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 668b8189a6c..21c7a9517b5 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"

+	/*
+	 * Top level local transaction id of the last transaction that dropped the
+	 * variable, if any.  We need this information to avoid freeing memory for
+	 * variables dropped by the local backend, in case the operation is rolled
+	 * back.
+	 */
+	LocalTransactionId drop_lxid;
+
 	/*
 	 * Stored value and type description can be outdated when we receive a
 	 * sinval message.  We then have to check if the stored data are still
@@ -83,6 +92,17 @@ static HTAB *sessionvars = NULL;	/* hash table for session variables */

+/* becomes true when we receive a sinval message */
+static bool needs_validation = false;
+
+/*
+ * The content of dropped session variables is not removed immediately.  We do
+ * that in the next transaction that reads or writes a session variable.
+ * "validated_lxid" stores the transaction that performed said validation, so
+ * that we can avoid repeating the effort.
+ */
+static LocalTransactionId validated_lxid = InvalidLocalTransactionId;
+
 /*
  * Callback function for session variable invalidation.
  */
@@ -114,6 +134,38 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
 		if (hashvalue == 0 || svar->hashvalue == hashvalue)
 		{
 			svar->is_valid = false;
+			needs_validation = true;
+		}
+	}
+}
+
+/*
+ * Handle the local memory cleanup for a DROP VARIABLE command.
+ *
+ * Caller should take care of removing the pg_variable entry first.
+ */
+void
+SessionVariableDropPostprocess(Oid varid)
+{
+	Assert(LocalTransactionIdIsValid(MyProc->vxid.lxid));
+
+	if (sessionvars)
+	{
+		bool		found;
+		SVariable	svar = (SVariable) hash_search(sessionvars, &varid,
+												   HASH_FIND, &found);
+
+		if (found)
+		{
+			/*
+			 * Save the current top level local transaction id to make sure we
+			 * won't automatically remove the local variable storage in
+			 * validate_all_session_variables() when the invalidation message
+			 * from DROP VARIABLE arrives.  After all, the transaction could
+			 * still be rolled back.
+			 */
+			svar->is_valid = false;
+			svar->drop_lxid = MyProc->vxid.lxid;
 		}
 	}
 }
@@ -167,6 +219,67 @@ is_session_variable_valid(SVariable svar)
 	return result;
 }
 
+/*
+ * Check all potentially invalid session variable data in local memory and free
+ * the memory for all invalid ones.  This function is called before any read or
+ * write of a session variable.  Freeing of a variable's memory is postponed if
+ * the variable has been dropped by the current transaction, since that
+ * operation could still be rolled back.
+ *
+ * It is possible that we receive a cache invalidation message while
+ * remove_invalid_session_variables() is executing, so we cannot guarantee that
+ * all entries in "sessionvars" will be set to "is_valid" after the function is
+ * done.  However, we can guarantee that all entries get checked once.
+ */
+static void
+remove_invalid_session_variables(void)
+{
+	HASH_SEQ_STATUS status;
+	SVariable	svar;
+
+	/*
+	 * The validation requires system catalog access, so the session state
+	 * should be "in transaction".
+	 */
+	Assert(IsTransactionState());
+
+	if (!needs_validation || !sessionvars)
+		return;
+
+	/*
+	 * Reset the flag before we start the validation.  It can be set again
+	 * by concurrently incoming sinval messages.
+	 */
+	needs_validation = false;
+
+	elog(DEBUG1, "effective call of validate_all_session_variables()");
+
+	hash_seq_init(&status, sessionvars);
+	while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+	{
+		if (!svar->is_valid)
+		{
+			if (svar->drop_lxid == MyProc->vxid.lxid)
+			{
+				/* try again in the next transaction */
+				needs_validation = true;
+				continue;
+			}
+
+			if (!is_session_variable_valid(svar))
+			{
+				Oid			varid = svar->varid;
+
+				free_session_variable_value(svar);
+				hash_search(sessionvars, &varid, HASH_REMOVE, NULL);
+				svar = NULL;
+			}
+			else
+				svar->is_valid = true;
+		}
+	}
+}
+
 /*
  * Initialize attributes cached in "svar"
  */
@@ -196,6 +309,8 @@ setup_session_variable(SVariable svar, Oid varid)
 	svar->domain_check_extra = NULL;
 	svar->domain_check_extra_lxid = InvalidLocalTransactionId;

@@ -319,22 +434,42 @@ get_session_variable(Oid varid)
 	if (!sessionvars)
 		create_sessionvars_hashtables();
 
+	if (validated_lxid == InvalidLocalTransactionId ||
+		validated_lxid != MyProc->vxid.lxid)
+	{
+		/* free the memory from dropped session variables */
+		remove_invalid_session_variables();
+
+		/* don't repeat the above step in the same transaction */
+		validated_lxid = MyProc->vxid.lxid;
+	}
+
 	svar = (SVariable) hash_search(sessionvars, &varid,
 								   HASH_ENTER, &found);
 
 	if (found)
 	{
+		/*
+		 * The session variable could have been dropped by a DROP VARIABLE
+		 * statement in a subtransaction that was later rolled back, which
+		 * means that we may have to work with the data of a variable marked
+		 * as invalid.
+		 */
 		if (!svar->is_valid)
 		{
 			/*
-			 * If there was an invalidation message, the variable might still be
-			 * valid, but we have to check with the system catalog.
+			 * We have to check the system catalog to see if the variable is
+			 * still valid, even if an invalidation message set it to invalid.
+			 *
+			 * The variable must be validated before it is accessed.  The oid
+			 * should be valid, because the related session variable is already
+			 * locked, and remove_invalid_session_variables() would remove
+			 * variables dropped by other transactions.
 			 */
 			if (is_session_variable_valid(svar))
 				svar->is_valid = true;
 			else
-				/* if the value cannot be validated, we have to discard it */
-				free_session_variable_value(svar);
+				elog(ERROR, "unexpected state of session variable %u", varid);
 		}
 	}
 	else
@@ -395,6 +530,16 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 	if (!sessionvars)
 		create_sessionvars_hashtables();
 
+	if (validated_lxid == InvalidLocalTransactionId ||
+		validated_lxid != MyProc->vxid.lxid)
+	{
+		/* free the memory from dropped session variables */
+		remove_invalid_session_variables();
+
+		/* don't repeat the above step in the same transaction */
+		validated_lxid = MyProc->vxid.lxid;
+	}
+
 	svar = (SVariable) hash_search(sessionvars, &varid,
 								   HASH_ENTER, &found);

index 2c4760fb687..85771032de3 100644
@@ -1044,3 +1044,220 @@ SELECT count(*) FROM pg_session_variables();
      0
 (1 row)
 
+-- dropped variables should be removed from memory before the next usage
+-- of any session variable in the next transaction
+LET var1 = 'Ahoj';
+SELECT name, typname, can_select, can_update FROM pg_session_variables();
+ name |      typname      | can_select | can_update 
+------+-------------------+------------+------------
+ var1 | character varying | t          | t
+(1 row)
+
+DROP VARIABLE var1;
+-- should be zero
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+ count 
+-------
+     0
+(1 row)
+
+-- the content of the value should be preserved when a variable is dropped
+-- by an aborted transaction
+CREATE VARIABLE var1 AS varchar;
+LET var1 = 'Ahoj';
+BEGIN;
+DROP VARIABLE var1;
+-- should fail
+SELECT var1;
+ERROR:  column "var1" does not exist
+LINE 1: SELECT var1;
+               ^
+ROLLBACK;
+-- should be ok
+SELECT var1;
+ var1 
+------
+ Ahoj
+(1 row)
+
+-- another test
+BEGIN;
+DROP VARIABLE var1;
+CREATE VARIABLE var1 AS int;
+LET var1 = 100;
+-- should be ok, result 100
+SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+ROLLBACK;
+-- should be ok, result 'Ahoj'
+SELECT var1;
+ var1 
+------
+ Ahoj
+(1 row)
+
+DROP VARIABLE var1;
+-- should be zero
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+ count 
+-------
+     0
+(1 row)
+
+BEGIN;
+  CREATE VARIABLE var1 AS int;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+  SELECT name, typname, can_select, can_update FROM pg_session_variables();
+ name | typname | can_select | can_update 
+------+---------+------------+------------
+ var1 | integer | t          | t
+(1 row)
+
+  DROP VARIABLE var1;
+COMMIT;
+-- should be zero
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+ count 
+-------
+     0
+(1 row)
+
+BEGIN;
+  CREATE VARIABLE var1 AS int;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+  SELECT name, typname, can_select, can_update FROM pg_session_variables();
+ name | typname | can_select | can_update 
+------+---------+------------+------------
+ var1 | integer | t          | t
+(1 row)
+
+  DROP VARIABLE var1;
+COMMIT;
+-- should be zero
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+ count 
+-------
+     0
+(1 row)
+
+CREATE VARIABLE var1 AS int;
+CREATE VARIABLE var2 AS int;
+LET var1 = 10;
+LET var2 = 0;
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+  ROLLBACK TO s1;
+  SAVEPOINT s2;
+  DROP VARIABLE var1;
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+  ROLLBACK TO s2;
+COMMIT;
+-- should be ok
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+  ROLLBACK TO s1;
+  SAVEPOINT s2;
+  DROP VARIABLE var1;
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+ROLLBACK;
+-- should be ok
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+  SAVEPOINT s2;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+  ROLLBACK TO s1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+ var2 
+------
+    0
+(1 row)
+
+COMMIT;
+-- should be ok
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+-- repeated aborted transaction
+BEGIN; DROP VARIABLE var1; ROLLBACK;
+BEGIN; DROP VARIABLE var1; ROLLBACK;
+BEGIN; DROP VARIABLE var1; ROLLBACK;
+-- should be ok
+SELECT var1;
+ var1 
+------
+   10
+(1 row)
+
+DROP VARIABLE var1, var2;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 979b9029e58..d8397573eeb 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -734,3 +734,123 @@ DISCARD VARIABLES;
 
 -- should be zero again
 SELECT count(*) FROM pg_session_variables();
+
+-- dropped variables should be removed from memory before the next usage
+-- of any session variable in the next transaction
+
+LET var1 = 'Ahoj';
+SELECT name, typname, can_select, can_update FROM pg_session_variables();
+DROP VARIABLE var1;
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+
+-- the content of the value should be preserved when a variable is dropped
+-- by an aborted transaction
+CREATE VARIABLE var1 AS varchar;
+LET var1 = 'Ahoj';
+BEGIN;
+DROP VARIABLE var1;
+
+-- should fail
+SELECT var1;
+
+ROLLBACK;
+
+-- should be ok
+SELECT var1;
+
+-- another test
+BEGIN;
+DROP VARIABLE var1;
+CREATE VARIABLE var1 AS int;
+LET var1 = 100;
+-- should be ok, result 100
+SELECT var1;
+ROLLBACK;
+-- should be ok, result 'Ahoj'
+SELECT var1;
+
+DROP VARIABLE var1;
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+
+BEGIN;
+  CREATE VARIABLE var1 AS int;
+  LET var1 = 100;
+  SELECT var1;
+  SELECT name, typname, can_select, can_update FROM pg_session_variables();
+  DROP VARIABLE var1;
+COMMIT;
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+
+BEGIN;
+  CREATE VARIABLE var1 AS int;
+  LET var1 = 100;
+  SELECT var1;
+  SELECT name, typname, can_select, can_update FROM pg_session_variables();
+  DROP VARIABLE var1;
+COMMIT;
+
+-- should be zero
+SELECT count(*) FROM pg_session_variables() WHERE NOT removed;
+
+CREATE VARIABLE var1 AS int;
+CREATE VARIABLE var2 AS int;
+LET var1 = 10;
+LET var2 = 0;
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+  ROLLBACK TO s1;
+  SAVEPOINT s2;
+  DROP VARIABLE var1;
+  SELECT var2;
+  ROLLBACK TO s2;
+COMMIT;
+-- should be ok
+SELECT var1;
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+  ROLLBACK TO s1;
+  SAVEPOINT s2;
+  DROP VARIABLE var1;
+  SELECT var2;
+ROLLBACK;
+-- should be ok
+SELECT var1;
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE var1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+
+  SAVEPOINT s2;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+  ROLLBACK TO s1;
+  -- force cleaning by touching another session variable
+  SELECT var2;
+COMMIT;
+-- should be ok
+SELECT var1;
+
+-- repeated aborted transaction
+BEGIN; DROP VARIABLE var1; ROLLBACK;
+BEGIN; DROP VARIABLE var1; ROLLBACK;
+BEGIN; DROP VARIABLE var1; ROLLBACK;
+
+-- should be ok
+SELECT var1;
+
+DROP VARIABLE var1, var2;
-- 
2.47.0

From 50caf27d3f787ec32677597df8e4013b70f468c4 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 14:06:06 +0100
Subject: [PATCH v20241113 06/20] plpgsql tests

---

index 00000000000..ab779900596
+-- execute in a transaction
+-- execute outside of a transaction
+-- execute inside a PL/pgSQL block
+-- should work again
+-- should fail, but not crash
index 00000000000..a3cc264cf38
+-- execute in a transaction
+-- execute outside of a transaction
+-- execute inside a PL/pgSQL block
+-- should work again
+-- should fail, but not crash

From c50bcd326fd7e3bf72dc673f48e18c01b1904adb Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH v20241113 07/20] GUC session_variables_ambiguity_warning

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index a84e60c09b9..d1b2175c022 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10740,6 +10740,66 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>

  <sect1 id="ddl-others">
 
 /* GUC parameters */

 /* GUC parameters */

index ab779900596..a6523f7afe2 100644
index a3cc264cf38..9b8e90e81fa 100644

 -- the value should not be corrupted
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 85771032de3..50fb478b5e9 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -1261,3 +1261,25 @@ SELECT var1;
 (1 row)
 
index d8397573eeb..e7a4fdedae4 100644
@@ -854,3 +854,23 @@ BEGIN; DROP VARIABLE var1; ROLLBACK;
 SELECT var1;

From 4f81575c8b68c83d18ab65fcca320602435af6d1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH v20241113 08/20] EXPLAIN LET support

 

 
 
index 50fb478b5e9..a867fdd3e75 100644
@@ -1283,3 +1283,43 @@ SET session_variables_ambiguity_warning TO off;
index e7a4fdedae4..8a6dbffd54e 100644
@@ -874,3 +874,24 @@ SET session_variables_ambiguity_warning TO off;
 
 DROP TABLE public.xxtab;

From 11e5ac4b1a87e5083a422db94e8fdceeb42886ef Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH v20241113 09/20] PREPARE LET support

 

-/* LET */
index a867fdd3e75..cc4b5964799 100644
@@ -1321,5 +1321,82 @@ SELECT var1;
    10
 (1 row)
 
index 8a6dbffd54e..65c2a79425d 100644
@@ -893,5 +893,62 @@ EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE, SUMMARY OFF) LET var1 = (SELECT count(*
 -- should be 10
 SELECT var1;

From a3d75a01384318e1ee95203c19114c78ffd88baf Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:00:28 +0100
Subject: [PATCH v20241113 10/20] implementation of temporary session variables

 src/backend/access/transam/xact.c             |   9 +
 src/backend/catalog/pg_variable.c             |  24 +-
 src/backend/commands/session_variable.c       | 218 +++++++++++++++++-
 .../regress/expected/session_variables.out    | 130 ++++++++++-
 23 files changed, 546 insertions(+), 70 deletions(-)

+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

index b7ebcc2a557..01e3d469306 100644

@@ -2912,6 +2916,7 @@ AbortTransaction(void)
 	AtAbort_Portals();
@@ -5190,6 +5195,8 @@ CommitSubTransaction(void)
@@ -5355,6 +5362,8 @@ AbortSubTransaction(void)
index c7369e2b2ac..4520eed6da8 100644

 /*

 

 

 
 
 	ReleaseSysCache(tup);
 
 	/* do the necessary cleanup in local memory, if needed */
-	SessionVariableDropPostprocess(varid);
+	SessionVariableDropPostprocess(varid, XactEndAction);
 }
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 21c7a9517b5..b23ae21ba55 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -16,6 +16,8 @@
 
 #include "access/xact.h"
 
@@ -96,13 +111,21 @@ static MemoryContext SVariableMemoryContext = NULL;
 
 /*
- * The content of dropped session variables is not removed immediately.  We do
- * that in the next transaction that reads or writes a session variable.
- * "validated_lxid" stores the transaction that performed said validation, so
- * that we can avoid repeating the effort.
+ * The content of dropped session variables is not removed immediately.  If
+ * possible, we do that at the end of the transaction.  But we cannot do that
+ * if the transaction aborted, because we lost access to the system catalog.
+ * In that case, we clean up in the next transaction that reads or writes a
+ * session variable.  "validated_lxid" stores the transaction that performed
+ * said validation, so that we can avoid repeating the effort.
  */
 static LocalTransactionId validated_lxid = InvalidLocalTransactionId;
 
@@ -139,16 +162,45 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
 	}
 }
 
+/*
 
+	if (XactEndAction == VARIABLE_XACTEND_DROP)
+	{
+		Assert(isTempNamespace(get_session_variable_namespace(varid)));
+
+		unregister_session_variable_xact_drop(varid);
+	}
+
 	if (sessionvars)
 	{
 		bool		found;
@@ -170,6 +222,57 @@ SessionVariableDropPostprocess(Oid varid)
 	}
 }
 
+/*
+ * Registration of actions to be executed on session variables at transaction
+ * end time. We want to drop temporary session variables with clause ON COMMIT
+ * DROP.
+ */
+
+/*
+ * Register a session variable xact action.
+ */
+static void
+register_session_variable_xact_drop(Oid varid)
+{
+	SVariableXActDropItem *xact_ai;
+	MemoryContext oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+	xact_ai = (SVariableXActDropItem *)
+		palloc(sizeof(SVariableXActDropItem));
+
+	xact_ai->varid = varid;
+
+	xact_ai->creating_subid = GetCurrentSubTransactionId();
+	xact_ai->deleting_subid = InvalidSubTransactionId;
+
+	xact_drop_items = lcons(xact_ai, xact_drop_items);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Unregister an id of a given session variable from drop list. In this
+ * moment, the action is just marked as deleted by setting deleting_subid. The
+ * calling even might be rollbacked, in which case we should not lose this
+ * action.
+ */
+static void
+unregister_session_variable_xact_drop(Oid varid)
+{
+	ListCell   *l;
+
+	foreach(l, xact_drop_items)
+	{
+		SVariableXActDropItem *xact_ai =
+			(SVariableXActDropItem *) lfirst(l);
+
+		if (xact_ai->varid == varid)
+			xact_ai->deleting_subid = GetCurrentSubTransactionId();
+	}
+}
+
 /*
  * Release stored value, free memory
  */
@@ -222,9 +325,11 @@ is_session_variable_valid(SVariable svar)
 /*
  * Check all potentially invalid session variable data in local memory and free
  * the memory for all invalid ones.  This function is called before any read or
- * write of a session variable.  Freeing of a variable's memory is postponed if
- * the variable has been dropped by the current transaction, since that
- * operation could still be rolled back.
+ * write of a session variable or when the transaction ends.  At the end of a
+ * transaction (atEOX is true) we can discard all invalid variables.  Inside a
+ * transaction (atEOX is false) we postpone freeing a variable's memory if the
+ * variable has been dropped by the current transaction, since that operation
+ * could still be rolled back.
  *
  * It is possible that we receive a cache invalidation message while
  * remove_invalid_session_variables() is executing, so we cannot guarantee that
@@ -232,7 +337,7 @@ is_session_variable_valid(SVariable svar)
  * done.  However, we can guarantee that all entries get checked once.
  */
 static void
-remove_invalid_session_variables(void)
+remove_invalid_session_variables(bool atEOX)
 {
 	HASH_SEQ_STATUS status;
 	SVariable	svar;
@@ -259,7 +364,7 @@ remove_invalid_session_variables(void)
 	{
 		if (!svar->is_valid)
 		{
-			if (svar->drop_lxid == MyProc->vxid.lxid)
+			if (!atEOX && svar->drop_lxid == MyProc->vxid.lxid)
 			{
 				/* try again in the next transaction */
 				needs_validation = true;
@@ -280,6 +385,95 @@ remove_invalid_session_variables(void)
 	}
 }
 
+/*
@@ -438,7 +632,7 @@ get_session_variable(Oid varid)
 		/* free the memory from dropped session variables */
 
 		/* don't repeat the above step in the same transaction */
 		validated_lxid = MyProc->vxid.lxid;
@@ -534,7 +728,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 		validated_lxid != MyProc->vxid.lxid)
 	{
 		/* free the memory from dropped session variables */
-		remove_invalid_session_variables();
+		remove_invalid_session_variables(false);
 
 		/* don't repeat the above step in the same transaction */
 
 		/*

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 
 
index cc4b5964799..6dadb9074da 100644
 
 DROP VARIABLE svartest.var1;
@@ -1400,3 +1400,123 @@ SELECT var1;
 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables
index 65c2a79425d..770ce650685 100644
@@ -826,6 +826,7 @@ BEGIN;
   DROP VARIABLE var1;
   SELECT var2;
 ROLLBACK;
+
 -- should be ok
 SELECT var1;
 
@@ -952,3 +953,68 @@ SELECT var1;
 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables

From bf9162bb0032389ff47dde89e850fe753d1883e8 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:02:13 +0100
Subject: [PATCH v20241113 11/20] Implementation ON TRANSACTION END RESET
 clause

 

index b23ae21ba55..0b512815d4a 100644
 
+	bool		reset_at_eox;
+
 	/*
 	 * Top level local transaction id of the last transaction that dropped the
 	 * variable, if any.  We need this information to avoid freeing memory for
@@ -110,6 +112,12 @@ static MemoryContext SVariableMemoryContext = NULL;
 /* becomes true when we receive a sinval message */
 static bool needs_validation = false;
 
+/*
+ * true, when some used session variable has ON COMMIT DROP
+ * or ON TRANSACTION END RESET clauses
+ */
+static bool has_session_variables_with_reset_at_eox = false;
+
 /*
  * The content of dropped session variables is not removed immediately.  If
  * possible, we do that at the end of the transaction.  But we cannot do that
@@ -385,6 +393,32 @@ remove_invalid_session_variables(bool atEOX)
 	}
 }
 
+/*
@@ -392,6 +426,8 @@ remove_invalid_session_variables(bool atEOX)
@@ -503,6 +539,21 @@ setup_session_variable(SVariable svar, Oid varid)
 
+	/*
 
 	svar->isnull = true;

 

 

 

 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4
index 6dadb9074da..99e4ac72be1 100644
@@ -1520,3 +1520,37 @@ SELECT count(*) FROM pg_session_variables();
      0
 (1 row)
 
+CREATE VARIABLE var1 AS int ON TRANSACTION END RESET;
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+COMMIT;
+-- should be NULL;
+SELECT var1 IS NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+ROLLBACK;
+-- should be NULL
+SELECT var1 IS NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP VARIABLE var1;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 770ce650685..7853261080b 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -1018,3 +1018,23 @@ COMMIT;
 SELECT count(*) FROM pg_variable WHERE varname = 'var1';
 -- should be zero
 SELECT count(*) FROM pg_session_variables();
+
+CREATE VARIABLE var1 AS int ON TRANSACTION END RESET;
+
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+COMMIT;
+
+-- should be NULL;
+SELECT var1 IS NULL;
+
+BEGIN;
+  LET var1 = 100;
+  SELECT var1;
+ROLLBACK;
+
+-- should be NULL
+SELECT var1 IS NULL;
+
+DROP VARIABLE var1;
-- 
2.47.0

From 14ad2c9f948a058db734a4ee9fd52eaf5b934e60 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:03:53 +0100
Subject: [PATCH v20241113 12/20] Implementation of DEFAULT clause - default
 expressions for session variables

 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>
 
index 4520eed6da8..36de12587c0 100644

+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{
 
index 0b512815d4a..0a1c326e72e 100644
@@ -510,11 +511,68 @@ AtEOSubXact_SessionVariables(bool isCommit,
 	}
 }
 
+/*
@@ -564,6 +622,9 @@ setup_session_variable(SVariable svar, Oid varid)
 
+	if (!is_write)
 
@@ -593,7 +654,7 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	 */
 	if (!svar->is_valid)
 	{
@@ -726,7 +787,25 @@ get_session_variable(Oid varid)
 
 		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by READ)",
 			 get_namespace_name(get_session_variable_namespace(varid)),
@@ -790,7 +869,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 
 	if (!found)
 	{

 
 CreateSessionVarStmt:

 			if (isAgg)

 

 
 
+	if (vardefexpr)

 
 
 	if (verbose)

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 
 
index 99e4ac72be1..ee153f8fb82 100644
 
 DROP VARIABLE svartest.var1;
@@ -1554,3 +1554,66 @@ SELECT var1 IS NULL;
 (1 row)
 
 DROP VARIABLE var1;
index 7853261080b..9d9d04ec76b 100644
@@ -1038,3 +1038,51 @@ ROLLBACK;
 SELECT var1 IS NULL;
 
 DROP VARIABLE var1;
+

From 5c63c3bdc9ef7124ee838771c5297367d09241f1 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:05:06 +0100
Subject: [PATCH v20241113 13/20] Implementation of NOT NULL and IMMUTABLE
 clauses

 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

index 36de12587c0..f261e3fd770 100644

 	if (varDefexpr)
 
index 0a1c326e72e..7f34f93b542 100644
 
+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 typedef SVariableData *SVariable;
@@ -563,6 +569,23 @@ eval_assign_defexpr(SVariable svar, HeapTuple tup)

 		FreeExecutorState(estate);
 	}
@@ -593,6 +616,9 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)

@@ -622,9 +648,31 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 
-	if (!is_write)
 
+		/*
 
@@ -644,6 +692,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	Assert(svar);
 	Assert(!isnull || value == (Datum) 0);
 
@@ -684,6 +747,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*

 /*
 
 CreateSessionVarStmt:

 

 

 
+	if (varnotnull)
 
 /*

 

+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 
 
index ee153f8fb82..a8520e5579d 100644
 
 DROP VARIABLE svartest.var1;
@@ -1617,3 +1617,148 @@ SELECT var1;
 
 DROP VARIABLE var1;
index 9d9d04ec76b..ac8e2cb0943 100644
@@ -1086,3 +1086,125 @@ SELECT var1;
 
 DROP VARIABLE var1;

From 486d023c9a4223d2ae0d7988bb7e4cab77b1db8d Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:07:03 +0100
Subject: [PATCH v20241113 14/20] allow read an value of session variable
 directly from expression executor

 
-	/*

 

 
-	/*

 
 
index a6523f7afe2..ecac64fcfb4 100644
 
 	/*
index a8520e5579d..2f1708f7c84 100644
index ac8e2cb0943..fcd783e966c 100644

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.47.0

From e8f178719805dd09845c4b8b560894780871f845 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:08:17 +0100
Subject: [PATCH v20241113 15/20] allow parallel execution queries with session
 variables

---

index 21e938a6227..783716c4e57 100644
@@ -212,7 +212,19 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)

 

 

 	/*

index 2f1708f7c84..8df3a91c05e 100644

From b11a6d0cc3da7d163540bc3bd1065244da258872 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH v20241113 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*

 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */

From c5f3065f5c3278ed5cd87339af69ed1e24317f20 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH v20241113 17/20] expression with session variables can be
 inlined

 

 
index 8df3a91c05e..96283ed0b9a 100644
 
 -- inlining is blocked

From 501b865dcf20e18e2d1793a94ded5cb1272bc2ed Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:10:39 +0100
Subject: [PATCH v20241113 18/20] this patch changes error message "column
 doesn't exist" to message "column or variable doesn't exist"

 
+/*

index ecac64fcfb4..8bd2b398bdd 100644
 -- should fail, but not crash

 

 
 select a from atacc1;

 
index 3014d047fef..7378f95b17d 100644
index 96283ed0b9a..5a66fc9ffab 100644
 
@@ -1070,7 +1070,7 @@ BEGIN;

From 4f4b6a7f145cb78b3513c255356049bd0d25546e Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:11:33 +0100
Subject: [PATCH v20241113 19/20] transactional variables

 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>
index f261e3fd770..771c2816048 100644

 	if (varDefexpr)
 
index 7f34f93b542..978c84c9bdb 100644
 
+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

+/* true, when transactional variables was modified */
+static bool has_modified_transactional_variables = false;
+
 /*
  * The content of dropped session variables is not removed immediately.  If
  * possible, we do that at the end of the transaction.  But we cannot do that
@@ -140,6 +176,7 @@ static List *xact_drop_items = NIL;

 /*
@@ -292,8 +329,25 @@ unregister_session_variable_xact_drop(Oid varid)
@@ -390,7 +444,7 @@ remove_invalid_session_variables(bool atEOX)
 			{
 				Oid			varid = svar->varid;
 
@@ -426,6 +480,94 @@ remove_session_variables_with_reset_at_eox(void)
 
+/*
@@ -473,6 +615,8 @@ AtPreEOXact_SessionVariables(bool isCommit)
 
+	remove_prev_values_at_eox(isCommit);
+
 	/*
 	 * We have to clean xact_drop_items. All related variables are dropped
 	 * now, or lost inside aborted transaction.
@@ -618,6 +762,7 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)

 	svar->is_domain = (get_typtype(varform->vartype) == TYPTYPE_DOMAIN);
 	svar->domain_check_extra = NULL;
@@ -643,6 +788,17 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	svar->isnull = true;
 	svar->value = (Datum) 0;
 
+	if (svar->is_transact)
+	{
 
 	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
@@ -676,6 +832,69 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write)
 	ReleaseSysCache(tup);
 }
 
+/*
@@ -688,6 +907,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	Assert(svar);
 	Assert(!isnull || value == (Datum) 0);
@@ -723,6 +946,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	else
 		_svar = svar;
 
@@ -734,7 +972,37 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	else
 		newval = value;

 	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new value",
 		 get_namespace_name(get_session_variable_namespace(svar->varid)),
@@ -747,6 +1015,9 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 
 	svar->value = newval;
 	svar->isnull = isnull;
 
 	/* don't allow more changes of value when variable is IMMUTABLE */
 	if (svar->is_immutable)
@@ -848,6 +1119,8 @@ get_session_variable(Oid varid)
 	else
 		svar->is_valid = false;
 
+reinit:
+
 	/*
 	 * Force setup for not yet initialized variables or variables that cannot
 	 * be validated.
@@ -880,6 +1153,28 @@ get_session_variable(Oid varid)
 			 varid);
 	}
 
+	/*

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:

 

 
 
 /*

 

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 
 
index 5a66fc9ffab..3e6b73684fd 100644
 
 DROP VARIABLE svartest.var1;
@@ -1763,3 +1763,103 @@ SELECT var1;
 LET var1 = 30;
 ERROR:  session variable "public.var1" is declared IMMUTABLE
 DROP VARIABLE var1;
+-- test transactional variables
+CREATE TRANSACTION VARIABLE tv AS int DEFAULT 0;
+BEGIN;
+  LET tv = 100;
+  SELECT tv;
+ tv  
+-----
+ 100
+(1 row)
+
+ROLLBACK;
+SELECT tv;
+ tv 
+----
+  0
+(1 row)
+
+LET tv = 100;
+BEGIN;
+  LET tv = 1000;
+COMMIT;
+SELECT tv;
+  tv  
+------
+ 1000
+(1 row)
+
+BEGIN;
+  LET tv = 0;
+  SELECT tv;
+ tv 
+----
+  0
+(1 row)
+
+ROLLBACK;
+SELECT tv;
+  tv  
+------
+ 1000
+(1 row)
+
+-- test subtransactions
+BEGIN;
+  LET tv = 1;
+SAVEPOINT x1;
+  LET tv = 2;
+SAVEPOINT x2;
+  LET tv = 3;
+ROLLBACK TO x2;
+  SELECT tv;
+ tv 
+----
+  2
+(1 row)
+
+  LET tv = 10;
+ROLLBACK TO x1;
+  SELECT tv;
+ tv 
+----
+  1
+(1 row)
+
+ROLLBACK;
+SELECT tv;
+  tv  
+------
+ 1000
+(1 row)
+
+BEGIN;
+  LET tv = 1;
+SAVEPOINT x1;
+  LET tv = 2;
+SAVEPOINT x2;
+  LET tv = 3;
+ROLLBACK TO x2;
+  SELECT tv;
+ tv 
+----
+  2
+(1 row)
+
+  LET tv = 10;
+ROLLBACK TO x1;
+  SELECT tv;
+ tv 
+----
+  1
+(1 row)
+
+COMMIT;
+SELECT tv;
+ tv 
+----
+  1
+(1 row)
+
+DROP VARIABLE tv;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index fcd783e966c..68fc0cde44a 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -1208,3 +1208,61 @@ SELECT var1;
 LET var1 = 30;
 
 DROP VARIABLE var1;
+

From afaca51817a974cc2481466be095de785349d48d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH v20241113 20/20] pg_restore -A, --variable

 
+     <varlistentry>

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 13 Nov 2024 16:06:37 +0100

> You probably forgot to move that to the patch for temporary variables.

+1

> I have moved the reference to the transaction end to the temporary variable

+1

> I hope I got everything right.  Attached is an updated patch set.

Thank you

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 16 Nov 2024 16:41:10 +0100

Hi

rebase - based on Laurenz's patch set

Regards

Pavel
From bd1e819a60815ab281848b02a7fa4eb3f1e93d21 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 21 Jul 2024 17:04:41 +0200
Subject: [PATCH 20/20] pg_restore -A, --variable

 
+     <varlistentry>

 

From c4ef2396a728956448d16ff3b38ab4e1b12a34a3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 16/20] plpgsql implementation for LET statement

DO $$
BEGIN
  FOR i IN 1..10000
  LOOP
    LET svar = i;
  END LOOP;
END;
$$;

 
 
+	/*

 /*

 

+			case PLPGSQL_STMT_LET:
 
+/* ----------
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index 64ddf589b3c..fe60f2916f4 100644

From 9902e51166dc22e6fe7c49f22da80081ada058fd Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:11:33 +0100
Subject: [PATCH 19/20] transactional variables

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9a8886fc69a..1064b3749e3 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9859,6 +9859,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
 
  <refsynopsisdiv>
 <synopsis>
 
+  <para>

 	if (varDefexpr)

+/*
 
+	/*
 
 	bool		not_null;
 	bool		is_immutable;
+	bool		is_transact;
 
 	bool		reset_at_eox;

 
 
 /*

+/*

 
 
+	if (svar->is_transact)
+	{

+/*
 
 	Assert(svar);

 
 
 	svar->value = newval;
 	svar->isnull = isnull;

+reinit:
+
 	/*
 
+	/*

 /*

 
 CreateSessionVarStmt:
 
+OptTransactional:

 

 
 
 /*

 

 
 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                                         List of variables

 

 
 DROP VARIABLE var1;
+

From 1b0b8fdd0ef355f0c6929be9337abc29a5140588 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 20 Jan 2024 20:35:38 +0100
Subject: [PATCH 17/20] expression with session variables can be inlined

 

 
 
 -- inlining is blocked

From fef845561ff518819cc2ed793ceaddf73d325a05 Mon Sep 17 00:00:00 2001

 
+/*

 

 

 select a from atacc1;

 

From beeac82c466fc624ef4404d40cdfecef299bb4f3 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:08:17 +0100
Subject: [PATCH 15/20] allow parallel execution queries with session variables

---

 

 

 
 
 	/*

 

From 5d2261066c9c7afea613f4a1155bd8990650360e Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:07:03 +0100
Subject: [PATCH 14/20] allow read an value of session variable directly from
 expression executor

 
-	/*

 
 
index 9cec17544b8..e8cc54351ed 100644
@@ -1136,6 +1136,12 @@ llvm_compile_expr(ExprState *state)
 					break;
 				}

-	/*

 

 	/*

+LET v = 1;
+

 
 
--- should fail
+-- should be ok
 EXECUTE ptest(v);
 
 DEALLOCATE ptest;
-- 
2.47.0

From 5c390461bd0f37b08644e51f7348efb43802390c Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:05:06 +0100
Subject: [PATCH 13/20] Implementation of NOT NULL and IMMUTABLE clauses

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 124902c4e44..9a8886fc69a 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9842,6 +9842,26 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
 
      <row>
 
  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

 
 
 	if (varDefexpr)

+	bool		not_null;
+	bool		is_immutable;
+
 	bool		reset_at_eox;
 
 	/*

 

 
 
-	if (!is_write)
 
+		/*

 
 	svar->value = newval;
 	svar->isnull = isnull;
+
 
 /*

 /*
 
 CreateSessionVarStmt:

 

 

 
+	if (varnotnull)
 
 /*

 

+	/* don't allow NULL */

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                               List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;

From 06a360958048e3726b9ae3dafb99dd4dc5e51552 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:03:53 +0100
Subject: [PATCH 12/20] Implementation of DEFAULT clause - default expressions
 for session variables

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 02b6b27c5cb..124902c4e44 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9874,6 +9874,14 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
 
    <para>
 
    <para>
 
   <para>
 
   <para>

 
+	if (varDefexpr)

 
 
+	if (stmt->defexpr)
+	{

+/*
 
+	if (!is_write)

 
 	if (!found)
 	{

 
 CreateSessionVarStmt:

 			if (isAgg)

 

 
 
+	if (vardefexpr)

 
 
 	if (verbose)

 

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-                          List of variables

 

 
 DROP VARIABLE var1;
 
 DROP VARIABLE var1;
+

From 6318058b9c0b4fc4c862dc6ba1450b9e684f56e4 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:02:13 +0100
Subject: [PATCH 11/20] Implementation ON TRANSACTION END RESET clause

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 94db502b72e..02b6b27c5cb 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9846,7 +9846,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
       <para>
        Action performed at end of transaction:
-       <literal>n</literal> = no action, <literal>d</literal> = drop the variable.
+       <literal>n</literal> = no action, <literal>d</literal> = drop the variable,
+       <literal>r</literal> = reset the variable to its default value.
       </para></entry>
      </row>

 
 
+	bool		reset_at_eox;
+
 	/*
 
+/*
 
+/*
 
+	/*
 
 	svar->isnull = true;

 

 

 

 /* ----------------
 
 step sr1: ROLLBACK;

 
 session s4

From 806e3e9ecb7af018e2bcb0ac64edf265fd14e65f Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 15:00:28 +0100
Subject: [PATCH 10/20] implementation of temporary session variables

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0c15d56dd42..94db502b72e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9840,6 +9840,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
 
    <para>
 
  <refsynopsisdiv>
 <synopsis>

 

 /*

 

 

 
 
 	ReleaseSysCache(tup);

 #include "access/xact.h"

 /*

+/*

+/*
 
+/*

 
 		/*

 CreateSessionVarStmt:
 
+/*

 /*
 
 static bool

 

 	if (verbose)
 	{

 

 

 step sr1: ROLLBACK;

 
 DROP VARIABLE var1;
 
 \dV "no.such.variable"
-            List of variables

 

 
 DEALLOCATE p1;
 DROP VARIABLE var1;
+-- temporary variables

 DEALLOCATE p1;
 DROP VARIABLE var1;
+
+-- temporary variables
index 68e3c3c7b63..64ddf589b3c 100644
@@ -2798,6 +2798,7 @@ SupportRequestWFuncMonotonic
 SVariable
 SVariableData
 SVariableState
+SVariableXActDropItem
 Syn
 SyncOps
 SyncRepConfigData
-- 
2.47.0

From 4d5ae6a75ed9551d4cc453a92a8159c502f55662 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 09:44:58 +0100
Subject: [PATCH 09/20] PREPARE LET support

 

-/* LET */

From 24083147d1ee71f581b2f2d3d30cdac13c04c429 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 17:55:24 +0200
Subject: [PATCH 07/20] GUC session_variables_ambiguity_warning

 
 
  <sect1 id="ddl-others">
 
 /* GUC parameters */

 /* GUC parameters */

 

 

From b2e86dff80c6caeb9d4cb04ca0e281fe9f1f03a5 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 14:06:06 +0100
Subject: [PATCH 06/20] plpgsql tests

---

From 576d30997ecacbfcecba0f2c8f05409e7418a86c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 21 May 2024 18:28:07 +0200
Subject: [PATCH 08/20] EXPLAIN LET support

 

 

 DROP TABLE public.xxtab;

From 4a779d7f957b020dd4ef1b93fd75e8d3439e0db0 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 09:31:28 +0100
Subject: [PATCH 05/20] memory cleaning after DROP VARIABLE

 

+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

 -- should be zero again

From f137ecc3e35358ee42d0a4d61390b11bfdd7c04c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 28 Jan 2024 20:54:20 +0100
Subject: [PATCH 04/20] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 	return (Datum) 0;
 }
+
+/*

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 
 RESET search_path;
+

From bc77a7f8412e9eecfdc59f1e32ef45d7ec643b93 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 6 Jul 2023 08:29:21 +0200
Subject: [PATCH 02/20] Storage for session variables and SQL interface

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bdd2ca0152f..0c15d56dd42 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9785,6 +9785,19 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
 
+   <para>

 

 
+/*

 
+	/*

 
 						/*
 
+	/*
 
 	/*

+	/*

+/*
 
 /*

+/*
 
 			/*

 
 
 				/*

 
 
+		case T_LetStmt:
 
 			/*

 

 

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
+	/*
 
 	if (indirection)

 
+	/*

 

 
 
 			/*

 /* GUC parameters */

 /*
 
+/*

 	/*
 
+/*
 
 			/*
 
 			/*

 

 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 
+/*

 /*
 
 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
 
+/* LET */

 
+	/*

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 

 

 	/*
 
 	NodeTag		type;

 

 
 typedef struct Param

 

 /* ----------------
 
 	/*
index 9e4f3c1444d..68e3c3c7b63 100644
@@ -2603,6 +2604,7 @@ SerializedTransactionState
@@ -2793,6 +2795,9 @@ SupportRequestRows

From c2e163bda598854492b8a7ed7714d39cf0dd3212 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 03/20] function pg_session_variables for cleaning tests

 
 	PopActiveSnapshot();
 }
+
+/*

From 1e991e730522dca5ac8a4900c107b07b60123939 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 15:41:55 +0100
Subject: [PATCH 01/20] Enhancing catalog for support session variables and
 related support

 src/backend/catalog/aclchk.c                  |  78 ++++++
 65 files changed, 2365 insertions(+), 26 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 59bb833f48d..bdd2ca0152f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -369,6 +369,11 @@
       <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
       <entry>mappings of users to foreign servers</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+      <entry>session variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -9734,4 +9739,119 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </table>
  </sect1>

 

 
 
   <para>

 

index aaf96a965e4..3620eb356c6 100644
@@ -760,6 +771,19 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
 
 	return objects;
@@ -846,6 +870,32 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
@@ -1005,6 +1055,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
@@ -1196,6 +1250,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
 
+		case OBJECT_VARIABLE:
+			objtype = DEFACLOBJ_VARIABLE;
+			if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
+				this_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) iacls->objtype);
@@ -1439,6 +1499,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case DEFACLOBJ_NAMESPACE:
 				iacls.objtype = OBJECT_SCHEMA;
 				break;
+			case DEFACLOBJ_VARIABLE:
+				iacls.objtype = OBJECT_VARIABLE;
+				break;
 			default:
 				/* Shouldn't get here */
 				elog(ERROR, "unexpected default ACL type: %d",
@@ -1499,6 +1562,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case ParameterAclRelationId:
 				istmt.objtype = OBJECT_PARAMETER_ACL;
 				break;
+			case VariableRelationId:
+				istmt.objtype = OBJECT_VARIABLE;
+				break;
 			default:
 				elog(ERROR, "unexpected object class %u", classid);
 				break;
@@ -2732,6 +2798,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("permission denied for type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("permission denied for session variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("permission denied for view %s");
 						break;
@@ -2843,6 +2912,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("must be owner of type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("must be owner of session variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("must be owner of view %s");
 						break;
@@ -2991,6 +3063,8 @@ pg_aclmask(ObjectType objtype, Oid object_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return object_aclmask(TypeRelationId, object_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return object_aclmask(VariableRelationId, object_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) objtype);
@@ -4258,6 +4332,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 			defaclobjtype = DEFACLOBJ_NAMESPACE;
 			break;
 
+		case OBJECT_VARIABLE:

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
 
index 7520a982151..2c8b9d7fd3c 100644
 
 /*
 
@@ -1127,6 +1146,9 @@ get_object_address(ObjectType objtype, Node *object,
 
@@ -2005,16 +2027,20 @@ get_object_address_defacl(List *object, bool missing_ok)
 
 	/*
@@ -2098,6 +2124,24 @@ textarray_to_strvaluelist(ArrayType *arr)
 	return list;
 }
 
+/*
@@ -2292,6 +2336,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
@@ -2463,6 +2508,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
@@ -3451,6 +3497,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
@@ -3803,6 +3875,16 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
@@ -4619,6 +4701,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 
@@ -5725,6 +5811,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
@@ -5965,6 +6055,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
index a45f3bb6b83..d4d88d11314 100644
@@ -558,6 +564,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 				Oid			classId;
@@ -640,6 +647,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 
@@ -870,6 +878,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 		case OBJECT_TABLESPACE:
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSCONFIGURATION:
+		case OBJECT_VARIABLE:
 			{
 				ObjectAddress address;

 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*
index c632d1e8245..3deef69295d 100644

 

 
 
 /*

 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 	int			useDB;
 
+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

 

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

 

 

 
 
+

 
+\dV "no.such.variable"
+            List of variables

 
 # ----------

 

 
 -- check \drg and \du
index 08521d51a9b..9e4f3c1444d 100644
@@ -3079,6 +3082,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.47.0

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 6 Oct 2025 07:54:09 +0200

Hi

fresh rebase after 1a8b5b11e48a8fb086228542d1d4b379f23bdc1e

I wrote (with Jim Jones's big help) notes to proposed implementation of
session variables and to session variables topics generally.

https://wiki.postgresql.org/wiki/Implementation_of_declarative_catalog_session_variables

please, read it, and ask if there is something unclear or messy

Regards

Pavel
From f068a1de43ac0424b23fd8ea350809e12ee79865 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 14:06:06 +0100
Subject: [PATCH 15/15] plpgsql tests

set of plpgsql related tests:

* check session variables and plpgsql variables are not in collision ever
* check correct plpgsql plan cache invalidation when session variable is dropped
* check so the value of session variable is not corrupted, when the variable is
  modified inside nested called functions
---
 src/pl/plpgsql/src/Makefile                   |   3 +-
 .../src/expected/plpgsql_session_variable.out | 239 ++++++++++++++++++
 src/pl/plpgsql/src/meson.build                |   1 +
 .../src/sql/plpgsql_session_variable.sql      | 168 ++++++++++++
 4 files changed, 410 insertions(+), 1 deletion(-)
 create mode 100644 src/pl/plpgsql/src/expected/plpgsql_session_variable.out
 create mode 100644 src/pl/plpgsql/src/sql/plpgsql_session_variable.sql

 # where to find gen_keywordlist.pl and subsidiary files
 TOOLSDIR = $(top_srcdir)/src/tools
diff --git a/src/pl/plpgsql/src/expected/plpgsql_session_variable.out b/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
new file mode 100644
index 00000000000..ec5ffcb42b2
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
@@ -0,0 +1,239 @@
+-- variables are not checked in compile time
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func00_01()
+RETURNS void AS $$
+BEGIN
+  RAISE NOTICE '%', VARIABLE(this_variable_doesnt_exists);
+END;
+$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func00_02()
+RETURNS void AS $$
+BEGIN
+  LET this_variable_doesnt_exists = 10;
+END;
+$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func00_03()
+RETURNS void AS $$
+BEGIN
+  LET this_variable_doesnt_exists[10] = 'Hello';
+END;
+$$ LANGUAGE plpgsql;
+-- should fail
+SELECT svartest_plpgsql_func00_01();
+ERROR:  session variable "this_variable_doesnt_exists" doesn't exist
+LINE 1: VARIABLE(this_variable_doesnt_exists)
+                 ^
+QUERY:  VARIABLE(this_variable_doesnt_exists)
+CONTEXT:  PL/pgSQL function svartest_plpgsql_func00_01() line 3 at RAISE
+SELECT svartest_plpgsql_func00_02();
+ERROR:  session variable "this_variable_doesnt_exists" doesn't exist
+LINE 1: LET this_variable_doesnt_exists = 10
+            ^
+QUERY:  LET this_variable_doesnt_exists = 10
+CONTEXT:  PL/pgSQL function svartest_plpgsql_func00_02() line 3 at SQL statement
+SELECT svartest_plpgsql_func00_03();
+ERROR:  session variable "this_variable_doesnt_exists" doesn't exist
+LINE 1: LET this_variable_doesnt_exists[10] = 'Hello'
+            ^
+QUERY:  LET this_variable_doesnt_exists[10] = 'Hello'
+CONTEXT:  PL/pgSQL function svartest_plpgsql_func00_03() line 3 at SQL statement
+DROP FUNCTION svartest_plpgsql_func00_01();
+DROP FUNCTION svartest_plpgsql_func00_02();
+DROP FUNCTION svartest_plpgsql_func00_03();
+-- check of correct plan cache invalidation
+CREATE VARIABLE plpgsql_sesvar01 AS int;
+CREATE VARIABLE plpgsql_sesvar02 AS int[];
+-- plpgsql variables and session variables are not in collision ever
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func01_01()
+RETURNS void AS $$
+DECLARE plpgsql_sesvar01 int;
+BEGIN
+  plpgsql_sesvar01 := 100;
+  LET plpgsql_sesvar01 = 1000;
+  RAISE NOTICE 'plpgsql var: %, session var: %',
+    plpgsql_sesvar01, VARIABLE(plpgsql_sesvar01);
+END;
+$$ LANGUAGE plpgsql;
+SELECT svartest_plpgsql_func01_01();
+NOTICE:  plpgsql var: 100, session var: 1000
+ svartest_plpgsql_func01_01 
+----------------------------
+ 
+(1 row)
+
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func01_02()
+RETURNS void AS $$
+DECLARE __plpgsql_sesvar01 int;
+BEGIN
+  __plpgsql_sesvar01 := 100;
+  LET __plpgsql_sesvar01 = 1000;
+  RAISE NOTICE 'plpgsql var: %, session var: %',
+    __plpgsql_sesvar01, VARIABLE(__plpgsql_sesvar01);
+END;
+$$ LANGUAGE plpgsql;
+-- should fail
+SELECT svartest_plpgsql_func01_02();
+ERROR:  session variable "__plpgsql_sesvar01" doesn't exist
+LINE 1: LET __plpgsql_sesvar01 = 1000
+            ^
+QUERY:  LET __plpgsql_sesvar01 = 1000
+CONTEXT:  PL/pgSQL function svartest_plpgsql_func01_02() line 5 at SQL statement
+-- should fail
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func01_03()
+RETURNS void AS $$
+BEGIN
+  plpgsql_sesvar01 := 100;
+  LET plpgsql_sesvar01 = 1000;
+  RAISE NOTICE 'plpgsql var: %, session var: %',
+    plpgsql_sesvar01, VARIABLE(plpgsql_sesvar01);
+END;
+$$ LANGUAGE plpgsql;
+ERROR:  "plpgsql_sesvar01" is not a known variable
+LINE 4:   plpgsql_sesvar01 := 100;
+          ^
+DROP FUNCTION svartest_plpgsql_func01_01();
+DROP FUNCTION svartest_plpgsql_func01_02();
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func02()
+RETURNS void AS $$
+DECLARE v int[] DEFAULT '{}';
+BEGIN
+  LET plpgsql_sesvar01 = 1;
+  v[VARIABLE(plpgsql_sesvar01)] = 100;
+  RAISE NOTICE '%', v;
+  LET plpgsql_sesvar02 = v;
+  LET plpgsql_sesvar02[VARIABLE(plpgsql_sesvar01)] = -1;
+  RAISE NOTICE '%', VARIABLE(plpgsql_sesvar02);
+END;
+$$ LANGUAGE plpgsql;
+SELECT svartest_plpgsql_func02();
+NOTICE:  {100}
+NOTICE:  {-1}
+ svartest_plpgsql_func02 
+-------------------------
+ 
+(1 row)
+
+DROP VARIABLE plpgsql_sesvar01, plpgsql_sesvar02;
+CREATE VARIABLE plpgsql_sesvar01 AS int;
+CREATE VARIABLE plpgsql_sesvar02 AS int[];
+SELECT svartest_plpgsql_func02();
+NOTICE:  {100}
+NOTICE:  {-1}
+ svartest_plpgsql_func02 
+-------------------------
+ 
+(1 row)
+
+DROP FUNCTION svartest_plpgsql_func02();
+DROP VARIABLE plpgsql_sesvar01, plpgsql_sesvar02;
+-- returns updated value
+CREATE VARIABLE plpgsql_sesvar01 AS int;
+CREATE OR REPLACE FUNCTION svartest_plpgsql_inc(int)
+RETURNS int AS $$
+BEGIN
+  LET plpgsql_sesvar01 = COALESCE(VARIABLE(plpgsql_sesvar01) + $1, $1);
+  RETURN VARIABLE(plpgsql_sesvar01);
+END;
+$$ LANGUAGE plpgsql;
+SELECT svartest_plpgsql_inc(1);
+ svartest_plpgsql_inc 
+----------------------
+                    1
+(1 row)
+
+SELECT svartest_plpgsql_inc(1);
+ svartest_plpgsql_inc 
+----------------------
+                    2
+(1 row)
+
+SELECT svartest_plpgsql_inc(1);
+ svartest_plpgsql_inc 
+----------------------
+                    3
+(1 row)
+
+SELECT svartest_plpgsql_inc(1) FROM generate_series(1,10);
+ svartest_plpgsql_inc 
+----------------------
+                    4
+                    5
+                    6
+                    7
+                    8
+                    9
+                   10
+                   11
+                   12
+                   13
+(10 rows)
+
+CREATE VARIABLE plpgsql_sesvar02 AS numeric;
+LET plpgsql_sesvar02 = 0.0;
+CREATE OR REPLACE FUNCTION svartest_plpgsql_inc(numeric)
+RETURNS int AS $$
+BEGIN
+  LET plpgsql_sesvar02 = COALESCE(VARIABLE(plpgsql_sesvar02) + $1, $1);
+  RETURN VARIABLE(plpgsql_sesvar02);
+END;
+$$ LANGUAGE plpgsql;
+SELECT svartest_plpgsql_inc(1.0);
+ svartest_plpgsql_inc 
+----------------------
+                    1
+(1 row)
+
+SELECT svartest_plpgsql_inc(1.0);
+ svartest_plpgsql_inc 
+----------------------
+                    2
+(1 row)
+
+SELECT svartest_plpgsql_inc(1.0);
+ svartest_plpgsql_inc 
+----------------------
+                    3
+(1 row)
+
+SELECT svartest_plpgsql_inc(1.0) FROM generate_series(1,10);
+ svartest_plpgsql_inc 
+----------------------
+                    4
+                    5
+                    6
+                    7
+                    8
+                    9
+                   10
+                   11
+                   12
+                   13
+(10 rows)
+
+DROP VARIABLE plpgsql_sesvar01, plpgsql_sesvar02;
+DROP FUNCTION svartest_plpgsql_inc(int);
+DROP FUNCTION svartest_plpgsql_inc(numeric);
+-- the value should not be corrupted
+CREATE VARIABLE plpgsql_sesvar03 text;
+LET plpgsql_sesvar03 = 'abc';
+CREATE FUNCTION svartest_plpgsql_func03()
+RETURNS text AS $$
+BEGIN
+  RETURN svartest_plpgsql_func_nested(VARIABLE(plpgsql_sesvar03));
+END
+$$ LANGUAGE plpgsql;
+CREATE FUNCTION svartest_plpgsql_func_nested(t text)
+RETURNS text AS $$
+BEGIN
+  LET plpgsql_sesvar03 = 'BOOM!';
+  RETURN t;
+END;
+$$ LANGUAGE plpgsql;
+SELECT svartest_plpgsql_func03();
+ svartest_plpgsql_func03 
+-------------------------
+ abc
+(1 row)
+
+DROP FUNCTION svartest_plpgsql_func03();
+DROP FUNCTION svartest_plpgsql_func_nested(text);
+DROP VARIABLE plpgsql_sesvar03;
diff --git a/src/pl/plpgsql/src/meson.build b/src/pl/plpgsql/src/meson.build
index 33c49ac25d9..1d01d1c2629 100644
--- a/src/pl/plpgsql/src/meson.build
+++ b/src/pl/plpgsql/src/meson.build
@@ -88,6 +88,7 @@ tests += {
       'plpgsql_trap',
       'plpgsql_trigger',
       'plpgsql_varprops',
+      'plpgsql_session_variable',
     ],
   },
 }
diff --git a/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql b/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql
new file mode 100644
index 00000000000..61d46a792ec
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql
@@ -0,0 +1,168 @@
+-- variables are not checked in compile time
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func00_01()
+RETURNS void AS $$
+BEGIN
+  RAISE NOTICE '%', VARIABLE(this_variable_doesnt_exists);
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func00_02()
+RETURNS void AS $$
+BEGIN
+  LET this_variable_doesnt_exists = 10;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func00_03()
+RETURNS void AS $$
+BEGIN
+  LET this_variable_doesnt_exists[10] = 'Hello';
+END;
+$$ LANGUAGE plpgsql;
+
+-- should fail
+SELECT svartest_plpgsql_func00_01();
+SELECT svartest_plpgsql_func00_02();
+SELECT svartest_plpgsql_func00_03();
+
+DROP FUNCTION svartest_plpgsql_func00_01();
+DROP FUNCTION svartest_plpgsql_func00_02();
+DROP FUNCTION svartest_plpgsql_func00_03();
+
+-- check of correct plan cache invalidation
+CREATE VARIABLE plpgsql_sesvar01 AS int;
+CREATE VARIABLE plpgsql_sesvar02 AS int[];
+
+-- plpgsql variables and session variables are not in collision ever
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func01_01()
+RETURNS void AS $$
+DECLARE plpgsql_sesvar01 int;
+BEGIN
+  plpgsql_sesvar01 := 100;
+  LET plpgsql_sesvar01 = 1000;
+  RAISE NOTICE 'plpgsql var: %, session var: %',
+    plpgsql_sesvar01, VARIABLE(plpgsql_sesvar01);
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT svartest_plpgsql_func01_01();
+
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func01_02()
+RETURNS void AS $$
+DECLARE __plpgsql_sesvar01 int;
+BEGIN
+  __plpgsql_sesvar01 := 100;
+  LET __plpgsql_sesvar01 = 1000;
+  RAISE NOTICE 'plpgsql var: %, session var: %',
+    __plpgsql_sesvar01, VARIABLE(__plpgsql_sesvar01);
+END;
+$$ LANGUAGE plpgsql;
+
+-- should fail
+SELECT svartest_plpgsql_func01_02();
+
+-- should fail
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func01_03()
+RETURNS void AS $$
+BEGIN
+  plpgsql_sesvar01 := 100;
+  LET plpgsql_sesvar01 = 1000;
+  RAISE NOTICE 'plpgsql var: %, session var: %',
+    plpgsql_sesvar01, VARIABLE(plpgsql_sesvar01);
+END;
+$$ LANGUAGE plpgsql;
+
+DROP FUNCTION svartest_plpgsql_func01_01();
+DROP FUNCTION svartest_plpgsql_func01_02();
+
+CREATE OR REPLACE FUNCTION svartest_plpgsql_func02()
+RETURNS void AS $$
+DECLARE v int[] DEFAULT '{}';
+BEGIN
+  LET plpgsql_sesvar01 = 1;
+  v[VARIABLE(plpgsql_sesvar01)] = 100;
+  RAISE NOTICE '%', v;
+  LET plpgsql_sesvar02 = v;
+  LET plpgsql_sesvar02[VARIABLE(plpgsql_sesvar01)] = -1;
+  RAISE NOTICE '%', VARIABLE(plpgsql_sesvar02);
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT svartest_plpgsql_func02();
+
+DROP VARIABLE plpgsql_sesvar01, plpgsql_sesvar02;
+
+CREATE VARIABLE plpgsql_sesvar01 AS int;
+CREATE VARIABLE plpgsql_sesvar02 AS int[];
+
+SELECT svartest_plpgsql_func02();
+
+DROP FUNCTION svartest_plpgsql_func02();
+
+DROP VARIABLE plpgsql_sesvar01, plpgsql_sesvar02;
+
+-- returns updated value
+CREATE VARIABLE plpgsql_sesvar01 AS int;
+
+CREATE OR REPLACE FUNCTION svartest_plpgsql_inc(int)
+RETURNS int AS $$
+BEGIN
+  LET plpgsql_sesvar01 = COALESCE(VARIABLE(plpgsql_sesvar01) + $1, $1);
+  RETURN VARIABLE(plpgsql_sesvar01);
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT svartest_plpgsql_inc(1);
+SELECT svartest_plpgsql_inc(1);
+SELECT svartest_plpgsql_inc(1);
+
+SELECT svartest_plpgsql_inc(1) FROM generate_series(1,10);
+
+CREATE VARIABLE plpgsql_sesvar02 AS numeric;
+
+LET plpgsql_sesvar02 = 0.0;
+
+CREATE OR REPLACE FUNCTION svartest_plpgsql_inc(numeric)
+RETURNS int AS $$
+BEGIN
+  LET plpgsql_sesvar02 = COALESCE(VARIABLE(plpgsql_sesvar02) + $1, $1);
+  RETURN VARIABLE(plpgsql_sesvar02);
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT svartest_plpgsql_inc(1.0);
+SELECT svartest_plpgsql_inc(1.0);
+SELECT svartest_plpgsql_inc(1.0);
+
+SELECT svartest_plpgsql_inc(1.0) FROM generate_series(1,10);
+
+DROP VARIABLE plpgsql_sesvar01, plpgsql_sesvar02;
+
+DROP FUNCTION svartest_plpgsql_inc(int);
+DROP FUNCTION svartest_plpgsql_inc(numeric);
+
+-- the value should not be corrupted
+CREATE VARIABLE plpgsql_sesvar03 text;
+LET plpgsql_sesvar03 = 'abc';
+
+CREATE FUNCTION svartest_plpgsql_func03()
+RETURNS text AS $$
+BEGIN
+  RETURN svartest_plpgsql_func_nested(VARIABLE(plpgsql_sesvar03));
+END
+$$ LANGUAGE plpgsql;
+
+CREATE FUNCTION svartest_plpgsql_func_nested(t text)
+RETURNS text AS $$
+BEGIN
+  LET plpgsql_sesvar03 = 'BOOM!';
+  RETURN t;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT svartest_plpgsql_func03();
+
+DROP FUNCTION svartest_plpgsql_func03();
+DROP FUNCTION svartest_plpgsql_func_nested(text);
+
+DROP VARIABLE plpgsql_sesvar03;
-- 
2.51.0

From df1b0f7eb5a6c6b9793d29827acc4c2853228585 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 12/15] function pg_get_session_variables_memory for cleaning
 tests

This is a function designed for testing and debugging.  It returns the
content of sessionvars as-is, and can therefore display entries about
session variables that were dropped but for which this backend didn't
process the shared invalidations yet.
---
 src/backend/commands/session_variable.c | 100 ++++++++++++++++++++++++
 src/include/catalog/pg_proc.dat         |   8 ++
 2 files changed, 108 insertions(+)

diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 768163e2009..73ebf0340e3 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -22,6 +22,7 @@
 #include "executor/execdesc.h"
 #include "executor/executor.h"
 #include "executor/svariableReceiver.h"
+#include "funcapi.h"
 #include "miscadmin.h"
 #include "nodes/plannodes.h"
 #include "parser/parse_type.h"
@@ -600,3 +601,102 @@ ExecuteLetStmt(ParseState *pstate,
 
 	PopActiveSnapshot();
 }
+
+/*
+ * pg_get_session_variables_memory - designed for testing
+ *
+ * This is a function designed for testing and debugging.  It returns the
+ * content of session variables as-is, and can therefore display data about
+ * session variables that were dropped, but for which this backend didn't
+ * process the shared invalidations yet.
+ */
+Datum
+pg_get_session_variables_memory(PG_FUNCTION_ARGS)
+{
+#define NUM_PG_GET_SESSION_VARIABLES_MEMORY_ATTS 8
+
+	/*
+	 * Make sure syscache entries are flushed for recent catalog changes. For
+	 * stable behavior we need to reliably detect which variables were
+	 * dropped.
+	 */
+	AcceptInvalidationMessages();
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	if (sessionvars)
+	{
+		ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+		HASH_SEQ_STATUS status;
+		SVariable	svar;
+
+		hash_seq_init(&status, sessionvars);
+
+		while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+		{
+			Datum		values[NUM_PG_GET_SESSION_VARIABLES_MEMORY_ATTS];
+			bool		nulls[NUM_PG_GET_SESSION_VARIABLES_MEMORY_ATTS];
+			HeapTuple	tp;
+			bool		var_is_valid = false;
+
+			memset(values, 0, sizeof(values));
+			memset(nulls, 0, sizeof(nulls));
+
+			values[0] = ObjectIdGetDatum(svar->varid);
+			values[3] = ObjectIdGetDatum(svar->typid);
+
+			/*
+			 * It is possible that the variable has been dropped from the
+			 * catalog, but not yet purged from the hash table.
+			 */
+			tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+			if (HeapTupleIsValid(tp))
+			{
+				Form_pg_variable varform = (Form_pg_variable) GETSTRUCT(tp);
+
+				/*
+				 * It is also possible that a variable has been dropped and
+				 * someone created a new variable with the same object ID.
+				 * Use the catalog information only if that is not the case.
+				 */
+				if (svar->create_lsn == varform->varcreate_lsn)
+				{
+					values[1] = CStringGetTextDatum(
+													get_namespace_name(varform->varnamespace));
+
+					values[2] = CStringGetTextDatum(NameStr(varform->varname));
+					values[4] = CStringGetTextDatum(format_type_be(svar->typid));
+					values[5] = BoolGetDatum(false);
+
+					values[6] = BoolGetDatum(
+											 object_aclcheck(VariableRelationId, svar->varid,
+															 GetUserId(), ACL_SELECT) == ACLCHECK_OK);
+
+					values[7] = BoolGetDatum(
+											 object_aclcheck(VariableRelationId, svar->varid,
+															 GetUserId(), ACL_UPDATE) == ACLCHECK_OK);
+
+					var_is_valid = true;
+				}
+
+				ReleaseSysCache(tp);
+			}
+
+			/* if there is no matching catalog entry, return null values */
+			if (!var_is_valid)
+			{
+				nulls[1] = true;
+				nulls[2] = true;
+				nulls[4] = true;
+				values[5] = BoolGetDatum(true);
+				nulls[6] = true;
+				nulls[7] = true;
+			}
+
+			tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+		}
+	}
+
+	return (Datum) 0;
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d28fb3283cb..597f56a4d95 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12611,4 +12611,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' },
 
+# Session variables support
+{ oid => '8488', descr => 'internal view of memory entries used by session variables (for debugging)',
+  proname => 'pg_get_session_variables_memory', prorows => '1000', proretset => 't',
+  provolatile => 's', proparallel => 'r', prorettype => 'record',
+  proargtypes => '', proallargtypes => '{oid,text,text,oid,text,bool,bool,bool}',
+  proargmodes => '{o,o,o,o,o,o,o,o}',
+  proargnames => '{varid,schema,name,typid,typname,dropped,can_select,can_update}',
+  prosrc => 'pg_get_session_variables_memory' },
 ]
-- 
2.51.0

From 4c646991d84f14ebaa0bbf9d48842c76028b7157 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 2 Jun 2025 22:33:25 +0200
Subject: [PATCH 14/15] memory cleaning after DROP VARIABLE

Accepting a sinval message invalidates entries in the "sessionvars" hash table.
These entries are validated before any read or write operations on session variables.
When the entry cannot be validated, it is removed.  Removal will be delayed when
the variable was dropped by the current transaction, which could still be rolled back.
---
 src/backend/catalog/pg_variable.c             |   7 +-
 src/backend/commands/session_variable.c       | 154 ++++++++++++-
 src/include/commands/session_variable.h       |   2 +
 .../isolation/expected/session-variable.out   | 110 +++++++++
 src/test/isolation/isolation_schedule         |   1 +
 .../isolation/specs/session-variable.spec     |  50 ++++
 .../expected/session_variables_ddl.out        | 214 ++++++++++++++++++
 .../regress/sql/session_variables_ddl.sql     | 151 ++++++++++++
 8 files changed, 683 insertions(+), 6 deletions(-)
 create mode 100644 src/test/isolation/expected/session-variable.out
 create mode 100644 src/test/isolation/specs/session-variable.spec

diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
index d8ede4fa8c8..c9411443c6d 100644
--- a/src/backend/catalog/pg_variable.c
+++ b/src/backend/catalog/pg_variable.c
@@ -22,6 +22,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
 #include "utils/builtins.h"
 #include "utils/pg_lsn.h"
 #include "utils/syscache.h"
@@ -154,7 +155,8 @@ create_variable(const char *varName,
 }
 
 /*
- * Drop variable by OID
+ * Drop variable by OID, and register the needed session variable
+ * cleanup.
  */
 void
 DropVariableById(Oid varid)
@@ -174,4 +176,7 @@ DropVariableById(Oid varid)
 	ReleaseSysCache(tup);
 
 	table_close(rel, RowExclusiveLock);
+
+	/* do the necessary cleanup in local memory, if needed */
+	SessionVariableDropPostprocess(varid);
 }
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 52126a10f62..cb7aa1a11ed 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -13,8 +13,8 @@
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
-
 #include "access/htup_details.h"
+#include "access/xact.h"
 #include "catalog/pg_variable.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
@@ -76,6 +76,14 @@ typedef struct SVariableData
 	void	   *domain_check_extra;
 	LocalTransactionId domain_check_extra_lxid;
 
+	/*
@@ -92,6 +100,17 @@ static HTAB *sessionvars = NULL;	/* hash table for session variables */

@@ -124,6 +143,38 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
@@ -177,6 +228,67 @@ is_session_variable_valid(SVariable svar)
 	return result;
 }
 
+/*
+	 * Reset the flag before we start the validation.  It can be set again by
+	 * concurrently incoming sinval messages.
@@ -206,6 +318,8 @@ setup_session_variable(SVariable svar, Oid varid)

@@ -329,22 +443,42 @@ get_session_variable(Oid varid)
 	if (!sessionvars)
 		create_sessionvars_hashtables();

 	if (found)
 	{
+		/*
+		 * The session variable could have been dropped by a DROP VARIABLE
+		 * statement in a subtransaction that was later rolled back, which
+		 * means that we may have to work with the data of a variable marked
+		 * as invalid.
+		 */
 		if (!svar->is_valid)
 		{
 			/*
-			 * If there was an invalidation message, the variable might still
-			 * be valid, but we have to check with the system catalog.
+			 * We have to check the system catalog to see if the variable is
+			 * still valid, even if an invalidation message set it to invalid.
+			 *
+			 * The variable must be validated before it is accessed.  The oid
+			 * should be valid, because the related session variable is
+			 * already locked, and remove_invalid_session_variables() would
+			 * remove variables dropped by other transactions.
 			 */
 			if (is_session_variable_valid(svar))
 				svar->is_valid = true;
 			else
-				/* if the value cannot be validated, we have to discard it */
-				free_session_variable_value(svar);
+				elog(ERROR, "unexpected state of session variable %u", varid);
 		}
 	}
 	else
@@ -405,6 +539,16 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 	if (!sessionvars)
 		create_sessionvars_hashtables();

diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index ac36dfcc19b..c06e1faf02c 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -21,6 +21,8 @@
 #include "nodes/parsenodes.h"
 #include "tcop/cmdtag.h"
 
+extern void SessionVariableDropPostprocess(Oid varid);
+
 extern void SetSessionVariable(Oid varid, Datum value, bool isNull);
 extern Datum GetSessionVariable(Oid varid, bool *isNull);
 
diff --git a/src/test/isolation/expected/session-variable.out b/src/test/isolation/expected/session-variable.out
new file mode 100644
index 00000000000..b69dac2df7a
--- /dev/null
+++ b/src/test/isolation/expected/session-variable.out
@@ -0,0 +1,110 @@
+Parsed test spec with 4 sessions
+
+starting permutation: let val drop val
+step let: LET myvar = 'test';
+step val: SELECT VARIABLE(myvar);
+myvar
+-----
+test 
+(1 row)
+
+step drop: DROP VARIABLE myvar;
+step val: SELECT VARIABLE(myvar);
+ERROR:  session variable "myvar" doesn't exist
+
+starting permutation: let val s1 drop val sr1
+step let: LET myvar = 'test';
+step val: SELECT VARIABLE(myvar);
+myvar
+-----
+test 
+(1 row)
+
+step s1: BEGIN;
+step drop: DROP VARIABLE myvar;
+step val: SELECT VARIABLE(myvar);
+ERROR:  session variable "myvar" doesn't exist
+step sr1: ROLLBACK;
+
+starting permutation: let val dbg drop create dbg val
+step let: LET myvar = 'test';
+step val: SELECT VARIABLE(myvar);
+myvar
+-----
+test 
+(1 row)
+
+step dbg: SELECT schema, name, dropped FROM pg_get_session_variables_memory();
+schema|name |dropped
+------+-----+-------
+public|myvar|f      
+(1 row)
+
+step drop: DROP VARIABLE myvar;
+step create: CREATE VARIABLE myvar AS text;
+step dbg: SELECT schema, name, dropped FROM pg_get_session_variables_memory();
+schema|name|dropped
+------+----+-------
+      |    |t      
+(1 row)
+
+step val: SELECT VARIABLE(myvar);
+myvar
+-----
+     
+(1 row)
+
+
+starting permutation: let val s1 dbg drop create dbg val sr1
+step let: LET myvar = 'test';
+step val: SELECT VARIABLE(myvar);
+myvar
+-----
+test 
+(1 row)
+
+step s1: BEGIN;
+step dbg: SELECT schema, name, dropped FROM pg_get_session_variables_memory();
+schema|name |dropped
+------+-----+-------
+public|myvar|f      
+(1 row)
+
+step drop: DROP VARIABLE myvar;
+step create: CREATE VARIABLE myvar AS text;
+step dbg: SELECT schema, name, dropped FROM pg_get_session_variables_memory();
+schema|name|dropped
+------+----+-------
+      |    |t      
+(1 row)
+
+step val: SELECT VARIABLE(myvar);
+myvar
+-----
+     
+(1 row)
+
+step sr1: ROLLBACK;
+
+starting permutation: create3 let3 s3 create4 let4 drop4 drop3 inval3 discard sc3 state
+step create3: CREATE VARIABLE myvar3 AS text;
+step let3: LET myvar3 = 'test';
+step s3: BEGIN;
+step create4: CREATE VARIABLE myvar4 AS text;
+step let4: LET myvar4 = 'test';
+step drop4: DROP VARIABLE myvar4;
+step drop3: DROP VARIABLE myvar3;
+step inval3: SELECT COUNT(*) >= 0 FROM pg_foreign_table;
+?column?
+--------
+t       
+(1 row)
+
+step discard: DISCARD VARIABLES;
+step sc3: COMMIT;
+step state: SELECT varname FROM pg_variable;
+varname
+-------
+myvar  
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 5afae33d370..3068ae401db 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -120,3 +120,4 @@ test: serializable-parallel-2
 test: serializable-parallel-3
 test: matview-write-skew
 test: lock-nowait
+test: session-variable
diff --git a/src/test/isolation/specs/session-variable.spec b/src/test/isolation/specs/session-variable.spec
new file mode 100644
index 00000000000..72629321d90
--- /dev/null
+++ b/src/test/isolation/specs/session-variable.spec
@@ -0,0 +1,50 @@
+# Test session variables memory cleanup for sinval
+
+setup
+{
+    CREATE VARIABLE myvar AS text;
+}
+
+teardown
+{
+    DROP VARIABLE IF EXISTS myvar;
+}
+
+session s1
+step s1		{ BEGIN; }
+step let	{ LET myvar = 'test'; }
+step val	{ SELECT VARIABLE(myvar); }
+step dbg	{ SELECT schema, name, dropped FROM pg_get_session_variables_memory(); }
+step sr1	{ ROLLBACK; }
+
+session s2
+step drop		{ DROP VARIABLE myvar; }
+step create		{ CREATE VARIABLE myvar AS text; }
+
+session s3
+step s3			{ BEGIN; }
+step let3		{ LET myvar3 = 'test'; }
+step create4	{ CREATE VARIABLE myvar4 AS text; }
+step let4		{ LET myvar4 = 'test'; }
+step drop4		{ DROP VARIABLE myvar4; }
+step inval3		{ SELECT COUNT(*) >= 0 FROM pg_foreign_table; }
+step discard	{ DISCARD VARIABLES; }
+step sc3		{ COMMIT; }
+step state		{ SELECT varname FROM pg_variable; }
+
+session s4
+step create3	{ CREATE VARIABLE myvar3 AS text; }
+step drop3		{ DROP VARIABLE myvar3; }
+
+# Concurrent drop of a known variable should lead to an error
+permutation let val drop val
+# Same, but with an explicit transaction
+permutation let val s1 drop val sr1
+# Concurrent drop/create of a known variable should lead to empty variable
+permutation let val dbg drop create dbg val
+# Concurrent drop/create of a known variable should lead to empty variable
+# We need a transaction to make sure that we won't accept invalidation when
+# calling the dbg step after the concurrent drop
+permutation let val s1 dbg drop create dbg val sr1
+# test for DISCARD ALL when all internal queues have actions registered
+permutation create3 let3 s3 create4 let4 drop4 drop3 inval3 discard sc3 state
diff --git a/src/test/regress/expected/session_variables_ddl.out b/src/test/regress/expected/session_variables_ddl.out
index 9c7595e9a41..3aef7665edc 100644
--- a/src/test/regress/expected/session_variables_ddl.out
+++ b/src/test/regress/expected/session_variables_ddl.out
@@ -161,3 +161,217 @@ DETAIL:  drop cascades to session variable svartest01_ddl.sesvar10
 drop cascades to session variable svartest01_ddl.sesvar11
 DROP SCHEMA svartest02_ddl CASCADE;
 NOTICE:  drop cascades to session variable svartest02_ddl.sesvar10
+CREATE SCHEMA svartest_ddl;
+CREATE VARIABLE svartest_ddl.sesvar60 AS varchar;
+-- dropped variables should be removed from memory before the next usage
+-- of any session variable in the next transaction
+LET svartest_ddl.sesvar60 = 'Hello';
+SELECT count(*) FROM pg_get_session_variables_memory()
+  WHERE schema = 'svartest_ddl'; -- 1
+ count 
+-------
+     1
+(1 row)
+
+DROP VARIABLE svartest_ddl.sesvar60;
+-- should be zero
+SELECT count(*) FROM pg_get_session_variables_memory()
+  WHERE schema = 'svartest_ddl'; -- 0
+ count 
+-------
+     0
+(1 row)
+
+-- the content of the value should be preserved when a variable is dropped
+-- by an aborted transaction
+CREATE VARIABLE svartest_ddl.sesvar60 AS varchar;
+LET svartest_ddl.sesvar60 = 'Hello';
+BEGIN;
+  DROP VARIABLE svartest_ddl.sesvar60;
+  -- should fail
+  SELECT VARIABLE(svartest_ddl.sesvar60);
+ERROR:  session variable "svartest_ddl.sesvar60" doesn't exist
+LINE 1: SELECT VARIABLE(svartest_ddl.sesvar60);
+                        ^
+ROLLBACK;
+-- should be ok
+SELECT VARIABLE(svartest_ddl.sesvar60); -- Hello
+ sesvar60 
+----------
+ Hello
+(1 row)
+
+-- another test
+BEGIN;
+  DROP VARIABLE svartest_ddl.sesvar60;
+  -- should be ok
+  CREATE VARIABLE svartest_ddl.sesvar60 AS int;
+  LET svartest_ddl.sesvar60 = 100;
+  SELECT VARIABLE(svartest_ddl.sesvar60); -- 100
+ sesvar60 
+----------
+      100
+(1 row)
+
+ROLLBACK;
+SELECT VARIABLE(svartest_ddl.sesvar60); -- Hello
+ sesvar60 
+----------
+ Hello
+(1 row)
+
+DROP VARIABLE svartest_ddl.sesvar60;
+-- should be zero
+SELECT count(*) FROM pg_get_session_variables_memory()
+  WHERE schema = 'svartest_ddl'; -- 0
+ count 
+-------
+     0
+(1 row)
+
+BEGIN;
+  CREATE VARIABLE svartest_ddl.sesvar60 AS int;
+  LET svartest_ddl.sesvar60 = 100;
+  SELECT VARIABLE(svartest_ddl.sesvar60);
+ sesvar60 
+----------
+      100
+(1 row)
+
+  SELECT count(*) FROM pg_get_session_variables_memory()
+    WHERE schema = 'svartest_ddl'; -- 1
+ count 
+-------
+     1
+(1 row)
+
+  DROP VARIABLE svartest_ddl.sesvar60;
+COMMIT;
+SELECT count(*) FROM pg_get_session_variables_memory()
+  WHERE schema = 'svartest_ddl'; -- 0
+ count 
+-------
+     0
+(1 row)
+
+CREATE VARIABLE svartest_ddl.sesvar61 AS int;
+CREATE VARIABLE svartest_ddl.sesvar62 AS int;
+LET svartest_ddl.sesvar61 = 10;
+LET svartest_ddl.sesvar62 = 0;
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE svartest_ddl.sesvar61;
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+ sesvar62 
+----------
+        0
+(1 row)
+
+  ROLLBACK TO s1;
+  SELECT VARIABLE(svartest_ddl.sesvar61);
+ sesvar61 
+----------
+       10
+(1 row)
+
+  SAVEPOINT s2;
+  DROP VARIABLE svartest_ddl.sesvar61;
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+ sesvar62 
+----------
+        0
+(1 row)
+
+  ROLLBACK TO s2;
+COMMIT;
+-- should be ok
+SELECT VARIABLE(svartest_ddl.sesvar61);
+ sesvar61 
+----------
+       10
+(1 row)
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE svartest_ddl.sesvar61;
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+ sesvar62 
+----------
+        0
+(1 row)
+
+  ROLLBACK TO s1;
+  SELECT VARIABLE(svartest_ddl.sesvar61);
+ sesvar61 
+----------
+       10
+(1 row)
+
+  SAVEPOINT s2;
+  DROP VARIABLE svartest_ddl.sesvar61;
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+ sesvar62 
+----------
+        0
+(1 row)
+
+  ROLLBACK TO s2;
+ROLLBACK;
+-- should be ok
+SELECT VARIABLE(svartest_ddl.sesvar61);
+ sesvar61 
+----------
+       10
+(1 row)
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE svartest_ddl.sesvar61;
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+ sesvar62 
+----------
+        0
+(1 row)
+
+  SAVEPOINT s2;
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+ sesvar62 
+----------
+        0
+(1 row)
+
+  ROLLBACK TO s1;
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+ sesvar62 
+----------
+        0
+(1 row)
+
+COMMIT;
+-- should be ok
+SELECT VARIABLE(svartest_ddl.sesvar61);
+ sesvar61 
+----------
+       10
+(1 row)
+
+-- repeated aborted transaction
+BEGIN; DROP VARIABLE svartest_ddl.sesvar61; ROLLBACK;
+BEGIN; DROP VARIABLE svartest_ddl.sesvar61; ROLLBACK;
+BEGIN; DROP VARIABLE svartest_ddl.sesvar61; ROLLBACK;
+-- should be ok
+SELECT VARIABLE(svartest_ddl.sesvar61);
+ sesvar61 
+----------
+       10
+(1 row)
+
+DROP VARIABLE svartest_ddl.sesvar61, svartest_ddl.sesvar62;
+DROP SCHEMA svartest_ddl;
diff --git a/src/test/regress/sql/session_variables_ddl.sql b/src/test/regress/sql/session_variables_ddl.sql
index f844469ecb1..a616ecdc8ef 100644
--- a/src/test/regress/sql/session_variables_ddl.sql
+++ b/src/test/regress/sql/session_variables_ddl.sql
@@ -148,3 +148,154 @@ ALTER VARIABLE svartest02_ddl.sesvar10 SET SCHEMA svartest01_ddl;
 
 DROP SCHEMA svartest01_ddl CASCADE;
 DROP SCHEMA svartest02_ddl CASCADE;
+
+CREATE SCHEMA svartest_ddl;
+
+CREATE VARIABLE svartest_ddl.sesvar60 AS varchar;
+
+-- dropped variables should be removed from memory before the next usage
+-- of any session variable in the next transaction
+
+LET svartest_ddl.sesvar60 = 'Hello';
+
+SELECT count(*) FROM pg_get_session_variables_memory()
+  WHERE schema = 'svartest_ddl'; -- 1
+
+DROP VARIABLE svartest_ddl.sesvar60;
+
+-- should be zero
+SELECT count(*) FROM pg_get_session_variables_memory()
+  WHERE schema = 'svartest_ddl'; -- 0
+
+-- the content of the value should be preserved when a variable is dropped
+-- by an aborted transaction
+CREATE VARIABLE svartest_ddl.sesvar60 AS varchar;
+
+LET svartest_ddl.sesvar60 = 'Hello';
+
+BEGIN;
+  DROP VARIABLE svartest_ddl.sesvar60;
+
+  -- should fail
+  SELECT VARIABLE(svartest_ddl.sesvar60);
+
+ROLLBACK;
+
+-- should be ok
+SELECT VARIABLE(svartest_ddl.sesvar60); -- Hello
+
+-- another test
+BEGIN;
+  DROP VARIABLE svartest_ddl.sesvar60;
+
+  -- should be ok
+  CREATE VARIABLE svartest_ddl.sesvar60 AS int;
+  LET svartest_ddl.sesvar60 = 100;
+  SELECT VARIABLE(svartest_ddl.sesvar60); -- 100
+
+ROLLBACK;
+
+SELECT VARIABLE(svartest_ddl.sesvar60); -- Hello
+
+DROP VARIABLE svartest_ddl.sesvar60;
+
+-- should be zero
+SELECT count(*) FROM pg_get_session_variables_memory()
+  WHERE schema = 'svartest_ddl'; -- 0
+
+BEGIN;
+  CREATE VARIABLE svartest_ddl.sesvar60 AS int;
+
+  LET svartest_ddl.sesvar60 = 100;
+
+  SELECT VARIABLE(svartest_ddl.sesvar60);
+
+  SELECT count(*) FROM pg_get_session_variables_memory()
+    WHERE schema = 'svartest_ddl'; -- 1
+
+  DROP VARIABLE svartest_ddl.sesvar60;
+
+COMMIT;
+
+SELECT count(*) FROM pg_get_session_variables_memory()
+  WHERE schema = 'svartest_ddl'; -- 0
+
+CREATE VARIABLE svartest_ddl.sesvar61 AS int;
+CREATE VARIABLE svartest_ddl.sesvar62 AS int;
+
+LET svartest_ddl.sesvar61 = 10;
+LET svartest_ddl.sesvar62 = 0;
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE svartest_ddl.sesvar61;
+
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+  ROLLBACK TO s1;
+
+  SELECT VARIABLE(svartest_ddl.sesvar61);
+
+  SAVEPOINT s2;
+  DROP VARIABLE svartest_ddl.sesvar61;
+
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+  ROLLBACK TO s2;
+COMMIT;
+
+-- should be ok
+SELECT VARIABLE(svartest_ddl.sesvar61);
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE svartest_ddl.sesvar61;
+
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+  ROLLBACK TO s1;
+
+  SELECT VARIABLE(svartest_ddl.sesvar61);
+
+  SAVEPOINT s2;
+  DROP VARIABLE svartest_ddl.sesvar61;
+
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+  ROLLBACK TO s2;
+ROLLBACK;
+
+-- should be ok
+SELECT VARIABLE(svartest_ddl.sesvar61);
+
+BEGIN;
+  SAVEPOINT s1;
+  DROP VARIABLE svartest_ddl.sesvar61;
+
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+
+  SAVEPOINT s2;
+
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+  ROLLBACK TO s1;
+
+  -- force cleaning by touching another session variable
+  SELECT VARIABLE(svartest_ddl.sesvar62);
+COMMIT;
+
+-- should be ok
+SELECT VARIABLE(svartest_ddl.sesvar61);
+
+-- repeated aborted transaction
+BEGIN; DROP VARIABLE svartest_ddl.sesvar61; ROLLBACK;
+BEGIN; DROP VARIABLE svartest_ddl.sesvar61; ROLLBACK;
+BEGIN; DROP VARIABLE svartest_ddl.sesvar61; ROLLBACK;
+
+-- should be ok
+SELECT VARIABLE(svartest_ddl.sesvar61);
+
+DROP VARIABLE svartest_ddl.sesvar61, svartest_ddl.sesvar62;
+
+DROP SCHEMA svartest_ddl;
-- 
2.51.0

From c18ea1263e01eff4c2c3fe441d0b4df7eee2a6ac Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 2 Jun 2025 20:41:57 +0200
Subject: [PATCH 13/15] DISCARD VARIABLES

Implementation of DISCARD VARIABLES commands by removing hash table with session variables
and resetting related memory context.
---
 doc/src/sgml/ref/discard.sgml                 | 13 ++++-
 src/backend/commands/discard.c                |  6 +++
 src/backend/commands/session_variable.c       | 32 ++++++++++--
 src/backend/parser/gram.y                     |  6 +++
 src/backend/tcop/utility.c                    |  3 ++
 src/bin/psql/tab-complete.in.c                |  2 +-
 src/include/commands/session_variable.h       |  2 +
 src/include/nodes/parsenodes.h                |  1 +
 src/include/tcop/cmdtaglist.h                 |  1 +
 .../expected/session_variables_dml.out        | 50 +++++++++++++++++++
 .../regress/sql/session_variables_dml.sql     | 24 +++++++++
 11 files changed, 135 insertions(+), 5 deletions(-)

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
index 81339a75a52..5904a6c4917 100644
 
index 73ebf0340e3..52126a10f62 100644
@@ -103,7 +103,13 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)

-	Assert(sessionvars);
+	/*
 
 	/*
 	 * If the hashvalue is not specified, we have to recheck all currently
@@ -657,8 +663,8 @@ pg_get_session_variables_memory(PG_FUNCTION_ARGS)
 
 				/*
 				 * It is also possible that a variable has been dropped and
-				 * someone created a new variable with the same object ID.
-				 * Use the catalog information only if that is not the case.
+				 * someone created a new variable with the same object ID. Use
+				 * the catalog information only if that is not the case.
 				 */
 				if (svar->create_lsn == varform->varcreate_lsn)
 				{
@@ -700,3 +706,23 @@ pg_get_session_variables_memory(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
+
+/*
index 5a14132e003..bcf2bf84503 100644
@@ -2120,7 +2120,13 @@ DiscardStmt:

diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ab0400c1f7a..7a9271d0639 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -2953,6 +2953,9 @@ CreateCommandTag(Node *parsetree)
 				case DISCARD_SEQUENCES:
 					tag = CMDTAG_DISCARD_SEQUENCES;
 					break;
+				case DISCARD_VARIABLES:
+					tag = CMDTAG_DISCARD_VARIABLES;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 49f77b7928f..dfd1973be38 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -4210,7 +4210,7 @@ match_previous_words(int pattern_id,
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 2ebe8477789..ac36dfcc19b 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -29,4 +29,6 @@ extern ObjectAddress CreateVariable(ParseState *pstate, CreateSessionVarStmt *st
 extern void ExecuteLetStmt(ParseState *pstate, LetStmt *stmt, ParamListInfo params,
 						   QueryEnvironment *queryEnv, QueryCompletion *qc);
 
+extern void ResetSessionVariables(void);
+
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 524f97ac5c2..f272d087adc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4107,6 +4107,7 @@ typedef enum DiscardMode
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
 	DISCARD_TEMP,
+	DISCARD_VARIABLES,
 } DiscardMode;
 
 typedef struct DiscardStmt
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 22082c30008..bef0ac25331 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -135,6 +135,7 @@ PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_VARIABLES, "DISCARD VARIABLES", false, false, false)
 PG_CMDTAG(CMDTAG_DO, "DO", false, false, false)
 PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
diff --git a/src/test/regress/expected/session_variables_dml.out b/src/test/regress/expected/session_variables_dml.out
index 67f78a548dc..dfbe7900c1c 100644
--- a/src/test/regress/expected/session_variables_dml.out
+++ b/src/test/regress/expected/session_variables_dml.out
@@ -466,3 +466,53 @@ drop cascades to session variable svartest_dml.sesvar47
 drop cascades to session variable svartest_dml.sesvar48
 drop cascades to session variable svartest_dml.sesvar49
 DROP ROLE regress_svartest_dml_write_only_role;
+CREATE SCHEMA svartest_dml_discard;
+CREATE VARIABLE svartest_dml_discard.sesvar50 AS varchar;
+LET svartest_dml_discard.sesvar50 = 'Hello';
+SELECT VARIABLE(svartest_dml_discard.sesvar50);
+ sesvar50 
+----------
+ Hello
+(1 row)
+
+SELECT count(*) FROM pg_get_session_variables_memory() WHERE schema = 'svartest_dml_discard';
+ count 
+-------
+     1
+(1 row)
+
+DISCARD ALL;
+SELECT count(*) FROM pg_get_session_variables_memory();
+ count 
+-------
+     0
+(1 row)
+
+SELECT VARIABLE(svartest_dml_discard.sesvar50);
+ sesvar50 
+----------
+ 
+(1 row)
+
+LET svartest_dml_discard.sesvar50 = 'Hello';
+SELECT count(*) FROM pg_get_session_variables_memory();
+ count 
+-------
+     1
+(1 row)
+
+DISCARD VARIABLES;
+SELECT count(*) FROM pg_get_session_variables_memory();
+ count 
+-------
+     0
+(1 row)
+
+SELECT VARIABLE(svartest_dml_discard.sesvar50);
+ sesvar50 
+----------
+ 
+(1 row)
+
+DROP SCHEMA svartest_dml_discard CASCADE;
+NOTICE:  drop cascades to session variable svartest_dml_discard.sesvar50
diff --git a/src/test/regress/sql/session_variables_dml.sql b/src/test/regress/sql/session_variables_dml.sql
index 2c8d6c3a497..b0aeeb865c2 100644
--- a/src/test/regress/sql/session_variables_dml.sql
+++ b/src/test/regress/sql/session_variables_dml.sql
@@ -348,3 +348,27 @@ SELECT VARIABLE(svartest_dml.sesvar49);
 
 DROP SCHEMA svartest_dml CASCADE;
 DROP ROLE regress_svartest_dml_write_only_role;
+
+CREATE SCHEMA svartest_dml_discard;
+
+CREATE VARIABLE svartest_dml_discard.sesvar50 AS varchar;
+LET svartest_dml_discard.sesvar50 = 'Hello';
+SELECT VARIABLE(svartest_dml_discard.sesvar50);
+
+SELECT count(*) FROM pg_get_session_variables_memory() WHERE schema = 'svartest_dml_discard';
+
+DISCARD ALL;
+
+SELECT count(*) FROM pg_get_session_variables_memory();
+
+SELECT VARIABLE(svartest_dml_discard.sesvar50);
+LET svartest_dml_discard.sesvar50 = 'Hello';
+
+SELECT count(*) FROM pg_get_session_variables_memory();
+
+DISCARD VARIABLES;
+
+SELECT count(*) FROM pg_get_session_variables_memory();
+
+SELECT VARIABLE(svartest_dml_discard.sesvar50);
+DROP SCHEMA svartest_dml_discard CASCADE;
-- 
2.51.0

From 9335dd8806744584fdcda093dfc773631d6272c1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 2 Jun 2025 08:29:37 +0200
Subject: [PATCH 11/15] LET command - assign a result of expression to the
 session variable
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The value is assigned to session variables usually by SET command. Unfortunately
there are two reasons why SET should not be used for this purpose in Postgres.

1. Using a_expr inside generic_set ram rule produces reduce conflicts, so it needs
   total reimplementation of related gram rules.

2. SET is no plan command - so it doesn't support usage of parameters.

3. Excepting implementation issues, there is fact, so if we use SET command
   for assigning values to session variables, then there can be collisions
   between session variables and GUC, and then we need some concepts, how
   these collisions should be solved, or how to protect self against these
   collisions. With the dedicated command, the collisions between GUC and session
   variables are not possible.

The command LET is executed as usual query execution. The result is stored
to the target session variable (resultVariable) by using VariableDestReceiver.

Implementations of EXPLAIN LET and PREPARE LET statements are not supported
now. Postponed to next step due reducing patch size.
---
 doc/src/sgml/ddl.sgml                         |  29 ++
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/ref/alter_variable.sgml          |   1 +
 doc/src/sgml/ref/create_variable.sgml         |   5 +-
 doc/src/sgml/ref/drop_variable.sgml           |   1 +
 doc/src/sgml/ref/let.sgml                     |  96 ++++++
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/commands/session_variable.c       |  86 ++++++
 src/backend/executor/execMain.c               |  23 +-
 src/backend/nodes/nodeFuncs.c                 |  10 +
 src/backend/optimizer/plan/planner.c          |  24 ++
 src/backend/optimizer/plan/setrefs.c          |  34 ++-
 src/backend/parser/analyze.c                  | 237 +++++++++++++++
 src/backend/parser/gram.y                     |  39 ++-
 src/backend/tcop/utility.c                    |  15 +
 src/backend/utils/cache/plancache.c           |  11 +
 src/bin/psql/tab-complete.in.c                |  12 +-
 src/include/commands/session_variable.h       |   5 +
 src/include/nodes/parsenodes.h                |  15 +
 src/include/nodes/pathnodes.h                 |   9 +
 src/include/nodes/plannodes.h                 |   7 +
 src/include/nodes/primnodes.h                 |   9 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 .../expected/session_variables_dml.out        | 277 ++++++++++++++++++
 .../regress/sql/session_variables_dml.sql     | 189 ++++++++++++
 src/tools/pgindent/typedefs.list              |   1 +
 27 files changed, 1124 insertions(+), 15 deletions(-)
 create mode 100644 doc/src/sgml/ref/let.sgml

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 2655fa5e7ce..bdf00b35508 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5403,10 +5403,39 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     session variable identifier, and can be used only for session variable
     identifier. The special syntax for accessing session variables removes
     risk of collisions between variable identifiers and column names.
+   </para>
+
+   <para>
+    The value of a session variable is set with the SQL statement
+    <command>LET</command>.  The value of a session variable can be retrieved
+    with the SQL statement <command>SELECT</command>.
 <programlisting>
+CREATE VARIABLE var1 AS date;
+LET var1 = current_date;
+SELECT VARIABLE(var1);
+</programlisting>
+
+    or
+
+<programlisting>
+CREATE VARIABLE public.current_user_id AS integer;
+GRANT SELECT ON VARIABLE public.current_user_id TO PUBLIC;
+LET current_user_id = (SELECT id FROM users WHERE usename = session_user);
 SELECT VARIABLE(current_user_id);
 </programlisting>
    </para>
+
+   <para>
+    The value of a session variable is local to the current session. Retrieving
+    a variable's value returns a <literal>NULL</literal>, unless its value has
+    been set to something else in the current session using the
+    <command>LET</command> command.  Session variables are not transactional:
+    any changes made to the value of a session variable in a transaction won't
+    be undone if the transaction is rolled back (just like variables in
+    procedural languages).  Session variables themselves are persistent, but
+    their values are neither persistent nor shared (like the content of
+    temporary tables).
+   </para>
   </sect1>
 
  <sect1 id="ddl-others">
index 96d2586423e..221a699469b 100644
index 6e988f2e472..43000ce004d 100644
@@ -120,9 +120,11 @@ CREATE VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceab
   <title>Examples</title>
 
   <para>
-   Create an date session variable <literal>var1</literal>:
+   Create a session variable <literal>var1</literal> of data type date:
 <programlisting>
 CREATE VARIABLE var1 AS date;
+LET var1 = current_date;
+SELECT VARIABLE(var1);
 </programlisting>
   </para>
 
@@ -143,6 +145,7 @@ CREATE VARIABLE var1 AS date;

index 00000000000..00f9bea91fe
+    <term><replaceable class="parameter">session_variable</replaceable></term>
+    <term><replaceable class="parameter">sql_expression</replaceable></term>
index dbc054795bb..768163e2009 100644
@@ -19,15 +19,22 @@
+#include "executor/execdesc.h"
+#include "executor/executor.h"
+#include "nodes/plannodes.h"
 #include "utils/datum.h"
 #include "utils/inval.h"
 #include "utils/memutils.h"
 
 /*
@@ -514,3 +521,82 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 
 	return variable;
 }
+
+/*
+	ExecutorRun(queryDesc, ForwardScanDirection, 2L);
index 9e05afa3745..2d424a95889 100644
@@ -234,13 +234,24 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 		/* fill the array */
 		foreach_oid(varid, queryDesc->plannedstmt->sessionVariables)
 		{
-			AclResult	aclresult;
+			/*
+			 * Permission check should be executed on all explicitly used
+			 * variables in the query. For implicitly used variable (like base
+			 * node of assignment indirect) we cannot do permission check,
+			 * because we need read the value (and user can have only UPDATE
+			 * variable). In this case the permission check is executed in
+			 * write time.
+			 */
+			if (varid != queryDesc->plannedstmt->exclSelectPermCheckVarid)
+			{
+				AclResult	aclresult;
 
-			aclresult = object_aclcheck(VariableRelationId, varid,
-										GetUserId(), ACL_SELECT);
-			if (aclresult != ACLCHECK_OK)
-				aclcheck_error(aclresult, OBJECT_VARIABLE,
-							   get_session_variable_name(varid));
+				aclresult = object_aclcheck(VariableRelationId, varid,
+											GetUserId(), ACL_SELECT);
+				if (aclresult != ACLCHECK_OK)
+					aclcheck_error(aclresult, OBJECT_VARIABLE,
+								   get_session_variable_name(varid));
+			}
 
 			estate->es_session_variables[i].value =
 				GetSessionVariable(varid,
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index cd609c6e479..575365eefcd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -4378,6 +4378,16 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_LetStmt:
+			{
+				LetStmt    *stmt = (LetStmt *) node;
+
+				if (WALK(stmt->target))
+					return true;
+				if (WALK(stmt->query))
+					return true;
+			}
+			break;
 		case T_PLAssignStmt:
 			{
 				PLAssignStmt *stmt = (PLAssignStmt *) node;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 401f71d0073..ea16431e22b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -352,6 +352,20 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->rel_notnullatts_hash = NULL;
 	glob->sessionVariables = NIL;
 
+	/*
+	 * The (session) result variable should be stored to global, because it is
+	 * not set in subquery.  When this variable is used other than in base
+	 * node of assignment indirection, we need to check the access rights (and
+	 * then we need to detect this situation). The variable used like base
+	 * node cannot be different than target (result) variable. Because we know
+	 * the result variable before planner invocation, we can simply search of
+	 * usage just this variable, and we don't need to to wait until the end of
+	 * planning when we know basenodeSessionVarid.
+	 */
+	glob->resultVariable = parse->resultVariable;
+	glob->basenodeSessionVarid = InvalidOid;
+	glob->basenodeSessionVarSelectCheck = false;
+
 	/*
 	 * Assess whether it's feasible to use parallel mode for this query. We
 	 * can't do this in a standalone backend, or if the command will try to
@@ -592,6 +606,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	result->sessionVariables = glob->sessionVariables;
 
+	/*
+	 * The session variable used (and only used) like base node for assignemnt
+	 * indirection should be excluded from permission check.
+	 */
+	if (OidIsValid(glob->basenodeSessionVarid) &&
+		(!glob->basenodeSessionVarSelectCheck))
+		result->exclSelectPermCheckVarid = glob->basenodeSessionVarid;
+	else
+		result->exclSelectPermCheckVarid = InvalidOid;
+
 	result->stmt_location = parse->stmt_location;
 	result->stmt_len = parse->stmt_len;
 
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 584cdf84e0f..b86ae00e9a2 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2230,6 +2230,27 @@ fix_param_node(PlannerInfo *root, Param *p)
 			p->paramid = n;
 		}
 
+		/*
+		 * We do SELECT permission check of all variables used by the query
+		 * excluding the variable that is used only as base node of assignment
+		 * indirection. The variable id assigned to this param should be same
+		 * like resultVariable id, and this param should be used only once in
+		 * query. When the variable is referenced by any other param, we
+		 * should to do SELECT permission check for this variable too.
+		 */
+		if (p->parambasenode)
+		{
+			Assert(!OidIsValid(root->glob->basenodeSessionVarid));
+			Assert(root->glob->resultVariable == p->paramvarid);
+
+			root->glob->basenodeSessionVarid = p->paramvarid;
+		}
+		else
+		{
+			if (p->paramvarid == root->glob->resultVariable)
+				root->glob->basenodeSessionVarSelectCheck = true;
+		}
+
 		return (Node *) p;
 	}
 
@@ -3715,7 +3736,7 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
 
 /*
  * Record dependency on a session variable.  The variable can be used as a
- * session variable in an expression list.
+ * session variable in an expression list, or as the target of a LET statement.
  */
 static void
 record_plan_variable_dependency(PlannerInfo *root, Oid varid)
@@ -3817,9 +3838,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 			}
 
 			/*
-			 * Ignore other utility statements, except those (such as EXPLAIN)
-			 * that contain a parsed-but-not-planned query.  For those, we
-			 * just need to transfer our attention to the contained query.
+			 * Ignore other utility statements, except those (such as EXPLAIN
+			 * or LET) that contain a parsed-but-not-planned query.  For
+			 * those, we just need to transfer our attention to the contained
+			 * query.
 			 */
 			query = UtilityContainsQuery(query->utilityStmt);
 			if (query == NULL)
@@ -3842,6 +3864,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 					lappend_oid(context->glob->relationOids, rte->relid);
 		}
 
 								 context, 0);
index c3897aafd21..fca7267ec8b 100644

@@ -94,6 +95,8 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 										 CreateTableAsStmt *stmt);
 static Query *transformCallStmt(ParseState *pstate,
 								CallStmt *stmt);
+static Query *transformLetStmt(ParseState *pstate,
+							   LetStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 								   LockingClause *lc, bool pushedDown);
 #ifdef DEBUG_NODE_TESTS_ENABLED
@@ -341,6 +344,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
 			case T_UpdateStmt:
 			case T_DeleteStmt:
 			case T_MergeStmt:
+			case T_LetStmt:
 				(void) test_raw_expression_coverage(parseTree, NULL);
 				break;
 			default:
@@ -420,6 +424,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
 									   (CallStmt *) parseTree);
 			break;
 
+		case T_LetStmt:
 
 			/*
@@ -471,6 +480,7 @@ stmt_requires_parse_analysis(RawStmt *parseTree)
 		case T_SelectStmt:
 		case T_ReturnStmt:
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			result = true;
 			break;
 
@@ -3319,6 +3329,233 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 	return result;
 }
 
+/*
+ * transformLetStmt -
+				 errmsg("cannot assign to field \"%s\" of session variable \"%s.%s\" because its type %s is not a composite type",
+						attrname,
+	pstate->p_expr_kind = EXPR_KIND_UPDATE_TARGET;
+	foreach_node(TargetEntry, tle, selectQuery->targetList)
+			 * The parameter used as basenode has to have special mark,
+			 * because requires special access when we do SELECT access check.
+			param->parambasenode = true;
+					 errmsg("variable \"%s.%s\" is of type %s, but expression is of type %s",
+	 * Save target session variable ID. It is used later for acquiring an
+	 * AccessShareLock on target variable, setting plan dependency and finally
+	 * for creating VariableDestReceiver.
  * Produce a string representation of a LockClauseStrength value.
  * This should only be applied to valid values (not LCS_NONE).
index 1d9c2298544..5a14132e003 100644
@@ -297,7 +297,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -742,7 +742,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEEP KEY KEYS

 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
@@ -1089,6 +1089,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -12917,6 +12918,38 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY:
+ *				LET STATEMENT
+ *
+ *****************************************************************************/
+LetStmt:	LET ColId opt_indirection '=' a_expr
+				{
+					LetStmt	   *n = makeNode(LetStmt);
+					SelectStmt *select;
+					ResTarget  *res;
+
+					n->target = lcons(makeString($2),
+									  check_indirection($3, yyscanner));
+
+					select = makeNode(SelectStmt);
+					res = makeNode(ResTarget);
+
+					/* create target list for implicit query */
+					res->name = NULL;
+					res->indirection = NIL;
+					res->val = (Node *) $5;
+					res->location = @5;
+
+					select->targetList = list_make1(res);
+					n->query = (Node *) select;
+
+					n->location = @2;
+					$$ = (Node *) n;
+				}
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -18013,6 +18046,7 @@ unreserved_keyword:
 			| LARGE_P
 			| LAST_P
 			| LEAKPROOF
+			| LET
 			| LEVEL
 			| LISTEN
 			| LOAD
@@ -18628,6 +18662,7 @@ bare_label_keyword:
 			| LEAKPROOF
 			| LEAST
 			| LEFT
+			| LET
 			| LEVEL
 			| LIKE
 			| LISTEN
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 1a0e11ba701..ab0400c1f7a 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -235,6 +235,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 				 * Commands inside the DO block or the called procedure might
@@ -1057,6 +1058,11 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_LetStmt:
@@ -2198,6 +2204,10 @@ UtilityContainsQuery(Node *parsetree)
 
+		case T_LetStmt:
+			qry = castNode(Query, ((LetStmt *) parsetree)->query);
+			return qry;
+
 		default:
 			return NULL;
 	}
@@ -2396,6 +2406,10 @@ CreateCommandTag(Node *parsetree)
 			tag = CMDTAG_SELECT;
 			break;
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -3281,6 +3295,7 @@ GetCommandLogLevel(Node *parsetree)
 			break;
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 1d20f3382b0..c8e91be1482 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -2053,6 +2053,17 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
 		query_tree_walker(parsetree, ScanQueryWalker, &acquire,
 						  QTW_IGNORE_RC_SUBQUERIES);
 	}
+
+	/* process session variables */
+	if (OidIsValid(parsetree->resultVariable))
+	{
+		if (acquire)
+			LockDatabaseObject(VariableRelationId, parsetree->resultVariable,
+							   0, AccessShareLock);
+		else
+			UnlockDatabaseObject(VariableRelationId, parsetree->resultVariable,
+								 0, AccessShareLock);
+	}
 }
 
 /*
index 5fc5c15057b..49f77b7928f 100644
@@ -1262,8 +1262,8 @@ static const char *const sql_commands[] = {
@@ -4783,6 +4783,14 @@ match_previous_words(int pattern_id,
 
+/* LET */
index 9f5c6e30fbd..2ebe8477789 100644
@@ -17,11 +17,16 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "nodes/params.h"
 #include "nodes/parsenodes.h"
+#include "tcop/cmdtag.h"
 
 extern void SetSessionVariable(Oid varid, Datum value, bool isNull);
 extern Datum GetSessionVariable(Oid varid, bool *isNull);
 
 extern ObjectAddress CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt);
 
+extern void ExecuteLetStmt(ParseState *pstate, LetStmt *stmt, ParamListInfo params,
+						   QueryEnvironment *queryEnv, QueryCompletion *qc);
+
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3652a14d1e0..524f97ac5c2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -147,6 +147,9 @@ typedef struct Query
 	 */
 	int			resultRelation pg_node_attr(query_jumble_ignore);
 
@@ -2170,6 +2173,18 @@ typedef struct MergeStmt
 
+	ParseLoc	location;
index ed5127942e0..19f25578ba1 100644
@@ -185,6 +185,15 @@ typedef struct PlannerGlobal
 
 	/* list of used session variables */
 	List	   *sessionVariables;
+
+	/* Oid of session variable used like target of LET command */
+	Oid			resultVariable;
+
+	/* oid of session variable used like base node for assignment indirection */
+	Oid			basenodeSessionVarid;
+
+	/* true, if we do SELECT permission check on basenodeSessionVarid */
+	bool		basenodeSessionVarSelectCheck;
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 6ee92f33dab..7e05e75dfbb 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -152,6 +152,13 @@ typedef struct PlannedStmt
 	/* OIDs for PARAM_VARIABLE Params */
 	List	   *sessionVariables;
 
+	/*
+	 * The oid of session variable execluded from permission check. This
+	 * session variable is used as base node of assignment indirection (and it
+	 * is used only there).
+	 */
+	int			exclSelectPermCheckVarid;
+
 	/* statement location in source string (copied from Query) */
 	/* start location, or -1 if unknown */
 	ParseLoc	stmt_location;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index c6ce5a34c05..6aa3467c529 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -404,6 +404,15 @@ typedef struct Param
 	Oid			paramcollid;
 	/* OID of used session variable or InvalidOid if none */
 	Oid			paramvarid pg_node_attr(query_jumble_ignore);
+
+	/*
+	 * true if param is used as base node of assignment indirection (when
+	 * target of LET statement is an array field or an record field). For this
+	 * param we do not check SELECT access right, because this param is used
+	 * just for execution of an modify operation.
+	 */
+	bool		parambasenode;
+
 	/* token location, or -1 if unknown */
 	ParseLoc	location;
 } Param;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 7e7d262b1bb..1ccaacc39ad 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -258,6 +258,7 @@ PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("let", LET, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD, AS_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index ea86954dded..22082c30008 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -186,6 +186,7 @@ PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
 PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
 PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
 PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
+PG_CMDTAG(CMDTAG_LET, "LET", false, false, false)
 PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
 PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
diff --git a/src/test/regress/expected/session_variables_dml.out b/src/test/regress/expected/session_variables_dml.out
index 3e21059acc2..67f78a548dc 100644
--- a/src/test/regress/expected/session_variables_dml.out
+++ b/src/test/regress/expected/session_variables_dml.out
@@ -189,3 +189,280 @@ drop cascades to table svartest_dml.testtab
 DROP ROLE regress_svartest_dml_read_role;
 DROP VARIABLE sesvar40;
 DROP TABLE svartest_dml;
+CREATE VARIABLE sesvar43 AS numeric;
+-- LET stmt is not allowed inside CTE
+WITH x AS (LET sesvar43 = 3.14) SELECT * FROM x;
+ERROR:  syntax error at or near "LET"
+LINE 1: WITH x AS (LET sesvar43 = 3.14) SELECT * FROM x;
+                   ^
+-- LET stmt requires result with exactly one row
+LET sesvar43 = generate_series(1,1);
+-- should fail
+LET sesvar43 = generate_series(1,2);
+ERROR:  expression returned more than one row
+LET sesvar43 = generate_series(1,0);
+ERROR:  expression returned no rows
+CREATE SCHEMA svartest_dml;
+CREATE VARIABLE svartest_dml.sesvar44 AS varchar;
+CREATE TYPE svartest_dml.composite_type AS (a int, b int, c int);
+CREATE VARIABLE svartest_dml.sesvar45 AS svartest_dml.composite_type;
+CREATE OR REPLACE FUNCTION svartest_dml.fx01(numeric)
+RETURNS void AS $$
+LET sesvar43 = $1;
+$$ LANGUAGE sql;
+CREATE OR REPLACE FUNCTION svartest_dml.fx02()
+RETURNS numeric AS $$
+SELECT VARIABLE(sesvar43);
+$$ LANGUAGE sql;
+SELECT svartest_dml.fx01(3.14);
+ fx01 
+------
+ 
+(1 row)
+
+SELECT svartest_dml.fx02(), VARIABLE(sesvar43);
+ fx02 | sesvar43 
+------+----------
+ 3.14 |     3.14
+(1 row)
+
+CREATE OR REPLACE FUNCTION svartest_dml.fx03(s varchar)
+RETURNS varchar AS $$
+BEGIN
+  LET svartest_dml.sesvar44 = s;
+  RETURN VARIABLE(svartest_dml.sesvar44);
+END
+$$ LANGUAGE plpgsql;
+SELECT svartest_dml.fx03('Hello');
+ fx03  
+-------
+ Hello
+(1 row)
+
+CREATE OR REPLACE FUNCTION svartest_dml.fx04(s varchar)
+RETURNS varchar AS $$
+BEGIN
+  LET sesvar44 = s;
+  RETURN VARIABLE(sesvar44);
+END
+$$ LANGUAGE plpgsql
+SET SEARCH_PATH TO 'svartest_dml';
+SELECT svartest_dml.fx04('Hello');
+ fx04  
+-------
+ Hello
+(1 row)
+
+CREATE OR REPLACE FUNCTION svartest_dml.fx05(a int, b int, c int)
+RETURNS svartest_dml.composite_type AS $$
+BEGIN
+  LET svartest_dml.sesvar45 = ROW(a, b, c);
+  RETURN VARIABLE(svartest_dml.sesvar45);
+END;
+$$ LANGUAGE plpgsql;
+SELECT row_to_json(svartest_dml.fx05(10, 20, 30));
+      row_to_json       
+------------------------
+ {"a":10,"b":20,"c":30}
+(1 row)
+
+SELECT VARIABLE(svartest_dml.sesvar45);
+  sesvar45  
+------------
+ (10,20,30)
+(1 row)
+
+SELECT VARIABLE(svartest_dml.sesvar45).*;
+ a  | b  | c  
+----+----+----
+ 10 | 20 | 30
+(1 row)
+
+SELECT VARIABLE(svartest_dml.sesvar45.a);
+ a  
+----
+ 10
+(1 row)
+
+SELECT VARIABLE(svartest_dml.sesvar45).a;
+ a  
+----
+ 10
+(1 row)
+
+ALTER TYPE svartest_dml.composite_type ADD ATTRIBUTE d int;
+-- composite value should be still readable
+SELECT row_to_json(VARIABLE(svartest_dml.sesvar45));
+           row_to_json           
+---------------------------------
+ {"a":10,"b":20,"c":30,"d":null}
+(1 row)
+
+LET svartest_dml.sesvar45 = ROW(100, 200, 300, NULL);
+SELECT row_to_json(VARIABLE(svartest_dml.sesvar45));
+            row_to_json             
+------------------------------------
+ {"a":100,"b":200,"c":300,"d":null}
+(1 row)
+
+-- use variables inside view
+CREATE VIEW svartest_dml.view01 AS SELECT VARIABLE(svartest_dml.sesvar45).*;
+SELECT * FROM svartest_dml.view01;
+  a  |  b  |  c  | d 
+-----+-----+-----+---
+ 100 | 200 | 300 |  
+(1 row)
+
+-- start new connection
+\c
+SELECT * FROM svartest_dml.view01;
+ a | b | c | d 
+---+---+---+---
+   |   |   |  
+(1 row)
+
+LET svartest_dml.sesvar45 = ROW(5, 6, 7, 8);
+SELECT * FROM svartest_dml.view01;
+ a | b | c | d 
+---+---+---+---
+ 5 | 6 | 7 | 8
+(1 row)
+
+-- should fail (dependency)
+DROP VARIABLE svartest_dml.sesvar45;
+ERROR:  cannot drop session variable svartest_dml.sesvar45 because other objects depend on it
+DETAIL:  view svartest_dml.view01 depends on session variable svartest_dml.sesvar45
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP VIEW svartest_dml.view01;
+-- test of access variables from generic plans
+CREATE OR REPLACE FUNCTION svartest_dml.fx06()
+RETURNS numeric AS $$
+BEGIN
+  RETURN VARIABLE(sesvar43);
+END;
+$$ LANGUAGE plpgsql;
+SET plan_cache_mode TO force_generic_plan;
+LET sesvar43 = 6.28;
+SELECT svartest_dml.fx06();
+ fx06 
+------
+ 6.28
+(1 row)
+
+LET sesvar43 = VARIABLE(sesvar43) * 2;
+SELECT svartest_dml.fx06();
+ fx06  
+-------
+ 12.56
+(1 row)
+
+-- plan cache invalidation test
+DROP VARIABLE sesvar43;
+-- should fail
+SELECT svartest_dml.fx06();
+ERROR:  session variable "sesvar43" doesn't exist
+LINE 1: VARIABLE(sesvar43)
+                 ^
+QUERY:  VARIABLE(sesvar43)
+CONTEXT:  PL/pgSQL function svartest_dml.fx06() line 3 at RETURN
+CREATE VARIABLE sesvar43 AS numeric;
+LET sesvar43 = 2.72;
+SELECT svartest_dml.fx06();
+ fx06 
+------
+ 2.72
+(1 row)
+
+DROP VARIABLE sesvar43;
+CREATE DOMAIN svartest_dml.int_not_null AS int CHECK(value IS NOT NULL);
+CREATE VARIABLE svartest_dml.sesvar46 AS svartest_dml.int_not_null;
+-- should fail
+LET svartest_dml.sesvar46 = NULL;
+ERROR:  value for domain svartest_dml.int_not_null violates check constraint "int_not_null_check"
+-- should be ok
+LET svartest_dml.sesvar46 = 100;
+LET svartest_dml.sesvar45 = ROW(1,2,3,4);
+LET svartest_dml.sesvar45.a = 100;
+SELECT row_to_json(VARIABLE(svartest_dml.sesvar45));
+         row_to_json         
+-----------------------------
+ {"a":100,"b":2,"c":3,"d":4}
+(1 row)
+
+CREATE ROLE regress_svartest_dml_write_only_role;
+GRANT USAGE ON SCHEMA svartest_dml TO regress_svartest_dml_write_only_role;
+GRANT UPDATE ON VARIABLE svartest_dml.sesvar45 TO regress_svartest_dml_write_only_role;
+SET ROLE TO regress_svartest_dml_write_only_role;
+-- should fail
+SELECT VARIABLE(svartest_dml.sesvar45);
+ERROR:  permission denied for session variable sesvar45
+-- should be ok
+LET svartest_dml.sesvar45.b = 200;
+SET ROLE TO DEFAULT;
+SELECT row_to_json(VARIABLE(svartest_dml.sesvar45));
+          row_to_json          
+-------------------------------
+ {"a":100,"b":200,"c":3,"d":4}
+(1 row)
+
+CREATE VARIABLE svartest_dml.sesvar47 AS int[];
+LET svartest_dml.sesvar47 = ARRAY[1,2,3];
+GRANT UPDATE ON VARIABLE svartest_dml.sesvar47 TO regress_svartest_dml_write_only_role;
+SET ROLE TO regress_svartest_dml_write_only_role;
+-- should fail
+SELECT VARIABLE(svartest_dml.sesvar47);
+ERROR:  permission denied for session variable sesvar47
+-- should be ok
+LET svartest_dml.sesvar47[1] = 200;
+SET ROLE TO DEFAULT;
+SELECT VARIABLE(svartest_dml.sesvar47);
+ sesvar47  
+-----------
+ {200,2,3}
+(1 row)
+
+CREATE VARIABLE svartest_dml.sesvar48 AS int4multirange[];
+LET svartest_dml.sesvar48 = NULL;
+LET svartest_dml.sesvar48 = '{"{[2,8),[11,14)}","{[5,8),[12,14)}"}';
+LET svartest_dml.sesvar48[2] = '{[5,8),[12,100)}';
+SELECT VARIABLE(svartest_dml.sesvar48);
+                sesvar48                
+----------------------------------------
+ {"{[2,8),[11,14)}","{[5,8),[12,100)}"}
+(1 row)
+
+-- test extended query protocol
+CREATE VARIABLE svartest_dml.sesvar49 AS int;
+LET svartest_dml.sesvar49 = $1 \bind 10 \g
+SELECT VARIABLE(svartest_dml.sesvar49);
+ sesvar49 
+----------
+       10
+(1 row)
+
+LET svartest_dml.sesvar49 = $1 \parse letps
+\bind_named letps 100 \g
+SELECT VARIABLE(svartest_dml.sesvar49);
+ sesvar49 
+----------
+      100
+(1 row)
+
+\close_prepared letps
+DROP SCHEMA svartest_dml CASCADE;
+NOTICE:  drop cascades to 14 other objects
+DETAIL:  drop cascades to session variable svartest_dml.sesvar44
+drop cascades to type svartest_dml.composite_type
+drop cascades to session variable svartest_dml.sesvar45
+drop cascades to function svartest_dml.fx01(numeric)
+drop cascades to function svartest_dml.fx02()
+drop cascades to function svartest_dml.fx03(character varying)
+drop cascades to function svartest_dml.fx04(character varying)
+drop cascades to function svartest_dml.fx05(integer,integer,integer)
+drop cascades to function svartest_dml.fx06()
+drop cascades to type svartest_dml.int_not_null
+drop cascades to session variable svartest_dml.sesvar46
+drop cascades to session variable svartest_dml.sesvar47
+drop cascades to session variable svartest_dml.sesvar48
+drop cascades to session variable svartest_dml.sesvar49
+DROP ROLE regress_svartest_dml_write_only_role;
diff --git a/src/test/regress/sql/session_variables_dml.sql b/src/test/regress/sql/session_variables_dml.sql
index b2870dde9e9..2c8d6c3a497 100644
--- a/src/test/regress/sql/session_variables_dml.sql
+++ b/src/test/regress/sql/session_variables_dml.sql
@@ -159,3 +159,192 @@ DROP ROLE regress_svartest_dml_read_role;
 DROP VARIABLE sesvar40;
 
 DROP TABLE svartest_dml;
+
+CREATE VARIABLE sesvar43 AS numeric;
+
+-- LET stmt is not allowed inside CTE
+WITH x AS (LET sesvar43 = 3.14) SELECT * FROM x;
+
+-- LET stmt requires result with exactly one row
+LET sesvar43 = generate_series(1,1);
+
+-- should fail
+LET sesvar43 = generate_series(1,2);
+LET sesvar43 = generate_series(1,0);
+
+CREATE SCHEMA svartest_dml;
+CREATE VARIABLE svartest_dml.sesvar44 AS varchar;
+CREATE TYPE svartest_dml.composite_type AS (a int, b int, c int);
+CREATE VARIABLE svartest_dml.sesvar45 AS svartest_dml.composite_type;
+
+CREATE OR REPLACE FUNCTION svartest_dml.fx01(numeric)
+RETURNS void AS $$
+LET sesvar43 = $1;
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION svartest_dml.fx02()
+RETURNS numeric AS $$
+SELECT VARIABLE(sesvar43);
+$$ LANGUAGE sql;
+
+SELECT svartest_dml.fx01(3.14);
+SELECT svartest_dml.fx02(), VARIABLE(sesvar43);
+
+CREATE OR REPLACE FUNCTION svartest_dml.fx03(s varchar)
+RETURNS varchar AS $$
+BEGIN
+  LET svartest_dml.sesvar44 = s;
+  RETURN VARIABLE(svartest_dml.sesvar44);
+END
+$$ LANGUAGE plpgsql;
+
+SELECT svartest_dml.fx03('Hello');
+
+CREATE OR REPLACE FUNCTION svartest_dml.fx04(s varchar)
+RETURNS varchar AS $$
+BEGIN
+  LET sesvar44 = s;
+  RETURN VARIABLE(sesvar44);
+END
+$$ LANGUAGE plpgsql
+SET SEARCH_PATH TO 'svartest_dml';
+
+SELECT svartest_dml.fx04('Hello');
+
+CREATE OR REPLACE FUNCTION svartest_dml.fx05(a int, b int, c int)
+RETURNS svartest_dml.composite_type AS $$
+BEGIN
+  LET svartest_dml.sesvar45 = ROW(a, b, c);
+  RETURN VARIABLE(svartest_dml.sesvar45);
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT row_to_json(svartest_dml.fx05(10, 20, 30));
+
+SELECT VARIABLE(svartest_dml.sesvar45);
+SELECT VARIABLE(svartest_dml.sesvar45).*;
+SELECT VARIABLE(svartest_dml.sesvar45.a);
+SELECT VARIABLE(svartest_dml.sesvar45).a;
+
+ALTER TYPE svartest_dml.composite_type ADD ATTRIBUTE d int;
+
+-- composite value should be still readable
+SELECT row_to_json(VARIABLE(svartest_dml.sesvar45));
+
+LET svartest_dml.sesvar45 = ROW(100, 200, 300, NULL);
+SELECT row_to_json(VARIABLE(svartest_dml.sesvar45));
+
+-- use variables inside view
+CREATE VIEW svartest_dml.view01 AS SELECT VARIABLE(svartest_dml.sesvar45).*;
+SELECT * FROM svartest_dml.view01;
+
+-- start new connection
+\c
+SELECT * FROM svartest_dml.view01;
+
+LET svartest_dml.sesvar45 = ROW(5, 6, 7, 8);
+
+SELECT * FROM svartest_dml.view01;
+
+-- should fail (dependency)
+DROP VARIABLE svartest_dml.sesvar45;
+
+DROP VIEW svartest_dml.view01;
+
+-- test of access variables from generic plans
+CREATE OR REPLACE FUNCTION svartest_dml.fx06()
+RETURNS numeric AS $$
+BEGIN
+  RETURN VARIABLE(sesvar43);
+END;
+$$ LANGUAGE plpgsql;
+
+SET plan_cache_mode TO force_generic_plan;
+
+LET sesvar43 = 6.28;
+
+SELECT svartest_dml.fx06();
+
+LET sesvar43 = VARIABLE(sesvar43) * 2;
+
+SELECT svartest_dml.fx06();
+
+-- plan cache invalidation test
+DROP VARIABLE sesvar43;
+
+-- should fail
+SELECT svartest_dml.fx06();
+
+CREATE VARIABLE sesvar43 AS numeric;
+
+LET sesvar43 = 2.72;
+
+SELECT svartest_dml.fx06();
+
+DROP VARIABLE sesvar43;
+
+CREATE DOMAIN svartest_dml.int_not_null AS int CHECK(value IS NOT NULL);
+CREATE VARIABLE svartest_dml.sesvar46 AS svartest_dml.int_not_null;
+
+-- should fail
+LET svartest_dml.sesvar46 = NULL;
+-- should be ok
+LET svartest_dml.sesvar46 = 100;
+
+LET svartest_dml.sesvar45 = ROW(1,2,3,4);
+LET svartest_dml.sesvar45.a = 100;
+SELECT row_to_json(VARIABLE(svartest_dml.sesvar45));
+
+CREATE ROLE regress_svartest_dml_write_only_role;
+GRANT USAGE ON SCHEMA svartest_dml TO regress_svartest_dml_write_only_role;
+GRANT UPDATE ON VARIABLE svartest_dml.sesvar45 TO regress_svartest_dml_write_only_role;
+
+SET ROLE TO regress_svartest_dml_write_only_role;
+
+-- should fail
+SELECT VARIABLE(svartest_dml.sesvar45);
+
+-- should be ok
+LET svartest_dml.sesvar45.b = 200;
+
+SET ROLE TO DEFAULT;
+
+SELECT row_to_json(VARIABLE(svartest_dml.sesvar45));
+
+CREATE VARIABLE svartest_dml.sesvar47 AS int[];
+LET svartest_dml.sesvar47 = ARRAY[1,2,3];
+
+GRANT UPDATE ON VARIABLE svartest_dml.sesvar47 TO regress_svartest_dml_write_only_role;
+
+SET ROLE TO regress_svartest_dml_write_only_role;
+
+-- should fail
+SELECT VARIABLE(svartest_dml.sesvar47);
+
+-- should be ok
+LET svartest_dml.sesvar47[1] = 200;
+
+SET ROLE TO DEFAULT;
+
+SELECT VARIABLE(svartest_dml.sesvar47);
+
+CREATE VARIABLE svartest_dml.sesvar48 AS int4multirange[];
+LET svartest_dml.sesvar48 = NULL;
+LET svartest_dml.sesvar48 = '{"{[2,8),[11,14)}","{[5,8),[12,14)}"}';
+LET svartest_dml.sesvar48[2] = '{[5,8),[12,100)}';
+SELECT VARIABLE(svartest_dml.sesvar48);
+
+-- test extended query protocol
+CREATE VARIABLE svartest_dml.sesvar49 AS int;
+
+LET svartest_dml.sesvar49 = $1 \bind 10 \g
+SELECT VARIABLE(svartest_dml.sesvar49);
+
+LET svartest_dml.sesvar49 = $1 \parse letps
+\bind_named letps 100 \g
+SELECT VARIABLE(svartest_dml.sesvar49);
+
+\close_prepared letps
+
+DROP SCHEMA svartest_dml CASCADE;
+DROP ROLE regress_svartest_dml_write_only_role;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6501516c086..20a44685879 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1546,6 +1546,7 @@ LargeObjectDesc
 Latch
 LauncherLastStartTimesEntry
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
-- 
2.51.0

From d3521b1252bb1e493f8cfd5dc9e77b2329c3b4e7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 10/15] svariableReceiver

allows to store result of the query to session variable

Check correct format of result - one column, one row.
---
 src/backend/executor/Makefile            |   1 +
 src/backend/executor/meson.build         |   1 +
 src/backend/executor/svariableReceiver.c | 173 +++++++++++++++++++++++
 src/backend/tcop/dest.c                  |   7 +
 src/include/executor/svariableReceiver.h |  22 +++
 src/include/tcop/dest.h                  |   1 +
 src/tools/pgindent/typedefs.list         |   1 +
 7 files changed, 206 insertions(+)
 create mode 100644 src/backend/executor/svariableReceiver.c
 create mode 100644 src/include/executor/svariableReceiver.h

index 2cea41f8771..491092fcc4c 100644
index 00000000000..aadd446f277
@@ -0,0 +1,173 @@
+#include "access/htup_details.h"
+ * there are more than one received rows.  A received tuple cannot to have
+ * deleted attributes.  The value is detoasted before storing it in the
+	Form_pg_attribute attr;
+	Oid			typid PG_USED_FOR_ASSERTS_ONLY;
+	Oid			collid PG_USED_FOR_ASSERTS_ONLY;
+	int32		typmod PG_USED_FOR_ASSERTS_ONLY;
+	Assert(typeinfo->natts == 1);
+	attr = TupleDescAttr(typeinfo, 0);
+	Assert(!attr->attisdropped);
+	Assert(attr->atttypid == typid);
+	Assert(attr->atttypmod < 0 || attr->atttypmod == typmod);
+	value = slot->tts_values[0];
+	isnull = slot->tts_isnull[0];
index b620766c938..b2f764b657f 100644

 
index 00c092e3d7c..6ce3ea0e617 100644
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9c719841c28..6501516c086 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2658,6 +2658,7 @@ STRLEN
 SV
 SVariableData
 SVariable
+SVariableState
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.51.0

From f281389e2a2ae5abb3f8a0d69072bfd5fea0aa1a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 30 May 2025 09:31:06 +0200
Subject: [PATCH 08/15] collect session variables used in plan and assign
 paramid

In the plan stage we need to collect used session variables. On the
order of this list, the param nodes gets paramid (fix_param_node).
This number is used (later) as index to buffer of values of the
used session variables. The buffer is prepared and filled by executor.

Some unsupported optimizations are disabled:

* parallel execution
* simple expression execution in PL/pgSQL
* SQL functions inlining

Before execution of query with session variables we need to collect
used session variables. This list is used for
---
 doc/src/sgml/parallel.sgml                |   6 ++
 src/backend/catalog/dependency.c          |   5 +
 src/backend/optimizer/plan/planner.c      |  11 ++
 src/backend/optimizer/plan/setrefs.c      | 124 +++++++++++++++++++++-
 src/backend/optimizer/prep/prepjointree.c |   3 +
 src/backend/optimizer/util/clauses.c      |  33 +++++-
 src/backend/utils/cache/plancache.c       |   6 +-
 src/backend/utils/fmgr/fmgr.c             |  10 +-
 src/include/nodes/pathnodes.h             |   5 +
 src/include/nodes/plannodes.h             |   3 +
 src/include/optimizer/planmain.h          |   2 +
 11 files changed, 199 insertions(+), 9 deletions(-)

  <sect2 id="parallel-labeling">
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 1d62e63d4f7..eb91b46b128 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1875,6 +1875,11 @@ find_expr_references_walker(Node *node,
 	{
 		Param	   *param = (Param *) node;
 
+		/* a variable parameter depends on the session variable */
+		if (param->paramkind == PARAM_VARIABLE)
+			add_object_address(VariableRelationId, param->paramvarid, 0,
+							   context->addrs);
+
 		/* A parameter must depend on the parameter's datatype */
 		add_object_address(TypeRelationId, param->paramtype, 0,
 						   context->addrs);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 41bd8353430..401f71d0073 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -350,6 +350,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->dependsOnRole = false;
 	glob->partition_directory = NULL;
 	glob->rel_notnullatts_hash = NULL;
+	glob->sessionVariables = NIL;
 
 	/*
@@ -588,6 +589,9 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
@@ -770,6 +774,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root,
 	 */
 	pull_up_subqueries(root);
 
+	/*
index 6950eff2c5b..584cdf84e0f 100644

 /*****************************************************************************
@@ -1322,6 +1325,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 	return plan;
 }
 
+/*
@@ -2022,8 +2069,9 @@ copyVar(Var *var)
@@ -2113,6 +2161,13 @@ fix_expr_common(PlannerInfo *root, Node *node)
 
 /*
@@ -2122,6 +2177,10 @@ fix_expr_common(PlannerInfo *root, Node *node)
@@ -2140,6 +2199,40 @@ fix_param_node(PlannerInfo *root, Param *p)
+		foreach_oid(varid, root->glob->sessionVariables)
+			if (varid == p->paramvarid)
 
@@ -2201,7 +2294,10 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
@@ -2223,7 +2319,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
@@ -3616,6 +3713,25 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
 	}
 }
 
+/*
+ * session variable in an expression list.
index 35e8d3c183b..d2470c9b2db 100644
@@ -1645,6 +1645,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 
index 81d768ff2a2..55c2ec50c95 100644
@@ -25,6 +25,7 @@
@@ -939,6 +940,13 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 
+		/* we don't support passing session variables to workers */
+		if (param->paramkind == PARAM_VARIABLE)
+		{
+			if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+				return true;
+		}
+
 		if (param->paramkind != PARAM_EXEC ||
 			!list_member_int(context->safe_param_ids, param->paramid))
 		{
@@ -2397,6 +2405,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
  *	  value of the Param.
  * 2. Fold stable, as well as immutable, functions to constants.
  * 3. Reduce PlaceHolderVar nodes to their contained expressions.
+ * 4. Current value of session variable can be used for estimation too.
  *--------------------
  */
 Node *
@@ -2523,6 +2532,27 @@ eval_const_expressions_mutator(Node *node,
 						}
 					}
 				}
+				else if (param->paramkind == PARAM_VARIABLE &&
+						 context->estimate)
+				{
+					int16		typLen;
+					bool		typByVal;
+					Datum		pval;
+					bool		isnull;
+
+					get_typlenbyval(param->paramtype,
+									&typLen, &typByVal);
+
+					pval = GetSessionVariable(param->paramvarid, &isnull);
+
+					return (Node *) makeConst(param->paramtype,
+											  param->paramtypmod,
+											  param->paramcollid,
+											  (int) typLen,
+											  pval,
+											  isnull,
+											  typByVal);
+				}
 
 				/*
@@ -4822,7 +4852,8 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 
index 6661d2c6b73..ab1f2af13e5 100644
 
@@ -153,6 +154,7 @@ InitPlanCache(void)
 
 /*
@@ -2196,7 +2198,9 @@ PlanCacheRelCallback(Datum arg, Oid relid)
 
 /*
index 0fe63c6bb83..d26e744e296 100644
@@ -1991,9 +1991,13 @@ get_call_expr_arg_stable(Node *expr, int argnum)
 
 	return false;
 }
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index b12a2508d8c..ed5127942e0 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -182,6 +182,9 @@ typedef struct PlannerGlobal
 
 	/* hash table for NOT NULL attnums of relations */
 	struct HTAB *rel_notnullatts_hash pg_node_attr(read_write_ignore);
+
+	/* list of used session variables */
+	List	   *sessionVariables;
 } PlannerGlobal;
 
@@ -526,6 +529,8 @@ struct PlannerInfo
 
 	/*
 	 * The rangetable index for the RTE_GROUP RTE, or 0 if there is no
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3d196f5078e..6ee92f33dab 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -149,6 +149,9 @@ typedef struct PlannedStmt
 	/* non-null if this is utility stmt */
 	Node	   *utilityStmt;
 
+	/* OIDs for PARAM_VARIABLE Params */
+	List	   *sessionVariables;
+
 	/* statement location in source string (copied from Query) */
 	/* start location, or -1 if unknown */
 	ParseLoc	stmt_location;
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 9d3debcab28..ba4305d61a7 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -131,4 +131,6 @@ extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
 extern void record_plan_type_dependency(PlannerInfo *root, Oid typid);
 extern bool extract_query_dependencies_walker(Node *node, PlannerInfo *context);
 
2.51.0

From 1dcb7a55b38ee44632d5ee3dd8e04f79a66d9c26 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 07:32:01 +0200
Subject: [PATCH 09/15] fill an auxiliary buffer with values of session
 variables used in query

and locks variables used in query. Now we can read the content of any
session variable. Direct reading from expression executor is not allowed,
so we cannot to use session variables inside CALL or EXECUTE commands
(can be supported with direct access to session variables (from expression
executor) - postponed).

Using session variables blocks parallel query execution. It is not
technical problem (it just needs a serialization/deserialization of
es_session_varibles buffer), but it increases a size of patch (and then
it is postponed).
---
 src/backend/executor/execExpr.c               |  29 +++
 src/backend/executor/execMain.c               |  56 +++++
 src/backend/utils/cache/plancache.c           |  24 ++-
 src/include/nodes/execnodes.h                 |  14 ++
 .../expected/session_variables_dml.out        | 191 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   7 +-
 .../regress/sql/session_variables_dml.sql     | 161 +++++++++++++++
 src/tools/pgindent/typedefs.list              |   1 +
 8 files changed, 479 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/session_variables_dml.out
 create mode 100644 src/test/regress/sql/session_variables_dml.sql

index f1569879b52..0457a729537 100644
@@ -1069,6 +1069,35 @@ ExecInitExprRec(Expr *node, ExprState *state,
+							 * In this case, pass the value like a constant.
 						elog(ERROR, "unrecognized paramkind: %d",
 							 (int) param->paramkind);
index 831c55ce787..9e05afa3745 100644
 #include "executor/execPartition.h"
@@ -196,6 +198,60 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 
+	/*
+	 * The executor doesn't work with session variables directly. Values of
+	 * related session variables are copied to a dedicated array, and this
+	 * array is passed to the executor. This array is stable "snapshot" of
+	 * values of used session variables. There are three benefits of this
+	 * strategy:
+	 *
+	 * - consistency with external parameters and plpgsql variables,
+	 *
+	 * - session variables can be parallel safe,
+	 *
+	 * - we don't need make fresh copy for any read of session variable (this
+	 * is necessary because the internally the session variable can be changed
+	 * inside query execution time, and then a reference to previously
+	 * returned value can be corrupted).
+	 */
+	if (queryDesc->plannedstmt->sessionVariables)
+	{
+		int			nSessionVariables;
+		int			i = 0;
+
+		/*
+		 * In this case, the query uses session variables, but we have to
+		 * prepare the array with passed values (of used session variables)
+		 * first.
+		 */
+		Assert(!IsParallelWorker());
+		nSessionVariables = list_length(queryDesc->plannedstmt->sessionVariables);
+
+		/* create the array used for passing values of used session variables */
+		estate->es_session_variables = (SessionVariableValue *)
+			palloc(nSessionVariables * sizeof(SessionVariableValue));
+
+		/* fill the array */
+		foreach_oid(varid, queryDesc->plannedstmt->sessionVariables)
+		{
+			AclResult	aclresult;
+
+			aclresult = object_aclcheck(VariableRelationId, varid,
+										GetUserId(), ACL_SELECT);
+			if (aclresult != ACLCHECK_OK)
+				aclcheck_error(aclresult, OBJECT_VARIABLE,
+							   get_session_variable_name(varid));
+
+			estate->es_session_variables[i].value =
+				GetSessionVariable(varid,
+								   &estate->es_session_variables[i].isnull);
+
+			i++;
+		}
+
+		estate->es_num_session_variables = nSessionVariables;
+	}
+
 	/*
 	 * Fill in the query environment, if any, from queryDesc.
 	 */
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index ab1f2af13e5..1d20f3382b0 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -2043,9 +2043,12 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
 
 	/*
 		query_tree_walker(parsetree, ScanQueryWalker, &acquire,
@@ -2053,7 +2056,8 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
 }
 
 /*
@@ -2068,6 +2072,20 @@ ScanQueryWalker(Node *node, bool *acquire)
 
 	/*
 	 * Do NOT recurse into Query nodes, because ScanQueryForLocks already
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a36653c37f9..43cd47b5280 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -645,6 +645,16 @@ typedef struct AsyncRequest
 								 * tuples) */
 } AsyncRequest;
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+	bool		isnull;
+	Datum		value;
+} SessionVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -704,6 +714,10 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */

 	/* Other working state: */
diff --git a/src/test/regress/expected/session_variables_dml.out b/src/test/regress/expected/session_variables_dml.out
new file mode 100644
index 00000000000..3e21059acc2
--- /dev/null
+++ b/src/test/regress/expected/session_variables_dml.out
@@ -0,0 +1,191 @@
+CREATE VARIABLE sesvar40 AS int;
+-- should not be accessible without variable's fence
+-- should fail
+SELECT sesvar40;
+ERROR:  column "sesvar40" does not exist
+LINE 1: SELECT sesvar40;
+               ^
+-- should be ok
+SELECT VARIABLE(sesvar40);
+ sesvar40 
+----------
+         
+(1 row)
+
+CREATE SCHEMA svartest_dml;
+CREATE VARIABLE svartest_dml.sesvar41 AS int;
+-- identifier collision test
+CREATE TABLE svartest_dml(sesvar41 int);
+INSERT INTO svartest_dml VALUES(100);
+SELECT sesvar41 FROM svartest_dml; -- 100
+ sesvar41 
+----------
+      100
+(1 row)
+
+SELECT VARIABLE(svartest_dml.sesvar41); -- NULL
+ sesvar41 
+----------
+         
+(1 row)
+
+-- should fail
+SELECT VARIABLE(sesvar41);
+ERROR:  session variable "sesvar41" doesn't exist
+LINE 1: SELECT VARIABLE(sesvar41);
+                        ^
+SET SEARCH_PATH TO svartest_dml;
+-- should be ok
+SELECT VARIABLE(sesvar41);
+ sesvar41 
+----------
+         
+(1 row)
+
+SET SEARCH_PATH TO DEFAULT;
+-- should not crash
+DO $$
+BEGIN
+  RAISE NOTICE '%', VARIABLE(svartest_dml.sesvar41);
+END;
+$$;
+NOTICE:  <NULL>
+CREATE OR REPLACE FUNCTION svartest_dml.testsql()
+RETURNS int AS $$
+SELECT VARIABLE(svartest_dml.sesvar41);
+$$ LANGUAGE sql;
+SELECT svartest_dml.testsql();
+ testsql 
+---------
+        
+(1 row)
+
+-- session variable cannot be used as parameter of CALL or EXECUTE
+CREATE OR REPLACE PROCEDURE svartest_dml.proc(int)
+AS $$
+BEGIN
+  RAISE NOTICE '%', $1;
+END;
+$$ LANGUAGE plpgsql;
+-- should not crash
+CALL svartest_dml.proc(VARIABLE(svartest_dml.sesvar41));
+ERROR:  session variable reference is not supported here
+LINE 1: CALL svartest_dml.proc(VARIABLE(svartest_dml.sesvar41));
+                                        ^
+PREPARE svartest_dml_prepstmt(int) AS SELECT $1;
+-- should not crash
+EXECUTE svartest_dml_prepstmt(VARIABLE(svartest_dml.sesvar41));
+ERROR:  session variable reference is not supported here
+LINE 1: EXECUTE svartest_dml_prepstmt(VARIABLE(svartest_dml.sesvar41...
+                                               ^
+DROP PROCEDURE svartest_dml.proc;
+DEALLOCATE svartest_dml_prepstmt;
+-- domains are supported
+CREATE DOMAIN svartest_dml_int_not_null AS int CHECK(value IS NOT NULL);
+CREATE VARIABLE svartest_dml.svartest42 AS svartest_dml_int_not_null;
+-- should fail
+SELECT VARIABLE(svartest_dml.svartest42);
+ERROR:  value for domain svartest_dml_int_not_null violates check constraint "svartest_dml_int_not_null_check"
+DROP VARIABLE svartest_dml.svartest42;
+DROP DOMAIN svartest_dml_int_not_null;
+CREATE ROLE regress_svartest_dml_read_role;
+CREATE OR REPLACE FUNCTION svartest_dml.func_secdef()
+RETURNS int AS $$
+SELECT VARIABLE(svartest_dml.sesvar41);
+$$ LANGUAGE SQL SECURITY DEFINER;
+GRANT USAGE ON SCHEMA svartest_dml TO regress_svartest_dml_read_role;
+SET ROLE TO regress_svartest_dml_read_role;
+-- should fail
+SELECT VARIABLE(svartest_dml.sesvar41);
+ERROR:  permission denied for session variable sesvar41
+-- should fail
+DO $$
+BEGIN
+  RAISE NOTICE '%', VARIABLE(svartest_dml.sesvar41);
+END;
+$$;
+ERROR:  permission denied for session variable sesvar41
+CONTEXT:  PL/pgSQL expression "VARIABLE(svartest_dml.sesvar41)"
+PL/pgSQL function inline_code_block line 3 at RAISE
+-- using sql function should to fail
+SELECT svartest_dml.testsql();
+ERROR:  permission denied for session variable sesvar41
+CONTEXT:  SQL function "testsql" statement 1
+-- using security definer should be ok
+SELECT svartest_dml.func_secdef();
+ func_secdef 
+-------------
+            
+(1 row)
+
+SET ROLE TO DEFAULT;
+DROP FUNCTION svartest_dml.func_secdef();
+GRANT SELECT ON VARIABLE svartest_dml.sesvar41 TO regress_svartest_dml_read_role;
+SET ROLE TO regress_svartest_dml_read_role;
+-- should be ok
+SELECT VARIABLE(svartest_dml.sesvar41);
+ sesvar41 
+----------
+         
+(1 row)
+
+-- should be ok
+DO $$
+BEGIN
+  RAISE NOTICE '%', VARIABLE(svartest_dml.sesvar41);
+END;
+$$;
+NOTICE:  <NULL>
+SET ROLE TO DEFAULT;
+CREATE TABLE svartest_dml.testtab(a int);
+INSERT INTO svartest_dml.testtab SELECT * FROM generate_series(1,1000);
+CREATE INDEX svartest_dml_testtab_a ON svartest_dml.testtab(a);
+ANALYZE svartest_dml.testtab;
+-- force index
+SET enable_seqscan TO OFF;
+-- index scan should be used
+EXPLAIN (COSTS OFF) SELECT * FROM svartest_dml.testtab WHERE a = VARIABLE(sesvar40);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Index Only Scan using svartest_dml_testtab_a on testtab
+   Index Cond: (a = VARIABLE(sesvar40))
+(2 rows)
+
+DROP INDEX svartest_dml.svartest_dml_testtab_a;
+SET enable_seqscan TO DEFAULT;
+-- parallel execution should be blocked
+-- Encourage use of parallel plans
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers_per_gather = 2;
+-- parallel plan should be used
+EXPLAIN (COSTS OFF) SELECT * FROM svartest_dml.testtab WHERE a = 100;
+             QUERY PLAN             
+------------------------------------
+ Gather
+   Workers Planned: 2
+   ->  Parallel Seq Scan on testtab
+         Filter: (a = 100)
+(4 rows)
+
+-- parallel plan should not be used
+EXPLAIN (COSTS OFF) SELECT * FROM svartest_dml.testtab WHERE a = VARIABLE(sesvar40);
+             QUERY PLAN             
+------------------------------------
+ Seq Scan on testtab
+   Filter: (a = VARIABLE(sesvar40))
+(2 rows)
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers_per_gather;
+DROP SCHEMA svartest_dml CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to session variable svartest_dml.sesvar41
+drop cascades to function svartest_dml.testsql()
+drop cascades to table svartest_dml.testtab
+DROP ROLE regress_svartest_dml_read_role;
+DROP VARIABLE sesvar40;
+DROP TABLE svartest_dml;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95c76baf9ae..b16ec065950 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -115,7 +115,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
 # NB: temp.sql does reconnects which transiently use 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml session_variables_ddl session_variables_acl
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
 
 # ----------
 # Another group of parallel tests
@@ -140,3 +140,8 @@ test: fast_default
 # run tablespace test at the end because it drops the tablespace created during
 # setup that other tests may use.
 test: tablespace
+
+# ----------
+# Another group of parallel tests (session variables related)
+# ----------
+test: session_variables_ddl session_variables_acl session_variables_dml
diff --git a/src/test/regress/sql/session_variables_dml.sql b/src/test/regress/sql/session_variables_dml.sql
new file mode 100644
index 00000000000..b2870dde9e9
--- /dev/null
+++ b/src/test/regress/sql/session_variables_dml.sql
@@ -0,0 +1,161 @@
+CREATE VARIABLE sesvar40 AS int;
+
+-- should not be accessible without variable's fence
+-- should fail
+SELECT sesvar40;
+
+-- should be ok
+SELECT VARIABLE(sesvar40);
+
+CREATE SCHEMA svartest_dml;
+CREATE VARIABLE svartest_dml.sesvar41 AS int;
+
+-- identifier collision test
+CREATE TABLE svartest_dml(sesvar41 int);
+INSERT INTO svartest_dml VALUES(100);
+
+SELECT sesvar41 FROM svartest_dml; -- 100
+SELECT VARIABLE(svartest_dml.sesvar41); -- NULL
+
+-- should fail
+SELECT VARIABLE(sesvar41);
+
+SET SEARCH_PATH TO svartest_dml;
+
+-- should be ok
+SELECT VARIABLE(sesvar41);
+
+SET SEARCH_PATH TO DEFAULT;
+
+-- should not crash
+DO $$
+BEGIN
+  RAISE NOTICE '%', VARIABLE(svartest_dml.sesvar41);
+END;
+$$;
+
+CREATE OR REPLACE FUNCTION svartest_dml.testsql()
+RETURNS int AS $$
+SELECT VARIABLE(svartest_dml.sesvar41);
+$$ LANGUAGE sql;
+
+SELECT svartest_dml.testsql();
+
+-- session variable cannot be used as parameter of CALL or EXECUTE
+CREATE OR REPLACE PROCEDURE svartest_dml.proc(int)
+AS $$
+BEGIN
+  RAISE NOTICE '%', $1;
+END;
+$$ LANGUAGE plpgsql;
+
+-- should not crash
+CALL svartest_dml.proc(VARIABLE(svartest_dml.sesvar41));
+
+PREPARE svartest_dml_prepstmt(int) AS SELECT $1;
+
+-- should not crash
+EXECUTE svartest_dml_prepstmt(VARIABLE(svartest_dml.sesvar41));
+
+DROP PROCEDURE svartest_dml.proc;
+DEALLOCATE svartest_dml_prepstmt;
+
+-- domains are supported
+CREATE DOMAIN svartest_dml_int_not_null AS int CHECK(value IS NOT NULL);
+CREATE VARIABLE svartest_dml.svartest42 AS svartest_dml_int_not_null;
+
+-- should fail
+SELECT VARIABLE(svartest_dml.svartest42);
+
+DROP VARIABLE svartest_dml.svartest42;
+DROP DOMAIN svartest_dml_int_not_null;
+
+CREATE ROLE regress_svartest_dml_read_role;
+
+CREATE OR REPLACE FUNCTION svartest_dml.func_secdef()
+RETURNS int AS $$
+SELECT VARIABLE(svartest_dml.sesvar41);
+$$ LANGUAGE SQL SECURITY DEFINER;
+
+GRANT USAGE ON SCHEMA svartest_dml TO regress_svartest_dml_read_role;
+
+SET ROLE TO regress_svartest_dml_read_role;
+
+-- should fail
+SELECT VARIABLE(svartest_dml.sesvar41);
+
+-- should fail
+DO $$
+BEGIN
+  RAISE NOTICE '%', VARIABLE(svartest_dml.sesvar41);
+END;
+$$;
+
+-- using sql function should to fail
+SELECT svartest_dml.testsql();
+
+-- using security definer should be ok
+SELECT svartest_dml.func_secdef();
+
+SET ROLE TO DEFAULT;
+
+DROP FUNCTION svartest_dml.func_secdef();
+
+GRANT SELECT ON VARIABLE svartest_dml.sesvar41 TO regress_svartest_dml_read_role;
+
+SET ROLE TO regress_svartest_dml_read_role;
+
+-- should be ok
+SELECT VARIABLE(svartest_dml.sesvar41);
+
+-- should be ok
+DO $$
+BEGIN
+  RAISE NOTICE '%', VARIABLE(svartest_dml.sesvar41);
+END;
+$$;
+
+SET ROLE TO DEFAULT;
+
+CREATE TABLE svartest_dml.testtab(a int);
+
+INSERT INTO svartest_dml.testtab SELECT * FROM generate_series(1,1000);
+
+CREATE INDEX svartest_dml_testtab_a ON svartest_dml.testtab(a);
+
+ANALYZE svartest_dml.testtab;
+
+-- force index
+SET enable_seqscan TO OFF;
+
+-- index scan should be used
+EXPLAIN (COSTS OFF) SELECT * FROM svartest_dml.testtab WHERE a = VARIABLE(sesvar40);
+
+DROP INDEX svartest_dml.svartest_dml_testtab_a;
+
+SET enable_seqscan TO DEFAULT;
+
+-- parallel execution should be blocked
+-- Encourage use of parallel plans
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers_per_gather = 2;
+
+-- parallel plan should be used
+EXPLAIN (COSTS OFF) SELECT * FROM svartest_dml.testtab WHERE a = 100;
+
+-- parallel plan should not be used
+EXPLAIN (COSTS OFF) SELECT * FROM svartest_dml.testtab WHERE a = VARIABLE(sesvar40);
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers_per_gather;
+
+DROP SCHEMA svartest_dml CASCADE;
+DROP ROLE regress_svartest_dml_read_role;
+
+DROP VARIABLE sesvar40;
+
+DROP TABLE svartest_dml;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5d89ab0d76e..9c719841c28 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2716,6 +2716,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
-- 
2.51.0

From fc6ca5b8c2129134a73bde65d60157c1c477403d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 30 May 2025 22:44:58 +0200
Subject: [PATCH 07/15] local HASHTAB for currently used session variables and
 low level access functions

Hash entries related to dropped session variables are not released. The memory
cleaning is implemented in memory-cleaning-after-DROP-VARIABLE patch.
---
 doc/src/sgml/glossary.sgml              |   5 +-
 src/backend/commands/session_variable.c | 428 ++++++++++++++++++++++++
 src/include/commands/session_variable.h |   3 +
 src/tools/pgindent/typedefs.list        |   2 +
 4 files changed, 436 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index c37fd5da50b..97cd13957bb 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -1717,8 +1717,9 @@
     <para>
      A persistent database object that holds a value in session memory.  This
      value is private to each session and is released when the session ends.
-     Read or write access to session variables is controlled by privileges,
-     similar to other database objects.
+     The default value of the session variable is null.  Read or write access
+     to session variables is controlled by privileges, similar to other database
+     objects.
     </para>
     <para>
      For more information, see <xref linkend="ddl-session-variables"/>.
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index f641e00c1ac..dbc054795bb 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -14,14 +14,442 @@
  */
 #include "postgres.h"
 
+#include "access/htup_details.h"
+#include "utils/memutils.h"
+	 * usually stored in fn_mcxt.  We do not have access to that memory
+	 * context for session variables, but we can use TopTransactionContext
+	 * instead. A fresh value is forced when we detect we are in a different
+	 * transaction (the local transaction ID differs from
+	 * domain_check_extra_lxid).
+	 * from its hashvalue, we have to iterate over all items in the hash
+	 * bucket.
+		 * values get recycled, and a new session variable could have got the
+		 * same OID.  We do a second check against the 64-bit LSN when the
+		 * variable was created.
+			 * If there was an invalidation message, the variable might still
+			 * be valid, but we have to check with the system catalog.
+GetSessionVariable(Oid varid, bool *isNull)
+	 * be false, if an invalidation message was processed during the domain
+	 * check. But the variable and all its dependencies are locked now, so we
+	 * don't need to repeat the validation.
 
 /*
  * Creates a new variable
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 49f36ac6885..9f5c6e30fbd 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -19,6 +19,9 @@
 #include "parser/parse_node.h"
 #include "nodes/parsenodes.h"
 
+extern void SetSessionVariable(Oid varid, Datum value, bool isNull);
+extern Datum GetSessionVariable(Oid varid, bool *isNull);
+
 extern ObjectAddress CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt);
 
 #endif
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 60e55a8058c..5d89ab0d76e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2656,6 +2656,8 @@ SSL_CTX
 STARTUPINFO
 STRLEN
 SV
+SVariableData
+SVariable
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.51.0

From c4d2abdf4cefc345f2e28f949bc1020516320e4f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 30 May 2025 07:27:27 +0200
Subject: [PATCH 06/15] session variable fences parsing

The session variables can be used in query only inside the variable fence.
This is special syntax VARIABLE(varname), that eliminates a risk of
collision between variable and column identifier.

The session variables cannot be used as parameters of CALL or EXECUTE
commands. These commands evaluates arguments by direct call of expression
executor, and direct access to session variables from expression executor
will be implemented later (in next step).
---
 doc/src/sgml/ddl.sgml               |  11 ++
 src/backend/catalog/namespace.c     | 289 ++++++++++++++++++++++++++++
 src/backend/commands/prepare.c      |   8 +
 src/backend/nodes/nodeFuncs.c       |   6 +
 src/backend/parser/analyze.c        |   7 +
 src/backend/parser/gram.y           |  28 ++-
 src/backend/parser/parse_expr.c     | 208 +++++++++++++++++++-
 src/backend/parser/parse_merge.c    |   1 +
 src/backend/parser/parse_target.c   |   7 +
 src/backend/utils/adt/ruleutils.c   |  46 +++++
 src/backend/utils/cache/lsyscache.c |  24 +++
 src/include/catalog/namespace.h     |   2 +
 src/include/nodes/parsenodes.h      |  12 ++
 src/include/nodes/primnodes.h       |   5 +
 src/include/parser/parse_node.h     |   1 +
 src/include/utils/lsyscache.h       |   4 +
 src/pl/plpgsql/src/pl_exec.c        |   3 +-
 src/tools/pgindent/typedefs.list    |   1 +
 18 files changed, 659 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 420a4d9ff11..2655fa5e7ce 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5396,6 +5396,17 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     The session variable holds value in session memory.  This value is private
     to each session and is released when the session ends.
    </para>
+
+   <para>
+    In an query the session variable can be used only inside
+    <firstterm>variable fence</firstterm>. This is special syntax for
+    session variable identifier, and can be used only for session variable
+    identifier. The special syntax for accessing session variables removes
+    risk of collisions between variable identifiers and column names.
+<programlisting>
+SELECT VARIABLE(current_user_id);
+</programlisting>
+   </para>
   </sect1>
 
  <sect1 id="ddl-others">
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 800f37dc2ea..b20d2074382 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -3567,6 +3567,295 @@ NamesFromList(List *names)
 	return result;
 }
 
+/* -----
+					 * a.b can mean "schema"."variable" or
+					 * "variable"."attribute".  Check both variants, and
+					 * returns InvalidOid with not_unique flag, when both
+					 * interpretations are possible.
+					 * The syntax ident.* is used only by relation aliases,
+					 * and then this identifier cannot be a reference to
+						 * schema.variable.attribute.
+		 * If no invalidation message were processed, we're done!
index 34b6410d6a2..fcadcd9bc3f 100644
@@ -341,6 +341,14 @@ EvaluateParams(ParseState *pstate, PreparedStatement *pstmt, List *params,
 		i++;
 	}
 
+	/*
+	 * The arguments of EXECUTE are evaluated by a direct expression executor
+	 * call.  This mode doesn't support session variables yet. It will be
+	 * enabled later. This case should be blocked parser by
+	 * expr_kind_allows_session_variables, so only assertions is used here.
+	 */
+	Assert(!pstate->p_hasSessionVariables);
+
 	/* Prepare the expressions for execution */
 	exprstates = ExecPrepareExprList(params, estate);
 
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 7bc823507f1..cd609c6e479 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1673,6 +1673,9 @@ exprLocation(const Node *expr)
 		case T_ParamRef:
 			loc = ((const ParamRef *) expr)->location;
 			break;
+		case T_VariableFence:
+			loc = ((const VariableFence *) expr)->location;
+			break;
 		case T_A_Const:
 			loc = ((const A_Const *) expr)->location;
 			break;
@@ -4705,6 +4708,9 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_VariableFence:
+			/* we assume the fields contain nothing interesting */
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 3b392b084ad..c3897aafd21 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -618,6 +618,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasAggs = pstate->p_hasAggs;
+	qry->hasSessionVariables = pstate->p_hasSessionVariables;

@@ -1043,6 +1044,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)

 
@@ -1526,6 +1528,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt,
 
 	foreach(l, stmt->lockingClause)
 	{
@@ -1752,6 +1755,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

 
@@ -2003,6 +2007,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 
@@ -2475,6 +2480,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)

@@ -2542,6 +2548,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)

 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1a826248bf9..1d9c2298544 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -526,7 +526,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	def_arg columnElem where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref having_clause func_table xmltable array_expr
-				OptWhereClause operator_def_arg
+				OptWhereClause operator_def_arg variable_fence
 %type <list>	opt_column_and_period_list
 %type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality opt_without_overlaps
@@ -882,7 +882,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  */
 %nonassoc	UNBOUNDED NESTED /* ideally would have same precedence as IDENT */
 %nonassoc	IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
-			SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH
+			SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH VARIABLE
 %left		Op OPERATOR		/* multi-character ops and user-defined operators */
 %left		'+' '-'
 %left		'*' '/' '%'
@@ -15704,6 +15704,19 @@ c_expr:		columnref								{ $$ = $1; }
 					else
 						$$ = $2;
 				}
+			| variable_fence opt_indirection
+				{
+					if ($2)
+					{
+						A_Indirection *n = makeNode(A_Indirection);
+
+						n->arg = (Node *) $1;
+						n->indirection = check_indirection($2, yyscanner);
+						$$ = (Node *) n;
+					}
+					else
+						$$ = $1;
+				}
 			| case_expr
 				{ $$ = $1; }
 			| func_expr
@@ -17086,6 +17099,17 @@ case_arg:	a_expr									{ $$ = $1; }
 			| /*EMPTY*/								{ $$ = NULL; }
 		;
 
+variable_fence:
+			VARIABLE '(' any_name ')'
+				{
+					VariableFence *vf = makeNode(VariableFence);
+
+					vf->varname = $3;
+					vf->location = @3;
+					$$ = (Node *) vf;
+				}
+		;
+
 columnref:	ColId
 				{
 					$$ = makeColumnRef($1, NIL, @1, yyscanner);
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 9d95c7140ee..7e97a9d9642 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
@@ -77,6 +78,7 @@ static Node *transformWholeRowRef(ParseState *pstate,
 static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
+static Node *transformVariableFence(ParseState *pstate, VariableFence *vf);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
 											JsonObjectConstructor *ctor);
 static Node *transformJsonArrayConstructor(ParseState *pstate,
@@ -106,7 +108,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,
 							  Node *ltree, Node *rtree, int location);
 static Node *make_nulltest_from_distinct(ParseState *pstate,
 										 A_Expr *distincta, Node *arg);
-
+static Node *makeParamSessionVariable(ParseState *pstate,
+									  Oid varid, Oid typid, int32 typmod, Oid collid,
+									  char *attrname, int location);
 
 /*
  * transformExpr -
@@ -370,6 +374,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr);
 			break;
 
+		case T_VariableFence:
+			result = transformVariableFence(pstate, (VariableFence *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -903,6 +911,135 @@ transformParamRef(ParseState *pstate, ParamRef *pref)
 	return result;
 }
 
+/*
+transformVariableFence(ParseState *pstate, VariableFence *vf)
+	Node	   *result;
+	/* VariableFence can be used only in context when variables are supported */
+	if (!expr_kind_allows_session_variables(pstate->p_expr_kind))
+				 errmsg("session variable reference is not supported here"),
+				 parser_errposition(pstate, vf->location)));
+	varid = IdentifyVariable(vf->varname, &attrname, &not_unique, false);
+						NameListToString(vf->varname)),
+				 parser_errposition(pstate, vf->location)));
+		result = makeParamSessionVariable(pstate,
+										  attrname, vf->location);
+						NameListToString(vf->varname)),
+				 parser_errposition(pstate, vf->location)));
+
+	return result;
+}
+
 /* Test whether an a_expr is a plain NULL constant or not */
 static bool
 exprIsNullConstant(Node *arg)
@@ -3122,6 +3259,75 @@ make_nulltest_from_distinct(ParseState *pstate, A_Expr *distincta, Node *arg)
 	return (Node *) nt;
 }
 
+/*
+				 errmsg("could not identify column \"%s\" in variable \"%s.%s\"",
+						attrname,
  * Produce a string identifying an expression by kind.
  *
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
index 51d7703eff7..244efcddf32 100644
--- a/src/backend/parser/parse_merge.c
+++ b/src/backend/parser/parse_merge.c
@@ -405,6 +405,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
 
 	qry->hasTargetSRFs = false;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
+	qry->hasSessionVariables = pstate->p_hasSessionVariables;

diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..8b5240cd54b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -2033,6 +2033,13 @@ FigureColnameInternal(Node *node, char **name)
 						 (int) ((JsonFuncExpr *) node)->op);
 			}
 			break;
+		case T_VariableFence:
+			{
+				/* return last field name */
+				*name = strVal(llast(((VariableFence *) node)->varname));
+				return 2;
+			}
+			break;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 21663af6979..c398ae79ef6 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -37,6 +37,7 @@
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/defrem.h"
 #include "commands/tablespace.h"
 #include "common/keywords.h"
@@ -534,6 +535,7 @@ static char *generate_function_name(Oid funcid, int nargs,
 static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
 static void add_cast_to(StringInfo buf, Oid typid);
 static char *generate_qualified_type_name(Oid typid);
+static char *generate_session_variable_name(Oid varid);
 static text *string_to_text(char *str);
 static char *flatten_reloptions(Oid relid);
 static void get_reloptions(StringInfo buf, Datum reloptions);
@@ -8804,6 +8806,14 @@ get_parameter(Param *param, deparse_context *context)
 		}
 	}
 
+	/* translate paramvarid to session variable name */
+	if (param->paramkind == PARAM_VARIABLE)
+	{
+		appendStringInfo(context->buf, "VARIABLE(%s)",
+						 generate_session_variable_name(param->paramvarid));
+		return;
+	}
+
 	/*
 	 * Not PARAM_EXEC, or couldn't find referent: just print $N.
 	 *
@@ -13574,6 +13584,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*
index 1c4031eea23..b9b0ac55475 100644
@@ -3946,3 +3946,27 @@ get_session_variable_namespace(Oid varid)
 
 	return varnamespace;
 }
+
+/*
index d12c3b957b7..5aa86b1db71 100644
@@ -116,6 +116,8 @@ extern Oid	TypenameGetTypidExtended(const char *typname, bool temp_ok);
 
 extern bool VariableIsVisible(Oid varid);
+extern Oid	IdentifyVariable(List *names, char **attrname,
+							 bool *not_unique, bool noerror);
 
index 6d006b38ac2..3652a14d1e0 100644
@@ -167,6 +167,8 @@ typedef struct Query
 
@@ -322,6 +324,16 @@ typedef struct ParamRef
 	ParseLoc	location;		/* token location, or -1 if unknown */
 } ParamRef;
 
+/*
+ * VariableFence - ensure so fields will be interpretted as a variable
+ */
+typedef struct VariableFence
+{
+	NodeTag		type;
+	List	   *varname;		/* variable name (String nodes) */
+	ParseLoc	location;		/* token location, or -1 if unknown */
+} VariableFence;
+
 /*
  * A_Expr - infix, prefix, and postfix expressions
  */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index e9d8bf74145..c6ce5a34c05 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -378,6 +378,8 @@ typedef struct Const
  *				of the `paramid' field contain the SubLink's subLinkId, and
  *				the low-order 16 bits contain the column number.  (This type
  *				of Param is also converted to PARAM_EXEC during planning.)
+ *		PARAM_VARIABLE:  The parameter is a reference to a session variable
+ *				(paramvarid holds the variable's OID).
  */
 typedef enum ParamKind
 {
@@ -385,6 +387,7 @@ typedef enum ParamKind
 	PARAM_EXEC,
 	PARAM_SUBLINK,
 	PARAM_MULTIEXPR,
+	PARAM_VARIABLE,
 } ParamKind;
 
 typedef struct Param
@@ -399,6 +402,8 @@ typedef struct Param
 	int32		paramtypmod;
 	/* OID of collation, or InvalidOid if none */
 	Oid			paramcollid;
+	/* OID of used session variable or InvalidOid if none */
+	Oid			paramvarid pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	ParseLoc	location;
 } Param;
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7d07c84542..84e886940d8 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -228,6 +228,7 @@ struct ParseState
 	bool		p_hasTargetSRFs;
 	bool		p_hasSubLinks;
 	bool		p_hasModifyingCTE;
+	bool		p_hasSessionVariables;

diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 03c4c58580e..5926a854d12 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -214,6 +214,10 @@ extern char *get_subscription_name(Oid subid, bool missing_ok);
 
 extern char *get_session_variable_name(Oid varid);
 extern Oid	get_session_variable_namespace(Oid varid);
+extern void get_session_variable_type_typmod_collid(Oid varid,
+													Oid *typid,
+													int32 *typmod,
+													Oid *collid);
 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index d19425b7a71..96857874ffe 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8268,7 +8268,8 @@ exec_is_simple_query(PLpgSQL_expr *expr)
 		query->sortClause ||
 		query->limitOffset ||
 		query->limitCount ||
-		query->setOperations)
+		query->setOperations ||
+		query->hasSessionVariables)
 		return false;
 
 	/*
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9fb3909f7f6..60e55a8058c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3204,6 +3204,7 @@ ValidatorValidateCB
 ValuesScan
 ValuesScanState
 Var
+VariableFence
 VarBit
 VarChar
 VarParamState
-- 
2.51.0

From 7af31bbb121a464d6a242c293a29fb7a2aea46de Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 22:54:09 +0200
Subject: [PATCH 05/15] support of session variables for pg_dump

This patch enhancing pg_dump to support session variables.
---
 src/bin/pg_dump/common.c             |   3 +
 src/bin/pg_dump/dumputils.c          |   6 +
 src/bin/pg_dump/pg_backup.h          |   2 +
 src/bin/pg_dump/pg_backup_archiver.c |   9 ++
 src/bin/pg_dump/pg_dump.c            | 190 +++++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.h            |  15 +++
 src/bin/pg_dump/pg_dump_sort.c       |   7 +
 src/bin/pg_dump/t/002_pg_dump.pl     |  63 +++++++++
 src/tools/pgindent/typedefs.list     |   1 +
 9 files changed, 296 insertions(+)

index a1976fae607..5333acb7bbd 100644

index 05b84c0d6e7..15cbe5616fa 100644
@@ -552,6 +552,12 @@ do { \
 
index d9041dad720..dac067bdc4c 100644
 
 	int			useDB;
index 59eaecb4ed7..06774dc75c1 100644
@@ -3241,6 +3241,14 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
@@ -3816,6 +3824,7 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te)
index 9fc3671cb35..992e332c026 100644
@@ -381,6 +381,7 @@ static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo);
@@ -5681,6 +5682,188 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
+	if (!varinfo->dobj.dump || !dopt->dumpSchema)
@@ -11813,6 +11996,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_REL_STATS:
 			dumpRelationStats(fout, (const RelStatsInfo *) dobj);
@@ -16319,6 +16505,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
 		case DEFACLOBJ_LARGEOBJECT:
 			type = "LARGE OBJECTS";
@@ -20137,6 +20326,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
index bcc94ff07cc..31e2b67deee 100644
@@ -53,6 +53,7 @@ typedef enum
 	DO_TABLE,
 	DO_TABLE_ATTACH,
 	DO_ATTRDEF,
+	DO_VARIABLE,
 	DO_INDEX,
 	DO_INDEX_ATTACH,
 	DO_STATSEXT,
@@ -745,6 +746,19 @@ typedef struct _SubRelInfo
 	char	   *srsublsn;
 } SubRelInfo;
 
+/*
@@ -829,5 +843,6 @@ extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
 
 #endif							/* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 2d02456664b..6b3fb9840ae 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -76,6 +76,7 @@ enum dbObjectTypePriorities
 	PRIO_TABLE_ATTACH,
 	PRIO_DUMMY_TYPE,
 	PRIO_ATTRDEF,
+	PRIO_VARIABLE,
 	PRIO_PRE_DATA_BOUNDARY,		/* boundary! */
 	PRIO_TABLE_DATA,
 	PRIO_SEQUENCE_SET,
@@ -119,6 +120,7 @@ static const int dbObjectTypePriority[] =
 	[DO_TABLE] = PRIO_TABLE,
 	[DO_TABLE_ATTACH] = PRIO_TABLE_ATTACH,
 	[DO_ATTRDEF] = PRIO_ATTRDEF,
+	[DO_VARIABLE] = PRIO_VARIABLE,
 	[DO_INDEX] = PRIO_INDEX,
 	[DO_INDEX_ATTACH] = PRIO_INDEX_ATTACH,
 	[DO_STATSEXT] = PRIO_STATSEXT,
@@ -1749,6 +1751,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "RELATION STATISTICS FOR %s  (ID %d OID %u)",
 					 obj->name, obj->dumpId, obj->catId.oid);
 			return;
+		case DO_VARIABLE:
+			snprintf(buf, bufsize,
+					 "VARIABLE %s  (ID %d OID %u)",
+					 obj->name, obj->dumpId, obj->catId.oid);
+			return;
 	}
 	/* shouldn't get here */
 	snprintf(buf, bufsize,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index fc5b9b52f80..07f42e0cb31 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -1008,6 +1008,16 @@ my %tests = (
 		unlike => { no_privs => 1, },
 	  },
 
@@ -2034,6 +2044,23 @@ my %tests = (
 		},
 	},
 
@@ -4405,6 +4432,23 @@ my %tests = (
 		},
 	},
 
@@ -4869,6 +4913,25 @@ my %tests = (
 		like => {},
 	},
 
index e35d9d7dfb0..9fb3909f7f6 100644
@@ -3212,6 +3212,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.51.0

From bf6c6a77407b3f3d83875e7c189f53724f385d85 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Aug 2025 06:42:02 +0200
Subject: [PATCH 04/15] support of session variables for psql

This patch enhancing psql to support session variables:

 * \dV[+] command
 * tab complete for CREATE, DROP, ALTER VARIABLE

Note: tab complete for variable fencing is not supported yet
---
 doc/src/sgml/func/func-info.sgml   |  13 ++++
 doc/src/sgml/ref/psql-ref.sgml     |  13 ++++
 src/backend/catalog/namespace.c    |  14 ++++
 src/bin/psql/command.c             |   3 +
 src/bin/psql/describe.c            | 100 ++++++++++++++++++++++++++++-
 src/bin/psql/describe.h            |   3 +
 src/bin/psql/help.c                |   1 +
 src/bin/psql/tab-complete.in.c     |  45 +++++++++++--
 src/include/catalog/pg_proc.dat    |   3 +
 src/test/regress/expected/psql.out |  50 +++++++++++++++
 src/test/regress/sql/psql.sql      |  21 ++++++
 11 files changed, 259 insertions(+), 7 deletions(-)

diff --git a/doc/src/sgml/func/func-info.sgml b/doc/src/sgml/func/func-info.sgml
index a57f7665054..3aad9d0529f 100644
--- a/doc/src/sgml/func/func-info.sgml
+++ b/doc/src/sgml/func/func-info.sgml
@@ -1377,6 +1377,19 @@ SELECT relname FROM pg_class WHERE pg_table_is_visible(oid);
         Is type (or domain) visible in search path?
        </para></entry>
       </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_variable_is_visible</primary>
+        </indexterm>
+        <function>pg_variable_is_visible</function> ( <parameter>variable</parameter> <type>oid</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is session variable visible in search path?
+       </para></entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1a339600bc4..6bb3a9dad50 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2144,6 +2144,19 @@ SELECT $1 \parse stmt1
         </listitem>
       </varlistentry>
 
+      <varlistentry id="app-psql-meta-command-dv">
+        <term><literal>\dV[+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+        <listitem>
+        <para>
+        Lists session variables.
+        If <replaceable class="parameter">pattern</replaceable> is
+        specified, only session variables whose names match the pattern are listed.
+        If the form <literal>\dV+</literal> is used, additional information
+        about each variable is shown, like  access privileges and description.
+        </para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry id="app-psql-meta-command-du">
         <term><literal>\du[Sx+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
         <listitem>
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index ab837a3cb9e..800f37dc2ea 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -5362,3 +5362,17 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(isOtherTempNamespace(oid));
 }
+
+Datum
+pg_variable_is_visible(PG_FUNCTION_ARGS)
+{
+	Oid			oid = PG_GETARG_OID(0);
+	bool		result;
+	bool		is_missing = false;
+
+	result = VariableIsVisibleExt(oid, &is_missing);
+
+	if (is_missing)
+		PG_RETURN_NULL();
+	PG_RETURN_BOOL(result);
+}
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index cc602087db2..0ac7b9881cd 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -1270,6 +1270,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 						break;
 				}
 				break;
+			case 'V':			/* Variables */
+				success = listVariables(pattern, show_verbose);
+				break;
 			case 'x':			/* Extensions */
 				if (show_verbose)
 					success = listExtensionContents(pattern);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 4aa793d7de7..1f5f7ced772 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1224,7 +1224,7 @@ listDefaultACLs(const char *pattern)
 					  "SELECT pg_catalog.pg_get_userbyid(d.defaclrole) AS \"%s\",\n"
 					  "  n.nspname AS \"%s\",\n"
 					  "  CASE d.defaclobjtype "
-					  "    WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s'"
+					  "    WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s'"
 					  "    WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' END AS \"%s\",\n"
 					  "  ",
 					  gettext_noop("Owner"),
@@ -1241,6 +1241,8 @@ listDefaultACLs(const char *pattern)
 					  gettext_noop("schema"),
 					  DEFACLOBJ_LARGEOBJECT,
 					  gettext_noop("large object"),
+					  DEFACLOBJ_VARIABLE,
+					  gettext_noop("session variable"),
 					  gettext_noop("Type"));
 
 	printACLColumn(&buf, "d.defaclacl");
@@ -5314,6 +5316,102 @@ error_return:
 	return false;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
index 18ecaa60949..55ced4aab7b 100644
 
+/* \dV */
+extern bool listVariables(const char *pattern, bool varbose);
+
 #endif							/* DESCRIBE_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index ed00c36695e..aa91a7ce10f 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -266,6 +266,7 @@ slashUsage(unsigned short int pager)
 	HELP0("  \\dT[Sx+] [PATTERN]     list data types\n");
 	HELP0("  \\du[Sx+] [PATTERN]     list roles\n");
 	HELP0("  \\dv[Sx+] [PATTERN]     list views\n");
+	HELP0("  \\dV[x+]  [PATTERN]     list session variables\n");
 	HELP0("  \\dx[x+]  [PATTERN]     list extensions\n");
 	HELP0("  \\dX[x]   [PATTERN]     list extended statistics\n");
 	HELP0("  \\dy[x+]  [PATTERN]     list event triggers\n");
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 6176741d20b..5fc5c15057b 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -988,6 +988,13 @@ static const SchemaQuery Query_for_trigger_of_table = {
 	.refnamespace = "c1.relnamespace",
 };

 /*
@@ -1352,6 +1359,7 @@ static const pgsql_thing_t words_after_create[] = {
@@ -1917,7 +1925,7 @@ psql_completion(const char *text, int start, int end)
 		"\\endif", "\\endpipeline", "\\errverbose", "\\ev",
 		"\\f", "\\flush", "\\flushrequest",
@@ -2631,6 +2639,9 @@ match_previous_words(int pattern_id,
@@ -3237,7 +3248,7 @@ match_previous_words(int pattern_id,
@@ -4043,6 +4054,13 @@ match_previous_words(int pattern_id,
 
@@ -4320,6 +4338,12 @@ match_previous_words(int pattern_id,
 
+	/* DROP VARIABLE */
+	else if (Matches("DROP", "VARIABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
+	else if (Matches("DROP", "VARIABLE", MatchAny))
+		COMPLETE_WITH("CASCADE", "RESTRICT");
+
 /* EXECUTE */
 	else if (Matches("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -4521,7 +4545,9 @@ match_previous_words(int pattern_id,
 		 * objects supported.
 		 */
 		if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
-			COMPLETE_WITH("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS", "LARGE OBJECTS");
+			COMPLETE_WITH("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES",
+						  "ROUTINES", "TYPES", "SCHEMAS", "LARGE OBJECTS",
+						  "VARIABLES");
 		else
 			COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_grantables,
 											"ALL FUNCTIONS IN SCHEMA",
@@ -4529,6 +4555,7 @@ match_previous_words(int pattern_id,
 											"ALL ROUTINES IN SCHEMA",
 											"ALL SEQUENCES IN SCHEMA",
 											"ALL TABLES IN SCHEMA",
+											"ALL VARIABLES IN SCHEMA",
 											"DATABASE",
 											"DOMAIN",
 											"FOREIGN DATA WRAPPER",
@@ -4543,7 +4570,8 @@ match_previous_words(int pattern_id,
 											"SEQUENCE",
 											"TABLE",
 											"TABLESPACE",
-											"TYPE");
+											"TYPE",
+											"VARIABLE");
 	}
 	else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL") ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "ALL"))
@@ -4551,7 +4579,8 @@ match_previous_words(int pattern_id,
 					  "PROCEDURES IN SCHEMA",
 					  "ROUTINES IN SCHEMA",
 					  "SEQUENCES IN SCHEMA",
-					  "TABLES IN SCHEMA");
+					  "TABLES IN SCHEMA",
+					  "VARIABLES IN SCHEMA");
 	else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN") ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH("DATA WRAPPER", "SERVER");
@@ -4587,6 +4616,8 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		else if (TailMatches("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
+		else if (TailMatches("VARIABLE"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
 		else if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH("TO");
 		else
@@ -4913,7 +4944,7 @@ match_previous_words(int pattern_id,
 
 /* PREPARE xx AS */
 
 /*
@@ -5402,6 +5433,8 @@ match_previous_words(int pattern_id,
index e5b48d3530d..d28fb3283cb 100644
@@ -6709,6 +6709,9 @@
+{ oid => '9999', descr => 'is session variable visible in search path?',
 
index a79325e8a2f..f2e506796db 100644
@@ -6025,6 +6025,30 @@ COMMIT;
@@ -6246,6 +6270,12 @@ cross-database references are not implemented: nonesuch.public.func_deps_stat
@@ -6480,6 +6510,12 @@ List of schemas
 
+\dV "no.such.variable"
+            List of variables
@@ -6649,6 +6685,12 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
         List of tables
@@ -6782,6 +6824,12 @@ List of text search templates
 
@@ -6829,6 +6877,8 @@ cross-database references are not implemented: "no.such.database"."no.such.schem
index f064e4f5456..8f6108e44de 100644
@@ -1645,6 +1645,19 @@ COMMIT;
 
@@ -1756,6 +1769,9 @@ DROP FUNCTION psql_error;
 
@@ -1797,6 +1813,8 @@ DROP FUNCTION psql_error;
 
@@ -1837,6 +1855,7 @@ DROP FUNCTION psql_error;
 
@@ -1861,6 +1880,7 @@ DROP FUNCTION psql_error;
 
@@ -1886,6 +1906,7 @@ DROP FUNCTION psql_error;
 
 -- check \drg and \du
2.51.0

From a8578418f28ac8ccd0438e36c21b2a9a5bb4404c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Aug 2025 06:37:28 +0200
Subject: [PATCH 03/15] GRANT, REVOKE variable

Access to session variables can be controlled by SELECT or UPDATE rights. Both rights are
introduced by this patch too. Default ACL are supported.
---
 doc/src/sgml/catalogs.sgml                    |   3 +-
 doc/src/sgml/ddl.sgml                         |  19 +-
 doc/src/sgml/func/func-info.sgml              |  23 +-
 .../sgml/ref/alter_default_privileges.sgml    |  29 +-
 doc/src/sgml/ref/grant.sgml                   |  20 +-
 doc/src/sgml/ref/revoke.sgml                  |   8 +
 src/backend/catalog/aclchk.c                  |  74 +++-
 src/backend/catalog/objectaddress.c           |  22 +-
 src/backend/catalog/pg_variable.c             |  11 +-
 src/backend/parser/gram.y                     |  21 +-
 src/backend/utils/adt/acl.c                   | 221 ++++++++++++
 src/include/catalog/pg_default_acl.h          |   1 +
 src/include/catalog/pg_proc.dat               |  20 ++
 src/include/parser/kwlist.h                   |   1 +
 src/include/utils/acl.h                       |   1 +
 .../expected/session_variables_acl.out        | 335 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 .../regress/sql/session_variables_acl.sql     | 182 ++++++++++
 18 files changed, 967 insertions(+), 26 deletions(-)
 create mode 100644 src/test/regress/expected/session_variables_acl.out
 create mode 100644 src/test/regress/sql/session_variables_acl.sql

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 487a58eda74..af178013cf4 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3360,7 +3360,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>f</literal> = function,
        <literal>T</literal> = type,
        <literal>n</literal> = schema,
-       <literal>L</literal> = large object
+       <literal>L</literal> = large object,
+       <literal>V</literal> = session variable
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index fa711a09bc4..420a4d9ff11 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2025,6 +2025,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
        For sequences, this privilege also allows use of the
        <function>currval</function> function.
        For large objects, this privilege allows the object to be read.
+       For session variables, this  privilege allows the object to be read.
       </para>
      </listitem>
     </varlistentry>
@@ -2060,6 +2061,8 @@ REVOKE ALL ON accounts FROM PUBLIC;
        <function>setval</function> functions.
        For large objects, this privilege allows writing or truncating the
        object.
+       For session variables, this privilege allows to set a value to the
+       object.
       </para>
      </listitem>
     </varlistentry>
@@ -2304,7 +2307,8 @@ REVOKE ALL ON accounts FROM PUBLIC;
        <literal>LARGE OBJECT</literal>,
        <literal>SEQUENCE</literal>,
        <literal>TABLE</literal> (and table-like objects),
-       table column
+       table column,
+       <literal>SESSION VARIABLE</literal>
       </entry>
      </row>
      <row>
@@ -2319,7 +2323,8 @@ REVOKE ALL ON accounts FROM PUBLIC;
        <literal>LARGE OBJECT</literal>,
        <literal>SEQUENCE</literal>,
        <literal>TABLE</literal>,
-       table column
+       table column,
+       <literal>SESSION VARIABLE</literal>
       </entry>
      </row>
      <row>
@@ -2506,6 +2511,12 @@ REVOKE ALL ON accounts FROM PUBLIC;
       <entry><literal>U</literal></entry>
       <entry><literal>\dT+</literal></entry>
      </row>
+     <row>
+      <entry><literal>SESSION VARIABLE</literal></entry>
+      <entry><literal>rw</literal></entry>
+      <entry><literal>none</literal></entry>
+      <entry><literal>\dV+</literal></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -5375,6 +5386,10 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    <para>
     Session variables are database objects that can hold a value.
 
    <para>
diff --git a/doc/src/sgml/func/func-info.sgml b/doc/src/sgml/func/func-info.sgml
index c393832d94c..a57f7665054 100644
--- a/doc/src/sgml/func/func-info.sgml
+++ b/doc/src/sgml/func/func-info.sgml
@@ -740,6 +740,25 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>has_session_variable_privilege</primary>
+        </indexterm>
+        <function>has_session_variable_privilege</function> (
+          <optional> <parameter>user</parameter> <type>name</type> or <type>oid</type>, </optional>
+          <parameter>session_variable</parameter> <type>text</type> or <type>oid</type>,
+          <parameter>privilege</parameter> <type>text</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does user have privilege for session variable?
+        Allowable privilege types are
+        <literal>SELECT</literal>, and
+        <literal>UPDATE</literal>.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
@@ -1089,8 +1108,8 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
         't' for <literal>TABLESPACE</literal>,
         'F' for <literal>FOREIGN DATA WRAPPER</literal>,
         'S' for <literal>FOREIGN SERVER</literal>,
-        or
-        'T' for <literal>TYPE</literal> or <literal>DOMAIN</literal>.
+        'T' for <literal>TYPE</literal> or <literal>DOMAIN</literal> or
+        'V' for <literal>SESSION VARIABLE</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 6acd0f1df91..bc73817061f 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -56,6 +56,11 @@ GRANT { { SELECT | UPDATE }
     ON LARGE OBJECTS
     TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
 
+GRANT { { SELECT | UPDATE }
+    [, ...] | ALL [ PRIVILEGES ] }
+    ON VARIABLES
+    TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
+
 REVOKE [ GRANT OPTION FOR ]
     { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
     [, ...] | ALL [ PRIVILEGES ] }
@@ -95,6 +100,14 @@ REVOKE [ GRANT OPTION FOR ]
     ON LARGE OBJECTS
     FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
     [ CASCADE | RESTRICT ]
+
+REVOKE [ GRANT OPTION FOR ]
+    { { SELECT | UPDATE }
+    [, ...] | ALL [ PRIVILEGES ] }
+    { { SELECT | UPDATE } [, ...] | ALL [ PRIVILEGES ] }
+    ON VARIABLES
+    FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
+    [ CASCADE | RESTRICT ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -129,14 +142,14 @@ REVOKE [ GRANT OPTION FOR ]
   <para>
    Currently,
    only the privileges for schemas, tables (including views and foreign
-   tables), sequences, functions, types (including domains), and large objects
-   can be altered.  For this command, functions include aggregates and procedures.
-   The words <literal>FUNCTIONS</literal> and <literal>ROUTINES</literal> are
-   equivalent in this command.  (<literal>ROUTINES</literal> is preferred
-   going forward as the standard term for functions and procedures taken
-   together.  In earlier PostgreSQL releases, only the
-   word <literal>FUNCTIONS</literal> was allowed.  It is not possible to set
-   default privileges for functions and procedures separately.)
+   tables), sequences, functions, types (including domains), large objects
+   and session variables can be altered.  For this command, functions include
+   aggregates and procedures.  The words <literal>FUNCTIONS</literal> and
+   <literal>ROUTINES</literal> are equivalent in this command.
+   (<literal>ROUTINES</literal> is preferred going forward as the standard term
+   for functions and procedures taken together.  In earlier PostgreSQL releases,
+   only the word <literal>FUNCTIONS</literal> was allowed.  It is not possible
+   to set default privileges for functions and procedures separately.)
   </para>
 
   <para>
index 999f657d5c0..c11860fa200 100644
 
+    ON { VARIABLE <replaceable>variable_name</replaceable> [, ...]
 
     [ GROUP ] <replaceable class="parameter">role_name</replaceable>
@@ -119,8 +125,8 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
    that grants privileges on a database object (table, column, view,
    foreign table, sequence, database, foreign-data wrapper, foreign server,
    function, procedure, procedural language, large object, configuration
-   parameter, schema, tablespace, or type), and one that grants
-   membership in a role.  These variants are similar in many ways, but
+   parameter, schema, session variable, tablespace, or type), and one that
+   grants membership in a role.  These variants are similar in many ways, but
    they are different enough to be described separately.
   </para>
 
@@ -236,9 +242,9 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
   <para>
    There is also an option to grant privileges on all objects of the same
    type within one or more schemas.  This functionality is currently supported
-   only for tables, sequences, functions, and procedures.  <literal>ALL
-   TABLES</literal> also affects views and foreign tables, just like the
-   specific-object <command>GRANT</command> command.  <literal>ALL
+   only for tables, sequences, functions, procedures and variables.
+   <literal>ALL TABLES</literal> also affects views and foreign tables, just
+   like the specific-object <command>GRANT</command> command.  <literal>ALL
    FUNCTIONS</literal> also affects aggregate and window functions, but not
    procedures, again just like the specific-object <command>GRANT</command>
    command.  Use <literal>ALL ROUTINES</literal> to include procedures.
@@ -518,8 +524,8 @@ GRANT admins TO joe;
    </para>
 
    <para>
-    Privileges on databases, tablespaces, schemas, languages, and
-    configuration parameters are
+    Privileges on databases, tablespaces, schemas, languages, session variables
+    and configuration parameters are
     <productname>PostgreSQL</productname> extensions.
    </para>
  </refsect1>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 8df492281a1..760fddb7c20 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -130,6 +130,14 @@ REVOKE [ { ADMIN | INHERIT | SET } OPTION FOR ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
     [ CASCADE | RESTRICT ]
 
+REVOKE [ GRANT OPTION FOR ]
+    { { SELECT | UPDATE } [, ...] | ALL [ PRIVILEGES ] }
+    ON { VARIABLE <replaceable>variable_name</replaceable> [, ...]
+       | ALL VARIABLES IN SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...] }
+    FROM { <replaceable class="parameter">role_specification</replaceable> | PUBLIC } [, ...]
+    [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
+    [ CASCADE | RESTRICT ]
+
 <phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase>
 
index 00e3630e0ec..93c10beebe9 100644
@@ -773,6 +784,18 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
+			foreach_node(RangeVar, varvar, objnames)
 
 	return objects;
@@ -859,6 +882,32 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 					table_close(rel, AccessShareLock);
 				}
 				break;
+			case OBJECT_VARIABLE:
+				{
+					ScanKeyData key;
+					Relation	rel;
+					TableScanDesc scan;
+					HeapTuple	tuple;
+
+					ScanKeyInit(&key,
+								Anum_pg_variable_varnamespace,
+								BTEqualStrategyNumber, F_OIDEQ,
+								ObjectIdGetDatum(namespaceId));
+
+					rel = table_open(VariableRelationId, AccessShareLock);
+					scan = table_beginscan_catalog(rel, 1, &key);
+
+					while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+					{
+						Oid			oid = ((Form_pg_proc) GETSTRUCT(tuple))->oid;
+
+						objects = lappend_oid(objects, oid);
+					}
+
+					table_endscan(scan);
+					table_close(rel, AccessShareLock);
+				}
+				break;
 			default:
 				/* should not happen */
 				elog(ERROR, "unrecognized GrantStmt.objtype: %d",
@@ -1022,6 +1071,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
 			all_privileges = ACL_ALL_RIGHTS_LARGEOBJECT;
 			errormsg = gettext_noop("invalid privilege type %s for large object");
 			break;
+		case OBJECT_VARIABLE:
+			all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			errormsg = gettext_noop("invalid privilege type %s for session variable");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) action->objtype);
@@ -1222,6 +1275,11 @@ SetDefaultACL(InternalDefaultACL *iacls)
 			if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
 				this_privileges = ACL_ALL_RIGHTS_LARGEOBJECT;
 			break;
+		case OBJECT_VARIABLE:
+			objtype = DEFACLOBJ_VARIABLE;
+			if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
+				this_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			break;
 
 		default:
 			elog(ERROR, "unrecognized object type: %d",
@@ -1469,6 +1527,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case DEFACLOBJ_LARGEOBJECT:
 				iacls.objtype = OBJECT_LARGEOBJECT;
 				break;
+			case DEFACLOBJ_VARIABLE:
+				iacls.objtype = OBJECT_VARIABLE;
+				break;
 			default:
 				/* Shouldn't get here */
 				elog(ERROR, "unexpected default ACL type: %d",
@@ -1529,6 +1590,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case ParameterAclRelationId:
 				istmt.objtype = OBJECT_PARAMETER_ACL;
 				break;
+			case VariableRelationId:
+				istmt.objtype = OBJECT_VARIABLE;
+				break;
 			default:
 				elog(ERROR, "unexpected object class %u", classid);
 				break;
@@ -2762,6 +2826,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("permission denied for type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("permission denied for session variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("permission denied for view %s");
 						break;
@@ -2784,7 +2851,6 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TSPARSER:
 					case OBJECT_TSTEMPLATE:
 					case OBJECT_USER_MAPPING:
-					case OBJECT_VARIABLE:
 						elog(ERROR, "unsupported object type: %d", objtype);
 				}
 
@@ -3025,6 +3091,8 @@ pg_aclmask(ObjectType objtype, Oid object_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return object_aclmask(TypeRelationId, object_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return object_aclmask(VariableRelationId, object_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) objtype);
@@ -4288,6 +4356,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 			defaclobjtype = DEFACLOBJ_LARGEOBJECT;
 			break;
 
+		case OBJECT_VARIABLE:
+			defaclobjtype = DEFACLOBJ_VARIABLE;
+			break;
+
 		default:
 			return NULL;
 	}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index ac1f8a4db3b..32cd078c162 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2029,17 +2029,21 @@ get_object_address_defacl(List *object, bool missing_ok)
 		case DEFACLOBJ_LARGEOBJECT:
 			objtype_str = "large objects";
 			break;
+		case DEFACLOBJ_VARIABLE:
+			objtype_str = "variables";
+			break;
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("unrecognized default ACL object type \"%c\"", objtype),
-					 errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
+					 errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
 							 DEFACLOBJ_RELATION,
 							 DEFACLOBJ_SEQUENCE,
 							 DEFACLOBJ_FUNCTION,
 							 DEFACLOBJ_TYPE,
 							 DEFACLOBJ_NAMESPACE,
-							 DEFACLOBJ_LARGEOBJECT)));
+							 DEFACLOBJ_LARGEOBJECT,
+							 DEFACLOBJ_VARIABLE)));
 	}
 
 	/*
@@ -3921,6 +3925,16 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 										 _("default privileges on new large objects belonging to role %s"),
 										 rolename);
 						break;
+					case DEFACLOBJ_VARIABLE:
+						if (nspname)
+							appendStringInfo(&buffer,
+											 _("default privileges on new session variables belonging to role %s in schema %s"),
+											 rolename, nspname);
+						else
+							appendStringInfo(&buffer,
+											 _("default privileges on new session variables belonging to role %s"),
+											 rolename);
+						break;
 					default:
 						/* shouldn't get here */
 						if (nspname)
@@ -5851,6 +5865,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 						appendStringInfoString(&buffer,
 											   " on large objects");
 						break;
+					case DEFACLOBJ_VARIABLE:
+						appendStringInfoString(&buffer,
+											   " on session variables");
+						break;
 				}
 
 				if (objname)
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
index bd6a29a79e5..d8ede4fa8c8 100644
--- a/src/backend/catalog/pg_variable.c
+++ b/src/backend/catalog/pg_variable.c
@@ -38,6 +38,7 @@ create_variable(const char *varName,
 				Oid varCollation,
 				bool if_not_exists)
 {
+	Acl		   *varacl;
 	NameData	varname;
 	bool		nulls[Natts_pg_variable];
 	Datum		values[Natts_pg_variable];
@@ -97,7 +98,12 @@ create_variable(const char *varName,
 	values[Anum_pg_variable_varowner - 1] = ObjectIdGetDatum(varOwner);
 	values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation);
 
-	nulls[Anum_pg_variable_varacl - 1] = true;
+	varacl = get_user_default_acl(OBJECT_VARIABLE, varOwner,
+								  varNamespace);
+	if (varacl != NULL)
+		values[Anum_pg_variable_varacl - 1] = PointerGetDatum(varacl);
+	else
+		nulls[Anum_pg_variable_varacl - 1] = true;
 
 	tupdesc = RelationGetDescr(rel);
 
@@ -131,6 +137,9 @@ create_variable(const char *varName,
 	/* dependency on owner */
 	recordDependencyOnOwner(VariableRelationId, varid, varOwner);
 
+	/* dependencies on roles mentioned in default ACL */
+	recordDependencyOnNewAcl(VariableRelationId, varid, 0, varOwner, varacl);
+
 	/* dependency on extension */
 	recordDependencyOnCurrentExtension(&myself, false);
 
index ab90df203ea..1a826248bf9 100644
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARIABLE
-	VARYING VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
+	VARIABLES VARYING VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE

@@ -8060,6 +8060,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -8105,6 +8113,14 @@ privilege_target:

@@ -8303,6 +8319,7 @@ defacl_privilege_target:
 			| TYPES_P		{ $$ = OBJECT_TYPE; }
 			| SCHEMAS		{ $$ = OBJECT_SCHEMA; }
 			| LARGE_P OBJECTS_P	{ $$ = OBJECT_LARGEOBJECT; }
+			| VARIABLES		{ $$ = OBJECT_VARIABLE; }
 		;

@@ -18145,6 +18162,7 @@ unreserved_keyword:
 			| VALIDATOR
 			| VALUE_P
 			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18802,6 +18820,7 @@ bare_label_keyword:
 			| VALUES
 			| VARCHAR
 			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index fbcd64a2609..9828382b900 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/proclang.h"
 #include "commands/tablespace.h"
 #include "common/hashfn.h"
@@ -128,6 +129,8 @@ static AclMode convert_type_priv_string(text *priv_type_text);
 static AclMode convert_parameter_priv_string(text *priv_text);
 static AclMode convert_largeobject_priv_string(text *priv_type_text);
 static AclMode convert_role_priv_string(text *priv_type_text);
+static Oid	convert_session_variable_name(text *varname);
+static AclMode convert_session_variable_priv_string(text *priv_type_text);
 static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
 
 static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -867,6 +870,10 @@ acldefault(ObjectType objtype, Oid ownerId)
 			world_default = ACL_NO_RIGHTS;
 			owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL;
 			break;
+		case OBJECT_VARIABLE:
+			world_default = ACL_NO_RIGHTS;
+			owner_default = ACL_ALL_RIGHTS_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d", (int) objtype);
 			world_default = ACL_NO_RIGHTS;	/* keep compiler quiet */
@@ -964,6 +971,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
 		case 'T':
 			objtype = OBJECT_TYPE;
 			break;
+		case 'V':
+			objtype = OBJECT_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized object type abbreviation: %c", objtypec);
 	}
@@ -5032,6 +5042,217 @@ pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode)
 	return ACLCHECK_NO_PRIV;
 }
 
+/*
+ * has_session_variable_privilege variants
+ *		These are all named "has_session_variable_privilege" at the SQL level.
+ *		They take various combinations of variable name, variable OID,
+ *		user name, user OID, or implicit user = current_user.
+ *
+ *		The result is a boolean value: true if user has the indicated
+ *		privilege, false if not, or NULL if session variable doesn't
+ *		exists.
+ */
+
+/*
+ * has_session_variable_privilege_name_name
+ *		Check user privileges on a session variable given
+ *		name username, text session variable name, and text priv name.
+ */
+Datum
+has_session_variable_privilege_name_name(PG_FUNCTION_ARGS)
+{
+	Name		rolename = PG_GETARG_NAME(0);
+	text	   *varname = PG_GETARG_TEXT_PP(1);
+	text	   *priv_type_text = PG_GETARG_TEXT_PP(2);
+	Oid			roleid;
+	Oid			varid;
+	AclMode		mode;
+	AclResult	aclresult;
+	bool		is_missing = false;
+
+	roleid = get_role_oid_or_public(NameStr(*rolename));
+	mode = convert_session_variable_priv_string(priv_type_text);
+	varid = convert_session_variable_name(varname);
+
+	aclresult = object_aclcheck_ext(VariableRelationId, varid,
+									roleid, mode,
+									&is_missing);
+
+	if (is_missing)
+		PG_RETURN_NULL();
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_session_variable_privilege_name
+ *		Check user privileges on a session variable given
+ *		text session variable and text priv name.
+ *		current_user is assumed
+ */
+Datum
+has_session_variable_privilege_name(PG_FUNCTION_ARGS)
+{
+	text	   *varname = PG_GETARG_TEXT_PP(0);
+	text	   *priv_type_text = PG_GETARG_TEXT_PP(1);
+	Oid			roleid;
+	Oid			varid;
+	AclMode		mode;
+	AclResult	aclresult;
+	bool		is_missing = false;
+
+	roleid = GetUserId();
+	mode = convert_session_variable_priv_string(priv_type_text);
+	varid = convert_session_variable_name(varname);
+
+	aclresult = object_aclcheck_ext(VariableRelationId, varid,
+									roleid, mode,
+									&is_missing);
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_session_variable_privilege_name_id
+ *		Check user privileges on a session variable given
+ *		name usename, session variable oid, and text priv name.
+ */
+Datum
+has_session_variable_privilege_name_id(PG_FUNCTION_ARGS)
+{
+	Name		username = PG_GETARG_NAME(0);
+	Oid			varid = PG_GETARG_OID(1);
+	text	   *priv_type_text = PG_GETARG_TEXT_PP(2);
+	Oid			roleid;
+	AclMode		mode;
+	AclResult	aclresult;
+	bool		is_missing = false;
+
+	roleid = get_role_oid_or_public(NameStr(*username));
+	mode = convert_session_variable_priv_string(priv_type_text);
+
+	aclresult = object_aclcheck_ext(VariableRelationId, varid,
+									roleid, mode,
+									&is_missing);
+
+	if (is_missing)
+		PG_RETURN_NULL();
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_session_variable_privilege_id
+ *		Check user privileges on a session variable given
+ *		session variable oid, and text priv name.
+ *		current_user is assumed
+ */
+Datum
+has_session_variable_privilege_id(PG_FUNCTION_ARGS)
+{
+	Oid			varid = PG_GETARG_OID(0);
+	text	   *priv_type_text = PG_GETARG_TEXT_PP(1);
+	Oid			roleid;
+	AclMode		mode;
+	AclResult	aclresult;
+	bool		is_missing = false;
+
+	roleid = GetUserId();
+	mode = convert_session_variable_priv_string(priv_type_text);
+
+	aclresult = object_aclcheck_ext(VariableRelationId, varid,
+									roleid, mode,
+									&is_missing);
+
+	if (is_missing)
+		PG_RETURN_NULL();
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_session_variable_privilege_id_name
+ *		Check user privileges on a session variable given
+ *		roleid, text session variable name, and text priv name.
+ */
+Datum
+has_session_variable_privilege_id_name(PG_FUNCTION_ARGS)
+{
+	Oid			roleid = PG_GETARG_OID(0);
+	text	   *varname = PG_GETARG_TEXT_PP(1);
+	text	   *priv_type_text = PG_GETARG_TEXT_PP(2);
+	Oid			varid;
+	AclMode		mode;
+	AclResult	aclresult;
+	bool		is_missing = false;
+
+	mode = convert_session_variable_priv_string(priv_type_text);
+	varid = convert_session_variable_name(varname);
+
+	aclresult = object_aclcheck_ext(VariableRelationId, varid,
+									roleid, mode,
+									&is_missing);
+
+	if (is_missing)
+		PG_RETURN_NULL();
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_session_variable_privilege_id_id
+ *		Check user privileges on a session variable given
+ *		roleid, session variable oid, and text priv name.
+ */
+Datum
+has_session_variable_privilege_id_id(PG_FUNCTION_ARGS)
+{
+	Oid			roleid = PG_GETARG_OID(0);
+	Oid			varid = PG_GETARG_OID(1);
+	text	   *priv_type_text = PG_GETARG_TEXT_PP(2);
+	AclMode		mode;
+	AclResult	aclresult;
+	bool		is_missing = false;
+
+	mode = convert_session_variable_priv_string(priv_type_text);
+
+	aclresult = object_aclcheck_ext(VariableRelationId, varid,
+									roleid, mode,
+									&is_missing);
+
+	if (is_missing)
+		PG_RETURN_NULL();
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * Given a session variable name expressed as a string, look it up and return
+ * Oid
+ */
+static Oid
+convert_session_variable_name(text *varname)
+{
+	return LookupVariableFromNameList(textToQualifiedNameList(varname), true);
+}
+
+/*
+ * convert_variable_priv_string
+ *		Convert text string to AclMode value.
+ */
+static AclMode
+convert_session_variable_priv_string(text *priv_type_text)
+{
+	static const priv_map session_variable_priv_map[] = {
+		{"SELECT", ACL_SELECT},
+		{"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)},
+		{"UPDATE", ACL_UPDATE},
+		{"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)},
+		{NULL, 0}
+	};
+
+	return convert_any_priv_string(priv_type_text, session_variable_priv_map);
+}
 
 /*
  * initialization function (called by InitPostgres)
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index ce6e5098eaf..087d35b943d 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -69,6 +69,7 @@ MAKE_SYSCACHE(DEFACLROLENSPOBJ, pg_default_acl_role_nsp_obj_index, 8);
 #define DEFACLOBJ_TYPE			'T' /* type */
 #define DEFACLOBJ_NAMESPACE		'n' /* namespace */
 #define DEFACLOBJ_LARGEOBJECT	'L' /* large object */
+#define DEFACLOBJ_VARIABLE		'V' /* variable */

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 01eba3b5a19..e5b48d3530d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5479,6 +5479,26 @@
   prorettype => 'bool', proargtypes => 'oid oid text',
   prosrc => 'has_largeobject_privilege_id_id' },
 
+{ oid => '9613', descr => 'user privilege on session variable by username, seq name',
+  proname => 'has_session_variable_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'name text text',
+  prosrc => 'has_session_variable_privilege_name_name' },
+{ oid => '9614', descr => 'user privilege on session variable by username, seq oid',
+  proname => 'has_session_variable_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'name oid text', prosrc => 'has_session_variable_privilege_name_id' },
+{ oid => '9615', descr => 'user privilege on session variable by user oid, seq name',
+  proname => 'has_session_variable_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'oid text text', prosrc => 'has_session_variable_privilege_id_name' },
+{ oid => '9616', descr => 'user privilege on session variable by user oid, seq oid',
+  proname => 'has_session_variable_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'oid oid text', prosrc => 'has_session_variable_privilege_id_id' },
+{ oid => '9617', descr => 'current user privilege on session variable by seq name',
+  proname => 'has_session_variable_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'text text', prosrc => 'has_session_variable_privilege_name' },
+{ oid => '9618', descr => 'current user privilege on session variable by seq oid',
+  proname => 'has_session_variable_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'oid text', prosrc => 'has_session_variable_privilege_id' },
+
 { oid => '3355', descr => 'I/O',
   proname => 'pg_ndistinct_in', prorettype => 'pg_ndistinct',
   proargtypes => 'cstring', prosrc => 'pg_ndistinct_in' },
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 5e6d3aff8af..7e7d262b1bb 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -489,6 +489,7 @@ PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("variables", VARIABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 01ae5b719fd..5e1a8a82e90 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -169,6 +169,7 @@ typedef struct ArrayType Acl;
 #define ACL_ALL_RIGHTS_SCHEMA		(ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE	(ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE			(ACL_USAGE)
+#define ACL_ALL_RIGHTS_VARIABLE		(ACL_SELECT|ACL_UPDATE)
 
 /* operation codes for pg_*_aclmask */
 typedef enum
diff --git a/src/test/regress/expected/session_variables_acl.out b/src/test/regress/expected/session_variables_acl.out
new file mode 100644
index 00000000000..f2219529916
--- /dev/null
+++ b/src/test/regress/expected/session_variables_acl.out
@@ -0,0 +1,335 @@
+-- check access rights and supported ALTER
+CREATE SCHEMA svartest_acl;
+CREATE ROLE regress_variable_owner_acl;
+CREATE ROLE regress_variable_reader_acl;
+GRANT ALL ON SCHEMA svartest_acl TO regress_variable_owner_acl;
+GRANT ALL ON SCHEMA public TO regress_variable_owner_acl;
+ALTER DEFAULT PRIVILEGES
+   FOR ROLE regress_variable_owner_acl
+   IN SCHEMA svartest_acl
+   GRANT SELECT ON VARIABLES TO regress_variable_reader_acl;
+ALTER DEFAULT PRIVILEGES
+   FOR ROLE regress_variable_owner_acl
+   IN SCHEMA svartest_acl
+   GRANT UPDATE ON VARIABLES TO regress_variable_reader_acl;
+-- should to fail
+ALTER DEFAULT PRIVILEGES
+   FOR ROLE regress_variable_owner_acl
+   IN SCHEMA svartest_acl
+   GRANT INSERT ON VARIABLES TO regress_variable_reader_acl;
+ERROR:  invalid privilege type INSERT for session variable
+-- should to fail
+ALTER DEFAULT PRIVILEGES
+   FOR ROLE regress_variable_owner_acl
+   IN SCHEMA svartest_acl
+   GRANT DELETE ON VARIABLES TO regress_variable_reader_acl;
+ERROR:  invalid privilege type DELETE for session variable
+-- creating variable with default privileges
+SET ROLE TO regress_variable_owner_acl;
+CREATE VARIABLE svartest_acl.sesvar20 AS int;
+SET ROLE TO DEFAULT;
+-- should be ok. since ALTER DEFAULT PRIVILEGES
+-- allow regress_variable_reader_acl to have SELECT priviledge
+SELECT has_session_variable_privilege('regress_variable_reader_acl', 'svartest_acl.sesvar20', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_reader_acl', 'svartest_acl.sesvar20', 'UPDATE'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+DROP VARIABLE svartest_acl.sesvar20;
+DROP SCHEMA svartest_acl;
+DROP ROLE regress_variable_reader_acl;
+--
+-- begin of check GRANT WITH GRANT OPTION and REVOKE GRANTED BY
+--
+CREATE ROLE regress_variable_r1_acl;
+CREATE ROLE regress_variable_r2_acl;
+SET ROLE TO regress_variable_owner_acl;
+CREATE VARIABLE sesvar22_acl AS int;      --sesvar22_acl will owned by regress_variable_owner_acl
+GRANT SELECT ON VARIABLE sesvar22_acl TO regress_variable_r1_acl WITH GRANT OPTION;
+SET ROLE TO regress_variable_r1_acl;
+GRANT SELECT ON VARIABLE sesvar22_acl TO regress_variable_r2_acl WITH GRANT OPTION;
+SET ROLE TO DEFAULT;
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'public.sesvar22_acl', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r2_acl', 'public.sesvar22_acl', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+REVOKE ALL PRIVILEGES ON VARIABLE sesvar22_acl FROM regress_variable_r1_acl CASCADE;
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'public.sesvar22_acl', 'SELECT'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r2_acl', 'public.sesvar22_acl', 'SELECT'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SET ROLE TO regress_variable_owner_acl;
+-- should to fail
+GRANT INSERT ON VARIABLE sesvar22_acl TO regress_variable_r2_acl;
+ERROR:  invalid privilege type INSERT for session variable
+GRANT DELETE ON VARIABLE sesvar22_acl TO regress_variable_r2_acl;
+ERROR:  invalid privilege type DELETE for session variable
+-- should be ok
+GRANT SELECT ON VARIABLE sesvar22_acl TO regress_variable_r2_acl;
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'public.sesvar22_acl', 'SELECT'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r2_acl', 'public.sesvar22_acl', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+REVOKE ALL ON VARIABLE sesvar22_acl FROM regress_variable_r2_acl GRANTED BY regress_variable_owner_acl;
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'public.sesvar22_acl', 'SELECT'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r2_acl', 'public.sesvar22_acl', 'SELECT'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_owner_acl', 'public.sesvar22_acl', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SET ROLE TO DEFAULT;
+DROP VARIABLE sesvar22_acl;
+--
+-- end of check GRANT WITH GRANT OPTION and REVOKE GRANTED BY
+--
+--
+-- begin of test: GRANT|REVOKE SELECT|UPDATE ON ALL VARIABLES IN SCHEMA
+--
+CREATE SCHEMA svartest_acl;
+GRANT ALL ON SCHEMA svartest_acl TO regress_variable_owner_acl;
+SET ROLE TO regress_variable_owner_acl;
+CREATE VARIABLE svartest_acl.sesvar20 AS int;
+CREATE VARIABLE svartest_acl.sesvar21 AS int;
+GRANT SELECT ON ALL VARIABLES IN SCHEMA svartest_acl TO regress_variable_r1_acl;
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'svartest_acl.sesvar20', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'svartest_acl.sesvar21', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+REVOKE SELECT ON ALL VARIABLES IN SCHEMA svartest_acl FROM regress_variable_r1_acl;
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'svartest_acl.sesvar20', 'SELECT'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'svartest_acl.sesvar21', 'SELECT'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SET ROLE TO DEFAULT;
+DROP VARIABLE svartest_acl.sesvar20;
+DROP VARIABLE svartest_acl.sesvar21;
+DROP SCHEMA svartest_acl;
+--
+-- end of test: GRANT|REVOKE SELECT|UPDATE ON ALL VARIABLES IN SCHEMA
+--
+--
+-- function has_session_variable_privilege have various kind of signature.
+-- the following are extensive test for it.
+--
+SET ROLE TO regress_variable_owner_acl;
+CREATE VARIABLE public.sesvar22_acl AS int;
+SET search_path TO public;
+GRANT SELECT ON VARIABLE public.sesvar22_acl TO regress_variable_r1_acl;
+GRANT SELECT, UPDATE ON VARIABLE public.sesvar22_acl TO regress_variable_r2_acl;
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'public.sesvar22_acl', 'SELECT');
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'public.notexists', 'SELECT') IS NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+SET ROLE TO regress_variable_r1_acl;
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'sesvar22_acl', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'sesvar22_acl', 'UPDATE'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r2_acl', 'sesvar22_acl', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r2_acl', 'sesvar22_acl', 'UPDATE'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('sesvar22_acl', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('sesvar22_acl', 'UPDATE'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SELECT oid AS varid
+  FROM pg_variable
+  WHERE varname = 'sesvar22_acl' AND varnamespace = 'public'::regnamespace \gset
+SELECT has_session_variable_privilege('sesvar22_acl', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('sesvar22_acl', 'UPDATE'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', :varid, 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', :varid, 'UPDATE'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r2_acl', :varid, 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r2_acl', :varid, 'UPDATE'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege(:varid, 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege(:varid, 'UPDATE'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl'::regrole, 'sesvar22_acl', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl'::regrole, 'sesvar22_acl', 'UPDATE'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r2_acl'::regrole, 'sesvar22_acl', 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r2_acl'::regrole, 'sesvar22_acl', 'UPDATE'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl'::regrole, :varid, 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl'::regrole, :varid, 'UPDATE'); -- f
+ has_session_variable_privilege 
+--------------------------------
+ f
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r2_acl'::regrole, :varid, 'SELECT'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+SELECT has_session_variable_privilege('regress_variable_r2_acl'::regrole, :varid, 'UPDATE'); -- t
+ has_session_variable_privilege 
+--------------------------------
+ t
+(1 row)
+
+--
+-- end of function has_session_variable_privilege tests.
+--
+SET ROLE TO DEFAULT;
+SET search_path TO DEFAULT;
+DROP VARIABLE public.sesvar22_acl;
+DROP ROLE regress_variable_r1_acl;
+DROP ROLE regress_variable_r2_acl;
+REVOKE ALL ON SCHEMA public FROM regress_variable_owner_acl;
+DROP ROLE regress_variable_owner_acl;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 3abc1aca5f2..95c76baf9ae 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -115,7 +115,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
 # NB: temp.sql does reconnects which transiently use 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml session_variables_ddl
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml session_variables_ddl session_variables_acl
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/sql/session_variables_acl.sql b/src/test/regress/sql/session_variables_acl.sql
new file mode 100644
index 00000000000..4b3653cd4ed
--- /dev/null
+++ b/src/test/regress/sql/session_variables_acl.sql
@@ -0,0 +1,182 @@
+-- check access rights and supported ALTER
+CREATE SCHEMA svartest_acl;
+CREATE ROLE regress_variable_owner_acl;
+CREATE ROLE regress_variable_reader_acl;
+
+GRANT ALL ON SCHEMA svartest_acl TO regress_variable_owner_acl;
+GRANT ALL ON SCHEMA public TO regress_variable_owner_acl;
+
+ALTER DEFAULT PRIVILEGES
+   FOR ROLE regress_variable_owner_acl
+   IN SCHEMA svartest_acl
+   GRANT SELECT ON VARIABLES TO regress_variable_reader_acl;
+
+ALTER DEFAULT PRIVILEGES
+   FOR ROLE regress_variable_owner_acl
+   IN SCHEMA svartest_acl
+   GRANT UPDATE ON VARIABLES TO regress_variable_reader_acl;
+
+-- should to fail
+ALTER DEFAULT PRIVILEGES
+   FOR ROLE regress_variable_owner_acl
+   IN SCHEMA svartest_acl
+   GRANT INSERT ON VARIABLES TO regress_variable_reader_acl;
+
+-- should to fail
+ALTER DEFAULT PRIVILEGES
+   FOR ROLE regress_variable_owner_acl
+   IN SCHEMA svartest_acl
+   GRANT DELETE ON VARIABLES TO regress_variable_reader_acl;
+
+-- creating variable with default privileges
+SET ROLE TO regress_variable_owner_acl;
+CREATE VARIABLE svartest_acl.sesvar20 AS int;
+SET ROLE TO DEFAULT;
+
+-- should be ok. since ALTER DEFAULT PRIVILEGES
+-- allow regress_variable_reader_acl to have SELECT priviledge
+SELECT has_session_variable_privilege('regress_variable_reader_acl', 'svartest_acl.sesvar20', 'SELECT'); -- t
+SELECT has_session_variable_privilege('regress_variable_reader_acl', 'svartest_acl.sesvar20', 'UPDATE'); -- t
+
+DROP VARIABLE svartest_acl.sesvar20;
+DROP SCHEMA svartest_acl;
+DROP ROLE regress_variable_reader_acl;
+
+--
+-- begin of check GRANT WITH GRANT OPTION and REVOKE GRANTED BY
+--
+CREATE ROLE regress_variable_r1_acl;
+CREATE ROLE regress_variable_r2_acl;
+
+SET ROLE TO regress_variable_owner_acl;
+CREATE VARIABLE sesvar22_acl AS int;      --sesvar22_acl will owned by regress_variable_owner_acl
+
+GRANT SELECT ON VARIABLE sesvar22_acl TO regress_variable_r1_acl WITH GRANT OPTION;
+SET ROLE TO regress_variable_r1_acl;
+GRANT SELECT ON VARIABLE sesvar22_acl TO regress_variable_r2_acl WITH GRANT OPTION;
+SET ROLE TO DEFAULT;
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'public.sesvar22_acl', 'SELECT'); -- t
+SELECT has_session_variable_privilege('regress_variable_r2_acl', 'public.sesvar22_acl', 'SELECT'); -- t
+
+REVOKE ALL PRIVILEGES ON VARIABLE sesvar22_acl FROM regress_variable_r1_acl CASCADE;
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'public.sesvar22_acl', 'SELECT'); -- f
+SELECT has_session_variable_privilege('regress_variable_r2_acl', 'public.sesvar22_acl', 'SELECT'); -- f
+
+SET ROLE TO regress_variable_owner_acl;
+
+-- should to fail
+GRANT INSERT ON VARIABLE sesvar22_acl TO regress_variable_r2_acl;
+GRANT DELETE ON VARIABLE sesvar22_acl TO regress_variable_r2_acl;
+
+-- should be ok
+GRANT SELECT ON VARIABLE sesvar22_acl TO regress_variable_r2_acl;
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'public.sesvar22_acl', 'SELECT'); -- f
+SELECT has_session_variable_privilege('regress_variable_r2_acl', 'public.sesvar22_acl', 'SELECT'); -- t
+
+REVOKE ALL ON VARIABLE sesvar22_acl FROM regress_variable_r2_acl GRANTED BY regress_variable_owner_acl;
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'public.sesvar22_acl', 'SELECT'); -- f
+SELECT has_session_variable_privilege('regress_variable_r2_acl', 'public.sesvar22_acl', 'SELECT'); -- f
+SELECT has_session_variable_privilege('regress_variable_owner_acl', 'public.sesvar22_acl', 'SELECT'); -- t
+SET ROLE TO DEFAULT;
+
+DROP VARIABLE sesvar22_acl;
+--
+-- end of check GRANT WITH GRANT OPTION and REVOKE GRANTED BY
+--
+
+--
+-- begin of test: GRANT|REVOKE SELECT|UPDATE ON ALL VARIABLES IN SCHEMA
+--
+CREATE SCHEMA svartest_acl;
+GRANT ALL ON SCHEMA svartest_acl TO regress_variable_owner_acl;
+SET ROLE TO regress_variable_owner_acl;
+
+CREATE VARIABLE svartest_acl.sesvar20 AS int;
+CREATE VARIABLE svartest_acl.sesvar21 AS int;
+
+GRANT SELECT ON ALL VARIABLES IN SCHEMA svartest_acl TO regress_variable_r1_acl;
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'svartest_acl.sesvar20', 'SELECT'); -- t
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'svartest_acl.sesvar21', 'SELECT'); -- t
+
+REVOKE SELECT ON ALL VARIABLES IN SCHEMA svartest_acl FROM regress_variable_r1_acl;
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'svartest_acl.sesvar20', 'SELECT'); -- f
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'svartest_acl.sesvar21', 'SELECT'); -- f
+
+SET ROLE TO DEFAULT;
+DROP VARIABLE svartest_acl.sesvar20;
+DROP VARIABLE svartest_acl.sesvar21;
+DROP SCHEMA svartest_acl;
+--
+-- end of test: GRANT|REVOKE SELECT|UPDATE ON ALL VARIABLES IN SCHEMA
+--
+
+--
+-- function has_session_variable_privilege have various kind of signature.
+-- the following are extensive test for it.
+--
+SET ROLE TO regress_variable_owner_acl;
+
+CREATE VARIABLE public.sesvar22_acl AS int;
+
+SET search_path TO public;
+
+GRANT SELECT ON VARIABLE public.sesvar22_acl TO regress_variable_r1_acl;
+GRANT SELECT, UPDATE ON VARIABLE public.sesvar22_acl TO regress_variable_r2_acl;
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'public.sesvar22_acl', 'SELECT');
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'public.notexists', 'SELECT') IS NULL;
+
+SET ROLE TO regress_variable_r1_acl;
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'sesvar22_acl', 'SELECT'); -- t
+SELECT has_session_variable_privilege('regress_variable_r1_acl', 'sesvar22_acl', 'UPDATE'); -- f
+SELECT has_session_variable_privilege('regress_variable_r2_acl', 'sesvar22_acl', 'SELECT'); -- t
+SELECT has_session_variable_privilege('regress_variable_r2_acl', 'sesvar22_acl', 'UPDATE'); -- t
+
+SELECT has_session_variable_privilege('sesvar22_acl', 'SELECT'); -- t
+SELECT has_session_variable_privilege('sesvar22_acl', 'UPDATE'); -- f
+
+SELECT oid AS varid
+  FROM pg_variable
+  WHERE varname = 'sesvar22_acl' AND varnamespace = 'public'::regnamespace \gset
+
+SELECT has_session_variable_privilege('sesvar22_acl', 'SELECT'); -- t
+SELECT has_session_variable_privilege('sesvar22_acl', 'UPDATE'); -- f
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl', :varid, 'SELECT'); -- t
+SELECT has_session_variable_privilege('regress_variable_r1_acl', :varid, 'UPDATE'); -- f
+SELECT has_session_variable_privilege('regress_variable_r2_acl', :varid, 'SELECT'); -- t
+SELECT has_session_variable_privilege('regress_variable_r2_acl', :varid, 'UPDATE'); -- t
+
+SELECT has_session_variable_privilege(:varid, 'SELECT'); -- t
+SELECT has_session_variable_privilege(:varid, 'UPDATE'); -- f
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl'::regrole, 'sesvar22_acl', 'SELECT'); -- t
+SELECT has_session_variable_privilege('regress_variable_r1_acl'::regrole, 'sesvar22_acl', 'UPDATE'); -- f
+SELECT has_session_variable_privilege('regress_variable_r2_acl'::regrole, 'sesvar22_acl', 'SELECT'); -- t
+SELECT has_session_variable_privilege('regress_variable_r2_acl'::regrole, 'sesvar22_acl', 'UPDATE'); -- t
+
+SELECT has_session_variable_privilege('regress_variable_r1_acl'::regrole, :varid, 'SELECT'); -- t
+SELECT has_session_variable_privilege('regress_variable_r1_acl'::regrole, :varid, 'UPDATE'); -- f
+SELECT has_session_variable_privilege('regress_variable_r2_acl'::regrole, :varid, 'SELECT'); -- t
+SELECT has_session_variable_privilege('regress_variable_r2_acl'::regrole, :varid, 'UPDATE'); -- t
+--
+-- end of function has_session_variable_privilege tests.
+--
+
+SET ROLE TO DEFAULT;
+SET search_path TO DEFAULT;
+
+DROP VARIABLE public.sesvar22_acl;
+
+DROP ROLE regress_variable_r1_acl;
+DROP ROLE regress_variable_r2_acl;
+
+REVOKE ALL ON SCHEMA public FROM regress_variable_owner_acl;
+DROP ROLE regress_variable_owner_acl;
-- 
2.51.0

From 658fd9ef2418a94484800f3e3c646c902b679189 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 11:26:17 +0200
Subject: [PATCH 02/15] CREATE, DROP, ALTER VARIABLE

Implementation of commands:

    CREATE VARIABLE varname AS type
    DROP VARIABLE varname
    ALTER VARIABLE varname OWNER TO
    ALTER VARIABLE varname RENAME TO
    ALTER VARIABLE varname SET SCHEMA

ALTER command uses already prepared infrastructure based on ObjectAddress API,
so this patch implements ObjectAddress related functionality too.

Event triggers for DDL over session variables are supported.
---
 doc/src/sgml/ddl.sgml                         |  21 ++
 doc/src/sgml/glossary.sgml                    |  15 ++
 doc/src/sgml/plpgsql.sgml                     |  14 ++
 doc/src/sgml/ref/allfiles.sgml                |   3 +
 doc/src/sgml/ref/alter_variable.sgml          | 178 +++++++++++++++
 doc/src/sgml/ref/comment.sgml                 |   1 +
 doc/src/sgml/ref/create_schema.sgml           |  12 +-
 doc/src/sgml/ref/create_variable.sgml         | 149 +++++++++++++
 doc/src/sgml/ref/drop_variable.sgml           | 117 ++++++++++
 doc/src/sgml/reference.sgml                   |   3 +
 src/backend/catalog/aclchk.c                  |   4 +
 src/backend/catalog/dependency.c              |   6 +
 src/backend/catalog/namespace.c               | 207 ++++++++++++++++++
 src/backend/catalog/objectaddress.c           |  99 +++++++++
 src/backend/catalog/pg_shdepend.c             |   2 +
 src/backend/commands/Makefile                 |   1 +
 src/backend/commands/alter.c                  |   9 +
 src/backend/commands/dropcmds.c               |   4 +
 src/backend/commands/event_trigger.c          |   4 +
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/seclabel.c               |   1 +
 src/backend/commands/session_variable.c       |  88 ++++++++
 src/backend/commands/tablecmds.c              |  41 ++++
 src/backend/commands/typecmds.c               |  15 ++
 src/backend/parser/gram.y                     |  86 +++++++-
 src/backend/parser/parse_utilcmd.c            |  12 +
 src/backend/tcop/utility.c                    |  20 ++
 src/backend/utils/cache/lsyscache.c           |  65 ++++++
 src/include/catalog/namespace.h               |   6 +
 src/include/commands/session_variable.h       |  24 ++
 src/include/nodes/parsenodes.h                |  16 ++
 src/include/parser/kwlist.h                   |   1 +
 src/include/tcop/cmdtaglist.h                 |   3 +
 src/include/utils/lsyscache.h                 |   4 +
 src/test/regress/expected/dependency.out      |  17 ++
 .../expected/session_variables_ddl.out        | 163 ++++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/dependency.sql           |  14 ++
 .../regress/sql/session_variables_ddl.sql     | 150 +++++++++++++
 src/tools/pgindent/typedefs.list              |   1 +
 40 files changed, 1571 insertions(+), 8 deletions(-)
 create mode 100644 doc/src/sgml/ref/alter_variable.sgml
 create mode 100644 doc/src/sgml/ref/create_variable.sgml
 create mode 100644 doc/src/sgml/ref/drop_variable.sgml
 create mode 100644 src/backend/commands/session_variable.c
 create mode 100644 src/include/commands/session_variable.h
 create mode 100644 src/test/regress/expected/session_variables_ddl.out
 create mode 100644 src/test/regress/sql/session_variables_ddl.sql

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 65bc070d2e5..fa711a09bc4 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5362,6 +5362,27 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>
 
+  <sect1 id="ddl-session-variables">
+   <title>Session Variables</title>
+
+   <indexterm zone="ddl-session-variables">
+    <primary>Session variables</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>session variable</primary>
+   </indexterm>
+
+   <para>
+    Session variables are database objects that can hold a value.
+   </para>
+
+   <para>
+    The session variable holds value in session memory.  This value is private
+    to each session and is released when the session ends.
+   </para>
+  </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>
 
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index 8651f0cdb91..c37fd5da50b 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -1711,6 +1711,21 @@
    </glossdef>
   </glossentry>
 
index e937491e6b8..1e4c43b8b61 100644
@@ -6036,6 +6036,20 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE;
 
index 00000000000..96d2586423e
 
index ed69298ccc6..d2bb265209b 100644
@@ -214,6 +215,11 @@ CREATE VIEW hollywood.winners AS
    The <literal>IF NOT EXISTS</literal> option is a
    <productname>PostgreSQL</productname> extension.
 
  <refsect1>
index 00000000000..6e988f2e472
@@ -0,0 +1,149 @@
+CREATE VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ]
index cd139bd65a6..00e3630e0ec 100644
@@ -2784,6 +2784,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 						elog(ERROR, "unsupported object type: %d", objtype);
 				}
 
@@ -2891,6 +2892,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TSDICTIONARY:
 						msg = gettext_noop("must be owner of text search dictionary %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("must be owner of session variable %s");
+						break;
 
 						/*
 						 * Special cases: For these, the error message talks
index 7dded634eb8..1d62e63d4f7 100644
 
+		case VariableRelationId:
+			DropVariableById(object->objectId);
+			break;
+
 		case CastRelationId:
 		case CollationRelationId:
 		case ConversionRelationId:
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..ab837a3cb9e 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -41,6 +41,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "common/hashfn_unstable.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
@@ -224,6 +225,7 @@ static bool TSParserIsVisibleExt(Oid prsId, bool *is_missing);
 static bool TSDictionaryIsVisibleExt(Oid dictId, bool *is_missing);
 static bool TSTemplateIsVisibleExt(Oid tmplId, bool *is_missing);
 static bool TSConfigIsVisibleExt(Oid cfgid, bool *is_missing);
+static bool VariableIsVisibleExt(Oid varid, bool *is_missing);
 static void recomputeNamespacePath(void);
 static void AccessTempTableNamespace(bool force);
 static void InitTempTableNamespace(void);
@@ -985,6 +987,84 @@ RelationIsVisibleExt(Oid relid, bool *is_missing)
 	return visible;
 }
 
+/*
+ * VariableIsVisible
+ *		Determine whether a variable (identified by OID) is visible in the
+ *		current search path. Visible means "would be found by searching
+ *		for the unqualified variable name".
+ */
+bool
+VariableIsVisible(Oid varid)
+{
+	return VariableIsVisibleExt(varid, NULL);
+}
+
+/*
+ * VariableIsVisibleExt
+ *		As above, but if the variable isn't found and is_missing is not NULL,
+ *		then set *is_missing = true and return false, instead of throwing
+ *		an error. (Caller must initialize *is_missing = false.)
+ */
+static bool
+VariableIsVisibleExt(Oid varid, bool *is_missing)
+{
+	HeapTuple	vartup;
+	Form_pg_variable varform;
+	Oid			varnamespace;
+	bool		visible;
+
+	vartup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+	if (!HeapTupleIsValid(vartup))
+	{
+		if (is_missing != NULL)
+		{
+			*is_missing = true;
+			return false;
+		}
+
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+	}
+	varform = (Form_pg_variable) GETSTRUCT(vartup);
+
+	recomputeNamespacePath();
+
+	/*
+	 * Quick check: if it ain't in the path at all, it ain't visible. We don't
+	 * expect usage of session variables in the system namespace.
+	 */
+	varnamespace = varform->varnamespace;
+	if (!list_member_oid(activeSearchPath, varnamespace))
+		visible = false;
+	else
+	{
+		/*
+		 * If it is in the path, it might still not be visible; it could be
+		 * hidden by another variable of the same name earlier in the path. So
+		 * we must do a slow check for conflicting relations.
+		 */
+		char	   *varname = NameStr(varform->varname);
+
+		visible = false;
+		foreach_oid(namespaceId, activeSearchPath)
+		{
+			if (namespaceId == varnamespace)
+			{
+				/* found it first in path */
+				visible = true;
+				break;
+			}
+			if (OidIsValid(get_varname_varid(varname, namespaceId)))
+			{
+				/* found something else first in path */
+				break;
+			}
+		}
+	}
+
+	ReleaseSysCache(vartup);
+
+	return visible;
+}
 
 /*
  * TypenameGetTypid
@@ -3359,6 +3439,133 @@ TSConfigIsVisibleExt(Oid cfgid, bool *is_missing)
 	return visible;
 }
 
+/*
+ * Returns oid of session variable specified by possibly qualified identifier.
+ *
+ * If not found, returns InvalidOid if missing_ok, else throws error.
+ */
+Oid
+LookupVariable(const char *nspname,
+			   const char *varname,
+			   bool missing_ok)
+{
+	Oid			varoid = InvalidOid;
+
+	if (nspname)
+	{
+		Oid			namespaceId = LookupExplicitNamespace(nspname, missing_ok);
+
+		/* if nspname is a known namespace, the variable must be there */
+		if (OidIsValid(namespaceId))
+		{
+			varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+									 PointerGetDatum(varname),
+									 ObjectIdGetDatum(namespaceId));
+		}
+	}
+	else
+	{
+		/* iterate over the schemas on the search_path */
+		recomputeNamespacePath();
+
+		foreach_oid(namespaceId, activeSearchPath)
+		{
+			varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+									 PointerGetDatum(varname),
+									 ObjectIdGetDatum(namespaceId));
+
+			if (OidIsValid(varoid))
+				break;
+		}
+	}
+
+	if (!OidIsValid(varoid) && !missing_ok)
+	{
+		if (nspname)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("session variable \"%s.%s\" does not exist",
+							nspname, varname)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("session variable \"%s\" does not exist",
+							varname)));
+	}
+
+	return varoid;
+}
+
+/*
+ * Returns oid of session variable specified by possibly qualified identifier
+ *
+ * If not found, returns InvalidOid if missing_ok, else throws error.
+ */
+Oid
+LookupVariableFromNameList(List *names,
+						   bool missing_ok)
+{
+	char	   *catname = NULL;
+	char	   *nspname = NULL;
+	char	   *varname = NULL;
+
+	switch (list_length(names))
+	{
+		case 1:
+			varname = strVal(linitial(names));
+			break;
+		case 2:
+			nspname = strVal(linitial(names));
+			varname = strVal(lsecond(names));
+			break;
+		case 3:
+			catname = strVal(linitial(names));
+			nspname = strVal(lsecond(names));
+			varname = strVal(lthird(names));
+
+			/* check catalog name */
+			if (strcmp(catname, get_database_name(MyDatabaseId)) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cross-database references are not implemented: %s",
+								NameListToString(names))));
+			break;
+		default:
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("improper session variable name (too many dotted names): %s",
+							NameListToString(names))));
+			break;
+	}
+
+	return LookupVariable(nspname, varname, missing_ok);
+}
+
+/*
+ * The input list contains names with indirection expressions used as the left
+ * part of LET statement. The following routine returns a new list with only
+ * initial strings (names) - without indirection expressions.
+ */
+List *
+NamesFromList(List *names)
+{
+	ListCell   *l;
+	List	   *result = NIL;
+
+	foreach(l, names)
+	{
+		Node	   *n = lfirst(l);
+
+		if (IsA(n, String))
+		{
+			result = lappend(result, n);
+		}
+		else
+			break;
+	}
+
+	return result;
+}
 
 /*
index c75b7131ed7..ac1f8a4db3b 100644
@@ -635,6 +636,20 @@ static const ObjectPropertyType ObjectProperty[] =
 
 /*
@@ -830,6 +845,9 @@ static const struct object_type_map
 	},
 	{
 		"statistics object", OBJECT_STATISTIC_EXT
+	},
+	{
+		"session variable", OBJECT_VARIABLE
 	}
 };
 
@@ -855,6 +873,7 @@ static ObjectAddress get_object_address_attrdef(ObjectType objtype,
 												bool missing_ok);
 
@@ -2101,6 +2123,24 @@ textarray_to_strvaluelist(ArrayType *arr)
 	return list;
 }
 
+/*
@@ -2295,6 +2335,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
@@ -2466,6 +2507,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
@@ -3495,6 +3537,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
@@ -4669,6 +4737,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 
+		case VariableRelationId:
+			appendStringInfoString(&buffer, "session variable");
+			break;
+
 		default:
 			elog(ERROR, "unsupported object class: %u", object->classId);
 	}
@@ -6019,6 +6091,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+					*objname = list_make2(schema, pstrdup(varname));
index 16e3e5c7457..6e3e8813328 100644
index cb2fbdc7c60..aee40e7bd59 100644
@@ -53,6 +53,7 @@ OBJS = \
index cb75e11fced..aaf61a6f61c 100644
@@ -140,6 +141,10 @@ report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
@@ -435,6 +440,7 @@ ExecRenameStmt(RenameStmt *stmt)
@@ -575,6 +581,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
@@ -657,6 +664,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 
@@ -887,6 +895,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 		case OBJECT_TABLESPACE:
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSCONFIGURATION:
+		case OBJECT_VARIABLE:
 			{
 				ObjectAddress address;
 
index ceb9a229b63..ebb585dc4a1 100644
 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index f34868da5ab..6b46aeaef59 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -2281,6 +2281,8 @@ stringify_grant_objtype(ObjectType objtype)
 			return "TABLESPACE";
 		case OBJECT_TYPE:
 			return "TYPE";
+		case OBJECT_VARIABLE:
+			return "VARIABLE";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
@@ -2364,6 +2366,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 			return "TABLESPACES";
 		case OBJECT_TYPE:
 			return "TYPES";
+		case OBJECT_VARIABLE:
+			return "VARIABLES";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index dd4cde41d32..101c8d75dd1 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -41,6 +41,7 @@ backend_sources += files(
   'schemacmds.c',
   'seclabel.c',
   'sequence.c',
+  'session_variable.c',
   'statscmds.c',
   'subscriptioncmds.c',
   'tablecmds.c',
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index cee5d7bbb9c..57b4e6719c2 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -92,6 +92,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_USER_MAPPING:
+		case OBJECT_VARIABLE:
 			return false;
 
 			/*
index 00000000000..f641e00c1ac
@@ -0,0 +1,88 @@
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+#include "catalog/pg_type.h"
+		collation = typcollation;
index fc89352b661..8891a468cfa 100644
@@ -6913,6 +6914,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -6976,6 +6978,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
+		/* check if the type is used as type of some session variable */
+		if (pg_depend->classid == VariableRelationId)
+		{
+			Oid			varid = pg_depend->objid;
+
+			if (origTypeName)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it",
+								origTypeName,
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter foreign table \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_RELATION ||
+					 origRelation->rd_rel->relkind == RELKIND_MATVIEW ||
+					 origRelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter table \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+
+			continue;
+		}
+
 		/* Else, ignore dependees that aren't relations */
 		if (pg_depend->classid != RelationRelationId)
 			continue;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index c6de04819f1..4749f3f7e56 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/defrem.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
@@ -3391,6 +3392,20 @@ get_rels_with_domain(Oid domainOid, LOCKMODE lockmode)
 			}
 			continue;
 		}
+		else if (pg_depend->classid == VariableRelationId)
+		{
+			/*
+			 * We cannot to validate constraint inside session variables from
+			 * other sessions, so better to fail if there are any session
+			 * variable, that use this domain.
+			 */
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot alter domain \"%s\" because session variable \"%s.%s\" uses it",
+							domainTypeName,
+							get_namespace_name(get_session_variable_namespace(pg_depend->objid)),
+							get_session_variable_name(pg_depend->objid))));
+		}
 
 		/* Else, ignore dependees that aren't user columns of relations */
 		/* (we assume system columns are never of domain types) */
index 57bf7a7c7f2..ab90df203ea 100644
@@ -285,8 +286,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -783,8 +784,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
-	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
+	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARIABLE
+	VARYING VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE

@@ -1050,6 +1051,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1627,6 +1629,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;

@@ -5309,6 +5312,34 @@ create_extension_opt_item:
 				}
 		;
 
@@ -7120,6 +7151,7 @@ object_type_any_name:
 
 /*
@@ -10055,6 +10087,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10416,6 +10466,24 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10697,6 +10765,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -18068,6 +18144,7 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18724,6 +18801,7 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..059d8f7f180 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -105,6 +105,7 @@ typedef struct
 	List	   *indexes;		/* CREATE INDEX items */
 	List	   *triggers;		/* CREATE TRIGGER items */
 	List	   *grants;			/* GRANT items */
+	List	   *variables;		/* CREATE VARIABLE items */
 } CreateSchemaStmtContext;

@@ -4112,6 +4113,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 	cxt.indexes = NIL;
 	cxt.triggers = NIL;
 	cxt.grants = NIL;
+	cxt.variables = NIL;
 
 	/*
 	 * Run through each schema element in the schema element list. Separate
@@ -4180,6 +4182,15 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				cxt.grants = lappend(cxt.grants, element);
 				break;
 
@@ -4193,6 +4204,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 
 	return result;
 }
index 918db53dd5e..1a0e11ba701 100644
@@ -48,6 +48,7 @@
@@ -1380,6 +1382,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				}
 				break;
 
+			case T_CreateSessionVarStmt:
+				address = CreateVariable(pstate, (CreateSessionVarStmt *) parsetree);
+				break;
+
 				/*
 				 * ************* object creation / destruction **************
 				 */
@@ -2333,6 +2339,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_STATISTIC_EXT:
 			tag = CMDTAG_ALTER_STATISTICS;
 			break;
+		case OBJECT_VARIABLE:
+			tag = CMDTAG_ALTER_VARIABLE;
+			break;
 		default:
 			tag = CMDTAG_UNKNOWN;
 			break;
@@ -2641,6 +2650,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_STATISTIC_EXT:
 					tag = CMDTAG_DROP_STATISTICS;
 					break;
+				case OBJECT_VARIABLE:
+					tag = CMDTAG_DROP_VARIABLE;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
@@ -3217,6 +3229,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
@@ -3751,6 +3767,10 @@ GetCommandLogLevel(Node *parsetree)
 			}
 			break;
 
+			lev = LOGSTMT_DDL;
index fa7cd7e06a7..1c4031eea23 100644
@@ -40,6 +40,7 @@
@@ -3881,3 +3882,67 @@ get_subscription_name(Oid subid, bool missing_ok)
 
 	return subname;
 }
+
index f1423f28c32..d12c3b957b7 100644
@@ -115,6 +115,8 @@ extern Oid	TypenameGetTypid(const char *typname);
 
@@ -189,6 +191,10 @@ extern SearchPathMatcher *GetSearchPathMatcher(MemoryContext context);
 
+extern List *NamesFromList(List *names);
+extern Oid	LookupVariable(const char *nspname, const char *varname, bool missing_ok);
+extern Oid	LookupVariableFromNameList(List *names, bool missing_ok);
+
 extern Oid	get_collation_oid(List *collname, bool missing_ok);
 extern Oid	get_conversion_oid(List *conname, bool missing_ok);
 extern Oid	FindDefaultConversionProc(int32 for_encoding, int32 to_encoding);
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
new file mode 100644
index 00000000000..49f36ac6885
--- /dev/null
+++ b/src/include/commands/session_variable.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * sessionvariable.h
+ *	  prototypes for sessionvariable.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/session_variable.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SESSIONVARIABLE_H
+#define SESSIONVARIABLE_H
+
+#include "catalog/objectaddress.h"
+#include "parser/parse_node.h"
+#include "nodes/parsenodes.h"
+
+extern ObjectAddress CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt);
+
+#endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 87c1086ec99..6d006b38ac2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2373,6 +2373,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW,
 } ObjectType;
 
@@ -3555,6 +3556,21 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+
index 84182eaaae2..5e6d3aff8af 100644
@@ -488,6 +488,7 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)
index d250a714d59..ea86954dded 100644
index 50fb149e9ac..03c4c58580e 100644
@@ -139,6 +139,7 @@ extern char get_func_prokind(Oid funcid);
@@ -211,6 +212,9 @@ extern char *get_publication_name(Oid pubid, bool missing_ok);
 
+extern char *get_session_variable_name(Oid varid);
+extern Oid	get_session_variable_namespace(Oid varid);
+
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
 #define type_is_array_domain(typid)  (get_base_element_type(typid) != InvalidOid)
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 75a078ada9e..cd8e4412fa9 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -151,3 +151,20 @@ owner of type deptest_t
 DROP OWNED BY regress_dep_user2, regress_dep_user0;
 DROP USER regress_dep_user2;
 DROP USER regress_dep_user0;
+-- dependency on type
+CREATE DOMAIN vardomain AS int;
+CREATE TYPE vartype AS (a int, b int, c vardomain);
+CREATE VARIABLE var1 AS vartype;
+-- should fail
+DROP DOMAIN vardomain;
+ERROR:  cannot drop type vardomain because other objects depend on it
+DETAIL:  column c of composite type vartype depends on type vardomain
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TYPE vartype;
+ERROR:  cannot drop type vartype because other objects depend on it
+DETAIL:  session variable var1 depends on type vartype
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- clean up
+DROP VARIABLE var1;
+DROP TYPE vartype;
+DROP DOMAIN vardomain;
diff --git a/src/test/regress/expected/session_variables_ddl.out b/src/test/regress/expected/session_variables_ddl.out
new file mode 100644
index 00000000000..9c7595e9a41
--- /dev/null
+++ b/src/test/regress/expected/session_variables_ddl.out
@@ -0,0 +1,163 @@
+SET log_statement TO ddl;
+CREATE VARIABLE ddltest_sesvar01 AS int;
+CREATE VARIABLE public.ddltest_sesvar02 AS int;
+CREATE SCHEMA sesvartest_ddl;
+CREATE VARIABLE sesvartest_ddl.ddltest_sesvar03 AS int;
+SELECT pg_identify_object_as_address(classid, objid, objsubid)
+  FROM pg_get_object_address('session variable', '{ddltest_sesvar01}', '{}');
+            pg_identify_object_as_address            
+-----------------------------------------------------
+ ("session variable","{public,ddltest_sesvar01}",{})
+(1 row)
+
+SELECT pg_identify_object_as_address(classid, objid, objsubid)
+  FROM pg_get_object_address('session variable', '{public,ddltest_sesvar02}', '{}');
+            pg_identify_object_as_address            
+-----------------------------------------------------
+ ("session variable","{public,ddltest_sesvar02}",{})
+(1 row)
+
+SELECT pg_identify_object_as_address(classid, objid, objsubid)
+  FROM pg_get_object_address('session variable', '{sesvartest_ddl,ddltest_sesvar03}', '{}');
+                pg_identify_object_as_address                
+-------------------------------------------------------------
+ ("session variable","{sesvartest_ddl,ddltest_sesvar03}",{})
+(1 row)
+
+DROP VARIABLE ddltest_sesvar01;
+DROP VARIABLE public.ddltest_sesvar02;
+CREATE TYPE sesvartest_type_ddl AS (a int, b int);
+CREATE DOMAIN sesvartest_domain_ddl AS int;
+CREATE TABLE sesvartest_table_ddl (a int, b int);
+/* prefix ddltest_ should not be used ever in another tests */
+CREATE VARIABLE ddltest_sesvar04 AS sesvartest_type_ddl;
+CREATE VARIABLE ddltest_sesvar05 AS sesvartest_domain_ddl;
+CREATE VARIABLE ddltest_sesvar06 AS sesvartest_table_ddl;
+-- add new field to composite value is supported,
+-- change type of field is prohibited
+-- should be ok
+ALTER TYPE sesvartest_type_ddl ADD ATTRIBUTE c int;
+ALTER TABLE sesvartest_table_ddl ADD COLUMN c int;
+-- should fail
+ALTER TYPE sesvartest_type_ddl ALTER ATTRIBUTE b TYPE numeric;
+ERROR:  cannot alter type "sesvartest_type_ddl" because session variable "public.ddltest_sesvar04" uses it
+ALTER TABLE sesvartest_table_ddl ALTER COLUMN b TYPE numeric;
+ERROR:  cannot alter table "sesvartest_table_ddl" because session variable "public.ddltest_sesvar06" uses it
+ALTER DOMAIN sesvartest_domain_ddl ADD CHECK(value <> 100);
+ERROR:  cannot alter domain "sesvartest_domain_ddl" because session variable "public.ddltest_sesvar05" uses it
+-- should fail
+DROP TYPE sesvartest_type_ddl;
+ERROR:  cannot drop type sesvartest_type_ddl because other objects depend on it
+DETAIL:  session variable ddltest_sesvar04 depends on type sesvartest_type_ddl
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP DOMAIN sesvartest_domain_ddl;
+ERROR:  cannot drop type sesvartest_domain_ddl because other objects depend on it
+DETAIL:  session variable ddltest_sesvar05 depends on type sesvartest_domain_ddl
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE sesvartest_table_ddl;
+ERROR:  cannot drop table sesvartest_table_ddl because other objects depend on it
+DETAIL:  session variable ddltest_sesvar06 depends on type sesvartest_table_ddl
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- check event trigger support
+CREATE OR REPLACE FUNCTION svar_event_trigger_report_dropped()
+RETURNS event_trigger
+AS $$
+DECLARE r record;
+BEGIN
+  FOR r IN SELECT * from pg_event_trigger_dropped_objects()
+  LOOP
+    IF r.classid = 'pg_variable'::regclass AND
+       r.address_names[2] like 'ddltest_sesvar%'
+    THEN
+      RAISE NOTICE
+         'NORMAL: orig=% normal=% istemp=% type=% identity=% name=% args=%',
+         r.original, r.normal, r.is_temporary, r.object_type,
+         r.object_identity, r.address_names, r.address_args;
+    END IF;
+  END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+CREATE EVENT TRIGGER svar_regress_event_trigger_report_dropped ON sql_drop
+  WHEN TAG IN ('DROP VARIABLE', 'DROP SCHEMA')
+  EXECUTE PROCEDURE svar_event_trigger_report_dropped();
+DROP VARIABLE ddltest_sesvar04;
+NOTICE:  NORMAL: orig=t normal=f istemp=f type=session variable identity=public.ddltest_sesvar04 name={public,ddltest_sesvar04} args={}
+DROP VARIABLE ddltest_sesvar05;
+NOTICE:  NORMAL: orig=t normal=f istemp=f type=session variable identity=public.ddltest_sesvar05 name={public,ddltest_sesvar05} args={}
+DROP VARIABLE ddltest_sesvar06;
+NOTICE:  NORMAL: orig=t normal=f istemp=f type=session variable identity=public.ddltest_sesvar06 name={public,ddltest_sesvar06} args={}
+-- should to fail
+DROP SCHEMA sesvartest_ddl;
+ERROR:  cannot drop schema sesvartest_ddl because other objects depend on it
+DETAIL:  session variable sesvartest_ddl.ddltest_sesvar03 depends on schema sesvartest_ddl
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- should be ok
+DROP SCHEMA sesvartest_ddl CASCADE;
+NOTICE:  drop cascades to session variable sesvartest_ddl.ddltest_sesvar03
+NOTICE:  NORMAL: orig=f normal=t istemp=f type=session variable identity=sesvartest_ddl.ddltest_sesvar03 name={sesvartest_ddl,ddltest_sesvar03} args={}
+DROP EVENT TRIGGER svar_regress_event_trigger_report_dropped;
+DROP FUNCTION svar_event_trigger_report_dropped();
+-- should be ok
+DROP TYPE sesvartest_type_ddl;
+DROP DOMAIN sesvartest_domain_ddl;
+DROP TABLE sesvartest_table_ddl;
+-- check comment on variable
+CREATE VARIABLE ddltest_sesvar07 AS int;
+COMMENT ON VARIABLE ddltest_sesvar07 IS 'some session variable comment';
+SELECT pg_catalog.obj_description(oid, 'pg_variable') FROM pg_variable WHERE varname = 'ddltest_sesvar07';
+        obj_description        
+-------------------------------
+ some session variable comment
+(1 row)
+
+DROP VARIABLE ddltest_sesvar07;
+CREATE VARIABLE ddltest_sesvar08 AS int;
+ALTER VARIABLE ddltest_sesvar08 RENAME TO ddltest_sesvar08_renamed;
+CREATE SCHEMA sesvartest_ddl;
+ALTER VARIABLE ddltest_sesvar08_renamed SET SCHEMA sesvartest_ddl;
+CREATE ROLE regress_variable_owner_ddl;
+GRANT ALL ON SCHEMA sesvartest_ddl TO regress_variable_owner_ddl;
+SET ROLE TO regress_variable_owner_ddl;
+-- should fail
+DROP VARIABLE sesvartest_ddl.ddltest_sesvar08_renamed;
+ERROR:  must be owner of session variable sesvartest_ddl.ddltest_sesvar08_renamed
+SET ROLE TO DEFAULT;
+ALTER VARIABLE sesvartest_ddl.ddltest_sesvar08_renamed OWNER TO regress_variable_owner_ddl;
+-- should fail
+DROP ROLE regress_variable_owner_ddl;
+ERROR:  role "regress_variable_owner_ddl" cannot be dropped because some objects depend on it
+DETAIL:  owner of session variable sesvartest_ddl.ddltest_sesvar08_renamed
+privileges for schema sesvartest_ddl
+-- should fail - not on search path
+DROP VARIABLE ddltest_sesvar08_renamed;
+ERROR:  session variable "ddltest_sesvar08_renamed" does not exist
+SET SEARCH_PATH TO 'sesvartest_ddl';
+-- should be ok
+DROP VARIABLE ddltest_sesvar08_renamed;
+SET SEARCH_PATH TO DEFAULT;
+SET ROLE TO DEFAULT;
+DROP SCHEMA sesvartest_ddl;
+DROP ROLE regress_variable_owner_ddl;
+SET log_statement TO DEFAULT;
+CREATE VARIABLE IF NOT EXISTS ddltest_sesvar09 AS int;
+CREATE VARIABLE IF NOT EXISTS ddltest_sesvar09 AS int;
+NOTICE:  session variable "ddltest_sesvar09" already exists, skipping
+DROP VARIABLE IF EXISTS ddltest_sesvar09;
+DROP VARIABLE IF EXISTS ddltest_sesvar09;
+NOTICE:  session variable "ddltest_sesvar09" does not exist, skipping
+CREATE SCHEMA svartest01_ddl CREATE VARIABLE sesvar10 AS int;
+CREATE VARIABLE svartest01_ddl.sesvar11 AS int;
+CREATE SCHEMA svartest02_ddl CREATE VARIABLE sesvar10 AS int;
+-- should to fail
+CREATE VARIABLE svartest01_ddl.sesvar10 AS int;
+ERROR:  session variable "sesvar10" already exists
+ALTER VARIABLE svartest01_ddl.sesvar11 RENAME TO sesvar10;
+ERROR:  session variable "sesvar10" already exists in schema "svartest01_ddl"
+ALTER VARIABLE svartest02_ddl.sesvar10 SET SCHEMA svartest01_ddl;
+ERROR:  session variable "sesvar10" already exists in schema "svartest01_ddl"
+DROP SCHEMA svartest01_ddl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to session variable svartest01_ddl.sesvar10
+drop cascades to session variable svartest01_ddl.sesvar11
+DROP SCHEMA svartest02_ddl CASCADE;
+NOTICE:  drop cascades to session variable svartest02_ddl.sesvar10
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fbffc67ae60..3abc1aca5f2 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -115,7 +115,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
 # NB: temp.sql does reconnects which transiently use 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml session_variables_ddl
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/sql/dependency.sql b/src/test/regress/sql/dependency.sql
index 8d74ed7122c..6c18b7f840a 100644
--- a/src/test/regress/sql/dependency.sql
+++ b/src/test/regress/sql/dependency.sql
@@ -114,3 +114,17 @@ DROP USER regress_dep_user2;
 DROP OWNED BY regress_dep_user2, regress_dep_user0;
 DROP USER regress_dep_user2;
 DROP USER regress_dep_user0;
+
+-- dependency on type
+CREATE DOMAIN vardomain AS int;
+CREATE TYPE vartype AS (a int, b int, c vardomain);
+CREATE VARIABLE var1 AS vartype;
+
+-- should fail
+DROP DOMAIN vardomain;
+DROP TYPE vartype;
+
+-- clean up
+DROP VARIABLE var1;
+DROP TYPE vartype;
+DROP DOMAIN vardomain;
diff --git a/src/test/regress/sql/session_variables_ddl.sql b/src/test/regress/sql/session_variables_ddl.sql
new file mode 100644
index 00000000000..f844469ecb1
--- /dev/null
+++ b/src/test/regress/sql/session_variables_ddl.sql
@@ -0,0 +1,150 @@
+SET log_statement TO ddl;
+
+CREATE VARIABLE ddltest_sesvar01 AS int;
+CREATE VARIABLE public.ddltest_sesvar02 AS int;
+CREATE SCHEMA sesvartest_ddl;
+CREATE VARIABLE sesvartest_ddl.ddltest_sesvar03 AS int;
+
+SELECT pg_identify_object_as_address(classid, objid, objsubid)
+  FROM pg_get_object_address('session variable', '{ddltest_sesvar01}', '{}');
+
+SELECT pg_identify_object_as_address(classid, objid, objsubid)
+  FROM pg_get_object_address('session variable', '{public,ddltest_sesvar02}', '{}');
+
+SELECT pg_identify_object_as_address(classid, objid, objsubid)
+  FROM pg_get_object_address('session variable', '{sesvartest_ddl,ddltest_sesvar03}', '{}');
+
+DROP VARIABLE ddltest_sesvar01;
+DROP VARIABLE public.ddltest_sesvar02;
+
+CREATE TYPE sesvartest_type_ddl AS (a int, b int);
+CREATE DOMAIN sesvartest_domain_ddl AS int;
+CREATE TABLE sesvartest_table_ddl (a int, b int);
+
+/* prefix ddltest_ should not be used ever in another tests */
+CREATE VARIABLE ddltest_sesvar04 AS sesvartest_type_ddl;
+CREATE VARIABLE ddltest_sesvar05 AS sesvartest_domain_ddl;
+CREATE VARIABLE ddltest_sesvar06 AS sesvartest_table_ddl;
+
+-- add new field to composite value is supported,
+-- change type of field is prohibited
+
+-- should be ok
+ALTER TYPE sesvartest_type_ddl ADD ATTRIBUTE c int;
+ALTER TABLE sesvartest_table_ddl ADD COLUMN c int;
+
+-- should fail
+ALTER TYPE sesvartest_type_ddl ALTER ATTRIBUTE b TYPE numeric;
+ALTER TABLE sesvartest_table_ddl ALTER COLUMN b TYPE numeric;
+ALTER DOMAIN sesvartest_domain_ddl ADD CHECK(value <> 100);
+
+-- should fail
+DROP TYPE sesvartest_type_ddl;
+DROP DOMAIN sesvartest_domain_ddl;
+DROP TABLE sesvartest_table_ddl;
+
+-- check event trigger support
+CREATE OR REPLACE FUNCTION svar_event_trigger_report_dropped()
+RETURNS event_trigger
+AS $$
+DECLARE r record;
+BEGIN
+  FOR r IN SELECT * from pg_event_trigger_dropped_objects()
+  LOOP
+    IF r.classid = 'pg_variable'::regclass AND
+       r.address_names[2] like 'ddltest_sesvar%'
+    THEN
+      RAISE NOTICE
+         'NORMAL: orig=% normal=% istemp=% type=% identity=% name=% args=%',
+         r.original, r.normal, r.is_temporary, r.object_type,
+         r.object_identity, r.address_names, r.address_args;
+    END IF;
+  END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE EVENT TRIGGER svar_regress_event_trigger_report_dropped ON sql_drop
+  WHEN TAG IN ('DROP VARIABLE', 'DROP SCHEMA')
+  EXECUTE PROCEDURE svar_event_trigger_report_dropped();
+
+DROP VARIABLE ddltest_sesvar04;
+DROP VARIABLE ddltest_sesvar05;
+DROP VARIABLE ddltest_sesvar06;
+
+-- should to fail
+DROP SCHEMA sesvartest_ddl;
+
+-- should be ok
+DROP SCHEMA sesvartest_ddl CASCADE;
+
+DROP EVENT TRIGGER svar_regress_event_trigger_report_dropped;
+
+DROP FUNCTION svar_event_trigger_report_dropped();
+
+-- should be ok
+DROP TYPE sesvartest_type_ddl;
+DROP DOMAIN sesvartest_domain_ddl;
+DROP TABLE sesvartest_table_ddl;
+
+-- check comment on variable
+CREATE VARIABLE ddltest_sesvar07 AS int;
+COMMENT ON VARIABLE ddltest_sesvar07 IS 'some session variable comment';
+SELECT pg_catalog.obj_description(oid, 'pg_variable') FROM pg_variable WHERE varname = 'ddltest_sesvar07';
+DROP VARIABLE ddltest_sesvar07;
+
+CREATE VARIABLE ddltest_sesvar08 AS int;
+ALTER VARIABLE ddltest_sesvar08 RENAME TO ddltest_sesvar08_renamed;
+
+CREATE SCHEMA sesvartest_ddl;
+ALTER VARIABLE ddltest_sesvar08_renamed SET SCHEMA sesvartest_ddl;
+
+CREATE ROLE regress_variable_owner_ddl;
+
+GRANT ALL ON SCHEMA sesvartest_ddl TO regress_variable_owner_ddl;
+
+SET ROLE TO regress_variable_owner_ddl;
+
+-- should fail
+DROP VARIABLE sesvartest_ddl.ddltest_sesvar08_renamed;
+
+SET ROLE TO DEFAULT;
+
+ALTER VARIABLE sesvartest_ddl.ddltest_sesvar08_renamed OWNER TO regress_variable_owner_ddl;
+
+-- should fail
+DROP ROLE regress_variable_owner_ddl;
+
+-- should fail - not on search path
+DROP VARIABLE ddltest_sesvar08_renamed;
+
+SET SEARCH_PATH TO 'sesvartest_ddl';
+
+-- should be ok
+DROP VARIABLE ddltest_sesvar08_renamed;
+
+SET SEARCH_PATH TO DEFAULT;
+
+SET ROLE TO DEFAULT;
+
+DROP SCHEMA sesvartest_ddl;
+
+DROP ROLE regress_variable_owner_ddl;
+
+SET log_statement TO DEFAULT;
+
+CREATE VARIABLE IF NOT EXISTS ddltest_sesvar09 AS int;
+CREATE VARIABLE IF NOT EXISTS ddltest_sesvar09 AS int;
+DROP VARIABLE IF EXISTS ddltest_sesvar09;
+DROP VARIABLE IF EXISTS ddltest_sesvar09;
+
+CREATE SCHEMA svartest01_ddl CREATE VARIABLE sesvar10 AS int;
+CREATE VARIABLE svartest01_ddl.sesvar11 AS int;
+CREATE SCHEMA svartest02_ddl CREATE VARIABLE sesvar10 AS int;
+
+-- should to fail
+CREATE VARIABLE svartest01_ddl.sesvar10 AS int;
+ALTER VARIABLE svartest01_ddl.sesvar11 RENAME TO sesvar10;
+ALTER VARIABLE svartest02_ddl.sesvar10 SET SCHEMA svartest01_ddl;
+
+DROP SCHEMA svartest01_ddl CASCADE;
+DROP SCHEMA svartest02_ddl CASCADE;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 51f18720deb..e35d9d7dfb0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -560,6 +560,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
-- 
2.51.0

From 97a92578d515e4faa571fbd6f98e7dcbe4d6899e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 08:30:25 +0200
Subject: [PATCH 01/15] introduce new class (catalog) pg_variable

This table holds metadata about session variables created by
command CREATE VARIABLE, and dropped by command DROP VARIABLE.
---
 doc/src/sgml/catalogs.sgml             | 133 ++++++++++++++++++++
 src/backend/catalog/Makefile           |   1 +
 src/backend/catalog/meson.build        |   1 +
 src/backend/catalog/pg_variable.c      | 168 +++++++++++++++++++++++++
 src/include/catalog/Makefile           |   3 +-
 src/include/catalog/meson.build        |   1 +
 src/include/catalog/pg_variable.h      |  96 ++++++++++++++
 src/test/regress/expected/oidjoins.out |   4 +
 src/tools/pgindent/typedefs.list       |   2 +
 9 files changed, 408 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/catalog/pg_variable.c
 create mode 100644 src/include/catalog/pg_variable.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index e9095bedf21..487a58eda74 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -369,6 +369,11 @@
       <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
       <entry>mappings of users to foreign servers</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+      <entry>session variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -9810,4 +9815,132 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </table>
  </sect1>
 
+       <structfield>varcreate_lsn</structfield> <type>pg_lsn</type>
index c090094ed08..2c20d60db19 100644
 
index 1958ea9238a..ed44c877fca 100644
index 00000000000..bd6a29a79e5
@@ -0,0 +1,168 @@
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Drop variable by OID
index 2bbc7805fe3..f98760de635 100644

index ec1cf467f6f..81398efa7c9 100644
 
index 00000000000..15f530894c5
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+#include "access/xlogdefs.h"
+extern ObjectAddress create_variable(const char *varName,
index 37f26f6c6b7..51f18720deb 100644
@@ -909,6 +909,7 @@ FormData_pg_ts_parser
@@ -968,6 +969,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
+Form_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
-- 
2.51.0

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 12 Oct 2025 06:57:56 +0200

Hi

fresh rebase and fix after c83ac02ec7309edb7561eee93895c31a54b93d3d

Regards

Pavel
From 803c810ff6d3bbe20309536cda70b2c26a05afb2 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 14:06:06 +0100
Subject: [PATCH 15/15] plpgsql tests

 

From 2031cf794765e1f79bb46ca345662b3eb171b03e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 2 Jun 2025 20:41:57 +0200
Subject: [PATCH 13/15] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
 
index 3bafa701509..c7f6424b76b 100644

-	Assert(sessionvars);
+	/*
 
 	/*
 
 				/*
 
 	return (Datum) 0;
 }
+
+/*
index 30eb0cc6abf..8eabbeef460 100644
@@ -2126,7 +2126,13 @@ DiscardStmt:

index 749034bad84..1f56aab0abb 100644
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
index dd4e2f151b5..7f0fb937e77 100644

From d368eb7d53f92f5f8cbcf9eaa8230a3b77a85f17 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 12/15] function pg_get_session_variables_memory for cleaning
 tests

index 9bdc92bfd30..3bafa701509 100644
 
 	PopActiveSnapshot();
 }
+
+/*
index 6f70ee3c640..9fce4d72505 100644
@@ -12624,4 +12624,12 @@

From 73ea21358fccfedb05cc2b5010262e5d7663a947 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 2 Jun 2025 22:33:25 +0200
Subject: [PATCH 14/15] memory cleaning after DROP VARIABLE

 
 /*
- * Drop variable by OID
 
index c7f6424b76b..bb063bb7bbc 100644
 
+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

From d9578dc2dc295a9300dde407e36a93725f45060a Mon Sep 17 00:00:00 2001

 
  <sect1 id="ddl-others">
 
   <para>

 
index dbc054795bb..9bdc92bfd30 100644
 
 /*
 
 	return variable;
 }
+
+/*
+						 CURSOR_OPT_PARALLEL_OK, params, NULL);

 			estate->es_session_variables[i].value =
 				GetSessionVariable(varid,
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c761fde6acb..0d5461cbd6b 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -4374,6 +4374,16 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_LetStmt:
+			{
+				LetStmt    *stmt = (LetStmt *) node;
+
+				if (WALK(stmt->target))
+					return true;
+				if (WALK(stmt->query))
+					return true;
+			}
+			break;
 		case T_PLAssignStmt:
 			{
 				PLAssignStmt *stmt = (PLAssignStmt *) node;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1b938a8147c..de86eba1c2b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -376,6 +376,20 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->rel_notnullatts_hash = NULL;
 	glob->sessionVariables = NIL;
 
+	/*
@@ -621,6 +635,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,

+	/*
 
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index a12a3c6094c..0cbe09834c5 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2249,6 +2249,27 @@ fix_param_node(PlannerInfo *root, Param *p)
 			p->paramid = n;
 		}
 
+		/*
 
@@ -3734,7 +3755,7 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
 
 /*
@@ -3836,9 +3857,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 			}
 
 			/*
@@ -3861,6 +3883,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -
index 97925b8ae80..30eb0cc6abf 100644
@@ -302,7 +302,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -748,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEEP KEY KEYS

 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
@@ -1095,6 +1095,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -12953,6 +12954,38 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -18049,6 +18082,7 @@ unreserved_keyword:
@@ -18664,6 +18698,7 @@ bare_label_keyword:
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 /*
index b6fade2b798..749034bad84 100644
 
+/* LET */

 
 
index 3a31232be7e..dd4e2f151b5 100644

index 0ebb32cef55..768519d9f1d 100644
@@ -192,6 +192,15 @@ typedef struct PlannerGlobal

 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 1b2e91093a8..613cd679608 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -161,6 +161,13 @@ typedef struct PlannedStmt
 	/* OIDs for PARAM_VARIABLE Params */
 	List	   *sessionVariables;
 
+	/*
index 5f3a1a2d1be..9107891fa9d 100644
 
 DROP TABLE svartest_dml;
+
index 3118ae7d28f..26f6f2e728e 100644
@@ -1548,6 +1548,7 @@ LargeObjectDesc

From 7f7212a7363d04ec5a93a49235a226dd7afa5750 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 10/15] svariableReceiver

 

 
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 084c526b6e8..3118ae7d28f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2663,6 +2663,7 @@ STRLEN
 SV
 SVariableData
 SVariable
+SVariableState
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.51.0

From 1b1b931519b50dadf4847e408b126902b89e0552 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 07:32:01 +0200
Subject: [PATCH 09/15] fill an auxiliary buffer with values of session
 variables used in query

 
+	/*
 
 	/*
 
 /*
 
 	/*
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index bdde86043e8..3428a442d0b 100644
 
 # ----------
index bc59698980e..084c526b6e8 100644
@@ -2721,6 +2721,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
-- 
2.51.0

From 7676a5fd532a4b84505bc56faec9931aa5ad04e1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 30 May 2025 07:27:27 +0200
Subject: [PATCH 06/15] session variable fences parsing

 
  <sect1 id="ddl-others">
 
+/* -----
 
+	/*
 
index ede838cd40c..c761fde6acb 100644
@@ -1669,6 +1669,9 @@ exprLocation(const Node *expr)
@@ -4701,6 +4704,9 @@ raw_expression_tree_walker_impl(Node *node,

 

 

 

 

 
index 39d2801a31d..97925b8ae80 100644
@@ -531,7 +531,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -888,7 +888,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -15740,6 +15740,19 @@ c_expr:		columnref								{ $$ = $1; }
@@ -17122,6 +17135,17 @@ case_arg:	a_expr									{ $$ = $1; }
 
+variable_fence:
index 12119f147fc..0265b8efc4d 100644
 
@@ -107,7 +109,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,
 
 /*
  * transformExpr -
@@ -371,6 +375,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr);
 			break;
 
+		case T_VariableFence:
+			result = transformVariableFence(pstate, (VariableFence *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -904,6 +912,135 @@ transformParamRef(ParseState *pstate, ParamRef *pref)
 	return result;
 }
 
+/*
@@ -3123,6 +3260,75 @@ make_nulltest_from_distinct(ParseState *pstate, A_Expr *distincta, Node *arg)
 	return (Node *) nt;
 }
 
+/*

 
index 050eef97a4c..dafaa3a7bdf 100644
@@ -8812,6 +8814,14 @@ get_parameter(Param *param, deparse_context *context)
 		}
 	}
 
@@ -13597,6 +13607,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*
 
 	return varnamespace;
 }
+
+/*

index a8cf47e8ac5..3a31232be7e 100644

+/*
index 1b4436f2ff6..5f3a1a2d1be 100644
 
 typedef struct Param

 

 	/*
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3d10aa335ed..cb64e0c3ed0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3209,6 +3209,7 @@ ValidatorValidateCB
 ValuesScan
 ValuesScanState
 Var
+VariableFence
 VarBit
 VarChar
 VarParamState
-- 
2.51.0

From 364a52de0a7ab77391a3798fd4fd4e7bf41e2ac3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 30 May 2025 09:31:06 +0200
Subject: [PATCH 08/15] collect session variables used in plan and assign
 paramid

* parallel execution

 
 
index e8ea78c0c97..1b938a8147c 100644
@@ -374,6 +374,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/*
@@ -617,6 +618,9 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
@@ -806,6 +810,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name,
 	 */
 	pull_up_subqueries(root);
 
+	/*
index ccdc9bc264a..a12a3c6094c 100644

 /*****************************************************************************
@@ -1341,6 +1344,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 	return plan;
 }
 
+/*
@@ -2041,8 +2088,9 @@ copyVar(Var *var)
@@ -2132,6 +2180,13 @@ fix_expr_common(PlannerInfo *root, Node *node)
 
 /*
@@ -2141,6 +2196,10 @@ fix_expr_common(PlannerInfo *root, Node *node)
@@ -2159,6 +2218,40 @@ fix_param_node(PlannerInfo *root, Param *p)
 
@@ -2220,7 +2313,10 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
@@ -2242,7 +2338,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
@@ -3635,6 +3732,25 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
 	}
 }
 
+/*
index 481d8011791..2355a7ce922 100644
@@ -1647,6 +1647,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,

 
 				/*

 
 /*
 
 /*
 
 	return false;
 }
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 79408743166..0ebb32cef55 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -189,6 +189,9 @@ typedef struct PlannerGlobal
 	/* extension state */
 	void	  **extension_state pg_node_attr(read_write_ignore);
 	int			extension_state_allocated;
+
+	/* list of used session variables */
+	List	   *sessionVariables;
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -547,6 +550,8 @@ struct PlannerInfo
 	bool		hasRecursion;
 	/* true if a planner extension may replan this subquery */
 	bool		assumeReplanning;
+	/* true if session variables were used */
+	bool		hasSessionVariables;
 
 	/*
 	 * The rangetable index for the RTE_GROUP RTE, or 0 if there is no
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 77ec2bc10b2..1b2e91093a8 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -158,6 +158,9 @@ typedef struct PlannedStmt
 	 */
 	List	   *extension_state;
 
index 09b48b26f8f..c79ea0ea18a 100644
@@ -132,4 +132,6 @@ extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);

From d2bd7a2434b757644045f4e3364acc1a567b270a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 22:54:09 +0200
Subject: [PATCH 05/15] support of session variables for pg_dump

 

 
 	int			useDB;
index 641bece12c7..d3d4533f1a7 100644
@@ -5694,6 +5695,188 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
@@ -11826,6 +12009,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
@@ -16332,6 +16518,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -20150,6 +20339,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
index fa6d1a510f7..c96059e18dd 100644
@@ -746,6 +747,19 @@ typedef struct _SubRelInfo
 	char	   *srsublsn;
 } SubRelInfo;
 
+/*
@@ -830,5 +844,6 @@ extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
 
 #endif							/* PG_DUMP_H */
index 6be6888b977..7c80ff36eb0 100644
@@ -1017,6 +1017,16 @@ my %tests = (
 
@@ -2043,6 +2053,23 @@ my %tests = (
 		},
 	},
 
@@ -4435,6 +4462,23 @@ my %tests = (
 		},
 	},
 
@@ -4899,6 +4943,25 @@ my %tests = (
 		like => {},
 	},
 
index 2d7ac17dfe9..3d10aa335ed 100644
@@ -3217,6 +3217,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.51.0

From 87317dbb2c1e06b5f2cdd5fb10dd472a45eeacc8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Aug 2025 06:42:02 +0200
Subject: [PATCH 04/15] support of session variables for psql

 * \dV[+] command

 
 
index 36f24502842..12bc383e83e 100644
 
 	printACLColumn(&buf, "d.defaclacl");
@@ -5348,6 +5350,102 @@ error_return:
 	return false;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 64bfd309c9a..b6fade2b798 100644

 /*

+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*
index 13fe172eb6a..6f70ee3c640 100644
@@ -6717,6 +6717,9 @@
 
index fa8984ffe0d..2ed7903589e 100644
 
+\dV "no.such.variable"
+            List of variables

 

 

 -- check \drg and \du

From dd847a5d883f5c6841a450ca36dbdacc5f02aad4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Aug 2025 06:37:28 +0200
Subject: [PATCH 03/15] GRANT, REVOKE variable

index 9c028421b39..0be9fe7ca16 100644
@@ -3363,7 +3363,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l

    <para>
 
    <para>
 
+      <row>

 
 
   <para>

 
 
    <para>

 
 	return objects;
 
 		default:

+		case OBJECT_VARIABLE:
 
 	/*
 
 				if (objname)

 

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index faa8b2be681..39d2801a31d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -791,7 +791,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING

 
@@ -8066,6 +8066,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -8111,6 +8119,14 @@ privilege_target:

@@ -8309,6 +8325,7 @@ defacl_privilege_target:

@@ -18181,6 +18198,7 @@ unreserved_keyword:
 			| VALIDATOR
 			| VALUE_P
 			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18838,6 +18856,7 @@ bare_label_keyword:
 			| VALUES
 			| VARCHAR
 			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P

+/*
 
 /*

index b51d2b17379..13fe172eb6a 100644

index 36911976cde..bdde86043e8 100644
 
 # ----------

From ff52c2cc6c6c281faf0ea3c3e1de7f3cbb178ad4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 08:30:25 +0200
Subject: [PATCH 01/15] introduce new class (catalog) pg_variable

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9b3aae8603b..9c028421b39 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -369,6 +369,11 @@
       <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
       <entry>mappings of users to foreign servers</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+      <entry>session variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -9823,4 +9828,132 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </table>
  </sect1>

 

index 5290b91e83e..67e6d5263c3 100644
@@ -910,6 +910,7 @@ FormData_pg_ts_parser
@@ -969,6 +970,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
+Form_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
-- 
2.51.0

From f7e313a26e51a788da730df12a77fe9ee8bd99f6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 11:26:17 +0200
Subject: [PATCH 02/15] CREATE, DROP, ALTER VARIABLE

 

 

  <refsect1>

 						/*

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
 
 /*

 
+/*

 

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*
index 5fd8b51312c..8a807161a43 100644
@@ -6912,6 +6913,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -6975,6 +6977,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}

index 21caf2d43bf..faa8b2be681 100644
@@ -290,8 +291,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -789,8 +790,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 
@@ -1056,6 +1057,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1633,6 +1635,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;

@@ -5315,6 +5318,34 @@ create_extension_opt_item:
 				}
 		;
 
@@ -7126,6 +7157,7 @@ object_type_any_name:
 
 /*
@@ -10061,6 +10093,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10422,6 +10472,24 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10703,6 +10771,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -18104,6 +18180,7 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18760,6 +18837,7 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P

 	/*

 	return result;
 }

 
 
 	return subname;
 }
+

index dc09d1a3f03..a8cf47e8ac5 100644

+
 
index f9450cdc477..36911976cde 100644
 
 # ----------
index 67e6d5263c3..2d7ac17dfe9 100644
@@ -561,6 +561,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
-- 
2.51.0

From 14bac17b30523003d13d86696ad185128de053ae Mon Sep 17 00:00:00 2001

 
 
 /*
  * Creates a new variable

 #endif
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cb64e0c3ed0..bc59698980e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2661,6 +2661,8 @@ SSL_CTX
 STARTUPINFO
 STRLEN
 SV
+SVariableData
+SVariable
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.51.0

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 30 Oct 2025 17:27:12 +0100

Hi

fresh rebase

Regards

Pavel
From 295f46994465a9503fbed276e893166c3b9a02e6 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 14:06:06 +0100
Subject: [PATCH 15/15] plpgsql tests

 
2.51.1

From 8b70358fa7bedfc3bed1c6be015e6c3d65011cba Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 12/15] function pg_get_session_variables_memory for cleaning
 tests

 
 	PopActiveSnapshot();
 }
+
+/*
index 8de4d9cfa76..9fb7be987bc 100644
@@ -12627,4 +12627,12 @@
 
+{ oid => '8068', descr => 'internal view of memory entries used by session variables (for debugging)',
2.51.1

From db9a5af8896a995e7be4b88f3ed56611a3fb892c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 2 Jun 2025 22:33:25 +0200
Subject: [PATCH 14/15] memory cleaning after DROP VARIABLE

 
 /*
- * Drop variable by OID

+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

2.51.1

From dafd85d6bd583d0d7f5b65aa7c8924b381f29ec1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 2 Jun 2025 20:41:57 +0200
Subject: [PATCH 13/15] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 				/*
 
 	return (Datum) 0;
 }
+
+/*
index de6a82bb466..173e6d738fa 100644

index dd7ae73e540..0cb50b585e8 100644
@@ -4211,7 +4211,7 @@ match_previous_words(int pattern_id,
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
index 834b44a6102..b09eb3351bc 100644

2.51.1

From e8de9fc142549bcd07709ff671f95bc3b26b8761 Mon Sep 17 00:00:00 2001

 
  <sect1 id="ddl-others">
 
   <para>

 
 
 /*
 
 	return variable;
 }
+
+/*
index 4c5b101c8ee..76f478ff580 100644

index cc03b0311a4..8dccd50737a 100644
 
+	/*

+	/*

+		/*

 /*
 
 			/*

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -
index 55170c9b7ca..de6a82bb466 100644

@@ -12962,6 +12963,38 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 
@@ -18058,6 +18091,7 @@ unreserved_keyword:
@@ -18673,6 +18707,7 @@ bare_label_keyword:
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 /*
index 18e795c3aaf..dd7ae73e540 100644
@@ -4790,6 +4790,14 @@ match_previous_words(int pattern_id,
 
+/* LET */

 
 
index 99947cadee8..834b44a6102 100644

index 75a863dad93..50d4a20be77 100644

index cf4bfab8981..39565b87964 100644
 
+	/*
 
 DROP TABLE svartest_dml;
+
index 82eafa9303c..1ceeb23e909 100644
2.51.1

From bd6d120376a8922194450fe20f3e57ae7e30a4aa Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 10/15] svariableReceiver

 

 
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 42f1e2b018e..82eafa9303c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2665,6 +2665,7 @@ STRLEN
 SV
 SVariableData
 SVariable
+SVariableState
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.51.1

From 147d699a421726bf949ae6b82456e7cb6ec24b1a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 07:32:01 +0200
Subject: [PATCH 09/15] fill an auxiliary buffer with values of session
 variables used in query

index 27c9eec697b..4c5b101c8ee 100644
 
+	/*
 
 	/*
 
 /*
 
 	/*
index 18ae8f0d4bb..b6048d96900 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index d8000da3f86..860c526edbe 100644
 # NB: temp.sql does reconnects which transiently uses 2 connections,
 
 # ----------
index 63e7f6ca1ed..42f1e2b018e 100644
@@ -2723,6 +2723,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
-- 
2.51.1

From fcaa2ce788101ce9f1b7c4adc5cde67623c3f30e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 30 May 2025 09:31:06 +0200
Subject: [PATCH 08/15] collect session variables used in plan and assign
 paramid

* parallel execution

 
 
index c4fd646b999..cc03b0311a4 100644
 
 	/*
 
@@ -805,6 +809,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name,
 	 */
 	pull_up_subqueries(root);
 
+	/*

+/*
 
 /*

+/*

 
 				/*

 
 /*
 
 /*
 
 	return false;
 }
index 30d889b54c5..75a863dad93 100644

 	/*
index 7cdd2b51c94..cf4bfab8981 100644

2.51.1

From 72d301b51fff4f0c4b9708083109dd2a522cd3f3 Mon Sep 17 00:00:00 2001

 
 
 /*
  * Creates a new variable

 #endif
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 02d9d76d1e4..63e7f6ca1ed 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2663,6 +2663,8 @@ SSL_CTX
 STARTUPINFO
 STRLEN
 SV
+SVariableData
+SVariable
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.51.1

From 4787c76852dbb7623d2ac32e9c80555c839535a7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 30 May 2025 07:27:27 +0200
Subject: [PATCH 06/15] session variable fences parsing

 
  <sect1 id="ddl-others">
 
+/* -----
 
+	/*

 

 

 

 

 
 
index a0d19e295c9..55170c9b7ca 100644
@@ -15749,6 +15749,19 @@ c_expr:		columnref								{ $$ = $1; }
@@ -17131,6 +17144,17 @@ case_arg:	a_expr									{ $$ = $1; }
 
+variable_fence:

 /*
  * transformExpr -
 
+		case T_VariableFence:
 
+/*
 
+/*

 
index 79ec136231b..0b1d9e0501d 100644

+/*
 
 	return varnamespace;
 }
+
+/*

index 6dde01f2c7b..99947cadee8 100644

+/*
 
 typedef struct Param

 

 	/*
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 900237acde4..02d9d76d1e4 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3211,6 +3211,7 @@ ValidatorValidateCB
 ValuesScan
 ValuesScanState
 Var
+VariableFence
 VarBit
 VarChar
 VarParamState
-- 
2.51.1

From 1e5466962e82d0350580e5a94f989bead02e5aac Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 22:54:09 +0200
Subject: [PATCH 05/15] support of session variables for pg_dump

diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 4e7303ea631..aea83e50a7b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -247,6 +247,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	pg_log_info("reading subscription membership of relations");
 	getSubscriptionRelations(fout);

 
 
 	int			useDB;
index 47913178a93..922b0060cd9 100644
 
+/*
+ * getVariables
@@ -11828,6 +12011,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
@@ -16329,6 +16515,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -20147,6 +20336,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
index 72a00e1bc20..f94e05a05f1 100644
 
+/*
 extern void getSubscriptionRelations(Archive *fout);
 
 #endif							/* PG_DUMP_H */
index 164c76e0864..9f6ff6b7d57 100644
@@ -1750,6 +1752,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
index 445a541abf6..d5a70b39cc6 100644
@@ -750,6 +750,16 @@ my %tests = (
 
@@ -1776,6 +1786,23 @@ my %tests = (
 		},
 	},
 
@@ -4074,6 +4101,23 @@ my %tests = (
 		},
 	},
 
@@ -4538,6 +4582,25 @@ my %tests = (
 		like => {},
 	},
 
index fc5a27c81f7..900237acde4 100644
@@ -3219,6 +3219,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.51.1

From b2e06770ded4020d4bb63dfc6081bfaafa15989d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Aug 2025 06:42:02 +0200
Subject: [PATCH 04/15] support of session variables for psql

 * \dV[+] command

index 84683f62b1c..40ed4b0fead 100644

 
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 36ea6a4d557..18e795c3aaf 100644

 /*
@@ -1918,7 +1926,7 @@ psql_completion(const char *text, int start, int end)
@@ -2632,6 +2640,9 @@ match_previous_words(int pattern_id,
@@ -3238,7 +3249,7 @@ match_previous_words(int pattern_id,
@@ -4044,6 +4055,13 @@ match_previous_words(int pattern_id,
 
@@ -4321,6 +4339,12 @@ match_previous_words(int pattern_id,
 
+	/* DROP VARIABLE */
@@ -4522,7 +4546,9 @@ match_previous_words(int pattern_id,
@@ -4530,6 +4556,7 @@ match_previous_words(int pattern_id,
@@ -4544,7 +4571,8 @@ match_previous_words(int pattern_id,
@@ -4552,7 +4580,8 @@ match_previous_words(int pattern_id,
 
 	/*
 	 * Complete "GRANT/REVOKE * ON DATABASE/DOMAIN/..." with a list of
@@ -4594,6 +4623,8 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		else if (TailMatches("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
+		else if (TailMatches("VARIABLE"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
 		else if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH("TO");
 		else
@@ -4920,7 +4951,7 @@ match_previous_words(int pattern_id,
 
 /* PREPARE xx AS */
 
 /*
@@ -5409,6 +5440,8 @@ match_previous_words(int pattern_id,
index 1ea3f51841c..8de4d9cfa76 100644

+\dV "no.such.variable"
+            List of variables

 

 

 -- check \drg and \du
2.51.1

From edc7fbc6834bef473be1909f31890f4303d03db9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Aug 2025 06:37:28 +0200
Subject: [PATCH 03/15] GRANT, REVOKE variable

index 7c79ddc9c09..7a6d25e2920 100644

    <para>
 
    <para>
 
+      <row>

 
 
   <para>

 
 
    <para>

 
 	return objects;
 
 		default:

+		case OBJECT_VARIABLE:
 
 	/*
 
 				if (objname)

 

index cee1bb78f7b..a0d19e295c9 100644

 

@@ -18190,6 +18207,7 @@ unreserved_keyword:
 			| VALIDATOR
 			| VALUE_P
 			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18847,6 +18865,7 @@ bare_label_keyword:
 			| VALUES
 			| VARCHAR
 			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P

+/*
 
 /*

index 9121a382f76..1ea3f51841c 100644

index 1bcf69031da..d8000da3f86 100644
 # NB: temp.sql does reconnects which transiently uses 2 connections,
 
 # ----------
2.51.1

From e5c4b683fe425473e5bb26d05f5ed9fae59cc728 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 11:26:17 +0200
Subject: [PATCH 02/15] CREATE, DROP, ALTER VARIABLE

 

 

  <refsect1>

 						/*

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
 
 /*

 
+/*

 

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*
 
index 5979580139f..08666ff7a8b 100644
@@ -3392,6 +3393,20 @@ get_rels_with_domain(Oid domainOid, LOCKMODE lockmode)
 
index a4b29c822e8..cee1bb78f7b 100644

 

 
 /*
 
 opt_column: COLUMN

@@ -18113,6 +18189,7 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18769,6 +18846,7 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P

 	/*

 	return result;
 }

 
 
 	return subname;
 }
+

index ecbddd12e1b..6dde01f2c7b 100644

+
 
index a0f5fab0f5d..1bcf69031da 100644
 # NB: temp.sql does reconnects which transiently uses 2 connections,
 
 # ----------
index acf090b1ccf..fc5a27c81f7 100644
2.51.1

From a10a9a0e0aafad7a38525cedd52024176ed86f91 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 08:30:25 +0200
Subject: [PATCH 01/15] introduce new class (catalog) pg_variable

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6c8a0f173c9..7c79ddc9c09 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -369,6 +369,11 @@
       <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
       <entry>mappings of users to foreign servers</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+      <entry>session variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -9834,4 +9839,132 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </table>
  </sect1>

 

index 018b5919cf6..acf090b1ccf 100644
2.51.1

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 2 Nov 2025 21:40:46 +0100

Hí

I rewrote the transformLetStmt routine. Now, some checks and
transformations are shared with VariableFence transformation and with
PLAssignStmt transformation (after
https://github.com/postgres/postgres/commit/b0fb2c6aa5a485e28210e13ae5536c1231b1261f
)

Although I removed redundant code, the patch 0011 is longer (4KB/58KB),
because I needed to modify another lines. But now, the code is much more
consistent (and more robust).

Regards

Pavel
From b90d0f6d90607bc4ba42e7ecccbfcc86ac34c686 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 14:06:06 +0100
Subject: [PATCH 15/15] plpgsql tests

 

From 1c940fc32c5e5cbeda1584cb58c6e70329ee3cc7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 2 Jun 2025 22:33:25 +0200
Subject: [PATCH 14/15] memory cleaning after DROP VARIABLE

 
 /*
- * Drop variable by OID

+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

From 14d9026ebecba6e144b634d74e00b3e540e4f828 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 2 Jun 2025 20:41:57 +0200
Subject: [PATCH 13/15] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 				/*
 
 	return (Datum) 0;
 }
+
+/*

 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))

 

From 9a2510d15818b6b3c51c40689116cb0fae378773 Mon Sep 17 00:00:00 2001

 src/backend/parser/analyze.c                  | 136 ++++++++-
 src/backend/parser/parse_agg.c                |   7 +
 src/backend/parser/parse_expr.c               |  26 +-
 src/backend/parser/parse_func.c               |   3 +
 31 files changed, 1043 insertions(+), 32 deletions(-)

  <sect1 id="ddl-others">
 
   <para>

 
 
 /*
 
 	return variable;
 }
+
+/*

 
+	/*

+	/*

+		/*

 /*
 
 			/*
 
index c3897aafd21..4038876497d 100644
@@ -52,15 +52,18 @@

-/* Passthrough data for transformPLAssignStmtTarget */
+/* Passthrough data for transformAssignTarget */
 typedef struct SelectStmtPassthrough
 {
-	PLAssignStmt *stmt;			/* the assignment statement */
+	Node	   *stmt;			/* the assignment statement */
 	Node	   *target;			/* node representing the target variable */
+	char	   *target_name;	/* the name used by err */
 	List	   *indirection;	/* indirection yet to be applied to target */
+	CoercionContext ccontext; 	/* context indicators to control coercions */
 } SelectStmtPassthrough;
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -84,7 +87,7 @@ static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
 static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
 static Query *transformPLAssignStmt(ParseState *pstate,
 									PLAssignStmt *stmt);
-static List *transformPLAssignStmtTarget(ParseState *pstate, List *tlist,
+static List *transformAssignTarget(ParseState *pstate, List *tlist,
 										 SelectStmtPassthrough *passthru);
 static Query *transformDeclareCursorStmt(ParseState *pstate,
 										 DeclareCursorStmt *stmt);
@@ -94,6 +97,8 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 										 CreateTableAsStmt *stmt);
 static Query *transformCallStmt(ParseState *pstate,
 								CallStmt *stmt);
+static Query *transformLetStmt(ParseState *pstate,
+							   LetStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 								   LockingClause *lc, bool pushedDown);
 #ifdef DEBUG_NODE_TESTS_ENABLED
@@ -341,6 +346,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
 			case T_UpdateStmt:
 			case T_DeleteStmt:
 			case T_MergeStmt:
+			case T_LetStmt:
 				(void) test_raw_expression_coverage(parseTree, NULL);
 				break;
 			default:
@@ -420,6 +426,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
 									   (CallStmt *) parseTree);
 			break;
 
+		case T_LetStmt:
 
 			/*
@@ -481,6 +492,7 @@ stmt_requires_parse_analysis(RawStmt *parseTree)
 		case T_ExplainStmt:
 		case T_CreateTableAsStmt:
 		case T_CallStmt:
+		case T_LetStmt:
 			result = true;
 			break;
 
@@ -546,6 +558,7 @@ query_requires_rewrite_plan(Query *query)
 			case T_ExplainStmt:
 			case T_CreateTableAsStmt:
 			case T_CallStmt:
+			case T_LetStmt:
 				result = true;
 				break;
 			default:
@@ -1389,7 +1402,7 @@ count_rowexpr_columns(ParseState *pstate, Node *expr)
  *
  * This function is also used to transform the source expression of a
  * PLAssignStmt.  In that usage, passthru is non-NULL and we need to
- * call transformPLAssignStmtTarget after the initial transformation of the
+ * call transformAssignTarget after the initial transformation of the
  * SELECT's targetlist.  (We could generalize this into an arbitrary callback
  * function, but for now that would just be more notation with no benefit.)
  * All the rest is the same as a regular SelectStmt.
@@ -1442,8 +1455,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt,
 	 * Otherwise, mark column origins (which are useless in a PLAssignStmt).
 	 */
 	if (passthru)
-		qry->targetList = transformPLAssignStmtTarget(pstate, qry->targetList,
-													  passthru);
+		qry->targetList = transformAssignTarget(pstate, qry->targetList,
+												passthru);
 	else
 		markTargetListOrigins(pstate, qry->targetList);
 
@@ -2841,9 +2854,11 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 						   EXPR_KIND_UPDATE_TARGET);
 
 	/* Set up passthrough data for transformPLAssignStmtTarget */
-	passthru.stmt = stmt;
+	passthru.stmt = (Node *) stmt;
 	passthru.target = target;
+	passthru.target_name = stmt->name;
 	passthru.indirection = indirection;
+	passthru.ccontext = COERCION_PLPGSQL;
 
 	/*
 	 * To avoid duplicating a lot of code, we use transformSelectStmt to do
@@ -2866,18 +2881,21 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 
 /*
  * Callback function to adjust a SELECT's tlist to make the output suitable
- * for assignment to a PLAssignStmt's target variable.
+ * for assignment to a PLAssignStmt's target variable pr LET's target
+ * session variable.
  *
  * Note: we actually modify the tle->expr in-place, but the function's API
  * is set up to not presume that.
  */
 static List *
-transformPLAssignStmtTarget(ParseState *pstate, List *tlist,
-							SelectStmtPassthrough *passthru)
+transformAssignTarget(ParseState *pstate, List *tlist,
+					  SelectStmtPassthrough *passthru)
 {
-	PLAssignStmt *stmt = passthru->stmt;
+	Node	   *stmt = passthru->stmt;
 	Node	   *target = passthru->target;
+	char	   *target_name = passthru->target_name;
 	List	   *indirection = passthru->indirection;
+	CoercionContext ccontext = passthru->ccontext;
 	Oid			targettype;
 	int32		targettypmod;
 	Oid			targetcollation;
@@ -2912,7 +2930,7 @@ transformPLAssignStmtTarget(ParseState *pstate, List *tlist,
 		tle->expr = (Expr *)
 			transformAssignmentIndirection(pstate,
 										   target,
-										   stmt->name,
+										   target_name,
 										   false,
 										   targettype,
 										   targettypmod,
@@ -2920,10 +2938,10 @@ transformPLAssignStmtTarget(ParseState *pstate, List *tlist,
 										   indirection,
 										   list_head(indirection),
 										   (Node *) tle->expr,
-										   COERCION_PLPGSQL,
+										   ccontext,
 										   exprLocation(target));
 	}
-	else if (targettype != type_id &&
+	else if (IsA(stmt, PLAssignStmt) && targettype != type_id &&
 			 (targettype == RECORDOID || ISCOMPLEX(targettype)) &&
 			 (type_id == RECORDOID || ISCOMPLEX(type_id)))
 	{
@@ -2946,7 +2964,7 @@ transformPLAssignStmtTarget(ParseState *pstate, List *tlist,
 			coerce_to_target_type(pstate,
 								  orig_expr, type_id,
 								  targettype, targettypmod,
-								  COERCION_PLPGSQL,
+								  ccontext,
 								  COERCE_IMPLICIT_CAST,
 								  -1);
 		/* With COERCION_PLPGSQL, this error is probably unreachable */
@@ -2955,7 +2973,7 @@ transformPLAssignStmtTarget(ParseState *pstate, List *tlist,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("variable \"%s\" is of type %s"
 							" but expression is of type %s",
-							stmt->name,
+							target_name,
 							format_type_be(targettype),
 							format_type_be(type_id)),
 					 errhint("You will need to rewrite or cast the expression."),
@@ -3319,6 +3337,92 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 	return result;
 }
 
+/*
+ * transformLetStmt -
+ *	  transform an Let Statement
+ */
+static Query *
+transformLetStmt(ParseState *pstate, LetStmt *stmt)
+{
+	Query	   *qry;
+	Query	   *result;
+	Node	   *target;
+	List	   *names = NULL;
+	int			nnames;
+	List	   *indirection;
+	VariableFence *vf;
+	SelectStmtPassthrough passthru;
+	Param	   *paramvar;
+
+	/* gram allows only SELECT */
+	Assert(IsA(stmt->query, SelectStmt));
+
+	names = NamesFromList(stmt->target);
+	nnames = list_length(names);
+
+	/* Use implicit VariableFence for forcing session variables */
+	vf = makeNode(VariableFence);
+	vf->varname = names;
+	vf->location = stmt->location;
+
+	target = transformExpr(pstate, (Node *) vf, EXPR_KIND_LET_TARGET);
+
+	/*
+	 * The FieldStore is returned when field name is specified.
+	 * In this case decrease nnames number (number of dotted names),
+	 * and get target from FieldSelect node. Field name will be part
+	 * of indirection.
+	 */
+	if (IsA(target, FieldStore))
+	{
+		nnames -= 1;
+		target = (Node *)((FieldStore *) target)->arg;
+	}
+
+	paramvar = castNode(Param, target);
+
+	Assert(paramvar->paramkind == PARAM_VARIABLE);
+
+	if (list_length(stmt->target) > nnames)
+		indirection = list_copy_tail(stmt->target, nnames);
+	else
+		indirection = NIL;
+
+	/*
+	 * The parameter used as basenode has to have special mark,
+	 * because requires special access when we do SELECT access check.
+	 */
+	if (indirection)
+	{
+		paramvar->parambasenode = true;
+		pstate->p_hasSessionVariables = true;
+	}
+
+	/* Set up passthrough data for transformAssignTarget */
+	passthru.stmt = (Node *) stmt;
+	passthru.target = (Node *) paramvar;
+	passthru.target_name = get_session_variable_name(paramvar->paramvarid);
+	passthru.indirection = indirection;
+	passthru.ccontext = COERCION_ASSIGNMENT;
+
+	/* we need to postpone conversion of "unknown" to text */
+	pstate->p_resolve_unknowns = false;
+
+	qry = transformSelectStmt(pstate, (SelectStmt *) stmt->query, &passthru);
+
+	qry->resultVariable = paramvar->paramvarid;
+	qry->canSetTag = true;
+
+	stmt->query = (Node *) qry;
+
+	/* represent the command as a utility Query */
+	result = makeNode(Query);
+	result->commandType = CMD_UTILITY;
+	result->utilityStmt = (Node *) stmt;
+
+	return result;
+}
+
 /*
  * Produce a string representation of a LockClauseStrength value.
  * This should only be applied to valid values (not LCS_NONE).
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 55170c9b7ca..de6a82bb466 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -302,7 +302,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropTransformStmt
 		DropUserMappingStmt ExplainStmt FetchStmt
 		GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
-		ListenStmt LoadStmt LockStmt MergeStmt NotifyStmt ExplainableStmt PreparableStmt
+		LetStmt ListenStmt LoadStmt LockStmt MergeStmt NotifyStmt ExplainableStmt PreparableStmt
 		CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
 		RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
 		RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@@ -748,7 +748,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEEP KEY KEYS

 
index 3254c83cc6c..ddf7905654c 100644
@@ -580,6 +580,10 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			errkind = true;
 			break;
 
@@ -996,6 +1000,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 0265b8efc4d..d13fa6dde6f 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -593,6 +593,9 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_PARTITION_BOUND:
 			err = _("cannot use column reference in partition bound expression");
 			break;
+		case EXPR_KIND_LET_TARGET:
+			err = _("cannot use column reference as target of LET command");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -963,6 +966,7 @@ expr_kind_allows_session_variables(ParseExprKind p_expr_kind)
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
 		case EXPR_KIND_VALUES_SINGLE:
+		case EXPR_KIND_LET_TARGET:
 			result = true;
 			break;
 
@@ -1998,6 +2002,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_GENERATED_COLUMN:
 			err = _("cannot use subquery in column generation expression");
 			break;
+		case EXPR_KIND_LET_TARGET:
+			err = _("cannot use subquery as a target of LET command");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3261,7 +3268,8 @@ make_nulltest_from_distinct(ParseState *pstate, A_Expr *distincta, Node *arg)
 }
 
 /*
- * Generate param variable for reference to session variable
+ * Generate param variable for reference to session variable. It can be
+ * wrapped by FieldSelect (Read) or FieldStore (Write) nodes.
  */
 static Node *
 makeParamSessionVariable(ParseState *pstate,
@@ -3285,6 +3293,20 @@ makeParamSessionVariable(ParseState *pstate,
 		TupleDesc	tupdesc;
 		int			i;
 
+		if (pstate->p_expr_kind == EXPR_KIND_LET_TARGET)
+		{
+			FieldStore *fstore = makeNode(FieldStore);
+
+			/*
+			 * make aux FieldStore wrapper - it is used just for signalization,
+			 * so session variable has an attribute. All necessary transformations
+			 * and checks will be done in transformAssignmentIndirection.
+			 */
+			fstore->arg = (Expr *) param;
+
+			return (Node *) fstore;
+		}
+
 		tupdesc = lookup_rowtype_tupdesc_noerror(typid, typmod, true);
 		if (!tupdesc)
 			ereport(ERROR,
@@ -3426,6 +3448,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 778d69c6f3c..13616c9b3c2 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2783,6 +2783,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_LET_TARGET:
+			errkind = true;
+			break;
 
 			/*
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 /*
 
+/* LET */

 

 

 
+	/*
index 84e886940d8..026743b7337 100644
@@ -82,6 +82,7 @@ typedef enum ParseExprKind
+	EXPR_KIND_LET_TARGET,		/* only session variables */
 } ParseExprKind;

 DROP TABLE svartest_dml;
+

From 9d3af41a6fb522eaf032816f80399e5cc24e146c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 12/15] function pg_get_session_variables_memory for cleaning
 tests

 
 	PopActiveSnapshot();
 }
+
+/*

From 5fab6fdd18ed5a0bbf2d152e542624fc355eb8b0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 10/15] svariableReceiver

 

 
 
 /* ----------------

From 98125c0939a67495923e2c86fd42c1d909716f15 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 07:32:01 +0200
Subject: [PATCH 09/15] fill an auxiliary buffer with values of session
 variables used in query

 
+	/*
 
 	/*
 
 /*
 
 	/*
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 
 # ----------

From 09636a60610da1b55384789dac62c699e1ce9cf7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 30 May 2025 09:31:06 +0200
Subject: [PATCH 08/15] collect session variables used in plan and assign
 paramid

* parallel execution

 

 	/*

+	/*

+/*
 
 /*

+/*

 
 				/*

 
 /*
 
 /*
 
 	return false;
 }

 	/*

From e69c6d69fad8ca068b00af704c66294074af86a4 Mon Sep 17 00:00:00 2001

 
 
 /*
  * Creates a new variable

 #endif

From 5c52340d6b952195b1288502430a21cc8630e8c6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 30 May 2025 07:27:27 +0200
Subject: [PATCH 06/15] session variable fences parsing

 
  <sect1 id="ddl-others">
 
+/* -----
 
+	/*

 

 

 

 

 

+variable_fence:

 /*
  * transformExpr -
 
+		case T_VariableFence:
 
+/*
 
+/*

 

+/*
 
 	return varnamespace;
 }
+
+/*

 
 
+/*
 
 typedef struct Param

 

 	/*

From 520c8f6901155d34fc9be91728128dc0ad66330a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 22:54:09 +0200
Subject: [PATCH 05/15] support of session variables for pg_dump

 

 
 	int			useDB;
 
+/*
+ * getVariables
 
+/*
 
 #endif							/* PG_DUMP_H */

 

From 060391224b6de397a964ff75f79b8d316c68b9e7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Aug 2025 06:42:02 +0200
Subject: [PATCH 04/15] support of session variables for psql

 * \dV[+] command

 

 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

+	/* DROP VARIABLE */
 
 	/*
 
 /* PREPARE xx AS */
 
 /*

+\dV "no.such.variable"
+            List of variables

 

 

 -- check \drg and \du

From e7cdf812fa068352fe04d29c9b0f4f47cd57ce52 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Aug 2025 06:37:28 +0200
Subject: [PATCH 03/15] GRANT, REVOKE variable

 
 
    <para>
 
    <para>
 
+      <row>

 
 
   <para>

 
 
    <para>

 
 	return objects;
 
 		default:

+		case OBJECT_VARIABLE:
 
 	/*
 
 				if (objname)

 

 

 
 
+/*
 
 /*

 

 # ----------

From 1ada634eac0eadb7b199c6c73b3bacde723c0f72 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 11:26:17 +0200
Subject: [PATCH 02/15] CREATE, DROP, ALTER VARIABLE

 

 

  <refsect1>

 						/*

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
 
 /*

 
+/*

 

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
 
 			/*

 

 
 
 /*
 
 opt_column: COLUMN

 
 	/*

 	return result;
 }

 
 
 	return subname;
 }
+

 
 
+

 # ----------

From a63fc6cac65a321688a369c32f84f6ca8a9d5c9e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 08:30:25 +0200
Subject: [PATCH 01/15] introduce new class (catalog) pg_variable

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 10 Nov 2025 07:40:30 +0100

Hi

fresh rebase

Regards

Pavel
From 30b5945ab434b281c65f73320b31fe05d650fba9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 2 Jun 2025 22:33:25 +0200
Subject: [PATCH 14/15] memory cleaning after DROP VARIABLE

 
 /*
- * Drop variable by OID

+	/*

 
+/*

 
 
 	if (found)
 	{
+		/*

 

From 0f2196c4fc10a1436f32164415896fae4bfa2889 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email-masked]>
Date: Wed, 13 Nov 2024 14:06:06 +0100
Subject: [PATCH 15/15] plpgsql tests

 

From dd3d2a8a8ebac2ffc82dc408a2407e67b978d596 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 2 Jun 2025 20:41:57 +0200
Subject: [PATCH 13/15] DISCARD VARIABLES

 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
-	Assert(sessionvars);
+	/*
 
 	/*
 
 				/*
 
 	return (Datum) 0;
 }
+
+/*
index 738d1391684..851c307704c 100644
@@ -2147,7 +2147,13 @@ DiscardStmt:

diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 0ea7a166977..aecb357f027 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -2967,6 +2967,9 @@ CreateCommandTag(Node *parsetree)
 				case DISCARD_SEQUENCES:
 					tag = CMDTAG_DISCARD_SEQUENCES;
 					break;
+				case DISCARD_VARIABLES:
+					tag = CMDTAG_DISCARD_VARIABLES;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index fe020a9b381..a4597abad05 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -4241,7 +4241,7 @@ match_previous_words(int pattern_id,
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
index 0fcce621c34..112440fa8b0 100644
 
index be14059cc79..bc2a81cbf2f 100644

From 31f6143a8d66ad2339257b306f36958263f43b85 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Jan 2024 20:01:56 +0100
Subject: [PATCH 12/15] function pg_get_session_variables_memory for cleaning
 tests

 
 	PopActiveSnapshot();
 }
+
+/*
index 00aefeda5e9..4c62ac61104 100644

From 4c4c9377da10d7df0b33d13958a064b23ce0ad91 Mon Sep 17 00:00:00 2001

index 910989a7aae..6cba31aaefc 100644
@@ -5695,10 +5695,39 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
  <sect1 id="ddl-others">
index 61db22a4c25..e392dc8d084 100644
 
   <para>

 
index 0fe33a7efcb..5b3be89f732 100644
 
 /*
 
 	return variable;
 }
+
+/*

 
+	/*

+	/*

+		/*

 /*
 
 			/*

 
 
+		case T_LetStmt:
 
 			/*

 
 
 	/*
 
 /*
 
+/*
+ * transformLetStmt -
index f5c2f151752..738d1391684 100644
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
-	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+	LEADING LEAKPROOF LEAST LET LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED LSN_P
 
@@ -12983,6 +12984,38 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 
@@ -18099,6 +18132,7 @@ unreserved_keyword:
@@ -18716,6 +18750,7 @@ bare_label_keyword:

 			/*
index 8b32f74de23..ff16a3bc3cc 100644
 
 			/*

 			/*
 
 /*

 			/*
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 760d0faaad0..0ea7a166977 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -236,6 +236,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 				 * Commands inside the DO block or the called procedure might
@@ -1065,6 +1066,11 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			}
 			break;
 
+		case T_LetStmt:
@@ -2212,6 +2218,10 @@ UtilityContainsQuery(Node *parsetree)
 
+		case T_LetStmt:
+			qry = castNode(Query, ((LetStmt *) parsetree)->query);
+			return qry;
+
 		default:
 			return NULL;
 	}
@@ -2410,6 +2420,10 @@ CreateCommandTag(Node *parsetree)
 			tag = CMDTAG_SELECT;
 			break;
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -3299,6 +3313,7 @@ GetCommandLogLevel(Node *parsetree)
 			break;
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 /*
index 094e65db849..fe020a9b381 100644
@@ -1272,8 +1272,8 @@ static const char *const sql_commands[] = {
@@ -4820,6 +4820,14 @@ match_previous_words(int pattern_id,
 
+/* LET */

 
 
index a4a9e281463..0fcce621c34 100644

 
 
index 0aa52cbc4c5..cf863c43d8c 100644
 
+	/*
index d1ad6861805..badd1618af8 100644

index cbb09fcf511..be14059cc79 100644
 
 DROP TABLE svartest_dml;
+
index c022959ecca..d3a9433b432 100644
@@ -1549,6 +1549,7 @@ LargeObjectDesc

From b4565c5ed413f0a11b74a23f417c16b83e0e7490 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 10/15] svariableReceiver

 

 
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 113e79c33fa..c022959ecca 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2667,6 +2667,7 @@ STRLEN
 SV
 SVariableData
 SVariable
+SVariableState
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.51.1

From c0445dc1e749a3b58e86d09b0856586295518837 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 07:32:01 +0200
Subject: [PATCH 09/15] fill an auxiliary buffer with values of session
 variables used in query

 
+	/*
 
 	/*
 
 /*
 
 	/*
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 
 # ----------
index 315c76ac03f..113e79c33fa 100644
@@ -2725,6 +2725,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
-- 
2.51.1

From fd2ce4187816f9ac19fe405078e010db812044cc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 30 May 2025 09:31:06 +0200
Subject: [PATCH 08/15] collect session variables used in plan and assign
 paramid

* parallel execution

 

 	/*

+	/*

+/*
 
 /*

+/*

 
 				/*

 
 /*
 
 /*
 
 	return false;
 }

 	/*
index c4393a94321..0aa52cbc4c5 100644
 
index 00addf15992..fb81ceb375f 100644

From 89d5ccd1110dfb961d1031c4b969c2a112aacb27 Mon Sep 17 00:00:00 2001

index 8f697bb2266..b29e3dde1ac 100644
@@ -1732,8 +1732,9 @@

 /*
  * Creates a new variable

 #endif
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 7f427231602..315c76ac03f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2665,6 +2665,8 @@ SSL_CTX
 STARTUPINFO
 STRLEN
 SV
+SVariableData
+SVariable
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.51.1

From 278b583095202f44211d60a71c74d101305dcad7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 22:54:09 +0200
Subject: [PATCH 05/15] support of session variables for pg_dump

 
 
index 2d22723aa91..0bf5453ad7a 100644

 	int			useDB;
index a00918bacb4..702ac7fa1c1 100644
 
+/*
+ * getVariables
@@ -16333,6 +16519,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -20151,6 +20340,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
+/*
 
 #endif							/* PG_DUMP_H */

 
 
index 78cc8372d33..00291b4f38e 100644
@@ -3221,6 +3221,7 @@ VarString
 VarStringSortSupport
 Variable
 VariableAssignHook
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.51.1

From a1b07a3d5f3615fbb0906cceea6a3b3fc3aea8ae Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 30 May 2025 07:27:27 +0200
Subject: [PATCH 06/15] session variable fences parsing

index 98d7c2120b1..910989a7aae 100644
@@ -5688,6 +5688,17 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
  <sect1 id="ddl-others">
 
+/* -----
 
+	/*

 

 

 

 

 
 
index 1510551e03c..f5c2f151752 100644
@@ -532,7 +532,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -15770,6 +15770,19 @@ c_expr:		columnref								{ $$ = $1; }
@@ -17172,6 +17185,17 @@ case_arg:	a_expr									{ $$ = $1; }
 
+variable_fence:
index 32d6ae918ca..8b32f74de23 100644

 /*
  * transformExpr -
 
+		case T_VariableFence:
 
+/*
 
+/*

 
index 556ab057e5a..5f3c9904af8 100644
@@ -8816,6 +8818,14 @@ get_parameter(Param *param, deparse_context *context)
 		}
 	}
 
@@ -13601,6 +13611,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*
 
 	return varnamespace;
 }
+
+/*

index e2b452e31c9..a4a9e281463 100644

+/*
 
 typedef struct Param

 

 	/*
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 00291b4f38e..7f427231602 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3213,6 +3213,7 @@ ValidatorValidateCB
 ValuesScan
 ValuesScanState
 Var
+VariableFence
 VarBit
 VarChar
 VarParamState
-- 
2.51.1

From 1d89fd39dff6d968d3118ebb0075944d1ca49652 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Aug 2025 06:42:02 +0200
Subject: [PATCH 04/15] support of session variables for psql

 * \dV[+] command

index c00506c44cb..7f97cbbd3b0 100644
index f56c70263e0..0e9c2ead73c 100644

index 4a2976dddf0..5d10646771a 100644

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index ec0b49b957b..97baf95d2eb 100644
index 316a2dafbf1..094e65db849 100644
@@ -998,6 +998,13 @@ static const SchemaQuery Query_for_trigger_of_table = {

 /*
@@ -1362,6 +1369,7 @@ static const pgsql_thing_t words_after_create[] = {
@@ -1929,7 +1937,7 @@ psql_completion(const char *text, int start, int end)
@@ -2643,6 +2651,9 @@ match_previous_words(int pattern_id,
@@ -3249,7 +3260,7 @@ match_previous_words(int pattern_id,
@@ -4074,6 +4085,13 @@ match_previous_words(int pattern_id,
 
@@ -4351,6 +4369,12 @@ match_previous_words(int pattern_id,
 
+	/* DROP VARIABLE */
@@ -4552,7 +4576,9 @@ match_previous_words(int pattern_id,
@@ -4560,6 +4586,7 @@ match_previous_words(int pattern_id,
@@ -4574,7 +4601,8 @@ match_previous_words(int pattern_id,
@@ -4582,7 +4610,8 @@ match_previous_words(int pattern_id,
 
 	/*
 	 * Complete "GRANT/REVOKE * ON DATABASE/DOMAIN/..." with a list of
@@ -4624,6 +4653,8 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		else if (TailMatches("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
+		else if (TailMatches("VARIABLE"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
 		else if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH("TO");
 		else
@@ -4950,7 +4981,7 @@ match_previous_words(int pattern_id,
 
 /* PREPARE xx AS */
 
 /*
@@ -5439,6 +5470,8 @@ match_previous_words(int pattern_id,
index dccccbde484..00aefeda5e9 100644
 
index c8f3932edf0..db2141bb606 100644
@@ -6057,6 +6057,30 @@ COMMIT;
@@ -6278,6 +6302,12 @@ cross-database references are not implemented: nonesuch.public.func_deps_stat
@@ -6512,6 +6542,12 @@ List of schemas
 
+\dV "no.such.variable"
+            List of variables
@@ -6681,6 +6717,12 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 
@@ -6814,6 +6856,12 @@ List of text search templates
 
@@ -6861,6 +6909,8 @@ cross-database references are not implemented: "no.such.database"."no.such.schem
index dcdbd4fc020..61ca2bb60ee 100644
@@ -1661,6 +1661,19 @@ COMMIT;
 
@@ -1772,6 +1785,9 @@ DROP FUNCTION psql_error;
 
@@ -1813,6 +1829,8 @@ DROP FUNCTION psql_error;
 
@@ -1853,6 +1871,7 @@ DROP FUNCTION psql_error;
 
@@ -1877,6 +1896,7 @@ DROP FUNCTION psql_error;
 
@@ -1902,6 +1922,7 @@ DROP FUNCTION psql_error;
 
 -- check \drg and \du

From 0ee964caf4d0ca47196b1759ee65a8f30447bfe2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Aug 2025 06:37:28 +0200
Subject: [PATCH 03/15] GRANT, REVOKE variable

 
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 76c91ea461d..98d7c2120b1 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2317,6 +2317,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
        For sequences, this privilege also allows use of the
        <function>currval</function> function.
        For large objects, this privilege allows the object to be read.
+       For session variables, this  privilege allows the object to be read.
       </para>
      </listitem>
     </varlistentry>
@@ -2352,6 +2353,8 @@ REVOKE ALL ON accounts FROM PUBLIC;
        <function>setval</function> functions.
        For large objects, this privilege allows writing or truncating the
        object.
+       For session variables, this privilege allows to set a value to the
+       object.
       </para>
      </listitem>
     </varlistentry>
@@ -2596,7 +2599,8 @@ REVOKE ALL ON accounts FROM PUBLIC;
        <literal>LARGE OBJECT</literal>,
        <literal>SEQUENCE</literal>,
        <literal>TABLE</literal> (and table-like objects),
-       table column
+       table column,
+       <literal>SESSION VARIABLE</literal>
       </entry>
      </row>
      <row>
@@ -2611,7 +2615,8 @@ REVOKE ALL ON accounts FROM PUBLIC;
        <literal>LARGE OBJECT</literal>,
        <literal>SEQUENCE</literal>,
        <literal>TABLE</literal>,
-       table column
+       table column,
+       <literal>SESSION VARIABLE</literal>
       </entry>
      </row>
      <row>
@@ -2798,6 +2803,12 @@ REVOKE ALL ON accounts FROM PUBLIC;
       <entry><literal>U</literal></entry>
       <entry><literal>\dT+</literal></entry>
      </row>
+     <row>
+      <entry><literal>SESSION VARIABLE</literal></entry>
+      <entry><literal>rw</literal></entry>
+      <entry><literal>none</literal></entry>
+      <entry><literal>\dV+</literal></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -5667,6 +5678,10 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    <para>
 
    <para>
index d4508114a48..c00506c44cb 100644
 
+      <row>

 
 
   <para>
index 043f5d5a40a..b76be52ea7e 100644

 
 
    <para>

 
 	return objects;
 
 		default:

+		case OBJECT_VARIABLE:
 
 	/*
 
 				if (objname)

 

index 09d16c1bd05..1510551e03c 100644

 	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -8087,6 +8087,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -8132,6 +8140,14 @@ privilege_target:

@@ -8330,6 +8346,7 @@ defacl_privilege_target:

@@ -18232,6 +18249,7 @@ unreserved_keyword:
 			| VALIDATOR
 			| VALUE_P
 			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18891,6 +18909,7 @@ bare_label_keyword:
 			| VALUES
 			| VARCHAR
 			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P

+/*
 
 /*

index 5cf9e12fcb9..dccccbde484 100644
 
index 8b1f2bd3b68..d1ad6861805 100644
@@ -490,6 +490,7 @@ PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD, BARE_LABEL)

 # ----------

From f67b10d5d5026fb7ecba3a32a33c56f5f802d80e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 11:26:17 +0200
Subject: [PATCH 02/15] CREATE, DROP, ALTER VARIABLE

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 948b9327f24..76c91ea461d 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5654,6 +5654,27 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>

diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index a76cf5c383f..8f697bb2266 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -1726,6 +1726,21 @@
    </glossdef>
   </glossentry>
 
index b561b4cc580..de10c356fe1 100644
 
index e167406c744..61db22a4c25 100644
index 8d81244910b..5336bf4eae6 100644

  <refsect1>
index 2cf02c37b17..0fe33a7efcb 100644

 						/*

+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
 
 /*

 
+/*

 
index f99acfd2b4b..29fe872dc16 100644

 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:
index 9f640ad4810..beecf3c8adf 100644
 
 			/*
index 3aac459e483..123074f59df 100644

index 57fe0186547..09d16c1bd05 100644

 	WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -1056,6 +1057,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1634,6 +1636,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;

@@ -5336,6 +5339,34 @@ create_extension_opt_item:
 				}
 		;
 
@@ -7147,6 +7178,7 @@ object_type_any_name:
 
 /*
@@ -10082,6 +10114,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10443,6 +10493,24 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10724,6 +10792,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -18155,6 +18231,7 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18813,6 +18890,7 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P

 	/*

 	return result;
 }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 082967c0a86..760d0faaad0 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -48,6 +48,7 @@
 #include "commands/schemacmds.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
+#include "commands/session_variable.h"
 #include "commands/subscriptioncmds.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -183,6 +184,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_CreateRangeStmt:
 		case T_CreateRoleStmt:
 		case T_CreateSchemaStmt:
+		case T_CreateSessionVarStmt:
 		case T_CreateSeqStmt:
 		case T_CreateStatsStmt:
 		case T_CreateStmt:
@@ -1388,6 +1390,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				}
 				break;
 
+			case T_CreateSessionVarStmt:
+				address = CreateVariable(pstate, (CreateSessionVarStmt *) parsetree);
+				break;
+
 				/*
 				 * ************* object creation / destruction **************
 				 */
@@ -2347,6 +2353,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_STATISTIC_EXT:
 			tag = CMDTAG_ALTER_STATISTICS;
 			break;
+		case OBJECT_VARIABLE:
+			tag = CMDTAG_ALTER_VARIABLE;
+			break;
 		default:
 			tag = CMDTAG_UNKNOWN;
 			break;
@@ -2655,6 +2664,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_STATISTIC_EXT:
 					tag = CMDTAG_DROP_STATISTICS;
 					break;
+				case OBJECT_VARIABLE:
+					tag = CMDTAG_DROP_VARIABLE;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
@@ -3235,6 +3247,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
@@ -3773,6 +3789,10 @@ GetCommandLogLevel(Node *parsetree)
 			}
 			break;

 	return subname;
 }
+

index d14294a4ece..e2b452e31c9 100644

+
index 5d4fe27ef96..8b1f2bd3b68 100644
@@ -489,6 +489,7 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)
index c4606d65043..cbb09fcf511 100644

 # ----------
index cf5c87c03a2..78cc8372d33 100644
@@ -562,6 +562,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
-- 
2.51.1

From a7513b34828091b6608b3abf93c15efaea9a800d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 28 May 2025 08:30:25 +0200
Subject: [PATCH 01/15] introduce new class (catalog) pg_variable

 

 
 
index 432509277c9..cf5c87c03a2 100644
@@ -911,6 +911,7 @@ FormData_pg_ts_parser
@@ -970,6 +971,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
+Form_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
-- 
2.51.1

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 24 Nov 2025 20:59:48 +0100

Hi

I was asked to write the most reduced implementation of session variables.
I thought about possible reductions and there is one - catalog. We
can implement temporary session variables. In this case the catalog is not
necessary. After reduction the total size of the patchset is 134KB (doc +
code + tests). The size of the previous patchset was 390KB.

Unfortunately - without catalog some the dependency check is not available,
and then features that depends on dependencies are not available:

1. no catalog, no dependencies - only buildin types can be used (no custom
types, no domains),
2. no catalog, no dependencies - variables cannot be used in objects that
depend on some objects (views, sql functions),
3. simplified syntax - only scalar types can be used (no arrays, no
composites),
4. no catalog, no access rights, only owner check (variables can be used
just by their creator (owner)),
5. no catalog, no plan cache invalidation support (instead type check
before any usage),
6. no catalog, only temp variables are supported - schema cannot be
specified,
7. without direct access from the expression executor - variables cannot be
a parameter of CALL statement
8. session variables block parallel execution
9. no catalog, no object address - no event triggers for session variables
10. no catalog - DDL for session variables (CREATE TEMP VARIABLE, DROP
VARIABLE) are not transactional. Without catalog, implementation of
transactional DDL can be very difficult.

Although there are lot of strong limits, I think so implemented feature can
be still useful (parametrization of anonymous block and secure
session scope storage)

CREATE TEMP VARIABLE x AS int;
CREATE TEMP VARIABLE y AS int;
LET x = 100;
SELECT VARIABLE(x);
DO $$
BEGIN
  RAISE NOTICE '%', VARIABLE(x);
  LET y = VARIABLE(x) + 10;
 END $$;
SELECT VARIABLE(y);
DROP VARIABLE x;
DROP VARIABLE y;

This simple and reduced implementation doesn't block continuous development
in different directions (without compatibility breaks).

When I worked on this reduced version I found the importance of catalog
based implementation. It is almost impossible to implement variables with
life scope longer than transactions without a catalog. There is a strong
necessity of using dependency mechanisms. With catalog, the plan cache
invalidation can be used (instead typecheck). It is more robust.

Comments, notes?

Regards

Pavel
From 6da18c23b603dc3772af64343546eef69db5535f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 18:05:03 +0100
Subject: [PATCH 6/7] LET command - assign a result of expression to the 
 session variable
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Implementations of EXPLAIN LET and PREPARE LET statements are not supported
now. Postponed to next step due reducing patch size.
---
 doc/src/sgml/ddl.sgml                         |  27 ++++
 doc/src/sgml/ref/allfiles.sgml                |   1 +
 doc/src/sgml/ref/create_variable.sgml         |   3 +
 doc/src/sgml/ref/drop_variable.sgml           |   1 +
 doc/src/sgml/reference.sgml                   |   1 +
 src/backend/commands/session_variable.c       |  86 ++++++++++++
 src/backend/nodes/nodeFuncs.c                 |   8 ++
 src/backend/optimizer/plan/planner.c          |   1 +
 src/backend/parser/analyze.c                  | 103 ++++++++++++---
 src/backend/parser/gram.y                     |  38 +++++-
 src/backend/parser/parse_agg.c                |   7 +
 src/backend/parser/parse_expr.c               |   9 ++
 src/backend/parser/parse_func.c               |   3 +
 src/backend/tcop/utility.c                    |  15 +++
 src/bin/psql/tab-complete.in.c                |   9 +-
 src/include/commands/session_variable.h       |   5 +
 src/include/nodes/parsenodes.h                |  15 +++
 src/include/nodes/pathnodes.h                 |   3 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/parser/parse_node.h               |   1 +
 src/include/tcop/cmdtaglist.h                 |   1 +
 .../expected/session_variables_dml.out        | 123 ++++++++++++++++++
 .../regress/sql/session_variables_dml.sql     |  87 +++++++++++++
 src/tools/pgindent/typedefs.list              |   1 +
 24 files changed, 529 insertions(+), 20 deletions(-)

index 150b04f1d4b..92163d0a74a 100644
@@ -5683,10 +5683,37 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+    procedural languages).
+   </para>
   </sect1>
 
  <sect1 id="ddl-others">
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index a7349919658..cd3faa667f0 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -157,6 +157,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY grant              SYSTEM "grant.sgml">
 <!ENTITY importForeignSchema SYSTEM "import_foreign_schema.sgml">
 <!ENTITY insert             SYSTEM "insert.sgml">
+<!ENTITY let                SYSTEM "let.sgml">
 <!ENTITY listen             SYSTEM "listen.sgml">
 <!ENTITY load               SYSTEM "load.sgml">
 <!ENTITY lock               SYSTEM "lock.sgml">
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index 5ed74c5c173..11b1ef74212 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -107,6 +107,8 @@ CREATE { TEMP | TEMPORAL } VARIABLE <replaceable class="parameter">name</replace
    Create an date session variable <literal>var1</literal>:
 <programlisting>
 CREATE TEMPORARY VARIABLE var1 AS date;
+LET var1 = current_date;
+SELECT VARIABLE(var1);
 </programlisting>
   </para>
 
@@ -126,6 +128,7 @@ CREATE TEMPORARY VARIABLE var1 AS date;

diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml
index e8517a78200..dede42e4ffb 100644
--- a/doc/src/sgml/ref/drop_variable.sgml
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -78,6 +78,7 @@ DROP VARIABLE var1;

index c03e7692c7a..6fcd7a81321 100644
@@ -185,6 +185,7 @@
index 95d70911bcc..36490387da0 100644
@@ -19,14 +19,19 @@
 #include "utils/acl.h"
 
 /*
@@ -326,3 +331,84 @@ DropVariableByName(char *varname)
 					   HASH_REMOVE,
 					   NULL);
 }
+
+/*
+ * Assign the result of the evaluated expression to the session variable
+ */
+void
+ExecuteLetStmt(ParseState *pstate,
+			   LetStmt *stmt,
+			   ParamListInfo params,
+			   QueryEnvironment *queryEnv,
+			   QueryCompletion *qc)
+{
+	Query	   *query = castNode(Query, stmt->query);
+	List	   *rewritten;
+	DestReceiver *dest;
+	PlannedStmt *plan;
+	QueryDesc  *queryDesc;
+	char	   *varname = query->resultVariable;
+	SVariable	svar;
+
+	svar = search_variable(varname);
+
+	/* only owner can set content of variable */
+	if (svar->varowner != GetUserId() && !superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied for session variable %s",
+						varname)));
+
+	/* create a dest receiver for LET */
+	dest = CreateVariableDestReceiver(varname);
+
+	/* run the query rewriter */
+	query = copyObject(query);
+
+	rewritten = QueryRewrite(query);
+
+	Assert(list_length(rewritten) == 1);
+
+	query = linitial_node(Query, rewritten);
+	Assert(query->commandType == CMD_SELECT);
+
+	/* plan the query */
+	plan = pg_plan_query(query, pstate->p_sourcetext,
+						 CURSOR_OPT_PARALLEL_OK, params, NULL);
+
+	/*
+	 * Use a snapshot with an updated command ID to ensure this query sees the
+	 * results of any previously executed queries.  (This could only matter if
+	 * the planner executed an allegedly-stable function that changed the
+	 * database contents, but let's do it anyway to be parallel to the EXPLAIN
+	 * code path.)
+	 */
+	PushCopiedSnapshot(GetActiveSnapshot());
+	UpdateActiveSnapshotCommandId();
+
+	/* create a QueryDesc, redirecting output to our tuple receiver */
+	queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
+								GetActiveSnapshot(), InvalidSnapshot,
+								dest, params, queryEnv, 0);
+
+	/* call ExecutorStart to prepare the plan for execution */
+	ExecutorStart(queryDesc, 0);
+
+	/*
+	 * Run the plan to completion.  The result should be only one row.  To
+	 * check if there are too many result rows, we try to fetch two.
+	 */
+	ExecutorRun(queryDesc, ForwardScanDirection, 2L);
+
+	/* save the rowcount if we're given a QueryCompletion to fill */
+	if (qc)
+		SetQueryCompletion(qc, CMDTAG_LET, queryDesc->estate->es_processed);
+
+	/* and clean up */
+	ExecutorFinish(queryDesc);
+	ExecutorEnd(queryDesc);
+
+	FreeQueryDesc(queryDesc);
+
+	PopActiveSnapshot();
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c761fde6acb..d10c317a2cc 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -4374,6 +4374,14 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_LetStmt:
+			{
+				LetStmt    *stmt = (LetStmt *) node;
+
+				if (WALK(stmt->query))
+					return true;
+			}
+			break;
 		case T_PLAssignStmt:
 			{
 				PLAssignStmt *stmt = (PLAssignStmt *) node;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index cc03b0311a4..6acc63c9bce 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -375,6 +375,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->partition_directory = NULL;
 	glob->rel_notnullatts_hash = NULL;
 	glob->sessionVariables = NIL;
+	glob->resultVariable = parse->resultVariable;
 
 	/*
index c3897aafd21..d024b932d08 100644

 
+		case T_LetStmt:
 
 			/*

 
 
 	/*
 
 /*
@@ -3319,6 +3337,59 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 	return result;
 }
 
+/*
+ * transformLetStmt -
+	vf->varname = stmt->target;
+	passthru.target_name = paramvar->paramvarname;
+	passthru.indirection = NIL;
+	qry->resultVariable = paramvar->paramvarname;
index 837330a807f..327ecf88712 100644
@@ -301,7 +301,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -747,7 +747,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEEP KEY KEYS

@@ -12932,6 +12933,37 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 
+/*****************************************************************************
+ *
+ *		QUERY:
+ *				LET STATEMENT
+ *
+ *****************************************************************************/
+LetStmt:	LET ColId '=' a_expr
+				{
+					LetStmt	   *n = makeNode(LetStmt);
+					SelectStmt *select;
+					ResTarget  *res;
+
+					n->target = $2;
+
+					select = makeNode(SelectStmt);
+					res = makeNode(ResTarget);
+
+					/* create target list for implicit query */
+					res->name = NULL;
+					res->indirection = NIL;
+					res->val = (Node *) $4;
+					res->location = @4;
+
+					select->targetList = list_make1(res);
+					n->query = (Node *) select;
+
+					n->location = @2;
+					$$ = (Node *) n;
+				}
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -18037,6 +18069,7 @@ unreserved_keyword:
 			| LARGE_P
 			| LAST_P
 			| LEAKPROOF
+			| LET
 			| LEVEL
 			| LISTEN
 			| LOAD
@@ -18653,6 +18686,7 @@ bare_label_keyword:
 			| LEAKPROOF
 			| LEAST
 			| LEFT
+			| LET
 			| LEVEL
 			| LIKE
 			| LISTEN
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b8340557b34..a9bd4e68154 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -584,6 +584,10 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			errkind = true;
 			break;
 
@@ -1023,6 +1027,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 
 			/*
index 89661d38357..a84810edbd8 100644
@@ -591,6 +591,9 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
 			/*
@@ -961,6 +964,7 @@ expr_kind_allows_session_variables(ParseExprKind p_expr_kind)
 
@@ -1980,6 +1984,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3339,6 +3346,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 7dc9c0a6c80..3f1c8cb5e89 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -237,6 +237,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 				 * Commands inside the DO block or the called procedure might
@@ -1075,6 +1076,11 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			DropVariableByName(((DropSessionVarStmt *) parsetree)->name);
 			break;
 
+		case T_LetStmt:
@@ -2219,6 +2225,10 @@ UtilityContainsQuery(Node *parsetree)
 
+		case T_LetStmt:
+			qry = castNode(Query, ((LetStmt *) parsetree)->query);
+			return qry;
+
 		default:
 			return NULL;
 	}
@@ -2414,6 +2424,10 @@ CreateCommandTag(Node *parsetree)
 			tag = CMDTAG_SELECT;
 			break;
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -3304,6 +3318,7 @@ GetCommandLogLevel(Node *parsetree)
 			break;
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index d257b4fc37a..4da36144145 100644
@@ -1265,8 +1265,8 @@ static const char *const sql_commands[] = {
@@ -4798,6 +4798,11 @@ match_previous_words(int pattern_id,
 
+/* LET */
+	/* Complete LET <variable> with "=" */
+	else if (TailMatches("LET", MatchAny))
+		COMPLETE_WITH("=");
+
 /* LOCK */
 	/* Complete LOCK [TABLE] [ONLY] with a list of tables */
 	else if (Matches("LOCK"))
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 610b757899e..c4b4d9e6832 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -16,8 +16,10 @@
 #define SESSIONVARIABLE_H

 extern void CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt);
 extern void DropVariableByName(char *varname);
@@ -32,4 +34,7 @@ extern void get_session_variable_type_typmod_collid(char *varname,
 													int32 *typmod,
 													Oid *collid);
 
index 487464b14b1..3decc639d81 100644
 
+	char	   *resultVariable;
 
+/* ----------------------
+ *		Let Statement
+ * ----------------------
+ */
+typedef struct LetStmt
+{
+	NodeTag		type;
+	char	   *target;			/* target variable */
+	Node	   *query;			/* source expression */
+	ParseLoc	location;
+} LetStmt;
+
 /* ----------------------
  *		Select Statement
  *
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 97a00df8d1f..4aa0a07f44a 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -192,6 +192,9 @@ typedef struct PlannerGlobal
 
 	/* list of used session variables */
 	List	   *sessionVariables;
+
+	/* name of session variable used like target of LET command */
+	char	   *resultVariable;
 } PlannerGlobal;
 
index 8b1f2bd3b68..2181927682c 100644

diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 7e59d0a5275..fd212257167 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -185,6 +185,7 @@ PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
 PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
 PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
 PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
+PG_CMDTAG(CMDTAG_LET, "LET", false, false, false)
 PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
 PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
 PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
diff --git a/src/test/regress/expected/session_variables_dml.out b/src/test/regress/expected/session_variables_dml.out
index 01efa71c8f0..79a062f6efa 100644
--- a/src/test/regress/expected/session_variables_dml.out
+++ b/src/test/regress/expected/session_variables_dml.out
@@ -133,3 +133,126 @@ RESET min_parallel_table_scan_size;
 RESET max_parallel_workers_per_gather;
 DROP TABLE testvar_testtab;
 DROP VARIABLE temp_var02;
+CREATE TEMP VARIABLE temp_var03 AS numeric;
+-- LET stmt is not allowed inside CTE
+WITH x AS (LET  temp_var03 = 3.14) SELECT * FROM x;
+ERROR:  syntax error at or near "LET"
+LINE 1: WITH x AS (LET  temp_var03 = 3.14) SELECT * FROM x;
+                   ^
+-- LET stmt requires result with exactly one row
+LET temp_var03 = generate_series(1,1);
+SELECT VARIABLE(temp_var03);
+ temp_var03 
+------------
+          1
+(1 row)
+
+-- should fail
+LET temp_var03 = generate_series(1,2);
+ERROR:  expression returned more than one row
+LET temp_var03 = generate_series(1,0);
+ERROR:  expression returned no rows
+CREATE OR REPLACE FUNCTION testvar_sql01(numeric)
+RETURNS void AS $$
+LET temp_var03 = $1;
+$$ LANGUAGE sql;
+CREATE OR REPLACE FUNCTION testvar_sql02()
+RETURNS numeric AS $$
+SELECT VARIABLE(temp_var03);
+$$ LANGUAGE sql;
+SELECT testvar_sql01(3.14);
+ testvar_sql01 
+---------------
+ 
+(1 row)
+
+SELECT testvar_sql02(), VARIABLE(temp_var03);
+ testvar_sql02 | temp_var03 
+---------------+------------
+          3.14 |       3.14
+(1 row)
+
+CREATE OR REPLACE FUNCTION testvar_pl(varchar)
+RETURNS varchar AS $$
+BEGIN
+  LET temp_var03 = $1::numeric;
+  RETURN VARIABLE(temp_var03);
+END
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+SELECT testvar_pl('3.14');
+ testvar_pl 
+------------
+ 3.14
+(1 row)
+
+DROP VARIABLE temp_var03;
+SET plan_cache_mode to force_generic_plan;
+-- should not crash
+SELECT testvar_sql01(3.14);
+ERROR:  session variable "temp_var03" doesn't exist
+CONTEXT:  SQL function "testvar_sql01" during inlining
+SELECT testvar_sql02(), VARIABLE(temp_var03);
+ERROR:  session variable "temp_var03" doesn't exist
+SELECT testvar_pl('3.141592');
+ERROR:  session variable "temp_var03" doesn't exist
+CONTEXT:  SQL statement "LET temp_var03 = $1::numeric"
+PL/pgSQL function testvar_pl(character varying) line 3 at SQL statement
+-- can work again if we create variable
+CREATE TEMP VARIABLE temp_var03 AS numeric;
+SELECT testvar_sql01(3.14);
+ testvar_sql01 
+---------------
+ 
+(1 row)
+
+SELECT testvar_sql02(), VARIABLE(temp_var03);
+ testvar_sql02 | temp_var03 
+---------------+------------
+          3.14 |       3.14
+(1 row)
+
+SELECT testvar_pl('3.141592');
+ testvar_pl 
+------------
+ 3.141592
+(1 row)
+
+CREATE ROLE regress_session_variable_test_role_04;
+SET ROLE regress_session_variable_test_role_04;
+-- should fail
+SELECT testvar_sql01(3.14);
+ERROR:  permission denied for session variable temp_var03
+CONTEXT:  SQL function "testvar_sql01" statement 1
+-- should be ok (security definer)
+SELECT testvar_pl('3.141592');
+ testvar_pl 
+------------
+ 3.141592
+(1 row)
+
+SET ROLE TO DEFAULT;
+DROP FUNCTION testvar_sql01(numeric);
+DROP FUNCTION testvar_sql02();
+DROP FUNCTION testvar_pl(varchar);
+DROP ROLE regress_session_variable_test_role_04;
+DROP VARIABLE temp_var03;
+SET plan_cache_mode TO DEFAULT;
+-- test extended query protocol
+CREATE TEMP VARIABLE temp_var04 AS int;
+LET temp_var04 = $1 \bind 10 \g
+SELECT VARIABLE(temp_var04);
+ temp_var04 
+------------
+         10
+(1 row)
+
+LET temp_var04 = $1 \parse letps
+\bind_named letps 100 \g
+SELECT VARIABLE(temp_var04);
+ temp_var04 
+------------
+        100
+(1 row)
+
+\close_prepared letps
+DROP VARIABLE temp_var04;
diff --git a/src/test/regress/sql/session_variables_dml.sql b/src/test/regress/sql/session_variables_dml.sql
index bf56b19467b..b8408c97cad 100644
--- a/src/test/regress/sql/session_variables_dml.sql
+++ b/src/test/regress/sql/session_variables_dml.sql
@@ -118,3 +118,90 @@ RESET max_parallel_workers_per_gather;
 
 DROP TABLE testvar_testtab;
 DROP VARIABLE temp_var02;
+
+CREATE TEMP VARIABLE temp_var03 AS numeric;
+
+-- LET stmt is not allowed inside CTE
+WITH x AS (LET  temp_var03 = 3.14) SELECT * FROM x;
+
+-- LET stmt requires result with exactly one row
+LET temp_var03 = generate_series(1,1);
+SELECT VARIABLE(temp_var03);
+
+-- should fail
+LET temp_var03 = generate_series(1,2);
+LET temp_var03 = generate_series(1,0);
+
+CREATE OR REPLACE FUNCTION testvar_sql01(numeric)
+RETURNS void AS $$
+LET temp_var03 = $1;
+$$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION testvar_sql02()
+RETURNS numeric AS $$
+SELECT VARIABLE(temp_var03);
+$$ LANGUAGE sql;
+
+SELECT testvar_sql01(3.14);
+SELECT testvar_sql02(), VARIABLE(temp_var03);
+
+CREATE OR REPLACE FUNCTION testvar_pl(varchar)
+RETURNS varchar AS $$
+BEGIN
+  LET temp_var03 = $1::numeric;
+  RETURN VARIABLE(temp_var03);
+END
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+
+SELECT testvar_pl('3.14');
+
+DROP VARIABLE temp_var03;
+
+SET plan_cache_mode to force_generic_plan;
+
+-- should not crash
+SELECT testvar_sql01(3.14);
+SELECT testvar_sql02(), VARIABLE(temp_var03);
+SELECT testvar_pl('3.141592');
+
+-- can work again if we create variable
+CREATE TEMP VARIABLE temp_var03 AS numeric;
+SELECT testvar_sql01(3.14);
+SELECT testvar_sql02(), VARIABLE(temp_var03);
+SELECT testvar_pl('3.141592');
+
+CREATE ROLE regress_session_variable_test_role_04;
+
+SET ROLE regress_session_variable_test_role_04;
+
+-- should fail
+SELECT testvar_sql01(3.14);
+
+-- should be ok (security definer)
+SELECT testvar_pl('3.141592');
+
+SET ROLE TO DEFAULT;
+
+DROP FUNCTION testvar_sql01(numeric);
+DROP FUNCTION testvar_sql02();
+DROP FUNCTION testvar_pl(varchar);
+
+DROP ROLE regress_session_variable_test_role_04;
+
+DROP VARIABLE temp_var03;
+
+SET plan_cache_mode TO DEFAULT;
+
+-- test extended query protocol
+CREATE TEMP VARIABLE temp_var04 AS int;
+
+LET temp_var04 = $1 \bind 10 \g
+SELECT VARIABLE(temp_var04);
+
+LET temp_var04 = $1 \parse letps
+\bind_named letps 100 \g
+SELECT VARIABLE(temp_var04);
+
+\close_prepared letps
+
+DROP VARIABLE temp_var04;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 73415652966..c72b2e8f1d0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1548,6 +1548,7 @@ LargeObjectDesc
 Latch
 LauncherLastStartTimesEntry
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
-- 
2.52.0

From 43df0988f54392bc776a322303ff71d38e59b185 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 20:04:16 +0100
Subject: [PATCH 7/7] DISCARD TEMP

---
 doc/src/sgml/ref/discard.sgml                 |  3 ++-
 src/backend/commands/discard.c                |  3 +++
 src/backend/commands/session_variable.c       | 20 +++++++++++++++++++
 src/include/commands/session_variable.h       |  2 ++
 .../expected/session_variables_ddl.out        |  7 +++++++
 .../regress/sql/session_variables_ddl.sql     | 10 ++++++++++
 6 files changed, 44 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml
index bf44c523cac..2700f7b7cd0 100644
--- a/doc/src/sgml/ref/discard.sgml
+++ b/doc/src/sgml/ref/discard.sgml
@@ -70,7 +70,8 @@ DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }
     <term><literal>TEMPORARY</literal> or <literal>TEMP</literal></term>
     <listitem>
      <para>
-      Drops all temporary tables created in the current session.
+      Drops all temporary tables and temporary session variables created in
+      the current session.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/commands/discard.c b/src/backend/commands/discard.c
index 81339a75a52..de322c35c51 100644
--- a/src/backend/commands/discard.c
+++ b/src/backend/commands/discard.c
@@ -19,6 +19,7 @@
 #include "commands/discard.h"
 #include "commands/prepare.h"
 #include "commands/sequence.h"
+#include "commands/session_variable.h"
 #include "utils/guc.h"
 #include "utils/portal.h"
 
@@ -46,6 +47,7 @@ DiscardCommand(DiscardStmt *stmt, bool isTopLevel)
 
 		case DISCARD_TEMP:
 
 		default:
@@ -75,4 +77,5 @@ DiscardAll(bool isTopLevel)
 	ResetPlanCache();
 	ResetTempTableNamespace();
 	ResetSequenceCaches();
+	ResetSessionVariables();
 }
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 36490387da0..7837e21cf1c 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -412,3 +412,23 @@ ExecuteLetStmt(ParseState *pstate,
 
 	PopActiveSnapshot();
 }
+
+/*
+ * This is used by the DISCARD TEMP.
index c4b4d9e6832..cc1aa7ce23b 100644
@@ -37,4 +37,6 @@ extern void get_session_variable_type_typmod_collid(char *varname,
 
+extern void ResetSessionVariables(void);
+
 #endif
diff --git a/src/test/regress/expected/session_variables_ddl.out b/src/test/regress/expected/session_variables_ddl.out
index cc6f59e8be5..f12d1f1a6ae 100644
--- a/src/test/regress/expected/session_variables_ddl.out
+++ b/src/test/regress/expected/session_variables_ddl.out
@@ -41,3 +41,10 @@ DROP VARIABLE x;
 SET ROLE TO DEFAULT;
 DROP ROLE regress_session_variable_test_role_01;
 DROP ROLE regress_session_variable_test_role_02;
+CREATE TEMP VARIABLE x AS int;
+-- should fail
+CREATE TEMP VARIABLE x AS int;
+ERROR:  session variable "x" already exists
+DISCARD TEMP;
+-- should be ok
+CREATE TEMP VARIABLE x AS int;
diff --git a/src/test/regress/sql/session_variables_ddl.sql b/src/test/regress/sql/session_variables_ddl.sql
index 92e97ea2ab0..c69fd4bfa47 100644
--- a/src/test/regress/sql/session_variables_ddl.sql
+++ b/src/test/regress/sql/session_variables_ddl.sql
@@ -54,3 +54,13 @@ DROP VARIABLE x;
 SET ROLE TO DEFAULT;
 DROP ROLE regress_session_variable_test_role_01;
 DROP ROLE regress_session_variable_test_role_02;
+
+CREATE TEMP VARIABLE x AS int;
+
+-- should fail
+CREATE TEMP VARIABLE x AS int;
+
+DISCARD TEMP;
+
+-- should be ok
+CREATE TEMP VARIABLE x AS int;
-- 
2.52.0

From 72c3b667bbbb4c99eba46f05af879d770ed2e3d3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 5/7] svariableReceiver

Check correct format of result - one column, one row.
---
 src/backend/commands/session_variable.c  |  50 ++++++++
 src/backend/executor/Makefile            |   1 +
 src/backend/executor/meson.build         |   1 +
 src/backend/executor/svariableReceiver.c | 149 +++++++++++++++++++++++
 src/backend/tcop/dest.c                  |   7 ++
 src/include/commands/session_variable.h  |   3 +
 src/include/executor/svariableReceiver.h |  22 ++++
 src/include/tcop/dest.h                  |   1 +
 src/tools/pgindent/typedefs.list         |   1 +
 9 files changed, 235 insertions(+)
 create mode 100644 src/backend/executor/svariableReceiver.c
 create mode 100644 src/include/executor/svariableReceiver.h

diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 0daa46fdde3..95d70911bcc 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -171,6 +171,56 @@ GetSessionVariableWithTypecheck(char *varname,
 	return result;
 }
 
+/*
+ * Store the given value in a session variable in the cache.
+ */
+void
+SetSessionVariableWithTypecheck(char *varname,
+								Oid typid, int32 typmod,
+								Datum value, bool isnull)
+{
+	SVariable	svar;
+
+	svar = search_variable(varname);
+
+	if (svar->vartype != typid || svar->vartypmod != typmod)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("session variable %s is not of a type %s but type %s",
+						varname,
+						format_type_with_typemod(typid, typmod),
+						format_type_with_typemod(svar->vartype, svar->vartypmod))));
+
+	/* only owner can set content of variable */
+	if (svar->varowner != GetUserId() && !superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied for session variable %s",
+						varname)));
+
+	if (!svar->typbyval)
+	{
+		if (!isnull)
+		{
+			MemoryContext oldcxt;
+
+			/*
+			 * Do copy of value in session variables context. This operation
+			 * can fail, so do it before releasing the old content.
+			 */
+			oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+			value = datumCopy(value, svar->typbyval, svar->typlen);
+			MemoryContextSwitchTo(oldcxt);
+		}
+
+		if (!svar->isnull)
+			pfree(DatumGetPointer(svar->value));
+	}
+
+	svar->value = value;
+	svar->isnull = isnull;
+}
+
 /*
  * Creates a new variable - does new entry in sessionvars
  *
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 11118d0ce02..71248a34f26 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -76,6 +76,7 @@ OBJS = \
 	nodeWindowAgg.o \
 	nodeWorktablescan.o \
 	spi.o \
+	svariableReceiver.o \
 	tqueue.o \
 	tstoreReceiver.o
 
index 00000000000..b2709e9211b
+	myState->typid = attr->atttypid;
+	myState->typmod = attr->atttypmod;
+	SetSessionVariableWithTypecheck(myState->varname,
+									myState->typid, myState->typmod,
+									value, isnull);
+	pfree(((SVariableState *) self)->varname);
+CreateVariableDestReceiver(char *varname)
+	self->varname = pstrdup(varname);
index b620766c938..0c1eeeb22a6 100644

 		case DestExplainSerialize:
 			return CreateExplainSerializeDestReceiver(NULL);
+
+		case DestVariable:
+			return CreateVariableDestReceiver(NULL);
 	}
 
 	/* should never get here */
@@ -191,6 +195,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
 		case DestTransientRel:
 		case DestTupleQueue:
 		case DestExplainSerialize:
+		case DestVariable:
 			break;
 	}
 }
@@ -237,6 +242,7 @@ NullCommand(CommandDest dest)
 		case DestTransientRel:
 		case DestTupleQueue:
 		case DestExplainSerialize:
+		case DestVariable:
 			break;
 	}
 }
@@ -281,6 +287,7 @@ ReadyForQuery(CommandDest dest)
 		case DestTransientRel:
 		case DestTupleQueue:
 		case DestExplainSerialize:
+		case DestVariable:
 			break;
 	}
 }
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 3687490bcb1..610b757899e 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -23,6 +23,9 @@ extern void CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt);
 extern void DropVariableByName(char *varname);
 
 extern Datum GetSessionVariableWithTypecheck(char *varname, Oid typid, int32 typmod, bool *isnull);
+extern void SetSessionVariableWithTypecheck(char *varname,
+											Oid typid, int32 typmod,
+											Datum value, bool isnull);
 
 extern void get_session_variable_type_typmod_collid(char *varname,
 													Oid *typid,
index 00000000000..dd01c93c9e8
+extern DestReceiver *CreateVariableDestReceiver(char *varname);
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 1e89b07f07b..73415652966 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2670,6 +2670,7 @@ STRLEN
 SV
 SVariableData
 SVariable
+SVariableState
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

From ded422613e51727dddc5c65f6d37aec169705b98 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 22 Nov 2025 06:40:46 +0100
Subject: [PATCH 4/7] fill an auxiliary buffer with values of session 
 variables used in query

Using session variables blocks parallel query execution. It is not
technical problem (it just needs a serialization/deserialization of
es_session_varibles buffer), but it increases a size of patch (and then
it is postponed).
---
 src/backend/executor/execExpr.c               |  29 ++++
 src/backend/executor/execMain.c               |  49 +++++++
 src/include/nodes/execnodes.h                 |  14 ++
 .../expected/session_variables_dml.out        | 135 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   5 +
 .../regress/sql/session_variables_dml.sql     | 120 ++++++++++++++++
 src/tools/pgindent/typedefs.list              |   1 +
 7 files changed, 353 insertions(+)
 create mode 100644 src/test/regress/expected/session_variables_dml.out
 create mode 100644 src/test/regress/sql/session_variables_dml.sql

index 27c9eec697b..198be0cf143 100644
@@ -44,6 +44,7 @@
@@ -196,6 +197,54 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 
+	/*
+		foreach_node(Param, param, queryDesc->plannedstmt->sessionVariables)
+				GetSessionVariableWithTypecheck(param->paramvarname,
+												param->paramtype,
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 	/* Other working state: */
diff --git a/src/test/regress/expected/session_variables_dml.out b/src/test/regress/expected/session_variables_dml.out
new file mode 100644
index 00000000000..01efa71c8f0
--- /dev/null
+++ b/src/test/regress/expected/session_variables_dml.out
@@ -0,0 +1,135 @@
+CREATE TEMP VARIABLE temp_var01 AS int;
+-- should not be accessible without variable's fence
+-- should fail
+SELECT temp_var01;
+ERROR:  column "temp_var01" does not exist
+LINE 1: SELECT temp_var01;
+               ^
+-- should be ok
+SELECT VARIABLE(temp_var01);
+ temp_var01 
+------------
+           
+(1 row)
+
+-- should not crash
+DO $$
+BEGIN
+  RAISE NOTICE '%', VARIABLE(temp_var01);
+END;
+$$;
+NOTICE:  <NULL>
+-- variables cannot be used by persistent objects
+-- that checks dependency
+-- should fail
+CREATE TEMP VIEW tempv AS SELECT VARIABLE(temp_var01);
+ERROR:  session variable "temp_var01" cannot be referenced in a persistent object
+CREATE OR REPLACE FUNCTION testvar_sql()
+RETURNS int AS $$
+SELECT VARIABLE(temp_var01);
+$$ LANGUAGE sql;
+SELECT testvar_sql();
+ testvar_sql 
+-------------
+            
+(1 row)
+
+-- session variable cannot be used as parameter of CALL or EXECUTE
+CREATE OR REPLACE PROCEDURE testvar_proc(int)
+AS $$
+BEGIN
+  RAISE NOTICE '%', $1;
+END;
+$$ LANGUAGE plpgsql;
+-- should not crash
+CALL testvar_proc(VARIABLE(temp_var01));
+ERROR:  session variable reference is not supported here
+LINE 1: CALL testvar_proc(VARIABLE(temp_var01));
+                                   ^
+PREPARE prepstmt(int) AS SELECT $1;
+-- should not crash
+EXECUTE prepstmt(VARIABLE(temp_var01));
+ERROR:  session variable reference is not supported here
+LINE 1: EXECUTE prepstmt(VARIABLE(temp_var01));
+                                  ^
+DROP PROCEDURE testvar_proc;
+DEALLOCATE prepstmt;
+CREATE ROLE regress_session_variable_test_role_03;
+CREATE OR REPLACE FUNCTION testvar_sd()
+RETURNS void AS $$
+BEGIN
+  RAISE NOTICE '%', VARIABLE(temp_var01);
+END;
+$$ LANGUAGE plpgsql;
+-- only owner can read data
+SET ROLE TO regress_session_variable_test_role_03;
+-- should fail
+SELECT VARIABLE(temp_var01);
+ERROR:  permission denied for session variable temp_var01
+-- fx with security definer should be ok
+SELECT testvar_sd();
+ERROR:  permission denied for session variable temp_var01
+CONTEXT:  PL/pgSQL expression "VARIABLE(temp_var01)"
+PL/pgSQL function testvar_sd() line 3 at RAISE
+SET ROLE TO default;
+DROP VARIABLE temp_var01;
+-- there is not plan cache invalidation
+-- but still functions that uses dropped variables
+-- should not to crash
+SELECT testvar_sd();
+ERROR:  session variable "temp_var01" doesn't exist
+CONTEXT:  PL/pgSQL expression "VARIABLE(temp_var01)"
+PL/pgSQL function testvar_sd() line 3 at RAISE
+SELECT testvar_sql();
+ERROR:  session variable "temp_var01" doesn't exist
+CONTEXT:  SQL function "testvar_sql" during inlining
+DROP FUNCTION testvar_sql();
+DROP FUNCTION testvar_sd();
+DROP ROLE regress_session_variable_test_role_03;
+CREATE TABLE testvar_testtab(a int);
+CREATE TEMP VARIABLE temp_var02 AS int;
+INSERT INTO testvar_testtab SELECT * FROM generate_series(1,1000);
+CREATE INDEX testvar_testtab_a ON testvar_testtab(a);
+ANALYZE testvar_testtab;
+-- force index
+SET enable_seqscan TO OFF;
+-- index scan should be used
+EXPLAIN (COSTS OFF) SELECT * FROM testvar_testtab WHERE a = VARIABLE(temp_var02);
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Index Only Scan using testvar_testtab_a on testvar_testtab
+   Index Cond: (a = VARIABLES(temp_var02))
+(2 rows)
+
+DROP INDEX testvar_testtab_a;
+SET enable_seqscan TO DEFAULT;
+-- parallel execution should be blocked
+-- Encourage use of parallel plans
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers_per_gather = 2;
+-- parallel plan should be used
+EXPLAIN (COSTS OFF) SELECT * FROM testvar_testtab WHERE a = 100;
+                 QUERY PLAN                 
+--------------------------------------------
+ Gather
+   Workers Planned: 2
+   ->  Parallel Seq Scan on testvar_testtab
+         Filter: (a = 100)
+(4 rows)
+
+-- parallel plan should not be used
+EXPLAIN (COSTS OFF) SELECT * FROM testvar_testtab WHERE a = VARIABLE(temp_var02);
+              QUERY PLAN               
+---------------------------------------
+ Seq Scan on testvar_testtab
+   Filter: (a = VARIABLES(temp_var02))
+(2 rows)
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers_per_gather;
+DROP TABLE testvar_testtab;
+DROP VARIABLE temp_var02;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 371c0a6bdd9..430baf8e04b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -140,3 +140,8 @@ test: fast_default
 # run tablespace test at the end because it drops the tablespace created during
 # setup that other tests may use.
 test: tablespace
+
+# ----------
+# Another group of parallel tests (session variables related)
+# ----------
+test: session_variables_ddl session_variables_dml
diff --git a/src/test/regress/sql/session_variables_dml.sql b/src/test/regress/sql/session_variables_dml.sql
new file mode 100644
index 00000000000..bf56b19467b
--- /dev/null
+++ b/src/test/regress/sql/session_variables_dml.sql
@@ -0,0 +1,120 @@
+CREATE TEMP VARIABLE temp_var01 AS int;
+
+-- should not be accessible without variable's fence
+-- should fail
+SELECT temp_var01;
+
+-- should be ok
+SELECT VARIABLE(temp_var01);
+
+-- should not crash
+DO $$
+BEGIN
+  RAISE NOTICE '%', VARIABLE(temp_var01);
+END;
+$$;
+
+-- variables cannot be used by persistent objects
+-- that checks dependency
+-- should fail
+CREATE TEMP VIEW tempv AS SELECT VARIABLE(temp_var01);
+
+CREATE OR REPLACE FUNCTION testvar_sql()
+RETURNS int AS $$
+SELECT VARIABLE(temp_var01);
+$$ LANGUAGE sql;
+
+SELECT testvar_sql();
+
+-- session variable cannot be used as parameter of CALL or EXECUTE
+CREATE OR REPLACE PROCEDURE testvar_proc(int)
+AS $$
+BEGIN
+  RAISE NOTICE '%', $1;
+END;
+$$ LANGUAGE plpgsql;
+
+-- should not crash
+CALL testvar_proc(VARIABLE(temp_var01));
+
+PREPARE prepstmt(int) AS SELECT $1;
+
+-- should not crash
+EXECUTE prepstmt(VARIABLE(temp_var01));
+
+DROP PROCEDURE testvar_proc;
+DEALLOCATE prepstmt;
+
+CREATE ROLE regress_session_variable_test_role_03;
+
+CREATE OR REPLACE FUNCTION testvar_sd()
+RETURNS void AS $$
+BEGIN
+  RAISE NOTICE '%', VARIABLE(temp_var01);
+END;
+$$ LANGUAGE plpgsql;
+
+-- only owner can read data
+SET ROLE TO regress_session_variable_test_role_03;
+
+-- should fail
+SELECT VARIABLE(temp_var01);
+
+-- fx with security definer should be ok
+SELECT testvar_sd();
+
+SET ROLE TO default;
+
+DROP VARIABLE temp_var01;
+
+-- there is not plan cache invalidation
+-- but still functions that uses dropped variables
+-- should not to crash
+
+SELECT testvar_sd();
+SELECT testvar_sql();
+
+DROP FUNCTION testvar_sql();
+DROP FUNCTION testvar_sd();
+
+DROP ROLE regress_session_variable_test_role_03;
+
+CREATE TABLE testvar_testtab(a int);
+CREATE TEMP VARIABLE temp_var02 AS int;
+
+INSERT INTO testvar_testtab SELECT * FROM generate_series(1,1000);
+
+CREATE INDEX testvar_testtab_a ON testvar_testtab(a);
+
+ANALYZE testvar_testtab;
+
+-- force index
+SET enable_seqscan TO OFF;
+
+-- index scan should be used
+EXPLAIN (COSTS OFF) SELECT * FROM testvar_testtab WHERE a = VARIABLE(temp_var02);
+
+DROP INDEX testvar_testtab_a;
+
+SET enable_seqscan TO DEFAULT;
+
+-- parallel execution should be blocked
+-- Encourage use of parallel plans
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers_per_gather = 2;
+
+-- parallel plan should be used
+EXPLAIN (COSTS OFF) SELECT * FROM testvar_testtab WHERE a = 100;
+
+-- parallel plan should not be used
+EXPLAIN (COSTS OFF) SELECT * FROM testvar_testtab WHERE a = VARIABLE(temp_var02);
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers_per_gather;
+
+DROP TABLE testvar_testtab;
+DROP VARIABLE temp_var02;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 0ff3b10277c..1e89b07f07b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2728,6 +2728,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
-- 
2.52.0

From ceded996fcd472b06ddd591819a2f476bf42030a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 20:42:56 +0100
Subject: [PATCH 3/7] collect session variables used in plan and assign paramid

* parallel execution

Before execution of query with session variables we need to collect
used session variables. This list is used for loading variables to
executed query.

plan
---
 doc/src/sgml/parallel.sgml                |  6 ++
 src/backend/catalog/dependency.c          | 10 +++
 src/backend/commands/session_variable.c   | 39 ++++++++++
 src/backend/optimizer/plan/planner.c      | 11 +++
 src/backend/optimizer/plan/setrefs.c      | 94 ++++++++++++++++++++++-
 src/backend/optimizer/prep/prepjointree.c |  3 +
 src/backend/optimizer/util/clauses.c      | 35 ++++++++-
 src/backend/utils/fmgr/fmgr.c             | 10 ++-
 src/include/commands/session_variable.h   |  2 +
 src/include/nodes/pathnodes.h             |  5 ++
 src/include/nodes/plannodes.h             |  3 +
 src/include/optimizer/planmain.h          |  2 +
 12 files changed, 214 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/parallel.sgml b/doc/src/sgml/parallel.sgml
index 1ce9abf86f5..00c0f715eea 100644
--- a/doc/src/sgml/parallel.sgml
+++ b/doc/src/sgml/parallel.sgml
@@ -515,6 +515,12 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
         Plan nodes that reference a correlated <literal>SubPlan</literal>.
       </para>
     </listitem>
+
+    <listitem>
+      <para>
+        Usage of a session variable.
+      </para>
+    </listitem>
   </itemizedlist>
 
  <sect2 id="parallel-labeling">
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index fba5b2e0d09..62d26563d17 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1902,6 +1902,16 @@ find_expr_references_walker(Node *node,
 	{
 		Param	   *param = (Param *) node;
 
+		/*
+		* catalog less session variable variable cannot be used in persistent
+		* catalog based object.
+		*/
+		if (param->paramkind == PARAM_VARIABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("session variable \"%s\" cannot be referenced in a persistent object",
+							param->paramvarname)));
+
 		/* A parameter must depend on the parameter's datatype */
 		add_object_address(TypeRelationId, param->paramtype, 0,
 						   context->addrs);
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 861c2a55f4e..0daa46fdde3 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -132,6 +132,45 @@ get_session_variable_type_typmod_collid(char *varname,
 	*collid = svar->varcollation;
 }
 
+/*
+ * Returns a copy of the value of the session variable (in the current memory
+ * context).
+ */
+Datum
+GetSessionVariableWithTypecheck(char *varname,
+								Oid typid, int32 typmod,
+								bool *isnull)
+{
+	SVariable	svar;
+	Datum		result;
+
+	svar = search_variable(varname);
+
+	if (svar->vartype != typid || svar->vartypmod != typmod)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("session variable %s is not of a type %s but type %s",
+						varname,
+						format_type_with_typemod(typid, typmod),
+						format_type_with_typemod(svar->vartype, svar->vartypmod))));
+
+	/* only owner can get content of variable */
+	if (svar->varowner != GetUserId() && !superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied for session variable %s",
+						varname)));
+
+	if (!svar->isnull)
+		result = datumCopy(svar->value, svar->typbyval, svar->typlen);
+	else
+		result = (Datum) 0;
+
+	*isnull = svar->isnull;
+
+	return result;
+}
+
 /*
  * Creates a new variable - does new entry in sessionvars
  *
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index c4fd646b999..cc03b0311a4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -374,6 +374,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->dependsOnRole = false;
 	glob->partition_directory = NULL;
 	glob->rel_notnullatts_hash = NULL;
+	glob->sessionVariables = NIL;
 
 	/*

+	/*
index ccdc9bc264a..48de8bf4f14 100644
@@ -210,6 +210,8 @@ static List *set_returning_clause_references(PlannerInfo *root,

 /*****************************************************************************
@@ -1341,6 +1343,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 	return plan;
 }
 
+/*
+ * Search usage of session variables in subqueries
+ */
+void
+pull_up_has_session_variables(PlannerInfo *root)
+{
+	Query	   *query = root->parse;
+
+	if (query->hasSessionVariables)
+	{
+		root->hasSessionVariables = true;
+	}
+	else
+	{
+		(void) query_tree_walker(query,
+								 pull_up_has_session_variables_walker,
+								 (void *) root, 0);
+	}
+}
+
+static bool
+pull_up_has_session_variables_walker(Node *node, PlannerInfo *root)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+
+		if (query->hasSessionVariables)
+		{
+			root->hasSessionVariables = true;
+			return false;
+		}
+
+		/* recurse into subselects */
+		return query_tree_walker((Query *) node,
+								 pull_up_has_session_variables_walker,
+								 (void *) root, 0);
+	}
+	return expression_tree_walker(node, pull_up_has_session_variables_walker,
+								  (void *) root);
+}
+
 /*
  * set_indexonlyscan_references
  *		Do set_plan_references processing on an IndexOnlyScan
@@ -2141,6 +2187,10 @@ fix_expr_common(PlannerInfo *root, Node *node)
  * If it's a PARAM_MULTIEXPR, replace it with the appropriate Param from
  * root->multiexpr_params; otherwise no change is needed.
  * Just for paranoia's sake, we make a copy of the node in either case.
+ *
+ * If it's a PARAM_VARIABLE, then we collect used session variables in
+ * the list root->glob->sessionVariable.  Also, assign the parameter's
+ * "paramid" to the parameter's position in that list.
  */
 static Node *
 fix_param_node(PlannerInfo *root, Param *p)
@@ -2159,6 +2209,43 @@ fix_param_node(PlannerInfo *root, Param *p)
 			elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", p->paramid);
 		return copyObject(list_nth(params, colno - 1));
 	}
+
+	if (p->paramkind == PARAM_VARIABLE)
+	{
+		int			n = 0;
+
+		/* we will modify object */
+		p = (Param *) copyObject(p);
+
+		/*
+		 * Now, we can actualize list of session variables, and we can
+		 * complete paramid parameter.
+		 */
+		foreach_node(Param, paramvar, root->glob->sessionVariables)
+		{
+			if (strcmp(paramvar->paramvarname, p->paramvarname) == 0)
+			{
+				p->paramid = paramvar->paramid;
+
+				return (Node *) p;
+			}
+
+			n += 1;
+		}
+
+		p->paramid = n;
+
+		/*
+		 * Because session variables are catalogless, we cannot to use plan
+		 * invalidation. Then we need to check type, typmod, collid any time,
+		 * when we load values of session variables to parameter's buffer.
+		 * For this purpose it is more easy to save complete Param node.
+		 */
+		root->glob->sessionVariables = lappend(root->glob->sessionVariables, p);
+
+		return (Node *) p;
+	}
+
 	return (Node *) copyObject(p);
 }
 
@@ -2220,7 +2307,9 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
+ * of session variables in the root->glob->sessionVariables list.
@@ -2242,7 +2331,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
index 7581695647d..282bb7d5e25 100644
@@ -1649,6 +1649,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 
index 202ba8ed4bb..19ca377e340 100644
@@ -945,6 +946,13 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 
+		/* we don't support passing session variables to workers */
+		if (param->paramkind == PARAM_VARIABLE)
+		{
+			if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+				return true;
+		}
+
 		if (param->paramkind != PARAM_EXEC ||
 			!list_member_int(context->safe_param_ids, param->paramid))
 		{
@@ -2403,6 +2411,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
  *	  value of the Param.
  * 2. Fold stable, as well as immutable, functions to constants.
  * 3. Reduce PlaceHolderVar nodes to their contained expressions.
+ * 4. Current value of session variable can be used for estimation too.
  *--------------------
  */
 Node *
@@ -2529,6 +2538,29 @@ eval_const_expressions_mutator(Node *node,
 						}
 					}
 				}
+				else if (param->paramkind == PARAM_VARIABLE &&
+						 context->estimate)
+				{
+					int16		typLen;
+					bool		typByVal;
+					Datum		pval;
+					bool		isnull;
+
+					get_typlenbyval(param->paramtype, &typLen, &typByVal);
+
+					pval = GetSessionVariableWithTypecheck(param->paramvarname,
+														   param->paramtype,
+														   param->paramtypmod,
+														   &isnull);
+
+					return (Node *) makeConst(param->paramtype,
+											  param->paramtypmod,
+											  param->paramcollid,
+											  (int) typLen,
+											  pval,
+											  isnull,
+											  typByVal);
+				}
 
 				/*
@@ -4828,7 +4860,8 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,

 	return false;
 }
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 96be968c3d4..3687490bcb1 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -22,6 +22,8 @@
 extern void CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt);
 extern void DropVariableByName(char *varname);
 
+extern Datum GetSessionVariableWithTypecheck(char *varname, Oid typid, int32 typmod, bool *isnull);
+
 extern void get_session_variable_type_typmod_collid(char *varname,
 													Oid *typid,
 													int32 *typmod,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 46a8655621d..97a00df8d1f 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -189,6 +189,9 @@ typedef struct PlannerGlobal
 	/* extension state */
 	void	  **extension_state pg_node_attr(read_write_ignore);
 	int			extension_state_allocated;
+
+	/* list of used session variables */
+	List	   *sessionVariables;
 } PlannerGlobal;

 	/*
index c4393a94321..61754ae4efc 100644
 
+	/* PARAM_VARIABLE Params */
 
2.52.0

From 551fb8e2a7a77ee1927659001838aa0b09c7bbc5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 15:28:59 +0100
Subject: [PATCH 2/7] parsing session variable fences

The session variables cannot be used as parameters of CALL or EXECUTE
commands. These commands evaluates arguments by direct call of expression
executor, and direct access to session variables from expression executor
will be implemented later (in next step)
---
 doc/src/sgml/ddl.sgml                   |  11 +++
 src/backend/commands/prepare.c          |   8 ++
 src/backend/commands/session_variable.c |  21 +++++
 src/backend/nodes/nodeFuncs.c           |   6 ++
 src/backend/parser/analyze.c            |   7 ++
 src/backend/parser/gram.y               |  17 +++-
 src/backend/parser/parse_expr.c         | 119 ++++++++++++++++++++++++
 src/backend/parser/parse_merge.c        |   1 +
 src/backend/parser/parse_target.c       |   7 ++
 src/backend/utils/adt/ruleutils.c       |   8 ++
 src/include/commands/session_variable.h |   5 +
 src/include/nodes/parsenodes.h          |  12 +++
 src/include/nodes/primnodes.h           |   5 +
 src/include/parser/parse_node.h         |   1 +
 src/pl/plpgsql/src/pl_exec.c            |   3 +-
 src/tools/pgindent/typedefs.list        |   1 +
 16 files changed, 229 insertions(+), 3 deletions(-)

index 10f1a00c275..150b04f1d4b 100644
@@ -5676,6 +5676,17 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
  <sect1 id="ddl-others">
 
+	/*
 
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index d78e82b87c0..861c2a55f4e 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -111,6 +111,27 @@ search_variable(char *varname)
 	return svar;
 }
 
+/*
+ * Returns the type, typmod and collid of the given session variable.
+ *
+ * Raises an error when the variable doesn't exists and *error is null.
+ */
+void
+get_session_variable_type_typmod_collid(char *varname,
+										Oid *typid,
+										int32 *typmod,
+										Oid *collid)
+{
+	SVariable	svar;
+
+	svar = search_variable(varname);
+
+	/* only owner can set content of variable */
+	*typid = svar->vartype;
+	*typmod = svar->vartypmod;
+	*collid = svar->varcollation;
+}
+
 /*
  * Creates a new variable - does new entry in sessionvars
  *
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index ede838cd40c..c761fde6acb 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1669,6 +1669,9 @@ exprLocation(const Node *expr)
 		case T_ParamRef:
 			loc = ((const ParamRef *) expr)->location;
 			break;
+		case T_VariableFence:
+			loc = ((const VariableFence *) expr)->location;
+			break;
 		case T_A_Const:
 			loc = ((const A_Const *) expr)->location;
 			break;
@@ -4701,6 +4704,9 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_VariableFence:
+			/* we assume the fields contain nothing interesting */
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 3b392b084ad..c3897aafd21 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -618,6 +618,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasAggs = pstate->p_hasAggs;
+	qry->hasSessionVariables = pstate->p_hasSessionVariables;

 

 

 

 

 
index 0a5efd733dc..837330a807f 100644
@@ -887,7 +887,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -15719,6 +15719,8 @@ c_expr:		columnref								{ $$ = $1; }
+			| variable_fence
@@ -17121,6 +17123,17 @@ case_arg:	a_expr									{ $$ = $1; }
 
+variable_fence:
+			VARIABLE '(' ColId ')'
+				{
index 32d6ae918ca..89661d38357 100644
@@ -371,6 +373,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 
+		case T_VariableFence:
+			result = transformVariableFence(pstate, (VariableFence *) expr);
+			break;
+
 		default:
 			/* should not reach here */
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -904,6 +910,119 @@ transformParamRef(ParseState *pstate, ParamRef *pref)
 	return result;
 }
 
+/*
+	get_session_variable_type_typmod_collid(vf->varname,
+											&typid, &typmod, &collid);
+	param->paramvarname = pstrdup(vf->varname);

 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 905c975d83b..2f42627f009 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -2033,6 +2033,13 @@ FigureColnameInternal(Node *node, char **name)
 						 (int) ((JsonFuncExpr *) node)->op);
 			}
 			break;
+		case T_VariableFence:
+			{
+				/* return last field name */
+				*name = ((VariableFence *) node)->varname;
+				return 2;
+			}
+			break;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 556ab057e5a..e050dc1345a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8816,6 +8816,14 @@ get_parameter(Param *param, deparse_context *context)
 		}
 	}
 
+	/* Note: can be be used by EXPLAIN */
+	if (param->paramkind == PARAM_VARIABLE)
+	{
+		appendStringInfo(context->buf, "VARIABLES(%s)",
+						 quote_identifier(param->paramvarname));
+		return;
+	}
+
 	/*
 	 * Not PARAM_EXEC, or couldn't find referent: just print $N.
 	 *
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 1ed40d87a38..96be968c3d4 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -22,4 +22,9 @@
 extern void CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt);
 extern void DropVariableByName(char *varname);
 
+extern void get_session_variable_type_typmod_collid(char *varname,
+													Oid *typid,
+													int32 *typmod,
+													Oid *collid);
+
 #endif
index 567e5970e50..487464b14b1 100644

+/*
+	char	   *varname;		/* variable name */
index 1b4436f2ff6..760ca03f7e1 100644
+ *				(paramvarname holds the variable's name).
 
 typedef struct Param
+	char	   *paramvarname pg_node_attr(query_jumble_ignore);

 
 	/*
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c806688892b..0ff3b10277c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3219,6 +3219,7 @@ ValidatorValidateCB
 ValuesScan
 ValuesScanState
 Var
+VariableFence
 VarBit
 VarChar
 VarParamState
-- 
2.52.0

From 43c80c67b351fad160f245f3a45d45b3ccf0bc19 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 19 Nov 2025 19:36:07 +0100
Subject: [PATCH 1/7] CREATE VARIABLE, DROP VARIABLE

---
 doc/src/sgml/ddl.sgml                         |  23 ++
 doc/src/sgml/glossary.sgml                    |  15 ++
 doc/src/sgml/ref/allfiles.sgml                |   2 +
 doc/src/sgml/ref/create_variable.sgml         | 132 +++++++++++
 doc/src/sgml/ref/drop_variable.sgml           |  84 +++++++
 doc/src/sgml/reference.sgml                   |   2 +
 src/backend/commands/Makefile                 |   1 +
 src/backend/commands/dropcmds.c               |   1 +
 src/backend/commands/meson.build              |   1 +
 src/backend/commands/session_variable.c       | 218 ++++++++++++++++++
 src/backend/parser/gram.y                     |  55 ++++-
 src/backend/tcop/utility.c                    |  26 +++
 src/bin/psql/tab-complete.in.c                |  10 +-
 src/include/commands/session_variable.h       |  25 ++
 src/include/nodes/parsenodes.h                |  23 ++
 src/include/parser/kwlist.h                   |   1 +
 src/include/tcop/cmdtaglist.h                 |   2 +
 .../expected/session_variables_ddl.out        |  43 ++++
 src/test/regress/parallel_schedule            |   2 +-
 .../regress/sql/session_variables_ddl.sql     |  56 +++++
 src/tools/pgindent/typedefs.list              |   4 +
 21 files changed, 719 insertions(+), 7 deletions(-)
 create mode 100644 doc/src/sgml/ref/create_variable.sgml
 create mode 100644 doc/src/sgml/ref/drop_variable.sgml
 create mode 100644 src/backend/commands/session_variable.c
 create mode 100644 src/include/commands/session_variable.h
 create mode 100644 src/test/regress/expected/session_variables_ddl.out
 create mode 100644 src/test/regress/sql/session_variables_ddl.sql

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 09ad8400fd0..10f1a00c275 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5655,6 +5655,29 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>
 
+  <sect1 id="ddl-session-variables">
+   <title>Session Variables</title>
+
+   <indexterm zone="ddl-session-variables">
+    <primary>Session variables</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>session variable</primary>
+   </indexterm>
+
+   <para>
+    Session variables are temporal database objects that can hold a value.
+    A session variable can be created by the <command>CREATE VARIABLE</command>
+    command. Only owner can use session variable.
+   </para>
+
+   <para>
+    The session variable holds value in session memory.  This value is private
+    to each session and is released when the session ends.
+   </para>
+  </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>
 
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index a76cf5c383f..64d5fd40219 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -1726,6 +1726,21 @@
    </glossdef>
   </glossentry>
 
+  <glossentry id="glossary-session-variable">
+   <glossterm>Session variable</glossterm>
+   <glossdef>
+    <para>
+     A temporal database object that holds a value in session memory.  This
+     value is private to each session and is released when the session ends.
+     The default value of the session variable is null.  Read or write access
+     to session variables is allowed only to owner (creator).
+    </para>
+    <para>
+     For more information, see <xref linkend="ddl-session-variables"/>.
+    </para>
+   </glossdef>
+  </glossentry>
+
   <glossentry id="glossary-shared-memory">
    <glossterm>Shared memory</glossterm>
    <glossdef>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index e167406c744..a7349919658 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -99,6 +99,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createType         SYSTEM "create_type.sgml">
 <!ENTITY createUser         SYSTEM "create_user.sgml">
 <!ENTITY createUserMapping  SYSTEM "create_user_mapping.sgml">
+<!ENTITY createVariable     SYSTEM "create_variable.sgml">
 <!ENTITY createView         SYSTEM "create_view.sgml">
 <!ENTITY deallocate         SYSTEM "deallocate.sgml">
 <!ENTITY declare            SYSTEM "declare.sgml">
@@ -147,6 +148,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropType           SYSTEM "drop_type.sgml">
 <!ENTITY dropUser           SYSTEM "drop_user.sgml">
 <!ENTITY dropUserMapping    SYSTEM "drop_user_mapping.sgml">
+<!ENTITY dropVariable       SYSTEM "drop_variable.sgml">
 <!ENTITY dropView           SYSTEM "drop_view.sgml">
 <!ENTITY end                SYSTEM "end.sgml">
 <!ENTITY execute            SYSTEM "execute.sgml">
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
new file mode 100644
index 00000000000..5ed74c5c173
--- /dev/null
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -0,0 +1,132 @@
+<!--
+doc/src/sgml/ref/create_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-createvariable">
+ <indexterm zone="sql-createvariable">
+  <primary>CREATE VARIABLE</primary>
+ </indexterm>
+
+ <indexterm>
+  <primary>session variable</primary>
+  <secondary>defining</secondary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE VARIABLE</refname>
+  <refpurpose>define a session variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE { TEMP | TEMPORAL } VARIABLE <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   The <command>CREATE VARIABLE</command> command creates a temporal session
+   variable.
+  </para>
+
+  <para>
+   The value of a session variable is local to the current session.  Retrieving
+   a session variable's value returns NULL, unless its value is set to
+   something else in the current session with a <command>LET</command> command.
+   The content of a session variable is not transactional. This is the same as
+   regular variables in procedural languages.
+  </para>
+
+  <para>
+   Session variables are retrieved by the <command>SELECT</command>
+   command.  Their value is set with the <command>LET</command> command.
+  </para>
+
+  <para>
+   Session variables cannot be used in views or in SQL functions using
+   SQL-conforming style syntax.
+  </para>
+
+  <note>
+   <para>
+    Session variables can be <quote>shadowed</quote> by other identifiers.
+    For details, see <xref linkend="ddl-session-variables"/>.
+   </para>
+  </note>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+
+   <varlistentry id="sql-createvariable-name">
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the session variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="sql-createvariable-data_type">
+    <term><replaceable class="parameter">data_type</replaceable></term>
+    <listitem>
+     <para>
+      The name, optionally schema-qualified, of the data type of the session
+      variable. Only buildin scalar data types are allowed. Arrays or composite
+      types are not allowed.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   Use the <command>DROP VARIABLE</command> command to remove a session
+   variable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create an date session variable <literal>var1</literal>:
+<programlisting>
+CREATE TEMPORARY VARIABLE var1 AS date;
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   The <command>CREATE VARIABLE</command> command is a
+   <productname>PostgreSQL</productname> extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-dropvariable"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml
new file mode 100644
index 00000000000..e8517a78200
--- /dev/null
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -0,0 +1,84 @@
+<!--
+doc/src/sgml/ref/drop_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-dropvariable">
+ <indexterm zone="sql-dropvariable">
+  <primary>DROP VARIABLE</primary>
+ </indexterm>
+
+ <indexterm>
+  <primary>session variable</primary>
+  <secondary>removing</secondary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP VARIABLE</refname>
+  <refpurpose>remove a session variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP VARIABLE <replaceable class="parameter">name</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP VARIABLE</command> removes a session variable.
+   A session variable can only be removed by its owner or a superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of a session variable.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To remove the session variable <literal>var1</literal>:
+
+<programlisting>
+DROP VARIABLE var1;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   The <command>DROP VARIABLE</command> command is a
+   <productname>PostgreSQL</productname> extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createvariable"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 2cf02c37b17..c03e7692c7a 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -127,6 +127,7 @@
    &createType;
    &createUser;
    &createUserMapping;
+   &createVariable;
    &createView;
    &deallocate;
    &declare;
@@ -175,6 +176,7 @@
    &dropType;
    &dropUser;
    &dropUserMapping;
+   &dropVariable;
    &dropView;
    &end;
    &execute;
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index f99acfd2b4b..29fe872dc16 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -53,6 +53,7 @@ OBJS = \
 	schemacmds.o \
 	seclabel.o \
 	sequence.o \
+	session_variable.o \
 	statscmds.o \
 	subscriptioncmds.o \
 	tablecmds.o \
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index ceb9a229b63..9524f867857 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -22,6 +22,7 @@
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_proc.h"
 #include "commands/defrem.h"
+#include "commands/session_variable.h"
 #include "miscadmin.h"
 #include "parser/parse_type.h"
 #include "utils/acl.h"
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 9f640ad4810..beecf3c8adf 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -41,6 +41,7 @@ backend_sources += files(
   'schemacmds.c',
   'seclabel.c',
   'sequence.c',
+  'session_variable.c',
   'statscmds.c',
   'subscriptioncmds.c',
   'tablecmds.c',
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
new file mode 100644
index 00000000000..d78e82b87c0
--- /dev/null
+++ b/src/backend/commands/session_variable.c
@@ -0,0 +1,218 @@
+/*-------------------------------------------------------------------------
+ *
+ * session_variable.c
+ *	  session variable creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/session_variable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/pg_language.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/session_variable.h"
+#include "miscadmin.h"
+#include "parser/parse_type.h"
+#include "storage/proc.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+
+/*
+ * The session variables are stored in the backend's private memory (data,
+ * metadata) in the dedicated memory context SVariableMemoryContext in binary
+ * format. They are stored in the "sessionvars" hash table, whose key is the
+ * name of the variable.
+ *
+ * Only owner (creator) can access the session variables. Because there is
+ * not catalog support, there is not possibility to track dependecies, and
+ * then only buildin types.
+ */
+typedef struct SVariableData
+{
+	NameData	varname;
+
+	Oid			varowner;
+	Oid			vartype;
+	int32		vartypmod;
+	Oid			varcollation;
+
+	bool		isnull;
+	Datum		value;
+
+	int16		typlen;
+	bool		typbyval;
+} SVariableData;
+
+typedef SVariableData *SVariable;
+
+static HTAB *sessionvars = NULL;	/* hash table for session variables */
+
+static MemoryContext SVariableMemoryContext = NULL;
+
+/*
+ * Create the hash table for storing session variables.
+ */
+static void
+create_sessionvars_hashtables(void)
+{
+	HASHCTL		vars_ctl;
+
+	Assert(!sessionvars);
+
+	if (!SVariableMemoryContext)
+	{
+		/* we need our own long-lived memory context */
+		SVariableMemoryContext =
+			AllocSetContextCreate(TopMemoryContext,
+								  "session variables",
+								  ALLOCSET_START_SMALL_SIZES);
+	}
+
+	vars_ctl.keysize = NAMEDATALEN;
+	vars_ctl.entrysize = sizeof(SVariableData);
+	vars_ctl.hcxt = SVariableMemoryContext;
+
+	sessionvars = hash_create("Session variables", 64, &vars_ctl,
+							  HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
+}
+
+/*
+ * Returns entry of session variable specified by name
+ */
+static SVariable
+search_variable(char *varname)
+{
+	SVariable	svar;
+
+	if (!sessionvars)
+		create_sessionvars_hashtables();
+
+	svar = (SVariable) hash_search(sessionvars, varname,
+								   HASH_FIND, NULL);
+
+	if (!svar)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("session variable \"%s\" doesn't exist",
+						varname)));
+
+	return svar;
+}
+
+/*
+ * Creates a new variable - does new entry in sessionvars
+ *
+ * Used by CREATE VARIABLE command
+ */
+void
+CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
+{
+	Oid			typeid;
+	int32		typmod;
+	Oid			typcollation;
+	Oid			varowner = GetUserId();
+	SVariable	svar;
+	bool		found;
+	HeapTuple	proctup;
+	Form_pg_proc proc;
+	int16		typlen;
+	bool		typbyval;
+	Oid			typinput;
+	Oid			typioparam;
+	AclResult	aclresult;
+
+	typenameTypeIdAndMod(pstate, stmt->typeName, &typeid, &typmod);
+
+	if (get_typtype(typeid) != TYPTYPE_BASE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("%s is not a base type",
+						format_type_be(typeid))));
+
+	if (OidIsValid(get_element_type(typeid)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("%s type is an array",
+						format_type_be(typeid))));
+
+	aclresult = object_aclcheck(TypeRelationId, typeid, GetUserId(), ACL_USAGE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error_type(aclresult, typeid);
+
+	/* allow only buildin types */
+	getTypeInputInfo(typeid, &typinput, &typioparam);
+
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(typinput));
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cannot to find function oid %d", typinput);
+
+	proc = (Form_pg_proc) GETSTRUCT(proctup);
+	if (proc->prolang != INTERNALlanguageId)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("%s is not a internal type",
+						format_type_be(typeid))));
+
+	ReleaseSysCache(proctup);
+
+	get_typlenbyval(typeid, &typlen, &typbyval);
+	typcollation = get_typcollation(typeid);
+
+	if (!sessionvars)
+		create_sessionvars_hashtables();
+
+	svar = hash_search(sessionvars, stmt->name,
+					   HASH_ENTER, &found);
+
+	if (found)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("session variable \"%s\" already exists",
+						stmt->name)));
+
+	namestrcpy(&svar->varname, stmt->name);
+	svar->vartype = typeid;
+	svar->vartypmod = typmod;
+	svar->varcollation = typcollation;
+	svar->varowner = varowner;
+	svar->typlen = typlen;
+	svar->typbyval = typbyval;
+
+	svar->value = (Datum) 0;
+	svar->isnull = true;
+}
+
+/*
+ * Drop variable by name
+ */
+void
+DropVariableByName(char *varname)
+{
+	SVariable	svar;
+
+	svar = search_variable(varname);
+
+	/* only owner can get content of variable */
+	if (svar->varowner != GetUserId() && !superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("must be owner of session variable %s",
+						varname)));
+
+	(void) hash_search(sessionvars,
+					   varname,
+					   HASH_REMOVE,
+					   NULL);
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c3a0a354a9c..0a5efd733dc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -290,13 +290,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
 		CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt
 		CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
-		CreateSchemaStmt CreateSeqStmt CreateStmt CreateStatsStmt CreateTableSpaceStmt
-		CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
+		CreateSchemaStmt CreateSeqStmt CreateSessionVarStmt CreateStmt CreateStatsStmt
+		CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
 		CreateAssertionStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
 		CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
 		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
 		DropOpClassStmt DropOpFamilyStmt DropStmt
-		DropCastStmt DropRoleStmt
+		DropCastStmt DropRoleStmt DropSessionVarStmt
 		DropdbStmt DropTableSpaceStmt
 		DropTransformStmt
 		DropUserMappingStmt ExplainStmt FetchStmt
@@ -789,8 +789,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UESCAPE UNBOUNDED UNCONDITIONAL UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING

 
@@ -1056,6 +1056,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1083,6 +1084,7 @@ stmt:
 			| DropTableSpaceStmt
 			| DropTransformStmt
 			| DropRoleStmt
+			| DropSessionVarStmt
 			| DropUserMappingStmt
 			| DropdbStmt
 			| ExecuteStmt
@@ -5336,6 +5338,47 @@ create_extension_opt_item:
 				}
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY :
+ *				CREATE { TEMP | TEMPORARY } VARIABLE varname [AS] type
+ *
+ *****************************************************************************/
+
+CreateSessionVarStmt:
+			CREATE OptTemp VARIABLE ColId opt_as Typename
+				{
+					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
+
+					if ($2 != RELPERSISTENCE_TEMP)
+						ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("only temporal session variables are supported"),
+							 parser_errposition(@2)));
+
+					n->name = $4;
+					n->typeName = $6;
+					$$ = (Node *) n;
+				}
+		;
+
+/*****************************************************************************
+ *
+ *		QUERY :
+ *				DROP VARIABLE varname
+ *
+ *****************************************************************************/
+
+DropSessionVarStmt:
+			DROP VARIABLE ColId
+				{
+					DropSessionVarStmt *n = makeNode(DropSessionVarStmt);
+
+					n->name = $3;
+					$$ = (Node *) n;
+				}
+		;
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
@@ -18154,6 +18197,7 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18812,6 +18856,7 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index d18a3a60a46..7dc9c0a6c80 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -48,6 +48,7 @@
 #include "commands/schemacmds.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
+#include "commands/session_variable.h"
 #include "commands/subscriptioncmds.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -183,6 +184,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_CreateRangeStmt:
 		case T_CreateRoleStmt:
 		case T_CreateSchemaStmt:
+		case T_CreateSessionVarStmt:
 		case T_CreateSeqStmt:
 		case T_CreateStatsStmt:
 		case T_CreateStmt:
@@ -201,6 +203,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_DropTableSpaceStmt:
 		case T_DropUserMappingStmt:
 		case T_DropdbStmt:
+		case T_DropSessionVarStmt:
 		case T_GrantRoleStmt:
 		case T_GrantStmt:
 		case T_ImportForeignSchemaStmt:
@@ -1063,6 +1066,15 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			}
 			break;
 
+		case T_CreateSessionVarStmt:
+			CreateVariable(pstate, (CreateSessionVarStmt *) parsetree);
+			break;
+
+		case T_DropSessionVarStmt:
+			/* No event triggers for catalog less session variables */
+			DropVariableByName(((DropSessionVarStmt *) parsetree)->name);
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -1388,6 +1400,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				}
 				break;
 
+
 				/*
 				 * ************* object creation / destruction **************
 				 */
@@ -3235,6 +3248,14 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_CreateSessionVarStmt:
+			tag = CMDTAG_CREATE_VARIABLE;
+			break;
+
+		case T_DropSessionVarStmt:
+			tag = CMDTAG_DROP_VARIABLE;
+			break;
+
 		default:
 			elog(WARNING, "unrecognized node type: %d",
 				 (int) nodeTag(parsetree));
@@ -3773,6 +3794,11 @@ GetCommandLogLevel(Node *parsetree)
 			}
 			break;
 
+		case T_CreateSessionVarStmt:
+		case T_DropSessionVarStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		default:
 			elog(WARNING, "unrecognized node type: %d",
 				 (int) nodeTag(parsetree));
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 20d7a65c614..d257b4fc37a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -1363,6 +1363,7 @@ static const pgsql_thing_t words_after_create[] = {
 																			 * TABLE ... */
 	{"USER", Query_for_list_of_roles, NULL, NULL, Keywords_for_user_thing},
 	{"USER MAPPING FOR", NULL, NULL, NULL},
+	{"VARIABLE", NULL, NULL, NULL, NULL, THING_NO_ALTER},
 	{"VIEW", NULL, NULL, &Query_for_list_of_views},
 	{NULL}						/* end of list */
 };
@@ -3723,7 +3724,7 @@ match_previous_words(int pattern_id,
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
-		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+		COMPLETE_WITH("SEQUENCE", "TABLE", "VARIABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or SEQUENCE */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "SEQUENCE");
@@ -4076,6 +4077,13 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH(",", ")");
 	}
 
+/* CREATE VARIABLE */
+	else if (Matches("CREATE", "TEMP|TEMPORARY", "VARIABLE", MatchAny))
+		COMPLETE_WITH("AS");
+	else if (TailMatches("VARIABLE", MatchAny, "AS"))
+		/* Complete CREATE VARIABLE <name> with AS types */
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
+
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS or WITH */
 	else if (TailMatches("CREATE", "VIEW", MatchAny) ||
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
new file mode 100644
index 00000000000..1ed40d87a38
--- /dev/null
+++ b/src/include/commands/session_variable.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * sessionvariable.h
+ *	  prototypes for sessionvariable.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/session_variable.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SESSIONVARIABLE_H
+#define SESSIONVARIABLE_H
+
+#include "catalog/objectaddress.h"
+#include "parser/parse_node.h"
+#include "nodes/parsenodes.h"
+
+extern void CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt);
+extern void DropVariableByName(char *varname);
+
+#endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d14294a4ece..567e5970e50 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3555,6 +3555,29 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+
+/* ----------------------
+ *		Create Variable Statement
+ * ----------------------
+ */
+typedef struct CreateSessionVarStmt
+{
+	NodeTag		type;
+	char	   *name;		/* the variable to create */
+	TypeName   *typeName;		/* the type of variable */
+} CreateSessionVarStmt;
+
+/* ----------------------
+ *		DROP Variable Statement
+ * ----------------------
+ */
+typedef struct DropSessionVarStmt
+{
+	NodeTag		type;
+	char	   *name;
+} DropSessionVarStmt;
+
+
 /* ----------------------
  *		Create Function Statement
  * ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 5d4fe27ef96..8b1f2bd3b68 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -489,6 +489,7 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index c4606d65043..7e59d0a5275 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -123,6 +123,7 @@ PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_VARIABLE, "CREATE VARIABLE", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false)
 PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false)
 PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false)
@@ -175,6 +176,7 @@ PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_VARIABLE, "DROP VARIABLE", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false)
 PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false)
 PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false)
diff --git a/src/test/regress/expected/session_variables_ddl.out b/src/test/regress/expected/session_variables_ddl.out
new file mode 100644
index 00000000000..cc6f59e8be5
--- /dev/null
+++ b/src/test/regress/expected/session_variables_ddl.out
@@ -0,0 +1,43 @@
+SET log_statement TO ddl;
+-- should to fail
+CREATE VARIABLE x AS int;
+ERROR:  only temporal session variables are supported
+-- should be ok
+CREATE TEMPORARY VARIABLE x AS int;
+-- should to fail
+CREATE TEMPORARY VARIABLE x AS int;
+ERROR:  session variable "x" already exists
+-- should to fail
+DROP VARIABLE y;
+ERROR:  session variable "y" doesn't exist
+-- should be ok
+DROP VARIABLE x;
+CREATE TYPE test_type AS (x int, y int);
+-- should to fail
+CREATE VARIABLE x AS test_type;
+ERROR:  only temporal session variables are supported
+DROP TYPE test_type;
+-- should to fail
+CREATE VARIABLE x AS int[];
+ERROR:  only temporal session variables are supported
+CREATE DOMAIN test_domain AS int;
+-- should to fail
+CREATE VARIABLE x AS test_domain;
+ERROR:  only temporal session variables are supported
+DROP DOMAIN test_domain;
+CREATE ROLE regress_session_variable_test_role_01;
+CREATE ROLE regress_session_variable_test_role_02;
+SET ROLE TO regress_session_variable_test_role_01;
+CREATE TEMP VARIABLE x AS int;
+SET ROLE TO default;
+SET ROLE TO regress_session_variable_test_role_02;
+-- should to fail
+DROP VARIABLE x;
+ERROR:  must be owner of session variable x
+SET ROLE TO default;
+SET ROLE TO regress_session_variable_test_role_01;
+-- should be ok
+DROP VARIABLE x;
+SET ROLE TO DEFAULT;
+DROP ROLE regress_session_variable_test_role_01;
+DROP ROLE regress_session_variable_test_role_02;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f56482fb9f1..371c0a6bdd9 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -115,7 +115,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
 # NB: temp.sql does reconnects which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml session_variables_ddl
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/sql/session_variables_ddl.sql b/src/test/regress/sql/session_variables_ddl.sql
new file mode 100644
index 00000000000..92e97ea2ab0
--- /dev/null
+++ b/src/test/regress/sql/session_variables_ddl.sql
@@ -0,0 +1,56 @@
+SET log_statement TO ddl;
+
+-- should to fail
+CREATE VARIABLE x AS int;
+
+-- should be ok
+CREATE TEMPORARY VARIABLE x AS int;
+
+-- should to fail
+CREATE TEMPORARY VARIABLE x AS int;
+
+-- should to fail
+DROP VARIABLE y;
+
+-- should be ok
+DROP VARIABLE x;
+
+CREATE TYPE test_type AS (x int, y int);
+
+-- should to fail
+CREATE VARIABLE x AS test_type;
+
+DROP TYPE test_type;
+
+-- should to fail
+CREATE VARIABLE x AS int[];
+
+CREATE DOMAIN test_domain AS int;
+
+-- should to fail
+CREATE VARIABLE x AS test_domain;
+
+DROP DOMAIN test_domain;
+
+CREATE ROLE regress_session_variable_test_role_01;
+CREATE ROLE regress_session_variable_test_role_02;
+
+SET ROLE TO regress_session_variable_test_role_01;
+
+CREATE TEMP VARIABLE x AS int;
+
+SET ROLE TO default;
+SET ROLE TO regress_session_variable_test_role_02;
+
+-- should to fail
+DROP VARIABLE x;
+
+SET ROLE TO default;
+SET ROLE TO regress_session_variable_test_role_01;
+
+-- should be ok
+DROP VARIABLE x;
+
+SET ROLE TO DEFAULT;
+DROP ROLE regress_session_variable_test_role_01;
+DROP ROLE regress_session_variable_test_role_02;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 57a8f0366a5..c806688892b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -562,6 +562,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
@@ -664,6 +665,7 @@ DropBehavior
 DropOwnedStmt
 DropReplicationSlotCmd
 DropRoleStmt
+DropSessionVarStmt
 DropStmt
 DropSubscriptionStmt
 DropTableSpaceStmt
@@ -2666,6 +2668,8 @@ SSL_CTX
 STARTUPINFO
 STRLEN
 SV
+SVariableData
+SVariable
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 24 Nov 2025 22:19:10 +0100

Hi

I am sorry. I forgot to add let.sgml file to patch

Regards

Pavel
From 2e94c8b21179bcfd44f6f19aa784803ee4d0110c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 20:04:16 +0100
Subject: [PATCH 7/7] DISCARD TEMP

---

 
 		case DISCARD_TEMP:
 
 		default:
 
 	PopActiveSnapshot();
 }
+
+/*

From 07aba25ec28b5fd36c2afba7599c1165ec595c2d Mon Sep 17 00:00:00 2001

 doc/src/sgml/ref/let.sgml                     |  95 ++++++++++++++
 25 files changed, 624 insertions(+), 20 deletions(-)

  <sect1 id="ddl-others">

 

index 00000000000..5909d203e94
@@ -0,0 +1,95 @@
 
 /*
 
 	/*

 
+		case T_LetStmt:
 
 			/*

 
 
 	/*
 
 /*
 
+/*
+ * transformLetStmt -

 

 			/*
 
 			/*

 			/*
 
 			/*
 
 			/*
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

+/* LET */

 

 

 

 
+	/*
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

 
+/*

 

 
 
 /* ----------------

* parallel execution

plan
---

 
+		/*
 
+/*
 
 	/*

+	/*

+/*

 
 
 				/*

 	return false;
 }

 
 	/*

 
  <sect1 id="ddl-others">
 
+	/*

+/*

 

 

 

 

 
 
+variable_fence:
+			VARIABLE '(' ColId ')'
+				{
 
+		case T_VariableFence:
 
+/*

 

 
 
+/*
 
 typedef struct Param

 
 	/*

---

 

 

 
 
+
 				/*

 
+/* CREATE VARIABLE */
 
+
 
 # ----------

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 25 Nov 2025 05:43:04 +0100

Hi

I am sending a link to our wiki with general description of implementations
of session variables in mayor RDBMS

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 30 Nov 2025 07:01:42 +0100

Hi

only rebase

Regards

Pavel
From 6705779d1128843052c2345eed2b7e7705c9330a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 5/7] svariableReceiver

 
+/*

 

 
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 2f562a217bf..088429dd424 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2674,6 +2674,7 @@ STRLEN
 SV
 SVariableData
 SVariable
+SVariableState
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

From deb8adaeae1645388b1c4065a54115a71c96ce85 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 20:04:16 +0100
Subject: [PATCH 7/7] DISCARD TEMP

---

 
 		case DISCARD_TEMP:
 
 		default:
 
 	PopActiveSnapshot();
 }
+
+/*

From 8c8e99dd3c14bdaa920d800c140a8bcb702dc902 Mon Sep 17 00:00:00 2001

 
  <sect1 id="ddl-others">

 

 
 /*
index c1eb81f79e5..fdb13ada75d 100644
 
 	/*
index 5b0b5951ef4..956551c5757 100644
@@ -53,15 +53,18 @@

 /* Hook for plugins to get control at end of parse analysis */
@@ -85,7 +88,7 @@ static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
 static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
 static Query *transformPLAssignStmt(ParseState *pstate,
 									PLAssignStmt *stmt);
-static List *transformPLAssignStmtTarget(ParseState *pstate, List *tlist,
+static List *transformAssignTarget(ParseState *pstate, List *tlist,
 										 SelectStmtPassthrough *passthru);
 static Query *transformDeclareCursorStmt(ParseState *pstate,
 										 DeclareCursorStmt *stmt);
@@ -95,6 +98,8 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 										 CreateTableAsStmt *stmt);
 static Query *transformCallStmt(ParseState *pstate,
 								CallStmt *stmt);
+static Query *transformLetStmt(ParseState *pstate,
+							   LetStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 								   LockingClause *lc, bool pushedDown);
 #ifdef DEBUG_NODE_TESTS_ENABLED
@@ -342,6 +347,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
 			case T_UpdateStmt:
 			case T_DeleteStmt:
 			case T_MergeStmt:
+			case T_LetStmt:
 				(void) test_raw_expression_coverage(parseTree, NULL);
 				break;
 			default:
@@ -421,6 +427,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
 									   (CallStmt *) parseTree);
 			break;
 
+		case T_LetStmt:
 
 			/*
@@ -482,6 +493,7 @@ stmt_requires_parse_analysis(RawStmt *parseTree)
 		case T_ExplainStmt:
 		case T_CreateTableAsStmt:
 		case T_CallStmt:
+		case T_LetStmt:
 			result = true;
 			break;
 
@@ -547,6 +559,7 @@ query_requires_rewrite_plan(Query *query)
 			case T_ExplainStmt:
@@ -1390,7 +1403,7 @@ count_rowexpr_columns(ParseState *pstate, Node *expr)
@@ -1443,8 +1456,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt,
 
@@ -2842,9 +2855,11 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 						   EXPR_KIND_UPDATE_TARGET);

 	/*
 	 * To avoid duplicating a lot of code, we use transformSelectStmt to do
@@ -2867,18 +2882,21 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
 
 /*
@@ -2913,7 +2931,7 @@ transformPLAssignStmtTarget(ParseState *pstate, List *tlist,
@@ -2921,10 +2939,10 @@ transformPLAssignStmtTarget(ParseState *pstate, List *tlist,
@@ -2947,7 +2965,7 @@ transformPLAssignStmtTarget(ParseState *pstate, List *tlist,
@@ -2956,7 +2974,7 @@ transformPLAssignStmtTarget(ParseState *pstate, List *tlist,
@@ -3324,6 +3342,59 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 	return result;
 }
 
+/*
+ * transformLetStmt -

 

 			/*
index f94d6cf0694..a0b1a15d8f1 100644
 
 			/*

 			/*
 
 			/*
 
 			/*
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

+/* LET */

 

 

 
index 088429dd424..7fbc5c8834d 100644
@@ -1550,6 +1550,7 @@ LargeObjectDesc

From fba078e4cab4ac187016a02be09a72eb332d82f1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 20:42:56 +0100
Subject: [PATCH 3/7] collect session variables used in plan and assign paramid

* parallel execution

plan
---

diff --git a/doc/src/sgml/parallel.sgml b/doc/src/sgml/parallel.sgml
index af43484703e..843e2c3f663 100644
--- a/doc/src/sgml/parallel.sgml
+++ b/doc/src/sgml/parallel.sgml
@@ -524,6 +524,12 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
         Plan nodes that reference a correlated <literal>SubPlan</literal>.
       </para>
     </listitem>
+
+    <listitem>
+      <para>
+        Usage of a session variable.
+      </para>
+    </listitem>
   </itemizedlist>
 
index 8e70a85a3f7..52442c746a5 100644
 
+		/*
 
+/*
 
 	/*

+	/*

+/*

index bda4c4eb292..c6aa09a8d39 100644
@@ -947,6 +948,13 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 
@@ -2405,6 +2413,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
@@ -2531,6 +2540,29 @@ eval_const_expressions_mutator(Node *node,
 
 				/*
@@ -4903,7 +4935,8 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,

 	return false;
 }

 
 	/*

From 379dcaa7877a4cc372a657e55ef8baf7c7841871 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 22 Nov 2025 06:40:46 +0100
Subject: [PATCH 4/7] fill an auxiliary buffer with values of session 
 variables used in query

 
+	/*
index 64ff6996431..d0642eae418 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index 9da7c6ab928..55367a13d69 100644
index 7bf4750c5b6..2f562a217bf 100644
@@ -2732,6 +2732,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
-- 
2.52.0

From 2bbf6ff1f5f676c27a5a963cb0e3b1541ad1d682 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 15:28:59 +0100
Subject: [PATCH 2/7] parsing session variable fences

 
  <sect1 id="ddl-others">
 
+	/*

+/*
index d228318dc72..c1eb81f79e5 100644
index 7843a0c857e..5b0b5951ef4 100644
@@ -619,6 +619,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)

@@ -1044,6 +1045,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)

 
@@ -1527,6 +1529,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt,
 
 	foreach(l, stmt->lockingClause)
 	{
@@ -1753,6 +1756,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

 
@@ -2004,6 +2008,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 
@@ -2476,6 +2481,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)

@@ -2543,6 +2549,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)

 
 
+variable_fence:
+			VARIABLE '(' ColId ')'
+				{
index 44fd1385f8c..f94d6cf0694 100644
 
+		case T_VariableFence:
 
+/*

 

 
 
+/*
 
 typedef struct Param

 
 	/*
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 0885e55fdec..7bf4750c5b6 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3225,6 +3225,7 @@ ValidatorValidateCB
 ValuesScan
 ValuesScanState
 Var
+VariableFence
 VarBit
 VarChar
 VarParamState
-- 
2.52.0

From 6dff765ed7fca8d7ee17236006e508bf13cf70d7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 19 Nov 2025 19:36:07 +0100
Subject: [PATCH 1/7] CREATE VARIABLE, DROP VARIABLE

---

 

 

 
 
+
 				/*

 
+/* CREATE VARIABLE */
 
+
index cc6d799bcea..9da7c6ab928 100644
 
 # ----------
index cf3f6a7dafd..0885e55fdec 100644
@@ -666,6 +667,7 @@ DropBehavior
 DropOwnedStmt
 DropReplicationSlotCmd
 DropRoleStmt
+DropSessionVarStmt
 DropStmt
 DropSubscriptionStmt
 DropTableSpaceStmt
@@ -2670,6 +2672,8 @@ SSL_CTX
 STARTUPINFO
 STRLEN
 SV
+SVariableData
+SVariable
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 3 Dec 2025 05:27:04 +0100

Hi

fresh rebase after a87987cafca683e9076c424f99bae117211a83a4

regards

Pavel
From 267400ed531dd199a8c25b88ab008cbdb6af7c0b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 20:04:16 +0100
Subject: [PATCH 7/7] DISCARD TEMP

---

 
 		case DISCARD_TEMP:
 
 		default:
 
 	PopActiveSnapshot();
 }
+
+/*

From 1d95b38b74959899e6b22831b4af905357f04de0 Mon Sep 17 00:00:00 2001

 
  <sect1 id="ddl-others">

 

 
 /*
index c00331be6b8..6dc63954a61 100644
 
 	/*
index 885106922d0..cd6aeff3040 100644

 
+		case T_LetStmt:
 
 			/*

 
 
 	/*
 
 /*
 
+/*
+ * transformLetStmt -

 

 			/*
index aa2483c96a6..bc6aab75cca 100644
 
 			/*

 			/*
 
 			/*
 
 			/*
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

+/* LET */

 

 

 

From 16d674aa9ea39b8e6994810b766b5d5cea7dfeb0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 22 Nov 2025 06:40:46 +0100
Subject: [PATCH 4/7] fill an auxiliary buffer with values of session 
 variables used in query

index b05ff476a63..9a86896221e 100644
 
+	/*
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

From cccf66a3cb8fff58da013ae00c14d2c2cf36149a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 20:42:56 +0100
Subject: [PATCH 3/7] collect session variables used in plan and assign paramid

* parallel execution

plan
---

 
+		/*
 
+/*
index 0e78628bf01..c00331be6b8 100644
 
 	/*

+	/*

+/*

 
 
 				/*

 	return false;
 }

 
 	/*

From 00abe49e29eefbff77ed1afe0ee9348325381cc5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 5/7] svariableReceiver

 
+/*

 

 
 
 /* ----------------

From 64330c45cd2aea493b3d8ede40802c85adc90db9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 15:28:59 +0100
Subject: [PATCH 2/7] parsing session variable fences

 
  <sect1 id="ddl-others">
 
+	/*

+/*
index 05314e7e76b..885106922d0 100644

 

 

 

 

 
 
+variable_fence:
+			VARIABLE '(' ColId ')'
+				{
index 4524e49c326..aa2483c96a6 100644
 
+		case T_VariableFence:
 
+/*

 
index 6cf90be40bb..cb9c456e331 100644

 
 
+/*
 
 typedef struct Param

 
 	/*

From 6824b12a780e72046b9e73469f133515226f8bfa Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 19 Nov 2025 19:36:07 +0100
Subject: [PATCH 1/7] CREATE VARIABLE, DROP VARIABLE

---

 
 
index 64cb6278409..d42ed8952a2 100644
@@ -54,6 +54,7 @@ OBJS = \
 	sequence_xlog.o \
index 5fc35826b1c..1eeb18fd960 100644
@@ -42,6 +42,7 @@ backend_sources += files(
   'sequence_xlog.c',

 

 
+
 				/*

 
+/* CREATE VARIABLE */
 
+
 
 # ----------

---

From: Jim Jones <[email-masked]>
Date: Wed, 3 Dec 2025 14:44:16 +0100

Hi Pavel

> fresh rebase after a87987cafca683e9076c424f99bae117211a83a4

I'm going through the patch again and have a few initial comments.

== Memory Management ==

DROP VARIABLE seems to be leaking memory:

postgres=# CREATE TEMPORARY VARIABLE var AS text;
CREATE VARIABLE
postgres=# LET var = repeat('🐘', 100000000);
LET
postgres=# SELECT pg_size_pretty(used_bytes)
FROM pg_backend_memory_contexts
WHERE name = 'session variables';
 pg_size_pretty
----------------
 381 MB
(1 row)

postgres=# DROP VARIABLE var;
DROP VARIABLE
postgres=# SELECT pg_size_pretty(used_bytes)
FROM pg_backend_memory_contexts
WHERE name = 'session variables';
 pg_size_pretty
----------------
 381 MB
(1 row)

If we simply set the variable to NULL it works as expected:

postgres=# LET var = repeat('🐘', 100000000);
LET
postgres=# SELECT pg_size_pretty(used_bytes)
FROM pg_backend_memory_contexts
WHERE name = 'session variables';
 pg_size_pretty
----------------
 381 MB
(1 row)

postgres=# LET var = NULL;
LET
postgres=# SELECT pg_size_pretty(used_bytes)
FROM pg_backend_memory_contexts
WHERE name = 'session variables';
 pg_size_pretty
----------------
 240 bytes
(1 row)

Most likely you forgot to pfree "svar->value" at DropVariableByName(), e.g.

void
DropVariableByName(char *varname)
{

...

if (!svar->typbyval && !svar->isnull)
	pfree(DatumGetPointer(svar->value));

(void) hash_search(sessionvars,
		   varname,
		   HASH_REMOVE,
		   NULL);

}

== TAB completion ==

Why suggest CREATE VARIABLE (non-temporary) if it is not supported?

postgres=# CREATE V<TAB>
VARIABLE  VIEW
postgres=# CREATE VARIABLE var AS int;
ERROR:  only temporal session variables are supported

It would be nice to have tab completion for DROP VARIABLE and LET as well:

postgres=# DROP VARIABLE <TAB>

postgres=# LET <TAB>

== Missing IF EXISTS in DROP VARIABLE ==

DROP VARIABLE IF EXISTS var;
ERROR:  syntax error at or near "EXISTS"
LINE 1: DROP VARIABLE IF EXISTS var;

               ^

Best, Jim

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 5 Dec 2025 07:50:25 +0100

Hi

> WHERE name = 'session variables';
yes, there was a bug, fixed

> LINE 1: DROP VARIABLE IF EXISTS var;

Both mentioned issues are related to the declared target of this patchset -
maximal reduction of the size.

But It not difficult to support these requested features - see the patches
0008 and 0009

Best regards

Pavel

> Best, Jim
From a6233adc8fa3c8f8530fb1cc0c82bf3846902f9b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 20:04:16 +0100
Subject: [PATCH 7/9] DISCARD TEMP

---

 
 		case DISCARD_TEMP:
 
 		default:
@@ -75,4 +77,5 @@ DiscardAll(bool isTopLevel)
 	ResetPlanCache();
 	ResetTempTableNamespace();
 	ResetSequenceCaches();
+	ResetSessionVariables();
 }
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 5a2de04e58b..474e3482c28 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -416,3 +416,23 @@ ExecuteLetStmt(ParseState *pstate,
 
 	PopActiveSnapshot();
 }
+
+/*

From 6651bc1d9758fa3a0f0fac78fc947cf474bb51d5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 4 Dec 2025 06:17:54 +0100
Subject: [PATCH 8/9] support CREATE IF NOT EXISTS and DROP IF EXISTS

---
 doc/src/sgml/ref/create_variable.sgml         | 12 +++++-
 doc/src/sgml/ref/drop_variable.sgml           | 12 +++++-
 src/backend/commands/session_variable.c       | 43 +++++++++++++------
 src/backend/parser/gram.y                     | 31 ++++++++++++-
 src/backend/tcop/utility.c                    |  2 +-
 src/include/commands/session_variable.h       |  2 +-
 src/include/nodes/parsenodes.h                |  4 +-
 .../expected/session_variables_ddl.out        |  6 +++
 .../regress/sql/session_variables_ddl.sql     |  6 +++
 9 files changed, 97 insertions(+), 21 deletions(-)

index 11b1ef74212..286bff62b6c 100644
 
  <refsynopsisdiv>
 <synopsis>
-CREATE { TEMP | TEMPORAL } VARIABLE <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable>
+CREATE { TEMP | TEMPORAL } VARIABLE  [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable>
 </synopsis>
  </refsynopsisdiv>
  <refsect1>
@@ -68,6 +68,16 @@ CREATE { TEMP | TEMPORAL } VARIABLE <replaceable class="parameter">name</replace
 
   <variablelist>
 
+   <varlistentry  id="sql-createvariable-if-not-exists">
+    <term><literal>IF NOT EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the name already exists. A notice is issued in
+      this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createvariable-name">
     <term><replaceable class="parameter">name</replaceable></term>
     <listitem>
diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml
index dede42e4ffb..5de6a737493 100644
--- a/doc/src/sgml/ref/drop_variable.sgml
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-DROP VARIABLE <replaceable class="parameter">name</replaceable>
+DROP VARIABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 </synopsis>
  </refsynopsisdiv>
 
@@ -42,6 +42,16 @@ DROP VARIABLE <replaceable class="parameter">name</replaceable>
  <refsect1>
   <title>Parameters</title>
   <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the session variable does not exist. A notice is
+      issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">name</replaceable></term>
     <listitem>
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 474e3482c28..f9fe16f4304 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -97,7 +97,7 @@ create_sessionvars_hashtables(void)
  * Returns entry of session variable specified by name
  */
 static SVariable
-search_variable(char *varname)
+search_variable(char *varname, bool missing_ok)
 {
 	SVariable	svar;
 
@@ -107,7 +107,7 @@ search_variable(char *varname)
 	svar = (SVariable) hash_search(sessionvars, varname,
 								   HASH_FIND, NULL);
 
-	if (!svar)
+	if (!svar && !missing_ok)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("session variable \"%s\" doesn't exist",
@@ -129,7 +129,7 @@ get_session_variable_type_typmod_collid(char *varname,
 {
 	SVariable	svar;
 
-	svar = search_variable(varname);
+	svar = search_variable(varname, false);
 
 	/* only owner can set content of variable */
 	*typid = svar->vartype;
@@ -149,7 +149,7 @@ GetSessionVariableWithTypecheck(char *varname,
 	SVariable	svar;
 	Datum		result;
 
-	svar = search_variable(varname);
+	svar = search_variable(varname, false);
 
 	if (svar->vartype != typid || svar->vartypmod != typmod)
 		ereport(ERROR,
@@ -186,7 +186,7 @@ SetSessionVariableWithTypecheck(char *varname,
 {
 	SVariable	svar;
 
-	svar = search_variable(varname);
+	svar = search_variable(varname, false);
 
 	if (svar->vartype != typid || svar->vartypmod != typmod)
 		ereport(ERROR,
@@ -292,10 +292,18 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 					   HASH_ENTER, &found);
 
 	if (found)
-		ereport(ERROR,
-				(errcode(ERRCODE_DUPLICATE_OBJECT),
-				 errmsg("session variable \"%s\" already exists",
-						stmt->name)));
+	{
+		if (stmt->if_not_exists)
+			ereport(NOTICE,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("session variable \"%s\" already exists, skipping",
+							stmt->name)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("session variable \"%s\" already exists",
+							stmt->name)));
+	}
 
 	namestrcpy(&svar->varname, stmt->name);
 	svar->vartype = typeid;
@@ -313,24 +321,31 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
  * Drop variable by name
  */
 void
-DropVariableByName(char *varname)
+DropVariableByName(DropSessionVarStmt *stmt)
 {
 	SVariable	svar;
 
-	svar = search_variable(varname);
+	svar = search_variable(stmt->name, stmt->missing_ok);
+	if (!svar)
+	{
+		ereport(NOTICE,
+				(errmsg("session variable \"%s\" does not exists, skipping",
+						stmt->name)));
+		return;
+	}
 
 	/* only owner can get content of variable */
 	if (svar->varowner != GetUserId() && !superuser())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("must be owner of session variable %s",
-						varname)));
+						stmt->name)));

 	if (hash_search(sessionvars,
-					   varname,
+					   stmt->name,
 					   HASH_REMOVE,
 					   NULL) == NULL)
 		elog(ERROR, "hash table corrupted");
@@ -354,7 +369,7 @@ ExecuteLetStmt(ParseState *pstate,
 	char	   *varname = query->resultVariable;
 	SVariable	svar;
 
-	svar = search_variable(varname);
+	svar = search_variable(varname, false);
 
 	/* only owner can set content of variable */
 	if (svar->varowner != GetUserId() && !superuser())
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 327ecf88712..8e3725df41c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5342,7 +5342,7 @@ create_extension_opt_item:
 /*****************************************************************************
  *
  *		QUERY :
- *				CREATE { TEMP | TEMPORARY } VARIABLE varname [AS] type
+ *				CREATE { TEMP | TEMPORARY } VARIABLE [IF NOT EXISTS ] varname [AS] type
  *
  *****************************************************************************/
 
@@ -5359,14 +5359,31 @@ CreateSessionVarStmt:
 
 					n->name = $4;
 					n->typeName = $6;
+		  | CREATE OptTemp VARIABLE IF_P NOT EXISTS ColId opt_as Typename
+					n->name = $7;
 
 /*****************************************************************************
  *
  *		QUERY :
- *				DROP VARIABLE varname
+ *				DROP VARIABLE [ IF EXISTS ] varname
  *
  *****************************************************************************/
 
@@ -5376,8 +5393,18 @@ DropSessionVarStmt:
 					DropSessionVarStmt *n = makeNode(DropSessionVarStmt);
 
 					n->name = $3;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
+			| DROP VARIABLE IF_P EXISTS ColId
+				{
+					DropSessionVarStmt *n = makeNode(DropSessionVarStmt);
+
+					n->name = $5;
+					n->missing_ok = true;
 					$$ = (Node *) n;
 				}
+
 		;
 
 /*****************************************************************************
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3f1c8cb5e89..fd911f5234f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1073,7 +1073,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 
 		case T_DropSessionVarStmt:
 			/* No event triggers for catalog less session variables */
-			DropVariableByName(((DropSessionVarStmt *) parsetree)->name);
+			DropVariableByName((DropSessionVarStmt *) parsetree);
 			break;
 
 		case T_LetStmt:
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index cc1aa7ce23b..3f07ae55aac 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -22,7 +22,7 @@
 #include "tcop/cmdtag.h"
 
 extern void CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt);
-extern void DropVariableByName(char *varname);
+extern void DropVariableByName(DropSessionVarStmt *stmt);
 
 extern Datum GetSessionVariableWithTypecheck(char *varname, Oid typid, int32 typmod, bool *isnull);
 extern void SetSessionVariableWithTypecheck(char *varname,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3decc639d81..47941d9dc99 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3592,6 +3592,7 @@ typedef struct CreateSessionVarStmt
 	NodeTag		type;
 	char	   *name;		/* the variable to create */
 	TypeName   *typeName;		/* the type of variable */
+	bool		if_not_exists;	/* just do nothing if variable already exists? */
 } CreateSessionVarStmt;
 
 /* ----------------------
@@ -3601,7 +3602,8 @@ typedef struct CreateSessionVarStmt
 typedef struct DropSessionVarStmt
 {
 	NodeTag		type;
-	char	   *name;
+	char	   *name;			/* the variable name to drop */
+	bool		missing_ok;		/* skip error of variable is missing */
 } DropSessionVarStmt;

diff --git a/src/test/regress/expected/session_variables_ddl.out b/src/test/regress/expected/session_variables_ddl.out
index f12d1f1a6ae..742728033e4 100644
--- a/src/test/regress/expected/session_variables_ddl.out
+++ b/src/test/regress/expected/session_variables_ddl.out
@@ -48,3 +48,9 @@ ERROR:  session variable "x" already exists
 DISCARD TEMP;
 -- should be ok
 CREATE TEMP VARIABLE x AS int;
+-- should be ok
+CREATE TEMP VARIABLE IF NOT EXISTS x AS int;
+NOTICE:  session variable "x" already exists, skipping
+DROP VARIABLE IF EXISTS x;
+DROP VARIABLE IF EXISTS x;
+NOTICE:  session variable "x" does not exists, skipping
diff --git a/src/test/regress/sql/session_variables_ddl.sql b/src/test/regress/sql/session_variables_ddl.sql
index c69fd4bfa47..a604f43555c 100644
--- a/src/test/regress/sql/session_variables_ddl.sql
+++ b/src/test/regress/sql/session_variables_ddl.sql
@@ -64,3 +64,9 @@ DISCARD TEMP;
 
 -- should be ok
 CREATE TEMP VARIABLE x AS int;
+
+-- should be ok
+CREATE TEMP VARIABLE IF NOT EXISTS x AS int;
+
+DROP VARIABLE IF EXISTS x;
+DROP VARIABLE IF EXISTS x;
-- 
2.52.0

From d4138e09db42494626761cb65e4b65e97d61efe3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 4 Dec 2025 18:49:06 +0100
Subject: [PATCH 9/9] use names of currently used temp variables for tab
 complete of DROP VARIABLE, LET and VARIABLE()

---
 src/backend/commands/session_variable.c       | 36 +++++++++++++++++++
 src/bin/psql/tab-complete.in.c                | 19 +++++++++-
 src/include/catalog/pg_proc.dat               |  5 +++
 .../expected/session_variables_ddl.out        | 16 +++++++++
 .../regress/sql/session_variables_ddl.sql     |  8 +++++
 5 files changed, 83 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index f9fe16f4304..97ff3e37b8c 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -21,6 +21,7 @@
 #include "commands/session_variable.h"
 #include "executor/executor.h"
 #include "executor/svariableReceiver.h"
+#include "funcapi.h"
 #include "miscadmin.h"
 #include "parser/parse_type.h"
 #include "rewrite/rewriteHandler.h"
@@ -451,3 +452,38 @@ ResetSessionVariables(void)
 	if (SVariableMemoryContext != NULL)
 		MemoryContextReset(SVariableMemoryContext);
 }
+
+/*
+ * pg_get_temporary_session_variables_names
+ *
+ * Returns list of temporary session variables. It is used by psql's
+ * tab complete for DROP VARIABLE and LET commands.
+ */
+Datum
+pg_get_temporary_session_variables_names(PG_FUNCTION_ARGS)
+{
+	InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
+
+	if (sessionvars)
+	{
+		ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+		HASH_SEQ_STATUS status;
+		SVariable	svar;
+
+		hash_seq_init(&status, sessionvars);
+
+		while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+		{
+			Datum		values[1];
+			bool		nulls[1];
+
+			values[0] = CStringGetTextDatum((NameStr(svar->varname)));
+			nulls[0] = false;
+
+			tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
+								 values, nulls);
+		}
+	}
+
+	return (Datum) 0;
+}
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 4451959b81e..bef22a331ed 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -1200,6 +1200,11 @@ Keywords_for_list_of_owner_roles, "PUBLIC"
 "  FROM pg_catalog.pg_timezone_names() "\
 " WHERE pg_catalog.quote_literal(pg_catalog.lower(name)) LIKE pg_catalog.lower('%s')"
 
+#define Query_for_list_of_temporary_session_variables \
+"SELECT varname "\
+"   FROM pg_catalog.pg_get_temporary_session_variables_names() AS varname "\
+"  WHERE varname LIKE '%s'"
+
 /* Privilege options shared between GRANT and REVOKE */
 #define Privilege_options_of_grant_and_revoke \
 "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER", \
@@ -4360,6 +4365,10 @@ match_previous_words(int pattern_id,
 	else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny, "LANGUAGE", MatchAny))
 		COMPLETE_WITH("CASCADE", "RESTRICT");
 
+	/* DROP VARIABLE */
+	else if (Matches("DROP", "VARIABLE"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_temporary_session_variables);
+
 /* EXECUTE */
 	else if (Matches("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -4800,6 +4809,8 @@ match_previous_words(int pattern_id,
 
 /* LET */
 	/* Complete LET <variable> with "=" */
+	else if (Matches("LET"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_temporary_session_variables);
 	else if (TailMatches("LET", MatchAny))
 		COMPLETE_WITH("=");
 
@@ -5292,7 +5303,7 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_vacuumables,
 										"VERBOSE",
 										"ANALYZE",
-										"ONLY");
+									"ONLY");
 	else if (Matches("VACUUM", MatchAnyN, "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_vacuumables,
 										"ANALYZE",
@@ -5359,6 +5370,12 @@ match_previous_words(int pattern_id,
 		 */
 	}
 
+/*
+ * VARIABLE fence
+ */
+	else if (TailMatches("VARIABLE", "("))
+		COMPLETE_WITH_QUERY(Query_for_list_of_temporary_session_variables);
+
 /* WITH [RECURSIVE] */
 
 	/*
index 66af2d96d67..097de5da861 100644
@@ -12612,4 +12612,9 @@
 
+# Session variables support
+{ oid => '8068', descr => 'returns names of temporary session variables',
+  proname => 'pg_get_temporary_session_variables_names', prorows => '1000', proretset => 't',
+  provolatile => 'v', proparallel => 'r', prorettype => 'text', proargtypes => '',
+  prosrc => 'pg_get_temporary_session_variables_names' },
 ]
diff --git a/src/test/regress/expected/session_variables_ddl.out b/src/test/regress/expected/session_variables_ddl.out
index 742728033e4..c8757f4cbbd 100644
--- a/src/test/regress/expected/session_variables_ddl.out
+++ b/src/test/regress/expected/session_variables_ddl.out
@@ -54,3 +54,19 @@ NOTICE:  session variable "x" already exists, skipping
 DROP VARIABLE IF EXISTS x;
 DROP VARIABLE IF EXISTS x;
 NOTICE:  session variable "x" does not exists, skipping
+CREATE TEMP VARIABLE x AS int;
+CREATE TEMP VARIABLE y AS int;
+SELECT * FROM pg_get_temporary_session_variables_names();
+ pg_get_temporary_session_variables_names 
+------------------------------------------
+ y
+ x
+(2 rows)
+
+DROP VARIABLE x;
+DROP VARIABLE y;
+SELECT * FROM pg_get_temporary_session_variables_names();
+ pg_get_temporary_session_variables_names 
+------------------------------------------
+(0 rows)
+
diff --git a/src/test/regress/sql/session_variables_ddl.sql b/src/test/regress/sql/session_variables_ddl.sql
index a604f43555c..0c51b0a2f40 100644
--- a/src/test/regress/sql/session_variables_ddl.sql
+++ b/src/test/regress/sql/session_variables_ddl.sql
@@ -70,3 +70,11 @@ CREATE TEMP VARIABLE IF NOT EXISTS x AS int;
 
 DROP VARIABLE IF EXISTS x;
 DROP VARIABLE IF EXISTS x;
+
+CREATE TEMP VARIABLE x AS int;
+CREATE TEMP VARIABLE y AS int;
+SELECT * FROM pg_get_temporary_session_variables_names();
+
+DROP VARIABLE x;
+DROP VARIABLE y;
+SELECT * FROM pg_get_temporary_session_variables_names();
-- 
2.52.0

From 2826ed43b3d0096e23f981210105235699cb76c5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 5/9] svariableReceiver

index 44aa9dd188b..c5b6f896271 100644
 
+/*

 

 
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 16dfb17b44e..c625d242b21 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2675,6 +2675,7 @@ STRLEN
 SV
 SVariableData
 SVariable
+SVariableState
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

From 5a7e363c6e02c6bbc159f794cc6875c07859968e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 18:05:03 +0100
Subject: [PATCH 6/9] LET command - assign a result of expression to the 
 session variable
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 
  <sect1 id="ddl-others">

 

index c5b6f896271..5a2de04e58b 100644
 
 /*
@@ -330,3 +335,84 @@ DropVariableByName(char *varname)
 					   NULL) == NULL)
 		elog(ERROR, "hash table corrupted");
 }
+
+/*
 
 	/*

 
+		case T_LetStmt:
 
 			/*

 
 
 	/*
 
 /*
 
+/*
+ * transformLetStmt -

 

 			/*
 
 			/*

 			/*
 
 			/*
 
 			/*
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index 022b9df405a..4451959b81e 100644
 
+/* LET */

 

 

 
index c625d242b21..0ddda5dc4d2 100644
@@ -1551,6 +1551,7 @@ LargeObjectDesc

From 2c49a5a1ef377790db29248928e423886b0567aa Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 20:42:56 +0100
Subject: [PATCH 3/9] collect session variables used in plan and assign paramid

* parallel execution

plan
---

 
+		/*
index 248e178220b..44aa9dd188b 100644
 
+/*
 
 	/*

+	/*

+/*

 
 
 				/*

 	return false;
 }

 
 	/*

From f16be821ac70b296d980e93259de92adf22fdb27 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 15:28:59 +0100
Subject: [PATCH 2/9] parsing session variable fences

 
  <sect1 id="ddl-others">
 
+	/*
 
index a7381df255a..248e178220b 100644
 
+/*

 

 

 

 

 
 
+variable_fence:
+			VARIABLE '(' ColId ')'
+				{
 
+		case T_VariableFence:
 
+/*

 

 
 
+/*
 
 typedef struct Param

 
 	/*
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5f259dd7c8f..f732ecbcf86 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3226,6 +3226,7 @@ ValidatorValidateCB
 ValuesScan
 ValuesScanState
 Var
+VariableFence
 VarBit
 VarChar
 VarParamState
-- 
2.52.0

From 4ee182e6bbbf018e75f6120527c3d1b0d5e416b0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 22 Nov 2025 06:40:46 +0100
Subject: [PATCH 4/9] fill an auxiliary buffer with values of session 
 variables used in query

 
+	/*
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index f732ecbcf86..16dfb17b44e 100644
@@ -2733,6 +2733,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
-- 
2.52.0

From 900dd6a9f10063edd0bbe83bbeb9a12e5894800d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 19 Nov 2025 19:36:07 +0100
Subject: [PATCH 1/9] CREATE VARIABLE, DROP VARIABLE

---
 src/backend/commands/session_variable.c       | 222 ++++++++++++++++++
 src/backend/tcop/utility.c                    |  26 ++
 21 files changed, 723 insertions(+), 7 deletions(-)

 
 
index 00000000000..a7381df255a
@@ -0,0 +1,222 @@
+	if (!svar->typbyval && !svar->isnull)
+	if (hash_search(sessionvars,
+					   NULL) == NULL)
+		elog(ERROR, "hash table corrupted");

 

 
+
 				/*

+		case T_CreateSessionVarStmt:
+		case T_DropSessionVarStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		default:
 			elog(WARNING, "unrecognized node type: %d",
 				 (int) nodeTag(parsetree));
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 20d7a65c614..022b9df405a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -1364,6 +1364,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"USER", Query_for_list_of_roles, NULL, NULL, Keywords_for_user_thing},
 	{"USER MAPPING FOR", NULL, NULL, NULL},
 	{"VIEW", NULL, NULL, &Query_for_list_of_views},
+	{"VARIABLE", NULL, NULL, NULL, NULL, THING_NO_CREATE},
 	{NULL}						/* end of list */
 };

+/* CREATE VARIABLE */
 
+
 
 # ----------
index c1ad80a418d..5f259dd7c8f 100644
@@ -563,6 +563,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
@@ -667,6 +668,7 @@ DropBehavior
 DropOwnedStmt
 DropReplicationSlotCmd
 DropRoleStmt
+DropSessionVarStmt
 DropStmt
 DropSubscriptionStmt
 DropTableSpaceStmt
@@ -2671,6 +2673,8 @@ SSL_CTX
 STARTUPINFO
 STRLEN
 SV
+SVariableData
+SVariable
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

---

From: Jim Jones <[email-masked]>
Date: Sat, 6 Dec 2025 12:29:48 +0100

> Both mentioned issues are related to the declared target of this
> patchset - maximal reduction of the size.

Nice, the memory is now being freed after a DROP VARIABLE and the tab
completion for LET and DROP VARIABLE works:

var  x
postgres=# LET var = repeat('🐘', 200000000);
 763 MB
(1 row)

var  x

-- DROP VARIABLE IF EXISTS also works:

postgres=# DROP VARIABLE IF EXISTS x;
DROP VARIABLE

Some comments and a few minor issues:

== session_variables_ddl.sql ==

1) duplicate tests

...

2) Typos in some comments "should to fail" > "should fail"

== Error messages ==

3) It is not possible to create a VIEW that depends on a session
variable, which makes perfect sense.

postgres=# CREATE VIEW v AS SELECT variable(var);
ERROR:  session variable "var" cannot be referenced in a persistent object

The error message is clear, but in case of TEMPORARY VIEWS it gets a bit
misleading, since a TEMPORARY VIEW is not a persistent object:

postgres=# CREATE TEMPORARY VIEW tv AS SELECT variable(var);
ERROR:  session variable "var" cannot be referenced in a persistent object

Perhaps something more generic? For instance:

errmsg("session variable \"%s\" cannot be referenced in catalog
objects", param->paramvarname)

== ddl.sgml ==

4) There are invalid examples

-- No schema qualified VARIABLE is supported:

CREATE VARIABLE public.current_user_id AS integer;

-- Only TEMPORARY VARIABLES are supported:
CREATE VARIABLE var1 AS date;

5) The term "variable fence" is introduced and emphasised, but not
described.

6) There is a slight repetition regarding the variable's isolation

"This value is private to each session .."
"The value of a session variable is local to the current session"

I would write something along these lines:

"Session variables are temporary database objects that can hold a value.
A session variable can be created using the CREATE VARIABLE command and
can only be accessed by its owner. The value of a session variable is
stored in session memory and is private to each session. It is
automatically released when the session ends.

In a query, a session variable can only be referenced using the special
<literal>VARIABLE(varname)</literal> syntax. This avoids any risk of
collision between variable names and column names.

You set the value of a session variable with the <command>LET</command>
statement and retrieve it with <command>SELECT</command>:

<programlisting>
LET var1 = current_date;
SELECT VARIABLE(var1);
    var1
------------
 2025-12-06
(1 row)
</programlisting>

By default, retrieving a session variable returns
<literal>NULL</literal> unless it has been set in the current session
using the <command>LET</command> command. Session variables are not
transactional: changes to their values persist even if the transaction
is rolled back, similar to variables in procedural languages."

== let.sgml ==

7) Invalid example (missing TEMP/TEMPORARY)

CREATE VARIABLE myvar AS integer;

8) Typo in the Synopsis (TEMPORAL should be TEMPORARY):

CREATE { TEMP | TEMPORAL } VARIABLE  [ IF NOT EXISTS ] name [ AS ] data_type

9) In the description it says "The CREATE VARIABLE command creates a
temporal session variable.", but isn't the command now CREATE
TEMP/TEMPORARY VARIABLE? Is it ok to remove the TEMPORARY in the
description?

10) The description includes also info regarding SELECT and LET. Since
this page is about CREATE TEMPORARY VARIABLE, I guess it is out of place?

Thanks!

Best, Jim

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 8 Dec 2025 09:15:50 +0100

Hi

fixed

fixed

> objects", param->paramvarname)

changed like you proposed

fixed

> 5) The term "variable fence" is introduced and emphasised, but not

?? There is already

+++ b/doc/src/sgml/ddl.sgml
@@ -5676,6 +5676,17 @@ EXPLAIN SELECT count(*) FROM measurement WHERE
logdate &gt;= DATE '2008-01-01';
     The session variable holds value in session memory.  This value is
private
     to each session and is released when the session ends.
    </para>
+
+   <para>
+    In an query the session variable can be used only inside
+    <firstterm>variable fence</firstterm>. This is special syntax for
+    session variable identifier, and can be used only for session variable
+    identifier. The special syntax for accessing session variables removes
+    risk of collisions between variable identifiers and column names.
+<programlisting>
+SELECT VARIABLE(current_user_id);
+</programlisting>
+   </para>
   </sect1>

done

done

done

fixed

fixed

> TEMP/TEMPORARY VARIABLE? Is it ok to remove the TEMPORARY in the

Although the TEMP clause in CREATE VARIABLE is mandatory now, I prefer to
look on this
like some temporary limit - so I don't libe to rename CREATE VARIABLE to
CREATE TEMP VARIABLE.

I changed this part to

  <para>
   The <command>CREATE VARIABLE</command> command creates a session
   variable. Currently only temporary session variables are supported,
   and then the keyword <literal>TEMPORARY</literal> is required.
  </para>

> this page is about CREATE TEMPORARY VARIABLE, I guess it is out of place?
I am sorry, I don't understand this point. Can you describe it?

For the current patchset I wrote initial support for transactional DDL for
session variables - patch 0010 and 0011.
Now, the DDL is blocked in read only transactions, parallel worker and
inside recovery.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 8 Dec 2025 14:57:51 +0100

Hi

I found a "native" way to disallow a usage of custom types - it is the same
as checking used by virtual columns - and virtual columns is another
feature where custom types are not allowed.

Regards

Pavel
From fbe4dcb12cdc4068f877dade2d0aafd072431b7b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 6 Dec 2025 07:35:30 +0100
Subject: [PATCH 10/11] transactional DDL - CREATE VARIABLE, DROP VARIABLE

---
 src/backend/access/transam/xact.c             |   5 +
 src/backend/commands/session_variable.c       | 217 ++++++++++++++++--
 src/include/commands/session_variable.h       |   2 +
 .../expected/session_variables_ddl.out        |  38 +++
 .../regress/sql/session_variables_ddl.sql     |  21 ++
 5 files changed, 258 insertions(+), 25 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 092e197eba3..8a80e9c00af 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -39,6 +39,7 @@
 #include "commands/async.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
+#include "commands/session_variable.h"
 #include "common/pg_prng.h"
 #include "executor/spi.h"
 #include "libpq/be-fsstubs.h"
@@ -2333,6 +2334,9 @@ CommitTransaction(void)
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
+	/* remove stacked session variables */
+	AtPreEOXact_SessionVariables(true);
+
 	/*
 	 * Insert notifications sent by NOTIFY commands into the queue.  This
 	 * should be late in the pre-commit sequence to minimize time spent
@@ -2937,6 +2941,7 @@ AbortTransaction(void)
 	AtAbort_Portals();
 	smgrDoPendingSyncs(false, is_parallel_worker);
 	AtEOXact_LargeObject(false);
+	AtPreEOXact_SessionVariables(false);
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index d06e4b91bcb..28dd23172f6 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -54,6 +54,11 @@ typedef struct SVariableData
 
 	int16		typlen;
 	bool		typbyval;
+
+	struct SVariableData *prev;
+	bool		stacked;
+	LocalTransactionId created_lxid;
+	LocalTransactionId dropped_lxid;
 } SVariableData;
 
 typedef SVariableData *SVariable;
@@ -62,6 +67,14 @@ static HTAB *sessionvars = NULL;	/* hash table for session variables */

+/*
+ * When we to remove committed dropped variables or uncommitted
+ * created variables from sessionvars tab. created_or_dropped_lxid
+ * is transaction id of transaction when some of DROP or CREATE variable
+ * was executed.
+ */
+static LocalTransactionId created_or_dropped_lxid = InvalidLocalTransactionId;
+
 /*
  * Create the hash table for storing session variables.
  */
@@ -103,6 +116,14 @@ search_variable(char *varname, bool missing_ok)
 	svar = (SVariable) hash_search(sessionvars, varname,
 								   HASH_FIND, NULL);
 
+	/* Session variable can be dropped inside current transaction */
+	if (svar && svar->dropped_lxid != InvalidLocalTransactionId)
+	{
+		Assert(created_or_dropped_lxid == MyProc->vxid.lxid);
+		Assert(svar->dropped_lxid == MyProc->vxid.lxid);
+		svar = NULL;
+	}
+
 	if (!svar && !missing_ok)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -235,6 +256,7 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 	Oid			typcollation;
 	Oid			varowner = GetUserId();
 	SVariable	svar;
+	SVariable	prev_svar = NULL;
 	bool		found;
 	int16		typlen;
 	bool		typbyval;
@@ -279,19 +301,37 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 
 	if (found)
 	{
-		if (stmt->if_not_exists)
+		if (svar->dropped_lxid == InvalidLocalTransactionId)
 		{
-			ereport(NOTICE,
-					(errcode(ERRCODE_DUPLICATE_OBJECT),
-					 errmsg("session variable \"%s\" already exists, skipping",
-							stmt->name)));
-			return;
+			if (stmt->if_not_exists)
+			{
+				ereport(NOTICE,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("session variable \"%s\" already exists, skipping",
+								stmt->name)));
+				return;
+			}
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("session variable \"%s\" already exists",
+								stmt->name)));
 		}
 		else
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_OBJECT),
-					 errmsg("session variable \"%s\" already exists",
-							stmt->name)));
+		{
+			MemoryContext oldcxt;
+
+			Assert(created_or_dropped_lxid == MyProc->vxid.lxid);
+			Assert(svar->dropped_lxid == MyProc->vxid.lxid);
+
+			oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+			prev_svar = palloc(sizeof(SVariableData));
+			memcpy(prev_svar, svar, sizeof(SVariableData));
+			prev_svar->stacked = true;
+			memset(svar, 0, sizeof(SVariableData));
+
+			MemoryContextSwitchTo(oldcxt);
+		}
 	}
 
 	namestrcpy(&svar->varname, stmt->name);
@@ -304,6 +344,12 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 
 	svar->value = (Datum) 0;
 	svar->isnull = true;
+
+	svar->prev = prev_svar;
+	svar->stacked = false;
+	svar->dropped_lxid = InvalidLocalTransactionId;
+	svar->created_lxid = MyProc->vxid.lxid;
+	created_or_dropped_lxid = MyProc->vxid.lxid;
 }
 
 /*
@@ -338,14 +384,129 @@ DropVariableByName(DropSessionVarStmt *stmt)
 				 errmsg("must be owner of session variable %s",
 						stmt->name)));
 
-	if (!svar->typbyval && !svar->isnull)
+	svar->dropped_lxid = MyProc->vxid.lxid;
+	created_or_dropped_lxid = MyProc->vxid.lxid;
+}
+
+static void
+free_svar_value(SVariable svar)
+{
+	if (!svar->isnull && !svar->typbyval)
 		pfree(DatumGetPointer(svar->value));
+}
+
+static void
+free_stacked_svars(SVariable svar)
+{
+	while (svar)
+	{
+		SVariable	current = svar;
+
+		free_svar_value(current);
+		svar = current->prev;
+		pfree(current);
+	}
+}
+
+/*
+ * remove dropped committed entries or created uncommitted entries
+ * from hash table.
+ */
+void
+AtPreEOXact_SessionVariables(bool isCommit)
+{
+	if (created_or_dropped_lxid != InvalidLocalTransactionId)
+	{
+		HASH_SEQ_STATUS status;
+		SVariable	svar;
 
-	if (hash_search(sessionvars,
-					   stmt->name,
-					   HASH_REMOVE,
-					   NULL) == NULL)
-		elog(ERROR, "hash table corrupted");
+		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))
+			{
+				Assert((svar->dropped_lxid == InvalidLocalTransactionId) ||
+						(svar->dropped_lxid == MyProc->vxid.lxid));
+
+				Assert((svar->created_lxid == InvalidLocalTransactionId) ||
+						(svar->created_lxid == MyProc->vxid.lxid));
+
+				if (isCommit)
+				{
+					if (svar->dropped_lxid == MyProc->vxid.lxid)
+					{
+						free_stacked_svars(svar->prev);
+						free_svar_value(svar);
+
+						(void) hash_search(sessionvars,
+										   NameStr(svar->varname),
+										   HASH_REMOVE,
+										   NULL);
+						svar = NULL;
+					}
+					else
+					{
+						free_stacked_svars(svar->prev);
+						svar->prev = NULL;
+						svar->created_lxid = InvalidLocalTransactionId;
+					}
+				}
+				else
+				{
+					SVariable	iter;
+
+					/*
+					 * We have to search value the oldest svar in the stack. If it is just dropped,
+					 * then we revert dropped flag. If it is created in current transaction, then
+					 * we remove this svar too.
+					 */
+					iter = svar;
+					while (iter->prev)
+					{
+						SVariable	current = iter;
+
+						free_svar_value(current);
+
+						iter = current->prev;
+
+						if (current->stacked)
+							pfree(current);
+					}
+
+					if (iter->created_lxid == MyProc->vxid.lxid)
+					{
+						free_svar_value(iter);
+						if (iter->stacked)
+							pfree(iter);
+
+						(void) hash_search(sessionvars,
+										   NameStr(svar->varname),
+										   HASH_REMOVE,
+										   NULL);
+					}
+					else
+					{
+						if (iter->stacked)
+						{
+							memcpy(svar, iter, sizeof(SVariableData));
+							svar->stacked = false;
+							pfree(iter);
+						}
+
+						/* revert dropped flag */
+						svar->dropped_lxid = InvalidLocalTransactionId;
+					}
+				}
+			}
+		}
+
+		created_or_dropped_lxid = InvalidLocalTransactionId;
+	}
 }
 
 /*
@@ -430,23 +591,29 @@ ExecuteLetStmt(ParseState *pstate,
 }
 
 /*
- * Fast drop of the complete content of the session variables hash table, and
- * cleanup of any list that wouldn't be relevant anymore.
  * This is used by the DISCARD TEMP.
  */
 void
 ResetSessionVariables(void)
 {
-	/* destroy hash table and reset related memory context */
+	/* mark all session variables as dropped */
 	if (sessionvars)
 	{
-		hash_destroy(sessionvars);
-		sessionvars = NULL;
-	}
+		HASH_SEQ_STATUS status;
+		SVariable	svar;
+		bool		found = false;
 
-	/* release memory allocated by session variables */
-	if (SVariableMemoryContext != NULL)
-		MemoryContextReset(SVariableMemoryContext);
+		hash_seq_init(&status, sessionvars);
+
+		while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+		{
+			svar->dropped_lxid = MyProc->vxid.lxid;
+			found = true;
+		}
+
+		if (found)
+			created_or_dropped_lxid = MyProc->vxid.lxid;
+	}
 }
 
 /*
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 3f07ae55aac..1218c566767 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -39,4 +39,6 @@ extern void ExecuteLetStmt(ParseState *pstate, LetStmt *stmt, ParamListInfo para
 
 extern void ResetSessionVariables(void);
 
+extern void AtPreEOXact_SessionVariables(bool isCommit);
+
 #endif
diff --git a/src/test/regress/expected/session_variables_ddl.out b/src/test/regress/expected/session_variables_ddl.out
index b8d6f629596..c10680512ce 100644
--- a/src/test/regress/expected/session_variables_ddl.out
+++ b/src/test/regress/expected/session_variables_ddl.out
@@ -70,3 +70,41 @@ SELECT * FROM pg_get_temporary_session_variables_names();
 ------------------------------------------
 (0 rows)
 
+CREATE TEMP VARIABLE x AS varchar;
+LET x = 'Hi';
+BEGIN;
+DROP VARIABLE x;
+CREATE TEMP VARIABLE x AS varchar;
+LET x = 'Hello';
+SELECT VARIABLE(x);
+   x   
+-------
+ Hello
+(1 row)
+
+COMMIT;
+SELECT VARIABLE(x);
+   x   
+-------
+ Hello
+(1 row)
+
+LET x = 'Hi';
+BEGIN;
+DROP VARIABLE x;
+CREATE TEMP VARIABLE x AS varchar;
+LET x = 'Hello';
+SELECT VARIABLE(x);
+   x   
+-------
+ Hello
+(1 row)
+
+ROLLBACK;
+SELECT VARIABLE(x);
+ x  
+----
+ Hi
+(1 row)
+
+DROP VARIABLE x;
diff --git a/src/test/regress/sql/session_variables_ddl.sql b/src/test/regress/sql/session_variables_ddl.sql
index 02af365338f..9e79feed92b 100644
--- a/src/test/regress/sql/session_variables_ddl.sql
+++ b/src/test/regress/sql/session_variables_ddl.sql
@@ -78,3 +78,24 @@ SELECT * FROM pg_get_temporary_session_variables_names();
 DROP VARIABLE x;
 DROP VARIABLE y;
 SELECT * FROM pg_get_temporary_session_variables_names();
+
+CREATE TEMP VARIABLE x AS varchar;
+LET x = 'Hi';
+BEGIN;
+DROP VARIABLE x;
+CREATE TEMP VARIABLE x AS varchar;
+LET x = 'Hello';
+SELECT VARIABLE(x);
+COMMIT;
+SELECT VARIABLE(x);
+
+LET x = 'Hi';
+BEGIN;
+DROP VARIABLE x;
+CREATE TEMP VARIABLE x AS varchar;
+LET x = 'Hello';
+SELECT VARIABLE(x);
+ROLLBACK;
+SELECT VARIABLE(x);
+
+DROP VARIABLE x;
-- 
2.52.0

From 293e01b383162a45dda56d2f8c393a286465dfc2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 20:04:16 +0100
Subject: [PATCH 07/11] DISCARD TEMP

---

 
 		case DISCARD_TEMP:
 
 		default:
@@ -75,4 +77,5 @@ DiscardAll(bool isTopLevel)
 	ResetPlanCache();
 	ResetTempTableNamespace();
 	ResetSequenceCaches();
+	ResetSessionVariables();
 }
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index e791196e559..33f6cebf38c 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -409,3 +409,23 @@ ExecuteLetStmt(ParseState *pstate,
 
 	PopActiveSnapshot();
 }
+
+/*
 
index e34e70022bc..5db330b86f5 100644
index 77af9de0d7c..31cca6530fe 100644

From d26c079fd056b895149b96dace10f74eac41bb10 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 4 Dec 2025 06:17:54 +0100
Subject: [PATCH 08/11] support CREATE IF NOT EXISTS and DROP IF EXISTS

---
 doc/src/sgml/ref/create_variable.sgml         | 12 ++++-
 doc/src/sgml/ref/drop_variable.sgml           | 12 ++++-
 src/backend/commands/session_variable.c       | 46 +++++++++++++------
 src/backend/parser/gram.y                     | 31 ++++++++++++-
 src/backend/tcop/utility.c                    |  2 +-
 src/include/commands/session_variable.h       |  2 +-
 src/include/nodes/parsenodes.h                |  4 +-
 .../expected/session_variables_ddl.out        |  6 +++
 .../regress/sql/session_variables_ddl.sql     |  6 +++
 9 files changed, 100 insertions(+), 21 deletions(-)

index 1315b1248c7..def368fc237 100644
 
  <refsynopsisdiv>
 <synopsis>
-CREATE { TEMP | TEMPORARY } VARIABLE <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable>
+CREATE { TEMP | TEMPORARY } VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable>
 </synopsis>
  </refsynopsisdiv>
  <refsect1>
@@ -69,6 +69,16 @@ CREATE { TEMP | TEMPORARY } VARIABLE <replaceable class="parameter">name</replac
 
   <variablelist>

  <refsynopsisdiv>
 <synopsis>
 
index 33f6cebf38c..70456fe9f7f 100644
@@ -92,7 +92,7 @@ create_sessionvars_hashtables(void)
 
@@ -102,7 +102,7 @@ search_variable(char *varname)
 	svar = (SVariable) hash_search(sessionvars, varname,
 								   HASH_FIND, NULL);
 
-	if (!svar)
+	if (!svar && !missing_ok)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("session variable \"%s\" doesn't exist",
@@ -124,7 +124,7 @@ get_session_variable_type_typmod_collid(char *varname,
 {
 	SVariable	svar;

 	/* only owner can set content of variable */
 	*typid = svar->vartype;
@@ -144,7 +144,7 @@ GetSessionVariableWithTypecheck(char *varname,
 	SVariable	svar;
 	Datum		result;

 	if (svar->vartype != typid || svar->vartypmod != typmod)
 		ereport(ERROR,
@@ -181,7 +181,7 @@ SetSessionVariableWithTypecheck(char *varname,
 {
 	SVariable	svar;

 	if (svar->vartype != typid || svar->vartypmod != typmod)
 		ereport(ERROR,
@@ -277,10 +277,21 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 					   HASH_ENTER, &found);
 
 	if (found)
-		ereport(ERROR,
 
 	namestrcpy(&svar->varname, stmt->name);
 	svar->vartype = typeid;
@@ -298,7 +309,7 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
  * Drop variable by name
  */
 void
-DropVariableByName(char *varname)
+DropVariableByName(DropSessionVarStmt *stmt)
 {
 	SVariable	svar;
 
@@ -310,20 +321,27 @@ DropVariableByName(char *varname)
 	PreventCommandIfParallelMode("DROP VARIABLE");
 	PreventCommandDuringRecovery("DROP VARIABLE");

 
 
 	if (hash_search(sessionvars,
-					   varname,
+					   stmt->name,
 					   HASH_REMOVE,
 					   NULL) == NULL)
 		elog(ERROR, "hash table corrupted");
@@ -347,7 +365,7 @@ ExecuteLetStmt(ParseState *pstate,
 	char	   *varname = query->resultVariable;
 	SVariable	svar;

 
 
 					n->name = $4;
 					n->typeName = $6;

 
 					n->name = $3;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}

 
 		case T_LetStmt:

 

index 5db330b86f5..c935d75fa01 100644
index 31cca6530fe..013d4fe4625 100644
 
 -- should be ok

From b0db6011b23fcad02a4c5d76df0e1ddc4831399b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
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(+)

index 8a80e9c00af..57ee8da6d1f 100644
@@ -5208,6 +5208,8 @@ CommitSubTransaction(void)
@@ -5377,6 +5379,8 @@ AbortSubTransaction(void)
index 28dd23172f6..a27a91c2362 100644
 
+#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

From ace20abfe3cd631c6a1d7e88d1895e3e1ad2d380 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 4 Dec 2025 18:49:06 +0100
Subject: [PATCH 09/11] use names of currently used temp variables for tab
 complete of DROP VARIABLE, LET and VARIABLE()

---

index 70456fe9f7f..d06e4b91bcb 100644
@@ -447,3 +448,38 @@ ResetSessionVariables(void)

+	/* DROP VARIABLE */
 
 /* LET */

+/*
+ * VARIABLE fence
+ */
 
 	/*
index fd9448ec7b9..ac08a4de8fe 100644
 
index c935d75fa01..b8d6f629596 100644
index 013d4fe4625..02af365338f 100644
 
 DROP VARIABLE x;

From 40764b31b8762d42e8a08302e7224ba466aaa4bc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 18:05:03 +0100
Subject: [PATCH 06/11] LET command - assign a result of expression to the 
 session variable
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 doc/src/sgml/ddl.sgml                         |  22 ++++
 25 files changed, 619 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index dd9bc69cec7..3dc7c07eab6 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5679,10 +5679,32 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     <literal>VARIABLE(varname)</literal> syntax. This avoids any risk of
     collision between variable names and column names.
    </para>
+
+   <para>
+    You set the value of a session variable with the <command>LET</command>
+    statement and retrieve it with <command>SELECT</command>:
+<programlisting>
+CREATE TEMP VARIABLE var1 AS date;
+LET var1 = current_date;
+SELECT VARIABLE(var1);
+</programlisting>
+
+    or
+
 <programlisting>
+CREATE TEMP VARIABLE current_user_id AS integer;
+LET current_user_id = (SELECT id FROM users WHERE usename = session_user);
 SELECT VARIABLE(current_user_id);
 </programlisting>
    </para>
+
+   <para>
+    By default, retrieving a session variable returns
+    <literal>NULL</literal> unless it has been set in the current session
+    using the <command>LET</command> command. Session variables are not
+    transactional: changes to their values persist even if the transaction
+    is rolled back, similar to variables in procedural languages
+   </para>
  </sect1>
 
  <sect1 id="ddl-others">
index 4e8c1940252..1315b1248c7 100644
@@ -108,6 +108,8 @@ CREATE { TEMP | TEMPORARY } VARIABLE <replaceable class="parameter">name</replac
 
@@ -127,6 +129,7 @@ CREATE TEMPORARY VARIABLE var1 AS date;

 
 
index 00000000000..33ee42d3f20
+CREATE TEMPORARY VARIABLE myvar AS integer;
index 4f277b6d31b..e791196e559 100644
@@ -16,13 +16,18 @@
 
 #include "catalog/pg_language.h"
 
 /*
  * The session variables are stored in the backend's private memory (data,
@@ -323,3 +328,84 @@ DropVariableByName(char *varname)
 					   NULL) == NULL)
index 5147be99d14..e16c82c2396 100644
 
 	/*

 
+		case T_LetStmt:
 
 			/*

 
 
 	/*
 
 /*
 
+/*
+ * transformLetStmt -

 

 			/*
 
 			/*

 			/*
 
 			/*
 
 			/*
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

+/* LET */

 

 

index 89ef60715fe..9bff0ecbc08 100644
 
index 23ff5dc2281..131b2da7bd9 100644

From 8febf1c7ee274946100836053bd73ca1c9fe17db Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 05/11] svariableReceiver

diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 17586dba921..4f277b6d31b 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -166,6 +166,56 @@ GetSessionVariableWithTypecheck(char *varname,
 	return result;
 }
 
+/*

 

 
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 40b920d91e3..23ff5dc2281 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2671,6 +2671,7 @@ STRLEN
 SV
 SVariableData
 SVariable
+SVariableState
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

From 4286b527cccd81dcb6f6c3a569a160add91321f4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 22 Nov 2025 06:40:46 +0100
Subject: [PATCH 04/11] fill an auxiliary buffer with values of session 
 variables used in query

 
+	/*
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index 00000000000..89ef60715fe
+ERROR:  session variable "temp_var01" cannot be referenced in a catalog object
index 0f3528b5c09..40b920d91e3 100644
@@ -2729,6 +2729,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
-- 
2.52.0

From d96d36671f58d27d415b1bed69d18dbe883c53b3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 20:42:56 +0100
Subject: [PATCH 03/11] collect session variables used in plan and assign
 paramid

* parallel execution

plan
---

index 8e70a85a3f7..22c5372ad80 100644
 
+		/*
+		* catalog less session variable variable cannot be used in persistent
+		* catalog based object.
+		*/
+		if (param->paramkind == PARAM_VARIABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("session variable \"%s\" cannot be referenced in a catalog object",
+							param->paramvarname)));
+
 		/* A parameter must depend on the parameter's datatype */
 		add_object_address(TypeRelationId, param->paramtype, 0,
 						   context->addrs);
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 62feb49d639..17586dba921 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -127,6 +127,45 @@ get_session_variable_type_typmod_collid(char *varname,
 	*collid = svar->varcollation;
 }
 
+/*
index fd77334e5fd..5147be99d14 100644
 
 	/*

+	/*

+/*

 
 
 				/*

 	return false;
 }

 
 	/*

From e9a2092800f9bfd00789e9cf1ba9a7bc1604da8d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 15:28:59 +0100
Subject: [PATCH 02/11] parsing session variable fences

 doc/src/sgml/ddl.sgml                   |  10 ++
 16 files changed, 228 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 387a6f52dc0..dd9bc69cec7 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5673,6 +5673,16 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     variable is stored in session memory and is private to each session. It is
     automatically released when the session ends.
    </para>
+
+   <para>
+    In a query, a session variable can only be referenced using the special
+    <literal>VARIABLE(varname)</literal> syntax. This avoids any risk of
+    collision between variable names and column names.
+   </para>
+<programlisting>
+SELECT VARIABLE(current_user_id);
+</programlisting>
+   </para>
  </sect1>
 
  <sect1 id="ddl-others">
 
+	/*
 
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 9f77b2cce47..62feb49d639 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -106,6 +106,27 @@ search_variable(char *varname)
 	return svar;
 }
 
+/*

 

 

 

 

 
 
+variable_fence:
+			VARIABLE '(' ColId ')'
+				{
 
+		case T_VariableFence:
 
+/*

 

 
 
+/*
 
 typedef struct Param

 
 	/*
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 00eadcbd348..0f3528b5c09 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3222,6 +3222,7 @@ ValidatorValidateCB
 ValuesScan
 ValuesScanState
 Var
+VariableFence
 VarBit
 VarChar
 VarParamState
-- 
2.52.0

From 8048c0ed51fdcdfb345c33552a702b3203b7a108 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 19 Nov 2025 19:36:07 +0100
Subject: [PATCH 01/11] CREATE VARIABLE, DROP VARIABLE

---
 doc/src/sgml/ref/create_variable.sgml         | 133 +++++++++++
 src/backend/commands/session_variable.c       | 215 ++++++++++++++++++
 21 files changed, 714 insertions(+), 7 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 09ad8400fd0..387a6f52dc0 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5655,6 +5655,26 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>
 
+ <sect1 id="ddl-session-variables">
+   <title>Session Variables</title>
+
+   <indexterm zone="ddl-session-variables">
+    <primary>Session variables</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>session variable</primary>
+   </indexterm>
+
+   <para>
+    Session variables are temporary database objects that can hold a value.
+    A session variable can be created by the <command>CREATE VARIABLE</command>
+    command and can only be accessed by its owner. The value of a session
+    variable is stored in session memory and is private to each session. It is
+    automatically released when the session ends.
+   </para>
+ </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>

index 00000000000..4e8c1940252
@@ -0,0 +1,133 @@
+CREATE { TEMP | TEMPORARY } VARIABLE <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable>
+   The <command>CREATE VARIABLE</command> command creates a session
+   variable. Currently only temporary session variables are supported,
+   and then the keyword <literal>TEMPORARY</literal> is required.
index 00000000000..9f77b2cce47
@@ -0,0 +1,215 @@
+	 * Current implementation is not catalog based, but we expect catalog
+	 * based implementation for future, so we force same limits.
+	PreventCommandIfReadOnly("CREATE VARIABLE");
+	PreventCommandIfParallelMode("CREATE VARIABLE");
+	PreventCommandDuringRecovery("CREATE VARIABLE");
+	if (typeid >= FirstUnpinnedObjectId)
+				errmsg("session variable cannot have a user-defined type"),
+				errdetail("Session variables that make use of user-defined types are not yet supported."));
+	 * Current implementation is not catalog based, but we expect catalog
+	 * based implementation for future, so we force same limits.
+	PreventCommandIfReadOnly("DROP VARIABLE");
+	PreventCommandIfParallelMode("DROP VARIABLE");
+	PreventCommandDuringRecovery("DROP VARIABLE");

 

 
+
 				/*

 
 
+/* CREATE VARIABLE */
 
+
index 00000000000..e34e70022bc
 
 # ----------
index 00000000000..77af9de0d7c
index 205b282f6b4..00eadcbd348 100644
@@ -2667,6 +2669,8 @@ SSL_CTX
 STARTUPINFO
 STRLEN
 SV
+SVariableData
+SVariable
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 9 Dec 2025 06:51:43 +0100

Hi

I fixed two issues

- broken regress test - test of blocking usage of domains
- typo in ruleutils.c s/VARIABLES/VARIABLE/

Best regards

Pavel
From a69b5a795af87792e37b8f4d2e9970ffb587307f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 6 Dec 2025 07:35:30 +0100
Subject: [PATCH 10/11] transactional DDL - CREATE VARIABLE, DROP VARIABLE

Generally PostgreSQL DDL statements are transactional. We can implement transactional behave
for ( CREATE | DROP ) VARIABLE too. Implementation is almost simple. DROP just set dropped_lxid,
CREATE set created_lxid and moves previous entry of related sessionvars hashtab to stack. At the
end of transaction for commit we just clean this stack. For rollback we do copy the bootom value
from the stack and again clean stack.
---
 src/backend/access/transam/xact.c             |   5 +
 src/backend/commands/session_variable.c       | 217 ++++++++++++++++--
 src/include/commands/session_variable.h       |   2 +
 .../expected/session_variables_ddl.out        |  38 +++
 .../regress/sql/session_variables_ddl.sql     |  21 ++
 5 files changed, 258 insertions(+), 25 deletions(-)

index 6430455c0a6..76121b24363 100644
 
 	int16		typlen;
 	bool		typbyval;
+

 
+/*

 	if (found)
 	{

 	svar->value = (Datum) 0;
 	svar->isnull = true;
+
 
 /*

 
 /*
 
 /*

 /*

index 758ce582fca..2d00471da96 100644
 
index c4cbfd17169..7335f15ed39 100644

From bea76596646bf7dac23eaaba9996d5441ecc6eb9 Mon Sep 17 00:00:00 2001

---

index 2e3ce5c4017..6430455c0a6 100644

+	/* DROP VARIABLE */
 
 /* LET */

+/*
+ * VARIABLE fence
+ */
 
 	/*
 
index 9f5b088de72..758ce582fca 100644
index 60f78671e3b..c4cbfd17169 100644
 
 DROP VARIABLE x;

From d0fa00af87990811012d5201ee1b25e01d907485 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 20:04:16 +0100
Subject: [PATCH 07/11] DISCARD TEMP

---

 
 		case DISCARD_TEMP:
 
 		default:
index bfc4191d954..5cc19216f6e 100644
 
 	PopActiveSnapshot();
 }
+
+/*
 
index 45c2d27ab44..c36febd894e 100644
index 34f34dd898f..7fd739d6677 100644

From 9ab5db5bcf9f30525cbe690080f40d017ea0447b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 4 Dec 2025 06:17:54 +0100
Subject: [PATCH 08/11] support CREATE IF NOT EXISTS and DROP IF EXISTS

---

  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

  <refsynopsisdiv>
 <synopsis>
 
index 5cc19216f6e..2e3ce5c4017 100644

-	if (!svar)

 

 
 
 	if (found)
-		ereport(ERROR,

 

 

 
 
 					n->name = $4;
 					n->typeName = $6;

 
 					n->name = $3;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}

 
 		case T_LetStmt:

 

index c36febd894e..9f5b088de72 100644
index 7fd739d6677..60f78671e3b 100644
 
 -- should be ok

From 0b5bb4074d3c7e3d52e325cc7a6ae0d433388624 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
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(+)

index 76121b24363..1790f2ca4f3 100644

 

 
 
+/*

 #endif
index 2d00471da96..9031b5c384c 100644
 
+BEGIN;
+SAVEPOINT s1;
+DROP VARIABLE x;
index 7335f15ed39..5ec412ad7c9 100644
 
+BEGIN;
+SAVEPOINT s1;
+DROP VARIABLE x;

From b91556a16ccff3647c14af91a79d0faad47f3c99 Mon Sep 17 00:00:00 2001

 
  <sect1 id="ddl-others">

 

index deb5d7e80f9..bfc4191d954 100644

 /*
 
 	/*

 
+		case T_LetStmt:
 
 			/*

 
 
 	/*
 
 /*
 
+/*
+ * transformLetStmt -

 

 			/*
 
 			/*

 			/*
 
 			/*
 
 			/*
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

+/* LET */

 

 

index 1519bf723e0..b87967bd7d5 100644
 
index 0ad972cc730..b28f776cb8a 100644

From 0634a5cc5c8df1105401a8d093d4cc1e8ff4b5fd Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 05/11] svariableReceiver

index f8ea8526e1d..deb5d7e80f9 100644
 
+/*

 

 
 
 /* ----------------
index 6a6e3a175ac..0ad972cc730 100644

From fed8ce90ed623bcd2cf0177bf9aece6f9abe641c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 22 Nov 2025 06:40:46 +0100
Subject: [PATCH 04/11] fill an auxiliary buffer with values of session 
 variables used in query

 
+	/*
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index 00000000000..1519bf723e0
+   Index Cond: (a = VARIABLE(temp_var02))
+--------------------------------------
+   Filter: (a = VARIABLE(temp_var02))
index 34dc48c0917..6a6e3a175ac 100644

From d1c97fc932345e0937f5f6755e07ba420d9e522a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 20:42:56 +0100
Subject: [PATCH 03/11] collect session variables used in plan and assign
 paramid

* parallel execution

plan
---

 
+		/*
index 75c62baeca2..f8ea8526e1d 100644
 
+/*
 
 	/*

+	/*

+/*

 
 
 				/*

 	return false;
 }

 
 	/*

From c56f69cd43cca4592445bf2e51df8caf2e14536d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 15:28:59 +0100
Subject: [PATCH 02/11] parsing session variable fences

 
  <sect1 id="ddl-others">
 
+	/*
 
index cd61df4a370..75c62baeca2 100644
 
+/*

 

 

 

 

 
 
+variable_fence:
+			VARIABLE '(' ColId ')'
+				{
 
+		case T_VariableFence:
 
+/*

 
index 6cf90be40bb..97bf1381d5b 100644

 
 
+/*
 
 typedef struct Param

 
 	/*
index 8f4160363bf..34dc48c0917 100644

From efe1983d25200ac103c4b44235c055b6d0f48918 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 19 Nov 2025 19:36:07 +0100
Subject: [PATCH 01/11] CREATE VARIABLE, DROP VARIABLE

---

 
 
index 00000000000..cd61df4a370

 

 
+
 				/*

 
 
+/* CREATE VARIABLE */
 
+
index 00000000000..45c2d27ab44
+CREATE TEMP VARIABLE x AS test_domain;
+ERROR:  test_domain is not a base type
 
 # ----------
index 00000000000..34f34dd898f
+CREATE TEMP VARIABLE x AS test_domain;
index 6e2ed0c8825..8f4160363bf 100644

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 12 Dec 2025 15:46:33 +0100

Hi

- rebase
- palloc replaced by palloc_object, and palloc_array

Regards

Pavel
From 8c3e400dd5138fa4b5246c865445a24123dfcdb9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 8 Dec 2025 05:00:12 +0100
Subject: [PATCH 11/11] subtransaction support for session variables DDL
 (CREATE, DROP)

index e5bc10b0473..f0f67b72830 100644
index 8f249d515d5..4a311d2498b 100644

 

 
 
+/*

 #endif
 
+BEGIN;
+SAVEPOINT s1;
+DROP VARIABLE x;
 
+BEGIN;
+SAVEPOINT s1;
+DROP VARIABLE x;

From 5dfa1787e407fecc6adb1e4a56f61bd828648a7e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 4 Dec 2025 06:17:54 +0100
Subject: [PATCH 08/11] support CREATE IF NOT EXISTS and DROP IF EXISTS

---

  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

  <refsynopsisdiv>
 <synopsis>

 
-	if (!svar)

 

 
 
 	if (found)
-		ereport(ERROR,

 

 

index 797165407e8..813c3b2f06c 100644

 					n->name = $4;
 					n->typeName = $6;

 
 					n->name = $3;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}

 
 		case T_LetStmt:

 

 -- should be ok

From 3e8d57dfbd16ac097aa79af80203c9ce3deb9734 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 20:04:16 +0100
Subject: [PATCH 07/11] DISCARD TEMP

---

 
 		case DISCARD_TEMP:
 
 		default:
 
 	PopActiveSnapshot();
 }
+
+/*

From 677b4ac17aa0c8a11eb510b00a326bf2d164edc2 Mon Sep 17 00:00:00 2001

---

 
+	/* DROP VARIABLE */
 
 /* LET */

+/*
+ * VARIABLE fence
+ */
 
 	/*

 DROP VARIABLE x;

From 1b787885bd8d5e3270d5f95ea7cc5779d2225a81 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 6 Dec 2025 07:35:30 +0100
Subject: [PATCH 10/11] transactional DDL - CREATE VARIABLE, DROP VARIABLE

index b69d452551a..e5bc10b0473 100644
 
index 6430455c0a6..8f249d515d5 100644
 
 	int16		typlen;
 	bool		typbyval;
+

 
+/*

 	if (found)
 	{
+			prev_svar = palloc_object(SVariableData);

 	svar->value = (Datum) 0;
 	svar->isnull = true;
+
 
 /*

 
 /*
 
 /*

 /*

 

From 96644fcb69ceae990c2d0480d887700b3bc8fe9d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 22 Nov 2025 06:40:46 +0100
Subject: [PATCH 04/11] fill an auxiliary buffer with values of session 
 variables used in query

index c35744b105e..a675ca07cd9 100644
index 797d8b1ca1c..0485bfd1487 100644
 
+	/*
+		estate->es_session_variables = palloc_array(SessionVariableValue,
+													nSessionVariables);
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index e1538221798..a35b1e83d99 100644
@@ -2735,6 +2735,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
-- 
2.52.0

From 4f8f26905b1b132e1780341dc2aefe22bd69e321 Mon Sep 17 00:00:00 2001

 
  <sect1 id="ddl-others">

 

 
 
 /*
index a1c94ff3f30..6966c3dbec8 100644
index cd6b3ee85ae..81613e7760e 100644
 
 	/*
index 96ef01f39c7..36b037f5248 100644

 
+		case T_LetStmt:
 
 			/*

 
 
 	/*
 
 /*
@@ -3323,6 +3341,59 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
 	return result;
 }
 
+/*
+ * transformLetStmt -
index d1b0416fd9c..797165407e8 100644

 

 			/*
index dbf5fa4f7fc..23a0c8752bc 100644
 
 			/*

 			/*
 
 			/*
 
 			/*
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

+/* LET */

 

 

 
index 0172e1bd05b..22ef3bcd4ba 100644

From 58b5a6775510c50335201720c59b9fe1250aad46 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 05/11] svariableReceiver

 
+/*

 

 
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a35b1e83d99..0172e1bd05b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2677,6 +2677,7 @@ STRLEN
 SV
 SVariableData
 SVariable
+SVariableState
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

From 6c35b750a251389758780cbc49ee9f048a5afdab Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 20:42:56 +0100
Subject: [PATCH 03/11] collect session variables used in plan and assign
 paramid

* parallel execution

plan
---

  <sect2 id="parallel-labeling">
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 838ed26d6b9..a71e5bff266 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1901,6 +1901,16 @@ find_expr_references_walker(Node *node,
 	{
 		Param	   *param = (Param *) node;
 
+		/*
 
+/*
index 8b22c30559b..cd6b3ee85ae 100644
 
 	/*

+	/*
index cd7ea1e6b58..b8b9444051d 100644

+/*
 
index c3b726e93e7..12301440d82 100644
 
index ddafc21c819..7e80d763cbd 100644

 				/*

 	return false;
 }

 
 	/*

From 7ff2b337707669d511e8057b827c471ec9bd639b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 15:28:59 +0100
Subject: [PATCH 02/11] parsing session variable fences

 
  <sect1 id="ddl-others">
 
+	/*

+/*
index 024a2b2fd84..a1c94ff3f30 100644
index 92be345d9a8..96ef01f39c7 100644

 

 

 

 

 
index 7d6b472b08f..d1b0416fd9c 100644
 
+variable_fence:
+			VARIABLE '(' ColId ')'
+				{
index 6b8fa15fca3..dbf5fa4f7fc 100644
 
+		case T_VariableFence:
 
+/*

 
index 9f85eb86da1..891674fbd45 100644

 
 
+/*
 
 typedef struct Param

diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 63598aba8a8..f9d206658b1 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8267,7 +8267,8 @@ exec_is_simple_query(PLpgSQL_expr *expr)
 		query->sortClause ||
 		query->limitOffset ||
 		query->limitCount ||
-		query->setOperations)
+		query->setOperations ||
+		query->hasSessionVariables)
 		return false;
 
 	/*
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 24f8f3b0048..e1538221798 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3229,6 +3229,7 @@ ValidatorValidateCB
 ValuesScan
 ValuesScanState
 Var
+VariableFence
 VarBit
 VarChar
 VarParamState
-- 
2.52.0

From 8c85e196c5a4c06a5100e0ebc21f1dc3fed56a9b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 19 Nov 2025 19:36:07 +0100
Subject: [PATCH 01/11] CREATE VARIABLE, DROP VARIABLE

---

 
 
index 7856ce9d78f..7d6b472b08f 100644

 

 
+
 				/*

 
 
+/* CREATE VARIABLE */
 
+
 
 # ----------
index 9dd65b10254..24f8f3b0048 100644
@@ -2673,6 +2675,8 @@ SSL_CTX
 STARTUPINFO
 STRLEN
 SV
+SVariableData
+SVariable
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 15 Dec 2025 05:42:34 +0100

Hi

only rebase

Regards

Pavel
From 82b7ab63278e05985311361f75e2145b7b9cfd6e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 8 Dec 2025 05:00:12 +0100
Subject: [PATCH 11/11] subtransaction support for session variables DDL
 (CREATE, DROP)

 

 

 
+/*

 #endif
 
+BEGIN;
+SAVEPOINT s1;
+DROP VARIABLE x;
 
+BEGIN;
+SAVEPOINT s1;
+DROP VARIABLE x;

From 354e56d1edf14df78d688d2204cea3049ed144d6 Mon Sep 17 00:00:00 2001

---

index de927ba569f..b5725d554c9 100644
 
@@ -4374,6 +4379,10 @@ match_previous_words(int pattern_id,
 
+	/* DROP VARIABLE */
@@ -4814,6 +4823,8 @@ match_previous_words(int pattern_id,
 
 /* LET */
 
@@ -5306,7 +5317,7 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_vacuumables,
 										"VERBOSE",
 										"ANALYZE",
-										"ONLY");
+									"ONLY");
 	else if (Matches("VACUUM", MatchAnyN, "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_vacuumables,
 										"ANALYZE",
@@ -5373,6 +5384,12 @@ match_previous_words(int pattern_id,
 		 */
 	}
 
+/*
+ * VARIABLE fence
+ */
 
 	/*

 DROP VARIABLE x;

From 1f2aa160b8a10eac544dca66128dac7acea9ca41 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 6 Dec 2025 07:35:30 +0100
Subject: [PATCH 10/11] transactional DDL - CREATE VARIABLE, DROP VARIABLE

 
 
 	int16		typlen;
 	bool		typbyval;
+

 
+/*

 	if (found)
 	{

 	svar->value = (Datum) 0;
 	svar->isnull = true;
+
 
 /*

 
 /*
 
 /*

 /*

 

From 419884ef42033c7544b3b9ff1a027342a806e216 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 4 Dec 2025 06:17:54 +0100
Subject: [PATCH 08/11] support CREATE IF NOT EXISTS and DROP IF EXISTS

---

  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

  <refsynopsisdiv>
 <synopsis>

 
-	if (!svar)

 

 
 
 	if (found)
-		ereport(ERROR,

 

 

index b26c3116cd9..426a7f1aea4 100644
@@ -5394,7 +5394,7 @@ create_extension_opt_item:
 
@@ -5411,14 +5411,31 @@ CreateSessionVarStmt:
 
 					n->name = $4;
 					n->typeName = $6;

@@ -5428,8 +5445,18 @@ DropSessionVarStmt:
 					DropSessionVarStmt *n = makeNode(DropSessionVarStmt);
 
 					n->name = $3;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}

 
 		case T_LetStmt:

index fbe118bde04..517bfd91f22 100644
@@ -3620,6 +3620,7 @@ typedef struct CreateSessionVarStmt
 
@@ -3629,7 +3630,8 @@ typedef struct CreateSessionVarStmt

 -- should be ok

From a7bb3da134a68abd91f779424d74126048b623f0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 20:04:16 +0100
Subject: [PATCH 07/11] DISCARD TEMP

---

 
 		case DISCARD_TEMP:
 
 		default:
 
 	PopActiveSnapshot();
 }
+
+/*

From 0ba0433e98825889efdb5d5495f6d1dc993569c3 Mon Sep 17 00:00:00 2001

index fa462881ea6..013610ae062 100644
@@ -5717,10 +5717,32 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
  <sect1 id="ddl-others">

 

 
 
 /*
 
 	/*

 
+		case T_LetStmt:
 
 			/*

 
 
 	/*
 
 /*
 
+/*
+ * transformLetStmt -
index a5f141ac0c9..b26c3116cd9 100644
@@ -750,7 +750,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEEP KEY KEYS

 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
@@ -1098,6 +1098,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -12984,6 +12985,37 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -18089,6 +18121,7 @@ unreserved_keyword:
@@ -18707,6 +18740,7 @@ bare_label_keyword:

 			/*
 
 			/*

 			/*
 
 			/*
 
 			/*
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index 8a491c21d7a..de927ba569f 100644
@@ -4812,6 +4812,11 @@ match_previous_words(int pattern_id,
 
+/* LET */

 
index 05b84a53aa2..fbe118bde04 100644
 
@@ -2196,6 +2199,18 @@ typedef struct MergeStmt
 
index 07ae9dce920..f40845ecf19 100644

index 95f2274dc5b..92eaa4cfb24 100644

index 15d9c516ff1..f399c3ab34a 100644
@@ -1560,6 +1560,7 @@ LargeObjectDesc

From b64ec736d64db3b3d1a872ed3ec66950e705ae4e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 22 Nov 2025 06:40:46 +0100
Subject: [PATCH 04/11] fill an auxiliary buffer with values of session 
 variables used in query

 
+	/*
index 3968429f991..11cb7f3580b 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index 80668f68944..d47b49b6088 100644
index 306cbc6d2f8..5787142eab1 100644
@@ -2747,6 +2747,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
-- 
2.52.0

From 35eacc36d220520796f060f89adf4151e15de71a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 05/11] svariableReceiver

 
+/*

 

 
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5787142eab1..15d9c516ff1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2689,6 +2689,7 @@ STRLEN
 SV
 SVariableData
 SVariable
+SVariableState
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

From a9c395087e8bc113672e83b4fe98111e0e926da7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 15:28:59 +0100
Subject: [PATCH 02/11] parsing session variable fences

index 321ea1c4b5b..fa462881ea6 100644
@@ -5711,6 +5711,16 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
  <sect1 id="ddl-others">
 
+	/*

+/*

 

 

 

 

 
index a502554bc75..a5f141ac0c9 100644
@@ -890,7 +890,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -15771,6 +15771,8 @@ c_expr:		columnref								{ $$ = $1; }
@@ -17173,6 +17175,17 @@ case_arg:	a_expr									{ $$ = $1; }
 
+variable_fence:
+			VARIABLE '(' ColId ')'
+				{
 
+		case T_VariableFence:
 
+/*

 

index 517f8a0aad4..05b84a53aa2 100644

+/*
 
 typedef struct Param

 
 	/*
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 7c1a30ba36b..306cbc6d2f8 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3246,6 +3246,7 @@ ValidatorValidateCB
 ValuesScan
 ValuesScanState
 Var
+VariableFence
 VarBit
 VarChar
 VarParamState
-- 
2.52.0

From d67f4346d4c624c713adea1fb54e09604934f358 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 20:42:56 +0100
Subject: [PATCH 03/11] collect session variables used in plan and assign
 paramid

* parallel execution

plan
---

  <sect2 id="parallel-labeling">
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 7489bbd5fb3..f7346b3aee1 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1951,6 +1951,16 @@ find_expr_references_walker(Node *node,
 	{
 		Param	   *param = (Param *) node;
 
+		/*
 
+/*
 
 	/*

+	/*

+/*

 
 
 				/*

 	return false;
 }
 
index b5ff456ef7f..07ae9dce920 100644

 	/*

From 29de9f7c34b2382959dc33634b7e15a12326d559 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 19 Nov 2025 19:36:07 +0100
Subject: [PATCH 01/11] CREATE VARIABLE, DROP VARIABLE

---

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index cea28c00f8a..321ea1c4b5b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5693,6 +5693,26 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>

 
index 28f4e11e30f..a502554bc75 100644
@@ -291,13 +291,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -792,8 +792,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 
@@ -1059,6 +1059,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1086,6 +1087,7 @@ stmt:
 			| DropTableSpaceStmt
 			| DropTransformStmt
 			| DropRoleStmt
+			| DropSessionVarStmt
 			| DropUserMappingStmt
 			| DropdbStmt
 			| ExecuteStmt
@@ -5388,6 +5390,47 @@ create_extension_opt_item:
 				}
 		;
 
@@ -18208,6 +18251,7 @@ unreserved_keyword:
@@ -18868,6 +18912,7 @@ bare_label_keyword:

+
 				/*

index b1ff6f6cd94..8a491c21d7a 100644
 
@@ -3737,7 +3738,7 @@ match_previous_words(int pattern_id,
@@ -4090,6 +4091,13 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH(",", ")");
 	}
 
+/* CREATE VARIABLE */
index bc7adba4a0f..517f8a0aad4 100644
@@ -3583,6 +3583,29 @@ typedef struct AlterStatsStmt
 
+
index 9fde58f541c..95f2274dc5b 100644
@@ -491,6 +491,7 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)
index 905f9bca959..80668f68944 100644
 
 # ----------
index 3451538565e..7c1a30ba36b 100644
@@ -570,6 +570,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
@@ -673,6 +674,7 @@ DropBehavior
 DropOwnedStmt
 DropReplicationSlotCmd
 DropRoleStmt
+DropSessionVarStmt
 DropStmt
 DropSubscriptionStmt
 DropTableSpaceStmt
@@ -2685,6 +2687,8 @@ SSL_CTX
 STARTUPINFO
 STRLEN
 SV
+SVariableData
+SVariable
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 27 Dec 2025 08:52:28 +0100

Hi

only rebase

Regards

Pavel
From e1848e1c16e93b76dbe169eaef08556fd130fccb Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 8 Dec 2025 05:00:12 +0100
Subject: [PATCH 11/11] subtransaction support for session variables DDL
 (CREATE, DROP)

index ed5c3d1385b..d2df1c6c448 100644
@@ -5211,6 +5211,8 @@ CommitSubTransaction(void)
@@ -5380,6 +5382,8 @@ AbortSubTransaction(void)

 

 
 
+/*

 #endif
 
+BEGIN;
+SAVEPOINT s1;
+DROP VARIABLE x;
 
+BEGIN;
+SAVEPOINT s1;
+DROP VARIABLE x;

From 119b0fcce5b582906def8d259744442429ab19f4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 6 Dec 2025 07:35:30 +0100
Subject: [PATCH 10/11] transactional DDL - CREATE VARIABLE, DROP VARIABLE

index 1b5c1f6b763..ed5c3d1385b 100644
 
@@ -2939,6 +2943,7 @@ AbortTransaction(void)
 
 	int16		typlen;
 	bool		typbyval;
+

 
+/*

 	if (found)
 	{

 	svar->value = (Datum) 0;
 	svar->isnull = true;
+
 
 /*

 
 /*
 
 /*

 /*

 

From 9ceadabba58aba2cc7aed481a3bff96f613f3d4d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 4 Dec 2025 06:17:54 +0100
Subject: [PATCH 08/11] support CREATE IF NOT EXISTS and DROP IF EXISTS

---

  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

  <refsynopsisdiv>
 <synopsis>

 
-	if (!svar)

 

 
 
 	if (found)
-		ereport(ERROR,

 

 

 
 
 					n->name = $4;
 					n->typeName = $6;

 
 					n->name = $3;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}

 
 		case T_LetStmt:

 

 -- should be ok

From ee5f84b3dce59fef2b165621eb8f2d9cb1eefa72 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 20:04:16 +0100
Subject: [PATCH 07/11] DISCARD TEMP

---

 
 		case DISCARD_TEMP:
 
 		default:
 
 	PopActiveSnapshot();
 }
+
+/*

From 907dd21de6e76197ce0c7f0784a64dd794c25b83 Mon Sep 17 00:00:00 2001

---
 src/backend/commands/session_variable.c       | 36 +++++++++++++++++++
 src/bin/psql/tab-complete.in.c                | 17 +++++++++
 src/include/catalog/pg_proc.dat               |  5 +++
 .../expected/session_variables_ddl.out        | 16 +++++++++
 .../regress/sql/session_variables_ddl.sql     |  8 +++++
 5 files changed, 82 insertions(+)

index 585a4325b9e..b02a4d83289 100644
 
@@ -4397,6 +4402,10 @@ match_previous_words(int pattern_id,
 
+	/* DROP VARIABLE */
@@ -4837,6 +4846,8 @@ match_previous_words(int pattern_id,
 
 /* LET */
 
@@ -5399,6 +5410,12 @@ match_previous_words(int pattern_id,
 		 */
 	}
 
+/*
+ * VARIABLE fence
+ */
 
 	/*

 DROP VARIABLE x;

From e22dc9ec9e05c963a8b337efeebf5e86042e842a Mon Sep 17 00:00:00 2001

index 8c0ae715869..19d2afc2201 100644
 
  <sect1 id="ddl-others">

 

 
 
 /*
index 1f41c210f2a..0f6dcb2568c 100644
 
 	/*

 
+		case T_LetStmt:
 
 			/*

 
 
 	/*
 
 /*
 
+/*
+ * transformLetStmt -

 

 			/*
 
 			/*

 			/*
 
 			/*
 
 			/*
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index 79afa0dc84b..585a4325b9e 100644
@@ -4835,6 +4835,11 @@ match_previous_words(int pattern_id,
 
+/* LET */

 

 

 
index eab87541f45..141e2bdfebf 100644
@@ -1561,6 +1561,7 @@ LargeObjectDesc

From a02e226e6974735cfb0671b6d365eca9b2d22bd5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 05/11] svariableReceiver

 
+/*

 

 
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9a98110d0a0..eab87541f45 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2691,6 +2691,7 @@ STRLEN
 SV
 SVariableData
 SVariable
+SVariableState
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

From 2a3556a1c5ac40a78974803c617a3a0d9b470d0d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 22 Nov 2025 06:40:46 +0100
Subject: [PATCH 04/11] fill an auxiliary buffer with values of session 
 variables used in query

 
+	/*
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index 0bcc56325b7..9a98110d0a0 100644
@@ -2749,6 +2749,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
-- 
2.52.0

From a00750819787407b536e414c187e408c04ddf1a7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 15:28:59 +0100
Subject: [PATCH 02/11] parsing session variable fences

index 35a6b322cd9..8c0ae715869 100644
 
  <sect1 id="ddl-others">
 
+	/*

+/*

 

 

 

 

 
 
+variable_fence:
+			VARIABLE '(' ColId ')'
+				{
 
+		case T_VariableFence:
 
+/*

 

 
 
+/*
 
 typedef struct Param

 
 	/*
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 16edf55b489..0bcc56325b7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3248,6 +3248,7 @@ ValidatorValidateCB
 ValuesScan
 ValuesScanState
 Var
+VariableFence
 VarBit
 VarChar
 VarParamState
-- 
2.52.0

From 386ca786bb2b7edf34a8fde290a6afcf66144d53 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 20:42:56 +0100
Subject: [PATCH 03/11] collect session variables used in plan and assign
 paramid

* parallel execution

plan
---

 
+		/*
 
+/*
index 1268ea92b6f..1f41c210f2a 100644
 
 	/*

+	/*

+/*

index 67b7de16fc5..416faae5cc2 100644

 				/*
@@ -5033,7 +5065,8 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,

 	return false;
 }

 
 	/*

From 031df7f7b14384dbeee1a124ad441d1b03b645d2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 19 Nov 2025 19:36:07 +0100
Subject: [PATCH 01/11] CREATE VARIABLE, DROP VARIABLE

---

index 9070aaa5a7c..35a6b322cd9 100644

 

 

 
+
 				/*

index 75a101c6ab5..79afa0dc84b 100644
 
@@ -3760,7 +3761,7 @@ match_previous_words(int pattern_id,
@@ -4113,6 +4114,13 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH(",", ")");
 	}
 
+/* CREATE VARIABLE */
 
+
 
 # ----------
index 5c88fa92f4e..16edf55b489 100644
@@ -571,6 +571,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
@@ -674,6 +675,7 @@ DropBehavior
 DropOwnedStmt
 DropReplicationSlotCmd
 DropRoleStmt
+DropSessionVarStmt
 DropStmt
 DropSubscriptionStmt
 DropTableSpaceStmt
@@ -2687,6 +2689,8 @@ SSL_CTX
 STARTUPINFO
 STRLEN
 SV
+SVariableData
+SVariable
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 9 Jan 2026 09:45:45 +0100

Hi

fresh rebase

Regards

Pavel
From f17180895ecf26aa4eecbe2b37895ae5ef2840c2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 8 Dec 2025 05:00:12 +0100
Subject: [PATCH 11/11] subtransaction support for session variables DDL
 (CREATE, DROP)

index bdd08e38483..c8df3b90316 100644
@@ -5212,6 +5212,8 @@ CommitSubTransaction(void)
@@ -5381,6 +5383,8 @@ AbortSubTransaction(void)

 

 
 
+/*

 #endif
 
+BEGIN;
+SAVEPOINT s1;
+DROP VARIABLE x;
 
+BEGIN;
+SAVEPOINT s1;
+DROP VARIABLE x;

From b911e8c680bc1a7ea3d1b6cc2f50fd45e7e7a918 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 6 Dec 2025 07:35:30 +0100
Subject: [PATCH 10/11] transactional DDL - CREATE VARIABLE, DROP VARIABLE

index c857e23552f..bdd08e38483 100644

 	int16		typlen;
 	bool		typbyval;
+

 
+/*

 	if (found)
 	{

 	svar->value = (Datum) 0;
 	svar->isnull = true;
+
 
 /*

 
 /*
 
 /*

 /*

 

From 9317276584abd900cb238013859a7371df5ece28 Mon Sep 17 00:00:00 2001

---

index 94311183636..60e2c15ce03 100644
 
@@ -4406,6 +4411,10 @@ match_previous_words(int pattern_id,
 
+	/* DROP VARIABLE */
@@ -4846,6 +4855,8 @@ match_previous_words(int pattern_id,
 
 /* LET */
 
@@ -5414,6 +5425,12 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH("'standby_replay'", "'standby_write'", "'standby_flush'", "'primary_flush'");
 	}
 
+/*
+ * VARIABLE fence
+ */
 
 	/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2ac69bf2df5..45fda49dff1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12683,4 +12683,9 @@
   proname => 'hashoid8extended', prorettype => 'int8',
   proargtypes => 'oid8 int8', prosrc => 'hashoid8extended' },

 DROP VARIABLE x;

From 1842117f36e1fdc081e09a8345fccf42744b7734 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 4 Dec 2025 06:17:54 +0100
Subject: [PATCH 08/11] support CREATE IF NOT EXISTS and DROP IF EXISTS

---

  <refsynopsisdiv>
 <synopsis>
 
   <variablelist>

  <refsynopsisdiv>
 <synopsis>

 
-	if (!svar)

 

 
 
 	if (found)
-		ereport(ERROR,

 

 

index 78504cb1a22..80a466f691e 100644

 					n->name = $4;
 					n->typeName = $6;

 
 					n->name = $3;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
 
index 063759df396..77e85d6f2b1 100644

 		case T_LetStmt:

index 99f7c5ee34c..c2dacdb158b 100644
@@ -3621,6 +3621,7 @@ typedef struct CreateSessionVarStmt
 
@@ -3630,7 +3631,8 @@ typedef struct CreateSessionVarStmt

 -- should be ok

From 8c1c2dced946cc7e6c36f9ff2554382f88cd9ce6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 24 Nov 2025 20:04:16 +0100
Subject: [PATCH 07/11] DISCARD TEMP

---

index 7b5520b9abe..de6d18fcc5e 100644

 		case DISCARD_TEMP:
 
 		default:
 
 	PopActiveSnapshot();
 }
+
+/*

From 0b55ba7bfa5915dc20d44a77a7b318376c1bea4f Mon Sep 17 00:00:00 2001

 
  <sect1 id="ddl-others">

 

 
 
 /*
index c2a675276dd..8f317e88b89 100644
@@ -4377,6 +4377,14 @@ raw_expression_tree_walker_impl(Node *node,
index c6e7407b027..38ca1ed5cd0 100644
 
 	/*
index 7de0133d2e8..9934a93f891 100644

 
+		case T_LetStmt:
 
 			/*

 
 
 	/*
 
 /*
 
+/*
+ * transformLetStmt -
index a3d83bb0999..78504cb1a22 100644

@@ -12989,6 +12990,37 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 
@@ -18094,6 +18126,7 @@ unreserved_keyword:
@@ -18712,6 +18745,7 @@ bare_label_keyword:
index 25ee0f87d93..2d7c40f7bbb 100644

 			/*
index 6d7c5f2914d..bcc1d8e5726 100644
 
 			/*

 			/*
 
 			/*
index 24f6745923b..2e63a56bee1 100644
 
 			/*
index 41a7edfe53d..063759df396 100644
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
@@ -2220,6 +2226,10 @@ UtilityContainsQuery(Node *parsetree)
 
+		case T_LetStmt:
+			qry = castNode(Query, ((LetStmt *) parsetree)->query);
+			return qry;
+
 		default:
 			return NULL;
 	}
@@ -2415,6 +2425,10 @@ CreateCommandTag(Node *parsetree)
 			tag = CMDTAG_SELECT;
 			break;
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -3305,6 +3319,7 @@ GetCommandLogLevel(Node *parsetree)
 			break;
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index b829eed2ab0..94311183636 100644
@@ -4844,6 +4844,11 @@ match_previous_words(int pattern_id,
 
+/* LET */

 
index 8207cda6cf9..99f7c5ee34c 100644
 
@@ -2197,6 +2200,18 @@ typedef struct MergeStmt
 
index f089985d1fd..fc5ae865667 100644

index d4b1223a33e..ca61e18d8ab 100644
index e51669396f0..3f8412d1050 100644

index 4b964d0fb5a..945a3a0f24b 100644
 
index 0cf514d244d..864a17d0a29 100644
@@ -1562,6 +1562,7 @@ LargeObjectDesc

From 05d954a69409b8b6fdc6ca4c9582e1431dde21ca Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sat, 22 Nov 2025 06:40:46 +0100
Subject: [PATCH 04/11] fill an auxiliary buffer with values of session 
 variables used in query

index 088eca24021..e722c723649 100644
index ca14cdabdd0..33dfc1f61c8 100644
 
+	/*
index 02265456978..1247fd3e802 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */

index bbc9c6a8b08..6ee20f44c2f 100644
index 7bcba5b2a5c..0c6cb47b518 100644
@@ -2748,6 +2748,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
-- 
2.52.0

From 56eae652749e1cb479faf19df04340292fa58c9c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 20:42:56 +0100
Subject: [PATCH 03/11] collect session variables used in plan and assign
 paramid

* parallel execution

plan
---

index f89267f0342..a3e0a477094 100644
 
+		/*
 
+/*
index 7a6b8b749f2..c6e7407b027 100644
 
 	/*

+	/*
index 16d200cfb46..cce1ecb36f4 100644

+/*
 
index c80bfc88d82..ca06eff56c9 100644
 
index 39d35827c35..b741c7f4745 100644

 				/*
 
index 05984e7ef26..5a1676ea43f 100644
 
 	return false;
 }
 
index 449885b9319..f089985d1fd 100644

 	/*
index 4bc6fb5670e..024c3a8c4bd 100644
 
index d0dc3761b13..46069ef2e9e 100644

From d8244f6d2637a92024fd1eb01ab9d7a7c37fc9d2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 21 Nov 2025 15:28:59 +0100
Subject: [PATCH 02/11] parsing session variable fences

 
  <sect1 id="ddl-others">
index 5b86a727587..452b2498716 100644
 
+	/*

+/*
index d29664ca5d4..c2a675276dd 100644
@@ -4704,6 +4707,9 @@ raw_expression_tree_walker_impl(Node *node,
index 029ca3b68c3..7de0133d2e8 100644

 

 

 

 

 
index 1740f5ddfc6..a3d83bb0999 100644
@@ -15776,6 +15776,8 @@ c_expr:		columnref								{ $$ = $1; }
@@ -17178,6 +17180,17 @@ case_arg:	a_expr									{ $$ = $1; }
 
+variable_fence:
+			VARIABLE '(' ColId ')'
+				{
index 9cba1272253..6d7c5f2914d 100644
 
+		case T_VariableFence:
 
+/*
index e08dc18dd75..bd2b6b9c7d1 100644

 
index b5a2f915b67..ee6783496a6 100644
index 033b625f3fc..e146db58de4 100644

index 087e1c4f66e..8207cda6cf9 100644

+/*
index 5211cadc258..5e1b734ad5d 100644
 
 typedef struct Param
index a9bffb8a78f..e51669396f0 100644

index 75325117ec9..b343538d190 100644
 
 	/*
index a2484a0ced0..7bcba5b2a5c 100644

From faa917654372b07601a82848109ea9234a29c0ff Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 1 Jun 2025 21:20:16 +0200
Subject: [PATCH 05/11] svariableReceiver

 
+/*
 
index dc45be0b2ce..a572b6dab7c 100644
index 931cc563ba6..b72f8769b30 100644

 

index 4e4f532d8cc..1e7043dc7f7 100644
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 0c6cb47b518..0cf514d244d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2690,6 +2690,7 @@ STRLEN
 SV
 SVariableData
 SVariable
+SVariableState
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0

From 4bb57a461d197bb8d54a2c42d9ed7df1ecceacc0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 19 Nov 2025 19:36:07 +0100
Subject: [PATCH 01/11] CREATE VARIABLE, DROP VARIABLE

---

 
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index e2db5bcc78c..eab22f11a64 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -1743,6 +1743,21 @@
    </glossdef>
   </glossentry>
 
index 92526012d2a..77075f9051d 100644
index ca3f53c6213..e88e3b9c058 100644
index 713ee5c10a2..1740f5ddfc6 100644

 
 
@@ -18213,6 +18256,7 @@ unreserved_keyword:
@@ -18873,6 +18917,7 @@ bare_label_keyword:
index 34dd6e18df5..41a7edfe53d 100644

+
 				/*
 				 * ************* object creation / destruction **************
 				 */
@@ -3236,6 +3249,14 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
@@ -3774,6 +3795,11 @@ GetCommandLogLevel(Node *parsetree)
 			}
 			break;
 
index 8b91bc00062..b829eed2ab0 100644
 
@@ -3769,7 +3770,7 @@ match_previous_words(int pattern_id,
@@ -4122,6 +4123,13 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH(",", ")");
 	}
 
+/* CREATE VARIABLE */
index aac4bfc70d9..087e1c4f66e 100644
@@ -3584,6 +3584,29 @@ typedef struct AlterStatsStmt
 
+
index f7753c5c8a8..d4b1223a33e 100644
index 1290c9bab68..4b964d0fb5a 100644
index 021d57f66bb..bbc9c6a8b08 100644
 
 # ----------
index 09e7f1d420e..a2484a0ced0 100644
@@ -2686,6 +2688,8 @@ SSL_CTX
 STARTUPINFO
 STRLEN
 SV
+SVariableData
+SVariable
 SYNCHRONIZATION_BARRIER
 SYSTEM_INFO
 SampleScan
-- 
2.52.0