[prev in list] [next in list] [prev in thread] [next in thread]
List: pgsql-hackers
Subject: Re: Schema variables - new implementation for Postgres 15
From: Pavel Stehule <pavel.stehule () gmail ! com>
Date: 2021-10-30 6:35:03
Message-ID: CAFj8pRAPcF7u_rV_2p=vaGFf8UkW1-Z7kef4rTFpsgy8JROH_w () mail ! gmail ! com
[Download RAW message or body]
[Attachment #2 (multipart/alternative)]
Hi
only rebase
Regards
Pavel
=C4=8Dt 16. 9. 2021 v 7:15 odes=C3=ADlatel Pavel Stehule <pavel.stehule@gma=
il.com>
napsal:
> Hi
>
> just rebase of patch
>
> Regards
>
> Pavel
>
>
[Attachment #5 (text/html)]
<div dir="ltr"><div>Hi</div><div><br></div><div>only \
rebase</div><div><br></div><div>Regards</div><div><br></div><div>Pavel<br></div><br><div \
class="gmail_quote"><div dir="ltr" class="gmail_attr">čt 16. 9. 2021 v 7:15 \
odesílatel Pavel Stehule <<a \
href="mailto:pavel.stehule@gmail.com">pavel.stehule@gmail.com</a>> \
napsal:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px \
0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div \
dir="ltr"><div>Hi</div><div><br></div><div>just rebase of \
patch</div><div><br></div><div>Regards</div><div><br></div><div>Pavel</div><div><br></div></div>
</blockquote></div></div>
--0000000000001d8fdb05cf8c2762--
["schema-variables-20211031.patch" (text/x-patch)]
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 71ae423f63..2d6555497c 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -700,6 +700,56 @@ SELECT name, elevation
</sect1>
+ <sect1 id="tutorial-schema-variables">
+ <title>Schema Variables</title>
+
+ <indexterm zone="tutorial-schema-variables">
+ <primary>Schema variables</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>schema variable</primary>
+ <secondary>introduction</secondary>
+ </indexterm>
+
+ <para>
+ Schema variables are database objects that can hold a value.
+ Schema variables, like relations, exist within a schema and their access is
+ controlled via <command>GRANT</command> and <command>REVOKE</command> commands.
+ The schema variable can be created by the <command>CREATE VARIABLE</command> \
command. + </para>
+
+ <para>
+ The value of a schema variable is local to the current session. Retrieving
+ a variable's value returns either a NULL or a default value, unless its value
+ is set to something else in the current session with a LET command. The content
+ of a variable is not transactional. This is the same as in regular variables
+ in PL languages.
+ </para>
+
+ <para>
+ Schema variables are retrieved by the <command>SELECT</command> SQL command.
+ Their value is set with the <command>LET</command> SQL command.
+ While schema variables share properties with tables, their value cannot be \
updated + with an <command>UPDATE</command> command.
+<programlisting>
+CREATE VARIABLE var1 AS date;
+LET var1 = current_date;
+SELECT var1;
+</programlisting>
+
+ or
+
+<programlisting>
+CREATE VARIABLE public.current_user_id AS integer;
+GRANT READ ON VARIABLE public.current_user_id TO PUBLIC;
+LET current_user_id = (SELECT id FROM users WHERE usename = session_user);
+SELECT current_user_id;
+</programlisting>
+ </para>
+ </sect1>
+
+
<sect1 id="tutorial-conclusion">
<title>Conclusion</title>
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c1d11be73f..179fa873dc 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -364,6 +364,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>
@@ -13959,4 +13964,143 @@ 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_variable</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</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 variable's data type.
+ </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 variable creation time (for example, the maximum
+ length of a <type>varchar</type> column). It is passed to
+ type-specific input
+ functions and length coercion functions.
+ The value will generally be -1 for types that do not need \
<structfield>vartypmod</structfield>. + </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>varisnotnull</structfield></entry>
+ <entry><type>boolean</type></entry>
+ <entry></entry>
+ <entry>
+ True if the schema variable doesn't allow null value. The default value is \
false. + </entry>
+ </row>
+
+ <row>
+ <entry><structfield>varisimmutable</structfield></entry>
+ <entry><type>boolean</type></entry>
+ <entry></entry>
+ <entry>
+ True if the variable is immutable (cannot be modified). The default value is \
false. + </entry>
+ </row>
+
+ <row>
+ <entry><structfield>vareoxaction</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ <literal>n</literal> = no action, <literal>d</literal> = drop the variable,
+ <literal>r</literal> = reset the variable to its default value.
+ </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/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index e5c1356d8c..e494161741 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5952,6 +5952,19 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE;
</programlisting>
</para>
</sect3>
+
+ <sect3>
+ <title><command>Global variables and constants</command></title>
+
+ <para>
+ The <application>PL/pgSQL</application> language has no packages,
+ and therefore no package variables or package constants.
+ <productname>PostgreSQL</productname> has schema variables and
+ immutable schema variables. Schema variables can be created
+ by <command>CREATE VARIABLE</command> described in <xref
+ linkend="sql-createvariable"/>.
+ </para>
+ </sect3>
</sect2>
<sect2 id="plpgsql-porting-appendix">
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index d67270ccc3..8458cad752 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_default_privileges.sgml \
b/doc/src/sgml/ref/alter_default_privileges.sgml index f1d54f5aa3..95e8352167 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -50,6 +50,10 @@ GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
ON SCHEMAS
TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } \
[, ...] [ WITH GRANT OPTION ]
+GRANT { READ | WRITE | ALL [ PRIVILEGES ] }
+ ON VARIABLES
+ TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } \
[, ...] [ WITH GRANT OPTION ] +
REVOKE [ GRANT OPTION FOR ]
{ { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
[, ...] | ALL [ PRIVILEGES ] }
@@ -81,6 +85,12 @@ REVOKE [ GRANT OPTION FOR ]
ON SCHEMAS
FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC \
} [, ...] [ CASCADE | RESTRICT ]
+
+REVOKE [ GRANT OPTION FOR ]
+ { { READ | WRITE } [, ...] | ALL [ PRIVILEGES ] }
+ ON VARIABLES
+ FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC \
} [, ...] + [ CASCADE | RESTRICT ]
</synopsis>
</refsynopsisdiv>
@@ -92,8 +102,8 @@ REVOKE [ GRANT OPTION FOR ]
that will be applied to objects created in the future. (It does not
affect privileges assigned to already-existing objects.) Currently,
only the privileges for schemas, tables (including views and foreign
- tables), sequences, functions, and types (including domains) can be
- altered. For this command, functions include aggregates and procedures.
+ tables), sequences, functions, types (including domains) and schema variables
+ can be altered. For this command, functions include aggregates and procedures.
The words <literal>FUNCTIONS</literal> and <literal>ROUTINES</literal> are
equivalent in this command. (<literal>ROUTINES</literal> is preferred
going forward as the standard term for functions and procedures taken
diff --git a/doc/src/sgml/ref/alter_variable.sgml \
b/doc/src/sgml/ref/alter_variable.sgml new file mode 100644
index 0000000000..85a38fb9d5
--- /dev/null
+++ b/doc/src/sgml/ref/alter_variable.sgml
@@ -0,0 +1,179 @@
+<!--
+doc/src/sgml/ref/alter_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-altervariable">
+ <indexterm zone="sql-altervariable">
+ <primary>ALTER VARIABLE</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>schema variable</primary>
+ <secondary>altering</secondary>
+ </indexterm>
+
+ <refmeta>
+ <refentrytitle>ALTER VARIABLE</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>ALTER VARIABLE</refname>
+ <refpurpose>
+ change the definition of a 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>
+ The <command>ALTER VARIABLE</command> 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>
+ Only the owner or a superuser is allowed to alter a schema variable.
+ In order to move a schema variable from one schema to another, the user must
+ also have the <literal>CREATE</literal> privilege on the new schema (or be
+ a superuser).
+
+ In order to move the schema variable ownership from one role to another,
+ the user must also be a direct or indirect member of the new
+ owning role, and that role must have the <literal>CREATE</literal> privilege
+ on the variable's schema (or be a superuser). These restrictions enforce that
+ altering the owner doesn't do anything you couldn't do by dropping and
+ recreating the variable.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term><replaceable class="parameter">name</replaceable></term>
+ <listitem>
+ <para>
+ The name (possibly schema-qualified) of the existing 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>
+ Schema variables and this command in particular are a PostgreSQL extension.
+ </para>
+ </refsect1>
+
+ <refsect1 id="sql-altervariable-see-also">
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-createvariable"/></member>
+ <member><xref linkend="sql-dropvariable"/></member>
+ <member><xref linkend="sql-let"/></member>
+ </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/create_variable.sgml \
b/doc/src/sgml/ref/create_variable.sgml new file mode 100644
index 0000000000..859c0bc2f1
--- /dev/null
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -0,0 +1,190 @@
+<!--
+doc/src/sgml/ref/create_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-createvariable">
+ <indexterm zone="sql-createvariable">
+ <primary>CREATE VARIABLE</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>schema variable</primary>
+ <secondary>defining</secondary>
+ </indexterm>
+
+ <refmeta>
+ <refentrytitle>CREATE VARIABLE</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>CREATE VARIABLE</refname>
+ <refpurpose>define a schema variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE [ { TEMPORARY | TEMP } ] [ IMMUTABLE ] VARIABLE [ IF NOT EXISTS ] \
<replaceable class="parameter">name</replaceable> [ AS ] <replaceable \
class="parameter">data_type</replaceable> ] [ COLLATE <replaceable \
class="parameter">collation</replaceable> ] + [ NOT NULL ] [ DEFAULT <replaceable \
class="parameter">default_expr</replaceable> ] [ { ON COMMIT DROP | ON TRANSACTION \
END RESET } ] +</synopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ The <command>CREATE VARIABLE</command> command creates a schema variable.
+ Schema variables, like relations, exist within a schema and their access is
+ controlled via <command>GRANT</command> and <command>REVOKE</command> commands.
+ Changing a schema variable is non-transactional.
+ </para>
+
+ <para>
+ The value of a schema variable is local to the current session. Retrieving
+ a variable's value returns either a NULL or a default value, unless its value
+ is set to something else in the current session with a LET command. The content
+ of a variable is not transactional. This is the same as in regular variables in \
PL languages. + </para>
+
+ <para>
+ Schema variables are retrieved by the <command>SELECT</command> SQL command.
+ Their value is set with the <command>LET</command> SQL command.
+ While schema variables share properties with tables, their value cannot be \
updated + with an <command>UPDATE</command> command.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>IMMUTABLE</literal></term>
+ <listitem>
+ <para>
+ The value of the variable cannot be changed.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <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. + </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">name</replaceable></term>
+ <listitem>
+ <para>
+ The name, optionally schema-qualified, of the variable.
+ </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.
+ </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>
+
+ <varlistentry>
+ <term><literal>NOT NULL</literal></term>
+ <listitem>
+ <para>
+ The <literal>NOT NULL</literal> clause forbids to set the variable to
+ a null value. A variable created as NOT NULL and without an explicitly
+ declared default value cannot be read until it is initialized by a LET
+ command. This obliges the user to explicitly initialize the variable
+ content before reading it.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DEFAULT <replaceable>default_expr</replaceable></literal></term>
+ <listitem>
+ <para>
+ The <literal>DEFAULT</literal> clause can be used to assign a default value to
+ a schema variable.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>ON COMMIT DROP</literal>, <literal>ON TRANSACTION END \
RESET</literal></term> + <listitem>
+ <para>
+ The <literal>ON COMMIT DROP</literal> clause specifies the behaviour
+ of a temporary schema variable at transaction commit. With this clause the
+ variable is dropped at commit time. The clause is only allowed
+ for temporary variables. The <literal>ON TRANSACTION END RESET</literal>
+ clause enforces the variable to be reset to its default value when
+ the transaction is committed or rolled back.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Use the <command>DROP VARIABLE</command> command to remove a variable.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>
+ Create an integer variable <literal>var1</literal>:
+<programlisting>
+CREATE VARIABLE var1 AS date;
+LET var1 = current_date;
+SELECT var1;
+</programlisting>
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ The <command>CREATE VARIABLE</command> command is a PostgreSQL extension.
+ <!-- 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>
+ <member><xref linkend="sql-let"/></member>
+ </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml
index bf44c523ca..698b2c07fd 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. If a variable
+ is later reused, it is re-initialized to either
+ NULL or its default value.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>ALL</literal></term>
<listitem>
@@ -93,6 +104,7 @@ SELECT pg_advisory_unlock_all();
DISCARD PLANS;
DISCARD TEMP;
DISCARD SEQUENCES;
+DISCARD VARIABLES;
</programlisting></para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/drop_variable.sgml \
b/doc/src/sgml/ref/drop_variable.sgml new file mode 100644
index 0000000000..da3632e780
--- /dev/null
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -0,0 +1,120 @@
+<!--
+doc/src/sgml/ref/drop_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-dropvariable">
+ <indexterm zone="sql-dropvariable">
+ <primary>DROP VARIABLE</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>schema variable</primary>
+ <secondary>removing</secondary>
+ </indexterm>
+
+ <refmeta>
+ <refentrytitle>DROP VARIABLE</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>DROP VARIABLE</refname>
+ <refpurpose>remove a 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.
+ </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>
+
+ <varlistentry>
+ <term><literal>CASCADE</literal></term>
+ <listitem>
+ <para>
+ Automatically drop objects that depend on the variable (such as
+ views),
+ and in turn all objects that depend on those objects
+ (see <xref linkend="ddl-depend"/>).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>RESTRICT</literal></term>
+ <listitem>
+ <para>
+ Refuse to drop the variable if any objects depend on it. This is
+ the default.
+ </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>
+ The <command>DROP VARIABLE</command> command is a PostgreSQL extension.
+ <!-- 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>
+ <member><xref linkend="sql-let"/></member>
+ </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index a897712de2..6586e412c1 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -103,6 +103,11 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, \
...] TO <replace | CURRENT_ROLE
| CURRENT_USER
| SESSION_USER
+
+GRANT { READ | WRITE | ALL [ PRIVILEGES ] }
+ ON VARIABLE <replaceable>variable_name</replaceable> [, ...]
+ TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ \
WITH GRANT OPTION ] +
</synopsis>
</refsynopsisdiv>
@@ -201,6 +206,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's content.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>ALL PRIVILEGES</literal></term>
<listitem>
diff --git a/doc/src/sgml/ref/let.sgml b/doc/src/sgml/ref/let.sgml
new file mode 100644
index 0000000000..d4396cad3c
--- /dev/null
+++ b/doc/src/sgml/ref/let.sgml
@@ -0,0 +1,107 @@
+<!--
+doc/src/sgml/ref/let.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-let">
+ <indexterm zone="sql-let">
+ <primary>LET</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>schema variable</primary>
+ <secondary>changing</secondary>
+ </indexterm>
+
+ <refmeta>
+ <refentrytitle>LET</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>LET</refname>
+ <refpurpose>change a schema variable's value</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+LET <replaceable class="parameter">schema_variable</replaceable> = <replaceable \
class="parameter">sql_expression</replaceable> +LET <replaceable \
class="parameter">schema_variable</replaceable> = DEFAULT +</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 the schema variable.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>sql_expression</literal></term>
+ <listitem>
+ <para>
+ An SQL expression. The result is cast into the schema variable's type.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DEFAULT</literal></term>
+ <listitem>
+ <para>
+ Reset the schema variable to its default value, if that is defined.
+ If no explicit default value has been assigned, the schema variable
+ is set to NULL.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>
+ Example:
+<programlisting>
+CREATE VARIABLE myvar AS integer;
+LET myvar = 10;
+LET myvar = (SELECT sum(val) FROM tab);
+LET myvar = DEFAULT;
+</programlisting>
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ <literal>LET</literal> extends the syntax defined in the SQL
+ standard. The <literal>SET</literal> command from the SQL standard
+ is used for different purposes in PostgreSQL.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-altervariable"/></member>
+ <member><xref linkend="sql-createvariable"/></member>
+ <member><xref linkend="sql-dropvariable"/></member>
+ </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index 93ea937ac8..0d86239eb4 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -106,6 +106,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-A <replaceable \
class="parameter">schema_variable</replaceable></option></term> + \
<term><option>--variable=<replaceable \
class="parameter">schema_variable</replaceable></option></term> + <listitem>
+ <para>
+ Restore a named schema variable only. Multiple schema variables may be \
specified with + multiple <option>-A</option> switches.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-c</option></term>
<term><option>--clean</option></term>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 3014c864ea..3e9f9804be 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -130,6 +130,12 @@ REVOKE [ ADMIN OPTION FOR ]
| CURRENT_ROLE
| CURRENT_USER
| SESSION_USER
+
+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 da421ff24e..f9e42443b6 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/access/transam/xact.c b/src/backend/access/transam/xact.c
index ca6f6d57d3..97757bd9c7 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -35,6 +35,7 @@
#include "catalog/pg_enum.h"
#include "catalog/storage.h"
#include "commands/async.h"
+#include "commands/schema_variable.h"
#include "commands/tablecmds.h"
#include "commands/trigger.h"
#include "executor/spi.h"
@@ -2146,6 +2147,9 @@ CommitTransaction(void)
*/
smgrDoPendingSyncs(true, is_parallel_worker);
+ /* Let ON COMMIT DROP or ON TRANSACTION END */
+ AtPreEOXact_SchemaVariable_on_commit_actions(true);
+
/* close large objects before lower-level cleanup */
AtEOXact_LargeObject(true);
@@ -2285,6 +2289,7 @@ CommitTransaction(void)
AtEOXact_SPI(true);
AtEOXact_Enum();
AtEOXact_on_commit_actions(true);
+ AtEOXact_SchemaVariable_on_commit_actions(true);
AtEOXact_Namespace(true, is_parallel_worker);
AtEOXact_SMgr();
AtEOXact_Files(true);
@@ -2722,6 +2727,9 @@ AbortTransaction(void)
AtAbort_Portals();
smgrDoPendingSyncs(false, is_parallel_worker);
AtEOXact_LargeObject(false);
+
+ /* 'false' means it's abort */
+ AtPreEOXact_SchemaVariable_on_commit_actions(false);
AtAbort_Notify();
AtEOXact_RelationMap(false, is_parallel_worker);
AtAbort_Twophase();
@@ -2786,6 +2794,7 @@ AbortTransaction(void)
AtEOXact_SPI(false);
AtEOXact_Enum();
AtEOXact_on_commit_actions(false);
+ AtEOXact_SchemaVariable_on_commit_actions(false);
AtEOXact_Namespace(false, is_parallel_worker);
AtEOXact_SMgr();
AtEOXact_Files(false);
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4e6efda97f..5c402a5ed8 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
pg_shdepend.o \
pg_subscription.o \
pg_type.o \
+ pg_variable.o \
storage.o \
toasting.o
@@ -69,7 +70,8 @@ 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_namespace.h \
- pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
+ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h \
+ pg_variable.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index ce0a4ff14e..8d1c02b871 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -58,6 +58,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 "commands/defrem.h"
#include "commands/event_trigger.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);
@@ -259,6 +261,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 */
@@ -498,6 +503,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);
@@ -600,6 +609,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);
@@ -759,6 +771,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 = LookupVariable(varvar->schemaname, varvar->relname, false);
+ objects = lappend_oid(objects, relOid);
+ }
+ break;
default:
elog(ERROR, "unrecognized GrantStmt.objtype: %d",
(int) objtype);
@@ -848,6 +870,33 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
table_close(rel, AccessShareLock);
}
break;
+ case OBJECT_VARIABLE:
+ {
+ ScanKeyData key;
+ Relation rel;
+ TableScanDesc scan;
+ HeapTuple tuple;
+
+ ScanKeyInit(&key,
+ Anum_pg_variable_varnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(namespaceId));
+
+ rel = table_open(VariableRelationId, AccessShareLock);
+ scan = table_beginscan_catalog(rel, 1, &key);
+
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Oid oid = ((Form_pg_proc) GETSTRUCT(tuple))->oid;
+
+ objects = lappend_oid(objects, oid);
+ }
+
+ table_endscan(scan);
+ table_close(rel, AccessShareLock);
+ }
+ break;
+
default:
/* should not happen */
elog(ERROR, "unrecognized GrantStmt.objtype: %d",
@@ -1007,6 +1056,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);
@@ -1204,6 +1257,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);
@@ -1437,6 +1496,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",
@@ -1494,6 +1556,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
case ForeignDataWrapperRelationId:
istmt.objtype = OBJECT_FDW;
break;
+ case VariableRelationId:
+ istmt.objtype = OBJECT_VARIABLE;
+ break;
default:
elog(ERROR, "unexpected object class %u", classid);
break;
@@ -3225,6 +3290,129 @@ ExecGrant_Type(InternalGrant *istmt)
table_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 = table_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();
+ }
+
+ table_close(relation, RowExclusiveLock);
+}
+
static AclMode
string_to_privilege(const char *privname)
@@ -3257,6 +3445,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)));
@@ -3292,6 +3484,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);
}
@@ -3415,6 +3611,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;
@@ -3526,6 +3725,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;
@@ -3671,6 +3873,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);
@@ -4539,6 +4743,66 @@ 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
*
@@ -4813,6 +5077,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).
*/
@@ -5430,6 +5706,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)
*
@@ -5558,6 +5861,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 9f8eb1a37f..4ec76d4e03 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -63,12 +63,15 @@
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/publicationcmds.h"
+#include "commands/schemacmds.h"
+#include "commands/schema_variable.h"
#include "commands/seclabel.h"
#include "commands/sequence.h"
#include "commands/trigger.h"
@@ -183,7 +186,8 @@ static const Oid object_classes[] = {
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
- TransformRelationId /* OCLASS_TRANSFORM */
+ TransformRelationId, /* OCLASS_TRANSFORM */
+ VariableRelationId /* OCLASS_VARIABLE */
};
@@ -1492,6 +1496,10 @@ doDeletion(const ObjectAddress *object, int flags)
DropObjectById(object);
break;
+ case OCLASS_VARIABLE:
+ RemoveSchemaVariable(object->objectId);
+ break;
+
/*
* These global object types are not supported here.
*/
@@ -1870,6 +1878,11 @@ find_expr_references_walker(Node *node,
{
Param *param = (Param *) node;
+ if (param->paramkind == PARAM_VARIABLE)
+ /* A variable parameter depends on the schema variable */
+ add_object_address(OCLASS_VARIABLE, param->paramvarid, 0,
+ context->addrs);
+
/* A parameter must depend on the parameter's datatype */
add_object_address(OCLASS_TYPE, param->paramtype, 0,
context->addrs);
@@ -2870,6 +2883,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 4de8400fd0..02c5de7f73 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"
@@ -763,6 +764,70 @@ 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
@@ -2842,6 +2907,271 @@ TSConfigIsVisible(Oid cfgid)
return visible;
}
+/*
+ * When we know a variable name, then we can find variable simply
+ */
+Oid
+LookupVariable(const char *nspname, const char *varname, bool missing_ok)
+{
+ Oid namespaceId;
+ Oid varoid = InvalidOid;
+ ListCell *l;
+
+ if (nspname)
+ {
+ namespaceId = LookupExplicitNamespace(nspname, missing_ok);
+ if (!OidIsValid(namespaceId))
+ return InvalidOid;
+
+ varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+ PointerGetDatum(varname),
+ ObjectIdGetDatum(namespaceId));
+ }
+ else
+ {
+ /* search for it in search path */
+ recomputeNamespacePath();
+
+ foreach(l, activeSearchPath)
+ {
+ namespaceId = lfirst_oid(l);
+
+ varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+ PointerGetDatum(varname),
+ ObjectIdGetDatum(namespaceId));
+
+ if (OidIsValid(varoid))
+ break;
+ }
+ }
+
+ if (!OidIsValid(varoid) && !missing_ok)
+ {
+ if (nspname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("variable \"%s\".\"%s\" does not exist",
+ nspname, varname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("variable \"%s\" does not exist",
+ varname)));
+ }
+
+ return varoid;
+}
+
+/*
+ * The source list holds names with indirection expressions used
+ * as left part of LET statement. Following routine makes new
+ * list with only initial strings (names) - without indirection
+ * expressions.
+ */
+List *
+NamesFromList(List *names)
+{
+ ListCell *l;
+ List *result = NIL;
+
+ foreach(l, names)
+ {
+ Node *n = lfirst(l);
+
+ if (IsA(n, String))
+ {
+ result = lappend(result, n);
+ }
+ else
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * IdentifyVariable
+ *
+ * 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
+IdentifyVariable(List *names, char **attrname, bool *not_uniq)
+{
+ Node *field1 = NULL;
+ Node *field2 = NULL;
+ Node *field3 = NULL;
+ Node *field4 = NULL;
+ char *a = NULL;
+ char *b = NULL;
+ char *c = NULL;
+ char *d = NULL;
+ Oid varoid_without_attr = InvalidOid;
+ Oid varoid_with_attr = InvalidOid;
+
+ *not_uniq = false;
+
+ switch (list_length(names))
+ {
+ case 1:
+ field1 = linitial(names);
+
+ Assert(IsA(field1, String));
+
+ return LookupVariable(NULL, strVal(field1), true);
+
+ case 2:
+ field1 = linitial(names);
+ field2 = lsecond(names);
+
+ Assert(IsA(field1, String));
+ a = strVal(field1);
+
+ if (IsA(field2, String))
+ {
+ b = strVal(field2);
+
+ /*
+ * a.b can mean "schema"."variable" or "variable"."field", Check
+ * both variants, and returns InvalidOid with not_uniq flag, when
+ * both interpretations are possible. Second node can be star. In
+ * this case, the only allowed possibility is "variable"."*".
+ */
+ varoid_without_attr = LookupVariable(a, b, true);
+ varoid_with_attr = LookupVariable(NULL, a, true);
+ }
+ else
+ {
+ Assert(IsA(field2, A_Star));
+
+ /*
+ * Schema variables doesn't support unboxing by star syntax. But
+ * this syntax have to be calculated here, because can come from
+ * non schema variables related expressions.
+ */
+ return InvalidOid;
+ }
+
+ 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:
+ field1 = linitial(names);
+ field2 = lsecond(names);
+ field3 = lthird(names);
+
+ Assert(IsA(field1, String));
+ Assert(IsA(field2, String));
+
+ a = strVal(field1);
+ b = strVal(field2);
+
+ if (IsA(field3, String))
+ {
+ c = strVal(field3);
+
+ /*
+ * a.b.c can mean "catalog"."schema"."variable" or
+ * "schema"."variable"."field", Check both variants, and returns
+ * InvalidOid with not_uniq flag, when both interpretations are
+ * possible. When third node is star, then only possible
+ * interpretation is "schema"."variable"."*".
+ */
+ varoid_without_attr = LookupVariable(b, c, true);
+ varoid_with_attr = LookupVariable(a, b, true);
+ }
+ else
+ {
+ Assert(IsA(field3, A_Star));
+ return InvalidOid;
+ }
+
+ 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:
+ field1 = linitial(names);
+ field2 = lsecond(names);
+ field3 = lthird(names);
+ field4 = lfourth(names);
+
+ Assert(IsA(field1, String));
+ Assert(IsA(field2, String));
+ Assert(IsA(field3, String));
+
+ a = strVal(field1);
+ b = strVal(field2);
+ c = strVal(field3);
+
+ /*
+ * 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))));
+
+ if (IsA(field4, String))
+ {
+ d = strVal(field4);
+ }
+ else
+ {
+ Assert(IsA(field4, A_Star));
+ return InvalidOid;
+ }
+
+ *attrname = d;
+ return LookupVariable(b, c, true);
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(names))));
+ break;
+ }
+}
/*
* DeconstructQualifiedName
@@ -4657,3 +4987,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 2bae3fbb17..182ffa142d 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -62,6 +62,7 @@
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
@@ -617,6 +618,20 @@ static const ObjectPropertyType ObjectProperty[] =
OBJECT_USER_MAPPING,
false
},
+ {
+ "schema variable",
+ VariableRelationId,
+ VariableObjectIndexId,
+ VARIABLEOID,
+ VARIABLENAMENSP,
+ Anum_pg_variable_oid,
+ Anum_pg_variable_varname,
+ Anum_pg_variable_varnamespace,
+ Anum_pg_variable_varowner,
+ Anum_pg_variable_varacl,
+ OBJECT_VARIABLE,
+ true
+ }
};
/*
@@ -845,6 +860,10 @@ static const struct object_type_map
/* OCLASS_STATISTIC_EXT */
{
"statistics object", OBJECT_STATISTIC_EXT
+ },
+ /* OCLASS_VARIABLE */
+ {
+ "schema variable", OBJECT_VARIABLE
}
};
@@ -870,6 +889,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,
@@ -1139,6 +1159,9 @@ get_object_address(ObjectType objtype, Node *object,
missing_ok);
address.objectSubId = 0;
break;
+ case OBJECT_VARIABLE:
+ address = get_object_address_variable(castNode(List, object), missing_ok);
+ break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
/* placate compiler, in case it thinks elog might return */
@@ -2038,16 +2061,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)));
}
/*
@@ -2132,6 +2159,24 @@ textarray_to_strvaluelist(ArrayType *arr)
return list;
}
+/*
+ * Find the ObjectAddress for a schema variable
+ */
+static ObjectAddress
+get_object_address_variable(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *nspname = NULL;
+ char *varname = NULL;
+
+ ObjectAddressSet(address, VariableRelationId, InvalidOid);
+
+ DeconstructQualifiedName(object, &nspname, &varname);
+ address.objectId = LookupVariable(nspname, varname, missing_ok);
+
+ return address;
+}
+
/*
* SQL-callable version of get_object_address
*/
@@ -2322,6 +2367,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:
@@ -2630,6 +2676,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, \
ObjectAddress address, aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
NameListToString(castNode(List, object)));
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);
@@ -3558,6 +3609,32 @@ getObjectDescription(const ObjectAddress *object, bool \
missing_ok) 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;
@@ -3868,6 +3945,16 @@ getObjectDescription(const ObjectAddress *object, bool \
missing_ok) _("default privileges on new schemas belonging to role %s"),
rolename);
break;
+ case DEFACLOBJ_VARIABLE:
+ if (nspname)
+ appendStringInfo(&buffer,
+ _("default privileges on new 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)
@@ -4610,6 +4697,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool \
missing_ok) 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.
@@ -5703,6 +5794,10 @@ getObjectIdentityParts(const ObjectAddress *object,
appendStringInfoString(&buffer,
" on schemas");
break;
+ case DEFACLOBJ_VARIABLE:
+ appendStringInfoString(&buffer,
+ " on schema variables");
+ break;
}
if (objname)
@@ -5918,6 +6013,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_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 8453d8fefd..bbf7e9413a 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -46,6 +46,7 @@
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
#include "commands/alter.h"
#include "commands/collationcmds.h"
#include "commands/conversioncmds.h"
@@ -1572,6 +1573,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
case DatabaseRelationId:
case TSConfigRelationId:
case TSDictionaryRelationId:
+ case VariableRelationId:
{
Oid classId = sdepForm->classid;
Relation catalog;
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
new file mode 100644
index 0000000000..451b57a0b7
--- /dev/null
+++ b/src/backend/catalog/pg_variable.c
@@ -0,0 +1,388 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.c
+ * schema variables
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/pg_variable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+
+#include "catalog/catalog.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 "commands/schema_variable.h"
+
+#include "storage/lmgr.h"
+
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+static VariableEOXActionCodes
+to_eoxaction_code(VariableEOXAction action)
+{
+ switch (action)
+ {
+ case VARIABLE_EOX_NOOP:
+ return VARIABLE_EOX_CODE_NOOP;
+
+ case VARIABLE_EOX_DROP:
+ return VARIABLE_EOX_CODE_DROP;
+
+ case VARIABLE_EOX_RESET:
+ return VARIABLE_EOX_CODE_RESET;
+
+ default:
+ elog(ERROR, "unexpected action");
+ }
+
+}
+
+static VariableEOXAction
+to_eoxaction(VariableEOXActionCodes code)
+{
+ switch (code)
+ {
+ case VARIABLE_EOX_CODE_NOOP:
+ return VARIABLE_EOX_NOOP;
+
+ case VARIABLE_EOX_CODE_DROP:
+ return VARIABLE_EOX_DROP;
+
+ case VARIABLE_EOX_CODE_RESET:
+ return VARIABLE_EOX_RESET;
+
+ default:
+ elog(ERROR, "unexpected code");
+ }
+}
+
+/*
+ * 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.
+ */
+void
+initVariable(Variable *var, Oid varid, bool missing_ok, bool fast_only)
+{
+ HeapTuple tup;
+ Form_pg_variable varform;
+ Datum defexpr_datum;
+ bool defexpr_isnull;
+
+ tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+ if (!HeapTupleIsValid(tup))
+ {
+ if (missing_ok)
+ {
+ var->oid = InvalidOid;
+ return;
+ }
+
+ elog(ERROR, "cache lookup failed for variable %u", varid);
+ }
+
+ varform = (Form_pg_variable) GETSTRUCT(tup);
+
+ 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;
+ var->eoxaction = to_eoxaction(varform->vareoxaction);
+ var->is_not_null = varform->varisnotnull;
+ var->is_immutable = varform->varisimmutable;
+
+ /* Get defexpr */
+ defexpr_datum = SysCacheGetAttr(VARIABLEOID,
+ tup,
+ Anum_pg_variable_vardefexpr,
+ &defexpr_isnull);
+
+ var->has_defexpr = !defexpr_isnull;
+
+ /*
+ * Sometimes we don't need deserialized defexpr, but we need info about
+ * existence of defexpr.
+ */
+
+ if (!fast_only)
+ {
+ Datum aclDatum;
+ bool isnull;
+
+ /* name */
+ var->name = pstrdup(NameStr(varform->varname));
+
+ if (!defexpr_isnull)
+ var->defexpr = stringToNode(TextDatumGetCString(defexpr_datum));
+ 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;
+ }
+ else
+ {
+ var->name = NULL;
+ var->defexpr = NULL;
+ var->acl = NULL;
+ }
+
+ ReleaseSysCache(tup);
+
+ return;
+}
+
+/*
+ * Create entry in pg_variable table
+ */
+ObjectAddress
+VariableCreate(const char *varName,
+ Oid varNamespace,
+ Oid varType,
+ int32 varTypmod,
+ Oid varOwner,
+ Oid varCollation,
+ Node *varDefexpr,
+ VariableEOXAction eoxaction,
+ bool is_not_null,
+ bool if_not_exists,
+ bool is_immutable)
+{
+ 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 varid;
+ int i;
+
+ for (i = 0; i < Natts_pg_variable; i++)
+ {
+ nulls[i] = false;
+ values[i] = (Datum) 0;
+ }
+
+ namestrcpy(&varname, varName);
+
+ rel = table_open(VariableRelationId, RowExclusiveLock);
+
+ varid = GetNewOidWithIndex(rel, VariableObjectIndexId, Anum_pg_variable_oid);
+ values[Anum_pg_variable_oid - 1] = ObjectIdGetDatum(varid);
+ values[Anum_pg_variable_varname - 1] = NameGetDatum(&varname);
+ values[Anum_pg_variable_varnamespace - 1] = ObjectIdGetDatum(varNamespace);
+ values[Anum_pg_variable_vartype - 1] = ObjectIdGetDatum(varType);
+ values[Anum_pg_variable_vartypmod - 1] = Int32GetDatum(varTypmod);
+ values[Anum_pg_variable_varowner - 1] = ObjectIdGetDatum(varOwner);
+ values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation);
+ values[Anum_pg_variable_vareoxaction - 1] = CharGetDatum((char) \
to_eoxaction_code(eoxaction)); + values[Anum_pg_variable_varisnotnull - 1] = \
BoolGetDatum(is_not_null); + values[Anum_pg_variable_varisimmutable - 1] = \
BoolGetDatum(is_immutable); + /* proacl will be determined later */
+
+ if (varDefexpr)
+ values[Anum_pg_variable_vardefexpr - 1] = \
CStringGetTextDatum(nodeToString(varDefexpr)); + else
+ nulls[Anum_pg_variable_vardefexpr - 1] = true;
+
+ 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)));
+
+ ReleaseSysCache(oldtup);
+ table_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);
+
+ myself.classId = VariableRelationId;
+ myself.objectId = varid;
+ 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 default expr */
+ if (varDefexpr)
+ recordDependencyOnExpr(&myself, (Node *) varDefexpr,
+ NIL, DEPENDENCY_NORMAL);
+
+ /* dependency on any roles mentioned in ACL */
+ if (varacl != NULL)
+ {
+ int nnewmembers;
+ Oid *newmembers;
+
+ nnewmembers = aclmembers(varacl, &newmembers);
+ updateAclDependencies(VariableRelationId, varid, 0,
+ varOwner,
+ 0, NULL,
+ nnewmembers, newmembers);
+ }
+
+ /* dependency on extension */
+ recordDependencyOnCurrentExtension(&myself, false);
+
+ /*
+ * register on commit action if it is necessary. This moment, the variable
+ * has not any value, so we don't need to solve content transactionality.
+ */
+ register_variable_on_commit_action(myself.objectId, eoxaction);
+
+ heap_freetuple(tup);
+
+ /* Post creation hook for new function */
+ InvokeObjectPostCreateHook(VariableRelationId, varid, 0);
+
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index e8504f0ae4..0b785be2ce 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -48,6 +48,7 @@ OBJS = \
proclang.o \
publicationcmds.o \
schemacmds.o \
+ schemavariable.o \
seclabel.o \
sequence.o \
statscmds.o \
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 40044070cf..c559449f1a 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -40,6 +40,7 @@
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_ts_parser.h"
#include "catalog/pg_ts_template.h"
+#include "catalog/pg_variable.h"
#include "commands/alter.h"
#include "commands/collationcmds.h"
#include "commands/conversioncmds.h"
@@ -141,6 +142,10 @@ report_namespace_conflict(Oid classId, const char *name, Oid \
nspOid) Assert(OidIsValid(nspOid));
msgfmt = gettext_noop("text search configuration \"%s\" already exists in schema \
\"%s\""); break;
+ case VariableRelationId:
+ Assert(OidIsValid(nspOid));
+ msgfmt = gettext_noop("schema variable \"%s\" already exists in schema \"%s\"");
+ break;
default:
elog(ERROR, "unsupported object class %u", classId);
break;
@@ -393,6 +398,7 @@ ExecRenameStmt(RenameStmt *stmt)
case OBJECT_TSTEMPLATE:
case OBJECT_PUBLICATION:
case OBJECT_SUBSCRIPTION:
+ case OBJECT_VARIABLE:
{
ObjectAddress address;
Relation catalog;
@@ -536,6 +542,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
case OBJECT_TSDICTIONARY:
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
+ case OBJECT_VARIABLE:
{
Relation catalog;
Relation relation;
@@ -626,6 +633,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_TSDICT:
case OCLASS_TSTEMPLATE:
case OCLASS_TSCONFIG:
+ case OCLASS_VARIABLE:
{
Relation catalog;
@@ -884,6 +892,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 57d3d7dd9b..a712bf44b6 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/schema_variable.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/dropcmds.c b/src/backend/commands/dropcmds.c
index 4e545adf95..1d14951e8c 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -481,6 +481,10 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
msg = gettext_noop("publication \"%s\" does not exist, skipping");
name = strVal(object);
break;
+ case OBJECT_VARIABLE:
+ msg = gettext_noop("schema variable \"%s\" does not exist, skipping");
+ name = NameListToString(castNode(List, object));
+ break;
default:
elog(ERROR, "unrecognized object type: %d", (int) objtype);
break;
diff --git a/src/backend/commands/event_trigger.c \
b/src/backend/commands/event_trigger.c index df264329d8..98d0e2ec25 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -991,6 +991,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_TSTEMPLATE:
case OBJECT_TYPE:
case OBJECT_USER_MAPPING:
+ case OBJECT_VARIABLE:
case OBJECT_VIEW:
return true;
@@ -1055,6 +1056,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
+ case OCLASS_VARIABLE:
return true;
/*
@@ -2106,6 +2108,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:
@@ -2189,6 +2193,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/explain.c b/src/backend/commands/explain.c
index 10644dfac4..77d18cb9ac 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -492,6 +492,22 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, \
ExplainState *es, else
ExplainDummyGroup("Notify", NULL, es);
}
+ else if (IsA(utilityStmt, LetStmt))
+ {
+ LetStmt *letstmt = (LetStmt *) utilityStmt;
+ List *rewritten;
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ appendStringInfoString(es->str, "SET SCHEMA VARIABLE\n");
+ else
+ ExplainDummyGroup("Set Schema Variable", NULL, es);
+
+ rewritten = QueryRewrite(castNode(Query, copyObject(letstmt->query)));
+ Assert(list_length(rewritten) == 1);
+ ExplainOneQuery(linitial_node(Query, rewritten),
+ CURSOR_OPT_PARALLEL_OK, NULL, es,
+ queryString, params, queryEnv);
+ }
else
{
if (es->format == EXPLAIN_FORMAT_TEXT)
diff --git a/src/backend/commands/schemavariable.c \
b/src/backend/commands/schemavariable.c new file mode 100644
index 0000000000..15e0567199
--- /dev/null
+++ b/src/backend/commands/schemavariable.c
@@ -0,0 +1,881 @@
+/*-------------------------------------------------------------------------
+ *
+ * schemavariable.c
+ * schema variable creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/commands/schemavariable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "miscadmin.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/pg_variable.h"
+#include "commands/schema_variable.h"
+#include "executor/executor.h"
+#include "executor/svariableReceiver.h"
+#include "optimizer/optimizer.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+/*
+ * ON COMMIT action list
+ */
+typedef struct OnCommitItem
+{
+ Oid varid; /* relid of relation */
+ VariableEOXAction eoxaction; /* what to do at end of xact */
+
+ /*
+ * If this entry was created during the current transaction,
+ * creating_subid is the ID of the creating subxact; if created in a prior
+ * transaction, creating_subid is zero. If deleted during the current
+ * transaction, deleting_subid is the ID of the deleting subxact; if no
+ * deletion request is pending, deleting_subid is zero.
+ */
+ SubTransactionId creating_subid;
+ SubTransactionId deleting_subid;
+} OnCommitItem;
+
+static List *on_commits = NIL;
+
+/*
+ * 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 */
+ 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 */
+ bool reset_auto; /* next read fix invalidate value by self */
+
+ bool is_not_null; /* don't allow null values */
+ bool has_defexpr; /* true when there are default value */
+ bool is_immutable; /* true when variable is immutable */
+
+ SubTransactionId creating_subid;
+} SchemaVariableData;
+
+typedef SchemaVariableData * SchemaVariable;
+
+static HTAB *schemavarhashtab = NULL; /* hash table for session variables */
+static MemoryContext SchemaVariableMemoryContext = NULL;
+
+static bool first_time = true;
+static bool clean_cache_req = false;
+
+static void create_schema_variable_hashtable(void);
+static void clean_cache_callback(XactEvent event, void *arg);
+static void free_schema_variable(SchemaVariable svar, bool force);
+static void set_schema_variable(SchemaVariable svar, Datum value, bool isnull, Oid \
typid); +static void init_schema_variable(SchemaVariable svar, Variable *var);
+static SchemaVariable prepare_variable_for_reading(Oid varid, bool reset);
+static void remove_variable_on_commit_actions(Oid varid, VariableEOXAction \
eoxaction); +static void clean_cache_variable(Oid varid);
+
+/*
+ * Save info about necessity to clean hash table, because some
+ * schema variable was dropped. Don't do here more, recheck
+ * needs to be in transaction state.
+ */
+static void
+InvalidateSchemaVariableCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+ if (cacheid != VARIABLEOID)
+ return;
+
+ clean_cache_req = true;
+}
+
+/*
+ * Recheck values of all schema variables stored in local memory.
+ * Remove the value from local memory, when related schema variable
+ * was dropped (and removed from system catalogue).
+ */
+static void
+clean_cache_callback(XactEvent event, void *arg)
+{
+ /*
+ * should continue only in transaction time, when syscache is available.
+ */
+ if (clean_cache_req && IsTransactionState())
+ {
+ HASH_SEQ_STATUS status;
+ SchemaVariable svar;
+
+ if (!schemavarhashtab)
+ return;
+
+ hash_seq_init(&status, schemavarhashtab);
+
+ while ((svar = (SchemaVariable) hash_seq_search(&status)) != NULL)
+ {
+ HeapTuple tp;
+
+ tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+ if (!HeapTupleIsValid(tp))
+ {
+ elog(DEBUG1, "variable %d is removed from cache", svar->varid);
+
+ free_schema_variable(svar, true);
+
+ remove_variable_on_commit_actions(svar->varid, VARIABLE_EOX_DROP);
+
+ (void) hash_search(schemavarhashtab, (void *) &svar->varid,
+ HASH_REMOVE, NULL);
+ }
+ else
+ ReleaseSysCache(tp);
+ }
+
+ clean_cache_req = false;
+ }
+}
+
+/*
+ * Create the hash table for storing schema variables
+ */
+static void
+create_schema_variable_hashtable(void)
+{
+ HASHCTL ctl;
+
+ /* set callbacks */
+ if (first_time)
+ {
+ CacheRegisterSyscacheCallback(VARIABLEOID,
+ InvalidateSchemaVariableCacheCallback,
+ (Datum) 0);
+
+ RegisterXactCallback(clean_cache_callback, 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)
+{
+ /* Remove temporal schema variables */
+ AtPreEOXact_SchemaVariable_on_commit_actions(true);
+
+ /* Destroy hash table and reset related memory context */
+ if (schemavarhashtab)
+ {
+ hash_destroy(schemavarhashtab);
+ schemavarhashtab = NULL;
+ }
+
+ /* Release memory allocated by schema variables */
+ if (SchemaVariableMemoryContext != NULL)
+ MemoryContextReset(SchemaVariableMemoryContext);
+}
+
+/*
+ * Release data stored inside svar. When a variable is transactional,
+ * and was not modified already in this transaction, then it archives
+ * current value for possible future usage on rollback.
+ *
+ * When force is true, then release current and possibly archived value.
+ */
+static void
+free_schema_variable(SchemaVariable svar, bool force)
+{
+ if (svar->freeval)
+ pfree(DatumGetPointer(svar->value));
+
+ /* Clean current value, and mark it as invalid */
+ svar->value = (Datum) 0;
+ svar->isnull = true;
+ svar->freeval = false;
+
+ svar->is_valid = false;
+}
+
+/*
+ * Assign some content to the schema variable. It does copy to
+ * SchemaVariableMemoryContext if it is necessary.
+ */
+static void
+set_schema_variable(SchemaVariable svar, Datum value, bool isnull, Oid typid)
+{
+ MemoryContext oldcxt;
+ Datum newval = value;
+
+ /* Don't allow assign null to NOT NULL variable */
+ if (isnull && svar->is_not_null)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value is not allowed for NOT NULL schema variable \"%s\"",
+ schema_variable_get_name(svar->varid))));
+
+ if (!isnull && svar->typid != typid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("type \"%s\" of assigned value is different than type \"%s\" of schema \
variable \"%s\"", + format_type_be(typid),
+ format_type_be(svar->typid),
+ schema_variable_get_name(svar->varid))));
+
+ /* copy value to session persistent context */
+ oldcxt = MemoryContextSwitchTo(SchemaVariableMemoryContext);
+ if (!isnull)
+ newval = datumCopy(value, svar->typbyval, svar->typlen);
+ MemoryContextSwitchTo(oldcxt);
+
+ free_schema_variable(svar, false);
+
+ svar->value = newval;
+ svar->isnull = isnull;
+ svar->freeval = newval != value;
+ svar->is_valid = true;
+ svar->reset_auto = false;
+}
+
+/*
+ * Initialize svar from var
+ * svar - SchemaVariable - holds data
+ * var - Variable - holds metadata
+ */
+static void
+init_schema_variable(SchemaVariable svar, Variable *var)
+{
+ Assert(OidIsValid(var->oid));
+
+ svar->varid = var->oid;
+ svar->typid = var->typid;
+
+ get_typlenbyval(var->typid,
+ &svar->typlen,
+ &svar->typbyval);
+
+ svar->isnull = true;
+ svar->freeval = false;
+ svar->value = (Datum) 0;
+
+ svar->is_rowtype = type_is_rowtype(var->typid);
+ svar->is_not_null = var->is_not_null;
+ svar->is_immutable = var->is_immutable;
+ svar->has_defexpr = var->has_defexpr;
+
+ /* the variable initialization was not complete here */
+ svar->is_valid = false;
+ svar->reset_auto = false;
+
+ svar->creating_subid = InvalidSubTransactionId;
+}
+
+/*
+ * Try to search value in hash table. If doesn't
+ * exists insert it (and calculate defexpr if exists.
+ * When reset is true, we would to enforce calculate
+ * defexpr. When some is wrong, then this function try
+ * don't break previous value.
+ */
+static SchemaVariable
+prepare_variable_for_reading(Oid varid, bool reset)
+{
+ SchemaVariable svar;
+ Variable var;
+ bool found;
+
+ var.oid = InvalidOid;
+
+ if (schemavarhashtab == NULL)
+ create_schema_variable_hashtable();
+
+ svar = (SchemaVariable) hash_search(schemavarhashtab, &varid,
+ HASH_ENTER, &found);
+
+ /* Return content if it is available, and reset is not required */
+ if (found && !reset && !svar->reset_auto)
+ {
+ /* raise exception when content is not valid */
+ if (!svar->is_valid)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("variable \"%s\" has not valid content",
+ schema_variable_get_name(varid)),
+ errhint("Overwrite the content of variable or assign DEFAULT again.")));
+
+ return svar;
+ }
+
+ /* We need to load defexpr. */
+ initVariable(&var, varid, false, false);
+
+ if (!found)
+ init_schema_variable(svar, &var);
+
+ /* Raise a error when we cannot to initialize variable correctly */
+ if (var.is_not_null && !var.defexpr)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value is not allowed for NOT NULL schema variable \"%s\"",
+ schema_variable_get_name(varid)),
+ errdetail("The schema variable was not initialized yet.")));
+
+ if (!found)
+ register_variable_on_commit_action(varid, var.eoxaction);
+
+ if (svar->has_defexpr)
+ {
+ Datum value = (Datum) 0;
+ bool isnull;
+ EState *estate = NULL;
+ Expr *defexpr;
+ ExprState *defexprs;
+ MemoryContext oldcxt;
+
+ /* Prepare default expr */
+ estate = CreateExecutorState();
+
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ defexpr = expression_planner((Expr *) var.defexpr);
+ defexprs = ExecInitExpr(defexpr, NULL);
+ value = ExecEvalExprSwitchContext(defexprs,
+ GetPerTupleExprContext(estate),
+ &isnull);
+
+
+ /* Store result before releasing Executor memory */
+ set_schema_variable(svar, value, isnull, svar->typid);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ FreeExecutorState(estate);
+ }
+ else
+ set_schema_variable(svar, (Datum) 0, true, svar->typid);
+
+ return svar;
+}
+
+/*
+ * Write value to variable. We expect secured access in this moment.
+ * We try to don't break previous value, if some is wrong.
+ */
+void
+SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid)
+{
+ SchemaVariable svar;
+ bool found;
+
+ if (schemavarhashtab == NULL)
+ create_schema_variable_hashtable();
+
+ svar = (SchemaVariable) hash_search(schemavarhashtab, &varid,
+ HASH_ENTER, &found);
+
+ /* Initialize svar when was not initialized or when stored value is null */
+ if (!found)
+ {
+ Variable var;
+
+ /* don't need defexpr and acl here */
+ initVariable(&var, varid, false, true);
+ init_schema_variable(svar, &var);
+ register_variable_on_commit_action(varid, var.eoxaction);
+ }
+
+ /* don't allow a update on immutable variable */
+ if (svar->is_immutable)
+ ereport(ERROR,
+ (errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
+ errmsg("schema variable \"%s\" is declared IMMUTABLE",
+ schema_variable_get_name(svar->varid))));
+
+ set_schema_variable(svar, value, isNull, typid);
+}
+
+
+/*
+ * Write value to variable with security check.
+ * We try to don't break previous value, if some is wrong.
+ */
+void
+SetSchemaVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typid)
+{
+ AclResult aclresult;
+
+ /*
+ * Is possible to write to schema variable?
+ */
+ aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, schema_variable_get_name(varid));
+
+ SetSchemaVariable(varid, value, isNull, typid);
+}
+
+/*
+ * Returns copy of value of schema variable spcified by varid
+ */
+Datum
+CopySchemaVariable(Oid varid, bool *isNull, Oid *typid)
+{
+ SchemaVariable svar;
+
+ svar = prepare_variable_for_reading(varid, false);
+ Assert(svar != NULL && svar->is_valid);
+
+ *isNull = svar->isnull;
+ *typid = svar->typid;
+
+ if (!svar->isnull)
+ return datumCopy(svar->value, svar->typbyval, svar->typlen);
+
+ return (Datum) 0;
+}
+
+/*
+ * Returns value of schema variable specified by varid. Check correct
+ * result type. Optionaly the result can be copy.
+ */
+Datum
+GetSchemaVariable(Oid varid, bool *isNull, Oid expected_typid, bool copy)
+{
+ SchemaVariable svar;
+ Datum value;
+ bool isnull;
+
+ svar = prepare_variable_for_reading(varid, false);
+ 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;
+}
+
+/*
+ * Clean variable defined by varid
+ */
+static void
+clean_cache_variable(Oid varid)
+{
+ SchemaVariable svar;
+ bool found;
+
+ if (schemavarhashtab == NULL)
+ return;
+
+ svar = (SchemaVariable) hash_search(schemavarhashtab, &varid,
+ HASH_FIND, &found);
+ if (found)
+ {
+ /* clean content, if it is necessary */
+ free_schema_variable(svar, true);
+
+ if (hash_search(schemavarhashtab,
+ (void *) &svar->varid,
+ HASH_REMOVE,
+ NULL) == NULL)
+ elog(DEBUG1, "hash table corrupted");
+ }
+}
+
+/*
+ * Drop variable by OID
+ */
+void
+RemoveSchemaVariable(Oid varid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(VariableRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for variable %u", varid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+
+ clean_cache_variable(varid);
+
+ /* remove variable from on_commits list */
+ remove_variable_on_commit_actions(varid, VARIABLE_EOX_DROP);
+}
+
+/*
+ * Assign result of evaluated expression to schema variable
+ */
+void
+ExecuteLetStmt(ParseState *pstate,
+ LetStmt *stmt,
+ ParamListInfo params,
+ QueryEnvironment *queryEnv,
+ QueryCompletion *qc)
+{
+ Query *query = castNode(Query, stmt->query);
+ List *rewritten;
+ DestReceiver *dest;
+ AclResult aclresult;
+ PlannedStmt *plan;
+ QueryDesc *queryDesc;
+ Oid varid = query->resultVariable;
+
+ Assert(OidIsValid(varid));
+
+ /*
+ * Is possible to write to schema variable?
+ */
+ aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, schema_variable_get_name(varid));
+
+ /* Create dest receiver for LET */
+ dest = CreateDestReceiver(DestVariable);
+ SetVariableDestReceiverParams(dest, varid);
+
+ /* run rewriter - there can be used for replacement of DEFAULT node */
+ query = copyObject(query);
+
+ rewritten = QueryRewrite(query);
+
+ Assert(list_length(rewritten) == 1);
+
+ query = linitial_node(Query, rewritten);
+ Assert(query->commandType == CMD_SELECT);
+
+ /* plan the query */
+ plan = pg_plan_query(query, pstate->p_sourcetext,
+ CURSOR_OPT_PARALLEL_OK, params);
+
+ /*
+ * Use a snapshot with an updated command ID to ensure this query sees
+ * results of any previously executed queries. (This could only
+ * matter if the planner executed an allegedly-stable function that
+ * changed the database contents, but let's do it anyway to be
+ * parallel to the EXPLAIN code path.)
+ */
+ PushCopiedSnapshot(GetActiveSnapshot());
+ UpdateActiveSnapshotCommandId();
+
+ /* Create a QueryDesc, redirecting output to our tuple receiver */
+ queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
+ GetActiveSnapshot(), InvalidSnapshot,
+ dest, params, queryEnv, 0);
+
+ /* call ExecutorStart to prepare the plan for execution */
+ ExecutorStart(queryDesc, 0);
+
+ /* run the plan to completion */
+ ExecutorRun(queryDesc, ForwardScanDirection, 2L, true);
+
+ /* save the rowcount if we're given a qc to fill */
+ if (qc)
+ SetQueryCompletion(qc, CMDTAG_LET, queryDesc->estate->es_processed);
+
+ /* and clean up */
+ ExecutorFinish(queryDesc);
+ ExecutorEnd(queryDesc);
+
+ FreeQueryDesc(queryDesc);
+
+ PopActiveSnapshot();
+}
+
+/*
+ * Creates new variable - entry in pg_catalog.pg_variable table
+ *
+ * Used by CREATE VARIABLE command
+ */
+ObjectAddress
+DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt *stmt)
+{
+ Oid namespaceid;
+ AclResult aclresult;
+ Oid typid;
+ int32 typmod;
+ Oid varowner = GetUserId();
+ Oid collation;
+ Oid typcollation;
+ ObjectAddress variable;
+
+ Node *cooked_default = NULL;
+
+ /*
+ * Check consistency of arguments
+ */
+ if (stmt->eoxaction == VARIABLE_EOX_DROP
+ && stmt->variable->relpersistence != RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("ON COMMIT DROP can only be used on temporary variables")));
+
+ if (stmt->is_not_null && stmt->is_immutable && !stmt->defexpr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("IMMUTABLE NOT NULL variable requires default expression")));
+
+ 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);
+ }
+
+ variable = VariableCreate(stmt->variable->relname,
+ namespaceid,
+ typid,
+ typmod,
+ varowner,
+ collation,
+ cooked_default,
+ stmt->eoxaction,
+ stmt->is_not_null,
+ stmt->if_not_exists,
+ stmt->is_immutable);
+
+ /*
+ * We must bump the command counter to make the newly-created variable
+ * tuple visible for any other operations.
+ */
+ CommandCounterIncrement();
+
+ return variable;
+}
+
+/*
+ * Register a newly-created variable ON COMMIT action.
+ */
+void
+register_variable_on_commit_action(Oid varid,
+ VariableEOXAction action)
+{
+ OnCommitItem *oc;
+ MemoryContext oldcxt;
+
+ /*
+ * We needn't bother registering the relation unless there is an ON COMMIT
+ * action we need to take.
+ */
+ if (action == VARIABLE_EOX_NOOP)
+ return;
+
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+ oc = (OnCommitItem *) palloc(sizeof(OnCommitItem));
+ oc->varid = varid;
+ oc->eoxaction = action;
+
+ oc->creating_subid = GetCurrentSubTransactionId();
+ oc->deleting_subid = InvalidSubTransactionId;
+
+ on_commits = lcons(oc, on_commits);
+
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Remove variable from on_commits action
+ */
+static void
+remove_variable_on_commit_actions(Oid varid,
+ VariableEOXAction eoxaction)
+{
+ ListCell *l;
+
+ foreach(l, on_commits)
+ {
+ OnCommitItem *oc = (OnCommitItem *) lfirst(l);
+
+ if (oc->varid == varid)
+ {
+ if (eoxaction == VARIABLE_EOX_DROP ||
+ (oc->eoxaction == VARIABLE_EOX_RESET &&
+ eoxaction == VARIABLE_EOX_RESET))
+ oc->deleting_subid = GetCurrentSubTransactionId();
+ }
+ }
+}
+
+/*
+ * Perform ON TRANSACTION END RESET, ON COMMIT DROP
+ * and COMMIT/ROLLBACK of transaction schema variables.
+ */
+void
+AtPreEOXact_SchemaVariable_on_commit_actions(bool isCommit)
+{
+ ListCell *l;
+
+ foreach(l, on_commits)
+ {
+ OnCommitItem *oc = (OnCommitItem *) lfirst(l);
+
+ /* Ignore entry if already dropped in this xact */
+ if (oc->deleting_subid != InvalidSubTransactionId)
+ continue;
+
+ switch (oc->eoxaction)
+ {
+ case VARIABLE_EOX_NOOP:
+ /* Do nothing */
+ break;
+ case VARIABLE_EOX_RESET:
+ clean_cache_variable(oc->varid);
+ remove_variable_on_commit_actions(oc->varid,
+ VARIABLE_EOX_RESET);
+ break;
+ case VARIABLE_EOX_DROP:
+ {
+ /*
+ * ON COMMIT DROP is allowed only for temp schema
+ * variables. So we should explicit delete only when
+ * current transaction was committed. When is rollback,
+ * then schema variable is removed automatically.
+ */
+ if (isCommit)
+ {
+ ObjectAddress object;
+
+ object.classId = VariableRelationId;
+ object.objectId = oc->varid;
+ object.objectSubId = 0;
+
+ /*
+ * Since this is an automatic drop, rather than one
+ * directly initiated by the user, we pass the
+ * PERFORM_DELETION_INTERNAL flag.
+ */
+ performDeletion(&object,
+ DROP_CASCADE, PERFORM_DELETION_INTERNAL);
+ }
+ }
+ break;
+ }
+ }
+}
+
+/*
+ * Post-commit or post-abort cleanup for ON COMMIT management.
+ *
+ * All we do here is remove no-longer-needed OnCommitItem entries.
+ *
+ */
+void
+AtEOXact_SchemaVariable_on_commit_actions(bool isCommit)
+{
+ ListCell *cur_item;
+
+ foreach(cur_item, on_commits)
+ {
+ OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
+
+ /*
+ * During commit, remove entries that were deleted during this
+ * transaction; during abort, remove those created during this
+ * transaction.
+ *
+ * The transact variable event is removed every time.
+ */
+
+ if ((isCommit ? oc->deleting_subid != InvalidSubTransactionId :
+ oc->creating_subid != InvalidSubTransactionId))
+ {
+ on_commits = foreach_delete_current(on_commits, cur_item);
+ pfree(oc);
+ }
+ else
+ {
+ oc->creating_subid = InvalidSubTransactionId;
+ oc->deleting_subid = InvalidSubTransactionId;
+ }
+ }
+}
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 53c18628a7..c094a3871c 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -91,6 +91,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
case OBJECT_USER_MAPPING:
+ case OBJECT_VARIABLE:
return false;
/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 857cc5ce6e..011534270f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12290,6 +12290,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 11118d0ce0..71248a34f2 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -76,6 +76,7 @@ OBJS = \
nodeWindowAgg.o \
nodeWorktablescan.o \
spi.o \
+ svariableReceiver.o \
tqueue.o \
tstoreReceiver.o
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 33ef39e2d4..3e8c1ee736 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/schema_variable.h"
#include "executor/execExpr.h"
#include "executor/nodeSubplan.h"
#include "funcapi.h"
@@ -994,6 +995,60 @@ ExecInitExprRec(Expr *node, ExprState *state,
scratch.d.param.paramtype = param->paramtype;
ExprEvalPushStep(state, &scratch);
break;
+
+ case PARAM_VARIABLE:
+ {
+ int es_num_schema_variables = 0;
+ SchemaVariableValue *es_schema_variables = NULL;
+
+ if (state->parent && state->parent->state)
+ {
+ es_schema_variables = state->parent->state->es_schema_variables;
+ es_num_schema_variables = state->parent->state->es_num_schema_variables;
+ }
+
+ /*
+ * We should use schema variable buffer, when
+ * it is available.
+ */
+ if (es_schema_variables)
+ {
+ SchemaVariableValue *var;
+
+ /* check params, unexpected */
+ if (param->paramid >= es_num_schema_variables)
+ elog(ERROR, "paramid of PARAM_VARIABLE param is out of range");
+
+ var = &es_schema_variables[param->paramid];
+
+ /* unexpected */
+ if (var->typid != param->paramtype)
+ elog(ERROR, "type of buffered value is different than PARAM_VARIABLE \
type"); +
+ /*
+ * In this case, the parameter is like a
+ * constant
+ */
+ scratch.opcode = EEOP_CONST;
+ scratch.d.constval.value = var->value;
+ scratch.d.constval.isnull = var->isnull;
+ ExprEvalPushStep(state, &scratch);
+ }
+ else
+ {
+ /*
+ * When we have not a full PlanState (plpgsql
+ * simple expr evaluation, then we should to
+ * use direct access.
+ */
+ scratch.opcode = EEOP_PARAM_VARIABLE;
+ scratch.d.vparam.varid = param->paramvarid;
+ scratch.d.vparam.vartype = param->paramtype;
+ ExprEvalPushStep(state, &scratch);
+ }
+ }
+ break;
+
case PARAM_EXTERN:
/*
diff --git a/src/backend/executor/execExprInterp.c \
b/src/backend/executor/execExprInterp.c index eb49817cee..ce34f7ff0c 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -59,6 +59,7 @@
#include "access/heaptoast.h"
#include "catalog/pg_type.h"
#include "commands/sequence.h"
+#include "commands/schema_variable.h"
#include "executor/execExpr.h"
#include "executor/nodeSubplan.h"
#include "funcapi.h"
@@ -444,6 +445,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,
@@ -1078,6 +1080,16 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool \
*isnull) EEO_NEXT();
}
+ EEO_CASE(EEOP_PARAM_VARIABLE)
+ {
+ /* direct access to schema variable (without buffering) */
+ *op->resvalue = GetSchemaVariable(op->d.vparam.varid,
+ op->resnull,
+ op->d.vparam.vartype,
+ true);
+ EEO_NEXT();
+ }
+
EEO_CASE(EEOP_CASE_TESTVAL)
{
/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..95dac87ca7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -45,10 +45,13 @@
#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 "commands/schema_variable.h"
#include "executor/execdebug.h"
#include "executor/nodeSubplan.h"
+#include "executor/svariableReceiver.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
#include "mb/pg_wchar.h"
@@ -199,6 +202,52 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
Assert(queryDesc->sourceText != NULL);
estate->es_sourceText = queryDesc->sourceText;
+ /*
+ * Prepare schema variables, if are not prepared in queryDesc
+ */
+ if (queryDesc->num_schema_variables > 0)
+ {
+ /*
+ * link shared memory with working copy of schema variable's values
+ * used in this query. This access is used by parallel query executor's
+ * workers.
+ */
+ estate->es_schema_variables = queryDesc->schema_variables;
+ estate->es_num_schema_variables = queryDesc->num_schema_variables;
+ }
+ else if (queryDesc->plannedstmt->schemaVariables)
+ {
+ ListCell *lc;
+ int nSchemaVariables;
+ int i = 0;
+
+ nSchemaVariables = list_length(queryDesc->plannedstmt->schemaVariables);
+
+ /* Create buffer for used schema variables */
+ estate->es_schema_variables = (SchemaVariableValue *)
+ palloc(nSchemaVariables * sizeof(SchemaVariableValue));
+
+ foreach(lc, queryDesc->plannedstmt->schemaVariables)
+ {
+ AclResult aclresult;
+ Oid varid = lfirst_oid(lc);
+
+ aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_READ);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE,
+ schema_variable_get_name(varid));
+
+ estate->es_schema_variables[i].varid = varid;
+ estate->es_schema_variables[i].value = CopySchemaVariable(varid,
+ &estate->es_schema_variables[i].isnull,
+ &estate->es_schema_variables[i].typid);
+
+ i++;
+ }
+
+ estate->es_num_schema_variables = nSchemaVariables;
+ }
+
/*
* Fill in the query environment, if any, from queryDesc.
*/
diff --git a/src/backend/executor/execParallel.c \
b/src/backend/executor/execParallel.c index f8a4a40e7b..341c817371 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -12,8 +12,9 @@
* workers and ensuring that their state generally matches that of the
* leader; see src/backend/access/transam/README.parallel for details.
* However, we must save and restore relevant executor state, such as
- * any ParamListInfo associated with the query, buffer/WAL usage info, and
- * the actual plan to be passed down to the worker.
+ * any ParamListInfo associated with the query, buffer/WAL usage info,
+ * used schema variables buffer, and the actual plan to be passed down
+ * to the worker.
*
* IDENTIFICATION
* src/backend/executor/execParallel.c
@@ -66,6 +67,7 @@
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008)
#define PARALLEL_KEY_JIT_INSTRUMENTATION UINT64CONST(0xE000000000000009)
#define PARALLEL_KEY_WAL_USAGE UINT64CONST(0xE00000000000000A)
+#define PARALLEL_KEY_SCHEMA_VARIABLES UINT64CONST(0xE00000000000000B)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -140,6 +142,12 @@ static bool ExecParallelRetrieveInstrumentation(PlanState \
*planstate, /* Helper function that runs in the parallel worker. */
static DestReceiver *ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc);
+/* Helper functions that can pass values of used schema variables */
+static Size EstimateSchemaVariables(EState *estate);
+static void SerializeSchemaVariables(EState *estate, char **start_address);
+static SchemaVariableValue * RestoreSchemaVariables(char **start_address,
+ int *num_schema_variables);
+
/*
* Create a serialized representation of the plan to be sent to each worker.
*/
@@ -597,6 +605,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
char *pstmt_data;
char *pstmt_space;
char *paramlistinfo_space;
+ char *schema_variables_space;
BufferUsage *bufusage_space;
WalUsage *walusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
@@ -606,6 +615,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
int instrumentation_len = 0;
int jit_instrumentation_len = 0;
int instrument_offset = 0;
+ int schema_variables_len = 0;
Size dsa_minsize = dsa_minimum_size();
char *query_string;
int query_len;
@@ -661,6 +671,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
shm_toc_estimate_chunk(&pcxt->estimator, paramlistinfo_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for serialized schema variables. */
+ schema_variables_len = EstimateSchemaVariables(estate);
+ shm_toc_estimate_chunk(&pcxt->estimator, schema_variables_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -755,6 +770,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMLISTINFO, paramlistinfo_space);
SerializeParamList(estate->es_param_list_info, ¶mlistinfo_space);
+ /* Store serialized schema variables. */
+ schema_variables_space = shm_toc_allocate(pcxt->toc, schema_variables_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_SCHEMA_VARIABLES, schema_variables_space);
+ SerializeSchemaVariables(estate, &schema_variables_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -1402,6 +1422,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
SharedJitInstrumentation *jit_instrumentation;
int instrument_options = 0;
void *area_space;
+ char *schemavariable_space;
dsa_area *area;
ParallelWorkerContext pwcxt;
@@ -1427,6 +1448,14 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
area_space = shm_toc_lookup(toc, PARALLEL_KEY_DSA, false);
area = dsa_attach_in_place(area_space, seg);
+ /* Reconstruct schema variables. */
+ schemavariable_space = shm_toc_lookup(toc,
+ PARALLEL_KEY_SCHEMA_VARIABLES,
+ false);
+ queryDesc->schema_variables =
+ RestoreSchemaVariables(&schemavariable_space,
+ &queryDesc->num_schema_variables);
+
/* Start up the executor */
queryDesc->plannedstmt->jitFlags = fpes->jit_flags;
ExecutorStart(queryDesc, fpes->eflags);
@@ -1496,3 +1525,118 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
FreeQueryDesc(queryDesc);
receiver->rDestroy(receiver);
}
+
+/*
+ * Estimate the amount of space required to serialize a used
+ * schema variables.
+ */
+static Size
+EstimateSchemaVariables(EState *estate)
+{
+ int i;
+ Size sz = sizeof(int);
+
+ if (estate->es_schema_variables == NULL)
+ return sz;
+
+ for (i = 0; i < estate->es_num_schema_variables; i++)
+ {
+ SchemaVariableValue *svarval;
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ svarval = &estate->es_schema_variables[i];
+
+ typeOid = svarval->typid;
+
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ Assert(OidIsValid(typeOid));
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+
+ sz = add_size(sz,
+ datumEstimateSpace(svarval->value, svarval->isnull, typByVal, typLen));
+ }
+
+ return sz;
+}
+
+/*
+ * Serialize a schema variables buffer into caller-provided storage.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte type OID, and then the datum as serialized by
+ * datumSerialize(). The caller is responsible for ensuring that there is
+ * enough storage to store the number of bytes that will be written; use
+ * EstimateSchemaVariables to find out how many will be needed.
+ * *start_address is updated to point to the byte immediately following those
+ * written.
+ *
+ * RestoreSchemaVariables can be used to recreate a schema variable buffer
+ * based on the serialized representation;
+ */
+static void
+SerializeSchemaVariables(EState *estate, char **start_address)
+{
+ int nparams;
+ int i;
+
+ /* Write number of parameters. */
+ nparams = estate->es_num_schema_variables;
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write each parameter in turn. */
+ for (i = 0; i < nparams; i++)
+ {
+ SchemaVariableValue *svarval;
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ svarval = &estate->es_schema_variables[i];
+ typeOid = svarval->typid;
+
+ /* Write type OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ Assert(OidIsValid(typeOid));
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+
+ datumSerialize(svarval->value, svarval->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+static SchemaVariableValue *
+RestoreSchemaVariables(char **start_address, int *num_schema_variables)
+{
+ SchemaVariableValue *schema_variables;
+ int i;
+ int nparams;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ *num_schema_variables = nparams;
+ schema_variables = (SchemaVariableValue *)
+ palloc(nparams * sizeof(SchemaVariableValue));
+
+ for (i = 0; i < nparams; i++)
+ {
+ SchemaVariableValue *svarval = &schema_variables[i];
+
+ /* Read type OID. */
+ memcpy(&svarval->typid, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ svarval->value = datumRestore(start_address, &svarval->isnull);
+ }
+
+ return schema_variables;
+}
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 0568ae123f..1b37ef33a4 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2867,6 +2867,9 @@ _SPI_error_callback(void *arg)
case RAW_PARSE_PLPGSQL_ASSIGN3:
errcontext("PL/pgSQL assignment \"%s\"", query);
break;
+ case RAW_PARSE_PLPGSQL_LET:
+ errcontext("LET statement \"%s\"", query);
+ break;
default:
errcontext("SQL statement \"%s\"", query);
break;
diff --git a/src/backend/executor/svariableReceiver.c \
b/src/backend/executor/svariableReceiver.c new file mode 100644
index 0000000000..298330c1cf
--- /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-2020, 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/detoast.h"
+#include "executor/svariableReceiver.h"
+#include "commands/schema_variable.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(detoast_external_attr((struct varlena *)
+ DatumGetPointer(value)));
+ freeval = true;
+ }
+
+ SetSchemaVariable(myState->varid, value, isnull, myState->typid);
+
+ 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/jit/llvm/llvmjit_expr.c \
b/src/backend/jit/llvm/llvmjit_expr.c index 6d1181225e..4299809eb5 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -1073,6 +1073,12 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_PARAM_VARIABLE:
+ build_EvalXFunc(b, mod, "ExecEvalParamVariable",
+ v_state, op, v_econtext);
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ break;
+
case EEOP_PARAM_CALLBACK:
{
LLVMTypeRef v_functype;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 82464c9889..44e42c6f69 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -106,6 +106,7 @@ _copyPlannedStmt(const PlannedStmt *from)
COPY_NODE_FIELD(invalItems);
COPY_NODE_FIELD(paramExecTypes);
COPY_NODE_FIELD(utilityStmt);
+ COPY_NODE_FIELD(schemaVariables);
COPY_LOCATION_FIELD(stmt_location);
COPY_SCALAR_FIELD(stmt_len);
@@ -1506,6 +1507,7 @@ _copyParam(const Param *from)
COPY_SCALAR_FIELD(paramtype);
COPY_SCALAR_FIELD(paramtypmod);
COPY_SCALAR_FIELD(paramcollid);
+ COPY_SCALAR_FIELD(paramvarid);
COPY_LOCATION_FIELD(location);
return newnode;
@@ -3162,6 +3164,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);
@@ -3172,6 +3175,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
COPY_SCALAR_FIELD(isReturn);
+ COPY_SCALAR_FIELD(hasSchemaVariables);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
@@ -3285,6 +3289,19 @@ _copySelectStmt(const SelectStmt *from)
return newnode;
}
+static LetStmt *
+_copyLetStmt(const LetStmt * from)
+{
+ LetStmt *newnode = makeNode(LetStmt);
+
+ COPY_NODE_FIELD(target);
+ COPY_NODE_FIELD(query);
+ COPY_SCALAR_FIELD(plpgsql_mode);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
static SetOperationStmt *
_copySetOperationStmt(const SetOperationStmt *from)
{
@@ -4899,6 +4916,23 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
return newnode;
}
+static CreateSchemaVarStmt *
+_copyCreateSchemaVarStmt(const CreateSchemaVarStmt *from)
+{
+ CreateSchemaVarStmt *newnode = makeNode(CreateSchemaVarStmt);
+
+ COPY_NODE_FIELD(variable);
+ COPY_NODE_FIELD(typeName);
+ COPY_NODE_FIELD(collClause);
+ COPY_NODE_FIELD(defexpr);
+ COPY_SCALAR_FIELD(eoxaction);
+ COPY_SCALAR_FIELD(if_not_exists);
+ COPY_SCALAR_FIELD(is_not_null);
+ COPY_SCALAR_FIELD(is_immutable);
+
+ return newnode;
+}
+
/* ****************************************************************
* extensible.h copy functions
* ****************************************************************
@@ -5402,6 +5436,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;
@@ -5747,6 +5784,9 @@ copyObjectImpl(const void *from)
case T_DropSubscriptionStmt:
retval = _copyDropSubscriptionStmt(from);
break;
+ case T_CreateSchemaVarStmt:
+ retval = _copyCreateSchemaVarStmt(from);
+ break;
case T_A_Expr:
retval = _copyA_Expr(from);
break;
@@ -5915,7 +5955,7 @@ copyObjectImpl(const void *from)
break;
default:
- elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
+ elog(ERROR, "urecognized node type: %d", (int) nodeTag(from));
retval = 0; /* keep compiler quiet */
break;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f537d3eb96..33bc273880 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -215,6 +215,7 @@ _equalParam(const Param *a, const Param *b)
COMPARE_SCALAR_FIELD(paramtype);
COMPARE_SCALAR_FIELD(paramtypmod);
COMPARE_SCALAR_FIELD(paramcollid);
+ COMPARE_SCALAR_FIELD(paramvarid);
COMPARE_LOCATION_FIELD(location);
return true;
@@ -980,6 +981,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);
@@ -990,6 +992,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
COMPARE_SCALAR_FIELD(isReturn);
+ COMPARE_SCALAR_FIELD(hasSchemaVariables);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
@@ -1093,6 +1096,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(query);
+ COMPARE_SCALAR_FIELD(plpgsql_mode);
+
+ return true;
+}
+
static bool
_equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
{
@@ -2377,6 +2390,22 @@ _equalDropSubscriptionStmt(const DropSubscriptionStmt *a,
return true;
}
+static bool
+_equalCreateSchemaVarStmt(const CreateSchemaVarStmt *a,
+ const CreateSchemaVarStmt *b)
+{
+ COMPARE_NODE_FIELD(variable);
+ COMPARE_NODE_FIELD(typeName);
+ COMPARE_NODE_FIELD(collClause);
+ COMPARE_NODE_FIELD(defexpr);
+ COMPARE_SCALAR_FIELD(eoxaction);
+ COMPARE_SCALAR_FIELD(if_not_exists);
+ COMPARE_SCALAR_FIELD(is_not_null);
+ COMPARE_SCALAR_FIELD(is_immutable);
+
+ return true;
+}
+
static bool
_equalCreatePolicyStmt(const CreatePolicyStmt *a, const CreatePolicyStmt *b)
{
@@ -3408,6 +3437,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;
@@ -3753,6 +3785,9 @@ equal(const void *a, const void *b)
case T_DropSubscriptionStmt:
retval = _equalDropSubscriptionStmt(a, b);
break;
+ case T_CreateSchemaVarStmt:
+ retval = _equalCreateSchemaVarStmt(a, b);
+ break;
case T_A_Expr:
retval = _equalA_Expr(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2e5ed77e18..20f06beeae 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -324,6 +324,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
WRITE_NODE_FIELD(invalItems);
WRITE_NODE_FIELD(paramExecTypes);
WRITE_NODE_FIELD(utilityStmt);
+ WRITE_NODE_FIELD(schemaVariables);
WRITE_LOCATION_FIELD(stmt_location);
WRITE_INT_FIELD(stmt_len);
}
@@ -1164,6 +1165,7 @@ _outParam(StringInfo str, const Param *node)
WRITE_OID_FIELD(paramtype);
WRITE_INT_FIELD(paramtypmod);
WRITE_OID_FIELD(paramcollid);
+ WRITE_OID_FIELD(paramvarid);
WRITE_LOCATION_FIELD(location);
}
@@ -2276,6 +2278,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
WRITE_NODE_FIELD(relationOids);
WRITE_NODE_FIELD(invalItems);
WRITE_NODE_FIELD(paramExecTypes);
+ WRITE_NODE_FIELD(schemaVariables);
WRITE_UINT_FIELD(lastPHId);
WRITE_UINT_FIELD(lastRowMarkId);
WRITE_INT_FIELD(lastPlanNodeId);
@@ -2336,6 +2339,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_BOOL_FIELD(hasPseudoConstantQuals);
WRITE_BOOL_FIELD(hasAlternativeSubPlans);
WRITE_BOOL_FIELD(hasRecursion);
+ WRITE_BOOL_FIELD(hasSchemaVariables);
WRITE_INT_FIELD(wt_param_id);
WRITE_BITMAPSET_FIELD(curOuterRels);
WRITE_NODE_FIELD(curOuterParams);
@@ -2870,6 +2874,17 @@ _outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outLetStmt(StringInfo str, const LetStmt * node)
+{
+ WRITE_NODE_TYPE("LET");
+
+ WRITE_NODE_FIELD(target);
+ WRITE_NODE_FIELD(query);
+ WRITE_BOOL_FIELD(plpgsql_mode);
+ WRITE_LOCATION_FIELD(location);
+}
+
static void
_outFuncCall(StringInfo str, const FuncCall *node)
{
@@ -3050,6 +3065,7 @@ _outQuery(StringInfo str, const Query *node)
case T_IndexStmt:
case T_NotifyStmt:
case T_DeclareCursorStmt:
+ case T_LetStmt:
WRITE_NODE_FIELD(utilityStmt);
break;
default:
@@ -3061,6 +3077,7 @@ _outQuery(StringInfo str, const Query *node)
appendStringInfoString(str, " :utilityStmt <>");
WRITE_INT_FIELD(resultRelation);
+ WRITE_OID_FIELD(resultVariable);
WRITE_BOOL_FIELD(hasAggs);
WRITE_BOOL_FIELD(hasWindowFuncs);
WRITE_BOOL_FIELD(hasTargetSRFs);
@@ -3071,6 +3088,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
WRITE_BOOL_FIELD(isReturn);
+ WRITE_BOOL_FIELD(hasSchemaVariables);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
@@ -4371,6 +4389,9 @@ outNode(StringInfo str, const void *obj)
case T_PLAssignStmt:
_outPLAssignStmt(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 abf08b7a2f..ce58599c20 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -254,6 +254,7 @@ _readQuery(void)
READ_BOOL_FIELD(canSetTag);
READ_NODE_FIELD(utilityStmt);
READ_INT_FIELD(resultRelation);
+ READ_OID_FIELD(resultVariable);
READ_BOOL_FIELD(hasAggs);
READ_BOOL_FIELD(hasWindowFuncs);
READ_BOOL_FIELD(hasTargetSRFs);
@@ -264,6 +265,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
READ_BOOL_FIELD(isReturn);
+ READ_BOOL_FIELD(hasSchemaVariables);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
@@ -628,6 +630,7 @@ _readParam(void)
READ_OID_FIELD(paramtype);
READ_INT_FIELD(paramtypmod);
READ_OID_FIELD(paramcollid);
+ READ_OID_FIELD(paramvarid);
READ_LOCATION_FIELD(location);
READ_DONE();
@@ -1599,6 +1602,7 @@ _readPlannedStmt(void)
READ_NODE_FIELD(invalItems);
READ_NODE_FIELD(paramExecTypes);
READ_NODE_FIELD(utilityStmt);
+ READ_NODE_FIELD(schemaVariables);
READ_LOCATION_FIELD(stmt_location);
READ_INT_FIELD(stmt_len);
diff --git a/src/backend/optimizer/plan/planner.c \
b/src/backend/optimizer/plan/planner.c index bd01ec0526..b93e7dd5a0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -316,6 +316,7 @@ standard_planner(Query *parse, const char *query_string, int \
cursorOptions, glob->lastPlanNodeId = 0;
glob->transientPlan = false;
glob->dependsOnRole = false;
+ glob->schemaVariables = NIL;
/*
* Assess whether it's feasible to use parallel mode for this query. We
@@ -529,6 +530,7 @@ standard_planner(Query *parse, const char *query_string, int \
cursorOptions, result->paramExecTypes = glob->paramExecTypes;
/* utilityStmt should be null, but we might as well copy it */
result->utilityStmt = parse->utilityStmt;
+ result->schemaVariables = glob->schemaVariables;
result->stmt_location = parse->stmt_location;
result->stmt_len = parse->stmt_len;
@@ -678,6 +680,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
*/
pull_up_subqueries(root);
+ /*
+ * Check if some subquery uses schema variable. Flag hasSchemaVariables
+ * should be true if query or some subquery uses any schema variable.
+ */
+ pull_up_has_schema_variables(root);
+
/*
* If this is a simple UNION ALL query, flatten it into an appendrel. We
* do this now because it requires applying pull_up_subqueries to the leaf
diff --git a/src/backend/optimizer/plan/setrefs.c \
b/src/backend/optimizer/plan/setrefs.c index 6ccec759bd..91717cd4e1 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -172,7 +172,8 @@ static List *set_returning_clause_references(PlannerInfo *root,
Plan *topplan,
Index resultRelation,
int rtoffset);
-
+static bool pull_up_has_schema_variables_walker(Node *node,
+ PlannerInfo *root);
/*****************************************************************************
*
@@ -1127,6 +1128,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
return plan;
}
+/*
+ * Search usage of schema variables in subqueries
+ */
+void
+pull_up_has_schema_variables(PlannerInfo *root)
+{
+ Query *query = root->parse;
+
+ if (query->hasSchemaVariables)
+ {
+ root->hasSchemaVariables = true;
+ }
+ else
+ {
+ (void) query_tree_walker(query,
+ pull_up_has_schema_variables_walker,
+ (void *) root, 0);
+ }
+}
+
+static bool
+pull_up_has_schema_variables_walker(Node *node, PlannerInfo *root)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Query))
+ {
+ Query *query = (Query *) node;
+
+ if (query->hasSchemaVariables)
+ {
+ root->hasSchemaVariables = true;
+ return false;
+ }
+
+ /* Recurse into subselects */
+ return query_tree_walker((Query *) node,
+ pull_up_has_schema_variables_walker,
+ (void *) root, 0);
+ }
+ return expression_tree_walker(node, pull_up_has_schema_variables_walker,
+ (void *) root);
+}
+
/*
* set_indexonlyscan_references
* Do set_plan_references processing on an IndexOnlyScan
@@ -1758,10 +1803,14 @@ fix_expr_common(PlannerInfo *root, Node *node)
/*
* fix_param_node
* Do set_plan_references processing on a Param
+ * Collect schema variables list and replace variable oid by
+ * index to collected list.
*
* If it's a PARAM_MULTIEXPR, replace it with the appropriate Param from
* root->multiexpr_params; otherwise no change is needed.
* Just for paranoia's sake, we make a copy of the node in either case.
+ *
+ * If it's a PARAM_VARIABLE, then we should to calculate paramid.
*/
static Node *
fix_param_node(PlannerInfo *root, Param *p)
@@ -1780,6 +1829,52 @@ fix_param_node(PlannerInfo *root, Param *p)
elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", p->paramid);
return copyObject(list_nth(params, colno - 1));
}
+
+ if (p->paramkind == PARAM_VARIABLE)
+ {
+ ListCell *lc;
+ int n = 0;
+ bool found = false;
+
+ /* We will modify object */
+ p = (Param *) copyObject(p);
+
+ /*
+ * Now, we can actualize list of schema variables, and we can complete
+ * paramid parameter.
+ */
+ foreach(lc, root->glob->schemaVariables)
+ {
+ if (lfirst_oid(lc) == p->paramvarid)
+ {
+ p->paramid = n;
+ found = true;
+ break;
+ }
+ n += 1;
+ }
+
+ if (!found)
+ {
+ PlanInvalItem *inval_item = makeNode(PlanInvalItem);
+
+ /* paramid is still schema variable id */
+ inval_item->cacheId = VARIABLEOID;
+ inval_item->hashValue = GetSysCacheHashValue1(VARIABLEOID,
+ ObjectIdGetDatum(p->paramvarid));
+
+ /* Append this variable to global, register dependency */
+ root->glob->invalItems = lappend(root->glob->invalItems,
+ inval_item);
+ root->glob->schemaVariables = lappend_oid(root->glob->schemaVariables,
+ p->paramvarid);
+
+ p->paramid = n;
+ }
+
+ return (Node *) p;
+ }
+
return (Node *) copyObject(p);
}
@@ -1841,7 +1936,9 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan \
*asplan,
* replacing Aggref nodes that should be replaced by initplan output Params,
* choosing the best implementation for AlternativeSubPlans,
* looking up operator opcode info for OpExpr and related nodes,
- * and adding OIDs from regclass Const nodes into root->glob->relationOids.
+ * and adding OIDs from regclass Const nodes into root->glob->relationOids,
+ * and replacing PARAM_VARIABLE paramid, that is oid of schema variable
+ * to offset to array of by query used schema variables.
*
* 'node': the expression to be modified
* 'rtoffset': how much to increment varnos by
@@ -1863,7 +1960,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, \
double num_exec) root->multiexpr_params != NIL ||
root->glob->lastPHId != 0 ||
root->minmax_aggs != NIL ||
- root->hasAlternativeSubPlans)
+ root->hasAlternativeSubPlans ||
+ root->hasSchemaVariables)
{
return fix_scan_expr_mutator(node, &context);
}
diff --git a/src/backend/optimizer/util/clauses.c \
b/src/backend/optimizer/util/clauses.c index 3412d31117..e1432563f5 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -26,6 +26,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "commands/schema_variable.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -818,7 +819,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 ||
@@ -2228,6 +2230,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
* value of the Param.
* 2. Fold stable, as well as immutable, functions to constants.
* 3. Reduce PlaceHolderVar nodes to their contained expressions.
+ * 4. Current value of schema variable can be used for estimation too.
*--------------------
*/
Node *
@@ -2350,6 +2353,30 @@ eval_const_expressions_mutator(Node *node,
}
}
}
+ else if (param->paramkind == PARAM_VARIABLE &&
+ context->estimate)
+ {
+ int16 typLen;
+ bool typByVal;
+ Datum pval;
+ bool isnull;
+
+ get_typlenbyval(param->paramtype,
+ &typLen, &typByVal);
+
+ pval = GetSchemaVariable(param->paramvarid,
+ &isnull,
+ param->paramtype,
+ true);
+
+ return (Node *) makeConst(param->paramtype,
+ param->paramtypmod,
+ param->paramcollid,
+ (int) typLen,
+ pval,
+ isnull,
+ typByVal);
+ }
/*
* Not replaceable, so just copy the Param (no need to
@@ -4734,7 +4761,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/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1e..8971cf7e80 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,8 +25,11 @@
#include "postgres.h"
#include "access/sysattr.h"
+#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
+#include "commands/schema_variable.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
@@ -50,6 +53,7 @@
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/queryjumble.h"
+#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -88,6 +92,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
@@ -289,6 +295,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:
@@ -358,6 +365,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
(CallStmt *) parseTree);
break;
+ case T_LetStmt:
+ result = transformLetStmt(pstate,
+ (LetStmt *) parseTree);
+ break;
+
default:
/*
@@ -399,6 +411,7 @@ analyze_requires_snapshot(RawStmt *parseTree)
case T_UpdateStmt:
case T_SelectStmt:
case T_PLAssignStmt:
+ case T_LetStmt:
result = true;
break;
@@ -482,6 +495,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasAggs = pstate->p_hasAggs;
+ qry->hasSchemaVariables = pstate->p_hasSchemaVariables;
+
assign_query_collations(pstate, qry);
/* this must be done after collations, for reliable comparison of exprs */
@@ -899,6 +914,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasSchemaVariables = pstate->p_hasSchemaVariables;
assign_query_collations(pstate, qry);
@@ -1241,6 +1257,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
Query *qry = makeNode(Query);
Node *qual;
ListCell *l;
+ ParseExprKind target_exprkind;
qry->commandType = CMD_SELECT;
@@ -1269,9 +1286,19 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
/* process the FROM clause */
transformFromClause(pstate, stmt->fromClause);
- /* transform targetlist */
- qry->targetList = transformTargetList(pstate, stmt->targetList,
- EXPR_KIND_SELECT_TARGET);
+ /*
+ * Transform targetlist. Second usage of this transformation
+ * is for Let statement. In this case we would to allow DEFAULT
+ * keyword - we specify EXPR_KIND_LET_TARGET.
+ */
+ target_exprkind =
+ (pstate->p_expr_kind != EXPR_KIND_LET_TARGET ||
+ pstate->parentParseState != NULL) ?
+ EXPR_KIND_SELECT_TARGET : EXPR_KIND_LET_TARGET;
+
+ qry->targetList = transformTargetList(pstate,
+ stmt->targetList,
+ target_exprkind);
/* mark column origins */
markTargetListOrigins(pstate, qry->targetList);
@@ -1361,6 +1388,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
(LockingClause *) lfirst(l), false);
}
+ qry->hasSchemaVariables = pstate->p_hasSchemaVariables;
+
assign_query_collations(pstate, qry);
/* this must be done after collations, for reliable comparison of exprs */
@@ -1585,12 +1614,261 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasSchemaVariables = pstate->p_hasSchemaVariables;
assign_query_collations(pstate, qry);
return qry;
}
+/*
+ * transformLetStmt -
+ * transform an Let Statement
+ */
+static Query *
+transformLetStmt(ParseState *pstate, LetStmt * stmt)
+{
+ Query *query;
+ Query *result;
+ List *exprList = NIL;
+ List *exprListCoer = NIL;
+ 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;
+ bool to_default = false;
+ 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_TARGET;
+
+ /* There can't be any outer WITH to worry about */
+ Assert(pstate->p_ctenamespace == NIL);
+
+ names = NamesFromList(stmt->target);
+
+ varid = IdentifyVariable(names, &attrname, ¬_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)));
+
+ indirection_start = list_length(names) - (attrname ? 1 : 0);
+ indirection = list_copy_tail(stmt->target, indirection_start);
+
+ 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->query);
+
+ /* The grammar should have produced a SELECT */
+ if (!IsA(selectQuery, Query) ||
+ selectQuery->commandType != CMD_SELECT)
+ elog(ERROR, "unexpected non-SELECT command in LET command");
+
+ /*----------
+ * 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;
+
+ if (IsA(tle->expr, SetToDefault))
+ to_default = true;
+
+ 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))));
+
+ if (to_default && attrname != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only complete variable can be set to default"),
+ parser_errposition(pstate, stmt->location)));
+
+ exprListCoer = NIL;
+
+ foreach(lc, exprList)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ Expr *coerced_expr;
+ Param *param;
+ Oid exprtypid;
+
+ if (IsA(expr, SetToDefault))
+ {
+ SetToDefault *def = (SetToDefault *) expr;
+
+ def->typeId = typid;
+ def->typeMod = typmod;
+ def->collation = collid;
+ }
+ else if (IsA(expr, Const) && ((Const *) expr)->constisnull)
+ {
+ /* use known type for NULL value */
+ expr = (Expr *) makeNullConst(typid, typmod, collid);
+ }
+
+ /* now we can read type of expression */
+ exprtypid = exprType((Node *) expr);
+
+ param = makeNode(Param);
+ param->paramkind = PARAM_VARIABLE;
+ param->paramvarid = varid;
+ param->paramtype = typid;
+ param->paramtypmod = typmod;
+
+ if (indirection != NULL)
+ {
+ bool targetIsArray;
+ char *targetName;
+
+ targetName = attrname != NULL ? attrname : get_schema_variable_name(varid);
+ targetIsArray = OidIsValid(get_element_type(typid));
+
+ pstate->p_hasSchemaVariables = true;
+
+ coerced_expr = (Expr *)
+ transformAssignmentIndirection(pstate,
+ (Node *) param,
+ targetName,
+ targetIsArray,
+ typid,
+ typmod,
+ InvalidOid,
+ indirection,
+ list_head(indirection),
+ (Node *) expr,
+ COERCION_PLPGSQL,
+ stmt->location);
+ }
+ else
+ coerced_expr = (Expr *)
+ coerce_to_target_type(pstate,
+ (Node *) expr,
+ exprtypid,
+ typid, typmod,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ stmt->location);
+
+ if (coerced_expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("variable \"%s\" 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 *) expr))));
+
+ exprListCoer = lappend(exprListCoer, coerced_expr);
+ }
+
+ /*
+ * Generate query's target list using the computed list of expressions.
+ */
+ query = makeNode(Query);
+ query->commandType = CMD_SELECT;
+
+ foreach(lc, exprListCoer)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ TargetEntry *tle;
+
+ tle = makeTargetEntry(expr,
+ i + 1,
+ FigureColname((Node *) expr),
+ false);
+ query->targetList = lappend(query->targetList, tle);
+ }
+
+ /* done building the range table and jointree */
+ query->rtable = pstate->p_rtable;
+ query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+ query->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ query->hasSubLinks = pstate->p_hasSubLinks;
+ query->hasSchemaVariables = pstate->p_hasSchemaVariables;
+
+ /* This is top query */
+ query->canSetTag = true;
+
+ /*
+ * rewrite SetToDefaults needs varid in Query structure
+ */
+ query->resultVariable = varid;
+
+ assign_query_collations(pstate, query);
+
+ pstate->p_expr_kind = sv_expr_kind;
+
+ stmt->query = (Node *) query;
+
+ /*
+ * When statement is executed as an expression as PLpgSQL LET
+ * statement, then we should to return query. We should not
+ * to use an utility wrapper node.
+ */
+ if (stmt->plpgsql_mode)
+ return query;
+
+ /* represent the command as a utility Query */
+ result = makeNode(Query);
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) stmt;
+
+ return result;
+}
+
/*
* transformSetOperationStmt -
* transforms a set-operations tree
@@ -1841,6 +2119,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
(LockingClause *) lfirst(l), false);
}
+ qry->hasSchemaVariables = pstate->p_hasSchemaVariables;
+
assign_query_collations(pstate, qry);
/* this must be done after collations, for reliable comparison of exprs */
@@ -2369,6 +2649,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasSchemaVariables = pstate->p_hasSchemaVariables;
assign_query_collations(pstate, qry);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d0eb80e69c..fb5391d173 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List \
*aliases, Node *query); JoinType jtype;
DropBehavior dbehavior;
OnCommitAction oncommit;
+ VariableEOXAction oneoxaction;
List *list;
Node *node;
ObjectType objtype;
@@ -281,8 +282,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
CreateAssertionStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
@@ -292,7 +293,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 ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@@ -432,6 +433,7 @@ static Node *makeRecursiveViewSelect(char *relname, List \
*aliases, Node *query); TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list
+ let_target
%type <node> opt_routine_body
%type <groupclause> group_clause
@@ -454,6 +456,7 @@ static Node *makeRecursiveViewSelect(char *relname, List \
*aliases, Node *query); %type <ival> OptTemp
%type <ival> OptNoLog
%type <oncommit> OnCommitOption
+%type <oneoxaction> OnEOXActionOption
%type <ival> for_locking_strength
%type <node> for_locking_item
@@ -615,6 +618,8 @@ static Node *makeRecursiveViewSelect(char *relname, List \
*aliases, Node *query); %type <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
+%type <node> OptSchemaVarDefExpr
+%type <boolean> OptNotNull OptImmutable
/*
@@ -684,7 +689,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
@@ -723,8 +728,8 @@ static Node *makeRecursiveViewSelect(char *relname, List \
*aliases, Node *query); UESCAPE 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
@@ -759,6 +764,7 @@ static Node *makeRecursiveViewSelect(char *relname, List \
*aliases, Node *query); %token MODE_PLPGSQL_ASSIGN1
%token MODE_PLPGSQL_ASSIGN2
%token MODE_PLPGSQL_ASSIGN3
+%token MODE_PLPGSQL_LET
/* Precedence: lowest to highest */
@@ -864,6 +870,13 @@ parse_toplevel:
pg_yyget_extra(yyscanner)->parsetree =
list_make1(makeRawStmt((Node *) n, 0));
}
+ | MODE_PLPGSQL_LET LetStmt
+ {
+ LetStmt *n = (LetStmt *) $2;
+ n->plpgsql_mode = true;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
;
/*
@@ -967,6 +980,7 @@ stmt:
| CreatePolicyStmt
| CreatePLangStmt
| CreateSchemaStmt
+ | CreateSchemaVarStmt
| CreateSeqStmt
| CreateStmt
| CreateSubscriptionStmt
@@ -1004,6 +1018,7 @@ stmt:
| ImportForeignSchemaStmt
| IndexStmt
| InsertStmt
+ | LetStmt
| ListenStmt
| RefreshMatViewStmt
| LoadStmt
@@ -1896,7 +1911,12 @@ DiscardStmt:
n->target = DISCARD_SEQUENCES;
$$ = (Node *) n;
}
-
+ | DISCARD VARIABLES
+ {
+ DiscardStmt *n = makeNode(DiscardStmt);
+ n->target = DISCARD_VARIABLES;
+ $$ = (Node *) n;
+ }
;
@@ -4655,6 +4675,61 @@ create_extension_opt_item:
}
;
+/*****************************************************************************
+ *
+ * QUERY :
+ * CREATE VARIABLE varname [AS] type
+ *
+ *****************************************************************************/
+
+CreateSchemaVarStmt:
+ CREATE OptTemp OptImmutable VARIABLE qualified_name opt_as Typename \
opt_collate_clause OptNotNull OptSchemaVarDefExpr OnEOXActionOption + {
+ CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+ $5->relpersistence = $2;
+ n->is_immutable = $3;
+ n->variable = $5;
+ n->typeName = $7;
+ n->collClause = (CollateClause *) $8;
+ n->is_not_null = $9;
+ n->defexpr = $10;
+ n->eoxaction = $11;
+ n->if_not_exists = false;
+ $$ = (Node *) n;
+ }
+ | CREATE OptTemp OptImmutable VARIABLE IF_P NOT EXISTS qualified_name opt_as \
Typename opt_collate_clause OptNotNull OptSchemaVarDefExpr OnEOXActionOption + {
+ CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+ $8->relpersistence = $2;
+ n->is_immutable = $3;
+ n->variable = $8;
+ n->typeName = $10;
+ n->collClause = (CollateClause *) $11;
+ n->is_not_null = $12;
+ n->defexpr = $13;
+ n->eoxaction = $14;
+ n->if_not_exists = true;
+ $$ = (Node *) n;
+ }
+ ;
+
+OptSchemaVarDefExpr: DEFAULT b_expr { $$ = $2; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+OnEOXActionOption: ON COMMIT DROP { $$ = VARIABLE_EOX_DROP; }
+ | ON TRANSACTION END_P RESET { $$ = VARIABLE_EOX_RESET; }
+ | /*EMPTY*/ { $$ = VARIABLE_EOX_NOOP; }
+ ;
+
+OptNotNull: NOT NULL_P { $$ = true; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+OptImmutable: IMMUTABLE { $$ = true; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
/*****************************************************************************
*
* ALTER EXTENSION name UPDATE [ TO version ]
@@ -6342,6 +6417,7 @@ object_type_any_name:
| TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; }
| TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; }
| TEXT_P SEARCH CONFIGURATION { $$ = OBJECT_TSCONFIGURATION; }
+ | VARIABLE { $$ = OBJECT_VARIABLE; }
;
/*
@@ -7110,6 +7186,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));
@@ -7150,6 +7234,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;
+ }
;
@@ -7310,6 +7402,7 @@ defacl_privilege_target:
| SEQUENCES { $$ = OBJECT_SEQUENCE; }
| TYPES_P { $$ = OBJECT_TYPE; }
| SCHEMAS { $$ = OBJECT_SCHEMA; }
+ | VARIABLES { $$ = OBJECT_VARIABLE; }
;
@@ -9012,6 +9105,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
@@ -9340,6 +9452,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;
+ }
+
;
/*****************************************************************************
@@ -9593,6 +9724,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;
+ }
;
@@ -10928,6 +11067,7 @@ ExplainableStmt:
| CreateMatViewStmt
| RefreshMatViewStmt
| ExecuteStmt /* by default all are $$=$1 */
+ | LetStmt
;
/*****************************************************************************
@@ -10956,6 +11096,7 @@ PreparableStmt:
| InsertStmt
| UpdateStmt
| DeleteStmt /* by default all are $$=$1 */
+ | LetStmt
;
/*****************************************************************************
@@ -11373,6 +11514,47 @@ opt_hold: /* EMPTY */ { $$ = 0; }
| WITHOUT HOLD { $$ = 0; }
;
+/*****************************************************************************
+ *
+ * QUERY:
+ * LET STATEMENTS
+ *
+ *****************************************************************************/
+LetStmt: LET let_target '=' a_expr
+ {
+ LetStmt *n = makeNode(LetStmt);
+ SelectStmt *select;
+ ResTarget *res;
+
+ n->target = $2;
+
+ select = makeNode(SelectStmt);
+ res = makeNode(ResTarget);
+
+ /* Create target list for implicit query */
+ res->name = NULL;
+ res->indirection = NIL;
+ res->val = (Node *) $4;
+ res->location = @4;
+
+ select->targetList = list_make1(res);
+ n->query = (Node *) select;
+
+ n->location = @2;
+ $$ = (Node *) n;
+ }
+ ;
+
+let_target:
+ ColId opt_indirection
+ {
+ $$ = list_make1(makeString($1));
+ if ($2)
+ $$ = list_concat($$,
+ check_indirection($2, yyscanner));
+ }
+ ;
+
/*****************************************************************************
*
* QUERY:
@@ -15681,6 +15863,7 @@ unreserved_keyword:
| LARGE_P
| LAST_P
| LEAKPROOF
+ | LET
| LEVEL
| LISTEN
| LOAD
@@ -15838,6 +16021,8 @@ unreserved_keyword:
| VALIDATE
| VALIDATOR
| VALUE_P
+ | VARIABLE
+ | VARIABLES
| VARYING
| VERSION_P
| VIEW
@@ -16245,6 +16430,7 @@ bare_label_keyword:
| LEAKPROOF
| LEAST
| LEFT
+ | LET
| LEVEL
| LIKE
| LISTEN
@@ -16444,6 +16630,8 @@ bare_label_keyword:
| VALUE_P
| VALUES
| VARCHAR
+ | VARIABLE
+ | VARIABLES
| VARIADIC
| VERBOSE
| VERSION_P
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 7d829a05a9..f3b384efbe 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -348,6 +348,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
Assert(false); /* can't happen */
break;
case EXPR_KIND_OTHER:
+ case EXPR_KIND_LET_TARGET:
/*
* Accept aggregate/grouping here; caller must throw error if
@@ -464,6 +465,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");
@@ -905,6 +907,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:
@@ -943,6 +946,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_CYCLE_MARK:
errkind = true;
break;
+ case EXPR_KIND_LET_TARGET:
+ err = _("window functions are not allowed in LET statement");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 2d1a477154..6aaaa5bb29 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"
@@ -36,6 +37,7 @@
#include "utils/date.h"
#include "utils/lsyscache.h"
#include "utils/timestamp.h"
+#include "utils/typcache.h"
#include "utils/xml.h"
/* GUC parameters */
@@ -82,7 +84,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,
Node *ltree, Node *rtree, int location);
static Node *make_nulltest_from_distinct(ParseState *pstate,
A_Expr *distincta, Node *arg);
-
+static Node *makeParamSchemaVariable(ParseState *pstate,
+ Oid varid, Oid typid, int32 typmod, Oid collid,
+ char *attrname, int location);
/*
* transformExpr -
@@ -277,6 +281,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
* processed it rather than passing it to transformExpr().
*/
case T_SetToDefault:
+ Assert(false);
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("DEFAULT is not allowed in this context"),
@@ -443,6 +448,9 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
char *relname = NULL;
char *colname = NULL;
ParseNamespaceItem *nsitem;
+ Oid varid = InvalidOid;
+ char *attrname = NULL;
+ bool not_unique;
int levels_up;
enum
{
@@ -504,6 +512,9 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_COPY_WHERE:
case EXPR_KIND_GENERATED_COLUMN:
case EXPR_KIND_CYCLE_MARK:
+ case EXPR_KIND_VARIABLE_DEFAULT:
+ case EXPR_KIND_LET_TARGET:
+
/* okay */
break;
@@ -761,6 +772,15 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
break;
}
+ varid = IdentifyVariable(cref->fields, &attrname, ¬_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
@@ -785,6 +805,78 @@ 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 cases with ambiguous references can be solved without
+ * raising an error. When there is an collision between column
+ * name (or label) and some schema variable name, and when we know
+ * attribute name, then we can ignore the collision when:
+ *
+ * a) variable is of scalar type (then indirection cannot be
+ * applied on this schema variable.
+ *
+ * b) when related variable has no field of attrname, then
+ * indirection cannot be applied on this schema variable.
+ */
+ 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.
*/
@@ -819,6 +911,74 @@ 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->paramvarid = varid;
+ param->paramtype = typid;
+ param->paramtypmod = typmod;
+ param->paramcollid = collid;
+
+ /*
+ * There are two access to schema variables - direct, used by simple
+ * plpgsql expressions, where there are not necessary to emulate
+ * stability. Buffered access is used elsewhere. We should to ensure
+ * stable values, and because schema variables are global, then we should
+ * to work with copied values instead direct access to variables. For
+ * direct access the varid is best for access. For buffered access we need
+ * to assign index to buffer - later, when we will know what variables are
+ * used. Now, we just remember, so we use schema variables.
+ */
+ pstate->p_hasSchemaVariables = true;
+
+ 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)
{
@@ -1721,6 +1881,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_VALUES:
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CYCLE_MARK:
+ case EXPR_KIND_LET_TARGET:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -1729,6 +1890,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:
@@ -3059,6 +3221,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";
@@ -3084,6 +3247,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "GENERATED AS";
case EXPR_KIND_CYCLE_MARK:
return "CYCLE";
+ case EXPR_KIND_LET_TARGET:
+ return "LET";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 3cec8de7da..3ee0083317 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2617,6 +2617,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:
@@ -2655,6 +2656,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, \
int location) case EXPR_KIND_CYCLE_MARK:
errkind = true;
break;
+ case EXPR_KIND_LET_TARGET:
+ 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 9ce3a0de96..a79e612129 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -89,7 +89,9 @@ transformTargetEntry(ParseState *pstate,
* through unmodified. (transformExpr will throw the appropriate
* error if we're disallowing it.)
*/
- if (exprKind == EXPR_KIND_UPDATE_SOURCE && IsA(node, SetToDefault))
+ if ((exprKind == EXPR_KIND_UPDATE_SOURCE ||
+ exprKind == EXPR_KIND_LET_TARGET)
+ && IsA(node, SetToDefault))
expr = node;
else
expr = transformExpr(pstate, node, exprKind);
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 875de7ba28..0f499712d8 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -61,7 +61,8 @@ raw_parser(const char *str, RawParseMode mode)
MODE_PLPGSQL_EXPR, /* RAW_PARSE_PLPGSQL_EXPR */
MODE_PLPGSQL_ASSIGN1, /* RAW_PARSE_PLPGSQL_ASSIGN1 */
MODE_PLPGSQL_ASSIGN2, /* RAW_PARSE_PLPGSQL_ASSIGN2 */
- MODE_PLPGSQL_ASSIGN3 /* RAW_PARSE_PLPGSQL_ASSIGN3 */
+ MODE_PLPGSQL_ASSIGN3, /* RAW_PARSE_PLPGSQL_ASSIGN3 */
+ MODE_PLPGSQL_LET /* RAW_PARSE_PLPGSQL_LET */
};
yyextra.have_lookahead = true;
diff --git a/src/backend/rewrite/rewriteHandler.c \
b/src/backend/rewrite/rewriteHandler.c index 9521e81100..e2c816bc4c 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -25,6 +25,7 @@
#include "access/table.h"
#include "catalog/dependency.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
#include "commands/trigger.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
@@ -3664,6 +3665,39 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
}
}
+ /*
+ * Rewrite SetToDefault by default expression of Let statement.
+ */
+ if (event == CMD_SELECT && OidIsValid(parsetree->resultVariable))
+ {
+ Oid resultVariable = parsetree->resultVariable;
+
+ if (list_length(parsetree->targetList) == 1 &&
+ IsA(((TargetEntry *) linitial(parsetree->targetList))->expr, SetToDefault))
+ {
+ Variable var;
+ TargetEntry *tle;
+ TargetEntry *newtle;
+ Expr *defexpr;
+
+ /* Read schema variable metadata with defexpr */
+ initVariable(&var, resultVariable, false, false);
+
+ if (var.has_defexpr)
+ defexpr = (Expr *) var.defexpr;
+ else
+ defexpr = (Expr *) makeNullConst(var.typid, var.typmod, var.collation);
+
+ tle = (TargetEntry *) linitial(parsetree->targetList);
+ newtle = makeTargetEntry(defexpr,
+ tle->resno,
+ pstrdup(tle->resname),
+ false);
+
+ parsetree->targetList = list_make1(newtle);
+ }
+ }
+
/*
* If the statement is an insert, update, or delete, adjust its targetlist
* as needed, and then fire INSERT/UPDATE/DELETE rules on it.
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e10f94904e..447c6f0abf 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -213,10 +213,10 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int \
rt_index, }
/*
- * For SELECT, 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.
+ * 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.
*/
get_policies_for_relation(rel, commandType, user_id, &permissive_policies,
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 1dfadfa8e1..c676cb19fc 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"
@@ -152,6 +153,9 @@ CreateDestReceiver(CommandDest dest)
case DestTupleQueue:
return CreateTupleQueueDestReceiver(NULL);
+
+ case DestVariable:
+ return CreateVariableDestReceiver();
}
/* should never get here */
@@ -207,6 +211,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool \
force_undecorated_o case DestSQLFunction:
case DestTransientRel:
case DestTupleQueue:
+ case DestVariable:
break;
}
}
@@ -252,6 +257,7 @@ NullCommand(CommandDest dest)
case DestSQLFunction:
case DestTransientRel:
case DestTupleQueue:
+ case DestVariable:
break;
}
}
@@ -295,6 +301,7 @@ ReadyForQuery(CommandDest dest)
case DestSQLFunction:
case DestTransientRel:
case DestTupleQueue:
+ case DestVariable:
break;
}
}
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 960f3fadce..6c2f393988 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -86,6 +86,9 @@ CreateQueryDesc(PlannedStmt *plannedstmt,
qd->queryEnv = queryEnv;
qd->instrument_options = instrument_options; /* instrumentation wanted? */
+ qd->num_schema_variables = 0;
+ qd->schema_variables = NULL;
+
/* null these fields until set by ExecutorStart */
qd->tupDesc = NULL;
qd->estate = NULL;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index bf085aa93b..f5f7f9ead7 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -48,6 +48,7 @@
#include "commands/proclang.h"
#include "commands/publicationcmds.h"
#include "commands/schemacmds.h"
+#include "commands/schema_variable.h"
#include "commands/seclabel.h"
#include "commands/sequence.h"
#include "commands/subscriptioncmds.h"
@@ -186,6 +187,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
case T_CreateRangeStmt:
case T_CreateRoleStmt:
case T_CreateSchemaStmt:
+ case T_CreateSchemaVarStmt:
case T_CreateSeqStmt:
case T_CreateStatsStmt:
case T_CreateStmt:
@@ -237,6 +239,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
case T_CallStmt:
case T_DoStmt:
+ case T_LetStmt:
{
/*
* Commands inside the DO block or the called procedure might
@@ -1061,6 +1064,11 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
}
+ case T_LetStmt:
+ ExecuteLetStmt(pstate, (LetStmt *) parsetree, params,
+ queryEnv, qc);
+ break;
+
default:
/* All other statement types have event trigger support */
ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -1385,6 +1393,10 @@ ProcessUtilitySlow(ParseState *pstate,
}
break;
+ case T_CreateSchemaVarStmt:
+ address = DefineSchemaVariable(pstate, (CreateSchemaVarStmt *) parsetree);
+ break;
+
/*
* ************* object creation / destruction **************
*/
@@ -2175,6 +2187,10 @@ UtilityContainsQuery(Node *parsetree)
return UtilityContainsQuery(qry->utilityStmt);
return qry;
+ case T_LetStmt:
+ qry = castNode(Query, ((LetStmt *) parsetree)->query);
+ return qry;
+
default:
return NULL;
}
@@ -2316,6 +2332,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
case OBJECT_STATISTIC_EXT:
tag = CMDTAG_ALTER_STATISTICS;
break;
+ case OBJECT_VARIABLE:
+ tag = CMDTAG_ALTER_VARIABLE;
+ break;
default:
tag = CMDTAG_UNKNOWN;
break;
@@ -2366,6 +2385,10 @@ CreateCommandTag(Node *parsetree)
tag = CMDTAG_SELECT;
break;
+ case T_LetStmt:
+ tag = CMDTAG_LET;
+ break;
+
/* utility statements --- same whether raw or cooked */
case T_TransactionStmt:
{
@@ -2620,6 +2643,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_STATISTIC_EXT:
tag = CMDTAG_DROP_STATISTICS;
break;
+ case OBJECT_VARIABLE:
+ tag = CMDTAG_DROP_VARIABLE;
+ break;
default:
tag = CMDTAG_UNKNOWN;
}
@@ -2908,6 +2934,9 @@ CreateCommandTag(Node *parsetree)
case DISCARD_SEQUENCES:
tag = CMDTAG_DISCARD_SEQUENCES;
break;
+ case DISCARD_VARIABLES:
+ tag = CMDTAG_DISCARD_VARIABLES;
+ break;
default:
tag = CMDTAG_UNKNOWN;
}
@@ -3192,6 +3221,10 @@ CreateCommandTag(Node *parsetree)
}
break;
+ case T_CreateSchemaVarStmt:
+ tag = CMDTAG_CREATE_VARIABLE;
+ break;
+
default:
elog(WARNING, "unrecognized node type: %d",
(int) nodeTag(parsetree));
@@ -3239,6 +3272,7 @@ GetCommandLogLevel(Node *parsetree)
break;
case T_PLAssignStmt:
+ case T_LetStmt:
lev = LOGSTMT_ALL;
break;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 67f8b29434..1b3c4bc41d 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -306,6 +306,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;
@@ -794,6 +800,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 */
@@ -888,6 +898,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);
}
@@ -1604,6 +1617,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),
@@ -1698,6 +1715,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 1bb25738a5..bfd860effa 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"
@@ -7915,6 +7916,14 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /* translate paramvarid to schema variable name */
+ if (param->paramkind == PARAM_VARIABLE)
+ {
+ appendStringInfo(context->buf, "%s",
+ schema_variable_get_name(param->paramvarid));
+ return;
+ }
+
/*
* If it's an external parameter, see if the outermost namespace provides
* function argument names.
diff --git a/src/backend/utils/cache/lsyscache.c \
b/src/backend/utils/cache/lsyscache.c index 4ebaa552a2..7a1797b1db 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
#include "catalog/pg_statistic.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "utils/array.h"
@@ -1860,6 +1861,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, Anum_pg_variable_oid,
+ 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 56870b46e4..b385e2e677 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -74,6 +74,7 @@
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
#include "lib/qunique.h"
#include "utils/catcache.h"
#include "utils/rel.h"
@@ -1014,6 +1015,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,
+ {
+ Anum_pg_variable_oid,
+ 0,
+ 0,
+ 0
+ },
+ 8
}
};
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 3dfe6e5825..3f60c7b365 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1910,15 +1910,19 @@ get_call_expr_arg_stable(Node *expr, int argnum)
arg = (Node *) list_nth(args, argnum);
/*
- * Either a true Const or an external Param will have a value that doesn't
- * change during the execution of the query. In future we might want to
- * consider other cases too, e.g. now().
+ * Either a true Const or an external Param or variable will have a value
+ * that doesn't change during the execution of the query. In future we
+ * might want to consider other cases too, e.g. now().
*/
if (IsA(arg, Const))
return true;
- if (IsA(arg, Param) &&
- ((Param *) arg)->paramkind == PARAM_EXTERN)
- return true;
+ if (IsA(arg, Param))
+ {
+ Param *p = (Param *) arg;
+
+ if (p->paramkind == PARAM_EXTERN || p->paramkind == PARAM_VARIABLE)
+ return true;
+ }
return false;
}
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index ecab0a9e4e..8c206b0200 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -263,7 +263,8 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading subscriptions");
getSubscriptions(fout);
- free(inhinfo); /* not needed any longer */
+ pg_log_info("reading variables");
+ getVariables(fout);
*numTablesPtr = numTables;
return tblinfo;
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 6af10a85a2..29082a0926 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -113,12 +113,14 @@ typedef struct _restoreOptions
int selFunction;
int selTrigger;
int selTable;
+ int selVariable;
SimpleStringList indexNames;
SimpleStringList functionNames;
SimpleStringList schemaNames;
SimpleStringList schemaExcludeNames;
SimpleStringList triggerNames;
SimpleStringList tableNames;
+ SimpleStringList variableNames;
int useDB;
ConnParams cparams; /* parameters to use if useDB */
diff --git a/src/bin/pg_dump/pg_backup_archiver.c \
b/src/bin/pg_dump/pg_backup_archiver.c index 2c4cfb9457..4141a21531 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2922,6 +2922,14 @@ _tocEntryRequired(TocEntry *te, teSection curSection, \
ArchiveHandle *AH)
!simple_string_list_member(&ropt->triggerNames, te->tag))
return 0;
}
+ else if (strcmp(te->desc, "VARIABLE") == 0)
+ {
+ if (!ropt->selVariable)
+ return 0;
+ if (ropt->variableNames.head != NULL &&
+ !simple_string_list_member(&ropt->variableNames, te->tag))
+ return 0;
+ }
else
return 0;
}
@@ -3405,6 +3413,7 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te)
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 ||
@@ -3597,7 +3606,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 d1842edde0..91bb0205f8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -279,6 +279,7 @@ static void dumpPolicy(Archive *fout, const PolicyInfo *polinfo);
static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo);
static void dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo);
static void dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo);
+static void dumpVariable(Archive *fout, const VariableInfo * varinfo);
static void dumpDatabase(Archive *AH);
static void dumpDatabaseConfig(Archive *AH, PQExpBuffer outbuf,
const char *dbname, Oid dboid);
@@ -4701,6 +4702,232 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer \
upgrade_query) return next_possible_free_oid;
}
+/*
+ * getVariables
+ * get information about variables
+ */
+void
+getVariables(Archive *fout)
+{
+ 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_vareoxaction;
+ int i_varisnotnull;
+ int i_varisimmutable;
+ int i,
+ ntups;
+
+ if (fout->remoteVersion < 130000)
+ 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",
+ "pip.initprivs", "'V'", dopt->binary_upgrade);
+
+ query = createPQExpBuffer();
+
+ resetPQExpBuffer(query);
+
+ /* Get the variables in current database. */
+ appendPQExpBuffer(query,
+ "SELECT v.tableoid, v.oid, v.varname, "
+ "v.vareoxaction, "
+ "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, "
+ "v.varisnotnull, "
+ "v.varisimmutable, "
+ "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_vareoxaction = PQfnumber(res, "vareoxaction");
+ 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");
+ i_varisnotnull = PQfnumber(res, "varisnotnull");
+ i_varisimmutable = PQfnumber(res, "varisimmutable");
+
+ varinfo = pg_malloc(ntups * sizeof(VariableInfo));
+
+ for (i = 0; i < ntups; i++)
+ {
+ TypeInfo *vtype;
+
+ varinfo[i].dobj.objType = DO_VARIABLE;
+ varinfo[i].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ varinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&varinfo[i].dobj);
+ varinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_varname));
+ varinfo[i].dobj.namespace =
+ findNamespace(atooid(PQgetvalue(res, i, i_varnamespace)));
+
+ varinfo[i].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].vareoxaction = pg_strdup(PQgetvalue(res, i, i_vareoxaction));
+
+ 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));
+
+ varinfo[i].varisnotnull = *(PQgetvalue(res, i, i_varisnotnull)) == 't';
+ varinfo[i].varisimmutable = *(PQgetvalue(res, i, i_varisimmutable)) == 't';
+
+ /* 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)
+ pg_log_warning("owner of variable \"%s\" appears to be invalid",
+ varinfo[i].dobj.name);
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(varinfo[i].dobj), fout);
+
+ vtype = findTypeByOid(varinfo[i].vartype);
+ addObjectDependency(&varinfo[i].dobj, vtype->dobj.dumpId);
+ }
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpVariable
+ * dump the definition of the given variables
+ */
+static void
+dumpVariable(Archive *fout, const VariableInfo * varinfo)
+{
+ DumpOptions *dopt = fout->dopt;
+
+ PQExpBuffer delq;
+ PQExpBuffer query;
+ const char *varname;
+ const char *vartypname;
+ const char *vardefexpr;
+ const char *vareoxaction;
+ const char *varisnotnull;
+ const char *varisimmutable;
+
+ /* 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;
+ vareoxaction = varinfo->vareoxaction;
+ varisnotnull = varinfo->varisnotnull ? " NOT NULL" : "";
+ varisimmutable = varinfo->varisimmutable ? "IMMUTABLE " : "";
+
+ appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
+ varname);
+
+ appendPQExpBuffer(query, "CREATE %sVARIABLE %s AS %s%s",
+ varisimmutable, varname, vartypname, varisnotnull);
+
+ if (vardefexpr)
+ appendPQExpBuffer(query, " DEFAULT %s",
+ vardefexpr);
+
+ if (strcmp(vareoxaction, "d") == 0)
+ appendPQExpBuffer(query, " ON COMMIT DROP");
+ else if (strcmp(vareoxaction, "r") == 0)
+ appendPQExpBuffer(query, " ON TRANSACTION END RESET");
+
+ appendPQExpBuffer(query, ";\n");
+
+ if (varinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
+ ArchiveEntry(fout, varinfo->dobj.catId, varinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = varinfo->dobj.name,
+ .namespace = varinfo->dobj.namespace->dobj.name,
+ .owner = varinfo->rolname,
+ .description = "VARIABLE",
+ .section = SECTION_PRE_DATA,
+ .createStmt = query->data,
+ .dropStmt = delq->data));
+
+ 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,
@@ -10338,6 +10565,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj)
case DO_SUBSCRIPTION:
dumpSubscription(fout, (const 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 */
@@ -18527,6 +18757,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_CONVERSION:
case DO_TABLE:
case DO_TABLE_ATTACH:
+ case DO_VARIABLE:
case DO_ATTRDEF:
case DO_PROCLANG:
case DO_CAST:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f9af14b793..17a1f90da8 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -52,6 +52,7 @@ typedef enum
DO_TABLE,
DO_TABLE_ATTACH,
DO_ATTRDEF,
+ DO_VARIABLE,
DO_INDEX,
DO_INDEX_ATTACH,
DO_STATSEXT,
@@ -659,6 +660,26 @@ 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 *vareoxaction;
+ char *vardefexpr;
+ char *varacl;
+ char *rvaracl;
+ char *initvaracl;
+ char *initrvaracl;
+ bool varisnotnull;
+ bool varisimmutable;
+} VariableInfo;
+
+
/*
* common utility functions
*/
@@ -741,5 +762,6 @@ extern void getPublicationNamespaces(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 9901d9e0ba..b49d5c6c62 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -66,6 +66,7 @@ enum dbObjectTypePriorities
PRIO_TABLE_ATTACH,
PRIO_DUMMY_TYPE,
PRIO_ATTRDEF,
+ PRIO_VARIABLE,
PRIO_BLOB,
PRIO_PRE_DATA_BOUNDARY, /* boundary! */
PRIO_TABLE_DATA,
@@ -107,6 +108,7 @@ static const int dbObjectTypePriority[] =
PRIO_TABLE, /* DO_TABLE */
PRIO_TABLE_ATTACH, /* DO_TABLE_ATTACH */
PRIO_ATTRDEF, /* DO_ATTRDEF */
+ PRIO_VARIABLE, /* DO_VARIABLE */
PRIO_INDEX, /* DO_INDEX */
PRIO_INDEX_ATTACH, /* DO_INDEX_ATTACH */
PRIO_STATSEXT, /* DO_STATSEXT */
@@ -1499,6 +1501,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/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 64aaa80eee..3f0af55d7d 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -102,6 +102,7 @@ main(int argc, char **argv)
{"trigger", 1, NULL, 'T'},
{"use-list", 1, NULL, 'L'},
{"username", 1, NULL, 'U'},
+ {"variable", 1, NULL, 'A'},
{"verbose", 0, NULL, 'v'},
{"single-transaction", 0, NULL, '1'},
@@ -149,7 +150,7 @@ main(int argc, char **argv)
}
}
- while ((c = getopt_long(argc, argv, "acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1",
+ while ((c = getopt_long(argc, argv, \
"A:acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1", cmdopts, NULL)) != -1)
{
switch (c)
@@ -157,6 +158,11 @@ main(int argc, char **argv)
case 'a': /* Dump data only */
opts->dataOnly = 1;
break;
+ case 'A': /* vAriable */
+ opts->selTypes = 1;
+ opts->selVariable = 1;
+ simple_string_list_append(&opts->variableNames, optarg);
+ break;
case 'c': /* clean (i.e., drop) schema prior to create */
opts->dropSchema = 1;
break;
@@ -461,6 +467,7 @@ usage(const char *progname)
printf(_("\nOptions controlling the restore:\n"));
printf(_(" -a, --data-only restore only the data, no schema\n"));
+ printf(_(" -A, --variable=NAME restore named schema variable\n"));
printf(_(" -c, --clean clean (drop) database objects before \
recreating\n")); printf(_(" -C, --create create the target \
database\n")); printf(_(" -e, --exit-on-error exit on error, default is to \
continue\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d293f52b05..1595dfc513 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2995,6 +2995,30 @@ my %tests = (
},
},
+ 'CREATE VARIABLE test_variable' => {
+ all_runs => 1,
+ catch_all => 'CREATE ... commands',
+ create_order => 61,
+ create_sql => 'CREATE VARIABLE dump_test.variable1 AS integer DEFAULT 0;',
+ regexp => qr/^
+ \QCREATE VARIABLE dump_test.variable1 AS integer DEFAULT 0;\E/xm,
+ like =>
+ { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+ unlike => { exclude_dump_test_schema => 1, },
+ },
+
+ 'CREATE IMMUTABLE VARIABLE test_variable' => {
+ all_runs => 1,
+ catch_all => 'CREATE ... commands',
+ create_order => 61,
+ create_sql => 'CREATE IMMUTABLE VARIABLE dump_test.variable2 AS integer DEFAULT \
0;', + regexp => qr/^
+ \QCREATE IMMUTABLE VARIABLE dump_test.variable2 AS integer DEFAULT 0;\E/xm,
+ like =>
+ { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+ unlike => { exclude_dump_test_schema => 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 49d4c0e3ce..3841b7e3e4 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -929,6 +929,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 006661412e..625b63ddcd 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5154,6 +5154,88 @@ 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, 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"
+ " NOT v.varisnotnull as \"%s\",\n"
+ " NOT v.varisimmutable as \"%s\",\n"
+ " pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\",\n"
+ " pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n"
+ " CASE v.vareoxaction\n"
+ " WHEN 'd' THEN 'ON COMMIT DROP'\n"
+ " WHEN 'r' THEN 'ON TRANSACTION END RESET' END as \"%s\"\n",
+ gettext_noop("Schema"),
+ gettext_noop("Name"),
+ gettext_noop("Type"),
+ gettext_noop("Is nullable"),
+ gettext_noop("Is mutable"),
+ gettext_noop("Default"),
+ gettext_noop("Owner"),
+ gettext_noop("Transactional end action"));
+
+ 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)
+ pg_log_error("Did not find any schema variable named \"%s\".",
+ pattern);
+ else
+ pg_log_error("Did not find any schema variables.");
+ }
+ else
+ {
+ myopt.nullPrint = NULL;
+ myopt.title = _("List of variables");
+ myopt.translate_header = true;
+ myopt.translate_columns = translate_columns;
+ myopt.n_translate_columns = lengthof(translate_columns);
+
+ printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ }
+
+ PQclear(res);
+ return true;
+}
/*
* \dFp
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 71b320f1fc..a86fccc0d7 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -139,5 +139,7 @@ extern bool listOpFamilyOperators(const char \
*accessMethod_pattern, extern bool listOpFamilyFunctions(const char \
*access_method_pattern, const char *family_pattern, bool verbose);
+/* \dV */
+extern bool listVariables(const char *pattern, bool varbose);
#endif /* DESCRIBE_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index db12a8b2f3..a6a3b2eba2 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -265,6 +265,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, _(" \\dX [PATTERN] list extended statistics\n"));
fprintf(output, _(" \\dy[+] [PATTERN] list event triggers\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8e01f54500..7045be20bf 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -628,6 +628,13 @@ static const SchemaQuery Query_for_list_of_collations = {
.result = "pg_catalog.quote_ident(c.collname)",
};
+static const SchemaQuery Query_for_list_of_variables = {
+ .min_server_version = 120000,
+ .catname = "pg_catalog.pg_variable v",
+ .viscondition = "pg_catalog.pg_variable_is_visible(v.oid)",
+ .namespace = "v.varnamespace",
+ .result = "pg_catalog.quote_ident(v.varname)",
+};
/*
* Queries to get lists of names of various kinds of things, possibly
@@ -1097,6 +1104,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 */
};
@@ -1495,8 +1503,8 @@ psql_completion(const char *text, int start, int end)
"ABORT", "ALTER", "ANALYZE", "BEGIN", "CALL", "CHECKPOINT", "CLOSE", "CLUSTER",
"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
- "FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LISTEN", "LOAD", \
"LOCK",
- "MOVE", "NOTIFY", "PREPARE",
+ "FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LET", "LISTEN", "LOAD",
+ "LOCK", "MOVE", "NOTIFY", "PREPARE",
"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
"RESET", "REVOKE", "ROLLBACK",
"SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
@@ -1515,7 +1523,7 @@ psql_completion(const char *text, int start, int end)
"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
"\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\dP", "\\dPi", "\\dPt",
"\\drds", "\\dRs", "\\dRp", "\\ds",
- "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dX", "\\dy",
+ "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dX", "\\dy", "\\dV",
"\\echo", "\\edit", "\\ef", "\\elif", "\\else", "\\encoding",
"\\endif", "\\errverbose", "\\ev",
"\\f",
@@ -1926,6 +1934,9 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
else if (Matches("ALTER", "SYSTEM", "SET", MatchAny))
COMPLETE_WITH("TO");
+ /* ALTER VARIABLE <name> */
+ else if (Matches("ALTER", "VARIABLE", MatchAny))
+ COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA");
/* ALTER VIEW <name> */
else if (Matches("ALTER", "VIEW", MatchAny))
COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME",
@@ -2780,7 +2791,7 @@ psql_completion(const char *text, int start, int end)
/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
- COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+ COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW", "VARIABLE");
/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
else if (TailMatches("CREATE", "UNLOGGED"))
COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
@@ -3070,6 +3081,14 @@ psql_completion(const char *text, int start, int end)
else if (TailMatches("=", MatchAnyExcept("*)")))
COMPLETE_WITH(",", ")");
}
+/* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+ /* Complete CREATE VARIABLE <name> with AS */
+ else if (TailMatches("CREATE", "VARIABLE", MatchAny) ||
+ TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny))
+ COMPLETE_WITH("AS");
+ else if (TailMatches("VARIABLE", MatchAny, "AS"))
+ /* Complete CREATE VARIABLE <name> with AS types */
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS */
@@ -3184,7 +3203,7 @@ psql_completion(const char *text, int start, int end)
/* DISCARD */
else if (Matches("DISCARD"))
- COMPLETE_WITH("ALL", "PLANS", "SEQUENCES", "TEMP");
+ COMPLETE_WITH("ALL", "PLANS", "SEQUENCES", "TEMP", "VARIABLES");
/* DO */
else if (Matches("DO"))
@@ -3290,6 +3309,12 @@ psql_completion(const char *text, int start, int end)
else if (Matches("DROP", "RULE", MatchAny, "ON", MatchAny))
COMPLETE_WITH("CASCADE", "RESTRICT");
+ /* DROP VARIABLE */
+ else if (Matches("DROP", "VARIABLE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
+ else if (Matches("DROP", "VARIABLE", MatchAny))
+ COMPLETE_WITH("CASCADE", "RESTRICT");
+
/* EXECUTE */
else if (Matches("EXECUTE"))
COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -3462,6 +3487,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'"
@@ -3475,14 +3501,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 (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL"))
COMPLETE_WITH("FUNCTIONS IN SCHEMA",
"PROCEDURES IN SCHEMA",
"ROUTINES IN SCHEMA",
"SEQUENCES IN SCHEMA",
- "TABLES IN SCHEMA");
+ "TABLES IN SCHEMA",
+ "VARIABLES IN SCHEMA");
else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
COMPLETE_WITH("DATA WRAPPER", "SERVER");
@@ -3516,6 +3544,8 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
else if (TailMatches("TYPE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+ else if (TailMatches("VARIABLE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
else if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny))
COMPLETE_WITH("TO");
else
@@ -3688,7 +3718,7 @@ psql_completion(const char *text, int start, int end)
/* PREPARE xx AS */
else if (Matches("PREPARE", MatchAny, "AS"))
- COMPLETE_WITH("SELECT", "UPDATE", "INSERT INTO", "DELETE FROM");
+ COMPLETE_WITH("SELECT", "UPDATE", "INSERT INTO", "DELETE FROM", "LET");
/*
* PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -3962,6 +3992,14 @@ psql_completion(const char *text, int start, int end)
else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
COMPLETE_WITH("=");
+/* LET --- can be inside EXPLAIN, PREPARE etc */
+ /* If prev. word is LET suggest a list of variables */
+ else if (TailMatches("LET"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
+ /* Complete LET <variable> with "=" */
+ else if (TailMatches("LET", MatchAny))
+ COMPLETE_WITH("=");
+
/* USER MAPPING */
else if (Matches("ALTER|CREATE|DROP", "USER", "MAPPING"))
COMPLETE_WITH("FOR");
@@ -4126,6 +4164,8 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_QUERY(Query_for_list_of_roles);
else if (TailMatchesCS("\\dv*"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+ else if (TailMatchesCS("\\dV*"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
else if (TailMatchesCS("\\dx*"))
COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
else if (TailMatchesCS("\\dX*"))
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 3eca295ff4..347008a606 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -125,10 +125,11 @@ typedef enum ObjectClass
OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */
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
+#define LAST_OCLASS OCLASS_VARIABLE
/* flag bits for performDeletion/performMultipleDeletions: */
#define PERFORM_DELETION_INTERNAL 0x0001 /* internal action */
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index b98f284356..7058b1acb6 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -96,6 +96,8 @@ extern Oid TypenameGetTypid(const char *typname);
extern Oid TypenameGetTypidExtended(const char *typname, bool temp_ok);
extern bool TypeIsVisible(Oid typid);
+extern bool VariableIsVisible(Oid varid);
+
extern FuncCandidateList FuncnameGetCandidates(List *names,
int nargs, List *argnames,
bool expand_variadic,
@@ -164,6 +166,10 @@ extern void SetTempNamespaceState(Oid tempNamespaceId,
Oid tempToastNamespaceId);
extern void ResetTempTableNamespace(void);
+extern List *NamesFromList(List *names);
+extern Oid LookupVariable(const char *nspname, const char *varname, bool \
missing_ok); +extern Oid IdentifyVariable(List *names, char **attrname, bool \
*not_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 eb72dd6293..2ca72a0dcd 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -66,6 +66,7 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_default_acl_oid_index, 828, \
DefaultAclOidIndexId, o #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 d068d6532e..28d1f9af17 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6242,6 +6242,9 @@
proname => 'pg_collation_is_visible', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'oid',
prosrc => 'pg_collation_is_visible' },
+{ oid => '9221', 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..7e53880a3c
--- /dev/null
+++ b/src/include/catalog/pg_variable.h
@@ -0,0 +1,117 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.h
+ * definition of schema variables system catalog (pg_variables)
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_variable.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_VARIABLE_H
+#define PG_VARIABLE_H
+
+#include "catalog/genbki.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_variable_d.h"
+#include "utils/acl.h"
+
+/* ----------------
+ * pg_variable definition. cpp turns this into
+ * typedef struct FormData_pg_variable
+ * ----------------
+ */
+CATALOG(pg_variable,9222,VariableRelationId)
+{
+ Oid oid; /* oid */
+ 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 */
+ bool varisnotnull; /* Don't allow NULL */
+ bool varisimmutable; /* Don't allow changes */
+ char vareoxaction; /* action on transaction end */
+
+#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;
+
+typedef enum VariableEOXActionCodes
+{
+ VARIABLE_EOX_CODE_NOOP = 'n', /* NOOP */
+ VARIABLE_EOX_CODE_DROP = 'd', /* ON COMMIT DROP */
+ VARIABLE_EOX_CODE_RESET = 'r', /* ON COMMIT RESET */
+} VariableEOXActionCodes;
+
+/* ----------------
+ * Form_pg_variable corresponds to a pointer to a tuple with
+ * the format of pg_variable relation.
+ * ----------------
+ */
+typedef FormData_pg_variable * Form_pg_variable;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_variable_oid_index, 9223, VariableOidIndexId, on \
pg_variable using btree(oid oid_ops)); +#define VariableObjectIndexId 9223
+
+DECLARE_UNIQUE_INDEX(pg_variable_varname_nsp_index, 9224, VariableNameNspIndexId, \
on pg_variable using btree(varname name_ops, varnamespace oid_ops)); +#define \
VariableNameNspIndexId 9224 +
+typedef struct Variable
+{
+ Oid oid;
+ char *name;
+ Oid namespace;
+ Oid typid;
+ int32 typmod;
+ Oid owner;
+ Oid collation;
+ bool is_not_null;
+ bool is_immutable;
+ VariableEOXAction eoxaction;
+ bool has_defexpr;
+ 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 void initVariable(Variable *var,
+ Oid varid,
+ bool missing_ok,
+ bool fast_only);
+extern ObjectAddress VariableCreate(const char *varName,
+ Oid varNamespace,
+ Oid varType,
+ int32 varTypmod,
+ Oid varOwner,
+ Oid varCollation,
+ Node *varDefexpr,
+ VariableEOXAction eoxaction,
+ bool is_not_null,
+ bool if_not_exists,
+ bool is_immutable);
+
+#endif /* PG_VARIABLE_H */
diff --git a/src/include/commands/schema_variable.h \
b/src/include/commands/schema_variable.h new file mode 100644
index 0000000000..5350829669
--- /dev/null
+++ b/src/include/commands/schema_variable.h
@@ -0,0 +1,44 @@
+/*-------------------------------------------------------------------------
+ *
+ * schemavariable.h
+ * prototypes for schemavariable.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "tcop/cmdtag.h"
+#include "utils/queryenvironment.h"
+
+extern void ResetSchemaVariableCache(void);
+
+extern void RemoveSchemaVariable(Oid varid);
+extern ObjectAddress DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt * \
stmt); +
+extern Datum GetSchemaVariable(Oid varid, bool *isNull, Oid expected_typid, bool \
copy); +extern Datum CopySchemaVariable(Oid varid, bool *isNull, Oid *typid);
+extern void SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid);
+extern void SetSchemaVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, \
Oid typid); +
+extern void ExecuteLetStmt(ParseState *pstate, LetStmt *stmt, ParamListInfo params,
+ QueryEnvironment *queryEnv, QueryCompletion *qc);
+
+extern void register_variable_on_commit_action(Oid varid, VariableEOXAction action);
+
+extern void AtPreEOXact_SchemaVariable_on_commit_actions(bool isCommit);
+extern void AtEOXact_SchemaVariable_on_commit_actions(bool isCommit);
+
+#endif
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 6a24341faa..123a18f298 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -158,6 +158,7 @@ typedef enum ExprEvalOp
EEOP_PARAM_EXEC,
EEOP_PARAM_EXTERN,
EEOP_PARAM_CALLBACK,
+ EEOP_PARAM_VARIABLE,
/* return CaseTestExpr value */
EEOP_CASE_TESTVAL,
@@ -380,6 +381,13 @@ typedef struct ExprEvalStep
Oid paramtype; /* OID of parameter's datatype */
} param;
+ /* for EEOP_PARAM_VARIABLE */
+ struct
+ {
+ Oid varid; /* OID of assigned variable */
+ Oid vartype; /* OID of parameter's datatype */
+ } vparam;
+
/* for EEOP_PARAM_CALLBACK */
struct
{
@@ -736,6 +744,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamVariable(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
extern void 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/execdesc.h b/src/include/executor/execdesc.h
index 017ad87117..c85e7ee89d 100644
--- a/src/include/executor/execdesc.h
+++ b/src/include/executor/execdesc.h
@@ -48,6 +48,10 @@ typedef struct QueryDesc
EState *estate; /* executor's query-wide state */
PlanState *planstate; /* tree of per-plan-node state */
+ /* reference to schema variables buffer */
+ int num_schema_variables;
+ SchemaVariableValue *schema_variables;
+
/* This field is set by ExecutorRun */
bool already_executed; /* true if previously executed */
diff --git a/src/include/executor/svariableReceiver.h \
b/src/include/executor/svariableReceiver.h new file mode 100644
index 0000000000..5305936547
--- /dev/null
+++ b/src/include/executor/svariableReceiver.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.h
+ * prototypes for svariableReceiver.c
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 2e8cbee69f..e0776ac489 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -544,6 +544,18 @@ typedef struct AsyncRequest
* tuples) */
} AsyncRequest;
+/* ----------------
+ * SchemaVariableValue
+ * ----------------
+ */
+typedef struct SchemaVariableValue
+{
+ Oid varid;
+ Oid typid;
+ bool isnull;
+ Datum value;
+} SchemaVariableValue;
+
/* ----------------
* EState information
*
@@ -595,6 +607,13 @@ typedef struct EState
ParamListInfo es_param_list_info; /* values of external params */
ParamExecData *es_param_exec_vals; /* values of internal params */
+ /* Variables info: */
+ /* number of used schema variables */
+ int es_num_schema_variables;
+
+ /* array of copied values of schema variables */
+ SchemaVariableValue *es_schema_variables;
+
QueryEnvironment *es_queryEnv; /* query environment */
/* Other working state: */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7c657c1241..14bbe45624 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -355,6 +355,7 @@ typedef enum NodeTag
T_CreateTableAsStmt,
T_CreateSeqStmt,
T_AlterSeqStmt,
+ T_CreateSchemaVarStmt,
T_VariableSetStmt,
T_VariableShowStmt,
T_DiscardStmt,
@@ -428,6 +429,7 @@ typedef enum NodeTag
T_AlterCollationStmt,
T_CallStmt,
T_AlterStatsStmt,
+ T_LetStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 49123e28a4..795bbc1170 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -92,7 +92,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
@@ -129,7 +131,7 @@ typedef struct Query
int resultRelation; /* rtable index of target relation for
* INSERT/UPDATE/DELETE; 0 for SELECT */
-
+ Oid resultVariable; /* target variable of LET statement */
bool hasAggs; /* has aggregates in tlist or havingQual */
bool hasWindowFuncs; /* has window functions in tlist */
bool hasTargetSRFs; /* has set-returning functions in tlist */
@@ -139,6 +141,7 @@ typedef struct Query
bool hasModifyingCTE; /* has INSERT/UPDATE/DELETE in WITH */
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
+ bool hasSchemaVariables; /* uses schema variables */
bool isReturn; /* is a RETURN statement */
@@ -1627,6 +1630,20 @@ typedef struct UpdateStmt
WithClause *withClause; /* WITH clause */
} UpdateStmt;
+/* ----------------------
+ * Let Statement
+ * ----------------------
+ */
+typedef struct LetStmt
+{
+ NodeTag type;
+ List *target; /* target variable */
+ Node *query; /* source expression */
+ bool plpgsql_mode; /* true, when command will be executed (parsed)
+ * by plpgsql runtime */
+ int location;
+} LetStmt;
+
/* ----------------------
* Select Statement
*
@@ -1836,6 +1853,7 @@ typedef enum ObjectType
OBJECT_TSTEMPLATE,
OBJECT_TYPE,
OBJECT_USER_MAPPING,
+ OBJECT_VARIABLE,
OBJECT_VIEW
} ObjectType;
@@ -2671,6 +2689,23 @@ 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 */
+ VariableEOXAction eoxaction; /* on commit action */
+ bool if_not_exists; /* do nothing if it already exists */
+ bool is_not_null; /* Disallow nulls */
+ bool is_immutable; /* Don't allow changes */
+} CreateSchemaVarStmt;
+
/* ----------------------
* Create {Aggregate|Operator|Type} Statement
* ----------------------
@@ -3444,7 +3479,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/pathnodes.h b/src/include/nodes/pathnodes.h
index 2a53a6e344..528fc118bc 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -130,6 +130,8 @@ typedef struct PlannerGlobal
char maxParallelHazard; /* worst PROPARALLEL hazard level */
PartitionDirectory partition_directory; /* partition descriptors */
+
+ List *schemaVariables; /* list of used schema variables */
} PlannerGlobal;
/* macro for fetching the Plan associated with a SubPlan node */
@@ -350,6 +352,7 @@ struct PlannerInfo
* pseudoconstant = true */
bool hasAlternativeSubPlans; /* true if we've made any of those */
bool hasRecursion; /* true if planning a recursive WITH item */
+ bool hasSchemaVariables; /* true if schema variables was used */
/*
* Information about aggregates. Filled by preprocess_aggrefs().
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 01a246d50e..c9eafd6fcf 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) */
@@ -85,6 +85,8 @@ typedef struct PlannedStmt
Node *utilityStmt; /* non-null if this is utility stmt */
+ List *schemaVariables; /* list of OIDs for PARAM_VARIABLE Params */
+
/* statement location in source string (copied from Query) */
int stmt_location; /* start location, or -1 if unknown */
int stmt_len; /* length in bytes; 0 means "rest of string" */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 433437643e..dc7c2008b0 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -43,7 +43,10 @@ typedef struct Alias
List *colnames; /* optional list of column aliases */
} Alias;
-/* What to do at commit time for temporary relations */
+/*
+ * What to do at commit time for temporary relations or
+ * persistent/temporary variable.
+ */
typedef enum OnCommitAction
{
ONCOMMIT_NOOP, /* No ON COMMIT clause (do nothing) */
@@ -52,6 +55,13 @@ typedef enum OnCommitAction
ONCOMMIT_DROP /* ON COMMIT DROP */
} OnCommitAction;
+typedef enum VariableEOXAction
+{
+ VARIABLE_EOX_NOOP, /* Do nothing */
+ VARIABLE_EOX_DROP, /* ON TRANSACTION END DROP */
+ VARIABLE_EOX_RESET /* ON TRANSACTION END RESET */
+} VariableEOXAction;
+
/*
* RangeVar - range variable, used in FROM clauses
*
@@ -252,13 +262,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
@@ -269,6 +283,7 @@ typedef struct Param
Oid paramtype; /* pg_type OID of parameter's datatype */
int32 paramtypmod; /* typmod value, if known */
Oid paramcollid; /* OID of collation, or InvalidOid if none */
+ Oid paramvarid; /* OID of schema variable if it is used */
int location; /* token location, or -1 if unknown */
} Param;
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index bf1adfc52a..4cbcbeba10 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -116,4 +116,6 @@ extern void record_plan_function_dependency(PlannerInfo *root, \
Oid funcid); extern void record_plan_type_dependency(PlannerInfo *root, Oid typid);
extern bool extract_query_dependencies_walker(Node *node, PlannerInfo *root);
+extern void pull_up_has_schema_variables(PlannerInfo *root);
+
#endif /* PLANMAIN_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..82f3bd2eb0 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -237,6 +237,7 @@ PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("let", LET, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD, AS_LABEL)
@@ -450,6 +451,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, \
BARE_LABEL) PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("variables", VARIABLES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index ee179082ce..aad0953ddb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -80,6 +80,8 @@ typedef enum ParseExprKind
EXPR_KIND_COPY_WHERE, /* WHERE condition in COPY FROM */
EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */
EXPR_KIND_CYCLE_MARK, /* cycle mark value */
+ EXPR_KIND_VARIABLE_DEFAULT, /* default value for schema variable */
+ EXPR_KIND_LET_TARGET /* LET assignment (should be same like UPDATE) */
} ParseExprKind;
@@ -210,6 +212,7 @@ struct ParseState
bool p_hasTargetSRFs;
bool p_hasSubLinks;
bool p_hasModifyingCTE;
+ bool p_hasSchemaVariables;
Node *p_last_srf; /* most recent set-returning func/op found */
diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
index 853b0f1606..587112e526 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -33,6 +33,9 @@
* RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement,
* and return a one-element List containing a RawStmt node. "n"
* gives the number of dotted names comprising the target ColumnRef.
+ *
+ * RAW_PARSE_PLPGSQL_LET: parse a LET statement, and return a
+ * one-element List containing a RawStmt node.
*/
typedef enum
{
@@ -41,7 +44,8 @@ typedef enum
RAW_PARSE_PLPGSQL_EXPR,
RAW_PARSE_PLPGSQL_ASSIGN1,
RAW_PARSE_PLPGSQL_ASSIGN2,
- RAW_PARSE_PLPGSQL_ASSIGN3
+ RAW_PARSE_PLPGSQL_ASSIGN3,
+ RAW_PARSE_PLPGSQL_LET
} RawParseMode;
/* Values for the backslash_quote GUC */
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 9ba24d4ca9..8a81e62849 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -68,6 +68,7 @@ PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, \
false) PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false)
PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_VARIABLE, "ALTER VARIABLE", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false)
PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false)
PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false)
@@ -123,6 +124,7 @@ PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, \
false, false) PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_VARIABLE, "CREATE VARIABLE", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false)
PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false)
PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false)
@@ -133,6 +135,7 @@ PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false)
PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false)
PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false)
PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_VARIABLES, "DISCARD VARIABLES", false, false, false)
PG_CMDTAG(CMDTAG_DO, "DO", false, false, false)
PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
@@ -175,6 +178,7 @@ PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, \
false) PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false)
PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false)
PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_VARIABLE, "DROP VARIABLE", true, false, false)
PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false)
PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false)
PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false)
@@ -183,6 +187,7 @@ PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
+PG_CMDTAG(CMDTAG_LET, "LET", false, false, false)
PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 88a7e59de5..e2f348eb33 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -97,7 +97,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 af771c901d..84f670d7f4 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -146,9 +146,11 @@ typedef struct 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
@@ -165,6 +167,7 @@ typedef struct ArrayType Acl;
#define ACL_ALL_RIGHTS_SCHEMA (ACL_USAGE|ACL_CREATE)
#define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE)
#define ACL_ALL_RIGHTS_TYPE (ACL_USAGE)
+#define ACL_ALL_RIGHTS_VARIABLE (ACL_READ|ACL_WRITE)
/* operation codes for pg_*_aclmask */
typedef enum
@@ -259,7 +262,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);
extern AclResult pg_attribute_aclcheck_ext(Oid table_oid, AttrNumber attnum,
@@ -280,6 +284,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);
@@ -316,6 +321,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 77871aaefc..af645a6818 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -132,6 +132,7 @@ extern char get_func_prokind(Oid funcid);
extern bool get_func_leakproof(Oid funcid);
extern RegProcedure get_func_support(Oid funcid);
extern Oid get_relname_relid(const char *relname, Oid relnamespace);
+extern Oid get_varname_varid(const char *varname, Oid varnamespace);
extern char *get_rel_name(Oid relid);
extern Oid get_rel_namespace(Oid relid);
extern Oid get_rel_type_id(Oid relid);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index c8cfbc30f6..d6f8611482 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -111,9 +111,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 0e1cfa3df6..3aa46dc431 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -24,6 +24,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
+#include "commands/schema_variable.h"
#include "executor/execExpr.h"
#include "executor/spi.h"
#include "executor/tstoreReceiver.h"
@@ -318,6 +319,8 @@ static int exec_stmt_commit(PLpgSQL_execstate *estate,
PLpgSQL_stmt_commit *stmt);
static int exec_stmt_rollback(PLpgSQL_execstate *estate,
PLpgSQL_stmt_rollback *stmt);
+static int exec_stmt_let(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_let *let);
static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
PLpgSQL_function *func,
@@ -2087,6 +2090,10 @@ exec_stmts(PLpgSQL_execstate *estate, List *stmts)
rc = exec_stmt_rollback(estate, (PLpgSQL_stmt_rollback *) stmt);
break;
+ case PLPGSQL_STMT_LET:
+ rc = exec_stmt_let(estate, (PLpgSQL_stmt_let *) stmt);
+ break;
+
default:
/* point err_stmt to parent, since this one seems corrupt */
estate->err_stmt = save_estmt;
@@ -4933,6 +4940,54 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, \
PLpgSQL_stmt_rollback *stmt) return PLPGSQL_RC_OK;
}
+/* ----------
+ * exec_stmt_let Evaluate an expression and
+ * put the result into a schema variable.
+ * ----------
+ */
+static int
+exec_stmt_let(PLpgSQL_execstate *estate, PLpgSQL_stmt_let *stmt)
+{
+ bool isNull;
+ Oid valtype;
+ int32 valtypmod;
+ Datum value;
+ Oid varid;
+
+ List *plansources;
+ CachedPlanSource *plansource;
+
+ value = exec_eval_expr(estate,
+ stmt->expr,
+ &isNull,
+ &valtype,
+ &valtypmod);
+
+ /*
+ * Oid of target schema variable is stored in Query structure.
+ * It is safer to read this value directly from plan, then to
+ * hold this value in plpgsql context, because I don't need to
+ * solve invalidation of this cached value. Next operations
+ * are read only without any allocations, so we can expect so
+ * taking varid from Query should be fast.
+ */
+ plansources = SPI_plan_get_plan_sources(stmt->expr->plan);
+ if (list_length(plansources) != 1)
+ elog(ERROR, "unexpected length of plansources of query for LET statement");
+
+ plansource = (CachedPlanSource *) linitial(plansources);
+ if (list_length(plansource->query_list) != 1)
+ elog(ERROR, "unexpected length of plansource of query for LET statement");
+
+ varid = linitial_node(Query, plansource->query_list)->resultVariable;
+ if (!OidIsValid(varid))
+ elog(ERROR, "oid of target schema variable is not valid");
+
+ SetSchemaVariableWithSecurityCheck(varid, value, isNull, valtype);
+
+ return PLPGSQL_RC_OK;
+}
+
/* ----------
* exec_assign_expr Put an expression's result into a variable.
* ----------
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index e0863acb3d..265f93fb7b 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -288,6 +288,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
return "COMMIT";
case PLPGSQL_STMT_ROLLBACK:
return "ROLLBACK";
+ case PLPGSQL_STMT_LET:
+ return "LET";
}
return "unknown";
@@ -368,6 +370,7 @@ static void free_perform(PLpgSQL_stmt_perform *stmt);
static void free_call(PLpgSQL_stmt_call *stmt);
static void free_commit(PLpgSQL_stmt_commit *stmt);
static void free_rollback(PLpgSQL_stmt_rollback *stmt);
+static void free_let(PLpgSQL_stmt_let *stmt);
static void free_expr(PLpgSQL_expr *expr);
@@ -457,6 +460,9 @@ free_stmt(PLpgSQL_stmt *stmt)
case PLPGSQL_STMT_ROLLBACK:
free_rollback((PLpgSQL_stmt_rollback *) stmt);
break;
+ case PLPGSQL_STMT_LET:
+ free_let((PLpgSQL_stmt_let *) stmt);
+ break;
default:
elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
break;
@@ -711,6 +717,12 @@ free_getdiag(PLpgSQL_stmt_getdiag *stmt)
{
}
+static void
+free_let(PLpgSQL_stmt_let *stmt)
+{
+ free_expr(stmt->expr);
+}
+
static void
free_expr(PLpgSQL_expr *expr)
{
@@ -813,6 +825,7 @@ static void dump_perform(PLpgSQL_stmt_perform *stmt);
static void dump_call(PLpgSQL_stmt_call *stmt);
static void dump_commit(PLpgSQL_stmt_commit *stmt);
static void dump_rollback(PLpgSQL_stmt_rollback *stmt);
+static void dump_let(PLpgSQL_stmt_let *stmt);
static void dump_expr(PLpgSQL_expr *expr);
@@ -912,6 +925,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
case PLPGSQL_STMT_ROLLBACK:
dump_rollback((PLpgSQL_stmt_rollback *) stmt);
break;
+ case PLPGSQL_STMT_LET:
+ dump_let((PLpgSQL_stmt_let *) stmt);
+ break;
default:
elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
break;
@@ -1588,6 +1604,14 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
printf("\n");
}
+static void
+dump_let(PLpgSQL_stmt_let *stmt)
+{
+ dump_ind();
+ dump_expr(stmt->expr);
+ printf("\n");
+}
+
static void
dump_expr(PLpgSQL_expr *expr)
{
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 0f6a5b30b1..14845e64a2 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -197,7 +197,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <stmt> stmt_return stmt_raise stmt_assert stmt_execsql
%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_call stmt_getdiag
%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
-%type <stmt> stmt_commit stmt_rollback
+%type <stmt> stmt_commit stmt_rollback stmt_let
%type <stmt> stmt_case stmt_foreach_a
%type <list> proc_exceptions
@@ -304,6 +304,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%token <keyword> K_INTO
%token <keyword> K_IS
%token <keyword> K_LAST
+%token <keyword> K_LET
%token <keyword> K_LOG
%token <keyword> K_LOOP
%token <keyword> K_MESSAGE
@@ -897,6 +898,8 @@ proc_stmt : pl_block ';'
{ $$ = $1; }
| stmt_rollback
{ $$ = $1; }
+ | stmt_let
+ { $$ = $1; }
;
stmt_perform : K_PERFORM
@@ -1013,6 +1016,29 @@ stmt_assign : T_DATUM
}
;
+stmt_let : K_LET
+ {
+ PLpgSQL_stmt_let *new;
+ RawParseMode pmode;
+
+ pmode = RAW_PARSE_PLPGSQL_LET;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_let));
+ new->cmd_type = PLPGSQL_STMT_LET;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+
+ /* Push back the head name to include it in the stmt */
+ plpgsql_push_back_token(K_LET);
+ new->expr = read_sql_construct(';', 0, 0, ";",
+ pmode,
+ false, true, true,
+ NULL, NULL);
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
{
PLpgSQL_stmt_getdiag *new;
diff --git a/src/pl/plpgsql/src/pl_reserved_kwlist.h \
b/src/pl/plpgsql/src/pl_reserved_kwlist.h index daf835e683..5e64984ff4 100644
--- a/src/pl/plpgsql/src/pl_reserved_kwlist.h
+++ b/src/pl/plpgsql/src/pl_reserved_kwlist.h
@@ -40,6 +40,7 @@ PG_KEYWORD("from", K_FROM)
PG_KEYWORD("if", K_IF)
PG_KEYWORD("in", K_IN)
PG_KEYWORD("into", K_INTO)
+PG_KEYWORD("let", K_LET)
PG_KEYWORD("loop", K_LOOP)
PG_KEYWORD("not", K_NOT)
PG_KEYWORD("null", K_NULL)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index ebd3a5d3c8..84c78bc431 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -127,7 +127,8 @@ typedef enum PLpgSQL_stmt_type
PLPGSQL_STMT_PERFORM,
PLPGSQL_STMT_CALL,
PLPGSQL_STMT_COMMIT,
- PLPGSQL_STMT_ROLLBACK
+ PLPGSQL_STMT_ROLLBACK,
+ PLPGSQL_STMT_LET
} PLpgSQL_stmt_type;
/*
@@ -519,6 +520,17 @@ typedef struct PLpgSQL_stmt_assign
PLpgSQL_expr *expr;
} PLpgSQL_stmt_assign;
+/*
+ * Let statement
+ */
+typedef struct PLpgSQL_stmt_let
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_expr *expr;
+} PLpgSQL_stmt_let;
+
/*
* PERFORM statement
*/
diff --git a/src/test/regress/expected/misc_sanity.out \
b/src/test/regress/expected/misc_sanity.out index a57fd142a9..ce9bad7211 100644
--- a/src/test/regress/expected/misc_sanity.out
+++ b/src/test/regress/expected/misc_sanity.out
@@ -60,7 +60,9 @@ 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)
-- system catalogs without primary keys
--
diff --git a/src/test/regress/expected/sanity_check.out \
b/src/test/regress/expected/sanity_check.out index d04dc66db9..54b2ac8383 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -165,6 +165,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..0246d86673
--- /dev/null
+++ b/src/test/regress/expected/schema_variables.out
@@ -0,0 +1,655 @@
+CREATE SCHEMA svartest;
+SET search_path = svartest;
+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;
+GRANT USAGE ON SCHEMA svartest TO var_test_role;
+SET ROLE TO var_test_role;
+-- should 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 fail
+LET var1 = 10;
+ERROR: permission denied for schema variable var1
+-- should 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 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 svartest.var1::int;
+$$ LANGUAGE sql SECURITY DEFINER;
+SELECT secure_var();
+ secure_var
+------------
+ 333
+(1 row)
+
+SET ROLE TO var_test_role;
+-- should fail
+SELECT svartest.var1;
+ERROR: permission denied for schema variable var1
+-- should 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 -
+SET search_path = svartest;
+-- should 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 see execution plan of LET statement
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+ QUERY PLAN
+----------------------------
+ SET SCHEMA VARIABLE
+ Result
+ Output: 3.14159265358979
+(3 rows)
+
+SELECT var1;
+ var1
+------------------
+ 3.14159265358979
+(1 row)
+
+CREATE VARIABLE var3 AS int;
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+ LET svartest.var3 = COALESCE(svartest.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 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 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 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;
+REVOKE USAGE ON SCHEMA svartest FROM var_test_role;
+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 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 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 svartest.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;
+-- ON TRANSACTION END RESET tests
+CREATE VARIABLE t1 AS int DEFAULT -1 ON TRANSACTION END RESET;
+BEGIN;
+ SELECT t1;
+ t1
+----
+ -1
+(1 row)
+
+ LET t1 = 100;
+ SELECT t1;
+ t1
+-----
+ 100
+(1 row)
+
+COMMIT;
+SELECT t1;
+ t1
+----
+ -1
+(1 row)
+
+BEGIN;
+ SELECT t1;
+ t1
+----
+ -1
+(1 row)
+
+ LET t1 = 100;
+ SELECT t1;
+ t1
+-----
+ 100
+(1 row)
+
+ROLLBACK;
+SELECT t1;
+ t1
+----
+ -1
+(1 row)
+
+DROP VARIABLE t1;
+CREATE VARIABLE v1 AS int DEFAULT 0;
+CREATE VARIABLE v2 AS text DEFAULT 'none';
+LET v1 = 100;
+LET v2 = 'Hello';
+SELECT v1, v2;
+ v1 | v2
+-----+-------
+ 100 | Hello
+(1 row)
+
+LET v1 = DEFAULT;
+LET v2 = DEFAULT;
+SELECT v1, v2;
+ v1 | v2
+----+------
+ 0 | none
+(1 row)
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+-- ON COMMIT DROP tests
+-- should be 0 always
+SELECT count(*) FROM pg_variable;
+ count
+-------
+ 0
+(1 row)
+
+CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+SELECT count(*) FROM pg_variable;
+ count
+-------
+ 0
+(1 row)
+
+BEGIN;
+ CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+COMMIT;
+SELECT count(*) FROM pg_variable;
+ count
+-------
+ 0
+(1 row)
+
+BEGIN;
+ CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+ROLLBACK;
+SELECT count(*) FROM pg_variable;
+ count
+-------
+ 0
+(1 row)
+
+-- test on query with workers
+CREATE TABLE svar_test(a int);
+INSERT INTO svar_test SELECT * FROM generate_series(1,1000000);
+ANALYZE svar_test;
+CREATE VARIABLE zero int;
+LET zero = 0;
+-- parallel workers should be used
+EXPLAIN (costs off) SELECT count(*) FROM svar_test WHERE a%10 = zero;
+ QUERY PLAN
+--------------------------------------------------
+ Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on svar_test
+ Filter: ((a % 10) = zero)
+(6 rows)
+
+-- result should be 100000
+SELECT count(*) FROM svar_test WHERE a%10 = zero;
+ count
+--------
+ 100000
+(1 row)
+
+LET zero = (SELECT count(*) FROM svar_test);
+-- result should be 1000000
+SELECT zero;
+ zero
+---------
+ 1000000
+(1 row)
+
+-- parallel workers should be used
+EXPLAIN (costs off) LET zero = (SELECT count(*) FROM svar_test);
+ QUERY PLAN
+----------------------------------------------------------
+ SET SCHEMA VARIABLE
+ Result
+ InitPlan 1 (returns $1)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on svar_test
+(8 rows)
+
+DROP TABLE svar_test;
+DROP VARIABLE zero;
+-- use variables in prepared statements
+CREATE VARIABLE v AS numeric;
+LET v = 3.14;
+-- use variables in views
+CREATE VIEW vv AS SELECT COALESCE(v, 0) + 1000 AS result;
+SELECT * FROM vv;
+ result
+---------
+ 1003.14
+(1 row)
+
+-- start a new session
+\c
+SET search_path to svartest;
+SELECT * FROM vv;
+ result
+--------
+ 1000
+(1 row)
+
+LET v = 3.14;
+SELECT * FROM vv;
+ result
+---------
+ 1003.14
+(1 row)
+
+-- should fail, dependency
+DROP VARIABLE v;
+ERROR: cannot drop schema variable v because other objects depend on it
+DETAIL: view vv depends on schema variable v
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- should be ok
+DROP VARIABLE v CASCADE;
+NOTICE: drop cascades to view vv
+-- other features
+CREATE VARIABLE dt AS integer DEFAULT 0;
+LET dt = 100;
+SELECT dt;
+ dt
+-----
+ 100
+(1 row)
+
+DISCARD VARIABLES;
+SELECT dt;
+ dt
+----
+ 0
+(1 row)
+
+DROP VARIABLE dt;
+-- NOT NULL
+CREATE VARIABLE v1 AS int NOT NULL;
+CREATE VARIABLE v2 AS int NOT NULL DEFAULT NULL;
+-- should fail
+SELECT v1;
+ERROR: null value is not allowed for NOT NULL schema variable "v1"
+DETAIL: The schema variable was not initialized yet.
+SELECT v2;
+ERROR: null value is not allowed for NOT NULL schema variable "v2"
+LET v1 = NULL;
+ERROR: null value is not allowed for NOT NULL schema variable "v1"
+LET v2 = NULL;
+ERROR: null value is not allowed for NOT NULL schema variable "v2"
+LET v1 = DEFAULT;
+ERROR: null value is not allowed for NOT NULL schema variable "v1"
+LET v2 = DEFAULT;
+ERROR: null value is not allowed for NOT NULL schema variable "v2"
+-- should be ok
+LET v1 = 100;
+LET v2 = 1000;
+SELECT v1, v2;
+ v1 | v2
+-----+------
+ 100 | 1000
+(1 row)
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+CREATE VARIABLE tv AS int;
+CREATE VARIABLE IF NOT EXISTS tv AS int;
+NOTICE: schema variable "tv" already exists, skipping
+DROP VARIABLE tv;
+CREATE IMMUTABLE VARIABLE iv AS int DEFAULT 100;
+SELECT iv;
+ iv
+-----
+ 100
+(1 row)
+
+-- should fail;
+LET iv = 10000;
+ERROR: schema variable "iv" is declared IMMUTABLE
+DROP VARIABLE iv;
+-- create variable inside plpgsql block
+DO $$
+BEGIN
+ CREATE VARIABLE do_test_svar AS date DEFAULT '2000-01-01';
+END;
+$$;
+SELECT do_test_svar;
+ do_test_svar
+--------------
+ 01-01-2000
+(1 row)
+
+DROP VARIABLE do_test_svar;
+-- should fail
+CREATE IMMUTABLE VARIABLE xx AS int NOT NULL;
+ERROR: IMMUTABLE NOT NULL variable requires default expression
+-- REASSIGN OWNED test
+CREATE ROLE var_test_role1;
+CREATE ROLE var_test_role2;
+CREATE VARIABLE xxx_var AS int;
+ALTER VARIABLE xxx_var OWNER TO var_test_role1;
+REASSIGN OWNED BY var_test_role1 to var_test_role2;
+SELECT varowner::regrole FROM pg_variable WHERE varname = 'xxx_var';
+ varowner
+----------------
+ var_test_role2
+(1 row)
+
+DROP OWNED BY var_test_role1;
+DROP ROLE var_test_role1;
+SELECT count(*) FROM pg_variable WHERE varname = 'xxx_var';
+ count
+-------
+ 1
+(1 row)
+
+DROP OWNED BY var_test_role2;
+DROP ROLE var_test_role2;
+SELECT count(*) FROM pg_variable WHERE varname = 'xxx_var';
+ count
+-------
+ 0
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0..cbaeedf8b4 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -115,7 +115,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding \
jsonb_jsonpath # NB: temp.sql does a reconnect which transiently uses 2 connections,
# so keep this parallel group to at most 19 tests
# ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion \
truncate alter_table sequence polymorphism rowtypes returning largeobject with xml \
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion \
truncate alter_table sequence polymorphism rowtypes returning largeobject with xml \
schema_variables
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/sql/schema_variables.sql \
b/src/test/regress/sql/schema_variables.sql new file mode 100644
index 0000000000..0bd7798cf9
--- /dev/null
+++ b/src/test/regress/sql/schema_variables.sql
@@ -0,0 +1,438 @@
+CREATE SCHEMA svartest;
+
+SET search_path = svartest;
+
+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;
+GRANT USAGE ON SCHEMA svartest TO var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should fail
+SELECT var1;
+
+SET ROLE TO DEFAULT;
+
+GRANT READ ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+-- should fail
+LET var1 = 10;
+-- should work
+SELECT var1;
+
+SET ROLE TO DEFAULT;
+
+GRANT WRITE ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should 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 svartest.var1::int;
+$$ LANGUAGE sql SECURITY DEFINER;
+
+SELECT secure_var();
+
+SET ROLE TO var_test_role;
+
+-- should fail
+SELECT svartest.var1;
+
+-- should 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 -
+
+SET search_path = svartest;
+
+-- should work still, but var will be empty
+SELECT * FROM schema_var_view;
+
+LET var1 = pi();
+
+SELECT var1;
+
+-- we can see execution plan of LET statement
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+
+SELECT var1;
+
+CREATE VARIABLE var3 AS int;
+
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+ LET svartest.var3 = COALESCE(svartest.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 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 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 fail
+SET ROLE TO var_test_role;
+
+SELECT v2.x;
+
+SET ROLE TO DEFAULT;
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+REVOKE USAGE ON SCHEMA svartest FROM var_test_role;
+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 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 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 svartest.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;
+
+-- ON TRANSACTION END RESET tests
+CREATE VARIABLE t1 AS int DEFAULT -1 ON TRANSACTION END RESET;
+
+BEGIN;
+ SELECT t1;
+ LET t1 = 100;
+ SELECT t1;
+COMMIT;
+
+SELECT t1;
+
+BEGIN;
+ SELECT t1;
+ LET t1 = 100;
+ SELECT t1;
+ROLLBACK;
+
+SELECT t1;
+
+DROP VARIABLE t1;
+
+CREATE VARIABLE v1 AS int DEFAULT 0;
+CREATE VARIABLE v2 AS text DEFAULT 'none';
+
+LET v1 = 100;
+LET v2 = 'Hello';
+SELECT v1, v2;
+LET v1 = DEFAULT;
+LET v2 = DEFAULT;
+SELECT v1, v2;
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+-- ON COMMIT DROP tests
+-- should be 0 always
+SELECT count(*) FROM pg_variable;
+
+CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+
+SELECT count(*) FROM pg_variable;
+
+BEGIN;
+ CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+COMMIT;
+
+SELECT count(*) FROM pg_variable;
+
+BEGIN;
+ CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+ROLLBACK;
+
+SELECT count(*) FROM pg_variable;
+
+-- test on query with workers
+CREATE TABLE svar_test(a int);
+INSERT INTO svar_test SELECT * FROM generate_series(1,1000000);
+ANALYZE svar_test;
+CREATE VARIABLE zero int;
+LET zero = 0;
+
+-- parallel workers should be used
+EXPLAIN (costs off) SELECT count(*) FROM svar_test WHERE a%10 = zero;
+
+-- result should be 100000
+SELECT count(*) FROM svar_test WHERE a%10 = zero;
+
+LET zero = (SELECT count(*) FROM svar_test);
+
+-- result should be 1000000
+SELECT zero;
+
+-- parallel workers should be used
+EXPLAIN (costs off) LET zero = (SELECT count(*) FROM svar_test);
+
+DROP TABLE svar_test;
+DROP VARIABLE zero;
+
+-- use variables in prepared statements
+CREATE VARIABLE v AS numeric;
+LET v = 3.14;
+
+-- use variables in views
+CREATE VIEW vv AS SELECT COALESCE(v, 0) + 1000 AS result;
+SELECT * FROM vv;
+
+-- start a new session
+\c
+
+SET search_path to svartest;
+
+SELECT * FROM vv;
+LET v = 3.14;
+SELECT * FROM vv;
+
+-- should fail, dependency
+DROP VARIABLE v;
+
+-- should be ok
+DROP VARIABLE v CASCADE;
+
+-- other features
+CREATE VARIABLE dt AS integer DEFAULT 0;
+
+LET dt = 100;
+SELECT dt;
+
+DISCARD VARIABLES;
+
+SELECT dt;
+
+DROP VARIABLE dt;
+
+-- NOT NULL
+CREATE VARIABLE v1 AS int NOT NULL;
+CREATE VARIABLE v2 AS int NOT NULL DEFAULT NULL;
+
+-- should fail
+SELECT v1;
+SELECT v2;
+LET v1 = NULL;
+LET v2 = NULL;
+LET v1 = DEFAULT;
+LET v2 = DEFAULT;
+
+-- should be ok
+LET v1 = 100;
+LET v2 = 1000;
+SELECT v1, v2;
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+CREATE VARIABLE tv AS int;
+CREATE VARIABLE IF NOT EXISTS tv AS int;
+DROP VARIABLE tv;
+
+CREATE IMMUTABLE VARIABLE iv AS int DEFAULT 100;
+SELECT iv;
+
+-- should fail;
+LET iv = 10000;
+
+DROP VARIABLE iv;
+
+-- create variable inside plpgsql block
+DO $$
+BEGIN
+ CREATE VARIABLE do_test_svar AS date DEFAULT '2000-01-01';
+END;
+$$;
+
+SELECT do_test_svar;
+
+DROP VARIABLE do_test_svar;
+
+-- should fail
+CREATE IMMUTABLE VARIABLE xx AS int NOT NULL;
+
+-- REASSIGN OWNED test
+CREATE ROLE var_test_role1;
+CREATE ROLE var_test_role2;
+
+CREATE VARIABLE xxx_var AS int;
+
+ALTER VARIABLE xxx_var OWNER TO var_test_role1;
+REASSIGN OWNED BY var_test_role1 to var_test_role2;
+
+SELECT varowner::regrole FROM pg_variable WHERE varname = 'xxx_var';
+
+DROP OWNED BY var_test_role1;
+DROP ROLE var_test_role1;
+SELECT count(*) FROM pg_variable WHERE varname = 'xxx_var';
+
+DROP OWNED BY var_test_role2;
+DROP ROLE var_test_role2;
+SELECT count(*) FROM pg_variable WHERE varname = 'xxx_var';
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic