diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3bb48d4ccf..ffeea7df3f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -359,6 +359,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>schema variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -11311,4 +11316,114 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
 
  </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> holds metadata
+   of schema variables.
+  </para>
+
+  <table>
+   <title><structname>pg_views</structname> Columns</title>
+
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>varname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>Name of the schema variable</entry>
+     </row>
+
+     <row>
+      <entry><structfield>varnamespace</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the namespace that contains this variable
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>vartype</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the data type of this variable.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>vartypmod</structfield></entry>
+      <entry><type>int4</type></entry>
+      <entry></entry>
+      <entry>
+       <structfield>vartypmod</structfield> records type-specific data
+       supplied at table 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>.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>varowner</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.oid</literal></entry>
+      <entry>Owner of the variable</entry>
+     </row>
+
+     <row>
+      <entry><structfield>varcollation</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-collation"><structname>pg_collation</structname></link>.oid</literal></entry>
+      <entry>
+       The defined collation of the variable, or zero if the variable is
+       not of a collatable data type.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>vardefexpr</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>The internal representation of the variable default value</entry>
+     </row>
+
+     <row>
+      <entry><structfield>varacl</structfield></entry>
+      <entry><type>aclitem[]</type></entry>
+      <entry></entry>
+      <entry>
+       Access privileges; see
+       <xref linkend="sql-grant"/> and
+       <xref linkend="sql-revoke"/>
+       for details
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
 </chapter>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index c81c87ef41..0631c9ed56 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">
@@ -148,6 +150,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropUser           SYSTEM "drop_user.sgml">
 <!ENTITY dropUserMapping    SYSTEM "drop_user_mapping.sgml">
 <!ENTITY dropView           SYSTEM "drop_view.sgml">
+<!ENTITY dropVariable       SYSTEM "drop_variable.sgml">
 <!ENTITY end                SYSTEM "end.sgml">
 <!ENTITY execute            SYSTEM "execute.sgml">
 <!ENTITY explain            SYSTEM "explain.sgml">
@@ -155,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
new file mode 100644
index 0000000000..6376ac716b
--- /dev/null
+++ b/doc/src/sgml/ref/alter_variable.sgml
@@ -0,0 +1,170 @@
+<!--
+doc/src/sgml/ref/alter_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-altervariable">
+ <indexterm zone="sql-altervariable">
+  <primary>ALTER VARIABLE</primary>
+ </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 variable
+  </refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER VARIABLE <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable class="parameter">new_owner</replaceable> | 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>
+   <command>ALTER VARIABLE</command> changes the definition of an existing variable.
+   There are several subforms:
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>OWNER</literal></term>
+    <listitem>
+     <para>
+      This form changes the owner of the variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RENAME</literal></term>
+    <listitem>
+     <para>
+      This form changes the name of the variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>SET SCHEMA</literal></term>
+    <listitem>
+     <para>
+      This form moves the variable into another schema.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+  </para>
+
+  <para>
+   You must own the variable to use <command>ALTER VARIABLE</command>.
+   To change the schema of a variable, you must also have
+   <literal>CREATE</literal> privilege on the new schema.
+   To alter the owner, you must also be a direct or indirect member of the new
+   owning role, and that role must have <literal>CREATE</literal> privilege on
+   the variable's schema.  (These restrictions enforce that altering the owner
+   doesn't do anything you couldn't do by dropping and recreating the variable.
+   However, a superuser can alter ownership of any type anyway.)
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+   <para>
+    <variablelist>
+     <varlistentry>
+      <term><replaceable class="parameter">name</replaceable></term>
+      <listitem>
+       <para>
+        The name (possibly schema-qualified) of an existing variable to
+        alter.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">new_name</replaceable></term>
+      <listitem>
+       <para>
+        The new name for the variable.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">new_owner</replaceable></term>
+      <listitem>
+       <para>
+        The user name of the new owner of the variable.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">new_schema</replaceable></term>
+      <listitem>
+       <para>
+        The new schema for the variable.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+  </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To rename a variable:
+<programlisting>
+ALTER VARIABLE foo RENAME TO boo;
+</programlisting>
+  </para>
+
+  <para>
+   To change the owner of the variable <literal>boo</literal>
+   to <literal>joe</literal>:
+<programlisting>
+ALTER VARIABLE boo OWNER TO joe;
+</programlisting>
+  </para>
+
+  <para>
+   To change the schema of the variable <literal>boo</literal>
+   to <literal>private</literal>:
+<programlisting>
+ALTER VARIABLE boo SET SCHEMA private;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   This comman is 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/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
new file mode 100644
index 0000000000..1bf127eccd
--- /dev/null
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -0,0 +1,145 @@
+<!--
+doc/src/sgml/ref/create_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-createvariable">
+ <indexterm zone="sql-createvariable">
+  <primary>CREATE VARIABLE</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE VARIABLE</refname>
+  <refpurpose>define a new permissioned typed schema 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>
+   <command>CREATE VARIABLE</command> creates a new schema variable.
+   These variables are scalar typed, non-transactional, and, like relations,
+   exist within a schema with access controlled via
+   <command>GRANT</command> and <command>REVOKE</command>.
+  </para>
+
+  <para>
+   The value of a schema variable is session-local. Retrieving
+   a variable's value will return NULL unless its value has been set
+   to something else in the current session.
+  </para>
+
+  <para>
+   Retrieval is done via the <function>get_schema_variable</function>dunxrion or the SQL
+   command <command>SELECT</command>.  Setting of values is done via the
+   <function>set_schema_variable</function> function or the SQL command
+   <command>LET</command>.
+   Notably, while schema variables are in many ways a kind of table you cannot use
+   <command>UPDATE</command> on them.
+  </para>
+
+  <para>
+   For purposes of name uniqueness relation-like objects (e.g., tables, indexes)
+   within the same schema are considered.  i.e., you cannot give a table and a
+   schema variable the same name.  This is a consequence of them being treated
+   like relations for purposes of <command>SELECT</command>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <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.
+      Note that type of the variable is not considered, nor could it be since the namespace
+      searched contains non-variable objects.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the variable to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">data_type</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the data type of the variable to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>COLLATE <replaceable>collation</replaceable></literal></term>
+    <listitem>
+     <para>
+      The <literal>COLLATE</literal> clause assigns a collation to
+      the variable (which must be of a collatable data type).
+      If not specified, the variable data type's default collation is used.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   Use <command>DROP VARIABLE</command> to remove a variable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create an integer variable <literal>var1</literal>:
+<programlisting>
+CREATE VARIABLE var1 AS integer;
+SELECT var1;
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE VARIABLE</command> is a PostgreSQL feature.
+   <!-- The choice of wording here seems to be left to personal preference... -->
+  </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/discard.sgml b/doc/src/sgml/ref/discard.sgml
index 6b909b7232..d83ad811fd 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>
 
@@ -75,6 +75,17 @@ DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>VARIABLES</literal></term>
+    <listitem>
+     <para>
+      Resets the value of all schema variables. When variables
+      will be used later, then will be initialized again to
+      NULL or default value.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ALL</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml
new file mode 100644
index 0000000000..c1c1a2bd67
--- /dev/null
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -0,0 +1,93 @@
+<!--
+doc/src/sgml/ref/drop_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-dropvariable">
+ <indexterm zone="sql-dropvariable">
+  <primary>DROP VARIABLE</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP VARIABLE</refname>
+  <refpurpose>remove a schema 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 schema variable.
+   A variable can only be dropped by its owner or a superuser.
+   <!-- this would suggest that we need an alter variable owner to command -->
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the 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 schema variable.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To remove the schema variable <literal>var1</literal>:
+
+<programlisting>
+DROP VARIABLE var1;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>DROP VARIABLE</command> is proprietary PostgreSQL command.
+   <!-- create variable is a "PostgreSQL feature",
+        this is a "proprietary PostgreSQL command" ... -->
+  </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 ff64c7a3ba..a83920a7a1 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -79,6 +79,10 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
     ON TYPE <replaceable>type_name</replaceable> [, ...]
     TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
 
+GRANT { READ | WRITE | ALL [ PRIVILEGES ] }
+    ON VARIABLE <replaceable>variable_name</replaceable> [, ...]
+    TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
+
 <phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase>
 
     [ GROUP ] <replaceable class="parameter">role_name</replaceable>
@@ -167,6 +171,7 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
    foreign servers,
    large objects,
    schemas,
+   schema variable
    or tablespaces.
    For other types of objects, the default privileges
    granted to <literal>PUBLIC</literal> are as follows:
@@ -385,6 +390,24 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>READ</literal></term>
+     <listitem>
+      <para>
+       Allows to read a schema variable.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>WRITE</literal></term>
+     <listitem>
+      <para>
+       Allows to set a schema variable.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><literal>ALL PRIVILEGES</literal></term>
      <listitem>
@@ -550,6 +573,8 @@ rolename=xxxx -- privileges granted to a role
             C -- CREATE
             c -- CONNECT
             T -- TEMPORARY
+            S -- READ
+            w -- WRITE
       arwdDxt -- ALL PRIVILEGES (for tables, varies for other objects)
             * -- grant option for preceding privilege
 
diff --git a/doc/src/sgml/ref/let.sgml b/doc/src/sgml/ref/let.sgml
new file mode 100644
index 0000000000..e8bf3f6dd4
--- /dev/null
+++ b/doc/src/sgml/ref/let.sgml
@@ -0,0 +1,90 @@
+<!--
+doc/src/sgml/ref/let.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-let">
+ <indexterm zone="sql-let">
+  <primary>LET</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>LET</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>LET</refname>
+  <refpurpose>change a schema variable's value</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+LET <replaceable class="parameter">schema_variable</replaceable> = <replaceable class="parameter">sql_expression</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   The <command>LET</command> command updates the specified schema variable' value.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>schema_variable</literal></term>
+    <listitem>
+     <para>
+      The name of schema variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>sql expression</literal></term>
+    <listitem>
+     <para>
+      An SQL expression, the result is cast to the schema variable's type.
+     </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>
+   <!-- this feels like it needs to be more specific,
+        but I don't know enough to make it so -->
+   <literal>LET</literal> extends syntax defined in the SQL
+   standard. The standard knows <literal>SET</literal> command,
+   that is used for different purpouse in PostgreSQL.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <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/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 5317f8ccba..8435e05957 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -108,6 +108,12 @@ REVOKE [ GRANT OPTION FOR ]
 REVOKE [ ADMIN OPTION FOR ]
     <replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable class="parameter">role_name</replaceable> [, ...]
     [ CASCADE | RESTRICT ]
+
+REVOKE [ GRANT OPTION FOR ]
+    { { READ | WRITE } [, ...] | ALL [ PRIVILEGES ] }
+    ON VARIABLE <replaceable>variable_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 db4f4167e3..5fb82df51e 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;
@@ -183,6 +186,7 @@
    &grant;
    &importForeignSchema;
    &insert;
+   &let;
    &listen;
    &load;
    &lock;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 0865240f11..1f7c4d1223 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -19,7 +19,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_publication.o pg_range.o \
 	   pg_db_role_setting.o pg_shdepend.o pg_subscription.o pg_type.o \
-	   storage.o toasting.o
+	   pg_variable.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -46,7 +46,7 @@ CATALOG_HEADERS := \
 	pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
 	pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	pg_sequence.h pg_publication.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) schemapg.h
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 578e4c6592..86917e15a8 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -57,6 +57,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_transform.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
@@ -112,6 +113,7 @@ static void ExecGrant_Largeobject(InternalGrant *grantStmt);
 static void ExecGrant_Namespace(InternalGrant *grantStmt);
 static void ExecGrant_Tablespace(InternalGrant *grantStmt);
 static void ExecGrant_Type(InternalGrant *grantStmt);
+static void ExecGrant_Variable(InternalGrant *grantStmt);
 
 static void SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames);
 static void SetDefaultACL(InternalDefaultACL *iacls);
@@ -284,6 +286,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
 		case OBJECT_TYPE:
 			whole_mask = ACL_ALL_RIGHTS_TYPE;
 			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 */
@@ -507,6 +512,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
 			all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER;
 			errormsg = gettext_noop("invalid privilege type %s for foreign server");
 			break;
+		case OBJECT_VARIABLE:
+			all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			errormsg = gettext_noop("invalid privilege type %s for schema variable");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) stmt->objtype);
@@ -609,6 +618,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 		case OBJECT_TABLESPACE:
 			ExecGrant_Tablespace(istmt);
 			break;
+		case OBJECT_VARIABLE:
+			ExecGrant_Variable(istmt);
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
@@ -768,6 +780,16 @@ objectNamesToOids(ObjectType objtype, List *objnames)
 				objects = lappend_oid(objects, srvid);
 			}
 			break;
+		case OBJECT_VARIABLE:
+			foreach(cell, objnames)
+			{
+				RangeVar   *varvar = (RangeVar *) lfirst(cell);
+				Oid			relOid;
+
+				relOid = lookup_variable(varvar->schemaname, varvar->relname, false);
+				objects = lappend_oid(objects, relOid);
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) objtype);
@@ -855,6 +877,31 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 					heap_close(rel, AccessShareLock);
 				}
 				break;
+			case OBJECT_VARIABLE:
+				{
+					ScanKeyData key;
+					Relation	rel;
+					HeapScanDesc scan;
+					HeapTuple	tuple;
+
+					ScanKeyInit(&key,
+								Anum_pg_variable_varnamespace,
+								BTEqualStrategyNumber, F_OIDEQ,
+								ObjectIdGetDatum(namespaceId));
+
+					rel = heap_open(VariableRelationId, AccessShareLock);
+					scan = heap_beginscan_catalog(rel, 1, &key);
+
+					while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+					{
+						objects = lappend_oid(objects, HeapTupleGetOid(tuple));
+					}
+
+					heap_endscan(scan);
+					heap_close(rel, AccessShareLock);
+				}
+				break;
+
 			default:
 				/* should not happen */
 				elog(ERROR, "unrecognized GrantStmt.objtype: %d",
@@ -1018,6 +1065,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 schema variable");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) action->objtype);
@@ -1215,6 +1266,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 objtype: %d",
 				 (int) iacls->objtype);
@@ -1441,6 +1498,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",
@@ -3266,6 +3326,129 @@ ExecGrant_Type(InternalGrant *istmt)
 	heap_close(relation, RowExclusiveLock);
 }
 
+static void
+ExecGrant_Variable(InternalGrant *istmt)
+{
+	Relation	relation;
+	ListCell   *cell;
+
+	if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
+		istmt->privileges = ACL_ALL_RIGHTS_VARIABLE;
+
+	relation = heap_open(VariableRelationId, RowExclusiveLock);
+
+	foreach(cell, istmt->objects)
+	{
+		Oid			varId = lfirst_oid(cell);
+		Form_pg_variable pg_variable_tuple;
+		Datum		aclDatum;
+		bool		isNull;
+		AclMode		avail_goptions;
+		AclMode		this_privileges;
+		Acl		   *old_acl;
+		Acl		   *new_acl;
+		Oid			grantorId;
+		Oid			ownerId;
+		HeapTuple	tuple;
+		HeapTuple	newtuple;
+		Datum		values[Natts_pg_variable];
+		bool		nulls[Natts_pg_variable];
+		bool		replaces[Natts_pg_variable];
+		int			noldmembers;
+		int			nnewmembers;
+		Oid		   *oldmembers;
+		Oid		   *newmembers;
+
+		tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varId));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for schema variables %u", varId);
+
+		pg_variable_tuple = (Form_pg_variable) GETSTRUCT(tuple);
+
+		/*
+		 * Get owner ID and working copy of existing ACL. If there's no ACL,
+		 * substitute the proper default.
+		 */
+		ownerId = pg_variable_tuple->varowner;
+		aclDatum = SysCacheGetAttr(VARIABLEOID, tuple, Anum_pg_variable_varacl,
+								   &isNull);
+		if (isNull)
+		{
+			old_acl = acldefault(OBJECT_VARIABLE, ownerId);
+			/* There are no old member roles according to the catalogs */
+			noldmembers = 0;
+			oldmembers = NULL;
+		}
+		else
+		{
+			old_acl = DatumGetAclPCopy(aclDatum);
+			/* Get the roles mentioned in the existing ACL */
+			noldmembers = aclmembers(old_acl, &oldmembers);
+		}
+
+		/* Determine ID to do the grant as, and available grant options */
+		select_best_grantor(GetUserId(), istmt->privileges,
+							old_acl, ownerId,
+							&grantorId, &avail_goptions);
+
+		/*
+		 * Restrict the privileges to what we can actually grant, and emit the
+		 * standards-mandated warning and error messages.
+		 */
+		this_privileges =
+			restrict_and_check_grant(istmt->is_grant, avail_goptions,
+									 istmt->all_privs, istmt->privileges,
+									 varId, grantorId, OBJECT_VARIABLE,
+									 NameStr(pg_variable_tuple->varname),
+									 0, NULL);
+
+		/*
+		 * Generate new ACL.
+		 */
+		new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
+									   istmt->grant_option, istmt->behavior,
+									   istmt->grantees, this_privileges,
+									   grantorId, ownerId);
+
+		/*
+		 * We need the members of both old and new ACLs so we can correct the
+		 * shared dependency information.
+		 */
+		nnewmembers = aclmembers(new_acl, &newmembers);
+
+		/* finished building new ACL value, now insert it */
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, false, sizeof(nulls));
+		MemSet(replaces, false, sizeof(replaces));
+
+		replaces[Anum_pg_variable_varacl - 1] = true;
+		values[Anum_pg_variable_varacl - 1] = PointerGetDatum(new_acl);
+
+		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
+									 nulls, replaces);
+
+		CatalogTupleUpdate(relation, &newtuple->t_self, newtuple);
+
+		/* Update initial privileges for extensions */
+		recordExtensionInitPriv(varId, VariableRelationId, 0, new_acl);
+
+		/* Update the shared dependency ACL info */
+		updateAclDependencies(VariableRelationId, varId, 0,
+							  ownerId,
+							  noldmembers, oldmembers,
+							  nnewmembers, newmembers);
+
+		ReleaseSysCache(tuple);
+
+		pfree(new_acl);
+
+		/* prevent error when processing duplicate objects */
+		CommandCounterIncrement();
+	}
+
+	heap_close(relation, RowExclusiveLock);
+}
+
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3298,6 +3481,10 @@ string_to_privilege(const char *privname)
 		return ACL_CONNECT;
 	if (strcmp(privname, "rule") == 0)
 		return 0;				/* ignore old RULE privileges */
+	if (strcmp(privname, "read") == 0)
+		return ACL_READ;
+	if (strcmp(privname, "write") == 0)
+		return ACL_WRITE;
 	ereport(ERROR,
 			(errcode(ERRCODE_SYNTAX_ERROR),
 			 errmsg("unrecognized privilege type \"%s\"", privname)));
@@ -3333,6 +3520,10 @@ privilege_to_string(AclMode privilege)
 			return "TEMP";
 		case ACL_CONNECT:
 			return "CONNECT";
+		case ACL_READ:
+			return "READ";
+		case ACL_WRITE:
+			return "WRITE";
 		default:
 			elog(ERROR, "unrecognized privilege: %d", (int) privilege);
 	}
@@ -3456,6 +3647,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 schema variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("permission denied for view %s");
 						break;
@@ -3566,6 +3760,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 schema variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("must be owner of view %s");
 						break;
@@ -3710,6 +3907,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return pg_type_aclmask(table_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return pg_variable_aclmask(table_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized objtype: %d",
 				 (int) objtype);
@@ -4499,6 +4698,67 @@ pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how)
 	return result;
 }
 
+/*
+ * Exported routine for examining a user's privileges for a variable.
+ */
+AclMode
+pg_variable_aclmask(Oid var_oid, Oid roleid, AclMode mask, AclMaskHow how)
+{
+	AclMode		result;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+	Acl		   *acl;
+	Oid			ownerId;
+
+	Form_pg_variable varForm;
+
+	/* Bypass permission checks for superusers */
+	if (superuser_arg(roleid))
+		return mask;
+
+	/*
+	 * Must get the type's tuple from pg_type
+	 */
+	tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(var_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("variable with OID %u does not exist",
+						var_oid)));
+	varForm = (Form_pg_variable) GETSTRUCT(tuple);
+
+	/*
+	 * Now get the type's owner and ACL from the tuple
+	 */
+	ownerId = varForm->varowner;
+
+	aclDatum = SysCacheGetAttr(VARIABLEOID, tuple,
+							   Anum_pg_variable_varacl, &isNull);
+	if (isNull)
+	{
+		/* No ACL, so build default ACL */
+		acl = acldefault(OBJECT_VARIABLE, ownerId);
+		aclDatum = (Datum) 0;
+	}
+	else
+	{
+		/* detoast rel's ACL if necessary */
+		acl = DatumGetAclP(aclDatum);
+	}
+
+	result = aclmask(acl, roleid, ownerId, mask, how);
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+
 /*
  * Exported routine for checking a user's access privileges to a column
  *
@@ -4744,6 +5004,18 @@ pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
 		return ACLCHECK_NO_PRIV;
 }
 
+/*
+ * Exported routine for checking a user's access privileges to a variable
+ */
+AclResult
+pg_variable_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
+{
+	if (pg_variable_aclmask(type_oid, roleid, mode, ACLMASK_ANY) != 0)
+		return ACLCHECK_OK;
+	else
+		return ACLCHECK_NO_PRIV;
+}
+
 /*
  * Ownership check for a relation (specified by OID).
  */
@@ -5361,6 +5633,33 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid)
 	return has_privs_of_role(roleid, ownerId);
 }
 
+/*
+ * Ownership check for a schema variables (specified by OID).
+ */
+bool
+pg_variable_ownercheck(Oid db_oid, Oid roleid)
+{
+	HeapTuple	tuple;
+	Oid			ownerId;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return true;
+
+	tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(db_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_DATABASE),
+				 errmsg("variable with OID %u does not exist", db_oid)));
+
+	ownerId = ((Form_pg_variable) GETSTRUCT(tuple))->varowner;
+
+	ReleaseSysCache(tuple);
+
+	return has_privs_of_role(roleid, ownerId);
+}
+
+
 /*
  * Check whether specified role has CREATEROLE privilege (or is a superuser)
  *
@@ -5486,6 +5785,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 4f1d365357..782ddb1655 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -59,6 +59,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/comment.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
@@ -67,6 +68,7 @@
 #include "commands/proclang.h"
 #include "commands/publicationcmds.h"
 #include "commands/schemacmds.h"
+#include "commands/schemavariable.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
 #include "commands/trigger.h"
@@ -1280,6 +1282,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			DropTransformById(object->objectId);
 			break;
 
+		case OCLASS_VARIABLE:
+			RemoveVariableById(object->objectId);
+			break;
+
 			/*
 			 * These global object types are not supported here.
 			 */
@@ -2537,6 +2543,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case TransformRelationId:
 			return OCLASS_TRANSFORM;
+
+		case VariableRelationId:
+			return OCLASS_VARIABLE;
 	}
 
 	/* shouldn't get here */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 3971346e73..90d16e263f 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -39,6 +39,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 "funcapi.h"
 #include "mb/pg_wchar.h"
@@ -755,6 +756,71 @@ RelationIsVisible(Oid relid)
 	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 schema 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 relation 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
@@ -2776,6 +2842,202 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
+/*
+ * When we know a variable name, then we can find variable simply
+ */
+Oid
+lookup_variable(const char *nspname, const char *varname, bool missing_ok)
+{
+	Oid			namespaceId;
+	Oid			varoid		= InvalidOid;
+	ListCell   *l;
+
+	if (nspname)
+	{
+		namespaceId = LookupExplicitNamespace(nspname, missing_ok);
+		if (!OidIsValid(namespaceId))
+			return InvalidOid;
+
+		varoid = GetSysCacheOid2(VARIABLENAMENSP,
+								  PointerGetDatum(varname),
+								  ObjectIdGetDatum(namespaceId));
+	}
+	else
+	{
+		/* search for it in search path */
+		recomputeNamespacePath();
+
+		foreach(l, activeSearchPath)
+		{
+			namespaceId = lfirst_oid(l);
+
+			varoid = GetSysCacheOid2(VARIABLENAMENSP,
+									  PointerGetDatum(varname),
+									  ObjectIdGetDatum(namespaceId));
+
+			if (OidIsValid(varoid))
+				break;
+		}
+	}
+
+	if (!OidIsValid(varoid) && !missing_ok)
+	{
+		if (nspname)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("variable \"%s\".\"%s\" does not exist",
+								nspname, varname)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("variable \"%s\" does not exist",
+								varname)));
+	}
+
+	return varoid;
+}
+
+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;
+}
+
+/*
+ * identify_variable
+ *
+ * Returns oid of not ambigonuous variable specified by qualified path
+ * or InvalidOid. When the path is ambigonuous, then not_uniq flag is
+ * is true.
+ */
+Oid
+identify_variable(List *names, char **attrname, bool *not_uniq)
+{
+	char   *a = NULL;
+	char   *b = NULL;
+	char   *c = NULL;
+	char   *d = NULL;
+	Oid		varoid_without_attr;
+	Oid		varoid_with_attr;
+
+	*not_uniq = false;
+
+	switch (list_length(names))
+	{
+		case 1:
+			a = strVal(linitial(names));
+			return lookup_variable(NULL, a, true);
+
+		case 2:
+			a = strVal(linitial(names));
+			b = strVal(lsecond(names));
+
+			/*
+			 * a.b can mean "schema"."variable" or "variable"."field",
+			 * Check both variants, and returns InvalidOid with not_uniq
+			 * flag, when both interpretations are possible.
+			 */
+			varoid_without_attr = lookup_variable(a, b, true);
+			varoid_with_attr = lookup_variable(NULL, a, true);
+
+			if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+			{
+				*not_uniq = true;
+				return InvalidOid;
+			}
+			else if (OidIsValid(varoid_without_attr))
+			{
+				*attrname = NULL;
+				return varoid_without_attr;
+			}
+			else
+			{
+				*attrname = b;
+				return varoid_with_attr;
+			}
+			break;
+
+		case 3:
+			a = strVal(linitial(names));
+			b = strVal(lsecond(names));
+			c = strVal(lthird(names));
+
+			/*
+			 * a.b.c can mean "catalog"."schema"."variable" or "schema"."variable"."field",
+			 * Check both variants, and returns InvalidOid with not_uniq
+			 * flag, when both interpretations are possible.
+			 */
+			varoid_without_attr = lookup_variable(b, c, true);
+			varoid_with_attr = lookup_variable(a, b, true);
+
+			if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+			{
+				*not_uniq = true;
+				return InvalidOid;
+			}
+			else if (OidIsValid(varoid_without_attr))
+			{
+				*attrname = NULL;
+
+				/*
+				 * We in this case a "a" is used as catalog name, check it.
+				 */
+				if (strcmp(a, get_database_name(MyDatabaseId)) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("cross-database references are not implemented: %s",
+									NameListToString(names))));
+
+				return varoid_without_attr;
+			}
+			else
+			{
+				*attrname = c;
+				return varoid_with_attr;
+			}
+			break;
+
+		case 4:
+			a = strVal(linitial(names));
+			b = strVal(lsecond(names));
+			c = strVal(lthird(names));
+			d = strVal(lfourth(names));
+
+			/*
+			 * We in this case a "a" is used as catalog name, check it.
+			 */
+			if (strcmp(a, get_database_name(MyDatabaseId)) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cross-database references are not implemented: %s",
+								NameListToString(names))));
+
+			*attrname = d;
+			return lookup_variable(b, c, true);
+
+		default:
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("improper qualified name (too many dotted names): %s",
+							NameListToString(names))));
+			break;
+	}
+}
 
 /*
  * DeconstructQualifiedName
@@ -4484,3 +4746,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 7db942dcba..cc3d415e61 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -58,6 +58,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"
@@ -489,6 +490,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,		/* no ACL (same as relation) */
 		OBJECT_STATISTIC_EXT,
 		true
+	},
+	{
+		VariableRelationId,
+		VariableObjectIndexId,
+		VARIABLEOID,
+		VARIABLENAMENSP,
+		Anum_pg_variable_varname,
+		Anum_pg_variable_varnamespace,
+		Anum_pg_variable_varowner,
+		Anum_pg_variable_varacl,
+		OBJECT_VARIABLE,
+		true
 	}
 };
 
@@ -714,6 +727,10 @@ static const struct object_type_map
 	/* OBJECT_STATISTIC_EXT */
 	{
 		"statistics object", OBJECT_STATISTIC_EXT
+	},
+	/* OCLASS_VARIABLE */
+	{
+		"schema variable", OBJECT_VARIABLE
 	}
 };
 
@@ -739,6 +756,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,
@@ -996,6 +1014,10 @@ 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;
+
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				/* placate compiler, in case it thinks elog might return */
@@ -1848,16 +1870,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)));
 	}
 
 	/*
@@ -1942,6 +1968,24 @@ textarray_to_strvaluelist(ArrayType *arr)
 	return list;
 }
 
+/*
+ * Find the ObjectAddress for a type or domain
+ */
+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 = lookup_variable(nspname, varname, missing_ok);
+
+	return address;
+}
+
 /*
  * SQL-callable version of get_object_address
  */
@@ -2131,6 +2175,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:
@@ -2415,6 +2460,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			if (!pg_statistics_object_ownercheck(address.objectId, roleid))
 				aclcheck_error_type(ACLCHECK_NOT_OWNER, address.objectId);
 			break;
+		case OBJECT_VARIABLE:
+			if (!pg_variable_ownercheck(address.objectId, roleid))
+				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
+							   NameListToString(castNode(List, object)));
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) objtype);
@@ -3157,6 +3207,32 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
+				Form_pg_variable	varform;
+
+				tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for schema 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, _("schema variable %s"),
+								 quote_qualified_identifier(nspname,
+															NameStr(varform->varname)));
+
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		case OCLASS_TSPARSER:
 			{
 				HeapTuple	tup;
@@ -3422,6 +3498,16 @@ getObjectDescription(const ObjectAddress *object)
 										 _("default privileges on new schemas belonging to role %s"),
 										 rolename);
 						break;
+					case DEFACLOBJ_VARIABLE:
+						if (nspname)
+							appendStringInfo(&buffer,
+											 _("default privileges on new variables belonging to role %s in schema %s"),
+											 rolename, nspname);
+						else
+							appendStringInfo(&buffer,
+											 _("default privileges on new variables belonging to role %s"),
+											 rolename);
+						break;
 					default:
 						/* shouldn't get here */
 						if (nspname)
@@ -4070,6 +4156,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "transform");
 			break;
 
+		case OCLASS_VARIABLE:
+			appendStringInfoString(&buffer, "schema variable");
+			break;
+
 			/*
 			 * There's intentionally no default: case here; we want the
 			 * compiler to warn if a new OCLASS hasn't been handled above.
@@ -4962,6 +5052,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 						appendStringInfoString(&buffer,
 											   " on schemas");
 						break;
+					case DEFACLOBJ_VARIABLE:
+						appendStringInfoString(&buffer,
+											   " on variables");
+						break;
 				}
 
 				if (objname)
@@ -5121,6 +5215,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+		case OCLASS_VARIABLE:
+			{
+				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 schema 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;
+			}
+
 			/*
 			 * There's intentionally no default: case here; we want the
 			 * compiler to warn if a new OCLASS hasn't been handled above.
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
new file mode 100644
index 0000000000..ea6b0960a3
--- /dev/null
+++ b/src/backend/catalog/pg_variable.c
@@ -0,0 +1,309 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.c
+ *		schema variables
+ *
+ * Portions Copyright (c) 1996-2018, 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 "miscadmin.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/xact.h"
+
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
+
+#include "nodes/makefuncs.h"
+
+#include "storage/lmgr.h"
+
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/pg_lsn.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * Returns name of schema variable. When variable is not on path,
+ * then the name is qualified.
+ */
+char *
+schema_variable_get_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 = NULL;
+	else
+		nspname = get_namespace_name(varform->varnamespace);
+
+	result = quote_qualified_identifier(nspname, varname);
+
+	ReleaseSysCache(tup);
+
+	return result;
+}
+
+/*
+ * Returns varname field of pg_variable
+ */
+char *
+get_schema_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 variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	varname = NameStr(varform->varname);
+
+	ReleaseSysCache(tup);
+
+	return varname;
+}
+
+/*
+ * Returns type, typmod of schema variable
+ */
+void
+get_schema_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 variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	*typid = varform->vartype;
+	*typmod = varform->vartypmod;
+	*collid = varform->varcollation;
+
+	ReleaseSysCache(tup);
+
+	return;
+}
+
+/*
+ * Fetch all fields of schema variable from the syscache.
+ */
+Variable *
+GetVariable(Oid varid, bool missing_ok)
+{
+	HeapTuple	tup;
+	Variable *var;
+	Form_pg_variable varform;
+	Datum		aclDatum;
+	Datum		defexprDatum;
+	bool		isnull;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+	{
+		if (missing_ok)
+			return NULL;
+
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+	}
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	var = (Variable *) palloc(sizeof(Variable));
+	var->oid = varid;
+	var->name = pstrdup(NameStr(varform->varname));
+	var->namespace = varform->varnamespace;
+	var->typid = varform->vartype;
+	var->typmod = varform->vartypmod;
+	var->owner = varform->varowner;
+	var->collation = varform->varcollation;
+
+	/* Get defexpr */
+	defexprDatum = SysCacheGetAttr(VARIABLEOID,
+							tup,
+							Anum_pg_variable_vardefexpr,
+							&isnull);
+
+	if (!isnull)
+		var->defexpr = stringToNode(TextDatumGetCString(defexprDatum));
+	else
+		var->defexpr = NULL;
+
+	/* Get varacl */
+	aclDatum = SysCacheGetAttr(VARIABLEOID,
+							tup,
+							Anum_pg_variable_varacl,
+							&isnull);
+	if (!isnull)
+		var->acl = DatumGetAclPCopy(aclDatum);
+	else
+		var->acl = NULL;
+
+	ReleaseSysCache(tup);
+
+	return var;
+}
+
+ObjectAddress
+VariableCreate(const char *varName,
+			   Oid varNamespace,
+			   Oid varType,
+			   int32 varTypmod,
+			   Oid varOwner,
+			   Oid varCollation,
+			   Node *varDefexpr,
+			   bool if_not_exists)
+{
+	Acl		   *varacl;
+	NameData	varname;
+	bool		nulls[Natts_pg_variable];
+	Datum		values[Natts_pg_variable];
+	Relation	rel;
+	HeapTuple	tup,
+				oldtup;
+	TupleDesc	tupdesc;
+	ObjectAddress	myself,
+					referenced;
+	Oid			retval;
+	int			i;
+
+	for (i = 0; i < Natts_pg_variable; i++)
+	{
+		nulls[i] = false;
+		values[i] = (Datum) 0;
+	}
+
+	namestrcpy(&varname, varName);
+	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);
+	/* proacl will be determined later */
+
+	if (varDefexpr)
+		values[Anum_pg_variable_vardefexpr - 1] = CStringGetTextDatum(nodeToString(varDefexpr));
+	else
+		nulls[Anum_pg_variable_vardefexpr - 1] = true;
+
+	rel = heap_open(VariableRelationId, RowExclusiveLock);
+	tupdesc = RelationGetDescr(rel);
+
+	oldtup = SearchSysCache2(VARIABLENAMENSP,
+							 PointerGetDatum(varName),
+							 ObjectIdGetDatum(varNamespace));
+
+	if (HeapTupleIsValid(oldtup))
+	{
+		if (if_not_exists)
+			ereport(NOTICE,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("schema variable \"%s\" already exists, skipping",
+							varName)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("schema variable \"%s\" already exists",
+							varName)));
+
+		heap_freetuple(oldtup);
+		heap_close(rel, RowExclusiveLock);
+
+		return InvalidObjectAddress;
+	}
+
+	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;
+
+	tup = heap_form_tuple(tupdesc, values, nulls);
+	CatalogTupleInsert(rel, tup);
+
+	retval = HeapTupleGetOid(tup);
+
+	myself.classId = VariableRelationId;
+	myself.objectId = retval;
+	myself.objectSubId = 0;
+
+	/* dependency on namespace */
+	referenced.classId = NamespaceRelationId;
+	referenced.objectId = varNamespace;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/* dependency on used type */
+	referenced.classId = TypeRelationId;
+	referenced.objectId = varType;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/* dependency on any roles mentioned in ACL */
+	if (varacl != NULL)
+	{
+		int			nnewmembers;
+		Oid		   *newmembers;
+
+		nnewmembers = aclmembers(varacl, &newmembers);
+		updateAclDependencies(VariableRelationId, retval, 0,
+							  varOwner,
+							  0, NULL,
+							  nnewmembers, newmembers);
+	}
+
+	/* dependency on extension */
+	recordDependencyOnCurrentExtension(&myself, false);
+
+	heap_freetuple(tup);
+
+	/* Post creation hook for new function */
+	InvokeObjectPostCreateHook(VariableRelationId, retval, 0);
+
+	heap_close(rel, RowExclusiveLock);
+
+	return myself;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 4a6c99e090..2cb5b1172d 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -18,7 +18,7 @@ OBJS = amcmds.o aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	policy.o portalcmds.o prepare.o proclang.o publicationcmds.o \
-	schemacmds.o seclabel.o sequence.o statscmds.o subscriptioncmds.o \
+	schemacmds.o seclabel.o sequence.o schemavariable.o statscmds.o subscriptioncmds.o \
 	tablecmds.o tablespace.o trigger.o tsearchcmds.o typecmds.o user.o \
 	vacuum.o vacuumlazy.o variable.o view.o
 
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index eff325cc7d..a9d5e5e0ad 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -387,6 +387,7 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_PUBLICATION:
 		case OBJECT_SUBSCRIPTION:
+		case OBJECT_VARIABLE:
 			{
 				ObjectAddress address;
 				Relation	catalog;
@@ -504,6 +505,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_VARIABLE:
 			{
 				Relation	catalog;
 				Relation	relation;
@@ -594,6 +596,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 		case OCLASS_TSDICT:
 		case OCLASS_TSTEMPLATE:
 		case OCLASS_TSCONFIG:
+		case OCLASS_VARIABLE:
 			{
 				Relation	catalog;
 
@@ -852,6 +855,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 		case OBJECT_TABLESPACE:
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSCONFIGURATION:
+		case OBJECT_VARIABLE:
 			{
 				Relation	catalog;
 				Relation	relation;
diff --git a/src/backend/commands/discard.c b/src/backend/commands/discard.c
index 01a999c2ac..fec2495e93 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/schemavariable.h"
 #include "utils/guc.h"
 #include "utils/portal.h"
 
@@ -48,6 +49,10 @@ DiscardCommand(DiscardStmt *stmt, bool isTopLevel)
 			ResetTempTableNamespace();
 			break;
 
+		case DISCARD_VARIABLES:
+			ResetSchemaVariableCache();
+			break;
+
 		default:
 			elog(ERROR, "unrecognized DISCARD target: %d", stmt->target);
 	}
@@ -75,4 +80,5 @@ DiscardAll(bool isTopLevel)
 	ResetPlanCache();
 	ResetTempTableNamespace();
 	ResetSequenceCaches();
+	ResetSchemaVariableCache();
 }
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index eecc85d14e..426df246b3 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -126,6 +126,7 @@ static event_trigger_support_data event_trigger_support[] = {
 	{"TEXT SEARCH TEMPLATE", true},
 	{"TYPE", true},
 	{"USER MAPPING", true},
+	{"VARIABLE", true},
 	{"VIEW", true},
 	{NULL, false}
 };
@@ -297,7 +298,8 @@ check_ddl_tag(const char *tag)
 		pg_strcasecmp(tag, "REVOKE") == 0 ||
 		pg_strcasecmp(tag, "DROP OWNED") == 0 ||
 		pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0 ||
-		pg_strcasecmp(tag, "SECURITY LABEL") == 0)
+		pg_strcasecmp(tag, "SECURITY LABEL") == 0 ||
+		pg_strcasecmp(tag, "CREATE VARIABLE") == 0)
 		return EVENT_TRIGGER_COMMAND_TAG_OK;
 
 	/*
@@ -1146,6 +1148,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_TYPE:
 		case OBJECT_USER_MAPPING:
+		case OBJECT_VARIABLE:
 		case OBJECT_VIEW:
 			return true;
 
@@ -1209,6 +1212,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_PUBLICATION_REL:
 		case OCLASS_SUBSCRIPTION:
 		case OCLASS_TRANSFORM:
+		case OCLASS_VARIABLE:
 			return true;
 
 			/*
@@ -2244,6 +2248,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:
@@ -2326,6 +2332,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/prepare.c b/src/backend/commands/prepare.c
index b945b1556a..eb8c08baf3 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -151,6 +151,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString,
 		case CMD_INSERT:
 		case CMD_UPDATE:
 		case CMD_DELETE:
+		case CMD_PLAN_UTILITY:
 			/* OK */
 			break;
 		default:
diff --git a/src/backend/commands/schemavariable.c b/src/backend/commands/schemavariable.c
new file mode 100644
index 0000000000..6bdd200e40
--- /dev/null
+++ b/src/backend/commands/schemavariable.c
@@ -0,0 +1,492 @@
+#include "postgres.h"
+#include "miscadmin.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/xact.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_variable.h"
+#include "commands/schemavariable.h"
+#include "executor/executor.h"
+#include "executor/svariableReceiver.h"
+#include "nodes/execnodes.h"
+#include "optimizer/planner.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/datum.h"
+#include "utils/inval.h"
+#include "utils/memutils.h"
+#include "utils/lsyscache.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+/*
+ * The content of variables is not transactional. Due this fact the
+ * implementation of DROP can be simple, because although DROP VARIABLE
+ * can be reverted, the content of variable can be lost. In this example,
+ * DROP VARIABLE is same like reset variable.
+ */
+
+typedef struct SchemaVariableData
+{
+	Oid			varid;			/* pg_variable OID of this sequence (hash key) */
+	Oid			typid;			/* OID of the data type */
+	int32		typmod;
+	int16		typlen;
+	bool		typbyval;
+	bool		isnull;
+	bool		freeval;
+	Datum		value;
+	bool		is_rowtype;		/* true when variable is composite */
+	bool		is_valid;		/* true when variable was successfuly initialized */
+} SchemaVariableData;
+
+typedef SchemaVariableData *SchemaVariable;
+
+static HTAB *schemavarhashtab = NULL;		/* hash table for session variables */
+static MemoryContext SchemaVariableMemoryContext = NULL;
+
+static bool first_time = true;
+static void create_schemavar_hashtable(void);
+static bool clean_cache_req = false;
+
+static void clean_cache(void);
+static void force_clean_cache(XactEvent event, void *arg);
+
+
+/*
+ * Save info about ncessity to clean hash table, because some
+ * schema variable was dropped. Don't do here more, recheck
+ * needs to be in transaction state.
+ */
+static void
+InvalidateSchemaVarCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+	if (cacheid != VARIABLEOID)
+		return;
+
+	clean_cache_req = true;
+}
+
+static void
+force_clean_cache(XactEvent event, void *arg)
+{
+	/*
+	 * should continue only in transaction time, when
+	 * syscache is available.
+	 */
+	if (clean_cache_req && IsTransactionState())
+	{
+		clean_cache();
+		clean_cache_req = false;
+	}
+}
+
+static void
+clean_cache(void)
+{
+	HASH_SEQ_STATUS status;
+	SchemaVariable		var;
+
+	if (!schemavarhashtab)
+		return;
+
+	hash_seq_init(&status, schemavarhashtab);
+
+	/*
+	 * Every valid variable have to have entry in system
+	 * catalog. Removed if there is nothing.
+	 */
+	while ((var = (SchemaVariable) hash_seq_search(&status)) != NULL)
+	{
+		HeapTuple	tp = InvalidOid;
+
+		tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(var->varid));
+		if (!HeapTupleIsValid(tp))
+		{
+			elog(DEBUG1, "variable %d is removed from cache", var->varid);
+
+			if (var->freeval)
+			{
+				pfree(DatumGetPointer(var->value));
+				var->freeval = false;
+			}
+
+			if (hash_search(schemavarhashtab,
+								(void *) &var->varid,
+								HASH_REMOVE,
+								NULL) == NULL)
+				elog(DEBUG1, "hash table corrupted");
+		}
+		else
+			ReleaseSysCache(tp);
+	}
+}
+
+/*
+ * Create the hash table for storing schema variables
+ */
+static void
+create_schemavar_hashtable(void)
+{
+	HASHCTL		ctl;
+
+	/* set callbacks */
+	if (first_time)
+	{
+		CacheRegisterSyscacheCallback(VARIABLEOID,
+									  InvalidateSchemaVarCacheCallback,
+									  (Datum) 0);
+
+		RegisterXactCallback(force_clean_cache, NULL);
+
+		first_time = false;
+	}
+
+	/* needs own long life memory context */
+	if (SchemaVariableMemoryContext == NULL)
+	{
+		SchemaVariableMemoryContext = AllocSetContextCreate(TopMemoryContext,
+															"schema variables",
+															ALLOCSET_START_SMALL_SIZES);
+	}
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(SchemaVariableData);
+	ctl.hcxt = SchemaVariableMemoryContext;
+
+	schemavarhashtab = hash_create("Schema variables", 64, &ctl,
+										HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+/*
+ * Fast drop complete content of schema variables
+ */
+void
+ResetSchemaVariableCache(void)
+{
+	if (schemavarhashtab)
+	{
+		hash_destroy(schemavarhashtab);
+		schemavarhashtab = NULL;
+	}
+
+	if (SchemaVariableMemoryContext != NULL)
+	{
+		MemoryContextReset(SchemaVariableMemoryContext);
+	}
+}
+
+/*
+ * Drop variable by OID
+ */
+void
+RemoveVariableById(Oid varid)
+{
+	Relation	rel;
+	HeapTuple	tup;
+
+	rel = heap_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);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Creates new variable - entry in pg_catalog.pg_variable table
+ */
+ObjectAddress
+DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt *stmt)
+{
+	Oid			namespaceid;
+	AclResult	aclresult;
+	Oid			typid;
+	int32		typmod;
+	Oid			varowner = GetUserId();
+	Oid			collation;
+	Oid			typcollation;
+
+	Node	   *cooked_default = NULL;
+
+	namespaceid =
+		RangeVarGetAndCheckCreationNamespace(stmt->variable, NoLock, NULL);
+
+	typenameTypeIdAndMod(pstate, stmt->typeName, &typid, &typmod);
+	typcollation = get_typcollation(typid);
+
+	aclresult = pg_type_aclcheck(typid, GetUserId(), ACL_USAGE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error_type(aclresult, 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)));
+
+	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);
+	}
+
+	return VariableCreate(stmt->variable->relname,
+						  namespaceid,
+						  typid,
+						  typmod,
+						  varowner,
+						  collation,
+						  cooked_default,
+						  stmt->if_not_exists);
+}
+
+/*
+ * Try to search value in hash table. If doesn't
+ * exists insert it (and calculate defexpr if exists.
+ */
+static SchemaVariable
+PrepareSchemaVariableForReading(Oid varid)
+{
+	SchemaVariable	svar;
+	Variable	*var;
+	bool			found;
+
+	if (schemavarhashtab == NULL)
+		create_schemavar_hashtable();
+
+	svar = (SchemaVariable) hash_search(schemavarhashtab, &varid,
+											  HASH_ENTER, &found);
+	if (!found)
+	{
+		var = GetVariable(varid, false);
+		get_typlenbyval(var->typid, &svar->typlen, &svar->typbyval);
+
+		svar->varid = varid;
+		svar->typid = var->typid;
+		svar->typmod = var->typmod;
+		svar->isnull = true;
+		svar->freeval = false;
+		svar->value = (Datum) 0;
+		svar->is_rowtype = type_is_rowtype(var->typid);
+
+		/* when we don't need calculate defexpr, value is valid already */
+		svar->is_valid = var->defexpr ? false : true;
+	}
+	else if (!svar->is_valid)
+	{
+		/* we need var to recalculate defexpr */
+		var = GetVariable(varid, false);
+	}
+	else
+		/* we don't need to go to sys cache */
+		var = NULL;
+
+	/*
+	 * Initialize variable when it is necessary. It is fresh
+	 * or last initialization was not successfull.
+	 */
+	if (var != NULL && var->defexpr && !svar->is_valid)
+	{
+		MemoryContext oldcontext = NULL;
+
+		Datum	   value = (Datum) 0;
+		bool	   null;
+		EState	   *estate = NULL;
+		Expr	   *defexpr;
+		ExprState  *defexprs;
+
+		/* Prepare default expr */
+		estate = CreateExecutorState();
+		oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		defexpr = expression_planner((Expr *) var->defexpr);
+		defexprs = ExecInitExpr(defexpr, NULL);
+		value = ExecEvalExprSwitchContext(defexprs, GetPerTupleExprContext(estate), &null);
+
+		MemoryContextSwitchTo(SchemaVariableMemoryContext);
+
+		if (!null)
+		{
+			svar->value = datumCopy(value, svar->typbyval, svar->typlen);
+			svar->freeval = svar->value != value;
+			svar->isnull = false;
+			svar->is_valid = true;
+		}
+		else
+		{
+			svar->isnull = true;
+			svar->is_valid = true;
+		}
+
+		MemoryContextSwitchTo(oldcontext);
+
+		FreeExecutorState(estate);
+	}
+
+	if (!svar->is_valid)
+		elog(ERROR, "the content of variable is not valid");
+
+	return svar;
+}
+
+/*
+ * Returns content of variable. We expext secured access now.
+ * Secure check should be done before.
+ */
+Datum
+GetSchemaVariable(Oid varid, bool *isNull, Oid expected_typid, bool copy)
+{
+	SchemaVariable	svar;
+	Datum			value;
+	bool			isnull;
+
+	svar = PrepareSchemaVariableForReading(varid);
+	Assert(svar != NULL);
+
+	if (expected_typid != svar->typid)
+		elog(ERROR, "type of variable \"%s\" is different than expected",
+				   schema_variable_get_name(varid));
+
+	value = svar->value;
+	isnull = svar->isnull;
+
+	*isNull = isnull;
+
+	if (!isnull && copy)
+		return datumCopy(value, svar->typbyval, svar->typlen);
+
+	return value;
+}
+
+/*
+ * Write value to variable. We expect secured access in this moment.
+ * In this time, we recheck syschache about used type.
+ */
+void
+SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid, int32 typmod)
+{
+	MemoryContext oldcontext = NULL;
+
+	SchemaVariable svar;
+	Oid			var_typid;
+	int32		var_typmod;
+	Oid			var_collid;
+	bool		found;
+
+	if (schemavarhashtab == NULL)
+		create_schemavar_hashtable();
+
+	svar = (SchemaVariable) hash_search(schemavarhashtab, &varid,
+											  HASH_ENTER, &found);
+
+	get_schema_variable_type_typmod_collid(varid,
+										   &var_typid,
+										   &var_typmod,
+										   &var_collid);
+
+	/* check types first */
+	if (var_typid != typid)
+		elog(ERROR, "type of expression is different than schema variable type");
+
+	if (found)
+	{
+		/* release current content first */
+		if (svar->freeval)
+		{
+			pfree(DatumGetPointer(svar->value));
+			svar->value = (Datum) 0;
+			svar->isnull = true;
+			svar->freeval = false;
+		}
+	}
+
+	get_typlenbyval(typid, &svar->typlen, &svar->typbyval);
+
+	svar->varid = varid;
+	svar->typid = typid;
+	svar->typmod = typmod;
+
+	svar->isnull = true;
+	svar->freeval = false;
+	svar->value = (Datum) 0;
+
+	svar->is_rowtype = type_is_rowtype(typid);
+	svar->is_valid = false;
+
+	oldcontext = MemoryContextSwitchTo(SchemaVariableMemoryContext);
+
+	if (!isNull)
+	{
+		svar->value = datumCopy(value, svar->typbyval, svar->typlen);
+		svar->freeval = svar->value != value;
+		svar->isnull = false;
+		svar->is_valid = true;
+	}
+	else
+	{
+		svar->isnull = true;
+		svar->is_valid = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+}
+
+void
+doLetStmt(PlannedStmt *pstmt,
+		  ParamListInfo params,
+		  QueryEnvironment *queryEnv,
+		  const char *queryString)
+{
+	QueryDesc *queryDesc;
+	DestReceiver *dest;
+
+	PushCopiedSnapshot(GetActiveSnapshot());
+	UpdateActiveSnapshotCommandId();
+
+	/* Create dest receiver for LET */
+	dest = CreateDestReceiver(DestVariable);
+
+	SetVariableDestReceiverParams(dest, pstmt->resultVariable);
+
+	/* Create a QueryDesc requesting no output */
+	queryDesc = CreateQueryDesc(pstmt, queryString,
+											GetActiveSnapshot(),
+											InvalidSnapshot,
+											dest, params, queryEnv, 0);
+
+	ExecutorStart(queryDesc, 0);
+	ExecutorRun(queryDesc, ForwardScanDirection, 2L, true);
+	ExecutorFinish(queryDesc);
+	ExecutorEnd(queryDesc);
+
+	FreeQueryDesc(queryDesc);
+
+	PopActiveSnapshot();
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f6210226e9..59dc30f14d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,6 +9650,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_PUBLICATION_REL:
 			case OCLASS_SUBSCRIPTION:
 			case OCLASS_TRANSFORM:
+			case OCLASS_VARIABLE:
 
 				/*
 				 * We don't expect any of these sorts of objects to depend on
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index cc09895fa5..ee8ff7da9e 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -29,6 +29,6 @@ OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \
        nodeCtescan.o nodeNamedtuplestorescan.o nodeWorktablescan.o \
        nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
        nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o \
-       nodeTableFuncscan.o
+       nodeTableFuncscan.o svariableReceiver.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index e284fd71d7..bb9bf53e1c 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -33,6 +33,7 @@
 #include "access/nbtree.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_type.h"
+#include "commands/schemavariable.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -727,6 +728,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				Param	   *param = (Param *) node;
 				ParamListInfo params;
+				AclResult	aclresult;
 
 				switch (param->paramkind)
 				{
@@ -736,6 +738,28 @@ ExecInitExprRec(Expr *node, ExprState *state,
 						scratch.d.param.paramtype = param->paramtype;
 						ExprEvalPushStep(state, &scratch);
 						break;
+
+					case PARAM_VARIABLE:
+
+						/* Check permission to read schema variable */
+						aclresult = pg_variable_aclcheck(param->paramid, GetUserId(), ACL_READ);
+						if (aclresult != ACLCHECK_OK)
+							aclcheck_error(aclresult, OBJECT_VARIABLE,
+											schema_variable_get_name(param->paramid));
+
+						/*
+						 * Using varoid as paramid is not practical. Better to recount
+						 * used schema variables from zero, and later to use paramid like
+						 * offset.
+						 */
+						scratch.opcode = EEOP_PARAM_VARIABLE;
+						scratch.d.vparam.paramid = state->nvariables++;
+						scratch.d.vparam.varoid = param->paramid;
+						scratch.d.vparam.paramtype = param->paramtype;
+
+						ExprEvalPushStep(state, &scratch);
+						break;
+
 					case PARAM_EXTERN:
 
 						/*
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 9d6e25aae5..4462dcc952 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -59,6 +59,7 @@
 #include "access/tuptoaster.h"
 #include "catalog/pg_type.h"
 #include "commands/sequence.h"
+#include "commands/schemavariable.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -351,6 +352,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,
@@ -1007,6 +1009,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_PARAM_VARIABLE)
+		{
+			/* iut of line implementation; too large */
+			ExecEvalParamVariable(state, op, econtext);
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_CASE_TESTVAL)
 		{
 			/*
@@ -2323,6 +2332,79 @@ ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 			 errmsg("no value found for parameter %d", paramId)));
 }
 
+/*
+ * Evaluate a PARAM_VARIABLE parameter
+ */
+void
+ExecEvalParamVariable(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	EState *estate = econtext->ecxt_estate;
+
+	/*
+	 * We should to ensure stable behave of schema variables in queries. It is
+	 * important, because optimizer uses these values as stable, like extern
+	 * parameters, what is nice, because queries are optimized well. So, don't
+	 * try to access variables directly, use this query variable cache.
+	 * This cache cannot be used when EState is shared - PLpgSQL did it for
+	 * simple expressions.
+	 */
+	if (estate && !estate->es_shared)
+	{
+		int		paramid = op->d.vparam.paramid;
+
+		if (estate->es_nvariables == 0)
+		{
+			MemoryContext	old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* initialize estate schema variable cache */
+
+			estate->es_nvariables = state->nvariables;
+			estate->es_varnulls = palloc(sizeof(bool) * state->nvariables);
+			estate->es_vartypes = palloc0(sizeof(Oid) * state->nvariables);
+			estate->es_varvalues = palloc(sizeof(Datum) * state->nvariables);
+
+			MemoryContextSwitchTo(old_cxt);
+		}
+
+		Assert(estate->es_nvariables == state->nvariables);
+		Assert(estate->es_nvariables > paramid);
+
+		if (!OidIsValid(estate->es_vartypes[paramid]))
+		{
+			MemoryContext	old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* copy variable to estate schema variable cache */
+			estate->es_varvalues[paramid] =
+										GetSchemaVariable(op->d.vparam.varoid,
+														  &estate->es_varnulls[paramid],
+														  op->d.vparam.paramtype,
+														  true);
+			estate->es_vartypes[paramid] = op->d.vparam.paramtype;
+
+			MemoryContextSwitchTo(old_cxt);
+		}
+
+		Assert(OidIsValid(estate->es_vartypes[paramid]));
+
+		*op->resvalue = estate->es_varvalues[paramid];
+		*op->resnull = estate->es_varnulls[paramid];
+	}
+	else
+	{
+		Datum		d;
+		bool		isnull;
+
+		/* read content of variable directly */
+		d = GetSchemaVariable(op->d.vparam.varoid,
+							  &isnull,
+							  op->d.vparam.paramtype,
+							  false);
+
+		*op->resvalue = d;
+		*op->resnull = isnull;
+	}
+}
+
 /*
  * Evaluate a SQLValueFunction expression.
  */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index c583e020a0..797c1f43b3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -43,9 +43,11 @@
 #include "access/xact.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_publication.h"
+#include "catalog/pg_variable.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
+#include "executor/svariableReceiver.h"
 #include "foreign/fdwapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
@@ -204,12 +206,18 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	 */
 	estate->es_queryEnv = queryDesc->queryEnv;
 
+	/*
+	 * Result can be stored in schema variable.
+	 */
+	estate->es_result_variable = queryDesc->plannedstmt->resultVariable;
+
 	/*
 	 * If non-read-only query, set the command ID to mark output tuples with
 	 */
 	switch (queryDesc->operation)
 	{
 		case CMD_SELECT:
+		case CMD_PLAN_UTILITY:
 
 			/*
 			 * SELECT FOR [KEY] UPDATE/SHARE and modifying CTEs need to mark
@@ -345,6 +353,7 @@ standard_ExecutorRun(QueryDesc *queryDesc,
 	estate->es_lastoid = InvalidOid;
 
 	sendTuples = (operation == CMD_SELECT ||
+				  OidIsValid(estate->es_result_variable) ||
 				  queryDesc->plannedstmt->hasReturning);
 
 	if (sendTuples)
@@ -924,6 +933,17 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 		estate->es_num_root_result_relations = 0;
 	}
 
+	if (OidIsValid(estate->es_result_variable))
+	{
+		AclResult	aclresult;
+		Oid			varid = estate->es_result_variable;
+
+		/* Ensure this variable is writeable */
+		aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error(aclresult, OBJECT_VARIABLE, schema_variable_get_name(varid));
+	}
+
 	/*
 	 * Similarly, we have to lock relations selected FOR [KEY] UPDATE/SHARE
 	 * before we initialize the plan tree, else we'd be risking lock upgrades.
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 5b3eaec80b..eca7805517 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -102,6 +102,7 @@ CreateExecutorState(void)
 	/*
 	 * Initialize all fields of the Executor State structure
 	 */
+	estate->es_shared = false;
 	estate->es_direction = ForwardScanDirection;
 	estate->es_snapshot = InvalidSnapshot;	/* caller must initialize this */
 	estate->es_crosscheck_snapshot = InvalidSnapshot;	/* no crosscheck */
diff --git a/src/backend/executor/svariableReceiver.c b/src/backend/executor/svariableReceiver.c
new file mode 100644
index 0000000000..0eac4b5d0c
--- /dev/null
+++ b/src/backend/executor/svariableReceiver.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.c
+ *	  An implementation of DestReceiver that stores the result value in
+ *	  a schema variable.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/svariableReceiver.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/tuptoaster.h"
+#include "executor/svariableReceiver.h"
+#include "commands/schemavariable.h"
+
+typedef struct
+{
+	DestReceiver pub;
+	Oid		varid;
+	Oid		typid;
+	int32	typmod;
+	int		typlen;
+	int		slot_offset;
+	int		rows;
+} 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;
+
+	for (i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+
+		if (attr->attisdropped)
+			continue;
+
+		if (++outcols > 1)
+			elog(ERROR, "svariable DestReceiver can take only one attribute");
+
+		myState->typid = attr->atttypid;
+		myState->typmod = attr->atttypmod;
+		myState->typlen = attr->attlen;
+		myState->slot_offset = i;
+	}
+
+	myState->rows = 0;
+}
+
+/*
+ * Receive a tuple from the executor and store it in schema 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->typlen == -1 && !isnull && VARATT_IS_EXTERNAL(DatumGetPointer(value)))
+	{
+		value = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
+													DatumGetPointer(value)));
+		freeval = true;
+	}
+
+	SetSchemaVariable(myState->varid, value, isnull, myState->typid, myState->typmod);
+
+	if (freeval)
+		pfree(DatumGetPointer(value));
+
+	return true;
+}
+
+/*
+ * Clean up at end of an executor run
+ */
+static void
+svariableShutdownReceiver(DestReceiver *self)
+{
+	/* Do nothing */
+}
+
+/*
+ * Destroy receiver when done with it
+ */
+static void
+svariableDestroyReceiver(DestReceiver *self)
+{
+	pfree(self);
+}
+
+/*
+ * Initially create a DestReceiver object.
+ */
+DestReceiver *
+CreateVariableDestReceiver(void)
+{
+	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;
+
+	/* private fields will be set by SetVariableDestReceiverParams */
+
+	return (DestReceiver *) self;
+}
+
+/*
+ * Set parameters for a VariableDestReceiver
+ */
+void
+SetVariableDestReceiverParams(DestReceiver *self, Oid varid)
+{
+	svariableState *myState = (svariableState *) self;
+
+	Assert(myState->pub.mydest == DestVariable);
+	Assert(OidIsValid(varid));
+
+	myState->varid = varid;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7c8220cf65..fcaa2db51a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -93,6 +93,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(nonleafResultRelations);
 	COPY_NODE_FIELD(rootResultRelations);
+	COPY_SCALAR_FIELD(resultVariable);
 	COPY_NODE_FIELD(subplans);
 	COPY_BITMAPSET_FIELD(rewindPlanIDs);
 	COPY_NODE_FIELD(rowMarks);
@@ -3000,6 +3001,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(resultVariable);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasWindowFuncs);
 	COPY_SCALAR_FIELD(hasTargetSRFs);
@@ -3118,6 +3120,18 @@ _copySelectStmt(const SelectStmt *from)
 	return newnode;
 }
 
+static LetStmt *
+_copyLetStmt(const LetStmt *from)
+{
+	LetStmt *newnode = makeNode(LetStmt);
+
+	COPY_NODE_FIELD(target);
+	COPY_NODE_FIELD(selectStmt);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 static SetOperationStmt *
 _copySetOperationStmt(const SetOperationStmt *from)
 {
@@ -5166,6 +5180,9 @@ copyObjectImpl(const void *from)
 		case T_SelectStmt:
 			retval = _copySelectStmt(from);
 			break;
+		case T_LetStmt:
+			retval = _copyLetStmt(from);
+			break;
 		case T_SetOperationStmt:
 			retval = _copySetOperationStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 378f2facb8..3ec472e19b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -949,6 +949,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(canSetTag);
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
+	COMPARE_SCALAR_FIELD(resultVariable);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasWindowFuncs);
 	COMPARE_SCALAR_FIELD(hasTargetSRFs);
@@ -1057,6 +1058,16 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	return true;
 }
 
+static bool
+_equalLetStmt(const LetStmt *a, const LetStmt *b)
+{
+	COMPARE_NODE_FIELD(target);
+	COMPARE_NODE_FIELD(selectStmt);
+
+	return true;
+}
+
+
 static bool
 _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 {
@@ -3225,6 +3236,9 @@ equal(const void *a, const void *b)
 		case T_SelectStmt:
 			retval = _equalSelectStmt(a, b);
 			break;
+		case T_LetStmt:
+			retval = _equalLetStmt(a, b);
+			break;
 		case T_SetOperationStmt:
 			retval = _equalSetOperationStmt(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6269f474d2..46404ff9ac 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -278,6 +278,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(nonleafResultRelations);
 	WRITE_NODE_FIELD(rootResultRelations);
+	WRITE_OID_FIELD(resultVariable);
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(rowMarks);
@@ -2793,6 +2794,16 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(rarg);
 }
 
+static void
+_outLetStmt(StringInfo str, const LetStmt *node)
+{
+	WRITE_NODE_TYPE("LET");
+
+	WRITE_NODE_FIELD(target);
+	WRITE_NODE_FIELD(selectStmt);
+	WRITE_LOCATION_FIELD(location);
+}
+
 static void
 _outFuncCall(StringInfo str, const FuncCall *node)
 {
@@ -2971,6 +2982,7 @@ _outQuery(StringInfo str, const Query *node)
 		appendStringInfoString(str, " :utilityStmt <>");
 
 	WRITE_INT_FIELD(resultRelation);
+	WRITE_INT_FIELD(resultVariable);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasWindowFuncs);
 	WRITE_BOOL_FIELD(hasTargetSRFs);
@@ -4191,6 +4203,9 @@ outNode(StringInfo str, const void *obj)
 			case T_SelectStmt:
 				_outSelectStmt(str, obj);
 				break;
+			case T_LetStmt:
+				_outLetStmt(str, obj);
+				break;
 			case T_ColumnDef:
 				_outColumnDef(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3254524223..4454327549 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -242,6 +242,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
+	READ_INT_FIELD(resultVariable);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasWindowFuncs);
 	READ_BOOL_FIELD(hasTargetSRFs);
@@ -1485,6 +1486,7 @@ _readPlannedStmt(void)
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(nonleafResultRelations);
 	READ_NODE_FIELD(rootResultRelations);
+	READ_OID_FIELD(resultVariable);
 	READ_NODE_FIELD(subplans);
 	READ_BITMAPSET_FIELD(rewindPlanIDs);
 	READ_NODE_FIELD(rowMarks);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 96bf0601a8..4573a88f35 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -335,7 +335,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	 */
 	if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
 		IsUnderPostmaster &&
-		parse->commandType == CMD_SELECT &&
+		(parse->commandType == CMD_SELECT ||
+		 parse->commandType == CMD_PLAN_UTILITY) &&
 		!parse->hasModifyingCTE &&
 		max_parallel_workers_per_gather > 0 &&
 		!IsParallelWorker() &&
@@ -352,6 +353,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 		glob->parallelModeOK = false;
 	}
 
+
+
 	/*
 	 * glob->parallelModeNeeded is normally set to false here and changed to
 	 * true during plan creation if a Gather or Gather Merge plan is actually
@@ -521,6 +524,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->resultRelations = glob->resultRelations;
 	result->nonleafResultRelations = glob->nonleafResultRelations;
 	result->rootResultRelations = glob->rootResultRelations;
+	result->resultVariable = parse->resultVariable;
 	result->subplans = glob->subplans;
 	result->rewindPlanIDs = glob->rewindPlanIDs;
 	result->rowMarks = glob->finalrowmarks;
@@ -2173,7 +2177,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 		 * If this is an INSERT/UPDATE/DELETE, and we're not being called from
 		 * inheritance_planner, add the ModifyTable node.
 		 */
-		if (parse->commandType != CMD_SELECT && !inheritance_update)
+		if (parse->commandType != CMD_SELECT && parse->commandType != CMD_PLAN_UTILITY && !inheritance_update)
 		{
 			List	   *withCheckOptionLists;
 			List	   *returningLists;
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 8603feef2b..2923e3fcc7 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -71,6 +71,7 @@ preprocess_targetlist(PlannerInfo *root)
 {
 	Query	   *parse = root->parse;
 	int			result_relation = parse->resultRelation;
+	int			result_variable = parse->resultVariable;
 	List	   *range_table = parse->rtable;
 	CmdType		command_type = parse->commandType;
 	RangeTblEntry *target_rte = NULL;
@@ -96,6 +97,10 @@ preprocess_targetlist(PlannerInfo *root)
 
 		target_relation = heap_open(target_rte->relid, NoLock);
 	}
+	else if (result_variable)
+	{
+		Assert(command_type == CMD_PLAN_UTILITY);
+	}
 	else
 		Assert(command_type == CMD_SELECT);
 
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a04ad6e99e..8f023225c6 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1254,7 +1254,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 	{
 		Param	   *param = (Param *) node;
 
-		if (param->paramkind == PARAM_EXTERN)
+		if (param->paramkind == PARAM_EXTERN ||
+			param->paramkind == PARAM_VARIABLE)
 			return false;
 
 		if (param->paramkind != PARAM_EXEC ||
@@ -4799,7 +4800,7 @@ substitute_actual_parameters_mutator(Node *node,
 {
 	if (node == NULL)
 		return NULL;
-	if (IsA(node, Param))
+	if (IsA(node, Param) && ((Param *) node)->paramkind != PARAM_VARIABLE)
 	{
 		Param	   *param = (Param *) node;
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8369e3ad62..fc0cf34c7d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1272,7 +1272,7 @@ get_relation_constraints(PlannerInfo *root,
 	 * descriptor, instead of constraint exclusion which is driven by the
 	 * individual partition's partition constraint.
 	 */
-	if (enable_partition_pruning && root->parse->commandType != CMD_SELECT)
+	if (enable_partition_pruning && root->parse->commandType != CMD_SELECT && root->parse->commandType != CMD_PLAN_UTILITY)
 	{
 		List	   *pcqual = RelationGetPartitionQual(relation);
 
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c601b6d40d..8a724fe3bf 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,7 +25,10 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
+#include "commands/schemavariable.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -44,6 +47,8 @@
 #include "parser/parse_target.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
 
 
@@ -78,6 +83,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
@@ -267,6 +274,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
 		case T_InsertStmt:
 		case T_UpdateStmt:
 		case T_DeleteStmt:
+		case T_LetStmt:
 			(void) test_raw_expression_coverage(parseTree, NULL);
 			break;
 		default:
@@ -327,6 +335,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
 									   (CallStmt *) parseTree);
 			break;
 
+		case T_LetStmt:
+			result = transformLetStmt(pstate,
+									  (LetStmt *) parseTree);
+			break;
+
 		default:
 
 			/*
@@ -367,6 +380,7 @@ analyze_requires_snapshot(RawStmt *parseTree)
 		case T_DeleteStmt:
 		case T_UpdateStmt:
 		case T_SelectStmt:
+		case T_LetStmt:
 			result = true;
 			break;
 
@@ -1567,6 +1581,204 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+ *	  transform an Let Statement
+ */
+static Query *
+transformLetStmt(ParseState *pstate, LetStmt *stmt)
+{
+	Query	   *qry = makeNode(Query);
+	List	   *exprList = NIL;
+	List	   *exprListCoer = NIL;
+	List	   *indirection = NIL;
+	ListCell   *lc;
+	Query	   *selectQuery;
+	int			i = 0;
+
+	Oid			varid;
+
+	ParseExprKind sv_expr_kind;
+	char	   *attrname = NULL;
+	bool		not_unique;
+	bool		is_rowtype;
+	Oid			typid;
+	int32		typmod;
+	Oid			collid;
+
+	AclResult	aclresult;
+	List	   *names = NULL;
+	int			indirection_start;
+
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = EXPR_KIND_LET;
+
+	/* There can't be any outer WITH to worry about */
+	Assert(pstate->p_ctenamespace == NIL);
+
+	/* Exec this command as utility */
+	qry->commandType = CMD_PLAN_UTILITY;
+	qry->utilityStmt = (Node *) stmt;
+
+	names = NamesFromList(stmt->target);
+
+	varid = identify_variable(names, &attrname, &not_unique);
+	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("schema variable \"%s\" doesn't exists",
+								NameListToString(names)),
+						 parser_errposition(pstate, stmt->location)));
+
+	qry->resultVariable = varid;
+
+	get_schema_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("target variable \"%s\" is not row type",
+						schema_variable_get_name(varid)),
+				parser_errposition(pstate, stmt->location)));
+
+	aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, NameListToString(names));
+
+	selectQuery = transformStmt(pstate, stmt->selectStmt);
+
+	/* The grammar should have produced a SELECT */
+	if (!IsA(selectQuery, Query) ||
+		selectQuery->commandType != CMD_SELECT)
+		elog(ERROR, "unexpected non-SELECT command in LET ... 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);
+	}
+
+	/*
+	 * Because doesn't support pattern matching, don't allow multicolumn result
+	 */
+	if (list_length(exprList) != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("expression is not scalar value"),
+				 parser_errposition(pstate,
+									exprLocation((Node *) exprList))));
+
+	indirection_start = list_length(names) - (attrname ? 1 : 0);
+	indirection = list_copy_tail(stmt->target, indirection_start);
+
+	exprListCoer = NIL;
+	foreach(lc, exprList)
+	{
+		Node	   *orig_expr = (Node*) lfirst(lc);
+		Oid			exprtypid = exprType((Node *) orig_expr);
+		Param *param = makeNode(Param);
+		Expr	   *expr = NULL;
+
+		param->paramkind = PARAM_VARIABLE;
+		param->paramid = varid;
+		param->paramtype = typid;
+		param->paramtypmod = typmod;
+
+		if (indirection != NULL)
+		{
+			bool		targetIsArray;
+			char	    *targetName;
+
+			targetName = attrname != NULL ? attrname : get_schema_variable_name(varid);
+			targetIsArray = OidIsValid(get_element_type(typid));
+
+			expr = (Expr *)
+					transformAssignmentIndirection(pstate,
+												(Node *) param,
+												targetName,
+												targetIsArray,
+												typid,
+												typmod,
+												InvalidOid,
+												list_head(indirection),
+												(Node *) orig_expr,
+												stmt->location);
+		}
+		else
+			expr = (Expr *)
+					coerce_to_target_type(pstate,
+											(Node *) orig_expr,
+											exprtypid,
+											typid, typmod,
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											stmt->location);
+
+		if (expr == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("variable \"%s\" is of type %s,"
+							  " but expression is of type %s",
+						schema_variable_get_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 *) orig_expr))));
+
+		exprListCoer = lappend(exprListCoer, expr);
+	}
+
+	/*
+	 * Generate query's target list using the computed list of expressions.
+	 * Also, mark all the target columns as needing insert permissions.
+	 */
+	qry->targetList = NIL;
+	foreach(lc, exprListCoer)
+	{
+		Expr	   *expr = (Expr *) lfirst(lc);
+		TargetEntry *tle;
+
+		tle = makeTargetEntry(expr,
+							  i + 1,
+							  FigureColname((Node *)expr),
+							  false);
+		qry->targetList = lappend(qry->targetList, tle);
+	}
+
+	/* done building the range table and jointree */
+	qry->rtable = pstate->p_rtable;
+	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+	qry->hasSubLinks = pstate->p_hasSubLinks;
+
+	assign_query_collations(pstate, qry);
+
+	pstate->p_expr_kind = sv_expr_kind;
+
+	return qry;
+}
+
+
 /*
  * transformSetOperationStmt -
  *	  transforms a set-operations tree
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 87f5e95827..4310c28538 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -257,8 +257,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 CreateSchemaVarStmt CreateSeqStmt CreateStmt CreateStatsStmt
+		CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
 		CreateAssertStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
 		CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
 		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
@@ -268,7 +268,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropTransformStmt
 		DropUserMappingStmt ExplainStmt FetchStmt
 		GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
-		ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
+		LetStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
 		CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
 		RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
 		RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@@ -400,6 +400,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				TriggerTransitions TriggerReferencing
 				publication_name_list
 				vacuum_relation_list opt_vacuum_relation_list
+				let_target
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -584,6 +585,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		hash_partbound partbound_datum_list range_datum_list
 %type <defelt>		hash_partbound_elem
+%type <node>		optSchemaVarDefExpr
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -649,7 +651,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY
 
 	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 MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -687,8 +689,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNBOUNDED 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
 
@@ -878,6 +880,7 @@ stmt :
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSchemaVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -917,6 +920,7 @@ stmt :
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -1808,7 +1812,12 @@ DiscardStmt:
 					n->target = DISCARD_SEQUENCES;
 					$$ = (Node *) n;
 				}
-
+			| DISCARD VARIABLES
+				{
+					DiscardStmt *n = makeNode(DiscardStmt);
+					n->target = DISCARD_VARIABLES;
+					$$ = (Node *) n;
+				}
 		;
 
 
@@ -4479,6 +4488,44 @@ create_extension_opt_item:
 				}
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY :
+ *				CREATE VARIABLE varname [AS] type
+ *
+ *****************************************************************************/
+
+CreateSchemaVarStmt:
+			CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause optSchemaVarDefExpr
+				{
+					CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+					$4->relpersistence = $2;
+					n->variable = $4;
+					n->typeName = $6;
+					n->collClause = $7;
+					n->defexpr = $8;
+					n->if_not_exists = false;
+					$$ = (Node *) n;
+				}
+			| CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause optSchemaVarDefExpr
+				{
+					CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+					$7->relpersistence = $2;
+					n->variable = $7;
+					n->typeName = $9;
+					n->collClause = $10;
+					n->defexpr = $11;
+					n->if_not_exists = true;
+					$$ = (Node *) n;
+				}
+		;
+
+optSchemaVarDefExpr: DEFAULT b_expr							{ $$ = $2; }
+			| /* EMPTY */									{ $$ = NULL; }
+		;
+
+
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
@@ -6335,6 +6382,7 @@ drop_type_any_name:
 			| TEXT_P SEARCH DICTIONARY				{ $$ = OBJECT_TSDICTIONARY; }
 			| TEXT_P SEARCH TEMPLATE				{ $$ = OBJECT_TSTEMPLATE; }
 			| TEXT_P SEARCH CONFIGURATION			{ $$ = OBJECT_TSCONFIGURATION; }
+			| VARIABLE								{ $$ = OBJECT_VARIABLE; }
 		;
 
 /* object types taking name_list */
@@ -6604,6 +6652,7 @@ comment_type_any_name:
 			| TEXT_P SEARCH DICTIONARY			{ $$ = OBJECT_TSDICTIONARY; }
 			| TEXT_P SEARCH PARSER				{ $$ = OBJECT_TSPARSER; }
 			| TEXT_P SEARCH TEMPLATE			{ $$ = OBJECT_TSTEMPLATE; }
+			| VARIABLE							{ $$ = OBJECT_VARIABLE; }
 		;
 
 /* object types taking name */
@@ -6742,6 +6791,7 @@ security_label_type_any_name:
 			| TABLE								{ $$ = OBJECT_TABLE; }
 			| VIEW								{ $$ = OBJECT_VIEW; }
 			| MATERIALIZED VIEW					{ $$ = OBJECT_MATVIEW; }
+			| VARIABLE							{ $$ = OBJECT_VARIABLE; }
 		;
 
 /* object types taking name */
@@ -7163,6 +7213,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));
@@ -7203,6 +7261,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;
+				}
 		;
 
 
@@ -7363,6 +7429,7 @@ defacl_privilege_target:
 			| SEQUENCES		{ $$ = OBJECT_SEQUENCE; }
 			| TYPES_P		{ $$ = OBJECT_TYPE; }
 			| SCHEMAS		{ $$ = OBJECT_SCHEMA; }
+			| VARIABLES		{ $$ = OBJECT_VARIABLE; }
 		;
 
 
@@ -8959,6 +9026,25 @@ 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									{ $$ = COLUMN; }
@@ -9277,6 +9363,25 @@ 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;
+				}
+
 		;
 
 /*****************************************************************************
@@ -9512,6 +9617,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;
+				}
 		;
 
 
@@ -10693,6 +10806,7 @@ ExplainableStmt:
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 explain_option_list:
@@ -10750,6 +10864,7 @@ PreparableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -11148,6 +11263,44 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY:
+ *				LET STATEMENTS
+ *
+ *****************************************************************************/
+LetStmt:	LET let_target '=' a_expr
+				{
+					LetStmt *n = makeNode(LetStmt);
+					SelectStmt *select = makeNode(SelectStmt);
+					ResTarget	*res = makeNode(ResTarget);
+
+					n->target = $2;
+
+					/* 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->selectStmt = (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:
@@ -15127,6 +15280,7 @@ unreserved_keyword:
 			| LARGE_P
 			| LAST_P
 			| LEAKPROOF
+			| LET
 			| LEVEL
 			| LISTEN
 			| LOAD
@@ -15275,6 +15429,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 61727e1d71..6823612fba 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -349,6 +349,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			Assert(false);		/* can't happen */
 			break;
 		case EXPR_KIND_OTHER:
+		case EXPR_KIND_LET:
 
 			/*
 			 * Accept aggregate/grouping here; caller must throw error if
@@ -465,6 +466,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");
@@ -879,6 +881,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:
@@ -902,6 +905,8 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_CALL_ARGUMENT:
 			err = _("window functions are not allowed in CALL arguments");
 			break;
+		case EXPR_KIND_LET:
+			err = _("window functions are not allowed in LET statement");
 
 			/*
 			 * 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 385e54a9b6..cc614b3902 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -37,6 +38,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -116,6 +118,9 @@ static Node *transformXmlSerialize(ParseState *pstate, XmlSerialize *xs);
 static Node *transformBooleanTest(ParseState *pstate, BooleanTest *b);
 static Node *transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr);
 static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
+static Node *makeParamSchemaVariable(ParseState *pstate,
+						  Oid varid, Oid typid, int32 typmod, Oid collid,
+						  char *attrname, int location);
 static Node *transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte,
 					 int location);
 static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
@@ -512,6 +517,10 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	char	   *nspname = NULL;
 	char	   *relname = NULL;
 	char	   *colname = NULL;
+	Oid			varid = InvalidOid;
+	char	   *attrname = NULL;
+	bool		not_unique;
+
 	RangeTblEntry *rte;
 	int			levels_up;
 	enum
@@ -749,6 +758,15 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 			break;
 	}
 
+	varid = identify_variable(cref->fields, &attrname, &not_unique);
+
+	if (not_unique)
+		ereport(ERROR,
+				(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+				 errmsg("schema variable reference \"%s\" is ambiguous",
+						NameListToString(cref->fields)),
+				 parser_errposition(pstate, cref->location)));
+
 	/*
 	 * Now give the PostParseColumnRefHook, if any, a chance.  We pass the
 	 * translation-so-far so that it can throw an error if it wishes in the
@@ -773,6 +791,72 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 					 parser_errposition(pstate, cref->location)));
 	}
 
+	if (OidIsValid(varid))
+	{
+		Oid		typid;
+		int32	typmod;
+		Oid		collid;
+
+		get_schema_variable_type_typmod_collid(varid, &typid, &typmod, &collid);
+
+		if (node != NULL)
+		{
+			/*
+			 * some collision can be solved simply here to reduce errors
+			 * based on simply existence of some variables. Often error
+			 * can be using alias same like variable name. In this case,
+			 * when we found column reference, and we found reference to
+			 * possible composite variable, but the variable is not composite,
+			 * then we can ignore the variable as simply improper, and we
+			 * use column reference only.
+			 */
+			if (attrname)
+			{
+				if (type_is_rowtype(typid))
+				{
+					TupleDesc		tupdesc;
+					bool			found = false;
+					int			i;
+
+					/* slow part, I hope it will not be to often */
+					tupdesc = lookup_rowtype_tupdesc(typid, typmod);
+					for (i = 0; i < tupdesc->natts; i++)
+					{
+						if (namestrcmp(&(TupleDescAttr(tupdesc, i)->attname), attrname) == 0 &&
+								!TupleDescAttr(tupdesc, i)->attisdropped)
+						{
+							found = true;
+							break;
+						}
+					}
+
+					FreeTupleDesc(tupdesc);
+
+					/* there are not composite variable with this field */
+					if (!found)
+						varid = InvalidOid;
+				}
+				else
+					/* there are not composite variable with this name */
+					varid = InvalidOid;
+			}
+
+			/* Raise error if varid is still valid. It should be really amigonuous */
+			if (OidIsValid(varid))
+				ereport(ERROR,
+						(errcode(ERRCODE_AMBIGUOUS_COLUMN),
+						 errmsg("column reference \"%s\" is ambiguous",
+								NameListToString(cref->fields)),
+						 errdetail("The qualified identifier can be column reference or schema variable reference"),
+						 parser_errposition(pstate, cref->location)));
+		}
+
+		if (OidIsValid(varid))
+			node = makeParamSchemaVariable(pstate,
+											varid, typid, typmod, collid,
+											attrname, cref->location);
+	}
+
 	/*
 	 * Throw error if no translation found.
 	 */
@@ -807,6 +891,60 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
+ * Generate param variable for reference to schema variable
+ */
+static Node *
+makeParamSchemaVariable(ParseState *pstate, Oid varid, Oid typid, int32 typmod, Oid collid, char *attrname, int location)
+{
+	Param	   *param;
+
+	param = makeNode(Param);
+
+	param->paramkind = PARAM_VARIABLE;
+	param->paramid = varid;
+	param->paramtype = typid;
+	param->paramtypmod = typmod;
+	param->paramcollid = collid;
+
+	if (attrname != NULL)
+	{
+		TupleDesc		tupdesc;
+		int		i;
+
+		tupdesc = lookup_rowtype_tupdesc(typid, typmod);
+
+		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)
 {
@@ -1818,6 +1956,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
 		case EXPR_KIND_VALUES_SINGLE:
+		case EXPR_KIND_LET:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -1826,6 +1965,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:
@@ -3460,6 +3600,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";
@@ -3475,6 +3616,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "PARTITION BY";
 		case EXPR_KIND_CALL_ARGUMENT:
 			return "CALL";
+		case EXPR_KIND_LET:
+			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 44257154b8..b2c9900e00 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2347,6 +2347,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:
@@ -2370,6 +2371,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_CALL_ARGUMENT:
 			err = _("set-returning functions are not allowed in CALL arguments");
 			break;
+		case EXPR_KIND_LET:
+			err = _("set-returning functions are not allowed in CALL arguments");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4932e58022..c60fe011f7 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -35,16 +35,6 @@
 
 static void markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
 					 Var *var, int levelsup);
-static Node *transformAssignmentIndirection(ParseState *pstate,
-							   Node *basenode,
-							   const char *targetName,
-							   bool targetIsArray,
-							   Oid targetTypeId,
-							   int32 targetTypMod,
-							   Oid targetCollation,
-							   ListCell *indirection,
-							   Node *rhs,
-							   int location);
 static Node *transformAssignmentSubscripts(ParseState *pstate,
 							  Node *basenode,
 							  const char *targetName,
@@ -672,7 +662,7 @@ updateTargetListEntry(ParseState *pstate,
  * might want to decorate indirection cells with their own location info,
  * in which case the location argument could probably be dropped.)
  */
-static Node *
+Node *
 transformAssignmentIndirection(ParseState *pstate,
 							   Node *basenode,
 							   const char *targetName,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 3123ee274d..10737d422d 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3350,7 +3350,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	 * get executed.  Also, utilities aren't rewritten at all (do we still
 	 * need that check?)
 	 */
-	if (event != CMD_SELECT && event != CMD_UTILITY)
+	if (event != CMD_SELECT && event != CMD_UTILITY && event != CMD_PLAN_UTILITY)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index 61ef396d8a..6a068af799 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -212,7 +212,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	}
 
 	/*
-	 * For SELECT, UPDATE and DELETE, add security quals to enforce the USING
+	 * For SELECT, LET, UPDATE and DELETE, add security quals to enforce the USING
 	 * policies.  These security quals control access to existing table rows.
 	 * Restrictive policies are combined together using AND, and permissive
 	 * policies are combined together using OR.
@@ -222,6 +222,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 							  &restrictive_policies);
 
 	if (commandType == CMD_SELECT ||
+		commandType == CMD_PLAN_UTILITY ||
 		commandType == CMD_UPDATE ||
 		commandType == CMD_DELETE)
 		add_security_quals(rt_index,
@@ -423,6 +424,7 @@ get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id,
 			switch (cmd)
 			{
 				case CMD_SELECT:
+				case CMD_PLAN_UTILITY:
 					if (policy->polcmd == ACL_SELECT_CHR)
 						cmd_matches = true;
 					break;
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index c95a4d519d..47fb0f38b1 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -37,6 +37,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"
 #include "utils/portal.h"
@@ -143,6 +144,9 @@ CreateDestReceiver(CommandDest dest)
 
 		case DestTupleQueue:
 			return CreateTupleQueueDestReceiver(NULL);
+
+		case DestVariable:
+			return CreateVariableDestReceiver();
 	}
 
 	/* should never get here */
@@ -178,6 +182,7 @@ EndCommand(const char *commandTag, CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
@@ -222,6 +227,7 @@ NullCommand(CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
@@ -268,6 +274,7 @@ ReadyForQuery(CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b5804f64ad..35199fd0dc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -47,6 +47,7 @@
 #include "commands/proclang.h"
 #include "commands/publicationcmds.h"
 #include "commands/schemacmds.h"
+#include "commands/schemavariable.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
 #include "commands/subscriptioncmds.h"
@@ -344,7 +345,7 @@ ProcessUtility(PlannedStmt *pstmt,
 			   char *completionTag)
 {
 	Assert(IsA(pstmt, PlannedStmt));
-	Assert(pstmt->commandType == CMD_UTILITY);
+	Assert(pstmt->commandType == CMD_UTILITY || pstmt->commandType == CMD_PLAN_UTILITY);
 	Assert(queryString != NULL);	/* required as of 8.4 */
 
 	/*
@@ -915,6 +916,14 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_LetStmt:
+			{
+				doLetStmt(pstmt, params, queryEnv, queryString);
+				if (completionTag)
+					strcpy(completionTag, "LET");
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -1221,6 +1230,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				}
 				break;
 
+			case T_CreateSchemaVarStmt:
+				address = DefineSchemaVariable(pstate, (CreateSchemaVarStmt *) parsetree);
+				break;
+
 				/*
 				 * ************* object creation / destruction **************
 				 */
@@ -2055,6 +2068,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_STATISTIC_EXT:
 			tag = "ALTER STATISTICS";
 			break;
+		case OBJECT_VARIABLE:
+			tag = "ALTER VARIABLE";
+			break;
 		default:
 			tag = "???";
 			break;
@@ -2104,6 +2120,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "SELECT";
 			break;
 
+		case T_LetStmt:
+			tag = "LET";
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -2358,6 +2378,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_STATISTIC_EXT:
 					tag = "DROP STATISTICS";
 					break;
+				case OBJECT_VARIABLE:
+					tag = "DROP VARIABLE";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2639,6 +2662,9 @@ CreateCommandTag(Node *parsetree)
 				case DISCARD_SEQUENCES:
 					tag = "DISCARD SEQUENCES";
 					break;
+				case DISCARD_VARIABLES:
+					tag = "DISCARD VARIABLES";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2844,6 +2870,7 @@ CreateCommandTag(Node *parsetree)
 						tag = "DELETE";
 						break;
 					case CMD_UTILITY:
+					case CMD_PLAN_UTILITY:
 						tag = CreateCommandTag(stmt->utilityStmt);
 						break;
 					default:
@@ -2915,6 +2942,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_CreateSchemaVarStmt:
+			tag = "CREATE VARIABLE";
+			break;
+
 		default:
 			elog(WARNING, "unrecognized node type: %d",
 				 (int) nodeTag(parsetree));
@@ -2961,6 +2992,10 @@ GetCommandLogLevel(Node *parsetree)
 				lev = LOGSTMT_ALL;
 			break;
 
+		case T_LetStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			lev = LOGSTMT_ALL;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index a45e093de7..952c0d9628 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -315,6 +315,12 @@ aclparse(const char *s, AclItem *aip)
 			case ACL_CONNECT_CHR:
 				read = ACL_CONNECT;
 				break;
+			case ACL_READ_CHR:
+				read = ACL_READ;
+				break;
+			case ACL_WRITE_CHR:
+				read = ACL_WRITE;
+				break;
 			case 'R':			/* ignore old RULE privileges */
 				read = 0;
 				break;
@@ -808,6 +814,10 @@ acldefault(ObjectType objtype, Oid ownerId)
 			world_default = ACL_USAGE;
 			owner_default = ACL_ALL_RIGHTS_TYPE;
 			break;
+		case OBJECT_VARIABLE:
+			world_default = ACL_NO_RIGHTS;
+			owner_default = ACL_ALL_RIGHTS_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			world_default = ACL_NO_RIGHTS;	/* keep compiler quiet */
@@ -903,6 +913,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
 		case 'T':
 			objtype = OBJECT_TYPE;
 			break;
+		case 'V':
+			objtype = OBJECT_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype abbreviation: %c", objtypec);
 	}
@@ -1627,6 +1640,10 @@ convert_priv_string(text *priv_type_text)
 		return ACL_CONNECT;
 	if (pg_strcasecmp(priv_type, "RULE") == 0)
 		return 0;				/* ignore old RULE privileges */
+	if (pg_strcasecmp(priv_type, "READ") == 0)
+		return ACL_READ;
+	if (pg_strcasecmp(priv_type, "WRITE") == 0)
+		return ACL_WRITE;
 
 	ereport(ERROR,
 			(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1721,6 +1738,10 @@ convert_aclright_to_string(int aclright)
 			return "TEMPORARY";
 		case ACL_CONNECT:
 			return "CONNECT";
+		case ACL_READ:
+			return "READ";
+		case ACL_WRITE:
+			return "WRITE";
 		default:
 			elog(ERROR, "unrecognized aclright: %d", aclright);
 			return NULL;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 03e9a28a63..cc8f3326ac 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -38,6 +38,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"
@@ -7362,6 +7363,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
+	/* translate paramid to original schema variable name */
+	if (param->paramkind == PARAM_VARIABLE)
+	{
+		appendStringInfo(context->buf, "%s",
+							schema_variable_get_name(param->paramid));
+		return;
+	}
+
 	/*
 	 * Not PARAM_EXEC, or couldn't find referent: just print $N.
 	 */
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index bba595ad1d..858a6dd4be 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1691,6 +1691,18 @@ get_relname_relid(const char *relname, Oid relnamespace)
 						   ObjectIdGetDatum(relnamespace));
 }
 
+/*
+ * 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,
+						   PointerGetDatum(varname),
+						   ObjectIdGetDatum(varnamespace));
+}
+
 #ifdef NOT_USED
 /*
  * get_relnatts
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 2b381782a3..35dc32f649 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -73,6 +73,7 @@
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "utils/rel.h"
 #include "utils/catcache.h"
 #include "utils/syscache.h"
@@ -968,6 +969,28 @@ static const struct cachedesc cacheinfo[] = {
 			0
 		},
 		2
+	},
+	{VariableRelationId,			/* VARIABLENAMENSP */
+		VariableNameNspIndexId,
+		2,
+		{
+			Anum_pg_variable_varname,
+			Anum_pg_variable_varnamespace,
+			0,
+			0
+		},
+		8
+	},
+	{VariableRelationId,			/* VARIABLEOID */
+		VariableObjectIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		8
 	}
 };
 
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 0d147cb08d..6d97931d85 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -296,6 +296,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading subscriptions\n");
 	getSubscriptions(fout);
 
+	if (g_verbose)
+		write_msg(NULL, "reading variables\n");
+	getVariables(fout);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 83c976eaf7..c9bc91ca68 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3471,6 +3471,7 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
 		strcmp(type, "TEXT SEARCH DICTIONARY") == 0 ||
 		strcmp(type, "TEXT SEARCH CONFIGURATION") == 0 ||
 		strcmp(type, "STATISTICS") == 0 ||
+		strcmp(type, "VARIABLE") == 0 ||
 	/* non-schema-specified objects */
 		strcmp(type, "DATABASE") == 0 ||
 		strcmp(type, "PROCEDURAL LANGUAGE") == 0 ||
@@ -3670,7 +3671,8 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 			strcmp(te->desc, "SERVER") == 0 ||
 			strcmp(te->desc, "STATISTICS") == 0 ||
 			strcmp(te->desc, "PUBLICATION") == 0 ||
-			strcmp(te->desc, "SUBSCRIPTION") == 0)
+			strcmp(te->desc, "SUBSCRIPTION") == 0 ||
+			strcmp(te->desc, "VARIABLE") == 0)
 		{
 			PQExpBuffer temp = createPQExpBuffer();
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 9baf7b2fde..f825a00c9d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -260,6 +260,7 @@ static void dumpPolicy(Archive *fout, PolicyInfo *polinfo);
 static void dumpPublication(Archive *fout, PublicationInfo *pubinfo);
 static void dumpPublicationTable(Archive *fout, PublicationRelInfo *pubrinfo);
 static void dumpSubscription(Archive *fout, SubscriptionInfo *subinfo);
+static void dumpVariable(Archive *fout, VariableInfo *varinfo);
 static void dumpDatabase(Archive *AH);
 static void dumpDatabaseConfig(Archive *AH, PQExpBuffer outbuf,
 				   const char *dbname, Oid dboid);
@@ -4221,6 +4222,208 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
+/*
+ * getVariables
+ *	  get information about variables
+ */
+void
+getVariables(Archive *fout)
+{
+	DumpOptions *dopt = fout->dopt;
+	PQExpBuffer query;
+	PQExpBuffer acl_subquery = createPQExpBuffer();
+	PQExpBuffer racl_subquery = createPQExpBuffer();
+	PQExpBuffer init_acl_subquery = createPQExpBuffer();
+	PQExpBuffer init_racl_subquery = createPQExpBuffer();
+	PGresult   *res;
+	VariableInfo *varinfo;
+	int			i_tableoid;
+	int			i_oid;
+	int			i_varname;
+	int			i_varnamespace;
+	int			i_vartype;
+	int			i_vartypname;
+	int			i_vardefexpr;
+	int			i_rolname;
+	int			i_varacl;
+	int			i_rvaracl;
+	int			i_initvaracl;
+	int			i_initrvaracl;
+	int			i,
+				ntups;
+
+	if (fout->remoteVersion <= 110000)
+		return;
+
+	acl_subquery = createPQExpBuffer();
+	racl_subquery = createPQExpBuffer();
+	init_acl_subquery = createPQExpBuffer();
+	init_racl_subquery = createPQExpBuffer();
+
+	buildACLQueries(acl_subquery, racl_subquery, init_acl_subquery,
+					init_racl_subquery, "v.varacl", "v.varowner", "'V'",
+					dopt->binary_upgrade);
+
+	query = createPQExpBuffer();
+
+	resetPQExpBuffer(query);
+
+	/* Get the variables in current database. */
+	appendPQExpBuffer(query,
+						  "SELECT v.tableoid, v.oid, v.varname, "
+						  "v.varnamespace,"
+						  "(%s varowner) AS rolname, "
+						  "%s as varacl, "
+						  "%s as rvaracl, "
+						  "%s as initvaracl, "
+						  "%s as initrvaracl, "
+						  "v.vartype, "
+						  "pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname, "
+						  "pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr "
+						  "FROM pg_variable v "
+						  "LEFT JOIN pg_init_privs pip "
+						  "ON (v.oid = pip.objoid "
+						  "AND pip.classoid = 'pg_variable'::regclass "
+						  "AND pip.objsubid = 0)",
+						  username_subquery,
+						  acl_subquery->data,
+						  racl_subquery->data,
+						  init_acl_subquery->data,
+						  init_racl_subquery->data);
+
+	destroyPQExpBuffer(acl_subquery);
+	destroyPQExpBuffer(racl_subquery);
+	destroyPQExpBuffer(init_acl_subquery);
+	destroyPQExpBuffer(init_racl_subquery);
+
+	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_rolname = PQfnumber(res, "rolname");
+	i_vartype = PQfnumber(res, "vartype");
+	i_vartypname = PQfnumber(res, "vartypname");
+	i_vardefexpr = PQfnumber(res, "vardefexpr");
+	i_varacl = PQfnumber(res, "varacl");
+	i_rvaracl = PQfnumber(res, "rvaracl");
+	i_initvaracl = PQfnumber(res, "initvaracl");
+	i_initrvaracl = PQfnumber(res, "initrvaracl");
+
+	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(fout,
+						  atooid(PQgetvalue(res, i, i_varnamespace)));
+
+		varinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+		varinfo[i].vartype = atooid(PQgetvalue(res, i, i_vartype));
+		varinfo[i].vartypname = pg_strdup(PQgetvalue(res, i, i_vartypname));
+
+		varinfo[i].varacl = pg_strdup(PQgetvalue(res, i, i_varacl));
+		varinfo[i].rvaracl = pg_strdup(PQgetvalue(res, i, i_rvaracl));
+		varinfo[i].initvaracl = pg_strdup(PQgetvalue(res, i, i_initvaracl));
+		varinfo[i].initrvaracl = pg_strdup(PQgetvalue(res, i, i_initrvaracl));
+
+		/* 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) && PQgetisnull(res, i, i_rvaracl) &&
+			PQgetisnull(res, i, i_initvaracl) &&
+			PQgetisnull(res, i, i_initrvaracl))
+			varinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL;
+
+		if (PQgetisnull(res, i, i_vardefexpr))
+			varinfo[i].vardefexpr = NULL;
+		else
+			varinfo[i].vardefexpr = pg_strdup(PQgetvalue(res, i, i_vardefexpr));
+
+		if (strlen(varinfo[i].rolname) == 0)
+			write_msg(NULL, "WARNING: owner of variable \"%s\" appears to be invalid\n",
+					  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 variables
+ */
+static void
+dumpVariable(Archive *fout, VariableInfo *varinfo)
+{
+	DumpOptions *dopt = fout->dopt;
+
+	PQExpBuffer delq;
+	PQExpBuffer query;
+	const char	   *varname;
+	const char	   *vartypname;
+	const char	   *vardefexpr;
+
+	/* Skip if not to be dumped */
+	if (!varinfo->dobj.dump || dopt->dataOnly)
+		return;
+
+	delq = createPQExpBuffer();
+	query = createPQExpBuffer();
+
+	varname = fmtQualifiedDumpable(varinfo);
+	vartypname = varinfo->vartypname;
+	vardefexpr = varinfo->vardefexpr;
+
+	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
+					  varname);
+
+	appendPQExpBuffer(query, "CREATE VARIABLE %s AS %s",
+					  varname, vartypname);
+
+	if (vardefexpr)
+		appendPQExpBuffer(query, " DEFAULT %s",
+						  vardefexpr);
+
+	appendPQExpBuffer(query, ";\n");
+
+	ArchiveEntry(fout, varinfo->dobj.catId, varinfo->dobj.dumpId,
+				 varinfo->dobj.name,
+				 NULL,
+				 NULL,
+				 varinfo->rolname, false,
+				 "VARIABLE", SECTION_PRE_DATA,
+				 query->data, delq->data, NULL,
+				 NULL, 0,
+				 NULL, NULL);
+
+	if (varinfo->dobj.dump & DUMP_COMPONENT_COMMENT)
+		dumpComment(fout, "VARIABLE", varname,
+					NULL, varinfo->rolname,
+					varinfo->dobj.catId, 0, varinfo->dobj.dumpId);
+
+	destroyPQExpBuffer(delq);
+	destroyPQExpBuffer(query);
+}
+
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
@@ -9849,6 +10052,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION:
 			dumpSubscription(fout, (SubscriptionInfo *) 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 */
@@ -17935,6 +18141,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_OPFAMILY:
 			case DO_COLLATION:
 			case DO_CONVERSION:
+			case DO_VARIABLE:
 			case DO_TABLE:
 			case DO_ATTRDEF:
 			case DO_PROCLANG:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 1448005f30..0d49bb7ed7 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -84,7 +84,8 @@ typedef enum
 	DO_POLICY,
 	DO_PUBLICATION,
 	DO_PUBLICATION_REL,
-	DO_SUBSCRIPTION
+	DO_SUBSCRIPTION,
+	DO_VARIABLE
 } DumpableObjectType;
 
 /* component types of an object which can be selected for dumping */
@@ -625,6 +626,22 @@ typedef struct _SubscriptionInfo
 	char	   *subpublications;
 } SubscriptionInfo;
 
+/*
+ * The VariableInfo struct is used to represent schema variables
+ */
+typedef struct _VariableInfo
+{
+	DumpableObject dobj;
+	Oid			vartype;
+	char	   *vartypname;
+	char	   *rolname;		/* name of owner, or empty string */
+	char	   *vardefexpr;
+	char	   *varacl;
+	char	   *rvaracl;
+	char	   *initvaracl;
+	char	   *initrvaracl;
+} VariableInfo;
+
 /*
  * We build an array of these with an entry for each object that is an
  * extension member according to pg_depend.
@@ -725,5 +742,6 @@ extern void getPublications(Archive *fout);
 extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
 					 int numTables);
 extern void getSubscriptions(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 6227a8fd26..969a021771 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1477,6 +1477,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 ec751a7c23..2a67766ed4 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2601,6 +2601,38 @@ my %tests = (
 		},
 	},
 
+	'CREATE VARIABLE test_variable' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE VARIABLE dump_test.variable AS integer DEFAULT 0;',
+		regexp => qr/^
+			\QCREATE VARIABLE dump_test.variable AS integer DEFAULT 0;\E/xm,
+		like => {
+			binary_upgrade          => 1,
+			clean                   => 1,
+			clean_if_exists         => 1,
+			createdb                => 1,
+			defaults                => 1,
+			exclude_test_table      => 1,
+			exclude_test_table_data => 1,
+			no_blobs                => 1,
+			no_privs                => 1,
+			no_owner                => 1,
+			only_dump_test_schema   => 1,
+			pg_dumpall_dbprivs      => 1,
+			schema_only             => 1,
+			section_pre_data        => 1,
+			test_schema_plus_blobs  => 1,
+			with_oids               => 1, },
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_test_table     => 1,
+			pg_dumpall_globals       => 1,
+			pg_dumpall_globals_clean => 1,
+			role                     => 1,
+			section_post_data        => 1, }, },
+
 	'CREATE VIEW test_view' => {
 		create_order => 61,
 		create_sql   => 'CREATE VIEW dump_test.test_view
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 5b4d54a442..73a752fd7e 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -853,6 +853,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 80d8338b96..d645bba7af 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4178,6 +4178,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
 	return true;
 }
 
+/*
+ * \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};
+
+	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"
+					  "  pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n"
+					  "  pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\"",
+					  gettext_noop("Schema"),
+					  gettext_noop("Name"),
+					  gettext_noop("Type"),
+					  gettext_noop("Owner"),
+					  gettext_noop("Default"));
+
+	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");
+
+	processSQLNamePattern(pset.db, &buf, pattern, true, false,
+						  "n.nspname", "v.varname", NULL,
+						  "pg_catalog.pg_variable_is_visible(v.oid)");
+
+	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)
+			psql_error("Did not find any schema variable named \"%s\".\n",
+					   pattern);
+		else
+			psql_error("Did not find any schema variables.\n");
+	}
+	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 a4cc5efae0..ecc4e3a531 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -63,6 +63,9 @@ extern bool listAllDbs(const char *pattern, bool verbose);
 /* \dt, \di, \ds, \dS, etc. */
 extern bool listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSystem);
 
+/* \dV */
+extern bool listVariables(const char *pattern, bool varbose);
+
 /* \dD */
 extern bool listDomains(const char *pattern, bool verbose, bool showSystem);
 
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 316030d358..adcc36cb6e 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -167,7 +167,7 @@ slashUsage(unsigned short int pager)
 	 * Use "psql --help=commands | wc" to count correctly.  It's okay to count
 	 * the USE_READLINE line even in builds without that.
 	 */
-	output = PageOutput(125, pager ? &(pset.popt.topt) : NULL);
+	output = PageOutput(126, pager ? &(pset.popt.topt) : NULL);
 
 	fprintf(output, _("General\n"));
 	fprintf(output, _("  \\copyright             show PostgreSQL usage and distribution terms\n"));
@@ -257,6 +257,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dT[S+] [PATTERN]      list data types\n"));
 	fprintf(output, _("  \\du[S+] [PATTERN]      list roles\n"));
 	fprintf(output, _("  \\dv[S+] [PATTERN]      list views\n"));
+	fprintf(output, _("  \\dV     [PATTERN]      list variables\n"));
 	fprintf(output, _("  \\dx[+]  [PATTERN]      list extensions\n"));
 	fprintf(output, _("  \\dy     [PATTERN]      list event triggers\n"));
 	fprintf(output, _("  \\l[+]   [PATTERN]      list databases\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index bb696f8ee9..a7583810e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -805,6 +805,22 @@ static const SchemaQuery Query_for_list_of_statistics = {
 	NULL
 };
 
+static const SchemaQuery Query_for_list_of_variables = {
+	/* min_server_version */
+	0,
+	/* catname */
+	"pg_catalog.pg_variable v",
+	/* selcondition */
+	NULL,
+	/* viscondition */
+	"pg_catalog.pg_variable_is_visible(v.oid)",
+	/* namespace */
+	"v.varnamespace",
+	/* result */
+	"pg_catalog.quote_ident(v.varname)",
+	/* qualresult */
+	NULL
+};
 
 /*
  * Queries to get lists of names of various kinds of things, possibly
@@ -1249,6 +1265,7 @@ static const pgsql_thing_t words_after_create[] = {
 																	 * TABLE ... */
 	{"USER", Query_for_list_of_roles " UNION SELECT 'MAPPING FOR'"},
 	{"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 */
 };
@@ -1604,7 +1621,7 @@ 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", "INSERT", "LISTEN", "LOAD", "LOCK",
+		"FETCH", "GRANT", "IMPORT", "INSERT", "LET", "LISTEN", "LOAD", "LOCK",
 		"MOVE", "NOTIFY", "PREPARE",
 		"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
 		"RESET", "REVOKE", "ROLLBACK",
@@ -1621,9 +1638,9 @@ psql_completion(const char *text, int start, int end)
 		"\\d", "\\da", "\\dA", "\\db", "\\dc", "\\dC", "\\dd", "\\ddp", "\\dD",
 		"\\des", "\\det", "\\deu", "\\dew", "\\dE", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
-		"\\dm", "\\dn", "\\do", "\\dO", "\\dp",
+		"\\dm", "\\dn", "\\do", "\\dO", "\\dp"
 		"\\drds", "\\dRs", "\\dRp", "\\ds", "\\dS",
-		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
+		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy", "\\dV",
 		"\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding",
 		"\\endif", "\\errverbose", "\\ev",
 		"\\f",
@@ -1988,6 +2005,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	else if (Matches4("ALTER", "SYSTEM", "SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
+	/* ALTER VARIABLE <name> */
+	else if (Matches3("ALTER", "VARIABLE", MatchAny))
+		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 	/* ALTER VIEW <name> */
 	else if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -2837,6 +2857,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
+/* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	/* Complete CREATE VARIABLE <name> with AS */
+	else if (TailMatches3("CREATE", "VARIABLE", MatchAny))
+		COMPLETE_WITH_CONST("AS");
+	/* Complete CREATE VARIABLE <name> with AS types*/
+	else if (TailMatches4("CREATE", "VARIABLE", MatchAny, "AS"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VIEW <name> with AS */
 	else if (TailMatches3("CREATE", "VIEW", MatchAny))
@@ -2890,7 +2918,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 	else if (Matches1("DISCARD"))
-		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
+		COMPLETE_WITH_LIST5("ALL", "PLANS", "SEQUENCES", "TEMP", "VARIABLES");
 
 /* DO */
 	else if (Matches1("DO"))
@@ -2992,6 +3020,12 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* DROP VARIABLE */
+	else if (Matches2("DROP", "VARIABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
+	else if (Matches3("DROP", "VARIABLE", MatchAny))
+		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
+
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -3002,14 +3036,14 @@ psql_completion(const char *text, int start, int end)
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
 	else if (Matches1("EXPLAIN"))
-		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
-							"ANALYZE", "VERBOSE");
+		COMPLETE_WITH_LIST8("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+							"ANALYZE", "VERBOSE", "LET");
 	else if (Matches2("EXPLAIN", "ANALYZE"))
-		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
-							"VERBOSE");
+		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+							"VERBOSE", "LET");
 	else if (Matches2("EXPLAIN", "VERBOSE") ||
 			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
-		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
+		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "LET");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
@@ -3118,6 +3152,7 @@ psql_completion(const char *text, int start, int end)
 									   " UNION SELECT 'ALL ROUTINES IN SCHEMA'"
 									   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
 									   " UNION SELECT 'ALL TABLES IN SCHEMA'"
+									   " UNION SELECT 'ALL VARIABLES IN SCHEMA'"
 									   " UNION SELECT 'DATABASE'"
 									   " UNION SELECT 'DOMAIN'"
 									   " UNION SELECT 'FOREIGN DATA WRAPPER'"
@@ -3131,14 +3166,16 @@ psql_completion(const char *text, int start, int end)
 									   " UNION SELECT 'SEQUENCE'"
 									   " UNION SELECT 'TABLE'"
 									   " UNION SELECT 'TABLESPACE'"
-									   " UNION SELECT 'TYPE'");
+									   " UNION SELECT 'TYPE'"
+									   " UNION SELECT 'VARIABLE'");
 	}
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
-		COMPLETE_WITH_LIST5("FUNCTIONS IN SCHEMA",
+		COMPLETE_WITH_LIST6("FUNCTIONS IN SCHEMA",
 							"PROCEDURES IN SCHEMA",
 							"ROUTINES IN SCHEMA",
 							"SEQUENCES IN SCHEMA",
-							"TABLES IN SCHEMA");
+							"TABLES IN SCHEMA",
+							"VARIABLES IN SCHEMA");
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
@@ -3172,6 +3209,8 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		else if (TailMatches1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		else if (TailMatches1("VARIABLE"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
 		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -3324,7 +3363,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 	else if (Matches3("PREPARE", MatchAny, "AS"))
-		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
+		COMPLETE_WITH_LIST5("SELECT", "UPDATE", "INSERT", "DELETE FROM", "LET");
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -3547,6 +3586,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
+/* LET --- can be inside EXPLAIN, PREPARE etc */
+	/* If prev. word is LET suggest a list of variables */
+	else if (TailMatches1("LET"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
+	/* Complete LET <variable> with "=" */
+	else if (TailMatches2("LET", MatchAny))
+		COMPLETE_WITH_CONST("=");
+
 /* USER MAPPING */
 	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 46c271a46c..3e38a05e55 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -180,7 +180,8 @@ typedef enum ObjectClass
 	OCLASS_PUBLICATION,			/* pg_publication */
 	OCLASS_PUBLICATION_REL,		/* pg_publication_rel */
 	OCLASS_SUBSCRIPTION,		/* pg_subscription */
-	OCLASS_TRANSFORM			/* pg_transform */
+	OCLASS_TRANSFORM,			/* pg_transform */
+	OCLASS_VARIABLE				/* pg_variable */
 } ObjectClass;
 
 #define LAST_OCLASS		OCLASS_TRANSFORM
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 24915824ca..dae80c20a8 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -360,4 +360,10 @@ DECLARE_UNIQUE_INDEX(pg_subscription_subname_index, 6115, on pg_subscription usi
 DECLARE_UNIQUE_INDEX(pg_subscription_rel_srrelid_srsubid_index, 6117, on pg_subscription_rel using btree(srrelid oid_ops, srsubid oid_ops));
 #define SubscriptionRelSrrelidSrsubidIndexId 6117
 
+DECLARE_UNIQUE_INDEX(pg_variable_oid_index, 4288, on pg_variable using btree(oid oid_ops));
+#define VariableObjectIndexId 4288
+
+DECLARE_UNIQUE_INDEX(pg_variable_varname_nsp_index, 4289, on pg_variable using btree(varname name_ops, varnamespace oid_ops));
+#define VariableNameNspIndexId  4289
+
 #endif							/* INDEXING_H */
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 0e202372d5..8812075b2e 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -75,10 +75,13 @@ extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *newRelation,
 extern void RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid);
 extern Oid	RelnameGetRelid(const char *relname);
 extern bool RelationIsVisible(Oid relid);
+extern bool VariableIsVisible(Oid relid);
 
 extern Oid	TypenameGetTypid(const char *typname);
 extern bool TypeIsVisible(Oid typid);
 
+extern bool VariableIsVisible(Oid varid);
+
 extern FuncCandidateList FuncnameGetCandidates(List *names,
 					  int nargs, List *argnames,
 					  bool expand_variadic,
@@ -146,6 +149,10 @@ extern void SetTempNamespaceState(Oid tempNamespaceId,
 					  Oid tempToastNamespaceId);
 extern void ResetTempTableNamespace(void);
 
+extern List *NamesFromList(List *names);
+extern Oid lookup_variable(const char *nspname, const char *varname, bool missing_ok);
+extern Oid identify_variable(List *names, char **attrname, bool *not_uniq);
+
 extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context);
 extern OverrideSearchPath *CopyOverrideSearchPath(OverrideSearchPath *path);
 extern bool OverrideSearchPathMatchesCurrent(OverrideSearchPath *path);
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index d0410f5586..56deef1a45 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -57,6 +57,7 @@ typedef FormData_pg_default_acl *Form_pg_default_acl;
 #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 a14651010f..61cbe65805 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5961,6 +5961,9 @@
   proname => 'pg_collation_is_visible', procost => '10', provolatile => 's',
   prorettype => 'bool', proargtypes => 'oid',
   prosrc => 'pg_collation_is_visible' },
+{ oid => '4187', descr => 'is schema 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 0000000000..2a2b1f3e08
--- /dev/null
+++ b/src/include/catalog/pg_variable.h
@@ -0,0 +1,91 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.h
+ *	  definition of schema variables system catalog (pg_variables)
+ *
+ *
+ * Portions Copyright (c) 1996-2018, 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,4287,VariableRelationId)
+{
+	NameData	varname;		/* variable name */
+	Oid			varnamespace;	/* OID of namespace containing variable class */
+	Oid			vartype;		/* OID of entry in pg_type for variable's type */
+	int32		vartypmod;			/* typmode for variable's type */
+	Oid			varowner;		/* class owner */
+	Oid			varcollation;	/* variable collation */
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+
+	/* list of expression trees for variable default (NULL if none) */
+	pg_node_tree vardefexpr BKI_DEFAULT(_null_);
+
+	aclitem		varacl[1] BKI_DEFAULT(_null_);		/* access permissions */
+
+#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;
+
+typedef struct Variable
+{
+	Oid		oid;
+	char   *name;
+	Oid		namespace;
+	Oid		typid;
+	int32	typmod;
+	Oid		owner;
+	Oid		collation;
+	Node   *defexpr;
+	Acl	   *acl;
+} Variable;
+
+/* returns fields from pg_variable table */
+extern char *get_schema_variable_name(Oid varid);
+extern void get_schema_variable_type_typmod_collid(Oid varid,
+												   Oid *typid,
+												   int32 *typmod,
+												   Oid *collid);
+
+/* returns name of variable based on current search path */
+extern char *schema_variable_get_name(Oid varid);
+
+extern Variable *GetVariable(Oid varid, bool missing_ok);
+extern ObjectAddress VariableCreate(const char *varName,
+									   Oid varNamespace,
+									   Oid varType,
+									   int32 varTypmod,
+									   Oid varOwner,
+									   Oid varCollation,
+									   Node *varDefexpr,
+									   bool if_not_exists);
+
+
+#endif							/* PG_VARIABLE_H */
diff --git a/src/include/commands/schemavariable.h b/src/include/commands/schemavariable.h
new file mode 100644
index 0000000000..2823d35b7c
--- /dev/null
+++ b/src/include/commands/schemavariable.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * schemavariable.h
+ *	  prototypes for schemavariable.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/schemavariable.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SCHEMAVARIABLE_H
+#define SCHEMAVARIABLE_H
+
+#include "catalog/objectaddress.h"
+#include "catalog/pg_variable.h"
+#include "nodes/params.h"
+#include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
+#include "utils/queryenvironment.h"
+
+extern void ResetSchemaVariableCache(void);
+
+extern void RemoveVariableById(Oid varid);
+extern ObjectAddress DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt *stmt);
+
+extern Datum GetSchemaVariable(Oid varid, bool *isNull, Oid expected_typid, bool copy);
+extern void SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid, int32 typmod);
+
+extern void doLetStmt(PlannedStmt *pstmt, ParamListInfo params, QueryEnvironment *queryEnv, const char *queryString);
+
+#endif
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index f7b1f77616..4fdceb6cee 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -138,6 +138,7 @@ typedef enum ExprEvalOp
 	EEOP_PARAM_EXEC,
 	EEOP_PARAM_EXTERN,
 	EEOP_PARAM_CALLBACK,
+	EEOP_PARAM_VARIABLE,
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -344,13 +345,22 @@ typedef struct ExprEvalStep
 			TupleDesc	argdesc;
 		}			nulltest_row;
 
-		/* for EEOP_PARAM_EXEC/EXTERN */
+		/* for EEOP_PARAM_EXEC/EXTERN/VARIABLE */
 		struct
 		{
-			int			paramid;	/* numeric ID for parameter */
-			Oid			paramtype;	/* OID of parameter's datatype */
+			int			paramid;		/* numeric ID for parameter */
+			Oid			paramtype;		/* OID of parameter's datatype */
 		}			param;
 
+		/* for EEOP_PARAM_VARIABLE */
+		struct
+		{
+			int			paramid;		/* numeric ID for parameter */
+			Oid			varoid;			/* OID of assigned variable */
+			Oid			paramtype;		/* OID of parameter's datatype */
+		}			vparam;
+
+
 		/* for EEOP_PARAM_CALLBACK */
 		struct
 		{
@@ -700,6 +710,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
 extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
 extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
 					ExprContext *econtext);
+extern void ExecEvalParamVariable(ExprState *state, ExprEvalStep *op,
+					ExprContext *econtext);
 extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/svariableReceiver.h b/src/include/executor/svariableReceiver.h
new file mode 100644
index 0000000000..8c8117701f
--- /dev/null
+++ b/src/include/executor/svariableReceiver.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.h
+ *	  prototypes for svariableReceiver.c
+ *
+ *
+ * Portions Copyright (c) 1996-2018, 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(void);
+
+extern void SetVariableDestReceiverParams(DestReceiver *self, Oid varid);
+
+#endif							/* SVARIABLE_RECEIVER_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 41fa2052a2..1d6eaf9ce7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -100,6 +100,8 @@ typedef struct ExprState
 	int			steps_len;		/* number of steps currently */
 	int			steps_alloc;	/* allocated length of steps array */
 
+	int			nvariables;		/* number of used variables */
+
 	struct PlanState *parent;	/* parent PlanState node, if any */
 	ParamListInfo ext_params;	/* for compiling PARAM_EXTERN nodes */
 
@@ -472,6 +474,7 @@ typedef struct ResultRelInfo
 typedef struct EState
 {
 	NodeTag		type;
+	bool		es_shared;		/* plpgsql uses share estate */
 
 	/* Basic state for all query types: */
 	ScanDirection es_direction; /* current scan direction */
@@ -564,6 +567,14 @@ typedef struct EState
 	/* The per-query shared memory area to use for parallel execution. */
 	struct dsa_area *es_query_dsa;
 
+	int			es_result_variable;	/* Oid of target variable */
+
+	/* query schema variable cache */
+	int			es_nvariables;
+	bool	   *es_varnulls;
+	Oid		   *es_vartypes;
+	Datum	   *es_varvalues;
+
 	/*
 	 * JIT information. es_jit_flags indicates whether JIT should be performed
 	 * and with which options.  es_jit is created on-demand when JITing is
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 697d3d7a5f..dd7fd8ed42 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -348,6 +348,7 @@ typedef enum NodeTag
 	T_CreateTableAsStmt,
 	T_CreateSeqStmt,
 	T_AlterSeqStmt,
+	T_CreateSchemaVarStmt,
 	T_VariableSetStmt,
 	T_VariableShowStmt,
 	T_DiscardStmt,
@@ -419,6 +420,7 @@ typedef enum NodeTag
 	T_CreateStatsStmt,
 	T_AlterCollationStmt,
 	T_CallStmt,
+	T_LetStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -663,6 +665,7 @@ typedef enum CmdType
 	CMD_DELETE,
 	CMD_UTILITY,				/* cmds like create, destroy, copy, vacuum,
 								 * etc. */
+	CMD_PLAN_UTILITY,			/* only let stmt now, requires planning */
 	CMD_NOTHING					/* dummy command for instead nothing rules
 								 * with qual */
 } CmdType;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07ab1a3dde..72e26b9f35 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -84,7 +84,9 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
 #define ACL_CREATE		(1<<9)	/* for namespaces and databases */
 #define ACL_CREATE_TEMP (1<<10) /* for databases */
 #define ACL_CONNECT		(1<<11) /* for databases */
-#define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
+#define ACL_READ		(1<<12) /* for variables */
+#define ACL_WRITE		(1<<13) /* for variables */
+#define N_ACL_RIGHTS	14		/* 1 plus the last 1<<x */
 #define ACL_NO_RIGHTS	0
 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
 #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
@@ -121,6 +123,7 @@ typedef struct Query
 
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
+	int			resultVariable;	/* Oid of target variable or 0 */
 
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
@@ -1505,6 +1508,18 @@ typedef struct UpdateStmt
 	WithClause *withClause;		/* WITH clause */
 } UpdateStmt;
 
+/* ----------------------
+ *		Let Statement
+ * ----------------------
+ */
+typedef struct LetStmt
+{
+	NodeTag		type;
+	List	   *target;			/* target variable */
+	Node	   *selectStmt;		/* source expression */
+	int			location;
+} LetStmt;
+
 /* ----------------------
  *		Select Statement
  *
@@ -1682,6 +1697,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -2497,6 +2513,20 @@ typedef struct AlterSeqStmt
 	bool		missing_ok;		/* skip error if a role is missing? */
 } AlterSeqStmt;
 
+/* ----------------------
+ *		{Create|Alter} VARIABLE Statement
+ * ----------------------
+ */
+typedef struct CreateSchemaVarStmt
+{
+	NodeTag		type;
+	RangeVar   *variable;		/* the variable to create */
+	TypeName   *typeName;		/* the type of variable */
+	CollateClause *collClause;
+	Node	   *defexpr;		/* default expression */
+	bool		if_not_exists;	/* do nothing if it already exists */
+} CreateSchemaVarStmt;
+
 /* ----------------------
  *		Create {Aggregate|Operator|Type} Statement
  * ----------------------
@@ -3238,7 +3268,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;
 
 typedef struct DiscardStmt
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 7c2abbd03a..2588f1455f 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -43,7 +43,7 @@ typedef struct PlannedStmt
 {
 	NodeTag		type;
 
-	CmdType		commandType;	/* select|insert|update|delete|utility */
+	CmdType		commandType;	/* select|let|insert|update|delete|utility */
 
 	uint64		queryId;		/* query identifier (copied from Query) */
 
@@ -81,6 +81,9 @@ typedef struct PlannedStmt
 	 */
 	List	   *rootResultRelations;
 
+	/* Oid of target variable for LET command */
+	Oid			resultVariable;
+
 	List	   *subplans;		/* Plan trees for SubPlan expressions; note
 								 * that some could be NULL */
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4b0d75af..97f838a54e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -229,13 +229,17 @@ 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 access to schema variable
+ *				paramid holds varid.
  */
 typedef enum ParamKind
 {
 	PARAM_EXTERN,
 	PARAM_EXEC,
 	PARAM_SUBLINK,
-	PARAM_MULTIEXPR
+	PARAM_MULTIEXPR,
+	PARAM_VARIABLE
 } ParamKind;
 
 typedef struct Param
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 23db40147b..d3ed3f4d0f 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -231,6 +231,7 @@ PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD)
 PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD)
 PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD)
 PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("let", LET, UNRESERVED_KEYWORD)
 PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
 PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
@@ -434,6 +435,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD)
 PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD)
 PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD)
+PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD)
+PG_KEYWORD("variables", VARIABLES, UNRESERVED_KEYWORD)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 0230543810..f7c2e67f33 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -69,7 +69,9 @@ typedef enum ParseExprKind
 	EXPR_KIND_TRIGGER_WHEN,		/* WHEN condition in CREATE TRIGGER */
 	EXPR_KIND_POLICY,			/* USING or WITH CHECK expr in policy */
 	EXPR_KIND_PARTITION_EXPRESSION, /* PARTITION BY expression */
-	EXPR_KIND_CALL_ARGUMENT		/* procedure argument in CALL */
+	EXPR_KIND_CALL_ARGUMENT,		/* procedure argument in CALL */
+	EXPR_KIND_VARIABLE_DEFAULT,	/* default value for schema variable */
+	EXPR_KIND_LET				/* LET assignment (should be same like UPDATE) */
 } ParseExprKind;
 
 
diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h
index ec6e0c102f..1ee199ed8f 100644
--- a/src/include/parser/parse_target.h
+++ b/src/include/parser/parse_target.h
@@ -32,6 +32,16 @@ extern Expr *transformAssignedExpr(ParseState *pstate, Expr *expr,
 					  int attrno,
 					  List *indirection,
 					  int location);
+extern Node *transformAssignmentIndirection(ParseState *pstate,
+							   Node *basenode,
+							   const char *targetName,
+							   bool targetIsArray,
+							   Oid targetTypeId,
+							   int32 targetTypMod,
+							   Oid targetCollation,
+							   ListCell *indirection,
+							   Node *rhs,
+							   int location);
 extern void updateTargetListEntry(ParseState *pstate, TargetEntry *tle,
 					  char *colname, int attrno,
 					  List *indirection,
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 82f0f2e741..c49b653555 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -96,7 +96,8 @@ typedef enum
 	DestCopyOut,				/* results sent to COPY TO code */
 	DestSQLFunction,			/* results sent to SQL-language func mgr */
 	DestTransientRel,			/* results sent to transient relation */
-	DestTupleQueue				/* results sent to tuple queue */
+	DestTupleQueue,				/* results sent to tuple queue */
+	DestVariable				/* results sents to schema variable */
 } CommandDest;
 
 /* ----------------
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index f4d4be8d0d..c624d8dd0b 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -147,9 +147,11 @@ typedef ArrayType Acl;
 #define ACL_CREATE_CHR			'C'
 #define ACL_CREATE_TEMP_CHR		'T'
 #define ACL_CONNECT_CHR			'c'
+#define ACL_READ_CHR			'S' /* 'R' is occupated by old RULE priv */
+#define ACL_WRITE_CHR			'W'
 
 /* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTc"
+#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTcSW"
 
 /*
  * Bitmasks defining "all rights" for each supported object type
@@ -166,6 +168,7 @@ typedef 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_READ|ACL_WRITE)
 
 /* operation codes for pg_*_aclmask */
 typedef enum
@@ -253,6 +256,8 @@ extern AclMode pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
 						  AclMode mask, AclMaskHow how);
 extern AclMode pg_type_aclmask(Oid type_oid, Oid roleid,
 				AclMode mask, AclMaskHow how);
+extern AclMode pg_variable_aclmask(Oid var_oid, Oid roleid,
+				AclMode mask, AclMaskHow how);
 
 extern AclResult pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum,
 					  Oid roleid, AclMode mode);
@@ -269,6 +274,7 @@ extern AclResult pg_tablespace_aclcheck(Oid spc_oid, Oid roleid, AclMode mode);
 extern AclResult pg_foreign_data_wrapper_aclcheck(Oid fdw_oid, Oid roleid, AclMode mode);
 extern AclResult pg_foreign_server_aclcheck(Oid srv_oid, Oid roleid, AclMode mode);
 extern AclResult pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode);
+extern AclResult pg_variable_aclcheck(Oid type_oid, Oid roleid, AclMode mode);
 
 extern void aclcheck_error(AclResult aclerr, ObjectType objtype,
 			   const char *objectname);
@@ -305,6 +311,7 @@ extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
 extern bool pg_publication_ownercheck(Oid pub_oid, Oid roleid);
 extern bool pg_subscription_ownercheck(Oid sub_oid, Oid roleid);
 extern bool pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid);
+extern bool pg_variable_ownercheck(Oid stat_oid, Oid roleid);
 extern bool has_createrole_privilege(Oid roleid);
 extern bool has_bypassrls_privilege(Oid roleid);
 
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index e55ea4035b..cb3f4aaca9 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -122,6 +122,7 @@ extern bool get_func_leakproof(Oid funcid);
 extern float4 get_func_cost(Oid funcid);
 extern float4 get_func_rows(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);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 4f333586ee..453699be3c 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -107,9 +107,11 @@ enum SysCacheIdentifier
 	TYPENAMENSP,
 	TYPEOID,
 	USERMAPPINGOID,
-	USERMAPPINGUSERSERVER
+	USERMAPPINGUSERSERVER,
+	VARIABLENAMENSP,
+	VARIABLEOID
 
-#define SysCacheSize (USERMAPPINGUSERSERVER + 1)
+#define SysCacheSize (VARIABLEOID + 1)
 };
 
 extern void InitCatalogCache(void);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 380d1de8f4..ac71dd7d7a 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8049,6 +8049,7 @@ plpgsql_create_econtext(PLpgSQL_execstate *estate)
 		{
 			oldcontext = MemoryContextSwitchTo(TopTransactionContext);
 			shared_simple_eval_estate = CreateExecutorState();
+			shared_simple_eval_estate->es_shared = true;
 			MemoryContextSwitchTo(oldcontext);
 		}
 		estate->simple_eval_estate = shared_simple_eval_estate;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 7d3647a12d..7f183d4f1b 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -332,6 +332,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
 
 	/* Create a private EState for simple-expression execution */
 	simple_eval_estate = CreateExecutorState();
+	simple_eval_estate->es_shared = true;
 
 	/* And run the function */
 	PG_TRY();
diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out
index 2d3522b500..48286f8e1a 100644
--- a/src/test/regress/expected/misc_sanity.out
+++ b/src/test/regress/expected/misc_sanity.out
@@ -105,5 +105,7 @@ ORDER BY 1, 2;
  pg_index                | indpred       | pg_node_tree
  pg_largeobject          | data          | bytea
  pg_largeobject_metadata | lomacl        | aclitem[]
-(11 rows)
+ pg_variable             | varacl        | aclitem[]
+ pg_variable             | vardefexpr    | pg_node_tree
+(13 rows)
 
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 0aa5357917..848b041a4b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -163,6 +163,7 @@ pg_ts_parser|t
 pg_ts_template|t
 pg_type|t
 pg_user_mapping|t
+pg_variable|t
 point_tbl|t
 polygon_tbl|t
 quad_box_tbl|t
diff --git a/src/test/regress/expected/schema_variables.out b/src/test/regress/expected/schema_variables.out
new file mode 100644
index 0000000000..f8c72ccd85
--- /dev/null
+++ b/src/test/regress/expected/schema_variables.out
@@ -0,0 +1,366 @@
+CREATE VARIABLE var1 AS integer;
+CREATE TEMP VARIABLE var2 AS text;
+DROP VARIABLE var1, var2;
+-- functional interface
+CREATE VARIABLE var1 AS numeric;
+CREATE ROLE var_test_role;
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT var1;
+ERROR:  permission denied for schema variable var1
+SET ROLE TO DEFAULT;
+GRANT READ ON VARIABLE var1 TO var_test_role;
+SET ROLE TO var_test_role;
+-- should to fail
+LET var1 = 10;
+ERROR:  permission denied for schema variable var1
+-- should to work
+SELECT var1;
+ var1 
+------
+     
+(1 row)
+
+SET ROLE TO DEFAULT;
+GRANT WRITE ON VARIABLE var1 TO var_test_role;
+SET ROLE TO var_test_role;
+-- should to work
+LET var1 = 333;
+SET ROLE TO DEFAULT;
+REVOKE ALL ON VARIABLE var1 FROM var_test_role;
+CREATE OR REPLACE FUNCTION secure_var()
+RETURNS int AS $$
+  SELECT public.var1::int;
+$$ LANGUAGE sql SECURITY DEFINER;
+SELECT secure_var();
+ secure_var 
+------------
+        333
+(1 row)
+
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT public.var1;
+ERROR:  permission denied for schema variable var1
+-- should to work;
+SELECT secure_var();
+ secure_var 
+------------
+        333
+(1 row)
+
+SET ROLE TO DEFAULT;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var1;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Function Scan on pg_catalog.generate_series g
+   Output: v
+   Function Call: generate_series(1, 100)
+   Filter: ((g.v)::numeric = var1)
+(4 rows)
+
+CREATE VIEW schema_var_view AS SELECT var1;
+SELECT * FROM schema_var_view;
+ var1 
+------
+  333
+(1 row)
+
+\c -
+-- should to work still, but var will be empty
+SELECT * FROM schema_var_view;
+ var1 
+------
+     
+(1 row)
+
+LET var1 = pi();
+SELECT var1;
+       var1       
+------------------
+ 3.14159265358979
+(1 row)
+
+-- we can look on execution plan
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+         QUERY PLAN         
+----------------------------
+ Result
+   Output: 3.14159265358979
+(2 rows)
+
+-- LET can be prepared
+PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2;
+EXECUTE var_pp(100, 1.23456);
+SELECT var1;
+   var1    
+-----------
+ 101.23456
+(1 row)
+
+CREATE VARIABLE var3 AS int;
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+  LET public.var3 = COALESCE(public.var3 + $1, $1);
+  RETURN var3;
+END;
+$$ LANGUAGE plpgsql;
+SELECT inc(1);
+ inc 
+-----
+   1
+(1 row)
+
+SELECT inc(1);
+ inc 
+-----
+   2
+(1 row)
+
+SELECT inc(1);
+ inc 
+-----
+   3
+(1 row)
+
+SELECT inc(1) FROM generate_series(1,10);
+ inc 
+-----
+   4
+   5
+   6
+   7
+   8
+   9
+  10
+  11
+  12
+  13
+(10 rows)
+
+SET ROLE TO var_test_role;
+-- should to fail
+LET var3 = 0;
+ERROR:  permission denied for schema variable var3
+SET ROLE TO DEFAULT;
+DROP VIEW schema_var_view;
+DROP VARIABLE var1 CASCADE;
+DROP VARIABLE var3 CASCADE;
+-- 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;
+\d v1
+\d v2
+LET v1 = (1,2,3.14);
+LET v2 = (10,20,3.14*10);
+-- should to work too - there are prepared casts
+LET v1 = (1,2,3.14);
+SELECT v1;
+     v1     
+------------
+ (1,2,3.14)
+(1 row)
+
+SELECT v2;
+      v2       
+---------------
+ (10,20,31.40)
+(1 row)
+
+SELECT (v1).*;
+ x | y |  z   
+---+---+------
+ 1 | 2 | 3.14
+(1 row)
+
+SELECT (v2).*;
+ x  | y  |   z   
+----+----+-------
+ 10 | 20 | 31.40
+(1 row)
+
+SELECT v1.x + v1.z;
+ ?column? 
+----------
+     4.14
+(1 row)
+
+SELECT v2.x + v2.z;
+ ?column? 
+----------
+    41.40
+(1 row)
+
+-- access to composite fields should be safe too
+-- should to fail
+SET ROLE TO var_test_role;
+SELECT v2.x;
+ERROR:  permission denied for schema variable v2
+SET ROLE TO DEFAULT;
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+DROP ROLE var_test_role;
+-- scalar variables should not be in conflict with qualified column
+CREATE VARIABLE varx AS text;
+SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class';
+ relname  
+----------
+ pg_class
+(1 row)
+
+-- should to fail
+SELECT varx.xxx;
+ERROR:  type text is not composite
+-- variables can be updated under RO transaction
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET varx = 'hello';
+COMMIT;
+SELECT varx;
+ varx  
+-------
+ hello
+(1 row)
+
+DROP VARIABLE varx;
+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 to fail
+LET v1.x = 10;
+ERROR:  cannot assign to field "x" of column "x" because there is no such column in data type t1
+LINE 1: LET v1.x = 10;
+            ^
+DROP VARIABLE v1;
+DROP TYPE t1;
+-- arrays are supported
+CREATE VARIABLE va1 AS numeric[];
+LET va1 = ARRAY[1.1,2.1];
+LET va1[1] = 10.1;
+SELECT va1;
+    va1     
+------------
+ {10.1,2.1}
+(1 row)
+
+CREATE TYPE ta2 AS (a numeric, b numeric[]);
+CREATE VARIABLE va2 AS ta2;
+LET va2 = (10.1, ARRAY[0.0, 0.0]);
+LET va2.a = 10.2;
+SELECT va2;
+        va2         
+--------------------
+ (10.2,"{0.0,0.0}")
+(1 row)
+
+LET va2.b[1] = 10.3;
+SELECT va2;
+         va2         
+---------------------
+ (10.2,"{10.3,0.0}")
+(1 row)
+
+DROP VARIABLE va1;
+DROP VARIABLE va2;
+DROP TYPE ta2;
+-- default values
+CREATE VARIABLE v1 AS numeric DEFAULT pi();
+LET v1 = v1 * 2;
+SELECT v1;
+        v1        
+------------------
+ 6.28318530717958
+(1 row)
+
+CREATE TYPE t2 AS (a numeric, b text);
+CREATE VARIABLE v2 AS t2 DEFAULT (NULL, 'Hello');
+LET public.v2.a = pi();
+SELECT v2;
+            v2            
+--------------------------
+ (3.14159265358979,Hello)
+(1 row)
+
+-- shoudl fail due dependency
+DROP TYPE t2;
+ERROR:  cannot drop type t2 because other objects depend on it
+DETAIL:  schema variable v2 depends on type t2
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- should be ok
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+-- tests of alters
+CREATE SCHEMA var_schema1;
+CREATE SCHEMA var_schema2;
+CREATE VARIABLE var_schema1.var1 AS integer;
+LET var_schema1.var1 = 1000;
+SELECT var_schema1.var1;
+ var1 
+------
+ 1000
+(1 row)
+
+ALTER VARIABLE var_schema1.var1 SET SCHEMA var_schema2;
+SELECT var_schema2.var1;
+ var1 
+------
+ 1000
+(1 row)
+
+CREATE ROLE var_test_role;
+ALTER VARIABLE var_schema2.var1 OWNER TO var_test_role;
+SET ROLE TO var_test_role;
+-- should fail, no access to schema var_schema2.var
+SELECT var_schema2.var1;
+ERROR:  permission denied for schema var_schema2
+DROP VARIABLE var_schema2.var1;
+ERROR:  permission denied for schema var_schema2
+SET ROLE TO DEFAULT;
+ALTER VARIABLE var_schema2.var1 SET SCHEMA public;
+SET ROLE TO var_test_role;
+SELECT public.var1;
+ var1 
+------
+ 1000
+(1 row)
+
+ALTER VARIABLE public.var1 RENAME TO var1_renamed;
+SELECT public.var1_renamed;
+ var1_renamed 
+--------------
+         1000
+(1 row)
+
+DROP VARIABLE public.var1_renamed;
+SET ROLE TO DEFAULt;
+DROP ROLE var_test_role;
+CREATE VARIABLE xx AS text DEFAULT 'hello';
+SELECT xx, upper(xx);
+  xx   | upper 
+-------+-------
+ hello | HELLO
+(1 row)
+
+LET xx = 'Hi';
+SELECT xx;
+ xx 
+----
+ Hi
+(1 row)
+
+DROP VARIABLE xx;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 16f979c8d9..9bf379b87b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # 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 without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml schema_variables
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 42632be675..42bf4ecb3f 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -191,3 +191,4 @@ test: partition_aggregate
 test: event_trigger
 test: fast_default
 test: stats
+test: schema_variables
diff --git a/src/test/regress/sql/schema_variables.sql b/src/test/regress/sql/schema_variables.sql
new file mode 100644
index 0000000000..6bd801e771
--- /dev/null
+++ b/src/test/regress/sql/schema_variables.sql
@@ -0,0 +1,257 @@
+CREATE VARIABLE var1 AS integer;
+CREATE TEMP VARIABLE var2 AS text;
+
+DROP VARIABLE var1, var2;
+
+-- functional interface
+CREATE VARIABLE var1 AS numeric;
+
+CREATE ROLE var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+SELECT var1;
+
+SET ROLE TO DEFAULT;
+
+GRANT READ ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+-- should to fail
+LET var1 = 10;
+-- should to work
+SELECT var1;
+
+SET ROLE TO DEFAULT;
+
+GRANT WRITE ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should to work
+LET var1 = 333;
+
+SET ROLE TO DEFAULT;
+
+REVOKE ALL ON VARIABLE var1 FROM var_test_role;
+
+CREATE OR REPLACE FUNCTION secure_var()
+RETURNS int AS $$
+  SELECT public.var1::int;
+$$ LANGUAGE sql SECURITY DEFINER;
+
+SELECT secure_var();
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+SELECT public.var1;
+
+-- should to work;
+SELECT secure_var();
+
+SET ROLE TO DEFAULT;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var1;
+
+CREATE VIEW schema_var_view AS SELECT var1;
+
+SELECT * FROM schema_var_view;
+
+\c -
+
+-- should to work still, but var will be empty
+SELECT * FROM schema_var_view;
+
+LET var1 = pi();
+
+SELECT var1;
+
+-- we can look on execution plan
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+
+-- LET can be prepared
+PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2;
+
+EXECUTE var_pp(100, 1.23456);
+
+SELECT var1;
+
+CREATE VARIABLE var3 AS int;
+
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+  LET public.var3 = COALESCE(public.var3 + $1, $1);
+  RETURN var3;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT inc(1);
+SELECT inc(1);
+SELECT inc(1);
+
+SELECT inc(1) FROM generate_series(1,10);
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+LET var3 = 0;
+
+SET ROLE TO DEFAULT;
+
+DROP VIEW schema_var_view;
+
+DROP VARIABLE var1 CASCADE;
+DROP VARIABLE var3 CASCADE;
+
+-- 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;
+
+\d v1
+\d v2
+
+LET v1 = (1,2,3.14);
+LET v2 = (10,20,3.14*10);
+
+-- should to work too - there are prepared casts
+LET v1 = (1,2,3.14);
+
+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
+-- should to fail
+SET ROLE TO var_test_role;
+
+SELECT v2.x;
+
+SET ROLE TO DEFAULT;
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+DROP ROLE var_test_role;
+
+-- scalar variables should not be in conflict with qualified column
+CREATE VARIABLE varx AS text;
+SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class';
+
+-- should to fail
+SELECT varx.xxx;
+
+-- variables can be updated under RO transaction
+
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET varx = 'hello';
+COMMIT;
+
+SELECT varx;
+
+DROP VARIABLE varx;
+
+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 to fail
+LET v1.x = 10;
+
+DROP VARIABLE v1;
+DROP TYPE t1;
+
+-- arrays are supported
+CREATE VARIABLE va1 AS numeric[];
+LET va1 = ARRAY[1.1,2.1];
+LET va1[1] = 10.1;
+SELECT va1;
+
+CREATE TYPE ta2 AS (a numeric, b numeric[]);
+CREATE VARIABLE va2 AS ta2;
+LET va2 = (10.1, ARRAY[0.0, 0.0]);
+LET va2.a = 10.2;
+SELECT va2;
+LET va2.b[1] = 10.3;
+SELECT va2;
+
+DROP VARIABLE va1;
+DROP VARIABLE va2;
+DROP TYPE ta2;
+
+-- default values
+CREATE VARIABLE v1 AS numeric DEFAULT pi();
+LET v1 = v1 * 2;
+SELECT v1;
+
+CREATE TYPE t2 AS (a numeric, b text);
+CREATE VARIABLE v2 AS t2 DEFAULT (NULL, 'Hello');
+LET public.v2.a = pi();
+SELECT v2;
+
+-- shoudl fail due dependency
+DROP TYPE t2;
+
+-- should be ok
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+-- tests of alters
+CREATE SCHEMA var_schema1;
+CREATE SCHEMA var_schema2;
+
+CREATE VARIABLE var_schema1.var1 AS integer;
+LET var_schema1.var1 = 1000;
+SELECT var_schema1.var1;
+ALTER VARIABLE var_schema1.var1 SET SCHEMA var_schema2;
+SELECT var_schema2.var1;
+
+CREATE ROLE var_test_role;
+
+ALTER VARIABLE var_schema2.var1 OWNER TO var_test_role;
+SET ROLE TO var_test_role;
+
+-- should fail, no access to schema var_schema2.var
+SELECT var_schema2.var1;
+DROP VARIABLE var_schema2.var1;
+
+SET ROLE TO DEFAULT;
+
+ALTER VARIABLE var_schema2.var1 SET SCHEMA public;
+
+SET ROLE TO var_test_role;
+SELECT public.var1;
+
+ALTER VARIABLE public.var1 RENAME TO var1_renamed;
+
+SELECT public.var1_renamed;
+
+DROP VARIABLE public.var1_renamed;
+
+SET ROLE TO DEFAULt;
+
+DROP ROLE var_test_role;
+
+CREATE VARIABLE xx AS text DEFAULT 'hello';
+
+SELECT xx, upper(xx);
+
+LET xx = 'Hi';
+
+SELECT xx;
+
+DROP VARIABLE xx;
