public inbox for [email protected]
help / color / mirror / Atom feedFrom: Laurenz Albe <[email protected]>
To: Pavel Stehule <[email protected]>
Cc: Erik Rijkers <[email protected]>
Cc: Michael Paquier <[email protected]>
Cc: Amit Kapila <[email protected]>
Cc: DUVAL REMI <[email protected]>
Cc: PostgreSQL Hackers <[email protected]>
Subject: Re: proposal: schema variables
Date: Wed, 31 Jul 2024 11:45:00 +0200
Message-ID: <[email protected]> (raw)
In-Reply-To: <CAFj8pRBEN2LbGs-djJMQLNsM9jLgKixwkjgduZFJAZy5Kd=mqw@mail.gmail.com>
References: <CAFj8pRBoGBKopkiTa4ki3dMgy-cSTRZ-BQPKOq7=Tk0SSNsowA@mail.gmail.com>
<CAFj8pRBCiWn12H9FHymYs17fk68nRd9Xpn+SYf18TLdb2YSUrQ@mail.gmail.com>
<CAA4eK1JV-Ox0oRFdXnPqXSzM84wTR5QFkRzCpNVF_+0FNjS5Mg@mail.gmail.com>
<CAFj8pRByCCcgDkeXyafAnH6LgxtAyCVwg6yfJAhyTY6GLscfZg@mail.gmail.com>
<CAFj8pRBvUonC_ug3F=w1Q55Dp=DggojvAeL7Vmh14Q-WhFHxzw@mail.gmail.com>
<CAFj8pRDj72P-f=SUygtOXnTOBQ0RzmL_fN=wLfaCzcbPVpGgzw@mail.gmail.com>
<CAFj8pRDD2GQaJ_iDT4vSVe658+oHRXU2S2af7Y1-it9jaP8VFg@mail.gmail.com>
<[email protected]>
<CAFj8pRAFktynx5wkanv5SRuzXkZgXu77XpVACiSE=v7i1xHFbw@mail.gmail.com>
<CAFj8pRA=bn_g5T2AZduy5gNOQoOnUJ+pMHmnRMHi6mR0n=TAsA@mail.gmail.com>
<[email protected]>
<CAFj8pRC9de05HSb4tEHDUwJ98+4Wh30W-rJrNOPnTz6ARcv0Fw@mail.gmail.com>
<CAFj8pRBC5Wz1xHKKBmKsM0xYN0+PdSZ5oXPsk5SZt+VprdUW3A@mail.gmail.com>
<CAFj8pRAh4pzMoZrKCLt9h+Lr2L=vhgs2PjAF45uLbp_7sijM5w@mail.gmail.com>
<CAFj8pRA-kxQ1oErcuDeUKYsrgwB5XGLhquatwxOe3dCVy1gcyQ@mail.gmail.com>
<[email protected]>
<CAFj8pRBbt2xhY9PyabOY0ZN+Aig6ee3oCon-DM9qi0Uw_3qfbw@mail.gmail.com>
<CAFj8pRDSa52J7kPmCYXgq1BBbu3YBXwpdSOVpjgU=hnE2k04Cw@mail.gmail.com>
<CAFj8pRD+QiWOoPrFk2NnPs3t5Eaf4X=aGRV-9ww11cnPP+fV4g@mail.gmail.com>
<[email protected]>
<CAFj8pRCGTjqHvH9oeiSf4T6Bydhk9pm033DxxibgF+B7SHC6MQ@mail.gmail.com>
<CAFj8pRAzNDhFgbZnT0T0mJ7ygA1Qje1Hc0TiKwXM8++kGooPYg@mail.gmail.com>
<[email protected]>
<CAFj8pRBarjJYfkN-0-i=JRZJ4PTOYC+K7XgAhfdDqWGqRiPkyw@mail.gmail.com>
<CAFj8pRDqdWdCULxd5asbKs5C4e9kT2TuKBkR5L-e1=hP5wF2uw@mail.gmail.com>
<[email protected]>
<CAFj8pRCPW56pFr0F0BcasdXjFeo3SFixNSpWKaBk0ibvznum-A@mail.gmail.com>
<CAFj8pRD1Feit93CgwmYm1Q=X+M+AZqffCEZPFQ7qEMNHZRN4fA@mail.gmail.com>
<CAFj8pRCc=B9-FRQg5eWDSkGwS2vpkq88hR6042cmPPizHuEGSA@mail.gmail.com>
<CAFj8pRBk8x7afUXKLBOU-Ctg6A7QJvTAGGVEi0b6Jc8YTe8nUg@mail.gmail.com>
<CAFj8pRCSwHQ4BJUbjF2YEausK1Z6+ejMyedpqAnWJbG+FEJDLw@mail.gmail.com>
<CAFj8pRAbY+N+UqjqgESL5x-bsGmV+aVyyUkxUSgaGDZToZjDqQ@mail.gmail.com>
<CAFj8pRBzKcqzj=23BHfv1QaXHt=2_SN=uhdR3rb_dAVQoit7ug@mail.gmail.com>
<CAFj8pRCi-n6SzkAB+OHG=TZvL13xxta_qgffBLDOY0HEBqDhvg@mail.gmail.com>
<[email protected]>
<CAFj8pRBaD0_bMrCREWnVLfcTMdc0v7ns7Rt=sEvd1EoFmLfarQ@mail.gmail.com>
<[email protected]>
<CAFj8pRDXo-RRcy2VFDm_vzv3Eaaz6Ex=X19up=x8W4COyBNmaQ@mail.gmail.com>
<[email protected]>
<CAFj8pRDgraz64HHcCoFGCKDM1Cxv1ukELsqfsxux4JP3PM6RyQ@mail.gmail.com>
<[email protected]>
<CAFj8pRAsjr0pzQpQeRrQ-wR-ew1oTc6qkW=4355KveN1jKQ+1Q@mail.gmail.com>
<[email protected]>
<[email protected]>
<[email protected]>
<CAFj8pRB4oRVXTqChg0JH0XCKv33assFGr4jmUw3ht+4C6ac2PQ@mail.gmail.com>
<[email protected]>
<CAFj8pRBEN2LbGs-djJMQLNsM9jLgKixwkjgduZFJAZy5Kd=mqw@mail.gmail.com>
On Wed, 2024-07-31 at 09:04 +0200, Pavel Stehule wrote:
> st 31. 7. 2024 v 8:57 odesílatel Laurenz Albe <[email protected]> napsal:
> > On Wed, 2024-07-31 at 08:41 +0200, Pavel Stehule wrote:
> > > Probably you didn't attach new files - the second patch is not complete. Or you didn't make changes there?
> >
> > Hm. What is missing?
>
> let.sgml,
> session_variable.c
> svariable_receiver.c
> session_variable.h
> ...
Argh, I forgit the new files, sorry.
The attached patches should be complete.
Yours,
Laurenz Albe
Attachments:
[text/x-patch] v20240731-0001-Enhancing-catalog-for-support-session-vari.patch (132.7K, 2-v20240731-0001-Enhancing-catalog-for-support-session-vari.patch)
download | inline diff:
From 4705db0de3f9f5031fa8238908421d217a77e291 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email protected]>
Date: Tue, 30 Jul 2024 21:39:12 +0200
Subject: [PATCH v20240731 1/2] Enhancing catalog for support session variables
and related support
This patch introduces new system catalog table pg_variable. This table holds metadata about
session variables created by command CREATE VARIABLE, and dropped by command DROP VARIABLE.
Both commands are implemented by this patch. Possibility to change owner, schema or rename.
Access to session variables can be controlled by SELECT or UPDATE rights. Both rights are
introduced by this patch too.
This patch enhancing pg_dump and psql to support session variables. The changes are related
to system catalog.
This patch is not short, but the code is simple.
---
doc/src/sgml/catalogs.sgml | 120 +++++++++
doc/src/sgml/ddl.sgml | 31 +++
doc/src/sgml/event-trigger.sgml | 24 ++
doc/src/sgml/func.sgml | 13 +
doc/src/sgml/glossary.sgml | 15 ++
doc/src/sgml/ref/allfiles.sgml | 3 +
.../sgml/ref/alter_default_privileges.sgml | 26 +-
doc/src/sgml/ref/alter_variable.sgml | 178 ++++++++++++
doc/src/sgml/ref/comment.sgml | 1 +
doc/src/sgml/ref/create_schema.sgml | 7 +-
doc/src/sgml/ref/create_variable.sgml | 151 +++++++++++
doc/src/sgml/ref/drop_variable.sgml | 117 ++++++++
doc/src/sgml/ref/grant.sgml | 6 +
doc/src/sgml/ref/revoke.sgml | 7 +
doc/src/sgml/reference.sgml | 3 +
src/backend/catalog/Makefile | 1 +
src/backend/catalog/aclchk.c | 77 ++++++
src/backend/catalog/dependency.c | 6 +
src/backend/catalog/meson.build | 1 +
src/backend/catalog/namespace.c | 135 ++++++++++
src/backend/catalog/objectaddress.c | 121 ++++++++-
src/backend/catalog/pg_shdepend.c | 2 +
src/backend/catalog/pg_variable.c | 255 ++++++++++++++++++
src/backend/commands/alter.c | 9 +
src/backend/commands/dropcmds.c | 4 +
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/seclabel.c | 1 +
src/backend/commands/tablecmds.c | 41 +++
src/backend/parser/gram.y | 105 +++++++-
src/backend/parser/parse_utilcmd.c | 12 +
src/backend/tcop/utility.c | 16 ++
src/backend/utils/adt/acl.c | 7 +
src/backend/utils/cache/lsyscache.c | 113 ++++++++
src/bin/pg_dump/common.c | 3 +
src/bin/pg_dump/dumputils.c | 6 +
src/bin/pg_dump/pg_backup.h | 2 +
src/bin/pg_dump/pg_backup_archiver.c | 9 +
src/bin/pg_dump/pg_dump.c | 195 +++++++++++++-
src/bin/pg_dump/pg_dump.h | 19 ++
src/bin/pg_dump/pg_dump_sort.c | 6 +
src/bin/pg_dump/t/002_pg_dump.pl | 64 +++++
src/bin/psql/command.c | 3 +
src/bin/psql/describe.c | 96 +++++++
src/bin/psql/describe.h | 3 +
src/bin/psql/help.c | 1 +
src/bin/psql/tab-complete.c | 44 ++-
src/include/catalog/Makefile | 3 +-
src/include/catalog/meson.build | 1 +
src/include/catalog/namespace.h | 4 +
src/include/catalog/pg_default_acl.h | 1 +
src/include/catalog/pg_proc.dat | 3 +
src/include/catalog/pg_variable.h | 81 ++++++
src/include/nodes/parsenodes.h | 16 ++
src/include/parser/kwlist.h | 2 +
src/include/tcop/cmdtaglist.h | 3 +
src/include/utils/acl.h | 1 +
src/include/utils/lsyscache.h | 9 +
src/test/regress/expected/dependency.out | 17 ++
src/test/regress/expected/oidjoins.out | 4 +
src/test/regress/expected/psql.out | 50 ++++
.../regress/expected/session_variables.out | 57 ++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/dependency.sql | 14 +
src/test/regress/sql/psql.sql | 21 ++
src/test/regress/sql/session_variables.sql | 61 +++++
src/tools/pgindent/typedefs.list | 4 +
66 files changed, 2390 insertions(+), 27 deletions(-)
create mode 100644 doc/src/sgml/ref/alter_variable.sgml
create mode 100644 doc/src/sgml/ref/create_variable.sgml
create mode 100644 doc/src/sgml/ref/drop_variable.sgml
create mode 100644 src/backend/catalog/pg_variable.c
create mode 100644 src/include/catalog/pg_variable.h
create mode 100644 src/test/regress/expected/session_variables.out
create mode 100644 src/test/regress/sql/session_variables.sql
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b654fae1b2..de122e543f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -369,6 +369,11 @@
<entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
<entry>mappings of users to foreign servers</entry>
</row>
+
+ <row>
+ <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+ <entry>session variables</entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -9726,4 +9731,119 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</table>
</sect1>
+ <sect1 id="catalog-pg-variable">
+ <title><structname>pg_variable</structname></title>
+
+ <indexterm zone="catalog-pg-variable">
+ <primary>pg_variable</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_variable</structname> stores information about
+ session variables.
+ </para>
+
+ <table>
+ <title><structname>pg_variable</structname> Columns</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>oid</structfield> <type>oid</type>
+ </para>
+ <para>
+ Row identifier
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>vartype</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ The OID of the variable's data type
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>varname</structfield> <type>name</type>
+ </para>
+ <para>
+ Name of the session variable
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>varnamespace</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ The OID of the namespace that contains this variable
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>varowner</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Owner of the variable
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>vartypmod</structfield> <type>int4</type>
+ </para>
+ <para>
+ <structfield>vartypmod</structfield> records type-specific data
+ supplied at variable creation time (for example, the maximum
+ length of a <type>varchar</type> column). It is passed to
+ type-specific input functions and length coercion functions.
+ The value will generally be -1 for types that do not need <structfield>vartypmod</structfield>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>varcollation</structfield> <type>oid</type>
+ (references <link linkend="catalog-pg-collation"><structname>pg_collation</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ The defined collation of the variable, or zero if the variable is
+ not of a collatable data type.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>varacl</structfield> <type>aclitem[]</type>
+ </para>
+ <para>
+ Access privileges; see
+ <xref linkend="sql-grant"/> and
+ <xref linkend="sql-revoke"/>
+ for details
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 626d35514c..7531de17fd 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5298,6 +5298,37 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
</para>
</sect1>
+ <sect1 id="ddl-session-variables">
+ <title>Session Variables</title>
+
+ <indexterm zone="ddl-session-variables">
+ <primary>Session variables</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>session variable</primary>
+ </indexterm>
+
+ <para>
+ Session variables are database objects that can hold a value.
+ Session variables, like relations, exist within a schema and their access
+ is controlled via <command>GRANT</command> and <command>REVOKE</command>
+ commands. A session variable can be created by the <command>CREATE
+ VARIABLE</command> command.
+ </para>
+
+ <para>
+ Inside a query or an expression, a session variable can be
+ <quote>shadowed</quote> by a column with the same name. Similarly, the
+ name of a function or procedure argument or a PL/pgSQL variable (see
+ <xref linkend="plpgsql-declarations"/>) can shadow a session variable
+ in the routine's body. Such collisions of identifiers can be resolved
+ by using qualified identifiers: Session variables can be qualified with
+ the schema name, columns can use table aliases, routine variables can use
+ block labels, and routine arguments can use the routine name.
+ </para>
+ </sect1>
+
<sect1 id="ddl-others">
<title>Other Database Objects</title>
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 8e009cca05..15b2af99dc 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -424,6 +424,14 @@
<entry align="center"><literal>-</literal></entry>
<entry align="left"></entry>
</row>
+ <row>
+ <entry align="left"><literal>ALTER VARIABLE</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
<row>
<entry align="left"><literal>ALTER VIEW</literal></entry>
<entry align="center"><literal>X</literal></entry>
@@ -712,6 +720,14 @@
<entry align="center"><literal>-</literal></entry>
<entry align="left"></entry>
</row>
+ <row>
+ <entry align="left"><literal>CREATE VARIABLE</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
<row>
<entry align="left"><literal>CREATE VIEW</literal></entry>
<entry align="center"><literal>X</literal></entry>
@@ -1000,6 +1016,14 @@
<entry align="center"><literal>-</literal></entry>
<entry align="left"></entry>
</row>
+ <row>
+ <entry align="left"><literal>DROP VARIABLE</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
<row>
<entry align="left"><literal>DROP VIEW</literal></entry>
<entry align="center"><literal>X</literal></entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b39f97dc8d..c3c4fbb3d9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25647,6 +25647,19 @@ SELECT relname FROM pg_class WHERE pg_table_is_visible(oid);
Is type (or domain) visible in search path?
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_variable_is_visible</primary>
+ </indexterm>
+ <function>pg_variable_is_visible</function> ( <parameter>variable</parameter> <type>oid</type> )
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is session variable visible in search path?
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index 405fe6dc8b..d5f4bcc6e0 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -1654,6 +1654,21 @@
</glossdef>
</glossentry>
+ <glossentry id="glossary-session-variable">
+ <glossterm>Session variable</glossterm>
+ <glossdef>
+ <para>
+ A persistent database object that holds a value in session memory. This
+ value is private to each session and is released when the session ends.
+ Read or write access to session variables is controlled by privileges,
+ similar to other database objects.
+ </para>
+ <para>
+ For more information, see <xref linkend="ddl-session-variables"/>.
+ </para>
+ </glossdef>
+ </glossentry>
+
<glossentry id="glossary-shared-memory">
<glossterm>Shared memory</glossterm>
<glossdef>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867..2f67de3e21 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -47,6 +47,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY alterType SYSTEM "alter_type.sgml">
<!ENTITY alterUser SYSTEM "alter_user.sgml">
<!ENTITY alterUserMapping SYSTEM "alter_user_mapping.sgml">
+<!ENTITY alterVariable SYSTEM "alter_variable.sgml">
<!ENTITY alterView SYSTEM "alter_view.sgml">
<!ENTITY analyze SYSTEM "analyze.sgml">
<!ENTITY begin SYSTEM "begin.sgml">
@@ -99,6 +100,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY createType SYSTEM "create_type.sgml">
<!ENTITY createUser SYSTEM "create_user.sgml">
<!ENTITY createUserMapping SYSTEM "create_user_mapping.sgml">
+<!ENTITY createVariable SYSTEM "create_variable.sgml">
<!ENTITY createView SYSTEM "create_view.sgml">
<!ENTITY deallocate SYSTEM "deallocate.sgml">
<!ENTITY declare SYSTEM "declare.sgml">
@@ -147,6 +149,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY dropType SYSTEM "drop_type.sgml">
<!ENTITY dropUser SYSTEM "drop_user.sgml">
<!ENTITY dropUserMapping SYSTEM "drop_user_mapping.sgml">
+<!ENTITY dropVariable SYSTEM "drop_variable.sgml">
<!ENTITY dropView SYSTEM "drop_view.sgml">
<!ENTITY end SYSTEM "end.sgml">
<!ENTITY execute SYSTEM "execute.sgml">
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 89aacec4fa..041c78d432 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -51,6 +51,10 @@ GRANT { { USAGE | CREATE }
ON SCHEMAS
TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
+GRANT { SELECT | UPDATE | ALL [ PRIVILEGES ] }
+ ON VARIABLES
+ TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
+
REVOKE [ GRANT OPTION FOR ]
{ { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
[, ...] | ALL [ PRIVILEGES ] }
@@ -83,6 +87,12 @@ REVOKE [ GRANT OPTION FOR ]
ON SCHEMAS
FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
[ CASCADE | RESTRICT ]
+
+REVOKE [ GRANT OPTION FOR ]
+ { { SELECT | UPDATE } [, ...] | ALL [ PRIVILEGES ] }
+ ON VARIABLES
+ FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
+ [ CASCADE | RESTRICT ]
</synopsis>
</refsynopsisdiv>
@@ -117,14 +127,14 @@ REVOKE [ GRANT OPTION FOR ]
<para>
Currently,
only the privileges for schemas, tables (including views and foreign
- tables), sequences, functions, and types (including domains) can be
- altered. For this command, functions include aggregates and procedures.
- The words <literal>FUNCTIONS</literal> and <literal>ROUTINES</literal> are
- equivalent in this command. (<literal>ROUTINES</literal> is preferred
- going forward as the standard term for functions and procedures taken
- together. In earlier PostgreSQL releases, only the
- word <literal>FUNCTIONS</literal> was allowed. It is not possible to set
- default privileges for functions and procedures separately.)
+ tables), sequences, functions, types (including domains), and session
+ variables can be altered. For this command, functions include aggregates
+ and procedures. The words <literal>FUNCTIONS</literal> and
+ <literal>ROUTINES</literal> are equivalent in this command.
+ (<literal>ROUTINES</literal> is preferred going forward as the standard
+ term for functions and procedures taken together. In earlier PostgreSQL
+ releases, only the word <literal>FUNCTIONS</literal> was allowed. It is not
+ possible to set default privileges for functions and procedures separately.)
</para>
<para>
diff --git a/doc/src/sgml/ref/alter_variable.sgml b/doc/src/sgml/ref/alter_variable.sgml
new file mode 100644
index 0000000000..d87570e7d8
--- /dev/null
+++ b/doc/src/sgml/ref/alter_variable.sgml
@@ -0,0 +1,178 @@
+<!--
+doc/src/sgml/ref/alter_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-altervariable">
+ <indexterm zone="sql-altervariable">
+ <primary>ALTER VARIABLE</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>session variable</primary>
+ <secondary>altering</secondary>
+ </indexterm>
+
+ <refmeta>
+ <refentrytitle>ALTER VARIABLE</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>ALTER VARIABLE</refname>
+ <refpurpose>
+ change the definition of a session variable
+ </refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER VARIABLE <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable class="parameter">new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
+ALTER VARIABLE <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
+ALTER VARIABLE <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ The <command>ALTER VARIABLE</command> command changes the definition of an
+ existing session variable. There are several subforms:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>OWNER</literal></term>
+ <listitem>
+ <para>
+ This form changes the owner of the session variable.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>RENAME</literal></term>
+ <listitem>
+ <para>
+ This form changes the name of the session variable.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>SET SCHEMA</literal></term>
+ <listitem>
+ <para>
+ This form moves the session variable into another schema.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </para>
+
+ <para>
+ Only the owner or a superuser is allowed to alter a session variable.
+ In order to move a session variable from one schema to another, the user
+ must also have the <literal>CREATE</literal> privilege on the new schema (or
+ be a superuser).
+
+ In order to move the session variable ownership from one role to another,
+ the user must also be a direct or indirect member of the new
+ owning role, and that role must have the <literal>CREATE</literal> privilege
+ on the session variable's schema (or be a superuser). These restrictions
+ enforce that altering the owner doesn't do anything you couldn't do by
+ dropping and recreating the session variable.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term><replaceable class="parameter">name</replaceable></term>
+ <listitem>
+ <para>
+ The name (possibly schema-qualified) of the existing session variable
+ to alter.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_name</replaceable></term>
+ <listitem>
+ <para>
+ The new name for the session variable.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_owner</replaceable></term>
+ <listitem>
+ <para>
+ The user name of the new owner of the session variable.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_schema</replaceable></term>
+ <listitem>
+ <para>
+ The new schema for the session variable.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>
+ To rename a session variable:
+<programlisting>
+ALTER VARIABLE foo RENAME TO boo;
+</programlisting>
+ </para>
+
+ <para>
+ To change the owner of the session variable <literal>boo</literal> to
+ <literal>joe</literal>:
+<programlisting>
+ALTER VARIABLE boo OWNER TO joe;
+</programlisting>
+ </para>
+
+ <para>
+ To change the schema of the session variable <literal>boo</literal> to
+ <literal>private</literal>:
+<programlisting>
+ALTER VARIABLE boo SET SCHEMA private;
+</programlisting>
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ Session variables and this command in particular are a PostgreSQL extension.
+ </para>
+ </refsect1>
+
+ <refsect1 id="sql-altervariable-see-also">
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-createvariable"/></member>
+ <member><xref linkend="sql-dropvariable"/></member>
+ </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index 5b43c56b13..21cd80818f 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -65,6 +65,7 @@ COMMENT ON
TRANSFORM FOR <replaceable>type_name</replaceable> LANGUAGE <replaceable>lang_name</replaceable> |
TRIGGER <replaceable class="parameter">trigger_name</replaceable> ON <replaceable class="parameter">table_name</replaceable> |
TYPE <replaceable class="parameter">object_name</replaceable> |
+ VARIABLE <replaceable class="parameter">object_name</replaceable> |
VIEW <replaceable class="parameter">object_name</replaceable>
} IS { <replaceable class="parameter">string_literal</replaceable> | NULL }
diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc..a834c876bc 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -103,9 +103,10 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
schema. Currently, only <command>CREATE
TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
- TRIGGER</command> and <command>GRANT</command> are accepted as clauses
- within <command>CREATE SCHEMA</command>. Other kinds of objects may
- be created in separate commands after the schema is created.
+ TRIGGER</command>, <command>GRANT</command> and <command>CREATE
+ VARIABLE</command> are accepted as clauses within <command>CREATE
+ SCHEMA</command>. Other kinds of objects may be created in separate
+ commands after the schema is created.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
new file mode 100644
index 0000000000..ec165ab96f
--- /dev/null
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -0,0 +1,151 @@
+<!--
+doc/src/sgml/ref/create_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-createvariable">
+ <indexterm zone="sql-createvariable">
+ <primary>CREATE VARIABLE</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>session variable</primary>
+ <secondary>defining</secondary>
+ </indexterm>
+
+ <refmeta>
+ <refentrytitle>CREATE VARIABLE</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>CREATE VARIABLE</refname>
+ <refpurpose>define a session variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
+</synopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ The <command>CREATE VARIABLE</command> command creates a session variable.
+ Session variables, like relations, exist within a schema and their access is
+ controlled via the commands <command>GRANT</command> and <command>REVOKE</command>.
+ </para>
+
+ <para>
+ The value of a session variable is local to the current session. Retrieving
+ a session variable's value returns NULL, unless its value is set to
+ something else in the current session with a <command>LET</command> command.
+ The content of a session variable is not transactional. This is the same as
+ regular variables in procedural languages.
+ </para>
+
+ <para>
+ Session variables are retrieved by the <command>SELECT</command>
+ command. Their value is set with the <command>LET</command> command.
+ </para>
+
+ <note>
+ <para>
+ Session variables can be <quote>shadowed</quote> by other identifiers.
+ For details, see <xref linkend="ddl-session-variables"/>.
+ </para>
+ </note>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+
+ <varlistentry id="sql-createvariable-if-not-exists">
+ <term><literal>IF NOT EXISTS</literal></term>
+ <listitem>
+ <para>
+ Do not throw an error if the name already exists. A notice is issued in
+ this case.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="sql-createvariable-name">
+ <term><replaceable class="parameter">name</replaceable></term>
+ <listitem>
+ <para>
+ The name, optionally schema-qualified, of the session variable.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="sql-createvariable-data_type">
+ <term><replaceable class="parameter">data_type</replaceable></term>
+ <listitem>
+ <para>
+ The name, optionally schema-qualified, of the data type of the session
+ variable.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="sql-createvariable-collate">
+ <term><literal>COLLATE <replaceable>collation</replaceable></literal></term>
+ <listitem>
+ <para>
+ The <literal>COLLATE</literal> clause assigns a collation to the session
+ variable (which must be of a collatable data type). If not specified,
+ the data type's default collation is used.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Use the <command>DROP VARIABLE</command> command to remove a session
+ variable.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>
+ Create an date session variable <literal>var1</literal>:
+<programlisting>
+CREATE VARIABLE var1 AS date;
+LET var1 = current_date;
+SELECT var1;
+</programlisting>
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ The <command>CREATE VARIABLE</command> command is a
+ <productname>PostgreSQL</productname> extension.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-altervariable"/></member>
+ <member><xref linkend="sql-dropvariable"/></member>
+ </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml
new file mode 100644
index 0000000000..5bdb3560f0
--- /dev/null
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -0,0 +1,117 @@
+<!--
+doc/src/sgml/ref/drop_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-dropvariable">
+ <indexterm zone="sql-dropvariable">
+ <primary>DROP VARIABLE</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>session variable</primary>
+ <secondary>removing</secondary>
+ </indexterm>
+
+ <refmeta>
+ <refentrytitle>DROP VARIABLE</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>DROP VARIABLE</refname>
+ <refpurpose>remove a session variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP VARIABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [, ...] [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>DROP VARIABLE</command> removes a session variable.
+ A session variable can only be removed by its owner or a superuser.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>IF EXISTS</literal></term>
+ <listitem>
+ <para>
+ Do not throw an error if the session variable does not exist. A notice is
+ issued in this case.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">name</replaceable></term>
+ <listitem>
+ <para>
+ The name, optionally schema-qualified, of a session variable.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>CASCADE</literal></term>
+ <listitem>
+ <para>
+ Automatically drop objects that depend on the session variable (such as
+ views), and in turn all objects that depend on those objects
+ (see <xref linkend="ddl-depend"/>).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>RESTRICT</literal></term>
+ <listitem>
+ <para>
+ Refuse to drop the session variable if any objects depend on it. This is
+ the default.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>
+ To remove the session variable <literal>var1</literal>:
+
+<programlisting>
+DROP VARIABLE var1;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ The <command>DROP VARIABLE</command> command is a
+ <productname>PostgreSQL</productname> extension.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-altervariable"/></member>
+ <member><xref linkend="sql-createvariable"/></member>
+ </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index 999f657d5c..78ff10fcf5 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -101,6 +101,12 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
[ WITH { ADMIN | INHERIT | SET } { OPTION | TRUE | FALSE } ]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
+GRANT { SELECT | UPDATE | ALL [ PRIVILEGES ] }
+ ON VARIABLE <replaceable>variable_name</replaceable> [, ...]
+ | ALL VARIABLES IN SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...] }
+ TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
+ [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
+
<phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase>
[ GROUP ] <replaceable class="parameter">role_name</replaceable>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 8df492281a..626c0231d0 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -137,6 +137,13 @@ REVOKE [ { ADMIN | INHERIT | SET } OPTION FOR ]
| CURRENT_ROLE
| CURRENT_USER
| SESSION_USER
+
+REVOKE [ GRANT OPTION FOR ]
+ { { SELECT | UPDATE } [, ...] | ALL [ PRIVILEGES ] }
+ ON VARIABLE <replaceable>variable_name</replaceable> [, ...]
+ | ALL VARIABLES IN SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...] }
+ FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
+ [ CASCADE | RESTRICT ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83f..25578f3946 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -75,6 +75,7 @@
&alterType;
&alterUser;
&alterUserMapping;
+ &alterVariable;
&alterView;
&analyze;
&begin;
@@ -127,6 +128,7 @@
&createType;
&createUser;
&createUserMapping;
+ &createVariable;
&createView;
&deallocate;
&declare;
@@ -175,6 +177,7 @@
&dropType;
&dropUser;
&dropUserMapping;
+ &dropVariable;
&dropView;
&end;
&execute;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1589a75fd5..7a90fcfc45 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -45,6 +45,7 @@ OBJS = \
pg_shdepend.o \
pg_subscription.o \
pg_type.o \
+ pg_variable.o \
storage.o \
toasting.o
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index a44ccee3b6..6968b1e6eb 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -64,6 +64,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
@@ -290,6 +291,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
case OBJECT_PARAMETER_ACL:
whole_mask = ACL_ALL_RIGHTS_PARAMETER_ACL;
break;
+ case OBJECT_VARIABLE:
+ whole_mask = ACL_ALL_RIGHTS_VARIABLE;
+ break;
default:
elog(ERROR, "unrecognized object type: %d", objtype);
/* not reached, but keep compiler quiet */
@@ -534,6 +538,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
all_privileges = ACL_ALL_RIGHTS_PARAMETER_ACL;
errormsg = gettext_noop("invalid privilege type %s for parameter");
break;
+ case OBJECT_VARIABLE:
+ all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+ errormsg = gettext_noop("invalid privilege type %s for session variable");
+ break;
default:
elog(ERROR, "unrecognized GrantStmt.objtype: %d",
(int) stmt->objtype);
@@ -639,6 +647,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
case OBJECT_PARAMETER_ACL:
ExecGrant_Parameter(istmt);
break;
+ case OBJECT_VARIABLE:
+ ExecGrant_common(istmt, VariableRelationId, ACL_ALL_RIGHTS_VARIABLE, NULL);
+ break;
default:
elog(ERROR, "unrecognized GrantStmt.objtype: %d",
(int) istmt->objtype);
@@ -829,6 +840,18 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
objects = lappend_oid(objects, parameterId);
}
break;
+ case OBJECT_VARIABLE:
+ foreach(cell, objnames)
+ {
+ RangeVar *varvar = (RangeVar *) lfirst(cell);
+ Oid relOid;
+
+ relOid = LookupVariable(varvar->schemaname,
+ varvar->relname,
+ false);
+ objects = lappend_oid(objects, relOid);
+ }
+ break;
default:
elog(ERROR, "unrecognized GrantStmt.objtype: %d",
(int) objtype);
@@ -918,6 +941,32 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
table_close(rel, AccessShareLock);
}
break;
+ case OBJECT_VARIABLE:
+ {
+ ScanKeyData key;
+ Relation rel;
+ TableScanDesc scan;
+ HeapTuple tuple;
+
+ ScanKeyInit(&key,
+ Anum_pg_variable_varnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(namespaceId));
+
+ rel = table_open(VariableRelationId, AccessShareLock);
+ scan = table_beginscan_catalog(rel, 1, &key);
+
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Oid oid = ((Form_pg_proc) GETSTRUCT(tuple))->oid;
+
+ objects = lappend_oid(objects, oid);
+ }
+
+ table_endscan(scan);
+ table_close(rel, AccessShareLock);
+ }
+ break;
default:
/* should not happen */
elog(ERROR, "unrecognized GrantStmt.objtype: %d",
@@ -1077,6 +1126,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
all_privileges = ACL_ALL_RIGHTS_SCHEMA;
errormsg = gettext_noop("invalid privilege type %s for schema");
break;
+ case OBJECT_VARIABLE:
+ all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+ errormsg = gettext_noop("invalid privilege type %s for session variable");
+ break;
default:
elog(ERROR, "unrecognized GrantStmt.objtype: %d",
(int) action->objtype);
@@ -1268,6 +1321,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
this_privileges = ACL_ALL_RIGHTS_SCHEMA;
break;
+ case OBJECT_VARIABLE:
+ objtype = DEFACLOBJ_VARIABLE;
+ if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
+ this_privileges = ACL_ALL_RIGHTS_VARIABLE;
+ break;
+
default:
elog(ERROR, "unrecognized object type: %d",
(int) iacls->objtype);
@@ -1511,6 +1570,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
case DEFACLOBJ_NAMESPACE:
iacls.objtype = OBJECT_SCHEMA;
break;
+ case DEFACLOBJ_VARIABLE:
+ iacls.objtype = OBJECT_VARIABLE;
+ break;
default:
/* Shouldn't get here */
elog(ERROR, "unexpected default ACL type: %d",
@@ -1571,6 +1633,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
case ParameterAclRelationId:
istmt.objtype = OBJECT_PARAMETER_ACL;
break;
+ case VariableRelationId:
+ istmt.objtype = OBJECT_VARIABLE;
+ break;
default:
elog(ERROR, "unexpected object class %u", classid);
break;
@@ -2810,6 +2875,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_TYPE:
msg = gettext_noop("permission denied for type %s");
break;
+ case OBJECT_VARIABLE:
+ msg = gettext_noop("permission denied for session variable %s");
+ break;
case OBJECT_VIEW:
msg = gettext_noop("permission denied for view %s");
break;
@@ -2921,6 +2989,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_TYPE:
msg = gettext_noop("must be owner of type %s");
break;
+ case OBJECT_VARIABLE:
+ msg = gettext_noop("must be owner of session variable %s");
+ break;
case OBJECT_VIEW:
msg = gettext_noop("must be owner of view %s");
break;
@@ -3069,6 +3140,8 @@ pg_aclmask(ObjectType objtype, Oid object_oid, AttrNumber attnum, Oid roleid,
return ACL_NO_RIGHTS;
case OBJECT_TYPE:
return object_aclmask(TypeRelationId, object_oid, roleid, mask, how);
+ case OBJECT_VARIABLE:
+ return object_aclmask(VariableRelationId, object_oid, roleid, mask, how);
default:
elog(ERROR, "unrecognized object type: %d",
(int) objtype);
@@ -4336,6 +4409,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
defaclobjtype = DEFACLOBJ_NAMESPACE;
break;
+ case OBJECT_VARIABLE:
+ defaclobjtype = DEFACLOBJ_VARIABLE;
+ break;
+
default:
return NULL;
}
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0489cbabcb..c9d686665d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -65,12 +65,14 @@
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/publicationcmds.h"
+#include "commands/schemacmds.h"
#include "commands/seclabel.h"
#include "commands/sequence.h"
#include "commands/trigger.h"
@@ -1444,6 +1446,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePublicationById(object->objectId);
break;
+ case VariableRelationId:
+ DropVariableById(object->objectId);
+ break;
+
case CastRelationId:
case CollationRelationId:
case ConversionRelationId:
diff --git a/src/backend/catalog/meson.build b/src/backend/catalog/meson.build
index 2f3ded8a0e..bfdb4da330 100644
--- a/src/backend/catalog/meson.build
+++ b/src/backend/catalog/meson.build
@@ -32,6 +32,7 @@ backend_sources += files(
'pg_shdepend.c',
'pg_subscription.c',
'pg_type.c',
+ 'pg_variable.c',
'storage.c',
'toasting.c',
)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 43b707699d..8752e1646e 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -41,6 +41,7 @@
#include "catalog/pg_ts_parser.h"
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
#include "commands/dbcommands.h"
#include "common/hashfn_unstable.h"
#include "funcapi.h"
@@ -971,6 +972,69 @@ RelationIsVisibleExt(Oid relid, bool *is_missing)
return visible;
}
+/*
+ * VariableIsVisible
+ * Determine whether a variable (identified by OID) is visible in the
+ * current search path. Visible means "would be found by searching
+ * for the unqualified variable name".
+ */
+bool
+VariableIsVisible(Oid varid)
+{
+ HeapTuple vartup;
+ Form_pg_variable varform;
+ Oid varnamespace;
+ bool visible;
+
+ vartup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+ if (!HeapTupleIsValid(vartup))
+ elog(ERROR, "cache lookup failed for session variable %u", varid);
+ varform = (Form_pg_variable) GETSTRUCT(vartup);
+
+ recomputeNamespacePath();
+
+ /*
+ * Quick check: if it ain't in the path at all, it ain't visible. Items in
+ * the system namespace are surely in the path and so we needn't even do
+ * list_member_oid() for them.
+ */
+ varnamespace = varform->varnamespace;
+ if (varnamespace != PG_CATALOG_NAMESPACE &&
+ !list_member_oid(activeSearchPath, varnamespace))
+ visible = false;
+ else
+ {
+ /*
+ * If it is in the path, it might still not be visible; it could be
+ * hidden by another variable of the same name earlier in the path. So
+ * we must do a slow check for conflicting relations.
+ */
+ char *varname = NameStr(varform->varname);
+ ListCell *l;
+
+ visible = false;
+ foreach(l, activeSearchPath)
+ {
+ Oid namespaceId = lfirst_oid(l);
+
+ if (namespaceId == varnamespace)
+ {
+ /* Found it first in path */
+ visible = true;
+ break;
+ }
+ if (OidIsValid(get_varname_varid(varname, namespaceId)))
+ {
+ /* Found something else first in path */
+ break;
+ }
+ }
+ }
+
+ ReleaseSysCache(vartup);
+
+ return visible;
+}
/*
* TypenameGetTypid
@@ -3274,6 +3338,66 @@ TSConfigIsVisibleExt(Oid cfgid, bool *is_missing)
return visible;
}
+/*
+ * Returns oid of session variable specified by possibly qualified identifier.
+ *
+ * If not found, returns InvalidOid if missing_ok, else throws error.
+ */
+Oid
+LookupVariable(const char *nspname,
+ const char *varname,
+ bool missing_ok)
+{
+ Oid namespaceId;
+ Oid varoid = InvalidOid;
+ ListCell *l;
+
+ if (nspname)
+ {
+ namespaceId = LookupExplicitNamespace(nspname, missing_ok);
+
+ /* if nspname is a known namespace, the variable must be there */
+ if (OidIsValid(namespaceId))
+ {
+ varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+ PointerGetDatum(varname),
+ ObjectIdGetDatum(namespaceId));
+ }
+ }
+ else
+ {
+ /* iterate over the schemas on the search_path */
+ recomputeNamespacePath();
+
+ foreach(l, activeSearchPath)
+ {
+ namespaceId = lfirst_oid(l);
+
+ varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+ PointerGetDatum(varname),
+ ObjectIdGetDatum(namespaceId));
+
+ if (OidIsValid(varoid))
+ break;
+ }
+ }
+
+ if (!OidIsValid(varoid) && !missing_ok)
+ {
+ if (nspname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("session variable \"%s.%s\" does not exist",
+ nspname, varname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("session variable \"%s\" does not exist",
+ varname)));
+ }
+
+ return varoid;
+}
/*
* DeconstructQualifiedName
@@ -5075,3 +5199,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 85a7b7e641..ee70eeeed4 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -62,6 +62,7 @@
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
@@ -636,6 +637,20 @@ static const ObjectPropertyType ObjectProperty[] =
OBJECT_USER_MAPPING,
false
},
+ {
+ "session variable",
+ VariableRelationId,
+ VariableOidIndexId,
+ VARIABLEOID,
+ VARIABLENAMENSP,
+ Anum_pg_variable_oid,
+ Anum_pg_variable_varname,
+ Anum_pg_variable_varnamespace,
+ Anum_pg_variable_varowner,
+ Anum_pg_variable_varacl,
+ OBJECT_VARIABLE,
+ true
+ }
};
/*
@@ -831,6 +846,9 @@ static const struct object_type_map
},
{
"statistics object", OBJECT_STATISTIC_EXT
+ },
+ {
+ "session variable", OBJECT_VARIABLE
}
};
@@ -856,6 +874,7 @@ static ObjectAddress get_object_address_attrdef(ObjectType objtype,
bool missing_ok);
static ObjectAddress get_object_address_type(ObjectType objtype,
TypeName *typename, bool missing_ok);
+static ObjectAddress get_object_address_variable(List *object, bool missing_ok);
static ObjectAddress get_object_address_opcf(ObjectType objtype, List *object,
bool missing_ok);
static ObjectAddress get_object_address_opf_member(ObjectType objtype,
@@ -1126,6 +1145,9 @@ get_object_address(ObjectType objtype, Node *object,
missing_ok);
address.objectSubId = 0;
break;
+ case OBJECT_VARIABLE:
+ address = get_object_address_variable(castNode(List, object), missing_ok);
+ break;
/* no default, to let compiler warn about missing case */
}
@@ -2000,16 +2022,20 @@ get_object_address_defacl(List *object, bool missing_ok)
case DEFACLOBJ_NAMESPACE:
objtype_str = "schemas";
break;
+ case DEFACLOBJ_VARIABLE:
+ objtype_str = "variables";
+ break;
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized default ACL object type \"%c\"", objtype),
- errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
+ errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
DEFACLOBJ_RELATION,
DEFACLOBJ_SEQUENCE,
DEFACLOBJ_FUNCTION,
DEFACLOBJ_TYPE,
- DEFACLOBJ_NAMESPACE)));
+ DEFACLOBJ_NAMESPACE,
+ DEFACLOBJ_VARIABLE)));
}
/*
@@ -2093,6 +2119,24 @@ textarray_to_strvaluelist(ArrayType *arr)
return list;
}
+/*
+ * Find the ObjectAddress for a session variable
+ */
+static ObjectAddress
+get_object_address_variable(List *object, bool missing_ok)
+{
+ ObjectAddress address;
+ char *nspname = NULL;
+ char *varname = NULL;
+
+ ObjectAddressSet(address, VariableRelationId, InvalidOid);
+
+ DeconstructQualifiedName(object, &nspname, &varname);
+ address.objectId = LookupVariable(nspname, varname, missing_ok);
+
+ return address;
+}
+
/*
* SQL-callable version of get_object_address
*/
@@ -2287,6 +2331,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_TABCONSTRAINT:
case OBJECT_OPCLASS:
case OBJECT_OPFAMILY:
+ case OBJECT_VARIABLE:
objnode = (Node *) name;
break;
case OBJECT_ACCESS_METHOD:
@@ -2458,6 +2503,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
case OBJECT_STATISTIC_EXT:
case OBJECT_TSDICTIONARY:
case OBJECT_TSCONFIGURATION:
+ case OBJECT_VARIABLE:
if (!object_ownercheck(address.classId, address.objectId, roleid))
aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
NameListToString(castNode(List, object)));
@@ -3446,6 +3492,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
break;
}
+ case VariableRelationId:
+ {
+ char *nspname;
+ HeapTuple tup;
+ Form_pg_variable varform;
+
+ tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for session variable %u",
+ object->objectId);
+
+ varform = (Form_pg_variable) GETSTRUCT(tup);
+
+ if (VariableIsVisible(object->objectId))
+ nspname = NULL;
+ else
+ nspname = get_namespace_name(varform->varnamespace);
+
+ appendStringInfo(&buffer, _("session variable %s"),
+ quote_qualified_identifier(nspname,
+ NameStr(varform->varname)));
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
case TSParserRelationId:
{
HeapTuple tup;
@@ -3798,6 +3870,16 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
_("default privileges on new schemas belonging to role %s"),
rolename);
break;
+ case DEFACLOBJ_VARIABLE:
+ if (nspname)
+ appendStringInfo(&buffer,
+ _("default privileges on new session variables belonging to role %s in schema %s"),
+ rolename, nspname);
+ else
+ appendStringInfo(&buffer,
+ _("default privileges on new session variables belonging to role %s"),
+ rolename);
+ break;
default:
/* shouldn't get here */
if (nspname)
@@ -4614,6 +4696,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "transform");
break;
+ case VariableRelationId:
+ appendStringInfoString(&buffer, "session variable");
+ break;
+
default:
elog(ERROR, "unsupported object class: %u", object->classId);
}
@@ -5720,6 +5806,10 @@ getObjectIdentityParts(const ObjectAddress *object,
appendStringInfoString(&buffer,
" on schemas");
break;
+ case DEFACLOBJ_VARIABLE:
+ appendStringInfoString(&buffer,
+ " on session variables");
+ break;
}
if (objname)
@@ -5960,6 +6050,33 @@ getObjectIdentityParts(const ObjectAddress *object,
}
break;
+ case VariableRelationId:
+ {
+ char *schema;
+ char *varname;
+ HeapTuple tup;
+ Form_pg_variable varform;
+
+ tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for session variable %u",
+ object->objectId);
+
+ varform = (Form_pg_variable) GETSTRUCT(tup);
+
+ schema = get_namespace_name_or_temp(varform->varnamespace);
+ varname = NameStr(varform->varname);
+
+ appendStringInfo(&buffer, "%s",
+ quote_qualified_identifier(schema, varname));
+
+ if (objname)
+ *objname = list_make2(schema, varname);
+
+ ReleaseSysCache(tup);
+ break;
+ }
+
default:
elog(ERROR, "unsupported object class: %u", object->classId);
}
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 753afb8845..1ea3d6db05 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -46,6 +46,7 @@
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
#include "commands/alter.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -1714,6 +1715,7 @@ shdepReassignOwned_Owner(Form_pg_shdepend sdepForm, Oid newrole)
case DatabaseRelationId:
case TSConfigRelationId:
case TSDictionaryRelationId:
+ case VariableRelationId:
AlterObjectOwner_internal(sdepForm->classid,
sdepForm->objid,
newrole);
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
new file mode 100644
index 0000000000..f65121440a
--- /dev/null
+++ b/src/backend/catalog/pg_variable.c
@@ -0,0 +1,255 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.c
+ * session variables
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/pg_variable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_variable.h"
+#include "miscadmin.h"
+#include "parser/parse_type.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+static ObjectAddress create_variable(const char *varName,
+ Oid varNamespace,
+ Oid varType,
+ int32 varTypmod,
+ Oid varOwner,
+ Oid varCollation,
+ bool if_not_exists);
+
+
+/*
+ * Creates entry in pg_variable table
+ */
+static ObjectAddress
+create_variable(const char *varName,
+ Oid varNamespace,
+ Oid varType,
+ int32 varTypmod,
+ Oid varOwner,
+ Oid varCollation,
+ bool if_not_exists)
+{
+ Acl *varacl;
+ NameData varname;
+ bool nulls[Natts_pg_variable];
+ Datum values[Natts_pg_variable];
+ Relation rel;
+ HeapTuple tup;
+ TupleDesc tupdesc;
+ ObjectAddress myself,
+ referenced;
+ ObjectAddresses *addrs;
+ Oid varid;
+
+ Assert(varName);
+ Assert(OidIsValid(varNamespace));
+ Assert(OidIsValid(varType));
+ Assert(OidIsValid(varOwner));
+
+ rel = table_open(VariableRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates. Note that this does not really prevent
+ * duplicates, it's here just to provide nicer error message in common
+ * case. The real protection is the unique key on the catalog.
+ */
+ if (SearchSysCacheExists2(VARIABLENAMENSP,
+ PointerGetDatum(varName),
+ ObjectIdGetDatum(varNamespace)))
+ {
+ if (if_not_exists)
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("session variable \"%s\" already exists, skipping",
+ varName)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("session variable \"%s\" already exists",
+ varName)));
+
+ table_close(rel, RowExclusiveLock);
+
+ return InvalidObjectAddress;
+ }
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ namestrcpy(&varname, varName);
+
+ varid = GetNewOidWithIndex(rel, VariableOidIndexId, Anum_pg_variable_oid);
+
+ values[Anum_pg_variable_oid - 1] = ObjectIdGetDatum(varid);
+ values[Anum_pg_variable_varname - 1] = NameGetDatum(&varname);
+ values[Anum_pg_variable_varnamespace - 1] = ObjectIdGetDatum(varNamespace);
+ values[Anum_pg_variable_vartype - 1] = ObjectIdGetDatum(varType);
+ values[Anum_pg_variable_vartypmod - 1] = Int32GetDatum(varTypmod);
+ values[Anum_pg_variable_varowner - 1] = ObjectIdGetDatum(varOwner);
+ values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation);
+
+ varacl = get_user_default_acl(OBJECT_VARIABLE, varOwner,
+ varNamespace);
+ if (varacl != NULL)
+ values[Anum_pg_variable_varacl - 1] = PointerGetDatum(varacl);
+ else
+ nulls[Anum_pg_variable_varacl - 1] = true;
+
+ tupdesc = RelationGetDescr(rel);
+
+ tup = heap_form_tuple(tupdesc, values, nulls);
+ CatalogTupleInsert(rel, tup);
+ Assert(OidIsValid(varid));
+
+ addrs = new_object_addresses();
+
+ ObjectAddressSet(myself, VariableRelationId, varid);
+
+ /* dependency on namespace */
+ ObjectAddressSet(referenced, NamespaceRelationId, varNamespace);
+ add_exact_object_address(&referenced, addrs);
+
+ /* dependency on used type */
+ ObjectAddressSet(referenced, TypeRelationId, varType);
+ add_exact_object_address(&referenced, addrs);
+
+ /* dependency on collation */
+ if (OidIsValid(varCollation) &&
+ varCollation != DEFAULT_COLLATION_OID)
+ {
+ ObjectAddressSet(referenced, CollationRelationId, varCollation);
+ add_exact_object_address(&referenced, addrs);
+ }
+
+ record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
+ free_object_addresses(addrs);
+
+ /* dependency on owner */
+ recordDependencyOnOwner(VariableRelationId, varid, varOwner);
+
+ /* dependencies on roles mentioned in default ACL */
+ recordDependencyOnNewAcl(VariableRelationId, varid, 0, varOwner, varacl);
+
+ /* dependency on extension */
+ recordDependencyOnCurrentExtension(&myself, false);
+
+ heap_freetuple(tup);
+
+ /* Post creation hook for new function */
+ InvokeObjectPostCreateHook(VariableRelationId, varid, 0);
+
+ table_close(rel, RowExclusiveLock);
+
+ return myself;
+}
+
+/*
+ * Creates a new variable
+ *
+ * Used by CREATE VARIABLE command
+ */
+ObjectAddress
+CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
+{
+ Oid namespaceid;
+ AclResult aclresult;
+ Oid typid;
+ int32 typmod;
+ Oid varowner = GetUserId();
+ Oid collation;
+ Oid typcollation;
+ ObjectAddress variable;
+
+ namespaceid =
+ RangeVarGetAndCheckCreationNamespace(stmt->variable, NoLock, NULL);
+
+ typenameTypeIdAndMod(pstate, stmt->typeName, &typid, &typmod);
+
+ /* Disallow pseudotypes */
+ if (get_typtype(typid) == TYPTYPE_PSEUDO)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("session variable cannot be pseudo-type %s",
+ format_type_be(typid))));
+
+ aclresult = object_aclcheck(TypeRelationId, typid, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, typid);
+
+ typcollation = get_typcollation(typid);
+
+ if (stmt->collClause)
+ collation = LookupCollation(pstate,
+ stmt->collClause->collname,
+ stmt->collClause->location);
+ else
+ collation = typcollation;;
+
+ /* Complain if COLLATE is applied to an uncollatable type */
+ if (OidIsValid(collation) && !OidIsValid(typcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collations are not supported by type %s",
+ format_type_be(typid)),
+ parser_errposition(pstate, stmt->collClause->location)));
+
+ variable = create_variable(stmt->variable->relname,
+ namespaceid,
+ typid,
+ typmod,
+ varowner,
+ collation,
+ stmt->if_not_exists);
+
+ elog(DEBUG1, "record for session variable \"%s\" (oid:%d) was created in pg_variable",
+ stmt->variable->relname, variable.objectId);
+
+ /* We want SessionVariableCreatePostprocess to see the catalog changes. */
+ CommandCounterIncrement();
+
+ return variable;
+}
+
+/*
+ * Drop variable by OID, and register the needed session variable
+ * cleanup.
+ */
+void
+DropVariableById(Oid varid)
+{
+ Relation rel;
+ HeapTuple tup;
+
+ rel = table_open(VariableRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for variable %u", varid);
+
+ CatalogTupleDelete(rel, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 4f99ebb447..63fa99fad3 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -41,6 +41,7 @@
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_ts_parser.h"
#include "catalog/pg_ts_template.h"
+#include "catalog/pg_variable.h"
#include "commands/alter.h"
#include "commands/collationcmds.h"
#include "commands/dbcommands.h"
@@ -139,6 +140,10 @@ report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
Assert(OidIsValid(nspOid));
msgfmt = gettext_noop("text search configuration \"%s\" already exists in schema \"%s\"");
break;
+ case VariableRelationId:
+ Assert(OidIsValid(nspOid));
+ msgfmt = gettext_noop("session variable \"%s\" already exists in schema \"%s\"");
+ break;
default:
elog(ERROR, "unsupported object class: %u", classId);
break;
@@ -418,6 +423,7 @@ ExecRenameStmt(RenameStmt *stmt)
case OBJECT_TSTEMPLATE:
case OBJECT_PUBLICATION:
case OBJECT_SUBSCRIPTION:
+ case OBJECT_VARIABLE:
{
ObjectAddress address;
Relation catalog;
@@ -561,6 +567,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
case OBJECT_TSDICTIONARY:
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
+ case OBJECT_VARIABLE:
{
Relation catalog;
Relation relation;
@@ -645,6 +652,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case TSDictionaryRelationId:
case TSTemplateRelationId:
case TSConfigRelationId:
+ case VariableRelationId:
{
Relation catalog;
@@ -875,6 +883,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
case OBJECT_TABLESPACE:
case OBJECT_TSDICTIONARY:
case OBJECT_TSCONFIGURATION:
+ case OBJECT_VARIABLE:
{
Relation relation;
ObjectAddress address;
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 85eec7e394..3cce17e92a 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -476,6 +476,10 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
msg = gettext_noop("publication \"%s\" does not exist, skipping");
name = strVal(object);
break;
+ case OBJECT_VARIABLE:
+ msg = gettext_noop("session variable \"%s\" does not exist, skipping");
+ name = NameListToString(castNode(List, object));
+ break;
case OBJECT_COLUMN:
case OBJECT_DATABASE:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 7a5ed6b985..6d41aa7ec3 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -2156,6 +2156,8 @@ stringify_grant_objtype(ObjectType objtype)
return "TABLESPACE";
case OBJECT_TYPE:
return "TYPE";
+ case OBJECT_VARIABLE:
+ return "VARIABLE";
/* these currently aren't used */
case OBJECT_ACCESS_METHOD:
case OBJECT_AGGREGATE:
@@ -2239,6 +2241,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
return "TABLESPACES";
case OBJECT_TYPE:
return "TYPES";
+ case OBJECT_VARIABLE:
+ return "VARIABLES";
/* these currently aren't used */
case OBJECT_ACCESS_METHOD:
case OBJECT_AGGREGATE:
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5607273bf9..c026ab5dcb 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -92,6 +92,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
case OBJECT_USER_MAPPING:
+ case OBJECT_VARIABLE:
return false;
/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0b2a52463f..f3564e6017 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -51,6 +51,7 @@
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "catalog/toasting.h"
@@ -6723,6 +6724,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
* (possibly nested several levels deep in composite types, arrays, etc!).
* Eventually, we'd like to propagate the check or rewrite operation
* into such tables, but for now, just error out if we find any.
+ * Also, check if "typeOid" is used as type of some session variable.
*
* Caller should provide either the associated relation of a rowtype,
* or a type name (not both) for use in the error message, if any.
@@ -6786,6 +6788,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
continue;
}
+ /* check if the type is used as type of some session variable */
+ if (pg_depend->classid == VariableRelationId)
+ {
+ Oid varid = pg_depend->objid;
+
+ if (origTypeName)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it",
+ origTypeName,
+ get_namespace_name(get_session_variable_namespace(varid)),
+ get_session_variable_name(varid))));
+ else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it",
+ RelationGetRelationName(origRelation),
+ get_namespace_name(get_session_variable_namespace(varid)),
+ get_session_variable_name(varid))));
+ else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot alter foreign table \"%s\" because session variable \"%s.%s\" uses it",
+ RelationGetRelationName(origRelation),
+ get_namespace_name(get_session_variable_namespace(varid)),
+ get_session_variable_name(varid))));
+ else if (origRelation->rd_rel->relkind == RELKIND_RELATION ||
+ origRelation->rd_rel->relkind == RELKIND_MATVIEW ||
+ origRelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot alter table \"%s\" because session variable \"%s.%s\" uses it",
+ RelationGetRelationName(origRelation),
+ get_namespace_name(get_session_variable_namespace(varid)),
+ get_session_variable_name(varid))));
+
+ continue;
+ }
+
/* Else, ignore dependees that aren't relations */
if (pg_depend->classid != RelationRelationId)
continue;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a043fd4c66..3f2c590ce3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -53,6 +53,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_am.h"
#include "catalog/pg_trigger.h"
+#include "catalog/pg_variable.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
#include "gramparse.h"
@@ -295,8 +296,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
- CreateSchemaStmt CreateSeqStmt CreateStmt CreateStatsStmt CreateTableSpaceStmt
- CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
+ CreateSchemaStmt CreateSeqStmt CreateSessionVarStmt CreateStmt CreateStatsStmt
+ CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
CreateAssertionStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
@@ -790,8 +791,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
UESCAPE UNBOUNDED UNCONDITIONAL UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
UNLISTEN UNLOGGED UNTIL UPDATE USER USING
- VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
- VERBOSE VERSION_P VIEW VIEWS VOLATILE
+ VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIABLE VARIABLES
+ VARIADIC VARYING VERBOSE VERSION_P VIEW VIEWS VOLATILE
WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
@@ -1061,6 +1062,7 @@ stmt:
| CreatePolicyStmt
| CreatePLangStmt
| CreateSchemaStmt
+ | CreateSessionVarStmt
| CreateSeqStmt
| CreateStmt
| CreateSubscriptionStmt
@@ -1605,6 +1607,7 @@ schema_stmt:
| CreateTrigStmt
| GrantStmt
| ViewStmt
+ | CreateSessionVarStmt
;
@@ -5202,6 +5205,34 @@ create_extension_opt_item:
}
;
+/*****************************************************************************
+ *
+ * QUERY :
+ * CREATE VARIABLE varname [AS] type
+ *
+ *****************************************************************************/
+
+CreateSessionVarStmt:
+ CREATE VARIABLE qualified_name opt_as Typename opt_collate_clause
+ {
+ CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
+ n->variable = $3;
+ n->typeName = $5;
+ n->collClause = (CollateClause *) $6;
+ n->if_not_exists = false;
+ $$ = (Node *) n;
+ }
+ | CREATE VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause
+ {
+ CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
+ n->variable = $6;
+ n->typeName = $8;
+ n->collClause = (CollateClause *) $9;
+ n->if_not_exists = true;
+ $$ = (Node *) n;
+ }
+ ;
+
/*****************************************************************************
*
* ALTER EXTENSION name UPDATE [ TO version ]
@@ -7006,6 +7037,7 @@ object_type_any_name:
| TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; }
| TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; }
| TEXT_P SEARCH CONFIGURATION { $$ = OBJECT_TSCONFIGURATION; }
+ | VARIABLE { $$ = OBJECT_VARIABLE; }
;
/*
@@ -7882,6 +7914,14 @@ privilege_target:
n->objs = $2;
$$ = n;
}
+ | VARIABLE qualified_name_list
+ {
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+ n->targtype = ACL_TARGET_OBJECT;
+ n->objtype = OBJECT_VARIABLE;
+ n->objs = $2;
+ $$ = n;
+ }
| ALL TABLES IN_P SCHEMA name_list
{
PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -7927,6 +7967,14 @@ privilege_target:
n->objs = $5;
$$ = n;
}
+ | ALL VARIABLES IN_P SCHEMA name_list
+ {
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+ n->targtype = ACL_TARGET_ALL_IN_SCHEMA;
+ n->objtype = OBJECT_VARIABLE;
+ n->objs = $5;
+ $$ = n;
+ }
;
@@ -8124,6 +8172,7 @@ defacl_privilege_target:
| SEQUENCES { $$ = OBJECT_SEQUENCE; }
| TYPES_P { $$ = OBJECT_TYPE; }
| SCHEMAS { $$ = OBJECT_SCHEMA; }
+ | VARIABLES { $$ = OBJECT_VARIABLE; }
;
@@ -9906,6 +9955,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
n->missing_ok = false;
$$ = (Node *) n;
}
+ | ALTER VARIABLE any_name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_VARIABLE;
+ n->object = (Node *) $3;
+ n->newname = $6;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER VARIABLE IF_P EXISTS any_name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_VARIABLE;
+ n->object = (Node *) $5;
+ n->newname = $8;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
;
opt_column: COLUMN
@@ -10267,6 +10334,24 @@ AlterObjectSchemaStmt:
n->missing_ok = false;
$$ = (Node *) n;
}
+ | ALTER VARIABLE any_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_VARIABLE;
+ n->object = (Node *) $3;
+ n->newschema = $6;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER VARIABLE IF_P EXISTS any_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_VARIABLE;
+ n->object = (Node *) $5;
+ n->newschema = $8;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
;
/*****************************************************************************
@@ -10548,6 +10633,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
n->newowner = $6;
$$ = (Node *) n;
}
+ | ALTER VARIABLE any_name OWNER TO RoleSpec
+ {
+ AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+ n->objectType = OBJECT_VARIABLE;
+ n->object = (Node *) $3;
+ n->newowner = $6;
+ $$ = (Node *)n;
+ }
;
@@ -17878,6 +17971,8 @@ unreserved_keyword:
| VALIDATE
| VALIDATOR
| VALUE_P
+ | VARIABLE
+ | VARIABLES
| VARYING
| VERSION_P
| VIEW
@@ -18533,6 +18628,8 @@ bare_label_keyword:
| VALUE_P
| VALUES
| VARCHAR
+ | VARIABLE
+ | VARIABLES
| VARIADIC
| VERBOSE
| VERSION_P
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index d5c2b2ff0b..2fba90c95a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -107,6 +107,7 @@ typedef struct
List *indexes; /* CREATE INDEX items */
List *triggers; /* CREATE TRIGGER items */
List *grants; /* GRANT items */
+ List *variables; /* CREATE VARIABLE items */
} CreateSchemaStmtContext;
@@ -3966,6 +3967,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
cxt.indexes = NIL;
cxt.triggers = NIL;
cxt.grants = NIL;
+ cxt.variables = NIL;
/*
* Run through each schema element in the schema element list. Separate
@@ -4034,6 +4036,15 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
cxt.grants = lappend(cxt.grants, element);
break;
+ case T_CreateSessionVarStmt:
+ {
+ CreateSessionVarStmt *elp = (CreateSessionVarStmt *) element;
+
+ setSchemaName(cxt.schemaname, &elp->variable->schemaname);
+ cxt.variables = lappend(cxt.variables, element);
+ }
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(element));
@@ -4047,6 +4058,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
result = list_concat(result, cxt.indexes);
result = list_concat(result, cxt.triggers);
result = list_concat(result, cxt.grants);
+ result = list_concat(result, cxt.variables);
return result;
}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index fa66b8017e..ce8db7cda4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -23,6 +23,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_inherits.h"
+#include "catalog/pg_variable.h"
#include "catalog/toasting.h"
#include "commands/alter.h"
#include "commands/async.h"
@@ -182,6 +183,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
case T_CreateRangeStmt:
case T_CreateRoleStmt:
case T_CreateSchemaStmt:
+ case T_CreateSessionVarStmt:
case T_CreateSeqStmt:
case T_CreateStatsStmt:
case T_CreateStmt:
@@ -1386,6 +1388,10 @@ ProcessUtilitySlow(ParseState *pstate,
}
break;
+ case T_CreateSessionVarStmt:
+ address = CreateVariable(pstate, (CreateSessionVarStmt *) parsetree);
+ break;
+
/*
* ************* object creation / destruction **************
*/
@@ -2338,6 +2344,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
case OBJECT_STATISTIC_EXT:
tag = CMDTAG_ALTER_STATISTICS;
break;
+ case OBJECT_VARIABLE:
+ tag = CMDTAG_ALTER_VARIABLE;
+ break;
default:
tag = CMDTAG_UNKNOWN;
break;
@@ -2646,6 +2655,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_STATISTIC_EXT:
tag = CMDTAG_DROP_STATISTICS;
break;
+ case OBJECT_VARIABLE:
+ tag = CMDTAG_DROP_VARIABLE;
+ break;
default:
tag = CMDTAG_UNKNOWN;
}
@@ -3222,6 +3234,10 @@ CreateCommandTag(Node *parsetree)
}
break;
+ case T_CreateSessionVarStmt:
+ tag = CMDTAG_CREATE_VARIABLE;
+ break;
+
default:
elog(WARNING, "unrecognized node type: %d",
(int) nodeTag(parsetree));
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index d7b39140b3..5f80af4f5e 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -850,6 +850,10 @@ acldefault(ObjectType objtype, Oid ownerId)
world_default = ACL_NO_RIGHTS;
owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL;
break;
+ case OBJECT_VARIABLE:
+ world_default = ACL_NO_RIGHTS;
+ owner_default = ACL_ALL_RIGHTS_VARIABLE;
+ break;
default:
elog(ERROR, "unrecognized object type: %d", (int) objtype);
world_default = ACL_NO_RIGHTS; /* keep compiler quiet */
@@ -947,6 +951,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
case 'T':
objtype = OBJECT_TYPE;
break;
+ case 'V':
+ objtype = OBJECT_VARIABLE;
+ break;
default:
elog(ERROR, "unrecognized object type abbreviation: %c", objtypec);
}
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 48a280d089..2eed9489f9 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -38,6 +38,7 @@
#include "catalog/pg_subscription.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "utils/array.h"
@@ -3714,3 +3715,115 @@ get_subscription_name(Oid subid, bool missing_ok)
return subname;
}
+
+/* ---------- PG_VARIABLE CACHE ---------- */
+
+/*
+ * get_varname_varid
+ * Given name and namespace of variable, look up the OID.
+ */
+Oid
+get_varname_varid(const char *varname, Oid varnamespace)
+{
+ return GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+ PointerGetDatum(varname),
+ ObjectIdGetDatum(varnamespace));
+}
+
+/*
+ * get_session_variable_name
+ * Returns a palloc'd copy of the name of a given session variable.
+ */
+char *
+get_session_variable_name(Oid varid)
+{
+ HeapTuple tup;
+ Form_pg_variable varform;
+ char *varname;
+
+ tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+ varform = (Form_pg_variable) GETSTRUCT(tup);
+
+ varname = pstrdup(NameStr(varform->varname));
+
+ ReleaseSysCache(tup);
+
+ return varname;
+}
+
+/*
+ * get_session_variable_namespace
+ * Returns the pg_namespace OID associated with a given session variable.
+ */
+Oid
+get_session_variable_namespace(Oid varid)
+{
+ HeapTuple tup;
+ Form_pg_variable varform;
+ Oid varnamespace;
+
+ tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for variable %u", varid);
+
+ varform = (Form_pg_variable) GETSTRUCT(tup);
+
+ varnamespace = varform->varnamespace;
+
+ ReleaseSysCache(tup);
+
+ return varnamespace;
+}
+
+/*
+ * Returns the type of the given session variable.
+ */
+Oid
+get_session_variable_type(Oid varid)
+{
+ HeapTuple tup;
+ Form_pg_variable varform;
+ Oid vartype;
+
+ tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+ varform = (Form_pg_variable) GETSTRUCT(tup);
+
+ vartype = varform->vartype;
+
+ ReleaseSysCache(tup);
+
+ return vartype;
+}
+
+/*
+ * Returns the type, typmod and collid of the given session variable.
+ */
+void
+get_session_variable_type_typmod_collid(Oid varid, Oid *typid, int32 *typmod,
+ Oid *collid)
+{
+ HeapTuple tup;
+ Form_pg_variable varform;
+
+ tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+ varform = (Form_pg_variable) GETSTRUCT(tup);
+
+ *typid = varform->vartype;
+ *typmod = varform->vartypmod;
+ *collid = varform->varcollation;
+
+ ReleaseSysCache(tup);
+}
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index c323b5bd3d..4079ad603a 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -247,6 +247,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
pg_log_info("reading subscription membership of tables");
getSubscriptionTables(fout);
+ pg_log_info("reading variables");
+ getVariables(fout);
+
free(inhinfo); /* not needed any longer */
*numTablesPtr = numTables;
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 5649859aa1..50de5d0ea2 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -511,6 +511,12 @@ do { \
CONVERT_PRIV('r', "SELECT");
CONVERT_PRIV('w', "UPDATE");
}
+ else if (strcmp(type, "VARIABLE") == 0 ||
+ strcmp(type, "VARIABLES") == 0)
+ {
+ CONVERT_PRIV('r', "SELECT");
+ CONVERT_PRIV('w', "UPDATE");
+ }
else
abort();
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index fbf5f1c515..e81cfde274 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -133,12 +133,14 @@ typedef struct _restoreOptions
int selFunction;
int selTrigger;
int selTable;
+ int selVariable;
SimpleStringList indexNames;
SimpleStringList functionNames;
SimpleStringList schemaNames;
SimpleStringList schemaExcludeNames;
SimpleStringList triggerNames;
SimpleStringList tableNames;
+ SimpleStringList variableNames;
int useDB;
ConnParams cparams; /* parameters to use if useDB */
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 8c20c263c4..6f012a1b55 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3098,6 +3098,14 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
!simple_string_list_member(&ropt->triggerNames, te->tag))
return 0;
}
+ else if (strcmp(te->desc, "VARIABLE") == 0)
+ {
+ if (!ropt->selVariable)
+ return 0;
+ if (ropt->variableNames.head != NULL &&
+ !simple_string_list_member(&ropt->variableNames, te->tag))
+ return 0;
+ }
else
return 0;
}
@@ -3647,6 +3655,7 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te)
strcmp(type, "TEXT SEARCH DICTIONARY") == 0 ||
strcmp(type, "TEXT SEARCH CONFIGURATION") == 0 ||
strcmp(type, "TYPE") == 0 ||
+ strcmp(type, "VARIABLE") == 0 ||
strcmp(type, "VIEW") == 0 ||
/* non-schema-specified objects */
strcmp(type, "DATABASE") == 0 ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2b02148559..d8c74f4e2e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -324,6 +324,7 @@ static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo);
static void dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo);
static void dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo);
static void dumpSubscriptionTable(Archive *fout, const SubRelInfo *subrinfo);
+static void dumpVariable(Archive *fout, const VariableInfo *varinfo);
static void dumpDatabase(Archive *fout);
static void dumpDatabaseConfig(Archive *AH, PQExpBuffer outbuf,
const char *dbname, Oid dboid);
@@ -5313,6 +5314,190 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
return next_possible_free_oid;
}
+/*
+ * getVariables
+ * get information about variables
+ */
+void
+getVariables(Archive *fout)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ VariableInfo *varinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_varname;
+ int i_varnamespace;
+ int i_vartype;
+ int i_vartypname;
+ int i_varowner;
+ int i_varcollation;
+ int i_varacl;
+ int i_acldefault;
+ int i,
+ ntups;
+
+ if (fout->remoteVersion < 180000)
+ return;
+
+ query = createPQExpBuffer();
+
+ resetPQExpBuffer(query);
+
+ /* Get the variables in current database. */
+ appendPQExpBuffer(query,
+ "SELECT v.tableoid, v.oid, v.varname,\n"
+ " v.varnamespace, v.vartype,\n"
+ " pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n"
+ " CASE WHEN v.varcollation <> t.typcollation "
+ " THEN v.varcollation\n"
+ " ELSE 0\n"
+ " END AS varcollation,\n"
+ " v.varowner, v.varacl,\n"
+ " acldefault('V', v.varowner) AS acldefault\n"
+ "FROM pg_catalog.pg_variable v\n"
+ "JOIN pg_catalog.pg_type t "
+ "ON (v.vartype = t.oid)");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_varname = PQfnumber(res, "varname");
+ i_varnamespace = PQfnumber(res, "varnamespace");
+ i_vartype = PQfnumber(res, "vartype");
+ i_vartypname = PQfnumber(res, "vartypname");
+ i_varcollation = PQfnumber(res, "varcollation");
+
+ i_varowner = PQfnumber(res, "varowner");
+ i_varacl = PQfnumber(res, "varacl");
+ i_acldefault = PQfnumber(res, "acldefault");
+
+ varinfo = pg_malloc(ntups * sizeof(VariableInfo));
+
+ for (i = 0; i < ntups; i++)
+ {
+ TypeInfo *vtype;
+
+ varinfo[i].dobj.objType = DO_VARIABLE;
+ varinfo[i].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, i, i_tableoid));
+ varinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&varinfo[i].dobj);
+ varinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_varname));
+ varinfo[i].dobj.namespace =
+ findNamespace(atooid(PQgetvalue(res, i, i_varnamespace)));
+
+ varinfo[i].vartype = atooid(PQgetvalue(res, i, i_vartype));
+ varinfo[i].vartypname = pg_strdup(PQgetvalue(res, i, i_vartypname));
+ varinfo[i].varcollation = atooid(PQgetvalue(res, i, i_varcollation));
+
+ varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl));
+ varinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault));
+ varinfo[i].dacl.privtype = 0;
+ varinfo[i].dacl.initprivs = NULL;
+ varinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_varowner));
+
+ /* Do not try to dump ACL if no ACL exists. */
+ if (!PQgetisnull(res, i, i_varacl))
+ varinfo[i].dobj.components |= DUMP_COMPONENT_ACL;
+
+ if (strlen(varinfo[i].rolname) == 0)
+ pg_log_warning("owner of variable \"%s\" appears to be invalid",
+ varinfo[i].dobj.name);
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(varinfo[i].dobj), fout);
+
+ vtype = findTypeByOid(varinfo[i].vartype);
+ addObjectDependency(&varinfo[i].dobj, vtype->dobj.dumpId);
+ }
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpVariable
+ * dump the definition of the given session variable
+ */
+static void
+dumpVariable(Archive *fout, const VariableInfo *varinfo)
+{
+ DumpOptions *dopt = fout->dopt;
+
+ PQExpBuffer delq;
+ PQExpBuffer query;
+ char *qualvarname;
+ const char *vartypname;
+ Oid varcollation;
+
+ /* Skip if not to be dumped */
+ if (!varinfo->dobj.dump || dopt->dataOnly)
+ return;
+
+ delq = createPQExpBuffer();
+ query = createPQExpBuffer();
+
+ qualvarname = pg_strdup(fmtQualifiedDumpable(varinfo));
+ vartypname = varinfo->vartypname;
+ varcollation = varinfo->varcollation;
+
+ appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
+ qualvarname);
+
+ appendPQExpBuffer(query, "CREATE VARIABLE %s AS %s",
+ qualvarname, vartypname);
+
+ if (OidIsValid(varcollation))
+ {
+ CollInfo *coll;
+
+ coll = findCollationByOid(varcollation);
+ if (coll)
+ appendPQExpBuffer(query, " COLLATE %s",
+ fmtQualifiedDumpable(coll));
+ }
+
+ appendPQExpBuffer(query, ";\n");
+
+ if (varinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
+ ArchiveEntry(fout, varinfo->dobj.catId, varinfo->dobj.dumpId,
+ ARCHIVE_OPTS(.tag = varinfo->dobj.name,
+ .namespace = varinfo->dobj.namespace->dobj.name,
+ .owner = varinfo->rolname,
+ .description = "VARIABLE",
+ .section = SECTION_PRE_DATA,
+ .createStmt = query->data,
+ .dropStmt = delq->data));
+
+ /* Dump comment if any */
+ if (varinfo->dobj.dump & DUMP_COMPONENT_COMMENT)
+ dumpComment(fout, "VARIABLE", qualvarname,
+ NULL, varinfo->rolname,
+ varinfo->dobj.catId, 0, varinfo->dobj.dumpId);
+
+ /* Dump ACL if any */
+ if (varinfo->dobj.dump & DUMP_COMPONENT_ACL)
+ {
+ char *qvarname = pg_strdup(fmtId(varinfo->dobj.name));
+
+ dumpACL(fout, varinfo->dobj.dumpId, InvalidDumpId, "VARIABLE",
+ qvarname, NULL,
+ varinfo->dobj.namespace->dobj.name, NULL, varinfo->rolname,
+ &varinfo->dacl);
+
+ free(qvarname);
+ }
+
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(query);
+
+ free(qualvarname);
+}
+
static void
binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
PQExpBuffer upgrade_buffer,
@@ -9875,7 +10060,8 @@ getAdditionalACLs(Archive *fout)
dobj->objType == DO_TABLE ||
dobj->objType == DO_PROCLANG ||
dobj->objType == DO_FDW ||
- dobj->objType == DO_FOREIGN_SERVER)
+ dobj->objType == DO_FOREIGN_SERVER ||
+ dobj->objType == DO_VARIABLE)
{
DumpableObjectWithAcl *daobj = (DumpableObjectWithAcl *) dobj;
@@ -10474,6 +10660,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
case DO_SUBSCRIPTION_REL:
dumpSubscriptionTable(fout, (const SubRelInfo *) dobj);
break;
+ case DO_VARIABLE:
+ dumpVariable(fout, (VariableInfo *) dobj);
+ break;
case DO_PRE_DATA_BOUNDARY:
case DO_POST_DATA_BOUNDARY:
/* never dumped, nothing to do */
@@ -14923,6 +15112,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
case DEFACLOBJ_NAMESPACE:
type = "SCHEMAS";
break;
+ case DEFACLOBJ_VARIABLE:
+ type = "VARIABLES";
+ break;
default:
/* shouldn't get here */
pg_fatal("unrecognized object type in default privileges: %d",
@@ -18432,6 +18624,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_CONVERSION:
case DO_TABLE:
case DO_TABLE_ATTACH:
+ case DO_VARIABLE:
case DO_ATTRDEF:
case DO_PROCLANG:
case DO_CAST:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 4b2e5870a9..36a69a0832 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -52,6 +52,7 @@ typedef enum
DO_TABLE,
DO_TABLE_ATTACH,
DO_ATTRDEF,
+ DO_VARIABLE,
DO_INDEX,
DO_INDEX_ATTACH,
DO_STATSEXT,
@@ -692,6 +693,23 @@ typedef struct _SubRelInfo
char *srsublsn;
} SubRelInfo;
+/*
+ * The VariableInfo struct is used to represent session variables
+ */
+typedef struct _VariableInfo
+{
+ DumpableObject dobj;
+ DumpableAcl dacl;
+ Oid vartype;
+ char *vartypname;
+ char *varacl;
+ char *rvaracl;
+ char *initvaracl;
+ char *initrvaracl;
+ Oid varcollation;
+ const char *rolname; /* name of owner, or empty string */
+} VariableInfo;
+
/*
* common utility functions
*/
@@ -775,5 +793,6 @@ extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
int numTables);
extern void getSubscriptions(Archive *fout);
extern void getSubscriptionTables(Archive *fout);
+extern void getVariables(Archive *fout);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 4cb754caa5..ebb9bf037c 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -78,6 +78,7 @@ enum dbObjectTypePriorities
PRIO_DUMMY_TYPE,
PRIO_ATTRDEF,
PRIO_LARGE_OBJECT,
+ PRIO_VARIABLE,
PRIO_PRE_DATA_BOUNDARY, /* boundary! */
PRIO_TABLE_DATA,
PRIO_SEQUENCE_SET,
@@ -119,6 +120,7 @@ static const int dbObjectTypePriority[] =
[DO_TABLE] = PRIO_TABLE,
[DO_TABLE_ATTACH] = PRIO_TABLE_ATTACH,
[DO_ATTRDEF] = PRIO_ATTRDEF,
+ [DO_VARIABLE] = PRIO_VARIABLE,
[DO_INDEX] = PRIO_INDEX,
[DO_INDEX_ATTACH] = PRIO_INDEX_ATTACH,
[DO_STATSEXT] = PRIO_STATSEXT,
@@ -1490,6 +1492,10 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"POST-DATA BOUNDARY (ID %d)",
obj->dumpId);
return;
+ case DO_VARIABLE:
+ snprintf(buf, bufsize,
+ "VARIABLE %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
}
/* shouldn't get here */
snprintf(buf, bufsize,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 5bcc2244d5..0d3765eed4 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -789,6 +789,16 @@ my %tests = (
unlike => { no_privs => 1, },
},
+ 'ALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT SELECT ON VARIABLES TO PUBLIC'
+ => {
+ create_order => 56,
+ create_sql => 'ALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT SELECT ON VARIABLES TO PUBLIC;',
+ regexp => qr/^
+ \QALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT SELECT ON VARIABLES TO PUBLIC;\E/xm,
+ like => { %full_runs, section_post_data => 1, },
+ unlike => { no_privs => 1, },
+ },
+
'ALTER ROLE regress_dump_test_role' => {
regexp => qr/^
\QALTER ROLE regress_dump_test_role WITH \E
@@ -1638,6 +1648,23 @@ my %tests = (
},
},
+ 'COMMENT ON VARIABLE dump_test.variable1' => {
+ create_order => 71,
+ create_sql => 'COMMENT ON VARIABLE dump_test.variable1
+ IS \'comment on variable\';',
+ regexp =>
+ qr/^\QCOMMENT ON VARIABLE dump_test.variable1 IS 'comment on variable';\E/m,
+ like => {
+ %full_runs,
+ %dump_test_schema_runs,
+ section_pre_data => 1,
+ },
+ unlike => {
+ exclude_dump_test_schema => 1,
+ only_dump_measurement => 1,
+ },
+ },
+
'COPY test_table' => {
create_order => 4,
create_sql => 'INSERT INTO dump_test.test_table (col1) '
@@ -3896,6 +3923,24 @@ my %tests = (
},
},
+ 'CREATE VARIABLE test_variable' => {
+ all_runs => 1,
+ catch_all => 'CREATE ... commands',
+ create_order => 61,
+ create_sql => 'CREATE VARIABLE dump_test.variable1 AS integer;',
+ regexp => qr/^
+ \QCREATE VARIABLE dump_test.variable1 AS integer;\E/xm,
+ like => {
+ %full_runs,
+ %dump_test_schema_runs,
+ section_pre_data => 1,
+ },
+ unlike => {
+ exclude_dump_test_schema => 1,
+ only_dump_measurement => 1,
+ },
+ },
+
'CREATE VIEW test_view' => {
create_order => 61,
create_sql => 'CREATE VIEW dump_test.test_view
@@ -4358,6 +4403,25 @@ my %tests = (
like => {},
},
+ 'GRANT SELECT ON VARIABLE dump_test.variable1' => {
+ create_order => 73,
+ create_sql =>
+ 'GRANT SELECT ON VARIABLE dump_test.variable1 TO regress_dump_test_role;',
+ regexp => qr/^
+ \QGRANT SELECT ON VARIABLE dump_test.variable1 TO regress_dump_test_role;\E
+ /xm,
+ like => {
+ %full_runs,
+ %dump_test_schema_runs,
+ section_pre_data => 1,
+ },
+ unlike => {
+ exclude_dump_test_schema => 1,
+ no_privs => 1,
+ only_dump_measurement => 1,
+ },
+ },
+
'REFRESH MATERIALIZED VIEW matview' => {
regexp => qr/^\QREFRESH MATERIALIZED VIEW dump_test.matview;\E/m,
like =>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 180781ecd0..26a9a3fac7 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -986,6 +986,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
break;
}
break;
+ case 'V': /* Variables */
+ success = listVariables(pattern, show_verbose);
+ break;
case 'x': /* Extensions */
if (show_verbose)
success = listExtensionContents(pattern);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 7c9a1f234c..adf83ed197 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5148,6 +5148,102 @@ error_return:
return false;
}
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
+listVariables(const char *pattern, bool verbose)
+{
+ PQExpBufferData buf;
+ PGresult *res;
+ printQueryOpt myopt = pset.popt;
+ static const bool translate_columns[] = {false, false, false, false, false, false, false};
+
+ if (pset.sversion < 180000)
+ {
+ char sverbuf[32];
+
+ pg_log_error("The server (version %s) does not support session variables.",
+ formatPGVersionNumber(pset.sversion, false,
+ sverbuf, sizeof(sverbuf)));
+ return true;
+ }
+
+ initPQExpBuffer(&buf);
+
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname as \"%s\",\n"
+ " v.varname as \"%s\",\n"
+ " pg_catalog.format_type(v.vartype, v.vartypmod) as \"%s\",\n"
+ " (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type bt\n"
+ " WHERE c.oid = v.varcollation AND bt.oid = v.vartype AND v.varcollation <> bt.typcollation) as \"%s\",\n"
+ " pg_catalog.pg_get_userbyid(v.varowner) as \"%s\"\n",
+ gettext_noop("Schema"),
+ gettext_noop("Name"),
+ gettext_noop("Type"),
+ gettext_noop("Collation"),
+ gettext_noop("Owner"));
+
+ if (verbose)
+ {
+ appendPQExpBufferStr(&buf, ",\n ");
+ printACLColumn(&buf, "v.varacl");
+ appendPQExpBuffer(&buf,
+ ",\n pg_catalog.obj_description(v.oid, 'pg_variable') AS \"%s\"",
+ gettext_noop("Description"));
+ }
+
+ appendPQExpBufferStr(&buf,
+ "\nFROM pg_catalog.pg_variable v"
+ "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = v.varnamespace");
+
+ appendPQExpBufferStr(&buf, "\nWHERE true\n");
+ if (!pattern)
+ appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
+ " AND n.nspname <> 'information_schema'\n");
+
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "v.varname", NULL,
+ "pg_catalog.pg_variable_is_visible(v.oid)",
+ NULL, 3))
+ return false;
+
+ appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
+
+ res = PSQLexec(buf.data);
+ termPQExpBuffer(&buf);
+ if (!res)
+ return false;
+
+ /*
+ * Most functions in this file are content to print an empty table when
+ * there are no matching objects. We intentionally deviate from that
+ * here, but only in !quiet mode, for historical reasons.
+ */
+ if (PQntuples(res) == 0 && !pset.quiet)
+ {
+ if (pattern)
+ pg_log_error("Did not find any session variable named \"%s\".",
+ pattern);
+ else
+ pg_log_error("Did not find any session variables.");
+ }
+ else
+ {
+ myopt.nullPrint = NULL;
+ myopt.title = _("List of variables");
+ myopt.translate_header = true;
+ myopt.translate_columns = translate_columns;
+ myopt.n_translate_columns = lengthof(translate_columns);
+
+ printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+ }
+
+ PQclear(res);
+ return true;
+}
/*
* \dFp
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 273f974538..4e15a180ea 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -149,4 +149,7 @@ extern bool listOpFamilyFunctions(const char *access_method_pattern,
/* \dl or \lo_list */
extern bool listLargeObjects(bool verbose);
+/* \dV */
+extern bool listVariables(const char *pattern, bool varbose);
+
#endif /* DESCRIBE_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 6f58a11074..bdc327ffe3 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -265,6 +265,7 @@ slashUsage(unsigned short int pager)
HELP0(" \\dT[S+] [PATTERN] list data types\n");
HELP0(" \\du[S+] [PATTERN] list roles\n");
HELP0(" \\dv[S+] [PATTERN] list views\n");
+ HELP0(" \\dV [PATTERN] list session variables\n");
HELP0(" \\dx[+] [PATTERN] list extensions\n");
HELP0(" \\dX [PATTERN] list extended statistics\n");
HELP0(" \\dy[+] [PATTERN] list event triggers\n");
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 024469474d..873a83fa5a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -963,6 +963,13 @@ static const SchemaQuery Query_for_trigger_of_table = {
.refnamespace = "c1.relnamespace",
};
+static const SchemaQuery Query_for_list_of_variables = {
+ .min_server_version = 180000,
+ .catname = "pg_catalog.pg_variable v",
+ .viscondition = "pg_catalog.pg_variable_is_visible(v.oid)",
+ .namespace = "v.varnamespace",
+ .result = "v.varname",
+};
/*
* Queries to get lists of names of various kinds of things, possibly
@@ -1283,6 +1290,7 @@ static const pgsql_thing_t words_after_create[] = {
* TABLE ... */
{"USER", Query_for_list_of_roles, NULL, NULL, Keywords_for_user_thing},
{"USER MAPPING FOR", NULL, NULL, NULL},
+ {"VARIABLE", NULL, NULL, &Query_for_list_of_variables},
{"VIEW", NULL, NULL, &Query_for_list_of_views},
{NULL} /* end of list */
};
@@ -1722,7 +1730,7 @@ psql_completion(const char *text, int start, int end)
"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
"\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\dP", "\\dPi", "\\dPt",
"\\drds", "\\drg", "\\dRs", "\\dRp", "\\ds",
- "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dX", "\\dy",
+ "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dX", "\\dy", "\\dV",
"\\echo", "\\edit", "\\ef", "\\elif", "\\else", "\\encoding",
"\\endif", "\\errverbose", "\\ev",
"\\f",
@@ -2224,6 +2232,9 @@ psql_completion(const char *text, int start, int end)
"ALL");
else if (Matches("ALTER", "SYSTEM", "SET", MatchAny))
COMPLETE_WITH("TO");
+ /* ALTER VARIABLE <name> */
+ else if (Matches("ALTER", "VARIABLE", MatchAny))
+ COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA");
/* ALTER VIEW <name> */
else if (Matches("ALTER", "VIEW", MatchAny))
COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME", "RESET", "SET");
@@ -2804,7 +2815,7 @@ psql_completion(const char *text, int start, int end)
"ROUTINE", "RULE", "SCHEMA", "SEQUENCE", "SERVER",
"STATISTICS", "SUBSCRIPTION", "TABLE",
"TABLESPACE", "TEXT SEARCH", "TRANSFORM FOR",
- "TRIGGER", "TYPE", "VIEW");
+ "TRIGGER", "TYPE", "VARIABLE", "VIEW");
else if (Matches("COMMENT", "ON", "ACCESS", "METHOD"))
COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
else if (Matches("COMMENT", "ON", "CONSTRAINT"))
@@ -3266,7 +3277,7 @@ psql_completion(const char *text, int start, int end)
/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
- COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+ COMPLETE_WITH("SEQUENCE", "TABLE", "VARIABLE", "VIEW");
/* Complete "CREATE UNLOGGED" with TABLE or SEQUENCE */
else if (TailMatches("CREATE", "UNLOGGED"))
COMPLETE_WITH("TABLE", "SEQUENCE");
@@ -3580,6 +3591,13 @@ psql_completion(const char *text, int start, int end)
else if (TailMatches("=", MatchAnyExcept("*)")))
COMPLETE_WITH(",", ")");
}
+/* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+ /* Complete CREATE VARIABLE <name> with AS */
+ else if (TailMatches("CREATE", "VARIABLE", MatchAny))
+ COMPLETE_WITH("AS");
+ else if (TailMatches("VARIABLE", MatchAny, "AS"))
+ /* Complete CREATE VARIABLE <name> with AS types */
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS or WITH */
@@ -3843,6 +3861,12 @@ psql_completion(const char *text, int start, int end)
else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny, "LANGUAGE", MatchAny))
COMPLETE_WITH("CASCADE", "RESTRICT");
+ /* DROP VARIABLE */
+ else if (Matches("DROP", "VARIABLE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
+ else if (Matches("DROP", "VARIABLE", MatchAny))
+ COMPLETE_WITH("CASCADE", "RESTRICT");
+
/* EXECUTE */
else if (Matches("EXECUTE"))
COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -4046,7 +4070,7 @@ psql_completion(const char *text, int start, int end)
* objects supported.
*/
if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
- COMPLETE_WITH("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS");
+ COMPLETE_WITH("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS", "VARIABLES");
else
COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_grantables,
"ALL FUNCTIONS IN SCHEMA",
@@ -4068,7 +4092,8 @@ psql_completion(const char *text, int start, int end)
"SEQUENCE",
"TABLE",
"TABLESPACE",
- "TYPE");
+ "TYPE",
+ "VARIABLE");
}
else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL") ||
TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "ALL"))
@@ -4076,7 +4101,8 @@ psql_completion(const char *text, int start, int end)
"PROCEDURES IN SCHEMA",
"ROUTINES IN SCHEMA",
"SEQUENCES IN SCHEMA",
- "TABLES IN SCHEMA");
+ "TABLES IN SCHEMA",
+ "VARIABLES IN SCHEMA");
else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN") ||
TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "FOREIGN"))
COMPLETE_WITH("DATA WRAPPER", "SERVER");
@@ -4112,6 +4138,8 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
else if (TailMatches("TYPE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
+ else if (TailMatches("VARIABLE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
else if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny))
COMPLETE_WITH("TO");
else
@@ -4415,7 +4443,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
@@ -4883,6 +4911,8 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_QUERY(Query_for_list_of_roles);
else if (TailMatchesCS("\\dv*"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
+ else if (TailMatchesCS("\\dV*"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
else if (TailMatchesCS("\\dx*"))
COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
else if (TailMatchesCS("\\dX*"))
diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile
index 167f91a6e3..507f380821 100644
--- a/src/include/catalog/Makefile
+++ b/src/include/catalog/Makefile
@@ -81,7 +81,8 @@ CATALOG_HEADERS := \
pg_publication_namespace.h \
pg_publication_rel.h \
pg_subscription.h \
- pg_subscription_rel.h
+ pg_subscription_rel.h \
+ pg_variable.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h)
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index f70d1daba5..6eec79d2ee 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -69,6 +69,7 @@ catalog_headers = [
'pg_publication_rel.h',
'pg_subscription.h',
'pg_subscription_rel.h',
+ 'pg_variable.h',
]
# The .dat files we need can just be listed alphabetically.
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 8d434d48d5..18ea6ac83a 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -97,6 +97,8 @@ extern Oid TypenameGetTypid(const char *typname);
extern Oid TypenameGetTypidExtended(const char *typname, bool temp_ok);
extern bool TypeIsVisible(Oid typid);
+extern bool VariableIsVisible(Oid varid);
+
extern FuncCandidateList FuncnameGetCandidates(List *names,
int nargs, List *argnames,
bool expand_variadic,
@@ -169,6 +171,8 @@ extern SearchPathMatcher *GetSearchPathMatcher(MemoryContext context);
extern SearchPathMatcher *CopySearchPathMatcher(SearchPathMatcher *path);
extern bool SearchPathMatchesCurrentEnvironment(SearchPathMatcher *path);
+extern Oid LookupVariable(const char *nspname, const char *varname, bool missing_ok);
+
extern Oid get_collation_oid(List *collname, bool missing_ok);
extern Oid get_conversion_oid(List *conname, bool missing_ok);
extern Oid FindDefaultConversionProc(int32 for_encoding, int32 to_encoding);
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index d272cdf08b..529d53c6bb 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -68,6 +68,7 @@ MAKE_SYSCACHE(DEFACLROLENSPOBJ, pg_default_acl_role_nsp_obj_index, 8);
#define DEFACLOBJ_FUNCTION 'f' /* function */
#define DEFACLOBJ_TYPE 'T' /* type */
#define DEFACLOBJ_NAMESPACE 'n' /* namespace */
+#define DEFACLOBJ_VARIABLE 'V' /* variable */
#endif /* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 06b2f4ba66..837d8dfae1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6472,6 +6472,9 @@
proname => 'pg_collation_is_visible', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'oid',
prosrc => 'pg_collation_is_visible' },
+{ oid => '9221', descr => 'is session variable visible in search path?',
+ proname => 'pg_variable_is_visible', procost => '10', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'oid', prosrc => 'pg_variable_is_visible' },
{ oid => '2854', descr => 'get OID of current session\'s temp schema, if any',
proname => 'pg_my_temp_schema', provolatile => 's', proparallel => 'r',
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
new file mode 100644
index 0000000000..3810e040f2
--- /dev/null
+++ b/src/include/catalog/pg_variable.h
@@ -0,0 +1,81 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.h
+ * definition of session variables system catalog (pg_variables)
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_variable.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_VARIABLE_H
+#define PG_VARIABLE_H
+
+#include "catalog/genbki.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_variable_d.h"
+#include "utils/acl.h"
+
+/* ----------------
+ * pg_variable definition. cpp turns this into
+ * typedef struct FormData_pg_variable
+ * ----------------
+ */
+CATALOG(pg_variable,9222,VariableRelationId)
+{
+ Oid oid; /* oid */
+
+ /* OID of entry in pg_type for variable's type */
+ Oid vartype BKI_LOOKUP(pg_type);
+
+ /* variable name */
+ NameData varname;
+
+ /* OID of namespace containing variable class */
+ Oid varnamespace BKI_LOOKUP(pg_namespace);
+
+ /* variable owner */
+ Oid varowner BKI_LOOKUP(pg_authid);
+
+ /* typmod for variable's type */
+ int32 vartypmod BKI_DEFAULT(-1);
+
+ /* variable collation */
+ Oid varcollation BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_collation);
+
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+
+ /* access permissions */
+ aclitem varacl[1] BKI_DEFAULT(_null_);
+
+#endif
+} FormData_pg_variable;
+
+/* ----------------
+ * Form_pg_variable corresponds to a pointer to a tuple with
+ * the format of the pg_variable relation.
+ * ----------------
+ */
+typedef FormData_pg_variable *Form_pg_variable;
+
+DECLARE_TOAST(pg_variable, 9223, 9224);
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_variable_oid_index, 9225, VariableOidIndexId, pg_variable, btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_variable_varname_nsp_index, 9226, VariableNameNspIndexId, pg_variable, btree(varname name_ops, varnamespace oid_ops));
+
+extern ObjectAddress CreateVariable(ParseState *pstate,
+ CreateSessionVarStmt *stmt);
+extern void DropVariableById(Oid varid);
+
+MAKE_SYSCACHE(VARIABLENAMENSP, pg_variable_varname_nsp_index, 8);
+MAKE_SYSCACHE(VARIABLEOID, pg_variable_oid_index, 8);
+
+#endif /* PG_VARIABLE_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 85a62b538e..80ea5928ee 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2309,6 +2309,7 @@ typedef enum ObjectType
OBJECT_TSTEMPLATE,
OBJECT_TYPE,
OBJECT_USER_MAPPING,
+ OBJECT_VARIABLE,
OBJECT_VIEW,
} ObjectType;
@@ -3425,6 +3426,21 @@ typedef struct AlterStatsStmt
bool missing_ok; /* skip error if statistics object is missing */
} AlterStatsStmt;
+
+/* ----------------------
+ * {Create|Alter} VARIABLE Statement
+ * ----------------------
+ */
+typedef struct CreateSessionVarStmt
+{
+ NodeTag type;
+ RangeVar *variable; /* the variable to create */
+ TypeName *typeName; /* the type of variable */
+ CollateClause *collClause;
+ bool if_not_exists; /* do nothing if it already exists */
+} CreateSessionVarStmt;
+
+
/* ----------------------
* Create Function Statement
* ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f7fe834cf4..c884cd5c46 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -486,6 +486,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("variables", VARIABLES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 7fdcec6dd9..9465df7b2f 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -68,6 +68,7 @@ PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false)
PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_VARIABLE, "ALTER VARIABLE", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false)
PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false)
PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false)
@@ -123,6 +124,7 @@ PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_VARIABLE, "CREATE VARIABLE", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false)
PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false)
PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false)
@@ -175,6 +177,7 @@ PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false)
PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false)
PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false)
PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_VARIABLE, "DROP VARIABLE", true, false, false)
PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false)
PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false)
PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false)
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 731d84b2a9..98aab98229 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -169,6 +169,7 @@ typedef struct ArrayType Acl;
#define ACL_ALL_RIGHTS_SCHEMA (ACL_USAGE|ACL_CREATE)
#define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE)
#define ACL_ALL_RIGHTS_TYPE (ACL_USAGE)
+#define ACL_ALL_RIGHTS_VARIABLE (ACL_SELECT|ACL_UPDATE)
/* operation codes for pg_*_aclmask */
typedef enum
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 20446f6f83..8a3684e711 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -134,6 +134,7 @@ extern char get_func_prokind(Oid funcid);
extern bool get_func_leakproof(Oid funcid);
extern RegProcedure get_func_support(Oid funcid);
extern Oid get_relname_relid(const char *relname, Oid relnamespace);
+extern Oid get_varname_varid(const char *varname, Oid varnamespace);
extern char *get_rel_name(Oid relid);
extern Oid get_rel_namespace(Oid relid);
extern Oid get_rel_type_id(Oid relid);
@@ -206,6 +207,14 @@ extern char *get_publication_name(Oid pubid, bool missing_ok);
extern Oid get_subscription_oid(const char *subname, bool missing_ok);
extern char *get_subscription_name(Oid subid, bool missing_ok);
+extern char *get_session_variable_name(Oid varid);
+extern Oid get_session_variable_namespace(Oid varid);
+extern Oid get_session_variable_type(Oid varid);
+extern void get_session_variable_type_typmod_collid(Oid varid,
+ Oid *typid,
+ int32 *typmod,
+ Oid *collid);
+
#define type_is_array(typid) (get_element_type(typid) != InvalidOid)
/* type_is_array_domain accepts both plain arrays and domains over arrays */
#define type_is_array_domain(typid) (get_base_element_type(typid) != InvalidOid)
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 74d9ff2998..c336f67a59 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -151,3 +151,20 @@ owner of type deptest_t
DROP OWNED BY regress_dep_user2, regress_dep_user0;
DROP USER regress_dep_user2;
DROP USER regress_dep_user0;
+-- dependency on type
+CREATE DOMAIN vardomain AS int;
+CREATE TYPE vartype AS (a int, b int, c vardomain);
+CREATE VARIABLE var1 AS vartype;
+-- should fail
+DROP DOMAIN vardomain;
+ERROR: cannot drop type vardomain because other objects depend on it
+DETAIL: column c of composite type vartype depends on type vardomain
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TYPE vartype;
+ERROR: cannot drop type vartype because other objects depend on it
+DETAIL: session variable var1 depends on type vartype
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- clean up
+DROP VARIABLE var1;
+DROP TYPE vartype;
+DROP DOMAIN vardomain;
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be..d995332140 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -266,3 +266,7 @@ NOTICE: checking pg_subscription {subdbid} => pg_database {oid}
NOTICE: checking pg_subscription {subowner} => pg_authid {oid}
NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid}
NOTICE: checking pg_subscription_rel {srrelid} => pg_class {oid}
+NOTICE: checking pg_variable {vartype} => pg_type {oid}
+NOTICE: checking pg_variable {varnamespace} => pg_namespace {oid}
+NOTICE: checking pg_variable {varowner} => pg_authid {oid}
+NOTICE: checking pg_variable {varcollation} => pg_collation {oid}
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 3bbe4c5f97..c89067490f 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5828,6 +5828,30 @@ COMMIT;
# final ON_ERROR_ROLLBACK: off
DROP TABLE bla;
DROP FUNCTION psql_error;
+-- session variable test
+CREATE ROLE regress_variable_owner;
+SET ROLE TO regress_variable_owner;
+CREATE VARIABLE var1 AS varchar COLLATE "C";
+\dV+ var1
+ List of variables
+ Schema | Name | Type | Collation | Owner | Access privileges | Description
+--------+------+-------------------+-----------+------------------------+-------------------+-------------
+ public | var1 | character varying | C | regress_variable_owner | |
+(1 row)
+
+GRANT SELECT ON VARIABLE var1 TO PUBLIC;
+COMMENT ON VARIABLE var1 IS 'some description';
+\dV+ var1
+ List of variables
+ Schema | Name | Type | Collation | Owner | Access privileges | Description
+--------+------+-------------------+-----------+------------------------+--------------------------------------------------+------------------
+ public | var1 | character varying | C | regress_variable_owner | regress_variable_owner=rw/regress_variable_owner+| some description
+ | | | | | =r/regress_variable_owner |
+(1 row)
+
+DROP VARIABLE var1;
+SET ROLE TO DEFAULT;
+DROP ROLE regress_variable_owner;
-- check describing invalid multipart names
\dA regression.heap
improper qualified name (too many dotted names): regression.heap
@@ -6049,6 +6073,12 @@ cross-database references are not implemented: nonesuch.public.func_deps_stat
improper qualified name (too many dotted names): regression.myevt
\dy nonesuch.myevt
improper qualified name (too many dotted names): nonesuch.myevt
+\dV host.regression.public.var
+improper qualified name (too many dotted names): host.regression.public.var
+\dV regression|mydb.public.var
+cross-database references are not implemented: regression|mydb.public.var
+\dV nonesuch.public.var
+cross-database references are not implemented: nonesuch.public.var
-- check that dots within quoted name segments are not counted
\dA "no.such.access.method"
List of access methods
@@ -6283,6 +6313,12 @@ List of schemas
------+-------+-------+---------+----------+------
(0 rows)
+\dV "no.such.variable"
+ List of variables
+ Schema | Name | Type | Collation | Owner
+--------+------+------+-----------+-------
+(0 rows)
+
-- again, but with dotted schema qualifications.
\dA "no.such.schema"."no.such.access.method"
improper qualified name (too many dotted names): "no.such.schema"."no.such.access.method"
@@ -6452,6 +6488,12 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
\dy "no.such.schema"."no.such.event.trigger"
improper qualified name (too many dotted names): "no.such.schema"."no.such.event.trigger"
+\dV "no.such.schema"."no.such.variable"
+ List of variables
+ Schema | Name | Type | Collation | Owner
+--------+------+------+-----------+-------
+(0 rows)
+
-- again, but with current database and dotted schema qualifications.
\dt regression."no.such.schema"."no.such.table.relation"
List of relations
@@ -6585,6 +6627,12 @@ List of text search templates
--------+------+------------+-----------+--------------+-----
(0 rows)
+\dV regression."no.such.schema"."no.such.variable"
+ List of variables
+ Schema | Name | Type | Collation | Owner
+--------+------+------+-----------+-------
+(0 rows)
+
-- again, but with dotted database and dotted schema qualifications.
\dt "no.such.database"."no.such.schema"."no.such.table.relation"
cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.table.relation"
@@ -6632,6 +6680,8 @@ cross-database references are not implemented: "no.such.database"."no.such.schem
cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.data.type"
\dX "no.such.database"."no.such.schema"."no.such.extended.statistics"
cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.extended.statistics"
+\dV "no.such.database"."no.such.schema"."no.such.variable"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.variable"
-- check \drg and \du
CREATE ROLE regress_du_role0;
CREATE ROLE regress_du_role1;
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
new file mode 100644
index 0000000000..aca766042d
--- /dev/null
+++ b/src/test/regress/expected/session_variables.out
@@ -0,0 +1,57 @@
+CREATE ROLE regress_variable_owner;
+-- should be ok
+CREATE VARIABLE var1 AS int;
+-- should fail, pseudotypes are not allowed
+CREATE VARIABLE var2 AS anyelement;
+ERROR: session variable cannot be pseudo-type anyelement
+-- should be ok, do nothing
+DROP VARIABLE IF EXISTS var2;
+NOTICE: session variable "var2" does not exist, skipping
+-- do nothing
+CREATE VARIABLE IF NOT EXISTS var1 AS int;
+NOTICE: session variable "var1" already exists, skipping
+-- should fail
+CREATE VARIABLE var1 AS int;
+ERROR: session variable "var1" already exists
+-- should be ok
+DROP VARIABLE IF EXISTS var1;
+-- check comment on variable
+CREATE VARIABLE var1 AS int;
+COMMENT ON VARIABLE var1 IS 'some variable comment';
+SELECT pg_catalog.obj_description(oid, 'pg_variable') FROM pg_variable WHERE varname = 'var1';
+ obj_description
+-----------------------
+ some variable comment
+(1 row)
+
+DROP VARIABLE var1;
+--- check access rights and supported ALTER
+CREATE SCHEMA svartest;
+GRANT ALL ON SCHEMA svartest TO regress_variable_owner;
+CREATE VARIABLE svartest.var1 AS int;
+CREATE ROLE regress_variable_reader;
+GRANT SELECT ON VARIABLE svartest.var1 TO regress_variable_reader;
+REVOKE ALL ON VARIABLE svartest.var1 FROM regress_variable_reader;
+ALTER VARIABLE svartest.var1 OWNER TO regress_variable_owner;
+ALTER VARIABLE svartest.var1 RENAME TO varxx;
+ALTER VARIABLE svartest.varxx SET SCHEMA public;
+DROP VARIABLE public.varxx;
+ALTER DEFAULT PRIVILEGES
+ FOR ROLE regress_variable_owner
+ IN SCHEMA svartest
+ GRANT SELECT ON VARIABLES TO regress_variable_reader;
+-- creating variable with default privileges
+SET ROLE TO regress_variable_owner;
+CREATE VARIABLE svartest.var1 AS int;
+SET ROLE TO DEFAULT;
+\dV+ svartest.var1
+ List of variables
+ Schema | Name | Type | Collation | Owner | Access privileges | Description
+----------+------+---------+-----------+------------------------+--------------------------------------------------+-------------
+ svartest | var1 | integer | | regress_variable_owner | regress_variable_owner=rw/regress_variable_owner+|
+ | | | | | regress_variable_reader=r/regress_variable_owner |
+(1 row)
+
+DROP VARIABLE svartest.var1;
+DROP SCHEMA svartest;
+DROP ROLE regress_variable_owner;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 2429ec2bba..ed6893d479 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
# NB: temp.sql does a reconnect which transiently uses 2 connections,
# so keep this parallel group to at most 19 tests
# ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml session_variables
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/sql/dependency.sql b/src/test/regress/sql/dependency.sql
index 8d74ed7122..6c18b7f840 100644
--- a/src/test/regress/sql/dependency.sql
+++ b/src/test/regress/sql/dependency.sql
@@ -114,3 +114,17 @@ DROP USER regress_dep_user2;
DROP OWNED BY regress_dep_user2, regress_dep_user0;
DROP USER regress_dep_user2;
DROP USER regress_dep_user0;
+
+-- dependency on type
+CREATE DOMAIN vardomain AS int;
+CREATE TYPE vartype AS (a int, b int, c vardomain);
+CREATE VARIABLE var1 AS vartype;
+
+-- should fail
+DROP DOMAIN vardomain;
+DROP TYPE vartype;
+
+-- clean up
+DROP VARIABLE var1;
+DROP TYPE vartype;
+DROP DOMAIN vardomain;
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 3b3c6f6e29..b80d56a27a 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1591,6 +1591,19 @@ COMMIT;
DROP TABLE bla;
DROP FUNCTION psql_error;
+-- session variable test
+CREATE ROLE regress_variable_owner;
+SET ROLE TO regress_variable_owner;
+CREATE VARIABLE var1 AS varchar COLLATE "C";
+\dV+ var1
+GRANT SELECT ON VARIABLE var1 TO PUBLIC;
+COMMENT ON VARIABLE var1 IS 'some description';
+\dV+ var1
+DROP VARIABLE var1;
+
+SET ROLE TO DEFAULT;
+DROP ROLE regress_variable_owner;
+
-- check describing invalid multipart names
\dA regression.heap
\dA nonesuch.heap
@@ -1702,6 +1715,9 @@ DROP FUNCTION psql_error;
\dX nonesuch.public.func_deps_stat
\dy regression.myevt
\dy nonesuch.myevt
+\dV host.regression.public.var
+\dV regression|mydb.public.var
+\dV nonesuch.public.var
-- check that dots within quoted name segments are not counted
\dA "no.such.access.method"
@@ -1743,6 +1759,8 @@ DROP FUNCTION psql_error;
\dx "no.such.installed.extension"
\dX "no.such.extended.statistics"
\dy "no.such.event.trigger"
+\dV "no.such.variable"
+
-- again, but with dotted schema qualifications.
\dA "no.such.schema"."no.such.access.method"
@@ -1783,6 +1801,7 @@ DROP FUNCTION psql_error;
\dx "no.such.schema"."no.such.installed.extension"
\dX "no.such.schema"."no.such.extended.statistics"
\dy "no.such.schema"."no.such.event.trigger"
+\dV "no.such.schema"."no.such.variable"
-- again, but with current database and dotted schema qualifications.
\dt regression."no.such.schema"."no.such.table.relation"
@@ -1807,6 +1826,7 @@ DROP FUNCTION psql_error;
\dP regression."no.such.schema"."no.such.partitioned.relation"
\dT regression."no.such.schema"."no.such.data.type"
\dX regression."no.such.schema"."no.such.extended.statistics"
+\dV regression."no.such.schema"."no.such.variable"
-- again, but with dotted database and dotted schema qualifications.
\dt "no.such.database"."no.such.schema"."no.such.table.relation"
@@ -1832,6 +1852,7 @@ DROP FUNCTION psql_error;
\dP "no.such.database"."no.such.schema"."no.such.partitioned.relation"
\dT "no.such.database"."no.such.schema"."no.such.data.type"
\dX "no.such.database"."no.such.schema"."no.such.extended.statistics"
+\dV "no.such.database"."no.such.schema"."no.such.variable"
-- check \drg and \du
CREATE ROLE regress_du_role0;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
new file mode 100644
index 0000000000..42dfb95a02
--- /dev/null
+++ b/src/test/regress/sql/session_variables.sql
@@ -0,0 +1,61 @@
+CREATE ROLE regress_variable_owner;
+
+-- should be ok
+CREATE VARIABLE var1 AS int;
+
+-- should fail, pseudotypes are not allowed
+CREATE VARIABLE var2 AS anyelement;
+
+-- should be ok, do nothing
+DROP VARIABLE IF EXISTS var2;
+
+-- do nothing
+CREATE VARIABLE IF NOT EXISTS var1 AS int;
+
+-- should fail
+CREATE VARIABLE var1 AS int;
+
+-- should be ok
+DROP VARIABLE IF EXISTS var1;
+
+-- check comment on variable
+CREATE VARIABLE var1 AS int;
+COMMENT ON VARIABLE var1 IS 'some variable comment';
+SELECT pg_catalog.obj_description(oid, 'pg_variable') FROM pg_variable WHERE varname = 'var1';
+
+DROP VARIABLE var1;
+
+--- check access rights and supported ALTER
+CREATE SCHEMA svartest;
+GRANT ALL ON SCHEMA svartest TO regress_variable_owner;
+
+CREATE VARIABLE svartest.var1 AS int;
+
+CREATE ROLE regress_variable_reader;
+
+GRANT SELECT ON VARIABLE svartest.var1 TO regress_variable_reader;
+REVOKE ALL ON VARIABLE svartest.var1 FROM regress_variable_reader;
+
+ALTER VARIABLE svartest.var1 OWNER TO regress_variable_owner;
+ALTER VARIABLE svartest.var1 RENAME TO varxx;
+ALTER VARIABLE svartest.varxx SET SCHEMA public;
+
+DROP VARIABLE public.varxx;
+
+ALTER DEFAULT PRIVILEGES
+ FOR ROLE regress_variable_owner
+ IN SCHEMA svartest
+ GRANT SELECT ON VARIABLES TO regress_variable_reader;
+
+-- creating variable with default privileges
+SET ROLE TO regress_variable_owner;
+CREATE VARIABLE svartest.var1 AS int;
+SET ROLE TO DEFAULT;
+
+\dV+ svartest.var1
+
+DROP VARIABLE svartest.var1;
+
+DROP SCHEMA svartest;
+
+DROP ROLE regress_variable_owner;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3deb6113b8..837e5b83a3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -533,6 +533,7 @@ CreateRoleStmt
CreateSchemaStmt
CreateSchemaStmtContext
CreateSeqStmt
+CreateSessionVarStmt
CreateStatsStmt
CreateStmt
CreateStmtContext
@@ -872,6 +873,7 @@ FormData_pg_ts_parser
FormData_pg_ts_template
FormData_pg_type
FormData_pg_user_mapping
+FormData_pg_variable
FormExtraData_pg_attribute
Form_pg_aggregate
Form_pg_am
@@ -931,6 +933,7 @@ Form_pg_ts_parser
Form_pg_ts_template
Form_pg_type
Form_pg_user_mapping
+Form_pg_variable
FormatNode
FreeBlockNumberArray
FreeListData
@@ -3061,6 +3064,7 @@ VarString
VarStringSortSupport
Variable
VariableAssignHook
+VariableInfo
VariableSetKind
VariableSetStmt
VariableShowStmt
--
2.45.2
[text/x-patch] v20240731-0002-Storage-for-session-variables-and-SQL-inte.patch (145.4K, 3-v20240731-0002-Storage-for-session-variables-and-SQL-inte.patch)
download | inline diff:
From 5bb113483c85f5ebfc8f244288e0b0e9da3caf2b Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email protected]>
Date: Wed, 31 Jul 2024 11:42:39 +0200
Subject: [PATCH v20240731 2/2] Storage for session variables and SQL interface
Session variables are stored in session memory in a dedicated hash table.
They are set by the LET command and read by the SELECT command.
The access rights should be checked.
The identifiers of session variables should always be shadowed by possible
column identifiers: we don't want to break an application by creating some
badly named session variable.
The limits of this patch (solved by other patches):
- session variables block parallel execution
- session variables blocks simple expression evaluation (in plpgsql)
- SQL functions with session variables are not inlined
- CALL statement is not supported (usage of direct access to express executor)
- memory used by dropped session variables is not released
Implementations of EXPLAIN LET and PREPARE LET statements
are in separate patches (for better readability)
---
doc/src/sgml/catalogs.sgml | 13 +
doc/src/sgml/ddl.sgml | 32 +
doc/src/sgml/event-trigger.sgml | 24 +
doc/src/sgml/parallel.sgml | 6 +
doc/src/sgml/plpgsql.sgml | 14 +
doc/src/sgml/ref/allfiles.sgml | 1 +
doc/src/sgml/ref/alter_variable.sgml | 1 +
doc/src/sgml/ref/create_variable.sgml | 1 +
doc/src/sgml/ref/drop_variable.sgml | 1 +
doc/src/sgml/ref/let.sgml | 96 ++
doc/src/sgml/reference.sgml | 1 +
src/backend/catalog/dependency.c | 5 +
src/backend/catalog/namespace.c | 317 ++++++
src/backend/catalog/pg_variable.c | 2 +
src/backend/commands/Makefile | 1 +
src/backend/commands/meson.build | 1 +
src/backend/commands/session_variable.c | 574 +++++++++++
src/backend/executor/Makefile | 1 +
src/backend/executor/execExpr.c | 42 +
src/backend/executor/execMain.c | 47 +
src/backend/executor/meson.build | 1 +
src/backend/executor/svariableReceiver.c | 201 ++++
src/backend/nodes/nodeFuncs.c | 10 +
src/backend/optimizer/plan/planner.c | 9 +
src/backend/optimizer/plan/setrefs.c | 135 ++-
src/backend/optimizer/prep/prepjointree.c | 3 +
src/backend/optimizer/util/clauses.c | 35 +-
src/backend/parser/analyze.c | 264 ++++-
src/backend/parser/gram.y | 49 +-
src/backend/parser/parse_agg.c | 9 +
src/backend/parser/parse_expr.c | 220 ++++-
src/backend/parser/parse_func.c | 2 +
src/backend/tcop/dest.c | 7 +
src/backend/tcop/pquery.c | 3 +
src/backend/tcop/utility.c | 16 +
src/backend/utils/adt/ruleutils.c | 46 +
src/backend/utils/cache/plancache.c | 41 +-
src/backend/utils/fmgr/fmgr.c | 10 +-
src/bin/psql/tab-complete.c | 12 +-
src/include/catalog/namespace.h | 2 +
src/include/catalog/pg_variable.h | 8 +
src/include/commands/session_variable.h | 32 +
src/include/executor/execdesc.h | 4 +
src/include/executor/svariableReceiver.h | 22 +
src/include/nodes/execnodes.h | 16 +
src/include/nodes/parsenodes.h | 17 +
src/include/nodes/pathnodes.h | 5 +
src/include/nodes/plannodes.h | 4 +-
src/include/nodes/primnodes.h | 6 +
src/include/optimizer/planmain.h | 2 +
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +
src/include/tcop/cmdtaglist.h | 1 +
src/include/tcop/dest.h | 1 +
src/pl/plpgsql/src/pl_exec.c | 3 +-
.../regress/expected/session_variables.out | 912 ++++++++++++++++++
src/test/regress/sql/session_variables.sql | 625 ++++++++++++
src/tools/pgindent/typedefs.list | 5 +
58 files changed, 3899 insertions(+), 23 deletions(-)
create mode 100644 doc/src/sgml/ref/let.sgml
create mode 100644 src/backend/commands/session_variable.c
create mode 100644 src/backend/executor/svariableReceiver.c
create mode 100644 src/include/commands/session_variable.h
create mode 100644 src/include/executor/svariableReceiver.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index de122e543f..ac3b04ff91 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9777,6 +9777,19 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>varcreate_lsn</structfield> <type>XLogRecPtr</type>
+ </para>
+ <para>
+ LSN of the transaction where the variable was created.
+ <structfield>varcreate_lsn</structfield> and
+ <structfield>oid</structfield> together form the all-time unique
+ identifier (<structfield>oid</structfield> alone is not enough, since
+ object identifiers can get reused).
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>varname</structfield> <type>name</type>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 7531de17fd..574540ca6f 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5317,6 +5317,38 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
VARIABLE</command> command.
</para>
+ <para>
+ The value of a session variable is set with the SQL statement
+ <command>LET</command>. The value of a session variable can be retrieved
+ with the SQL statement <command>SELECT</command>.
+<programlisting>
+CREATE VARIABLE var1 AS date;
+LET var1 = current_date;
+SELECT var1;
+</programlisting>
+
+ or
+
+<programlisting>
+CREATE VARIABLE public.current_user_id AS integer;
+GRANT READ ON VARIABLE public.current_user_id TO PUBLIC;
+LET current_user_id = (SELECT id FROM users WHERE usename = session_user);
+SELECT current_user_id;
+</programlisting>
+ </para>
+
+ <para>
+ The value of a session variable is local to the current session. Retrieving
+ a variable's value returns a <literal>NULL</literal>, unless its value has
+ been set to something else in the current session using the
+ <command>LET</command> command. Session variables are not transactional:
+ any changes made to the value of a session variable in a transaction won't
+ be undone if the transaction is rolled back (just like variables in
+ procedural languages). Session variables themselves are persistent, but
+ their values are neither persistent nor shared (like the content of
+ temporary tables).
+ </para>
+
<para>
Inside a query or an expression, a session variable can be
<quote>shadowed</quote> by a column with the same name. Similarly, the
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 15b2af99dc..578b97afee 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -432,6 +432,14 @@
<entry align="center"><literal>-</literal></entry>
<entry align="left"></entry>
</row>
+ <row>
+ <entry align="left"><literal>ALTER VARIABLE</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
<row>
<entry align="left"><literal>ALTER VIEW</literal></entry>
<entry align="center"><literal>X</literal></entry>
@@ -728,6 +736,14 @@
<entry align="center"><literal>-</literal></entry>
<entry align="left"></entry>
</row>
+ <row>
+ <entry align="left"><literal>CREATE VARIABLE</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
<row>
<entry align="left"><literal>CREATE VIEW</literal></entry>
<entry align="center"><literal>X</literal></entry>
@@ -1024,6 +1040,14 @@
<entry align="center"><literal>-</literal></entry>
<entry align="left"></entry>
</row>
+ <row>
+ <entry align="left"><literal>DROP VARIABLE</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
<row>
<entry align="left"><literal>DROP VIEW</literal></entry>
<entry align="center"><literal>X</literal></entry>
diff --git a/doc/src/sgml/parallel.sgml b/doc/src/sgml/parallel.sgml
index 590cb385dd..a5cef4cd79 100644
--- a/doc/src/sgml/parallel.sgml
+++ b/doc/src/sgml/parallel.sgml
@@ -521,6 +521,12 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
Plan nodes that reference a correlated <literal>SubPlan</literal>.
</para>
</listitem>
+
+ <listitem>
+ <para>
+ Plan nodes that use a session variable.
+ </para>
+ </listitem>
</itemizedlist>
<sect2 id="parallel-labeling">
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index a675923867..c1196c7107 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -6034,6 +6034,20 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE;
</programlisting>
</para>
</sect3>
+
+ <sect3 id="plpgsql-porting-package-variables">
+ <title><command>Packages and package variables</command></title>
+
+ <para>
+ The <application>PL/pgSQL</application> language has no packages, and
+ therefore no package variables or package constants.
+ You can consider translating an Oracle package into a schema in
+ <productname>PostgreSQL</productname>. Package functions and procedures
+ would then become functions and procedures in that schema, and package
+ variables could be translated to session variables in that schema.
+ (see <xref linkend="ddl-session-variables"\>).
+ </para>
+ </sect3>
</sect2>
<sect2 id="plpgsql-porting-appendix">
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 2f67de3e21..cc3bd5ab54 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -158,6 +158,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY grant SYSTEM "grant.sgml">
<!ENTITY importForeignSchema SYSTEM "import_foreign_schema.sgml">
<!ENTITY insert SYSTEM "insert.sgml">
+<!ENTITY let SYSTEM "let.sgml">
<!ENTITY listen SYSTEM "listen.sgml">
<!ENTITY load SYSTEM "load.sgml">
<!ENTITY lock SYSTEM "lock.sgml">
diff --git a/doc/src/sgml/ref/alter_variable.sgml b/doc/src/sgml/ref/alter_variable.sgml
index d87570e7d8..d2036351e5 100644
--- a/doc/src/sgml/ref/alter_variable.sgml
+++ b/doc/src/sgml/ref/alter_variable.sgml
@@ -173,6 +173,7 @@ ALTER VARIABLE boo SET SCHEMA private;
<simplelist type="inline">
<member><xref linkend="sql-createvariable"/></member>
<member><xref linkend="sql-dropvariable"/></member>
+ <member><xref linkend="sql-let"/></member>
</simplelist>
</refsect1>
</refentry>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index ec165ab96f..05bf67e341 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -145,6 +145,7 @@ SELECT var1;
<simplelist type="inline">
<member><xref linkend="sql-altervariable"/></member>
<member><xref linkend="sql-dropvariable"/></member>
+ <member><xref linkend="sql-let"/></member>
</simplelist>
</refsect1>
diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml
index 5bdb3560f0..67988b5fcd 100644
--- a/doc/src/sgml/ref/drop_variable.sgml
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -111,6 +111,7 @@ DROP VARIABLE var1;
<simplelist type="inline">
<member><xref linkend="sql-altervariable"/></member>
<member><xref linkend="sql-createvariable"/></member>
+ <member><xref linkend="sql-let"/></member>
</simplelist>
</refsect1>
diff --git a/doc/src/sgml/ref/let.sgml b/doc/src/sgml/ref/let.sgml
new file mode 100644
index 0000000000..f6640576be
--- /dev/null
+++ b/doc/src/sgml/ref/let.sgml
@@ -0,0 +1,96 @@
+<!--
+doc/src/sgml/ref/let.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-let">
+ <indexterm zone="sql-let">
+ <primary>LET</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>session variable</primary>
+ <secondary>changing</secondary>
+ </indexterm>
+
+ <refmeta>
+ <refentrytitle>LET</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>LET</refname>
+ <refpurpose>change a session variable's value</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+LET <replaceable class="parameter">session_variable</replaceable> = <replaceable class="parameter">sql_expression</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ The <command>LET</command> command assigns a value to the specified session
+ variable.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>session_variable</literal></term>
+ <listitem>
+ <para>
+ The name of the session variable.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>sql_expression</literal></term>
+ <listitem>
+ <para>
+ An arbitrary SQL expression. The result must be of a data type that can
+ be cast to the type of the session variable in an assignment.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ <para>
+ Example:
+<programlisting>
+CREATE VARIABLE myvar AS integer;
+LET myvar = 10;
+LET myvar = (SELECT sum(val) FROM tab);
+</programlisting>
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ The <command>LET</command> is a <productname>PostgreSQL</productname>
+ extension.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-altervariable"/></member>
+ <member><xref linkend="sql-createvariable"/></member>
+ <member><xref linkend="sql-dropvariable"/></member>
+ </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 25578f3946..13e4adc5df 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -186,6 +186,7 @@
&grant;
&importForeignSchema;
&insert;
+ &let;
&listen;
&load;
&lock;
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index c9d686665d..148719e490 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1864,6 +1864,11 @@ find_expr_references_walker(Node *node,
{
Param *param = (Param *) node;
+ /* a variable parameter depends on the session variable */
+ if (param->paramkind == PARAM_VARIABLE)
+ add_object_address(VariableRelationId, param->paramvarid, 0,
+ context->addrs);
+
/* A parameter must depend on the parameter's datatype */
add_object_address(TypeRelationId, param->paramtype, 0,
context->addrs);
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 8752e1646e..2e128ef7ec 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -3399,6 +3399,323 @@ LookupVariable(const char *nspname,
return varoid;
}
+/*
+ * The input list contains names with indirection expressions used as the left
+ * part of LET statement. The following routine returns a new list with only
+ * initial strings (names) - without indirection expressions.
+ */
+List *
+NamesFromList(List *names)
+{
+ ListCell *l;
+ List *result = NIL;
+
+ foreach(l, names)
+ {
+ Node *n = lfirst(l);
+
+ if (IsA(n, String))
+ {
+ result = lappend(result, n);
+ }
+ else
+ break;
+ }
+
+ return result;
+}
+
+/* -----
+ * IdentifyVariable - try to find a variable from a list of identifiers
+ *
+ * Returns the OID of the variable found, or InvalidOid.
+ *
+ * "names" is a list of up to four identifiers; possible meanings are:
+ * - variable (searched on the search_path)
+ * - schema.variable
+ * - variable.attribute (searched on the search_path)
+ * - schema.variable.attribute
+ * - database.schema.variable
+ * - database.schema.variable.attribute
+ *
+ * If there is more than one way to identify a variable, "not_unique" will be
+ * set to true.
+ *
+ * Unless "noerror" is true, an error is raised if there are more than four
+ * identifiers in the list, or if the named database is not the current one.
+ * This is useful if we want to identify a shadowed variable.
+ *
+ * If an attribute is identified, it is stored in "attrname", otherwise the
+ * parameter is set to NULL.
+ *
+ * The identified session variable will be locked with an AccessShareLock.
+ * -----
+ */
+Oid
+IdentifyVariable(List *names, char **attrname, bool *not_unique, bool noerror)
+{
+ Node *field1 = NULL;
+ Node *field2 = NULL;
+ Node *field3 = NULL;
+ Node *field4 = NULL;
+ char *a = NULL;
+ char *b = NULL;
+ char *c = NULL;
+ char *d = NULL;
+ Oid varid = InvalidOid;
+ Oid old_varid = InvalidOid;
+ uint64 inval_count;
+ bool retry = false;
+
+ /*
+ * DDL operations can change the results of a name lookup. Since all such
+ * operations will generate invalidation messages, we keep track of
+ * whether any such messages show up while we're performing the operation,
+ * and retry until either (1) no more invalidation messages show up or (2)
+ * the answer doesn't change.
+ */
+ for (;;)
+ {
+ Oid varoid_without_attr = InvalidOid;
+ Oid varoid_with_attr = InvalidOid;
+
+ *not_unique = false;
+ *attrname = NULL;
+ varid = InvalidOid;
+
+ inval_count = SharedInvalidMessageCounter;
+
+ switch (list_length(names))
+ {
+ case 1:
+ field1 = linitial(names);
+
+ Assert(IsA(field1, String));
+
+ varid = LookupVariable(NULL, strVal(field1), true);
+ break;
+
+ case 2:
+ field1 = linitial(names);
+ field2 = lsecond(names);
+
+ Assert(IsA(field1, String));
+ a = strVal(field1);
+
+ if (IsA(field2, String))
+ {
+ /* when both fields are of string type */
+ b = strVal(field2);
+
+ /*
+ * a.b can mean "schema"."variable" or "variable"."field".
+ * Check both variants, and returns InvalidOid with
+ * not_unique flag, when both interpretations are
+ * possible.
+ */
+ varoid_without_attr = LookupVariable(a, b, true);
+ varoid_with_attr = LookupVariable(NULL, a, true);
+ }
+ else
+ {
+ /* The last field of list can be star too. */
+ Assert(IsA(field2, A_Star));
+
+ /*
+ * In this case, the field1 should be variable name. But
+ * direct unboxing of composite session variables is not
+ * supported now, and then we don't need to try lookup
+ * related variable.
+ *
+ * Unboxing is supported by syntax (var).*
+ */
+ return InvalidOid;
+ }
+
+ if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+ {
+ *not_unique = true;
+ varid = varoid_without_attr;
+ }
+ else if (OidIsValid(varoid_without_attr))
+ {
+ varid = varoid_without_attr;
+ }
+ else if (OidIsValid(varoid_with_attr))
+ {
+ *attrname = b;
+ varid = varoid_with_attr;
+ }
+ break;
+
+ case 3:
+ {
+ bool field1_is_catalog = false;
+
+ field1 = linitial(names);
+ field2 = lsecond(names);
+ field3 = lthird(names);
+
+ Assert(IsA(field1, String));
+ Assert(IsA(field2, String));
+
+ a = strVal(field1);
+ b = strVal(field2);
+
+ if (IsA(field3, String))
+ {
+ c = strVal(field3);
+
+ /*
+ * a.b.c can mean catalog.schema.variable or
+ * schema.variable.field.
+ *
+ * Check both variants, and set not_unique flag, when
+ * both interpretations are possible.
+ *
+ * When third node is star, only possible
+ * interpretation is schema.variable.*, but this
+ * pattern is not supported now.
+ */
+ varoid_with_attr = LookupVariable(a, b, true);
+
+ /*
+ * check pattern catalog.schema.variable only when
+ * there is possibility to success.
+ */
+ if (strcmp(a, get_database_name(MyDatabaseId)) == 0)
+ {
+ field1_is_catalog = true;
+ varoid_without_attr = LookupVariable(b, c, true);
+ }
+ }
+ else
+ {
+ Assert(IsA(field3, A_Star));
+ return InvalidOid;
+ }
+
+ if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+ {
+ *not_unique = true;
+ varid = varoid_without_attr;
+ }
+ else if (OidIsValid(varoid_without_attr))
+ {
+ varid = varoid_without_attr;
+ }
+ else if (OidIsValid(varoid_with_attr))
+ {
+ *attrname = c;
+ varid = varoid_with_attr;
+ }
+
+ /*
+ * When we didn't find variable, we can (when it is
+ * allowed) raise cross-database reference error.
+ */
+ if (!OidIsValid(varid) && !noerror && !field1_is_catalog)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cross-database references are not implemented: %s",
+ NameListToString(names))));
+ }
+ break;
+
+ case 4:
+ {
+ field1 = linitial(names);
+ field2 = lsecond(names);
+ field3 = lthird(names);
+ field4 = lfourth(names);
+
+ Assert(IsA(field1, String));
+ Assert(IsA(field2, String));
+ Assert(IsA(field3, String));
+
+ a = strVal(field1);
+ b = strVal(field2);
+ c = strVal(field3);
+
+ /*
+ * In this case, "a" is used as catalog name - check it.
+ */
+ if (strcmp(a, get_database_name(MyDatabaseId)) != 0)
+ {
+ if (!noerror)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cross-database references are not implemented: %s",
+ NameListToString(names))));
+ }
+ else
+ {
+ if (IsA(field4, String))
+ {
+ d = strVal(field4);
+ }
+ else
+ {
+ Assert(IsA(field4, A_Star));
+ return InvalidOid;
+ }
+
+ *attrname = d;
+ varid = LookupVariable(b, c, true);
+ }
+ }
+ break;
+
+ default:
+ if (!noerror)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name (too many dotted names): %s",
+ NameListToString(names))));
+ return InvalidOid;
+ }
+
+ /*
+ * If, upon retry, we get back the same OID we did last time, then the
+ * invalidation messages we processed did not change the final answer.
+ * So we're done.
+ *
+ * If we got a different OID, we've locked the variable that used to
+ * have this name rather than the one that does now. So release the
+ * lock.
+ */
+ if (retry)
+ {
+ if (old_varid == varid)
+ break;
+
+ if (OidIsValid(old_varid))
+ UnlockDatabaseObject(VariableRelationId, old_varid, 0, AccessShareLock);
+ }
+
+ /*
+ * Lock the variable. This will also accept any pending invalidation
+ * messages. If we got back InvalidOid, indicating not found, then
+ * there's nothing to lock, but we accept invalidation messages
+ * anyway, to flush any negative catcache entries that may be
+ * lingering.
+ */
+ if (!OidIsValid(varid))
+ AcceptInvalidationMessages();
+ else if (OidIsValid(varid))
+ LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+ if (inval_count == SharedInvalidMessageCounter)
+ break;
+
+ retry = true;
+ old_varid = varid;
+ varid = InvalidOid;
+ }
+
+ return varid;
+}
+
/*
* DeconstructQualifiedName
* Given a possibly-qualified name expressed as a list of String nodes,
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
index f65121440a..1c73181c3d 100644
--- a/src/backend/catalog/pg_variable.c
+++ b/src/backend/catalog/pg_variable.c
@@ -26,6 +26,7 @@
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
+#include "utils/pg_lsn.h"
#include "utils/syscache.h"
static ObjectAddress create_variable(const char *varName,
@@ -101,6 +102,7 @@ create_variable(const char *varName,
varid = GetNewOidWithIndex(rel, VariableOidIndexId, Anum_pg_variable_oid);
values[Anum_pg_variable_oid - 1] = ObjectIdGetDatum(varid);
+ values[Anum_pg_variable_varcreate_lsn - 1] = LSNGetDatum(GetXLogInsertRecPtr());
values[Anum_pg_variable_varname - 1] = NameGetDatum(&varname);
values[Anum_pg_variable_varnamespace - 1] = ObjectIdGetDatum(varNamespace);
values[Anum_pg_variable_vartype - 1] = ObjectIdGetDatum(varType);
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91..1cfaeca51e 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -50,6 +50,7 @@ OBJS = \
schemacmds.o \
seclabel.o \
sequence.o \
+ session_variable.o \
statscmds.o \
subscriptioncmds.o \
tablecmds.o \
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 6dd00a4abd..ca621be5ec 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -38,6 +38,7 @@ backend_sources += files(
'schemacmds.c',
'seclabel.c',
'sequence.c',
+ 'session_variable.c',
'statscmds.c',
'subscriptioncmds.c',
'tablecmds.c',
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
new file mode 100644
index 0000000000..23e7fa99f7
--- /dev/null
+++ b/src/backend/commands/session_variable.c
@@ -0,0 +1,574 @@
+/*-------------------------------------------------------------------------
+ *
+ * session_variable.c
+ * session variable creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/commands/session_variable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
+#include "executor/svariableReceiver.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "rewrite/rewriteHandler.h"
+#include "storage/lmgr.h"
+#include "storage/proc.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+/*
+ * The values of session variables are stored in the backend's private memory
+ * in the dedicated memory context SVariableMemoryContext in binary format.
+ * They are stored in the "sessionvars" hash table, whose key is the OID of the
+ * variable. However, the OID is not good enough to identify a session
+ * variable: concurrent sessions could drop the session variable and create a
+ * new one, which could be assigned the same OID. To ensure that the values
+ * stored in memory and the catalog definition match, we also keep track of
+ * the "create_lsn". Before any access to the variable values, we need to
+ * check if the LSN stored in memory matches the LSN in the catalog. If there
+ * is a mismatch between the LSNs, or if the OID is not present in pg_variable
+ * at all, the value stored in memory is released.
+ */
+typedef struct SVariableData
+{
+ Oid varid; /* pg_variable OID of the variable (hash key) */
+ XLogRecPtr create_lsn;
+
+ bool isnull;
+ Datum value;
+
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ bool is_domain;
+
+ /*
+ * domain_check_extra holds cached domain metadata. This "extra" is
+ * usually stored in fn_mcxt. We do not have access to that memory context
+ * for session variables, but we can use TopTransactionContext instead.
+ * A fresh value is forced when we detect we are in a different transaction
+ * (the local transaction ID differs from domain_check_extra_lxid).
+ */
+ void *domain_check_extra;
+ LocalTransactionId domain_check_extra_lxid;
+
+ /*
+ * Stored value and type description can be outdated when we receive a
+ * sinval message. We then have to check if the stored data are still
+ * trustworthy.
+ */
+ bool is_valid;
+
+ uint32 hashvalue; /* used for pairing sinval message */
+} SVariableData;
+
+typedef SVariableData *SVariable;
+
+static HTAB *sessionvars = NULL; /* hash table for session variables */
+
+static MemoryContext SVariableMemoryContext = NULL;
+
+/*
+ * Callback function for session variable invalidation.
+ */
+static void
+pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
+{
+ HASH_SEQ_STATUS status;
+ SVariable svar;
+
+ elog(DEBUG1, "pg_variable_cache_callback %u %u", cacheid, hashvalue);
+
+ Assert(sessionvars);
+
+ /*
+ * If the hashvalue is not specified, we have to recheck all currently
+ * used session variables. Since we can't tell the exact session variable
+ * from its hashvalue, we have to iterate over all items in the hash bucket.
+ */
+ hash_seq_init(&status, sessionvars);
+
+ while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+ {
+ if (hashvalue == 0 || svar->hashvalue == hashvalue)
+ {
+ svar->is_valid = false;
+ }
+ }
+}
+
+/*
+ * Release stored value, free memory
+ */
+static void
+free_session_variable_value(SVariable svar)
+{
+ /* clean the current value */
+ if (!svar->isnull)
+ {
+ if (!svar->typbyval)
+ pfree(DatumGetPointer(svar->value));
+
+ svar->isnull = true;
+ }
+
+ svar->value = (Datum) 0;
+}
+
+/*
+ * Returns true when the entry in pg_variable is consistent with the given
+ * session variable.
+ */
+static bool
+is_session_variable_valid(SVariable svar)
+{
+ HeapTuple tp;
+ bool result = false;
+
+ Assert(OidIsValid(svar->varid));
+
+ tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+ if (HeapTupleIsValid(tp))
+ {
+ /*
+ * The OID alone is not enough as an unique identifier, because OID
+ * values get recycled, and a new session variable could have got
+ * the same OID. We do a second check against the 64-bit LSN when
+ * the variable was created.
+ */
+ if (svar->create_lsn == ((Form_pg_variable) GETSTRUCT(tp))->varcreate_lsn)
+ result = true;
+
+ ReleaseSysCache(tp);
+ }
+
+ return result;
+}
+
+/*
+ * Initialize attributes cached in "svar"
+ */
+static void
+setup_session_variable(SVariable svar, Oid varid)
+{
+ HeapTuple tup;
+ Form_pg_variable varform;
+
+ Assert(OidIsValid(varid));
+
+ tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+ varform = (Form_pg_variable) GETSTRUCT(tup);
+
+ svar->varid = varid;
+ svar->create_lsn = varform->varcreate_lsn;
+
+ svar->typid = varform->vartype;
+
+ get_typlenbyval(svar->typid, &svar->typlen, &svar->typbyval);
+
+ svar->is_domain = (get_typtype(varform->vartype) == TYPTYPE_DOMAIN);
+ svar->domain_check_extra = NULL;
+ svar->domain_check_extra_lxid = InvalidLocalTransactionId;
+
+ svar->isnull = true;
+ svar->value = (Datum) 0;
+
+ svar->is_valid = true;
+
+ svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
+ ObjectIdGetDatum(varid));
+
+ ReleaseSysCache(tup);
+}
+
+/*
+ * Assign a new value to the session variable. It is copied to
+ * SVariableMemoryContext if necessary.
+ *
+ * If any error happens, the existing value won't be modified.
+ */
+static void
+set_session_variable(SVariable svar, Datum value, bool isnull)
+{
+ Datum newval;
+ SVariableData locsvar,
+ *_svar;
+
+ Assert(svar);
+ Assert(!isnull || value == (Datum) 0);
+
+ /*
+ * Use typbyval, typbylen from session variable only when they are
+ * trustworthy (the invalidation message was not accepted for this
+ * variable). If the variable might be invalid, force setup.
+ *
+ * Do not overwrite the passed session variable until we can be certain
+ * that no error can be thrown.
+ */
+ if (!svar->is_valid)
+ {
+ setup_session_variable(&locsvar, svar->varid);
+ _svar = &locsvar;
+ }
+ else
+ _svar = svar;
+
+ if (!isnull)
+ {
+ MemoryContext oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+
+ newval = datumCopy(value, _svar->typbyval, _svar->typlen);
+
+ MemoryContextSwitchTo(oldcxt);
+ }
+ else
+ newval = value;
+
+ free_session_variable_value(svar);
+
+ elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new value",
+ get_namespace_name(get_session_variable_namespace(svar->varid)),
+ get_session_variable_name(svar->varid),
+ svar->varid);
+
+ /* no more error expected, so we can overwrite the old variable now */
+ if (svar != _svar)
+ memcpy(svar, _svar, sizeof(SVariableData));
+
+ svar->value = newval;
+ svar->isnull = isnull;
+}
+
+/*
+ * Create the hash table for storing session variables.
+ */
+static void
+create_sessionvars_hashtables(void)
+{
+ HASHCTL vars_ctl;
+
+ Assert(!sessionvars);
+
+ if (!SVariableMemoryContext)
+ {
+ /* read sinval messages */
+ CacheRegisterSyscacheCallback(VARIABLEOID,
+ pg_variable_cache_callback,
+ (Datum) 0);
+
+ /* we need our own long-lived memory context */
+ SVariableMemoryContext =
+ AllocSetContextCreate(TopMemoryContext,
+ "session variables",
+ ALLOCSET_START_SMALL_SIZES);
+ }
+
+ memset(&vars_ctl, 0, sizeof(vars_ctl));
+ vars_ctl.keysize = sizeof(Oid);
+ vars_ctl.entrysize = sizeof(SVariableData);
+ vars_ctl.hcxt = SVariableMemoryContext;
+
+ sessionvars = hash_create("Session variables", 64, &vars_ctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+/*
+ * Search a session variable in the hash table given its OID. If it
+ * doesn't exist, then insert it there.
+ *
+ * The caller is responsible for doing permission checks.
+ *
+ * As a side effect, this function acquires a AccessShareLock on the
+ * session variable until the end of the transaction.
+ */
+static SVariable
+get_session_variable(Oid varid)
+{
+ SVariable svar;
+ bool found;
+
+ /* protect the used session variable against DROP */
+ LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+ if (!sessionvars)
+ create_sessionvars_hashtables();
+
+ svar = (SVariable) hash_search(sessionvars, &varid,
+ HASH_ENTER, &found);
+
+ if (found)
+ {
+ if (!svar->is_valid)
+ {
+ /*
+ * If there was an invalidation message, the variable might still be
+ * valid, but we have to check with the system catalog.
+ */
+ if (is_session_variable_valid(svar))
+ svar->is_valid = true;
+ else
+ /* if the value cannot be validated, we have to discard it */
+ free_session_variable_value(svar);
+ }
+ }
+ else
+ svar->is_valid = false;
+
+ /*
+ * Force setup for not yet initialized variables or variables that cannot
+ * be validated.
+ */
+ if (!svar->is_valid)
+ {
+ setup_session_variable(svar, varid);
+
+ elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by READ)",
+ get_namespace_name(get_session_variable_namespace(varid)),
+ get_session_variable_name(varid),
+ varid);
+ }
+
+ /* ensure the returned data is still of the correct domain */
+ if (svar->is_domain)
+ {
+ /*
+ * Store "extra" for domain_check() in TopTransactionContext. When we
+ * are in a new transaction, domain_check_extra cache is not valid any
+ * more.
+ */
+ if (svar->domain_check_extra_lxid != MyProc->vxid.lxid)
+ svar->domain_check_extra = NULL;
+
+ domain_check(svar->value, svar->isnull,
+ svar->typid, &svar->domain_check_extra,
+ TopTransactionContext);
+
+ svar->domain_check_extra_lxid = MyProc->vxid.lxid;
+ }
+
+ return svar;
+}
+
+/*
+ * Store the given value in a session variable in the cache.
+ *
+ * The caller is responsible for doing permission checks.
+ *
+ * As a side effect, this function acquires a AccessShareLock on the session
+ * variable until the end of the transaction.
+ */
+void
+SetSessionVariable(Oid varid, Datum value, bool isNull)
+{
+ SVariable svar;
+ bool found;
+
+ /* protect used session variable against DROP */
+ LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+ if (!sessionvars)
+ create_sessionvars_hashtables();
+
+ svar = (SVariable) hash_search(sessionvars, &varid,
+ HASH_ENTER, &found);
+
+ if (!found)
+ {
+ setup_session_variable(svar, varid);
+
+ elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by WRITE)",
+ get_namespace_name(get_session_variable_namespace(svar->varid)),
+ get_session_variable_name(svar->varid),
+ varid);
+ }
+
+ /* if this fails, it won't change the stored value */
+ set_session_variable(svar, value, isNull);
+}
+
+/*
+ * Wrapper around SetSessionVariable with permission checks.
+ */
+void
+SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull)
+{
+ AclResult aclresult;
+
+ /* is the caller allowed to update the session variable? */
+ aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));
+
+ SetSessionVariable(varid, value, isNull);
+}
+
+/*
+ * Returns a copy of the value stored in a variable.
+ */
+static inline Datum
+copy_session_variable_value(SVariable svar, bool *isNull)
+{
+ Datum value;
+
+ /* force copy of non NULL value */
+ if (!svar->isnull)
+ {
+ value = datumCopy(svar->value, svar->typbyval, svar->typlen);
+ *isNull = false;
+ }
+ else
+ {
+ value = (Datum) 0;
+ *isNull = true;
+ }
+
+ return value;
+}
+
+/*
+ * Returns a copy of the value of the session variable (in the current memory
+ * context). The caller is responsible for permission checks.
+ */
+Datum
+GetSessionVariable(Oid varid, bool *isNull, Oid *typid)
+{
+ SVariable svar;
+
+ svar = get_session_variable(varid);
+
+ /*
+ * Although svar is freshly validated in this point, the svar->is_valid can
+ * be false, due possible accepting invalidation message inside domain
+ * check. Now, the validation is done after lock, that can also accept
+ * invalidation message, so validation should be trustful.
+ *
+ * For now, we don't need to repeat validation. Only svar should be valid
+ * pointer.
+ */
+ Assert(svar);
+
+ *typid = svar->typid;
+
+ return copy_session_variable_value(svar, isNull);
+}
+
+/*
+ * Returns a copy of the value of the session variable after checking if the
+ * type is the same as "expected_typid". The caller is responsible for
+ * permission checks.
+ */
+Datum
+GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
+{
+ SVariable svar;
+
+ svar = get_session_variable(varid);
+
+ Assert(svar && svar->is_valid);
+
+ if (expected_typid != svar->typid)
+ elog(ERROR, "type of variable \"%s.%s\" is different than expected",
+ get_namespace_name(get_session_variable_namespace(varid)),
+ get_session_variable_name(varid));
+
+ return copy_session_variable_value(svar, isNull);
+}
+
+/*
+ * Assign the result of the evaluated expression to the session variable
+ */
+void
+ExecuteLetStmt(ParseState *pstate,
+ LetStmt *stmt,
+ ParamListInfo params,
+ QueryEnvironment *queryEnv,
+ QueryCompletion *qc)
+{
+ Query *query = castNode(Query, stmt->query);
+ List *rewritten;
+ DestReceiver *dest;
+ AclResult aclresult;
+ PlannedStmt *plan;
+ QueryDesc *queryDesc;
+ Oid varid = query->resultVariable;
+
+ Assert(OidIsValid(varid));
+
+ /* do we have permission to write to the session variable? */
+ aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));
+
+ /* create a dest receiver for LET */
+ dest = CreateVariableDestReceiver(varid);
+
+ /* run the query rewriter */
+ query = copyObject(query);
+
+ rewritten = QueryRewrite(query);
+
+ Assert(list_length(rewritten) == 1);
+
+ query = linitial_node(Query, rewritten);
+ Assert(query->commandType == CMD_SELECT);
+
+ /* plan the query */
+ plan = pg_plan_query(query, pstate->p_sourcetext,
+ CURSOR_OPT_PARALLEL_OK, params);
+
+ /*
+ * Use a snapshot with an updated command ID to ensure this query sees the
+ * results of any previously executed queries. (This could only matter if
+ * the planner executed an allegedly-stable function that changed the
+ * database contents, but let's do it anyway to be parallel to the EXPLAIN
+ * code path.)
+ */
+ PushCopiedSnapshot(GetActiveSnapshot());
+ UpdateActiveSnapshotCommandId();
+
+ /* create a QueryDesc, redirecting output to our tuple receiver */
+ queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
+ GetActiveSnapshot(), InvalidSnapshot,
+ dest, params, queryEnv, 0);
+
+ /* call ExecutorStart to prepare the plan for execution */
+ ExecutorStart(queryDesc, 0);
+
+ /*
+ * Run the plan to completion. The result should be only one row. To
+ * check if there are too many result rows, we try to fetch two.
+ */
+ ExecutorRun(queryDesc, ForwardScanDirection, 2L, true);
+
+ /* save the rowcount if we're given a QueryCompletion to fill */
+ if (qc)
+ SetQueryCompletion(qc, CMDTAG_LET, queryDesc->estate->es_processed);
+
+ /* and clean up */
+ ExecutorFinish(queryDesc);
+ ExecutorEnd(queryDesc);
+
+ FreeQueryDesc(queryDesc);
+
+ PopActiveSnapshot();
+}
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 11118d0ce0..71248a34f2 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -76,6 +76,7 @@ OBJS = \
nodeWindowAgg.o \
nodeWorktablescan.o \
spi.o \
+ svariableReceiver.o \
tqueue.o \
tstoreReceiver.o
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 9b52bab52f..3fdd9cb630 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -34,6 +34,7 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
#include "executor/execExpr.h"
#include "executor/nodeSubplan.h"
#include "funcapi.h"
@@ -988,6 +989,47 @@ ExecInitExprRec(Expr *node, ExprState *state,
scratch.d.param.paramtype = param->paramtype;
ExprEvalPushStep(state, &scratch);
break;
+
+ case PARAM_VARIABLE:
+ {
+ int es_num_session_variables = 0;
+ SessionVariableValue *es_session_variables = NULL;
+ SessionVariableValue *var;
+
+ if (state->parent && state->parent->state)
+ {
+ es_session_variables = state->parent->state->es_session_variables;
+ es_num_session_variables = state->parent->state->es_num_session_variables;
+ }
+
+ Assert(es_session_variables);
+
+ /*
+ * Use buffered session variables when the
+ * buffer with copied values is avaiable
+ * (standard query executor mode)
+ */
+
+ /* Parameter sanity checks. */
+ if (param->paramid >= es_num_session_variables)
+ elog(ERROR, "paramid of PARAM_VARIABLE param is out of range");
+
+ var = &es_session_variables[param->paramid];
+
+ if (var->typid != param->paramtype)
+ elog(ERROR, "type of buffered value is different than PARAM_VARIABLE type");
+
+ /*
+ * In this case, pass the value like a
+ * constant.
+ */
+ scratch.opcode = EEOP_CONST;
+ scratch.d.constval.value = var->value;
+ scratch.d.constval.isnull = var->isnull;
+ ExprEvalPushStep(state, &scratch);
+ }
+ break;
+
case PARAM_EXTERN:
/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4d7c92d63c..f2fb680def 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -43,7 +43,9 @@
#include "access/xact.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
+#include "catalog/pg_variable.h"
#include "commands/matview.h"
+#include "commands/session_variable.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
@@ -196,6 +198,51 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
Assert(queryDesc->sourceText != NULL);
estate->es_sourceText = queryDesc->sourceText;
+ /*
+ * The executor doesn't work with session variables directly. Values of
+ * related session variables are copied to dedicated array, and this array
+ * is passed to executor.
+ */
+ if (queryDesc->plannedstmt->sessionVariables)
+ {
+ ListCell *lc;
+ int nSessionVariables;
+ int i = 0;
+
+ /*
+ * In this case, the query uses session variables, but we have to
+ * prepare the array with passed values (of used session variables)
+ * first.
+ */
+ Assert(!IsParallelWorker());
+ nSessionVariables = list_length(queryDesc->plannedstmt->sessionVariables);
+
+ /* Create the array used for passing values of used session variables */
+ estate->es_session_variables = (SessionVariableValue *)
+ palloc(nSessionVariables * sizeof(SessionVariableValue));
+
+ /* Fill the array */
+ foreach(lc, queryDesc->plannedstmt->sessionVariables)
+ {
+ AclResult aclresult;
+ Oid varid = lfirst_oid(lc);
+
+ aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_SELECT);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE,
+ get_session_variable_name(varid));
+
+ estate->es_session_variables[i].varid = varid;
+ estate->es_session_variables[i].value = GetSessionVariable(varid,
+ &estate->es_session_variables[i].isnull,
+ &estate->es_session_variables[i].typid);
+
+ i++;
+ }
+
+ estate->es_num_session_variables = nSessionVariables;
+ }
+
/*
* Fill in the query environment, if any, from queryDesc.
*/
diff --git a/src/backend/executor/meson.build b/src/backend/executor/meson.build
index b511a429ad..b6662c6e8f 100644
--- a/src/backend/executor/meson.build
+++ b/src/backend/executor/meson.build
@@ -64,6 +64,7 @@ backend_sources += files(
'nodeWindowAgg.c',
'nodeWorktablescan.c',
'spi.c',
+ 'svariableReceiver.c',
'tqueue.c',
'tstoreReceiver.c',
)
diff --git a/src/backend/executor/svariableReceiver.c b/src/backend/executor/svariableReceiver.c
new file mode 100644
index 0000000000..c4cff44ecf
--- /dev/null
+++ b/src/backend/executor/svariableReceiver.c
@@ -0,0 +1,201 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.c
+ * An implementation of DestReceiver that stores the result value in
+ * a session variable.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/executor/svariableReceiver.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "miscadmin.h"
+
+#include "access/detoast.h"
+#include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
+#include "executor/svariableReceiver.h"
+#include "storage/lock.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+/*
+ * This DestReceiver is used by the LET command for storing the result to a
+ * session variable. The result has to have only one tuple with only one
+ * non-deleted attribute. The row counter (field "rows") is incremented
+ * after receiving a row, and an error is raised when there are no rows or
+ * there are more than one received rows. Because a received tuple can have
+ * deleted attributes, we need to find the first non-deleted attribute
+ * (field "slot_offset"). The value is detoasted before storing it in the
+ * session variable.
+ */
+typedef struct
+{
+ DestReceiver pub;
+ Oid varid;
+ bool need_detoast; /* do we need to detoast the attribute? */
+ int slot_offset; /* position of non-deleted attribute */
+ int rows; /* row counter */
+} SVariableState;
+
+/*
+ * Prepare to receive tuples from executor.
+ */
+static void
+svariableStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+ SVariableState *myState = (SVariableState *) self;
+ int natts = typeinfo->natts;
+ int outcols = 0;
+ int i;
+ LOCKTAG locktag PG_USED_FOR_ASSERTS_ONLY;
+
+ Assert(myState->pub.mydest == DestVariable);
+ Assert(OidIsValid(myState->varid));
+ Assert(SearchSysCacheExists1(VARIABLEOID, myState->varid));
+
+#ifdef USE_ASSERT_CHECKING
+
+ SET_LOCKTAG_OBJECT(locktag,
+ MyDatabaseId,
+ VariableRelationId,
+ myState->varid,
+ 0);
+
+ Assert(LockHeldByMe(&locktag, AccessShareLock, false));
+
+#endif
+
+ for (i = 0; i < natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+ Oid typid;
+ Oid collid;
+ int32 typmod;
+
+ if (attr->attisdropped)
+ continue;
+
+ if (++outcols > 1)
+ continue;
+
+ get_session_variable_type_typmod_collid(myState->varid,
+ &typid,
+ &typmod,
+ &collid);
+
+ /*
+ * Double check - the type and typmod of target variable should be the
+ * same as the type and typmod of assignment expression. The
+ * expression should be wrapped by a cast to the target type/typmod.
+ */
+ if (attr->atttypid != typid ||
+ (attr->atttypmod >= 0 &&
+ attr->atttypmod != typmod))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("target session variable is of type %s"
+ " but expression is of type %s",
+ format_type_with_typemod(typid, typmod),
+ format_type_with_typemod(attr->atttypid,
+ attr->atttypmod))));
+
+ myState->need_detoast = attr->attlen == -1;
+ myState->slot_offset = i;
+ }
+
+ if (outcols != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg_plural("assignment expression returned %d column",
+ "assignment expression returned %d columns",
+ outcols,
+ outcols)));
+
+ myState->rows = 0;
+}
+
+/*
+ * Receive a tuple from the executor and store it in the session variable.
+ */
+static bool
+svariableReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
+{
+ SVariableState *myState = (SVariableState *) self;
+ Datum value;
+ bool isnull;
+ bool freeval = false;
+
+ /* make sure the tuple is fully deconstructed */
+ slot_getallattrs(slot);
+
+ value = slot->tts_values[myState->slot_offset];
+ isnull = slot->tts_isnull[myState->slot_offset];
+
+ if (myState->need_detoast && !isnull && VARATT_IS_EXTERNAL(DatumGetPointer(value)))
+ {
+ value = PointerGetDatum(detoast_external_attr((struct varlena *)
+ DatumGetPointer(value)));
+ freeval = true;
+ }
+
+ myState->rows += 1;
+
+ if (myState->rows > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ROWS),
+ errmsg("expression returned more than one row")));
+
+ SetSessionVariable(myState->varid, value, isnull);
+
+ if (freeval)
+ pfree(DatumGetPointer(value));
+
+ return true;
+}
+
+/*
+ * Clean up at end of the executor run
+ */
+static void
+svariableShutdownReceiver(DestReceiver *self)
+{
+ if (((SVariableState *) self)->rows == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("expression returned no rows")));
+}
+
+/*
+ * Destroy the receiver when we are done with it
+ */
+static void
+svariableDestroyReceiver(DestReceiver *self)
+{
+ pfree(self);
+}
+
+/*
+ * Initially create a DestReceiver object.
+ */
+DestReceiver *
+CreateVariableDestReceiver(Oid varid)
+{
+ SVariableState *self = (SVariableState *) palloc0(sizeof(SVariableState));
+
+ self->pub.receiveSlot = svariableReceiveSlot;
+ self->pub.rStartup = svariableStartupReceiver;
+ self->pub.rShutdown = svariableShutdownReceiver;
+ self->pub.rDestroy = svariableDestroyReceiver;
+ self->pub.mydest = DestVariable;
+
+ self->varid = varid;
+
+ return (DestReceiver *) self;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index d2e2af4f81..07fe05deaf 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -4328,6 +4328,16 @@ raw_expression_tree_walker_impl(Node *node,
return true;
}
break;
+ case T_LetStmt:
+ {
+ LetStmt *stmt = (LetStmt *) node;
+
+ if (WALK(stmt->target))
+ return true;
+ if (WALK(stmt->query))
+ return true;
+ }
+ break;
case T_PLAssignStmt:
{
PLAssignStmt *stmt = (PLAssignStmt *) node;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 948afd9094..e5024f33a5 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -323,6 +323,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
glob->lastPlanNodeId = 0;
glob->transientPlan = false;
glob->dependsOnRole = false;
+ glob->sessionVariables = NIL;
/*
* Assess whether it's feasible to use parallel mode for this query. We
@@ -557,6 +558,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
result->paramExecTypes = glob->paramExecTypes;
/* utilityStmt should be null, but we might as well copy it */
result->utilityStmt = parse->utilityStmt;
+ result->sessionVariables = glob->sessionVariables;
result->stmt_location = parse->stmt_location;
result->stmt_len = parse->stmt_len;
@@ -727,6 +729,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root,
*/
pull_up_subqueries(root);
+ /*
+ * Check if some subquery uses a session variable. The flag
+ * hasSessionVariables should be true if the query or some subquery uses a
+ * session variable.
+ */
+ pull_up_has_session_variables(root);
+
/*
* If this is a simple UNION ALL query, flatten it into an appendrel. We
* do this now because it requires applying pull_up_subqueries to the leaf
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 7aed84584c..95cda4726c 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -209,6 +209,9 @@ static List *set_returning_clause_references(PlannerInfo *root,
static List *set_windowagg_runcondition_references(PlannerInfo *root,
List *runcondition,
Plan *plan);
+static bool pull_up_has_session_variables_walker(Node *node,
+ PlannerInfo *root);
+static void record_plan_variable_dependency(PlannerInfo *root, Oid varid);
/*****************************************************************************
@@ -1308,6 +1311,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
return plan;
}
+/*
+ * Search usage of session variables in subqueries
+ */
+void
+pull_up_has_session_variables(PlannerInfo *root)
+{
+ Query *query = root->parse;
+
+ if (query->hasSessionVariables)
+ {
+ root->hasSessionVariables = true;
+ }
+ else
+ {
+ (void) query_tree_walker(query,
+ pull_up_has_session_variables_walker,
+ (void *) root, 0);
+ }
+}
+
+static bool
+pull_up_has_session_variables_walker(Node *node, PlannerInfo *root)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Query))
+ {
+ Query *query = (Query *) node;
+
+ if (query->hasSessionVariables)
+ {
+ root->hasSessionVariables = true;
+ return false;
+ }
+
+ /* Recurse into subselects */
+ return query_tree_walker((Query *) node,
+ pull_up_has_session_variables_walker,
+ (void *) root, 0);
+ }
+ return expression_tree_walker(node, pull_up_has_session_variables_walker,
+ (void *) root);
+}
+
/*
* set_indexonlyscan_references
* Do set_plan_references processing on an IndexOnlyScan
@@ -1956,8 +2003,9 @@ copyVar(Var *var)
* This is code that is common to all variants of expression-fixing.
* We must look up operator opcode info for OpExpr and related nodes,
* add OIDs from regclass Const nodes into root->glob->relationOids, and
- * add PlanInvalItems for user-defined functions into root->glob->invalItems.
- * We also fill in column index lists for GROUPING() expressions.
+ * add PlanInvalItems for user-defined functions and session variables into
+ * root->glob->invalItems. We also fill in column index lists for GROUPING()
+ * expressions.
*
* We assume it's okay to update opcode info in-place. So this could possibly
* scribble on the planner's input data structures, but it's OK.
@@ -2047,6 +2095,13 @@ fix_expr_common(PlannerInfo *root, Node *node)
g->cols = cols;
}
}
+ else if (IsA(node, Param))
+ {
+ Param *p = (Param *) node;
+
+ if (p->paramkind == PARAM_VARIABLE)
+ record_plan_variable_dependency(root, p->paramvarid);
+ }
}
/*
@@ -2056,6 +2111,10 @@ fix_expr_common(PlannerInfo *root, Node *node)
* If it's a PARAM_MULTIEXPR, replace it with the appropriate Param from
* root->multiexpr_params; otherwise no change is needed.
* Just for paranoia's sake, we make a copy of the node in either case.
+ *
+ * If it's a PARAM_VARIABLE, then we collect used session variables in
+ * the list root->glob->sessionVariable. Also, assign the parameter's
+ * "paramid" to the parameter's position in that list.
*/
static Node *
fix_param_node(PlannerInfo *root, Param *p)
@@ -2074,6 +2133,41 @@ fix_param_node(PlannerInfo *root, Param *p)
elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", p->paramid);
return copyObject(list_nth(params, colno - 1));
}
+
+ if (p->paramkind == PARAM_VARIABLE)
+ {
+ ListCell *lc;
+ int n = 0;
+ bool found = false;
+
+ /* We will modify object */
+ p = (Param *) copyObject(p);
+
+ /*
+ * Now, we can actualize list of session variables, and we can
+ * complete paramid parameter.
+ */
+ foreach(lc, root->glob->sessionVariables)
+ {
+ if (lfirst_oid(lc) == p->paramvarid)
+ {
+ p->paramid = n;
+ found = true;
+ break;
+ }
+ n += 1;
+ }
+
+ if (!found)
+ {
+ root->glob->sessionVariables = lappend_oid(root->glob->sessionVariables,
+ p->paramvarid);
+ p->paramid = n;
+ }
+
+ return (Node *) p;
+ }
+
return (Node *) copyObject(p);
}
@@ -2135,7 +2229,10 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
* replacing Aggref nodes that should be replaced by initplan output Params,
* choosing the best implementation for AlternativeSubPlans,
* looking up operator opcode info for OpExpr and related nodes,
- * and adding OIDs from regclass Const nodes into root->glob->relationOids.
+ * adding OIDs from regclass Const nodes into root->glob->relationOids,
+ * assigning paramvarid to PARAM_VARIABLE params, and collecting the
+ * OIDs of session variables in the root->glob->sessionVariables list
+ * (paramvarid is the position of the session variable in this list).
*
* 'node': the expression to be modified
* 'rtoffset': how much to increment varnos by
@@ -2157,7 +2254,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
root->multiexpr_params != NIL ||
root->glob->lastPHId != 0 ||
root->minmax_aggs != NIL ||
- root->hasAlternativeSubPlans)
+ root->hasAlternativeSubPlans ||
+ root->hasSessionVariables)
{
return fix_scan_expr_mutator(node, &context);
}
@@ -3520,6 +3618,25 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
}
}
+/*
+ * Record dependency on a session variable. The variable can be used as a
+ * session variable in an expression list, or as the target of a LET statement.
+ */
+static void
+record_plan_variable_dependency(PlannerInfo *root, Oid varid)
+{
+ PlanInvalItem *inval_item = makeNode(PlanInvalItem);
+
+ /* paramid is still session variable id */
+ inval_item->cacheId = VARIABLEOID;
+ inval_item->hashValue = GetSysCacheHashValue1(VARIABLEOID,
+ ObjectIdGetDatum(varid));
+
+ /* Append this variable to global, register dependency */
+ root->glob->invalItems = lappend(root->glob->invalItems,
+ inval_item);
+}
+
/*
* extract_query_dependencies
* Given a rewritten, but not yet planned, query or queries
@@ -3605,9 +3722,9 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
}
/*
- * Ignore other utility statements, except those (such as EXPLAIN)
- * that contain a parsed-but-not-planned query. For those, we
- * just need to transfer our attention to the contained query.
+ * Ignore other utility statements, except those (such as EXPLAIN
+ * or LET) that contain a parsed-but-not-planned query. For those,
+ * we just need to transfer our attention to the contained query.
*/
query = UtilityContainsQuery(query->utilityStmt);
if (query == NULL)
@@ -3630,6 +3747,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
lappend_oid(context->glob->relationOids, rte->relid);
}
+ /* record dependency on the target variable of a LET command */
+ if (OidIsValid(query->resultVariable))
+ record_plan_variable_dependency(context, query->resultVariable);
+
/* And recurse into the query's subexpressions */
return query_tree_walker(query, extract_query_dependencies_walker,
(void *) context, 0);
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 969e257f70..061ac2dc7c 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1314,6 +1314,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
/* If subquery had any RLS conditions, now main query does too */
parse->hasRowSecurity |= subquery->hasRowSecurity;
+ /* if the subquery had session variables, the main query does too */
+ parse->hasSessionVariables |= subquery->hasSessionVariables;
+
/*
* subquery won't be pulled up if it hasAggs, hasWindowFuncs, or
* hasTargetSRFs, so no work needed on those flags
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index b4e085e9d4..0063b2b632 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -24,6 +24,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "commands/session_variable.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -935,6 +936,13 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
if (param->paramkind == PARAM_EXTERN)
return false;
+ /* we don't support passing session variables to workers */
+ if (param->paramkind == PARAM_VARIABLE)
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+ return true;
+ }
+
if (param->paramkind != PARAM_EXEC ||
!list_member_int(context->safe_param_ids, param->paramid))
{
@@ -2389,6 +2397,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
* value of the Param.
* 2. Fold stable, as well as immutable, functions to constants.
* 3. Reduce PlaceHolderVar nodes to their contained expressions.
+ * 4. Current value of session variable can be used for estimation too.
*--------------------
*/
Node *
@@ -2515,6 +2524,29 @@ eval_const_expressions_mutator(Node *node,
}
}
}
+ else if (param->paramkind == PARAM_VARIABLE &&
+ context->estimate)
+ {
+ int16 typLen;
+ bool typByVal;
+ Datum pval;
+ bool isnull;
+
+ get_typlenbyval(param->paramtype,
+ &typLen, &typByVal);
+
+ pval = GetSessionVariableWithTypeCheck(param->paramvarid,
+ &isnull,
+ param->paramtype);
+
+ return (Node *) makeConst(param->paramtype,
+ param->paramtypmod,
+ param->paramcollid,
+ (int) typLen,
+ pval,
+ isnull,
+ typByVal);
+ }
/*
* Not replaceable, so just copy the Param (no need to
@@ -4706,7 +4738,8 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree->limitOffset ||
querytree->limitCount ||
querytree->setOperations ||
- list_length(querytree->targetList) != 1)
+ (list_length(querytree->targetList) != 1) ||
+ querytree->hasSessionVariables)
goto fail;
/* If the function result is composite, resolve it */
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 28fed9d87f..0630503694 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,9 +25,12 @@
#include "postgres.h"
#include "access/sysattr.h"
+#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
#include "commands/defrem.h"
+#include "commands/session_variable.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
@@ -50,6 +53,7 @@
#include "parser/parsetree.h"
#include "utils/backend_status.h"
#include "utils/builtins.h"
+#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -82,6 +86,8 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
CreateTableAsStmt *stmt);
static Query *transformCallStmt(ParseState *pstate,
CallStmt *stmt);
+static Query *transformLetStmt(ParseState *pstate,
+ LetStmt *stmt);
static void transformLockingClause(ParseState *pstate, Query *qry,
LockingClause *lc, bool pushedDown);
#ifdef RAW_EXPRESSION_COVERAGE_TEST
@@ -325,6 +331,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
case T_UpdateStmt:
case T_DeleteStmt:
case T_MergeStmt:
+ case T_LetStmt:
(void) test_raw_expression_coverage(parseTree, NULL);
break;
default:
@@ -403,6 +410,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
(CallStmt *) parseTree);
break;
+ case T_LetStmt:
+ result = transformLetStmt(pstate,
+ (LetStmt *) parseTree);
+ break;
+
default:
/*
@@ -454,6 +466,7 @@ stmt_requires_parse_analysis(RawStmt *parseTree)
case T_SelectStmt:
case T_ReturnStmt:
case T_PLAssignStmt:
+ case T_LetStmt:
result = true;
break;
@@ -562,6 +575,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasAggs = pstate->p_hasAggs;
+ qry->hasSessionVariables = pstate->p_hasSessionVariables;
assign_query_collations(pstate, qry);
@@ -988,6 +1002,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasSessionVariables = pstate->p_hasSessionVariables;
assign_query_collations(pstate, qry);
@@ -1453,6 +1468,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasAggs = pstate->p_hasAggs;
+ qry->hasSessionVariables = pstate->p_hasSessionVariables;
foreach(l, stmt->lockingClause)
{
@@ -1679,12 +1695,235 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasSessionVariables = pstate->p_hasSessionVariables;
assign_query_collations(pstate, qry);
return qry;
}
+/*
+ * transformLetStmt -
+ * transform an Let Statement
+ */
+static Query *
+transformLetStmt(ParseState *pstate, LetStmt *stmt)
+{
+ Query *query;
+ Query *result;
+ List *exprList = NIL;
+ List *exprListCoer = NIL;
+ ListCell *lc;
+ ListCell *indirection_head = NULL;
+ Query *selectQuery;
+ Oid varid;
+ char *attrname = NULL;
+ bool not_unique;
+ bool is_rowtype;
+ Oid typid;
+ int32 typmod;
+ Oid collid;
+ AclResult aclresult;
+ List *names = NULL;
+ int indirection_start;
+ int i = 0;
+
+ /* There can't be any outer WITH to worry about */
+ Assert(pstate->p_ctenamespace == NIL);
+
+ names = NamesFromList(stmt->target);
+
+ /* locks the variable with an AccessShareLock */
+ varid = IdentifyVariable(names, &attrname, ¬_unique, false);
+ if (not_unique)
+ ereport(ERROR,
+ (errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+ errmsg("target \"%s\" of LET command is ambiguous",
+ NameListToString(names)),
+ parser_errposition(pstate, stmt->location)));
+
+ if (!OidIsValid(varid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("session variable \"%s\" doesn't exist",
+ NameListToString(names)),
+ parser_errposition(pstate, stmt->location)));
+
+ /*
+ * Calculate start of possible position of an indirection in list, and
+ * when it is inside the list, store pointer on first node of indirection.
+ */
+ indirection_start = list_length(names) - (attrname ? 1 : 0);
+ if (list_length(stmt->target) > indirection_start)
+ indirection_head = list_nth_cell(stmt->target, indirection_start);
+
+ get_session_variable_type_typmod_collid(varid, &typid, &typmod, &collid);
+
+ is_rowtype = type_is_rowtype(typid);
+
+ if (attrname && !is_rowtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("type \"%s\" of target session variable \"%s.%s\" is not a composite type",
+ format_type_be(typid),
+ get_namespace_name(get_session_variable_namespace(varid)),
+ get_session_variable_name(varid)),
+ parser_errposition(pstate, stmt->location)));
+
+ aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, NameListToString(names));
+
+ pstate->p_expr_kind = EXPR_KIND_LET_TARGET;
+
+ /* we need to postpone conversion of "unknown" to text */
+ pstate->p_resolve_unknowns = false;
+
+ selectQuery = transformStmt(pstate, stmt->query);
+
+ /* the grammar should have produced a SELECT */
+ Assert(IsA(selectQuery, Query) && selectQuery->commandType == CMD_SELECT);
+
+ /*
+ * Generate an expression list for the LET that selects all the non-resjunk
+ * columns from the subquery.
+ */
+ exprList = NIL;
+ foreach(lc, selectQuery->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ if (tle->resjunk)
+ continue;
+
+ exprList = lappend(exprList, tle->expr);
+ }
+
+ /* don't allow multicolumn result */
+ if (list_length(exprList) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg_plural("assignment expression returned %d column",
+ "assignment expression returned %d columns",
+ list_length(exprList),
+ list_length(exprList)),
+ parser_errposition(pstate,
+ exprLocation((Node *) exprList))));
+
+ exprListCoer = NIL;
+
+ foreach(lc, exprList)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ Expr *coerced_expr;
+ Param *param;
+ Oid exprtypid;
+
+ /* now we can read the type of the expression */
+ exprtypid = exprType((Node *) expr);
+
+ param = makeNode(Param);
+ param->paramkind = PARAM_VARIABLE;
+ param->paramvarid = varid;
+ param->paramtype = typid;
+ param->paramtypmod = typmod;
+
+ if (indirection_head)
+ {
+ bool targetIsArray;
+ char *targetName;
+
+ targetName = get_session_variable_name(varid);
+ targetIsArray = OidIsValid(get_element_type(typid));
+
+ pstate->p_hasSessionVariables = true;
+
+ coerced_expr = (Expr *)
+ transformAssignmentIndirection(pstate,
+ (Node *) param,
+ targetName,
+ targetIsArray,
+ typid,
+ typmod,
+ InvalidOid,
+ stmt->target,
+ indirection_head,
+ (Node *) expr,
+ COERCION_ASSIGNMENT,
+ stmt->location);
+ }
+ else
+ coerced_expr = (Expr *)
+ coerce_to_target_type(pstate,
+ (Node *) expr,
+ exprtypid,
+ typid, typmod,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ stmt->location);
+
+ if (coerced_expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("variable \"%s.%s\" is of type %s,"
+ " but expression is of type %s",
+ get_namespace_name(get_session_variable_namespace(varid)),
+ get_session_variable_name(varid),
+ format_type_be(typid),
+ format_type_be(exprtypid)),
+ errhint("You will need to rewrite or cast the expression."),
+ parser_errposition(pstate, exprLocation((Node *) expr))));
+
+ exprListCoer = lappend(exprListCoer, coerced_expr);
+ }
+
+ /* generate query's target list using the computed list of expressions */
+ query = makeNode(Query);
+ query->commandType = CMD_SELECT;
+
+ foreach(lc, exprListCoer)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ TargetEntry *tle;
+
+ tle = makeTargetEntry(expr,
+ i + 1,
+ FigureColname((Node *) expr),
+ false);
+ query->targetList = lappend(query->targetList, tle);
+ }
+
+ /* done building the range table and jointree */
+ query->rtable = pstate->p_rtable;
+ query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+ query->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ query->hasSubLinks = pstate->p_hasSubLinks;
+ query->hasSessionVariables = pstate->p_hasSessionVariables;
+
+ /* this is top-level query */
+ query->canSetTag = true;
+
+ /*
+ * Save target session variable ID. This value is used multiple times: by
+ * the query rewriter (for getting related defexpr), by planner (for
+ * acquiring an AccessShareLock on target variable), and by the executor
+ * (we need to know the target variable ID).
+ */
+ query->resultVariable = varid;
+
+ assign_query_collations(pstate, query);
+
+ stmt->query = (Node *) query;
+
+ /* represent the command as a utility Query */
+ result = makeNode(Query);
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) stmt;
+
+ return result;
+}
+
/*
* transformSetOperationStmt -
* transforms a set-operations tree
@@ -1930,6 +2169,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasAggs = pstate->p_hasAggs;
+ qry->hasSessionVariables = pstate->p_hasSessionVariables;
foreach(l, lockingClause)
{
@@ -2404,6 +2644,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasAggs = pstate->p_hasAggs;
+ qry->hasSessionVariables = pstate->p_hasSessionVariables;
assign_query_collations(pstate, qry);
@@ -2471,6 +2712,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasSessionVariables = pstate->p_hasSessionVariables;
assign_query_collations(pstate, qry);
@@ -2658,9 +2900,15 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
/*
* Transform the target reference. Typically we will get back a Param
* node, but there's no reason to be too picky about its type.
+ *
+ * Session variables should not be used as target of a PL/pgSQL assign
+ * statement. So we should use a dedicated expression kind and disallow
+ * session variables there. The dedicated context allows to eliminate
+ * undesirable warnings about the possibility of a target PL/pgSQL variable
+ * shadowing a session variable.
*/
target = transformExpr(pstate, (Node *) cref,
- EXPR_KIND_UPDATE_TARGET);
+ EXPR_KIND_ASSIGN_TARGET);
targettype = exprType(target);
targettypmod = exprTypmod(target);
targetcollation = exprCollation(target);
@@ -2702,6 +2950,10 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
*/
type_id = exprType((Node *) tle->expr);
+ /*
+ * For indirection processing and additional casts we can use expr_kind
+ * EXPR_KIND_UPDATE_TARGET.
+ */
pstate->p_expr_kind = EXPR_KIND_UPDATE_TARGET;
if (indirection)
@@ -2844,6 +3096,8 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
(LockingClause *) lfirst(l), false);
}
+ qry->hasSessionVariables = pstate->p_hasSessionVariables;
+
assign_query_collations(pstate, qry);
/* this must be done after collations, for reliable comparison of exprs */
@@ -3117,6 +3371,14 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
true,
stmt->funccall->location);
+ /*
+ * The arguments of CALL statement are evaluated by a direct expression
+ * executor call. This path is unsupported yet, so block it. It will be
+ * enabled later.
+ */
+ if (pstate->p_hasSessionVariables)
+ elog(ERROR, "session variable cannot be used as an argument");
+
assign_expr_collations(pstate, node);
fexpr = castNode(FuncExpr, node);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f2c590ce3..5787402d38 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -307,7 +307,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DropTransformStmt
DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
- ListenStmt LoadStmt LockStmt MergeStmt NotifyStmt ExplainableStmt PreparableStmt
+ LetStmt ListenStmt LoadStmt LockStmt MergeStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@@ -457,6 +457,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list
+ let_target
%type <node> opt_routine_body
%type <groupclause> group_clause
@@ -749,7 +750,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEEP KEY KEYS
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LET LEVEL LIKE LIMIT LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
@@ -1100,6 +1101,7 @@ stmt:
| ImportForeignSchemaStmt
| IndexStmt
| InsertStmt
+ | LetStmt
| ListenStmt
| RefreshMatViewStmt
| LoadStmt
@@ -12768,6 +12770,47 @@ opt_hold: /* EMPTY */ { $$ = 0; }
| WITHOUT HOLD { $$ = 0; }
;
+/*****************************************************************************
+ *
+ * QUERY:
+ * LET STATEMENT
+ *
+ *****************************************************************************/
+LetStmt: LET let_target '=' a_expr
+ {
+ LetStmt *n = makeNode(LetStmt);
+ SelectStmt *select;
+ ResTarget *res;
+
+ n->target = $2;
+
+ select = makeNode(SelectStmt);
+ res = makeNode(ResTarget);
+
+ /* Create target list for implicit query */
+ res->name = NULL;
+ res->indirection = NIL;
+ res->val = (Node *) $4;
+ res->location = @4;
+
+ select->targetList = list_make1(res);
+ n->query = (Node *) select;
+
+ n->location = @2;
+ $$ = (Node *) n;
+ }
+ ;
+
+let_target:
+ ColId opt_indirection
+ {
+ $$ = list_make1(makeString($1));
+ if ($2)
+ $$ = list_concat($$,
+ check_indirection($2, yyscanner));
+ }
+ ;
+
/*****************************************************************************
*
* QUERY:
@@ -17799,6 +17842,7 @@ unreserved_keyword:
| LARGE_P
| LAST_P
| LEAKPROOF
+ | LET
| LEVEL
| LISTEN
| LOAD
@@ -18412,6 +18456,7 @@ bare_label_keyword:
| LEAKPROOF
| LEAST
| LEFT
+ | LET
| LEVEL
| LIKE
| LISTEN
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index bee7d8346a..b494c885e9 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -577,6 +577,11 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
errkind = true;
break;
+ case EXPR_KIND_ASSIGN_TARGET:
+ case EXPR_KIND_LET_TARGET:
+ errkind = true;
+ break;
+
/*
* There is intentionally no default: case here, so that the
* compiler will warn if we add a new ParseExprKind without
@@ -967,6 +972,10 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_CYCLE_MARK:
errkind = true;
break;
+ case EXPR_KIND_ASSIGN_TARGET:
+ case EXPR_KIND_LET_TARGET:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d2db69a3f9..b50ac801a9 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -18,6 +18,7 @@
#include "catalog/pg_aggregate.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -34,12 +35,14 @@
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
+#include "storage/lmgr.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/fmgroids.h"
#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/timestamp.h"
+#include "utils/typcache.h"
#include "utils/xml.h"
/* GUC parameters */
@@ -108,6 +111,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,
Node *ltree, Node *rtree, int location);
static Node *make_nulltest_from_distinct(ParseState *pstate,
A_Expr *distincta, Node *arg);
+static Node *makeParamSessionVariable(ParseState *pstate,
+ Oid varid, Oid typid, int32 typmod, Oid collid,
+ char *attrname, int location);
/*
@@ -501,6 +507,89 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
return result;
}
+/*
+ * Returns true if the given expression kind is valid for session variables.
+ * Session variables can be used everywhere where external parameters can be
+ * used. Session variables are not allowed in DDL commands or in constraints.
+ *
+ * An identifier can be parsed as a session variable only for expression kinds
+ * where session variables are allowed. This is the primary usage of this
+ * function.
+ *
+ * The second usage of this function is to decide whether a "column does not
+ * exist" or a "column or variable does not exist" error message should be
+ * printed. When we are in an expression where session variables cannot be
+ * used, we raise the first form of error message.
+ */
+static bool
+expr_kind_allows_session_variables(ParseExprKind p_expr_kind)
+{
+ bool result = false;
+
+ switch (p_expr_kind)
+ {
+ case EXPR_KIND_NONE:
+ Assert(false); /* can't happen */
+ return false;
+
+ /* session variables allowed */
+ case EXPR_KIND_OTHER:
+ case EXPR_KIND_JOIN_ON:
+ case EXPR_KIND_FROM_SUBSELECT:
+ case EXPR_KIND_FROM_FUNCTION:
+ case EXPR_KIND_WHERE:
+ case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
+ case EXPR_KIND_WINDOW_PARTITION:
+ case EXPR_KIND_WINDOW_ORDER:
+ case EXPR_KIND_WINDOW_FRAME_RANGE:
+ case EXPR_KIND_WINDOW_FRAME_ROWS:
+ case EXPR_KIND_WINDOW_FRAME_GROUPS:
+ case EXPR_KIND_SELECT_TARGET:
+ case EXPR_KIND_INSERT_TARGET:
+ case EXPR_KIND_UPDATE_SOURCE:
+ case EXPR_KIND_UPDATE_TARGET:
+ case EXPR_KIND_MERGE_WHEN:
+ case EXPR_KIND_MERGE_RETURNING:
+ case EXPR_KIND_GROUP_BY:
+ case EXPR_KIND_ORDER_BY:
+ case EXPR_KIND_DISTINCT_ON:
+ case EXPR_KIND_LIMIT:
+ case EXPR_KIND_OFFSET:
+ case EXPR_KIND_RETURNING:
+ case EXPR_KIND_VALUES:
+ case EXPR_KIND_VALUES_SINGLE:
+ case EXPR_KIND_ALTER_COL_TRANSFORM:
+ case EXPR_KIND_EXECUTE_PARAMETER:
+ case EXPR_KIND_POLICY:
+ case EXPR_KIND_CALL_ARGUMENT:
+ case EXPR_KIND_COPY_WHERE:
+ case EXPR_KIND_LET_TARGET:
+ result = true;
+ break;
+
+ /* session variables not allowed */
+ case EXPR_KIND_CHECK_CONSTRAINT:
+ case EXPR_KIND_DOMAIN_CHECK:
+ case EXPR_KIND_COLUMN_DEFAULT:
+ case EXPR_KIND_FUNCTION_DEFAULT:
+ case EXPR_KIND_INDEX_EXPRESSION:
+ case EXPR_KIND_INDEX_PREDICATE:
+ case EXPR_KIND_STATS_EXPRESSION:
+ case EXPR_KIND_TRIGGER_WHEN:
+ case EXPR_KIND_PARTITION_BOUND:
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ case EXPR_KIND_GENERATED_COLUMN:
+ case EXPR_KIND_JOIN_USING:
+ case EXPR_KIND_CYCLE_MARK:
+ case EXPR_KIND_ASSIGN_TARGET:
+ result = false;
+ break;
+ }
+
+ return result;
+}
+
/*
* Transform a ColumnRef.
*
@@ -577,6 +666,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_COPY_WHERE:
case EXPR_KIND_GENERATED_COLUMN:
case EXPR_KIND_CYCLE_MARK:
+ case EXPR_KIND_ASSIGN_TARGET:
+ case EXPR_KIND_LET_TARGET:
/* okay */
break;
@@ -849,8 +940,61 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
}
/*
- * Throw error if no translation found.
+ * There are contexts where session variables are not allowed. We don't
+ * need to identify session variables in such a context, but identifying
+ * them allows us to raise meaningful error messages like "you cannot use
+ * session variables here".
*/
+ if (expr_kind_allows_session_variables(pstate->p_expr_kind))
+ {
+ Oid varid = InvalidOid;
+ char *attrname = NULL;
+ bool not_unique;
+
+ /* -----
+ * Session variables are shadowed by columns, routine variables or
+ * routine arguments. We certainly don't want to use a session variable
+ * when it is exactly shadowed, but a RTE like this is conceivable:
+ *
+ * CREATE TYPE t AS (c int);
+ * CREATE VARIABLE foo AS t;
+ * CREATE TABLE foo(a int, b int);
+ *
+ * SELECT foo.a, foo.b, foo.c FROM foo;
+ *
+ * However, that is very confusing, so we disallow it. We don't try to
+ * identify a variable if we know that it would be shadowed.
+ * -----
+ */
+ if (!node && !(relname && crerr == CRERR_NO_COLUMN))
+ {
+ /* takes an AccessShareLock on the session variable */
+ varid = IdentifyVariable(cref->fields, &attrname, ¬_unique, false);
+
+ if (OidIsValid(varid))
+ {
+ Oid typid;
+ int32 typmod;
+ Oid collid;
+
+ if (not_unique)
+ ereport(ERROR,
+ (errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+ errmsg("session variable reference \"%s\" is ambiguous",
+ NameListToString(cref->fields)),
+ parser_errposition(pstate, cref->location)));
+
+ get_session_variable_type_typmod_collid(varid, &typid, &typmod,
+ &collid);
+
+ node = makeParamSessionVariable(pstate,
+ varid, typid, typmod, collid,
+ attrname, cref->location);
+ }
+ }
+ }
+
+ /* throw an error if no translation was found */
if (node == NULL)
{
switch (crerr)
@@ -882,6 +1026,72 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
return node;
}
+/*
+ * Generate param variable for reference to session variable
+ */
+static Node *
+makeParamSessionVariable(ParseState *pstate,
+ Oid varid, Oid typid, int32 typmod, Oid collid,
+ char *attrname, int location)
+{
+ Param *param;
+
+ param = makeNode(Param);
+
+ param->paramkind = PARAM_VARIABLE;
+ param->paramvarid = varid;
+ param->paramtype = typid;
+ param->paramtypmod = typmod;
+ param->paramcollid = collid;
+
+ pstate->p_hasSessionVariables = true;
+
+ if (attrname != NULL)
+ {
+ TupleDesc tupdesc;
+ int i;
+
+ tupdesc = lookup_rowtype_tupdesc_noerror(typid, typmod, true);
+ if (!tupdesc)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("variable \"%s.%s\" is of type \"%s\", which is not a composite type",
+ get_namespace_name(get_session_variable_namespace(varid)),
+ get_session_variable_name(varid),
+ format_type_be(typid)),
+ parser_errposition(pstate, location)));
+
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+ if (strcmp(attrname, NameStr(att->attname)) == 0 &&
+ !att->attisdropped)
+ {
+ /* Success, so generate a FieldSelect expression */
+ FieldSelect *fselect = makeNode(FieldSelect);
+
+ fselect->arg = (Expr *) param;
+ fselect->fieldnum = i + 1;
+ fselect->resulttype = att->atttypid;
+ fselect->resulttypmod = att->atttypmod;
+ /* save attribute's collation for parse_collate.c */
+ fselect->resultcollid = att->attcollation;
+
+ ReleaseTupleDesc(tupdesc);
+ return (Node *) fselect;
+ }
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("could not identify column \"%s\" in variable", attrname),
+ parser_errposition(pstate, location)));
+ }
+
+ return (Node *) param;
+}
+
static Node *
transformParamRef(ParseState *pstate, ParamRef *pref)
{
@@ -1816,6 +2026,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_VALUES:
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CYCLE_MARK:
+ case EXPR_KIND_LET_TARGET:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -1859,6 +2070,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_GENERATED_COLUMN:
err = _("cannot use subquery in column generation expression");
break;
+ case EXPR_KIND_ASSIGN_TARGET:
+ err = _("cannot use subquery as target of assign statement");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3196,6 +3410,10 @@ ParseExprKindName(ParseExprKind exprKind)
return "GENERATED AS";
case EXPR_KIND_CYCLE_MARK:
return "CYCLE";
+ case EXPR_KIND_ASSIGN_TARGET:
+ return "ASSIGN";
+ case EXPR_KIND_LET_TARGET:
+ return "LET";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 9b23344a3b..9aa4f60768 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2656,6 +2656,8 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
err = _("set-returning functions are not allowed in column generation expressions");
break;
case EXPR_KIND_CYCLE_MARK:
+ case EXPR_KIND_ASSIGN_TARGET:
+ case EXPR_KIND_LET_TARGET:
errkind = true;
break;
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 96f80b3046..dac78ba5cc 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -38,6 +38,7 @@
#include "executor/functions.h"
#include "executor/tqueue.h"
#include "executor/tstoreReceiver.h"
+#include "executor/svariableReceiver.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
@@ -155,6 +156,9 @@ CreateDestReceiver(CommandDest dest)
case DestExplainSerialize:
return CreateExplainSerializeDestReceiver(NULL);
+
+ case DestVariable:
+ return CreateVariableDestReceiver(InvalidOid);
}
/* should never get here */
@@ -191,6 +195,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
case DestTransientRel:
case DestTupleQueue:
case DestExplainSerialize:
+ case DestVariable:
break;
}
}
@@ -237,6 +242,7 @@ NullCommand(CommandDest dest)
case DestTransientRel:
case DestTupleQueue:
case DestExplainSerialize:
+ case DestVariable:
break;
}
}
@@ -281,6 +287,7 @@ ReadyForQuery(CommandDest dest)
case DestTransientRel:
case DestTupleQueue:
case DestExplainSerialize:
+ case DestVariable:
break;
}
}
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 0c45fcf318..85b809cb07 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -86,6 +86,9 @@ CreateQueryDesc(PlannedStmt *plannedstmt,
qd->queryEnv = queryEnv;
qd->instrument_options = instrument_options; /* instrumentation wanted? */
+ qd->num_session_variables = 0;
+ qd->session_variables = NULL;
+
/* null these fields until set by ExecutorStart */
qd->tupDesc = NULL;
qd->estate = NULL;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ce8db7cda4..662dbcd1dd 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -49,6 +49,7 @@
#include "commands/schemacmds.h"
#include "commands/seclabel.h"
#include "commands/sequence.h"
+#include "commands/session_variable.h"
#include "commands/subscriptioncmds.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -235,6 +236,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
case T_CallStmt:
case T_DoStmt:
+ case T_LetStmt:
{
/*
* Commands inside the DO block or the called procedure might
@@ -1064,6 +1066,11 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
}
+ case T_LetStmt:
+ ExecuteLetStmt(pstate, (LetStmt *) parsetree, params,
+ queryEnv, qc);
+ break;
+
default:
/* All other statement types have event trigger support */
ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2203,6 +2210,10 @@ UtilityContainsQuery(Node *parsetree)
return UtilityContainsQuery(qry->utilityStmt);
return qry;
+ case T_LetStmt:
+ qry = castNode(Query, ((LetStmt *) parsetree)->query);
+ return qry;
+
default:
return NULL;
}
@@ -2401,6 +2412,10 @@ CreateCommandTag(Node *parsetree)
tag = CMDTAG_SELECT;
break;
+ case T_LetStmt:
+ tag = CMDTAG_LET;
+ break;
+
/* utility statements --- same whether raw or cooked */
case T_TransactionStmt:
{
@@ -3286,6 +3301,7 @@ GetCommandLogLevel(Node *parsetree)
break;
case T_PLAssignStmt:
+ case T_LetStmt:
lev = LOGSTMT_ALL;
break;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 653685bffc..a0edd5e049 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -37,6 +37,7 @@
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
#include "commands/defrem.h"
#include "commands/tablespace.h"
#include "common/keywords.h"
@@ -519,6 +520,7 @@ static char *generate_function_name(Oid funcid, int nargs,
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
static void add_cast_to(StringInfo buf, Oid typid);
static char *generate_qualified_type_name(Oid typid);
+static char *generate_session_variable_name(Oid varid);
static text *string_to_text(char *str);
static char *flatten_reloptions(Oid relid);
static void get_reloptions(StringInfo buf, Datum reloptions);
@@ -8351,6 +8353,14 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /* translate paramvarid to session variable name */
+ if (param->paramkind == PARAM_VARIABLE)
+ {
+ appendStringInfo(context->buf, "%s",
+ generate_session_variable_name(param->paramvarid));
+ return;
+ }
+
/*
* Alternatively, maybe it's a subplan output, which we print as a
* reference to the subplan. (We could drill down into the subplan and
@@ -13131,6 +13141,42 @@ generate_collation_name(Oid collid)
return result;
}
+/*
+ * generate_session_variable_name
+ * Compute the name to display for a session variable specified by OID
+ *
+ * The result includes all necessary quoting and schema-prefixing.
+ */
+static char *
+generate_session_variable_name(Oid varid)
+{
+ HeapTuple tup;
+ Form_pg_variable varform;
+ char *varname;
+ char *nspname;
+ char *result;
+
+ tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for variable %u", varid);
+
+ varform = (Form_pg_variable) GETSTRUCT(tup);
+
+ varname = NameStr(varform->varname);
+
+ if (!VariableIsVisible(varid))
+ nspname = get_namespace_name_or_temp(varform->varnamespace);
+ else
+ nspname = NULL;
+
+ result = quote_qualified_identifier(nspname, varname);
+
+ ReleaseSysCache(tup);
+
+ return result;
+}
+
/*
* Given a C string, produce a TEXT datum.
*
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 5af1a168ec..7e1e9e28f1 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -58,6 +58,7 @@
#include "access/transam.h"
#include "catalog/namespace.h"
+#include "catalog/pg_variable.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
@@ -162,6 +163,7 @@ InitPlanCache(void)
CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheSysCallback, (Datum) 0);
+ CacheRegisterSyscacheCallback(VARIABLEOID, PlanCacheObjectCallback, (Datum) 0);
}
/*
@@ -1903,18 +1905,33 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
/*
* Recurse into sublink subqueries, too. But we already did the ones in
- * the rtable and cteList.
+ * the rtable and cteList. We need to force a recursive call for session
+ * variables too, to find and lock variables used in the query (see
+ * ScanQueryWalker).
*/
- if (parsetree->hasSubLinks)
+ if (parsetree->hasSubLinks ||
+ parsetree->hasSessionVariables)
{
query_tree_walker(parsetree, ScanQueryWalker,
(void *) &acquire,
QTW_IGNORE_RC_SUBQUERIES);
}
+
+ /* process session variables */
+ if (OidIsValid(parsetree->resultVariable))
+ {
+ if (acquire)
+ LockDatabaseObject(VariableRelationId, parsetree->resultVariable,
+ 0, AccessShareLock);
+ else
+ UnlockDatabaseObject(VariableRelationId, parsetree->resultVariable,
+ 0, AccessShareLock);
+ }
}
/*
- * Walker to find sublink subqueries for ScanQueryForLocks
+ * Walker to find sublink subqueries or referenced session variables
+ * for ScanQueryForLocks
*/
static bool
ScanQueryWalker(Node *node, bool *acquire)
@@ -1929,6 +1946,20 @@ ScanQueryWalker(Node *node, bool *acquire)
ScanQueryForLocks(castNode(Query, sub->subselect), *acquire);
/* Fall through to process lefthand args of SubLink */
}
+ else if (IsA(node, Param))
+ {
+ Param *p = (Param *) node;
+
+ if (p->paramkind == PARAM_VARIABLE)
+ {
+ if (acquire)
+ LockDatabaseObject(VariableRelationId, p->paramvarid,
+ 0, AccessShareLock);
+ else
+ UnlockDatabaseObject(VariableRelationId, p->paramvarid,
+ 0, AccessShareLock);
+ }
+ }
/*
* Do NOT recurse into Query nodes, because ScanQueryForLocks already
@@ -2060,7 +2091,9 @@ PlanCacheRelCallback(Datum arg, Oid relid)
/*
* PlanCacheObjectCallback
- * Syscache inval callback function for PROCOID and TYPEOID caches
+ * Syscache inval callback function for TYPEOID, PROCOID, NAMESPACEOID,
+ * OPEROID, AMOPOPID, FOREIGNSERVEROID, FOREIGNDATAWRAPPEROID and
+ * VARIABLEOID caches.
*
* Invalidate all plans mentioning the object with the specified hash value,
* or all plans mentioning any member of this cache if hashvalue == 0.
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index e48a86be54..eaae0c5a93 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -2026,9 +2026,13 @@ get_call_expr_arg_stable(Node *expr, int argnum)
*/
if (IsA(arg, Const))
return true;
- if (IsA(arg, Param) &&
- ((Param *) arg)->paramkind == PARAM_EXTERN)
- return true;
+ if (IsA(arg, Param))
+ {
+ Param *p = (Param *) arg;
+
+ if (p->paramkind == PARAM_EXTERN || p->paramkind == PARAM_VARIABLE)
+ return true;
+ }
return false;
}
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 873a83fa5a..a642926180 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1709,8 +1709,8 @@ psql_completion(const char *text, int start, int end)
"ABORT", "ALTER", "ANALYZE", "BEGIN", "CALL", "CHECKPOINT", "CLOSE", "CLUSTER",
"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
- "FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LISTEN", "LOAD", "LOCK",
- "MERGE INTO", "MOVE", "NOTIFY", "PREPARE",
+ "FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LET",
+ "LISTEN", "LOAD", "LOCK", "MERGE INTO", "MOVE", "NOTIFY", "PREPARE",
"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
"RESET", "REVOKE", "ROLLBACK",
"SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
@@ -4282,6 +4282,14 @@ psql_completion(const char *text, int start, int end)
else if (TailMatches("VALUES") && !TailMatches("DEFAULT", "VALUES"))
COMPLETE_WITH("(");
+/* LET */
+ /* If prev. word is LET suggest a list of variables */
+ else if (Matches("LET"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
+ /* Complete LET <variable> with "=" */
+ else if (TailMatches("LET", MatchAny))
+ COMPLETE_WITH("=");
+
/* LOCK */
/* Complete LOCK [TABLE] [ONLY] with a list of tables */
else if (Matches("LOCK"))
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 18ea6ac83a..def691dafb 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -171,7 +171,9 @@ extern SearchPathMatcher *GetSearchPathMatcher(MemoryContext context);
extern SearchPathMatcher *CopySearchPathMatcher(SearchPathMatcher *path);
extern bool SearchPathMatchesCurrentEnvironment(SearchPathMatcher *path);
+extern List *NamesFromList(List *names);
extern Oid LookupVariable(const char *nspname, const char *varname, bool missing_ok);
+extern Oid IdentifyVariable(List *names, char **attrname, bool *not_unique, bool noerror);
extern Oid get_collation_oid(List *collname, bool missing_ok);
extern Oid get_conversion_oid(List *conname, bool missing_ok);
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
index 3810e040f2..db593cd5be 100644
--- a/src/include/catalog/pg_variable.h
+++ b/src/include/catalog/pg_variable.h
@@ -35,6 +35,14 @@ CATALOG(pg_variable,9222,VariableRelationId)
/* OID of entry in pg_type for variable's type */
Oid vartype BKI_LOOKUP(pg_type);
+ /*
+ * Used for identity check [oid, create_lsn].
+ *
+ * This column of the 8-byte XlogRecPtr type should be at an address that
+ * is divisible by 8, but before any column of type NameData.
+ */
+ XLogRecPtr varcreate_lsn;
+
/* variable name */
NameData varname;
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
new file mode 100644
index 0000000000..b3f03c6582
--- /dev/null
+++ b/src/include/commands/session_variable.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * sessionvariable.h
+ * prototypes for sessionvariable.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/session_variable.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SESSIONVARIABLE_H
+#define SESSIONVARIABLE_H
+
+#include "nodes/params.h"
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+#include "tcop/cmdtag.h"
+#include "utils/queryenvironment.h"
+
+extern void SetSessionVariable(Oid varid, Datum value, bool isNull);
+extern void SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull);
+extern Datum GetSessionVariable(Oid varid, bool *isNull, Oid *typid);
+extern Datum GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid);
+
+extern void ExecuteLetStmt(ParseState *pstate, LetStmt *stmt, ParamListInfo params,
+ QueryEnvironment *queryEnv, QueryCompletion *qc);
+
+#endif
diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h
index 0a7274e26c..d1b2f59e0c 100644
--- a/src/include/executor/execdesc.h
+++ b/src/include/executor/execdesc.h
@@ -48,6 +48,10 @@ typedef struct QueryDesc
EState *estate; /* executor's query-wide state */
PlanState *planstate; /* tree of per-plan-node state */
+ /* reference to session variables buffer */
+ int num_session_variables;
+ SessionVariableValue *session_variables;
+
/* This field is set by ExecutorRun */
bool already_executed; /* true if previously executed */
diff --git a/src/include/executor/svariableReceiver.h b/src/include/executor/svariableReceiver.h
new file mode 100644
index 0000000000..db44d8b94c
--- /dev/null
+++ b/src/include/executor/svariableReceiver.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.h
+ * prototypes for svariableReceiver.c
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/svariableReceiver.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SVARIABLE_RECEIVER_H
+#define SVARIABLE_RECEIVER_H
+
+#include "tcop/dest.h"
+
+extern DestReceiver *CreateVariableDestReceiver(Oid varid);
+
+#endif /* SVARIABLE_RECEIVER_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cac684d9b3..449050f7fe 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -609,6 +609,18 @@ typedef struct AsyncRequest
* tuples) */
} AsyncRequest;
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+ Oid varid;
+ Oid typid;
+ bool isnull;
+ Datum value;
+} SessionVariableValue;
+
/* ----------------
* EState information
*
@@ -661,6 +673,10 @@ typedef struct EState
ParamListInfo es_param_list_info; /* values of external params */
ParamExecData *es_param_exec_vals; /* values of internal params */
+ /* Session variables info: */
+ int es_num_session_variables; /* number of used variables */
+ SessionVariableValue *es_session_variables; /* array of copies of values */
+
QueryEnvironment *es_queryEnv; /* query environment */
/* Other working state: */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 80ea5928ee..74bf5af776 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -142,6 +142,9 @@ typedef struct Query
*/
int resultRelation pg_node_attr(query_jumble_ignore);
+ /* target variable of LET statement */
+ Oid resultVariable;
+
/* has aggregates in tlist or havingQual */
bool hasAggs pg_node_attr(query_jumble_ignore);
/* has window functions in tlist */
@@ -160,6 +163,8 @@ typedef struct Query
bool hasForUpdate pg_node_attr(query_jumble_ignore);
/* rewriter has applied some RLS policy */
bool hasRowSecurity pg_node_attr(query_jumble_ignore);
+ /* uses session variables */
+ bool hasSessionVariables pg_node_attr(query_jumble_ignore);
/* is a RETURN statement */
bool isReturn pg_node_attr(query_jumble_ignore);
@@ -2095,6 +2100,18 @@ typedef struct MergeStmt
WithClause *withClause; /* WITH clause */
} MergeStmt;
+/* ----------------------
+ * Let Statement
+ * ----------------------
+ */
+typedef struct LetStmt
+{
+ NodeTag type;
+ List *target; /* target variable */
+ Node *query; /* source expression */
+ int location;
+} LetStmt;
+
/* ----------------------
* Select Statement
*
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 14ccfc1ac1..c16385512f 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -163,6 +163,9 @@ typedef struct PlannerGlobal
/* partition descriptors */
PartitionDirectory partition_directory pg_node_attr(read_write_ignore);
+
+ /* list of used session variables */
+ List *sessionVariables;
} PlannerGlobal;
/* macro for fetching the Plan associated with a SubPlan node */
@@ -508,6 +511,8 @@ struct PlannerInfo
bool placeholdersFrozen;
/* true if planning a recursive WITH item */
bool hasRecursion;
+ /* true if session variables were used */
+ bool hasSessionVariables;
/*
* Information about aggregates. Filled by preprocess_aggrefs().
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 1aeeaec95e..6b9b9904fd 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -49,7 +49,7 @@ typedef struct PlannedStmt
NodeTag type;
- CmdType commandType; /* select|insert|update|delete|merge|utility */
+ CmdType commandType; /* select|let|insert|update|delete|merge|utility */
uint64 queryId; /* query identifier (copied from Query) */
@@ -94,6 +94,8 @@ typedef struct PlannedStmt
Node *utilityStmt; /* non-null if this is utility stmt */
+ List *sessionVariables; /* OIDs for PARAM_VARIABLE Params */
+
/* statement location in source string (copied from Query) */
ParseLoc stmt_location; /* start location, or -1 if unknown */
ParseLoc stmt_len; /* length in bytes; 0 means "rest of string" */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index ea47652adb..5f7b83d13f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -361,6 +361,9 @@ typedef struct Const
* of the `paramid' field contain the SubLink's subLinkId, and
* the low-order 16 bits contain the column number. (This type
* of Param is also converted to PARAM_EXEC during planning.)
+ *
+ * PARAM_VARIABLE: The parameter is a reference to a session variable
+ * (paramid holds the variable's OID).
*/
typedef enum ParamKind
{
@@ -368,6 +371,7 @@ typedef enum ParamKind
PARAM_EXEC,
PARAM_SUBLINK,
PARAM_MULTIEXPR,
+ PARAM_VARIABLE,
} ParamKind;
typedef struct Param
@@ -380,6 +384,8 @@ typedef struct Param
int32 paramtypmod pg_node_attr(query_jumble_ignore);
/* OID of collation, or InvalidOid if none */
Oid paramcollid pg_node_attr(query_jumble_ignore);
+ /* OID of session variable if it is used */
+ Oid paramvarid;
/* token location, or -1 if unknown */
ParseLoc location;
} Param;
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index aafc173792..de471692e8 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -120,4 +120,6 @@ extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
extern void record_plan_type_dependency(PlannerInfo *root, Oid typid);
extern bool extract_query_dependencies_walker(Node *node, PlannerInfo *context);
+extern void pull_up_has_session_variables(PlannerInfo *root);
+
#endif /* PLANMAIN_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index c884cd5c46..2e88d7d950 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -256,6 +256,7 @@ PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("let", LET, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD, AS_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 5b781d87a9..6d93691965 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -82,6 +82,8 @@ typedef enum ParseExprKind
EXPR_KIND_COPY_WHERE, /* WHERE condition in COPY FROM */
EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */
EXPR_KIND_CYCLE_MARK, /* cycle mark value */
+ EXPR_KIND_ASSIGN_TARGET, /* PL/pgSQL assignment target */
+ EXPR_KIND_LET_TARGET, /* LET target */
} ParseExprKind;
@@ -225,6 +227,7 @@ struct ParseState
bool p_hasTargetSRFs;
bool p_hasSubLinks;
bool p_hasModifyingCTE;
+ bool p_hasSessionVariables;
Node *p_last_srf; /* most recent set-returning func/op found */
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 9465df7b2f..a921af2486 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -186,6 +186,7 @@ PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
+PG_CMDTAG(CMDTAG_LET, "LET", false, false, false)
PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index a3d521b6f9..38cc87f4e7 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -97,6 +97,7 @@ typedef enum
DestTransientRel, /* results sent to transient relation */
DestTupleQueue, /* results sent to tuple queue */
DestExplainSerialize, /* results are serialized and discarded */
+ DestVariable, /* results sent to session variable */
} CommandDest;
/* ----------------
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 239b3250a9..a9e1e323ad 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8101,7 +8101,8 @@ exec_is_simple_query(PLpgSQL_expr *expr)
query->sortClause ||
query->limitOffset ||
query->limitCount ||
- query->setOperations)
+ query->setOperations ||
+ query->hasSessionVariables)
return false;
/*
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index aca766042d..287628ede6 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -55,3 +55,915 @@ SET ROLE TO DEFAULT;
DROP VARIABLE svartest.var1;
DROP SCHEMA svartest;
DROP ROLE regress_variable_owner;
+-- check access rights
+CREATE ROLE regress_noowner;
+CREATE VARIABLE var1 AS int;
+CREATE OR REPLACE FUNCTION sqlfx(int)
+RETURNS int AS $$ SELECT $1 + var1 $$ LANGUAGE sql;
+CREATE OR REPLACE FUNCTION sqlfx_sd(int)
+RETURNS int AS $$ SELECT $1 + var1 $$ LANGUAGE sql SECURITY DEFINER;
+CREATE OR REPLACE FUNCTION plpgsqlfx(int)
+RETURNS int AS $$ BEGIN RETURN $1 + var1; END $$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION plpgsqlfx_sd(int)
+RETURNS int AS $$ BEGIN RETURN $1 + var1; END $$ LANGUAGE plpgsql SECURITY DEFINER;
+LET var1 = 10;
+-- should be ok
+SELECT var1;
+ var1
+------
+ 10
+(1 row)
+
+SELECT sqlfx(20);
+ sqlfx
+-------
+ 30
+(1 row)
+
+SELECT sqlfx_sd(20);
+ sqlfx_sd
+----------
+ 30
+(1 row)
+
+SELECT plpgsqlfx(20);
+ plpgsqlfx
+-----------
+ 30
+(1 row)
+
+SELECT plpgsqlfx_sd(20);
+ plpgsqlfx_sd
+--------------
+ 30
+(1 row)
+
+-- should fail
+SET ROLE TO regress_noowner;
+SELECT var1;
+ERROR: permission denied for session variable var1
+SELECT sqlfx(20);
+ERROR: permission denied for session variable var1
+CONTEXT: SQL function "sqlfx" statement 1
+SELECT plpgsqlfx(20);
+ERROR: permission denied for session variable var1
+CONTEXT: SQL expression "$1 + var1"
+PL/pgSQL function plpgsqlfx(integer) line 1 at RETURN
+-- should be ok
+SELECT sqlfx_sd(20);
+ sqlfx_sd
+----------
+ 30
+(1 row)
+
+SELECT plpgsqlfx_sd(20);
+ plpgsqlfx_sd
+--------------
+ 30
+(1 row)
+
+SET ROLE TO DEFAULT;
+GRANT SELECT ON VARIABLE var1 TO regress_noowner;
+-- should be ok
+SET ROLE TO regress_noowner;
+SELECT var1;
+ var1
+------
+ 10
+(1 row)
+
+SELECT sqlfx(20);
+ sqlfx
+-------
+ 30
+(1 row)
+
+SELECT plpgsqlfx(20);
+ plpgsqlfx
+-----------
+ 30
+(1 row)
+
+SET ROLE TO DEFAULT;
+DROP VARIABLE var1;
+DROP FUNCTION sqlfx(int);
+DROP FUNCTION plpgsqlfx(int);
+DROP FUNCTION sqlfx_sd(int);
+DROP FUNCTION plpgsqlfx_sd(int);
+DROP ROLE regress_noowner;
+-- use variables inside views
+CREATE VARIABLE var1 AS numeric;
+-- use variables in views
+CREATE VIEW test_view AS SELECT COALESCE(var1 + v, 0) AS result FROM generate_series(1,2) g(v);
+SELECT * FROM test_view;
+ result
+--------
+ 0
+ 0
+(2 rows)
+
+LET var1 = 3.14;
+SELECT * FROM test_view;
+ result
+--------
+ 4.14
+ 5.14
+(2 rows)
+
+-- start a new session
+\c
+SELECT * FROM test_view;
+ result
+--------
+ 0
+ 0
+(2 rows)
+
+LET var1 = 3.14;
+SELECT * FROM test_view;
+ result
+--------
+ 4.14
+ 5.14
+(2 rows)
+
+-- should fail, dependency
+DROP VARIABLE var1;
+ERROR: cannot drop session variable var1 because other objects depend on it
+DETAIL: view test_view depends on session variable var1
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- should be ok
+DROP VARIABLE var1 CASCADE;
+NOTICE: drop cascades to view test_view
+CREATE VARIABLE var1 text;
+CREATE VARIABLE var2 text;
+-- use variables in SQL functions
+CREATE OR REPLACE FUNCTION sqlfx1(varchar)
+RETURNS varchar AS $$ SELECT var1 || ', ' || $1 $$ LANGUAGE sql;
+CREATE OR REPLACE FUNCTION sqlfx2( varchar)
+RETURNS varchar AS $$ SELECT var2 || ', ' || $1 $$ LANGUAGE sql;
+LET var1 = 'str1';
+LET var2 = 'str2';
+SELECT sqlfx1(sqlfx2('Hello'));
+ sqlfx1
+-------------------
+ str1, str2, Hello
+(1 row)
+
+-- inlining is blocked
+EXPLAIN (COSTS OFF, VERBOSE) SELECT sqlfx1(sqlfx2('Hello'));
+ QUERY PLAN
+------------------------------------------------------
+ Result
+ Output: sqlfx1(sqlfx2('Hello'::character varying))
+(2 rows)
+
+DROP FUNCTION sqlfx1(varchar);
+DROP FUNCTION sqlfx2(varchar);
+DROP VARIABLE var1;
+DROP VARIABLE var2;
+-- access from cached plans should work
+CREATE VARIABLE var1 AS numeric;
+CREATE OR REPLACE FUNCTION plpgsqlfx()
+RETURNS numeric AS $$ BEGIN RETURN var1; END $$ LANGUAGE plpgsql;
+set plan_cache_mode TO force_generic_plan;
+LET var1 = 3.14;
+SELECT plpgsqlfx();
+ plpgsqlfx
+-----------
+ 3.14
+(1 row)
+
+LET var1 = 3.14 * 2;
+SELECT plpgsqlfx();
+ plpgsqlfx
+-----------
+ 6.28
+(1 row)
+
+DROP VARIABLE var1;
+-- dependency (plan invalidation) should work
+CREATE VARIABLE var1 AS numeric;
+LET var1 = 3.14 * 3;
+SELECT plpgsqlfx();
+ plpgsqlfx
+-----------
+ 9.42
+(1 row)
+
+LET var1 = 3.14 * 4;
+SELECT plpgsqlfx();
+ plpgsqlfx
+-----------
+ 12.56
+(1 row)
+
+DROP VARIABLE var1;
+DROP FUNCTION plpgsqlfx();
+set plan_cache_mode TO DEFAULT;
+-- usage LET statement in plpgsql should work
+CREATE VARIABLE var1 int;
+CREATE VARIABLE var2 numeric[];
+DO $$
+BEGIN
+ LET var2 = '{}'::int[];
+ FOR i IN 1..10
+ LOOP
+ LET var1 = i;
+ LET var2[var1] = i;
+ END LOOP;
+ RAISE NOTICE 'result array: %', var2;
+END;
+$$;
+NOTICE: result array: {1,2,3,4,5,6,7,8,9,10}
+DROP VARIABLE var1;
+DROP VARIABLE var2;
+-- CALL statement is not supported yet
+-- requires direct access to session variable from expression executor
+CREATE VARIABLE v int;
+CREATE PROCEDURE p(arg int) AS $$ BEGIN RAISE NOTICE '%', arg; END $$ LANGUAGE plpgsql;
+-- should not crash (but is not supported yet)
+CALL p(v);
+ERROR: session variable cannot be used as an argument
+DO $$ BEGIN CALL p(v); END $$;
+ERROR: session variable cannot be used as an argument
+CONTEXT: SQL statement "CALL p(v)"
+PL/pgSQL function inline_code_block line 1 at CALL
+DROP PROCEDURE p(int);
+DROP VARIABLE v;
+-- test search path
+CREATE SCHEMA svartest;
+CREATE VARIABLE svartest.var1 AS numeric;
+-- should fail
+LET var1 = pi();
+ERROR: session variable "var1" doesn't exist
+LINE 1: LET var1 = pi();
+ ^
+SELECT var1;
+ERROR: column "var1" does not exist
+LINE 1: SELECT var1;
+ ^
+-- should be ok
+LET svartest.var1 = pi();
+SELECT svartest.var1;
+ var1
+------------------
+ 3.14159265358979
+(1 row)
+
+SET search_path TO svartest;
+-- should be ok
+LET var1 = pi() + 10;
+SELECT var1;
+ var1
+------------------
+ 13.1415926535898
+(1 row)
+
+RESET search_path;
+DROP SCHEMA svartest CASCADE;
+NOTICE: drop cascades to session variable svartest.var1
+CREATE VARIABLE var1 AS text;
+-- variables can be updated under RO transaction
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET var1 = 'hello';
+COMMIT;
+SELECT var1;
+ var1
+-------
+ hello
+(1 row)
+
+DROP VARIABLE var1;
+-- test of domains
+CREATE DOMAIN int_domain AS int NOT NULL CHECK (VALUE > 100);
+CREATE VARIABLE var1 AS int_domain;
+-- should fail
+SELECT var1;
+ERROR: domain int_domain does not allow null values
+-- should be ok
+LET var1 = 1000;
+SELECT var1;
+ var1
+------
+ 1000
+(1 row)
+
+-- should fail
+LET var1 = 10;
+ERROR: value for domain int_domain violates check constraint "int_domain_check"
+-- should fail
+LET var1 = NULL;
+ERROR: domain int_domain does not allow null values
+-- note - domain defaults are not supported yet (like PLpgSQL)
+DROP VARIABLE var1;
+DROP DOMAIN int_domain;
+-- the expression should remain "unknown"
+CREATE VARIABLE var1 AS int4multirange[];
+-- should be ok
+LET var1 = NULL;
+LET var1 = '{"{[2,8),[11,14)}","{[5,8),[12,14)}"}';
+LET var1[2] = '{[5,8),[12,100)}';
+SELECT var1;
+ var1
+----------------------------------------
+ {"{[2,8),[11,14)}","{[5,8),[12,100)}"}
+(1 row)
+
+--It should work in plpgsql too
+DO $$
+BEGIN
+ LET var1 = NULL;
+ LET var1 = '{"{[2,8),[11,14)}","{[5,8),[12,14)}"}';
+ LET var1[2] = '{[5,8),[12,100)}';
+
+ RAISE NOTICE '%', var1;
+END;
+$$;
+NOTICE: {"{[2,8),[11,14)}","{[5,8),[12,100)}"}
+DROP VARIABLE var1;
+CREATE SCHEMA svartest CREATE VARIABLE var1 AS int CREATE TABLE foo(a int);
+LET svartest.var1 = 100;
+SELECT svartest.var1;
+ var1
+------
+ 100
+(1 row)
+
+SET search_path to public, svartest;
+SELECT var1;
+ var1
+------
+ 100
+(1 row)
+
+DROP SCHEMA svartest CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table foo
+drop cascades to session variable var1
+CREATE VARIABLE var1 AS int;
+CREATE VARIABLE var2 AS int[];
+LET var1 = 2;
+LET var2 = '{}'::int[];
+LET var2[var1] = 0;
+SELECT var2;
+ var2
+-----------
+ [2:2]={0}
+(1 row)
+
+DROP VARIABLE var1, var2;
+CREATE VARIABLE var1 AS int;
+CREATE VARIABLE var2 AS int[];
+LET var1 = 2;
+LET var2 = '{}'::int[];
+SELECT var2;
+ var2
+------
+ {}
+(1 row)
+
+DROP VARIABLE var1, var2;
+-- the LET statement should be disallowed in CTE
+CREATE VARIABLE var1 AS int;
+WITH x AS (LET var1 = 100) SELECT * FROM x;
+ERROR: syntax error at or near "LET"
+LINE 1: WITH x AS (LET var1 = 100) SELECT * FROM x;
+ ^
+-- should be ok
+LET var1 = generate_series(1, 1);
+-- should fail
+LET var1 = generate_series(1, 2);
+ERROR: expression returned more than one row
+LET var1 = generate_series(1, 0);
+ERROR: expression returned no rows
+DROP VARIABLE var1;
+-- composite variables
+CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2));
+CREATE VARIABLE v1 AS sv_xyz;
+CREATE VARIABLE v2 AS sv_xyz;
+LET v1 = (1, 2, 3.14);
+LET v2 = (10, 20, 3.14 * 10);
+-- should work too - there are prepared casts
+LET v1 = (1, 2, 3);
+SELECT v1;
+ v1
+------------
+ (1,2,3.00)
+(1 row)
+
+SELECT v2;
+ v2
+---------------
+ (10,20,31.40)
+(1 row)
+
+SELECT (v1).*;
+ x | y | z
+---+---+------
+ 1 | 2 | 3.00
+(1 row)
+
+SELECT (v2).*;
+ x | y | z
+----+----+-------
+ 10 | 20 | 31.40
+(1 row)
+
+SELECT v1.x + v1.z;
+ ?column?
+----------
+ 4.00
+(1 row)
+
+SELECT v2.x + v2.z;
+ ?column?
+----------
+ 41.40
+(1 row)
+
+-- access to composite fields should be safe too
+CREATE ROLE regress_var_test_role;
+SET ROLE TO regress_var_test_role;
+-- should fail
+SELECT v2.x;
+ERROR: permission denied for session variable v2
+SET ROLE TO DEFAULT;
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+DROP ROLE regress_var_test_role;
+CREATE TYPE t1 AS (a int, b numeric, c text);
+CREATE VARIABLE v1 AS t1;
+LET v1 = (1, pi(), 'hello');
+SELECT v1;
+ v1
+----------------------------
+ (1,3.14159265358979,hello)
+(1 row)
+
+LET v1.b = 10.2222;
+SELECT v1;
+ v1
+-------------------
+ (1,10.2222,hello)
+(1 row)
+
+-- should fail, attribute doesn't exist
+LET v1.x = 10;
+ERROR: cannot assign to field "x" of column "v1" because there is no such column in data type t1
+LINE 1: LET v1.x = 10;
+ ^
+-- should fail, don't allow multi column query
+LET v1 = (NULL::t1).*;
+ERROR: assignment expression returned 3 columns
+LINE 1: LET v1 = (NULL::t1).*;
+ ^
+-- allow DROP or ADD ATTRIBUTE on composite types
+-- should be ok
+ALTER TYPE t1 DROP ATTRIBUTE c;
+SELECT v1;
+ v1
+-------------
+ (1,10.2222)
+(1 row)
+
+-- should be ok
+ALTER TYPE t1 ADD ATTRIBUTE c int;
+SELECT v1;
+ v1
+--------------
+ (1,10.2222,)
+(1 row)
+
+LET v1 = (10, 10.3, 20);
+SELECT v1;
+ v1
+--------------
+ (10,10.3,20)
+(1 row)
+
+-- should be ok
+ALTER TYPE t1 DROP ATTRIBUTE b;
+SELECT v1;
+ v1
+---------
+ (10,20)
+(1 row)
+
+-- should fail, disallow data type change
+ALTER TYPE t1 ALTER ATTRIBUTE c TYPE int;
+ERROR: cannot alter type "t1" because session variable "public.v1" uses it
+DROP VARIABLE v1;
+DROP TYPE t1;
+-- the table type can be used as composite type too
+CREATE TABLE svar_test(a int, b numeric, c date);
+CREATE VARIABLE var1 AS svar_test;
+LET var1 = (10, pi(), '2023-05-26');
+SELECT var1;
+ var1
+----------------------------------
+ (10,3.14159265358979,05-26-2023)
+(1 row)
+
+-- should fail due dependency
+ALTER TABLE svar_test ALTER COLUMN a TYPE text;
+ERROR: cannot alter table "svar_test" because session variable "public.var1" uses it
+-- should fail
+DROP TABLE svar_test;
+ERROR: cannot drop table svar_test because other objects depend on it
+DETAIL: session variable var1 depends on type svar_test
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP VARIABLE var1;
+DROP TABLE svar_test;
+-- arrays are supported
+CREATE VARIABLE var1 AS numeric[];
+LET var1 = ARRAY[1.1,2.1];
+LET var1[1] = 10.1;
+SELECT var1;
+ var1
+------------
+ {10.1,2.1}
+(1 row)
+
+-- LET target doesn't allow srf, should fail
+LET var1[generate_series(1,3)] = 100;
+ERROR: set-returning functions are not allowed in LET
+LINE 1: LET var1[generate_series(1,3)] = 100;
+ ^
+DROP VARIABLE var1;
+-- arrays inside composite
+CREATE TYPE t1 AS (a numeric, b numeric[]);
+CREATE VARIABLE var1 AS t1;
+LET var1 = (10.1, ARRAY[0.0, 0.0]);
+LET var1.a = 10.2;
+SELECT var1;
+ var1
+--------------------
+ (10.2,"{0.0,0.0}")
+(1 row)
+
+LET var1.b[1] = 10.3;
+SELECT var1;
+ var1
+---------------------
+ (10.2,"{10.3,0.0}")
+(1 row)
+
+DROP VARIABLE var1;
+DROP TYPE t1;
+-- Encourage use of parallel plans
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers_per_gather = 2;
+-- test on query with workers
+CREATE TABLE svar_test(a int);
+INSERT INTO svar_test SELECT * FROM generate_series(1,1000);
+ANALYZE svar_test;
+CREATE VARIABLE zero int;
+LET zero = 0;
+-- result should be 100
+SELECT count(*) FROM svar_test WHERE a%10 = zero;
+ count
+-------
+ 100
+(1 row)
+
+-- parallel execution is not supported yet
+EXPLAIN (COSTS OFF) SELECT count(*) FROM svar_test WHERE a%10 = zero;
+ QUERY PLAN
+-----------------------------------
+ Aggregate
+ -> Seq Scan on svar_test
+ Filter: ((a % 10) = zero)
+(3 rows)
+
+LET zero = (SELECT count(*) FROM svar_test);
+-- result should be 1000
+SELECT zero;
+ zero
+------
+ 1000
+(1 row)
+
+DROP VARIABLE zero;
+DROP TABLE svar_test;
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers_per_gather;
+-- the result of view should be same in parallel mode too
+CREATE VARIABLE var1 AS int;
+LET var1 = 10;
+CREATE VIEW var1view AS SELECT COALESCE(var1, 0) AS result;
+SELECT * FROM var1view;
+ result
+--------
+ 10
+(1 row)
+
+SET debug_parallel_query TO on;
+SELECT * FROM var1view;
+ result
+--------
+ 10
+(1 row)
+
+SET debug_parallel_query TO off;
+DROP VIEW var1view;
+DROP VARIABLE var1;
+CREATE VARIABLE varid int;
+CREATE TABLE svar_test(id int, v int);
+LET varid = 1;
+INSERT INTO svar_test VALUES(varid, 100);
+SELECT * FROM svar_test;
+ id | v
+----+-----
+ 1 | 100
+(1 row)
+
+UPDATE svar_test SET v = 200 WHERE id = varid;
+SELECT * FROM svar_test;
+ id | v
+----+-----
+ 1 | 200
+(1 row)
+
+DELETE FROM svar_test WHERE id = varid;
+SELECT * FROM svar_test;
+ id | v
+----+---
+(0 rows)
+
+DROP TABLE svar_test;
+DROP VARIABLE varid;
+-- visibility check
+-- variables should be shadowed always
+CREATE VARIABLE var1 AS text;
+SELECT var1.relname FROM pg_class var1 WHERE var1.relname = 'pg_class';
+ relname
+----------
+ pg_class
+(1 row)
+
+DROP VARIABLE var1;
+CREATE TABLE xxtab(avar int);
+INSERT INTO xxtab VALUES(333);
+CREATE TYPE xxtype AS (avar int);
+CREATE VARIABLE xxtab AS xxtype;
+INSERT INTO xxtab VALUES(10);
+-- it is ambiguous, but columns are preferred
+SELECT xxtab.avar FROM xxtab;
+ avar
+------
+ 333
+ 10
+(2 rows)
+
+-- should be ok
+SELECT avar FROM xxtab;
+ avar
+------
+ 333
+ 10
+(2 rows)
+
+CREATE VARIABLE public.avar AS int;
+-- should be ok, see the table
+SELECT avar FROM xxtab;
+ avar
+------
+ 333
+ 10
+(2 rows)
+
+-- should be ok
+SELECT public.avar FROM xxtab;
+ avar
+------
+
+
+(2 rows)
+
+DROP VARIABLE xxtab;
+SELECT xxtab.avar FROM xxtab;
+ avar
+------
+ 333
+ 10
+(2 rows)
+
+DROP VARIABLE public.avar;
+DROP TYPE xxtype;
+DROP TABLE xxtab;
+-- The variable can be shadowed by table or by alias
+CREATE TYPE public.svar_type AS (a int, b int, c int);
+CREATE VARIABLE public.svar AS public.svar_type;
+CREATE TABLE public.svar(a int, b int);
+INSERT INTO public.svar VALUES(10, 20);
+LET public.svar = (100, 200, 300);
+-- should be ok
+-- show table
+SELECT * FROM public.svar;
+ a | b
+----+----
+ 10 | 20
+(1 row)
+
+SELECT svar.a FROM public.svar;
+ a
+----
+ 10
+(1 row)
+
+SELECT svar.* FROM public.svar;
+ a | b
+----+----
+ 10 | 20
+(1 row)
+
+-- show variable
+SELECT public.svar;
+ svar
+---------------
+ (100,200,300)
+(1 row)
+
+SELECT public.svar.c;
+ c
+-----
+ 300
+(1 row)
+
+SELECT (public.svar).*;
+ a | b | c
+-----+-----+-----
+ 100 | 200 | 300
+(1 row)
+
+-- the variable is shadowed, raise error
+SELECT public.svar.c FROM public.svar;
+ERROR: column svar.c does not exist
+LINE 1: SELECT public.svar.c FROM public.svar;
+ ^
+-- can be fixed by alias
+SELECT public.svar.c FROM public.svar x;
+ c
+-----
+ 300
+(1 row)
+
+SELECT svar.a FROM public.svar;
+ a
+----
+ 10
+(1 row)
+
+SELECT svar.* FROM public.svar;
+ a | b
+----+----
+ 10 | 20
+(1 row)
+
+-- show variable
+SELECT public.svar;
+ svar
+---------------
+ (100,200,300)
+(1 row)
+
+SELECT public.svar.c;
+ c
+-----
+ 300
+(1 row)
+
+SELECT (public.svar).*;
+ a | b | c
+-----+-----+-----
+ 100 | 200 | 300
+(1 row)
+
+-- the variable is shadowed, raise error
+SELECT public.svar.c FROM public.svar;
+ERROR: column svar.c does not exist
+LINE 1: SELECT public.svar.c FROM public.svar;
+ ^
+-- can be fixed by alias
+SELECT public.svar.c FROM public.svar x;
+ c
+-----
+ 300
+(1 row)
+
+DROP VARIABLE public.svar;
+DROP TABLE public.svar;
+DROP TYPE public.svar_type;
+CREATE TYPE ab AS (a integer, b integer);
+CREATE VARIABLE v_ab AS ab;
+CREATE TABLE v_ab (a integer, b integer);
+INSERT INTO v_ab VALUES(10,20);
+-- we should see table
+SELECT v_ab.a FROM v_ab;
+ a
+----
+ 10
+(1 row)
+
+CREATE SCHEMA v_ab;
+CREATE VARIABLE v_ab.a AS integer;
+-- we should see table
+SELECT v_ab.a FROM v_ab;
+ a
+----
+ 10
+(1 row)
+
+DROP VARIABLE v_ab;
+DROP TABLE v_ab;
+DROP TYPE ab;
+CREATE TYPE t_am_type AS (b int);
+CREATE SCHEMA xxx_am;
+SET search_path TO public;
+CREATE VARIABLE xxx_am AS t_am_type;
+LET xxx_am = ROW(10);
+-- should be ok
+SELECT xxx_am;
+ xxx_am
+--------
+ (10)
+(1 row)
+
+CREATE VARIABLE xxx_am.b AS int;
+LET :"DBNAME".xxx_am.b = 20;
+-- should be still ok
+SELECT xxx_am;
+ xxx_am
+--------
+ (10)
+(1 row)
+
+-- should fail, the reference should be ambiguous
+SELECT xxx_am.b;
+ERROR: session variable reference "xxx_am.b" is ambiguous
+LINE 1: SELECT xxx_am.b;
+ ^
+-- enhanced references should be ok
+SELECT public.xxx_am.b;
+ b
+----
+ 10
+(1 row)
+
+SELECT :"DBNAME".xxx_am.b;
+ b
+----
+ 20
+(1 row)
+
+CREATE TABLE xxx_am(b int);
+INSERT INTO xxx_am VALUES(10);
+-- we should see table
+SELECT xxx_am.b FROM xxx_am;
+ b
+----
+ 10
+(1 row)
+
+SELECT x.b FROM xxx_am x;
+ b
+----
+ 10
+(1 row)
+
+DROP TABLE xxx_am;
+DROP VARIABLE public.xxx_am;
+DROP VARIABLE xxx_am.b;
+DROP SCHEMA xxx_am;
+CREATE SCHEMA :"DBNAME";
+CREATE VARIABLE :"DBNAME".:"DBNAME".:"DBNAME" AS t_am_type;
+CREATE VARIABLE :"DBNAME".:"DBNAME".b AS int;
+SET search_path TO :"DBNAME";
+-- should be ambiguous
+SELECT :"DBNAME".b;
+ERROR: session variable reference "regression.b" is ambiguous
+LINE 1: SELECT "regression".b;
+ ^
+-- should be ambiguous too
+SELECT :"DBNAME".:"DBNAME".b;
+ERROR: session variable reference "regression.regression.b" is ambiguous
+LINE 1: SELECT "regression"."regression".b;
+ ^
+CREATE TABLE :"DBNAME"(b int);
+-- should be ok
+SELECT :"DBNAME".b FROM :"DBNAME";
+ b
+---
+(0 rows)
+
+DROP TABLE :"DBNAME";
+DROP VARIABLE :"DBNAME".:"DBNAME".b;
+DROP VARIABLE :"DBNAME".:"DBNAME".:"DBNAME";
+DROP SCHEMA :"DBNAME";
+RESET search_path;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 42dfb95a02..db5ba176db 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -59,3 +59,628 @@ DROP VARIABLE svartest.var1;
DROP SCHEMA svartest;
DROP ROLE regress_variable_owner;
+
+-- check access rights
+CREATE ROLE regress_noowner;
+
+CREATE VARIABLE var1 AS int;
+
+CREATE OR REPLACE FUNCTION sqlfx(int)
+RETURNS int AS $$ SELECT $1 + var1 $$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION sqlfx_sd(int)
+RETURNS int AS $$ SELECT $1 + var1 $$ LANGUAGE sql SECURITY DEFINER;
+
+CREATE OR REPLACE FUNCTION plpgsqlfx(int)
+RETURNS int AS $$ BEGIN RETURN $1 + var1; END $$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION plpgsqlfx_sd(int)
+RETURNS int AS $$ BEGIN RETURN $1 + var1; END $$ LANGUAGE plpgsql SECURITY DEFINER;
+
+LET var1 = 10;
+-- should be ok
+SELECT var1;
+SELECT sqlfx(20);
+SELECT sqlfx_sd(20);
+SELECT plpgsqlfx(20);
+SELECT plpgsqlfx_sd(20);
+
+-- should fail
+SET ROLE TO regress_noowner;
+
+SELECT var1;
+SELECT sqlfx(20);
+SELECT plpgsqlfx(20);
+
+-- should be ok
+SELECT sqlfx_sd(20);
+SELECT plpgsqlfx_sd(20);
+
+SET ROLE TO DEFAULT;
+GRANT SELECT ON VARIABLE var1 TO regress_noowner;
+
+-- should be ok
+SET ROLE TO regress_noowner;
+
+SELECT var1;
+SELECT sqlfx(20);
+SELECT plpgsqlfx(20);
+
+SET ROLE TO DEFAULT;
+DROP VARIABLE var1;
+DROP FUNCTION sqlfx(int);
+DROP FUNCTION plpgsqlfx(int);
+DROP FUNCTION sqlfx_sd(int);
+DROP FUNCTION plpgsqlfx_sd(int);
+
+DROP ROLE regress_noowner;
+
+-- use variables inside views
+CREATE VARIABLE var1 AS numeric;
+
+-- use variables in views
+CREATE VIEW test_view AS SELECT COALESCE(var1 + v, 0) AS result FROM generate_series(1,2) g(v);
+SELECT * FROM test_view;
+LET var1 = 3.14;
+SELECT * FROM test_view;
+
+-- start a new session
+\c
+
+SELECT * FROM test_view;
+LET var1 = 3.14;
+SELECT * FROM test_view;
+
+-- should fail, dependency
+DROP VARIABLE var1;
+
+-- should be ok
+DROP VARIABLE var1 CASCADE;
+
+CREATE VARIABLE var1 text;
+CREATE VARIABLE var2 text;
+
+-- use variables in SQL functions
+CREATE OR REPLACE FUNCTION sqlfx1(varchar)
+RETURNS varchar AS $$ SELECT var1 || ', ' || $1 $$ LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION sqlfx2( varchar)
+RETURNS varchar AS $$ SELECT var2 || ', ' || $1 $$ LANGUAGE sql;
+
+LET var1 = 'str1';
+LET var2 = 'str2';
+
+SELECT sqlfx1(sqlfx2('Hello'));
+
+-- inlining is blocked
+EXPLAIN (COSTS OFF, VERBOSE) SELECT sqlfx1(sqlfx2('Hello'));
+
+DROP FUNCTION sqlfx1(varchar);
+DROP FUNCTION sqlfx2(varchar);
+DROP VARIABLE var1;
+DROP VARIABLE var2;
+
+-- access from cached plans should work
+CREATE VARIABLE var1 AS numeric;
+
+CREATE OR REPLACE FUNCTION plpgsqlfx()
+RETURNS numeric AS $$ BEGIN RETURN var1; END $$ LANGUAGE plpgsql;
+
+set plan_cache_mode TO force_generic_plan;
+
+LET var1 = 3.14;
+SELECT plpgsqlfx();
+LET var1 = 3.14 * 2;
+SELECT plpgsqlfx();
+
+DROP VARIABLE var1;
+
+-- dependency (plan invalidation) should work
+CREATE VARIABLE var1 AS numeric;
+
+LET var1 = 3.14 * 3;
+SELECT plpgsqlfx();
+LET var1 = 3.14 * 4;
+SELECT plpgsqlfx();
+
+DROP VARIABLE var1;
+DROP FUNCTION plpgsqlfx();
+
+set plan_cache_mode TO DEFAULT;
+
+-- usage LET statement in plpgsql should work
+CREATE VARIABLE var1 int;
+CREATE VARIABLE var2 numeric[];
+
+DO $$
+BEGIN
+ LET var2 = '{}'::int[];
+ FOR i IN 1..10
+ LOOP
+ LET var1 = i;
+ LET var2[var1] = i;
+ END LOOP;
+ RAISE NOTICE 'result array: %', var2;
+END;
+$$;
+
+DROP VARIABLE var1;
+DROP VARIABLE var2;
+
+-- CALL statement is not supported yet
+-- requires direct access to session variable from expression executor
+CREATE VARIABLE v int;
+
+CREATE PROCEDURE p(arg int) AS $$ BEGIN RAISE NOTICE '%', arg; END $$ LANGUAGE plpgsql;
+
+-- should not crash (but is not supported yet)
+CALL p(v);
+
+DO $$ BEGIN CALL p(v); END $$;
+
+DROP PROCEDURE p(int);
+DROP VARIABLE v;
+
+-- test search path
+CREATE SCHEMA svartest;
+CREATE VARIABLE svartest.var1 AS numeric;
+
+-- should fail
+LET var1 = pi();
+SELECT var1;
+
+-- should be ok
+LET svartest.var1 = pi();
+SELECT svartest.var1;
+
+SET search_path TO svartest;
+
+-- should be ok
+LET var1 = pi() + 10;
+SELECT var1;
+
+RESET search_path;
+DROP SCHEMA svartest CASCADE;
+
+CREATE VARIABLE var1 AS text;
+
+-- variables can be updated under RO transaction
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET var1 = 'hello';
+COMMIT;
+
+SELECT var1;
+
+DROP VARIABLE var1;
+
+-- test of domains
+CREATE DOMAIN int_domain AS int NOT NULL CHECK (VALUE > 100);
+CREATE VARIABLE var1 AS int_domain;
+
+-- should fail
+SELECT var1;
+
+-- should be ok
+LET var1 = 1000;
+SELECT var1;
+
+-- should fail
+LET var1 = 10;
+
+-- should fail
+LET var1 = NULL;
+
+-- note - domain defaults are not supported yet (like PLpgSQL)
+
+DROP VARIABLE var1;
+DROP DOMAIN int_domain;
+
+-- the expression should remain "unknown"
+CREATE VARIABLE var1 AS int4multirange[];
+-- should be ok
+LET var1 = NULL;
+LET var1 = '{"{[2,8),[11,14)}","{[5,8),[12,14)}"}';
+LET var1[2] = '{[5,8),[12,100)}';
+SELECT var1;
+
+--It should work in plpgsql too
+DO $$
+BEGIN
+ LET var1 = NULL;
+ LET var1 = '{"{[2,8),[11,14)}","{[5,8),[12,14)}"}';
+ LET var1[2] = '{[5,8),[12,100)}';
+
+ RAISE NOTICE '%', var1;
+END;
+$$;
+
+DROP VARIABLE var1;
+
+CREATE SCHEMA svartest CREATE VARIABLE var1 AS int CREATE TABLE foo(a int);
+LET svartest.var1 = 100;
+SELECT svartest.var1;
+
+SET search_path to public, svartest;
+
+SELECT var1;
+
+DROP SCHEMA svartest CASCADE;
+
+CREATE VARIABLE var1 AS int;
+CREATE VARIABLE var2 AS int[];
+
+LET var1 = 2;
+LET var2 = '{}'::int[];
+
+LET var2[var1] = 0;
+
+SELECT var2;
+
+DROP VARIABLE var1, var2;
+
+CREATE VARIABLE var1 AS int;
+CREATE VARIABLE var2 AS int[];
+
+LET var1 = 2;
+LET var2 = '{}'::int[];
+
+SELECT var2;
+
+DROP VARIABLE var1, var2;
+
+-- the LET statement should be disallowed in CTE
+CREATE VARIABLE var1 AS int;
+WITH x AS (LET var1 = 100) SELECT * FROM x;
+
+-- should be ok
+LET var1 = generate_series(1, 1);
+
+-- should fail
+LET var1 = generate_series(1, 2);
+LET var1 = generate_series(1, 0);
+
+DROP VARIABLE var1;
+
+-- composite variables
+CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2));
+
+CREATE VARIABLE v1 AS sv_xyz;
+CREATE VARIABLE v2 AS sv_xyz;
+
+LET v1 = (1, 2, 3.14);
+LET v2 = (10, 20, 3.14 * 10);
+
+-- should work too - there are prepared casts
+LET v1 = (1, 2, 3);
+
+SELECT v1;
+SELECT v2;
+SELECT (v1).*;
+SELECT (v2).*;
+
+SELECT v1.x + v1.z;
+SELECT v2.x + v2.z;
+
+-- access to composite fields should be safe too
+CREATE ROLE regress_var_test_role;
+
+SET ROLE TO regress_var_test_role;
+
+-- should fail
+SELECT v2.x;
+
+SET ROLE TO DEFAULT;
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+DROP ROLE regress_var_test_role;
+
+CREATE TYPE t1 AS (a int, b numeric, c text);
+
+CREATE VARIABLE v1 AS t1;
+LET v1 = (1, pi(), 'hello');
+SELECT v1;
+LET v1.b = 10.2222;
+SELECT v1;
+
+-- should fail, attribute doesn't exist
+LET v1.x = 10;
+
+-- should fail, don't allow multi column query
+LET v1 = (NULL::t1).*;
+
+-- allow DROP or ADD ATTRIBUTE on composite types
+-- should be ok
+ALTER TYPE t1 DROP ATTRIBUTE c;
+SELECT v1;
+
+-- should be ok
+ALTER TYPE t1 ADD ATTRIBUTE c int;
+SELECT v1;
+
+LET v1 = (10, 10.3, 20);
+SELECT v1;
+
+-- should be ok
+ALTER TYPE t1 DROP ATTRIBUTE b;
+SELECT v1;
+
+-- should fail, disallow data type change
+ALTER TYPE t1 ALTER ATTRIBUTE c TYPE int;
+
+DROP VARIABLE v1;
+DROP TYPE t1;
+
+-- the table type can be used as composite type too
+CREATE TABLE svar_test(a int, b numeric, c date);
+CREATE VARIABLE var1 AS svar_test;
+
+LET var1 = (10, pi(), '2023-05-26');
+SELECT var1;
+
+-- should fail due dependency
+ALTER TABLE svar_test ALTER COLUMN a TYPE text;
+
+-- should fail
+DROP TABLE svar_test;
+
+DROP VARIABLE var1;
+DROP TABLE svar_test;
+
+-- arrays are supported
+CREATE VARIABLE var1 AS numeric[];
+LET var1 = ARRAY[1.1,2.1];
+LET var1[1] = 10.1;
+SELECT var1;
+
+-- LET target doesn't allow srf, should fail
+LET var1[generate_series(1,3)] = 100;
+
+DROP VARIABLE var1;
+
+-- arrays inside composite
+CREATE TYPE t1 AS (a numeric, b numeric[]);
+CREATE VARIABLE var1 AS t1;
+LET var1 = (10.1, ARRAY[0.0, 0.0]);
+LET var1.a = 10.2;
+SELECT var1;
+LET var1.b[1] = 10.3;
+SELECT var1;
+
+DROP VARIABLE var1;
+DROP TYPE t1;
+
+-- Encourage use of parallel plans
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers_per_gather = 2;
+
+-- test on query with workers
+CREATE TABLE svar_test(a int);
+INSERT INTO svar_test SELECT * FROM generate_series(1,1000);
+ANALYZE svar_test;
+CREATE VARIABLE zero int;
+LET zero = 0;
+
+-- result should be 100
+SELECT count(*) FROM svar_test WHERE a%10 = zero;
+
+-- parallel execution is not supported yet
+EXPLAIN (COSTS OFF) SELECT count(*) FROM svar_test WHERE a%10 = zero;
+
+LET zero = (SELECT count(*) FROM svar_test);
+
+-- result should be 1000
+SELECT zero;
+
+DROP VARIABLE zero;
+DROP TABLE svar_test;
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers_per_gather;
+
+-- the result of view should be same in parallel mode too
+CREATE VARIABLE var1 AS int;
+LET var1 = 10;
+
+CREATE VIEW var1view AS SELECT COALESCE(var1, 0) AS result;
+
+SELECT * FROM var1view;
+
+SET debug_parallel_query TO on;
+
+SELECT * FROM var1view;
+
+SET debug_parallel_query TO off;
+
+DROP VIEW var1view;
+DROP VARIABLE var1;
+
+CREATE VARIABLE varid int;
+CREATE TABLE svar_test(id int, v int);
+
+LET varid = 1;
+INSERT INTO svar_test VALUES(varid, 100);
+SELECT * FROM svar_test;
+UPDATE svar_test SET v = 200 WHERE id = varid;
+SELECT * FROM svar_test;
+DELETE FROM svar_test WHERE id = varid;
+SELECT * FROM svar_test;
+
+DROP TABLE svar_test;
+DROP VARIABLE varid;
+
+
+-- visibility check
+-- variables should be shadowed always
+CREATE VARIABLE var1 AS text;
+SELECT var1.relname FROM pg_class var1 WHERE var1.relname = 'pg_class';
+
+DROP VARIABLE var1;
+
+CREATE TABLE xxtab(avar int);
+
+INSERT INTO xxtab VALUES(333);
+
+CREATE TYPE xxtype AS (avar int);
+
+CREATE VARIABLE xxtab AS xxtype;
+
+INSERT INTO xxtab VALUES(10);
+
+-- it is ambiguous, but columns are preferred
+SELECT xxtab.avar FROM xxtab;
+
+-- should be ok
+SELECT avar FROM xxtab;
+
+CREATE VARIABLE public.avar AS int;
+
+-- should be ok, see the table
+SELECT avar FROM xxtab;
+
+-- should be ok
+SELECT public.avar FROM xxtab;
+
+DROP VARIABLE xxtab;
+
+SELECT xxtab.avar FROM xxtab;
+
+DROP VARIABLE public.avar;
+
+DROP TYPE xxtype;
+
+DROP TABLE xxtab;
+
+-- The variable can be shadowed by table or by alias
+CREATE TYPE public.svar_type AS (a int, b int, c int);
+CREATE VARIABLE public.svar AS public.svar_type;
+
+CREATE TABLE public.svar(a int, b int);
+
+INSERT INTO public.svar VALUES(10, 20);
+
+LET public.svar = (100, 200, 300);
+
+-- should be ok
+-- show table
+SELECT * FROM public.svar;
+SELECT svar.a FROM public.svar;
+SELECT svar.* FROM public.svar;
+
+-- show variable
+SELECT public.svar;
+SELECT public.svar.c;
+SELECT (public.svar).*;
+
+-- the variable is shadowed, raise error
+SELECT public.svar.c FROM public.svar;
+
+-- can be fixed by alias
+SELECT public.svar.c FROM public.svar x;
+
+SELECT svar.a FROM public.svar;
+SELECT svar.* FROM public.svar;
+
+-- show variable
+SELECT public.svar;
+SELECT public.svar.c;
+SELECT (public.svar).*;
+
+-- the variable is shadowed, raise error
+SELECT public.svar.c FROM public.svar;
+
+-- can be fixed by alias
+SELECT public.svar.c FROM public.svar x;
+
+DROP VARIABLE public.svar;
+DROP TABLE public.svar;
+DROP TYPE public.svar_type;
+
+CREATE TYPE ab AS (a integer, b integer);
+
+CREATE VARIABLE v_ab AS ab;
+
+CREATE TABLE v_ab (a integer, b integer);
+INSERT INTO v_ab VALUES(10,20);
+
+-- we should see table
+SELECT v_ab.a FROM v_ab;
+
+CREATE SCHEMA v_ab;
+
+CREATE VARIABLE v_ab.a AS integer;
+
+-- we should see table
+SELECT v_ab.a FROM v_ab;
+
+DROP VARIABLE v_ab;
+DROP TABLE v_ab;
+DROP TYPE ab;
+
+CREATE TYPE t_am_type AS (b int);
+CREATE SCHEMA xxx_am;
+
+SET search_path TO public;
+
+CREATE VARIABLE xxx_am AS t_am_type;
+LET xxx_am = ROW(10);
+
+-- should be ok
+SELECT xxx_am;
+
+CREATE VARIABLE xxx_am.b AS int;
+LET :"DBNAME".xxx_am.b = 20;
+
+-- should be still ok
+SELECT xxx_am;
+
+-- should fail, the reference should be ambiguous
+SELECT xxx_am.b;
+
+-- enhanced references should be ok
+SELECT public.xxx_am.b;
+SELECT :"DBNAME".xxx_am.b;
+
+CREATE TABLE xxx_am(b int);
+INSERT INTO xxx_am VALUES(10);
+
+-- we should see table
+SELECT xxx_am.b FROM xxx_am;
+SELECT x.b FROM xxx_am x;
+
+DROP TABLE xxx_am;
+DROP VARIABLE public.xxx_am;
+DROP VARIABLE xxx_am.b;
+DROP SCHEMA xxx_am;
+
+CREATE SCHEMA :"DBNAME";
+
+CREATE VARIABLE :"DBNAME".:"DBNAME".:"DBNAME" AS t_am_type;
+CREATE VARIABLE :"DBNAME".:"DBNAME".b AS int;
+
+SET search_path TO :"DBNAME";
+
+-- should be ambiguous
+SELECT :"DBNAME".b;
+
+-- should be ambiguous too
+SELECT :"DBNAME".:"DBNAME".b;
+
+CREATE TABLE :"DBNAME"(b int);
+
+-- should be ok
+SELECT :"DBNAME".b FROM :"DBNAME";
+
+DROP TABLE :"DBNAME";
+
+DROP VARIABLE :"DBNAME".:"DBNAME".b;
+DROP VARIABLE :"DBNAME".:"DBNAME".:"DBNAME";
+DROP SCHEMA :"DBNAME";
+
+RESET search_path;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 837e5b83a3..bc3dbd7260 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1494,6 +1494,7 @@ LargeObjectDesc
Latch
LauncherLastStartTimesEntry
LerpFunc
+LetStmt
LexDescr
LexemeEntry
LexemeHashKey
@@ -2590,6 +2591,7 @@ SerializedTransactionState
Session
SessionBackupState
SessionEndType
+SessionVariableValue
SetConstraintState
SetConstraintStateData
SetConstraintTriggerData
@@ -2782,6 +2784,9 @@ SupportRequestRows
SupportRequestSelectivity
SupportRequestSimplify
SupportRequestWFuncMonotonic
+SVariable
+SVariableData
+SVariableState
Syn
SyncOps
SyncRepConfigData
--
2.45.2
view thread (439+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: proposal: schema variables
In-Reply-To: <[email protected]>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox