From d25c97d24ec91f1951fc70dedf9fd5970587098e Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.com>
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 b88cac598e9..83b13f98f28 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -1708,6 +1708,21 @@
    </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
+     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.
+    </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/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index e937491e6b8..1e4c43b8b61 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -6036,6 +6036,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>
 
   <sect2 id="plpgsql-porting-appendix">
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_variable.sgml b/doc/src/sgml/ref/alter_variable.sgml
new file mode 100644
index 00000000000..96d2586423e
--- /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_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_name</replaceable></term>
+      <listitem>
+       <para>
+        The new name for 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..d2bb265209b 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>
@@ -214,6 +215,11 @@ CREATE VIEW hollywood.winners AS
    The <literal>IF NOT EXISTS</literal> option is a
    <productname>PostgreSQL</productname> extension.
   </para>
+
+  <para>
+   The <command>CREATE VARIABLE</command> command is a
+   <productname>PostgreSQL</productname> extension.
+  </para>
  </refsect1>
 
  <refsect1>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
new file mode 100644
index 00000000000..6e988f2e472
--- /dev/null
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -0,0 +1,149 @@
+<!--
+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 the commands <command>GRANT</command> and <command>REVOKE</command>.
+  </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>
+
+  <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-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;
+</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/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/aclchk.c b/src/backend/catalog/aclchk.c
index 9ca8a88dc91..7075c86378a 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -2771,6 +2771,7 @@ 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);
 				}
 
@@ -2878,6 +2879,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
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 18316a3968b..9e0f648b74f 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:
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d97d632a7ef..4abbe733f45 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"
@@ -225,6 +226,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);
@@ -986,6 +988,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
@@ -3289,6 +3369,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;
+}
 
 /*
  * DeconstructQualifiedName
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index b63fd57dc04..9413b7619c5 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,
@@ -1127,6 +1146,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 */
 		}
 
@@ -2102,6 +2124,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
  */
@@ -2296,6 +2336,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:
@@ -2467,6 +2508,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)));
@@ -3496,6 +3538,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;
@@ -4670,6 +4738,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);
 	}
@@ -6020,6 +6092,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, pstrdup(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 536191284e8..c6fd1fa074f 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/commands/Makefile b/src/backend/commands/Makefile
index cb2fbdc7c60..aee40e7bd59 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/alter.c b/src/backend/commands/alter.c
index c801c869c1c..c72a4adb07b 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"
@@ -140,6 +141,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;
@@ -435,6 +440,7 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_PUBLICATION:
 		case OBJECT_SUBSCRIPTION:
+		case OBJECT_VARIABLE:
 			{
 				ObjectAddress address;
 				Relation	catalog;
@@ -575,6 +581,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_VARIABLE:
 			{
 				Relation	catalog;
 				Oid			classId;
@@ -657,6 +664,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 		case TSDictionaryRelationId:
 		case TSTemplateRelationId:
 		case TSConfigRelationId:
+		case VariableRelationId:
 			{
 				Relation	catalog;
 
@@ -887,6 +895,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 		case OBJECT_TABLESPACE:
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSCONFIGURATION:
+		case OBJECT_VARIABLE:
 			{
 				ObjectAddress address;
 
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index ceb9a229b63..ebb585dc4a1 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 edc2c988e29..1839c1f82c5 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/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;
 
 			/*
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
new file mode 100644
index 00000000000..f641e00c1ac
--- /dev/null
+++ b/src/backend/commands/session_variable.c
@@ -0,0 +1,88 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_variable.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "commands/session_variable.h"
+#include "miscadmin.h"
+#include "parser/parse_type.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+
+/*
+ * 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);
+
+	return variable;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 074ddb6b9cd..a9424b1b267 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -52,6 +52,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"
@@ -6910,6 +6911,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.
@@ -6973,6 +6975,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 45ae7472ab5..8f8b7d09f32 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"
@@ -3373,6 +3374,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) */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 50f53159d58..92bf8419833 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -52,6 +52,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"
@@ -284,8 +285,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 CreateSeqStmt CreateSessionVarStmt CreateStmt CreateStatsStmt
+		CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
 		CreateAssertionStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
 		CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
 		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
@@ -782,8 +783,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 VIRTUAL VOLATILE
+	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARIABLE
+	VARYING VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -1049,6 +1050,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1591,6 +1593,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;
 
 
@@ -5265,6 +5268,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 ]
@@ -7056,6 +7087,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; }
 		;
 
 /*
@@ -9963,6 +9995,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
@@ -10324,6 +10374,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;
+				}
 		;
 
 /*****************************************************************************
@@ -10605,6 +10673,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;
+				}
 		;
 
 
@@ -17991,6 +18067,7 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18647,6 +18724,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 62015431fdf..b6cbe354c51 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;
 
 
@@ -4091,6 +4092,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
@@ -4159,6 +4161,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));
@@ -4172,6 +4183,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 25fe3d58016..782b022da9c 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"
@@ -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:
@@ -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;
 
+		case T_CreateSessionVarStmt:
+			tag = CMDTAG_CREATE_VARIABLE;
+			break;
+
 		default:
 			elog(WARNING, "unrecognized node type: %d",
 				 (int) nodeTag(parsetree));
@@ -3759,6 +3775,10 @@ GetCommandLogLevel(Node *parsetree)
 			}
 			break;
 
+		case T_CreateSessionVarStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		default:
 			elog(WARNING, "unrecognized node type: %d",
 				 (int) nodeTag(parsetree));
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index c460a72b75d..fdb7eaef75a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -39,6 +39,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"
@@ -3854,3 +3855,67 @@ 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;
+}
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 8c7ccc69a3c..bdac0c13bec 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,6 +171,10 @@ 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	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 ba12678d1cb..8abe3f0d409 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2370,6 +2370,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW,
 } ObjectType;
 
@@ -3520,6 +3521,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 a4af3f717a1..6f513f04225 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -486,6 +486,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 d250a714d59..ea86954dded 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/lsyscache.h b/src/include/utils/lsyscache.h
index fa7c7e0323b..87e1fe636b9 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -139,6 +139,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);
@@ -211,6 +212,9 @@ 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);
+
 #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 a424be2a6bf..0f75bfaf5ae 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 e0e831b797f..0f4eecccc23 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -563,6 +563,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
-- 
2.49.0

