Subject: Re: Schema variables - new implementation for Postgres 15

From: Pavel Stehule <[email-masked]>
Date: Wed, 23 Mar 2022 21:58:59 +0100

Hi

A bit more work seems to be needed for deparsing session variables:
>
> # create variable myvar text;
> CREATE VARIABLE
>
> # create view myview as select myvar;
> CREATE VIEW
>
> # \d+ myview
>                           View "public.myview"
>  Column | Type | Collation | Nullable | Default | Storage  | Description
> --------+------+-----------+----------+---------+----------+-------------
>  myvar  | text |           |          |         | extended |
> View definition:
>  SELECT myvar AS myvar;
>
> There shouldn't be an explicit alias I think.
>

I check this issue, and I afraid so it is not fixable. The target list
entry related to session variable has not some magic value like ?column?
that can be used for check if tle->resname is implicit or explicit

And in this time I cannot to use FigureColname because it doesn't work with
transformed nodes. More - the Param node can be nested in SubscriptingRef
or FieldSelect. It doesn't work perfectly now. See following example:

create type xt as (a int, b int);
create view b as select (10, ((random()*100)::int)::xt).b;
\d+ b
SELECT (ROW(10, (random() * 100::double precision)::integer)::xt).b AS b;

Regards

Pavel

---

From: Julien Rouhaud <[email-masked]>
Date: Fri, 25 Mar 2022 12:18:42 +0800

Hi,

> SELECT (ROW(10, (random() * 100::double precision)::integer)::xt).b AS b;

Fair enough.  Since there is other code that already behaves the same I agree
that it's better to not add special cases in ruleutils.c and have an explicit
alias in the deparsed view, which isn't incorrect.

---

From: Julien Rouhaud <[email-masked]>
Date: Mon, 11 Apr 2022 23:34:32 +0800

Hi,

On Sun, Apr 10, 2022 at 03:43:33PM -0500, Justin Pryzby wrote:
> On Sun, Apr 10, 2022 at 08:30:39PM +0200, Pavel Stehule wrote:
> > I am sending fresh rebased patch + separation to more patches. This split
> > is initial, and can be changed later
> 
> The 0001 patch requires this, but it's not included until 0003.
> src/include/commands/session_variable.h
> 
> Each patch should compile and pass tests with the preceding patches, without
> the following patches.  I think the regression tests should be included with
> their corresponding patch.  Maybe it's ok to separate out the changes for
> pg_dump, docs, and psql - but they'd have to be merged together eventually.
> I realize some of this runs counter to Julien's suggestion to split patches.

Note that most of my suggestions were only to make the patch easier to review,
which was mostly trying to limit a bit the core of the new code.

Unfortunately, given the feature we can't really split the patch in many and
smaller parts and expect them to be completely self contained, so I'm not
against splitting smaller chunks like psql support and whatnot.  But I'm not
convinced that it will make it easier to review.

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 21 Jun 2022 10:46:06 +0200

čt 3. 3. 2022 v 8:06 odesílatel Julien Rouhaud <[email-masked]> napsal:

> Hi,
>
> On Wed, Mar 02, 2022 at 06:03:06AM +0100, Pavel Stehule wrote:
> >
> > I lost commit with this change. I am sending updated patch.
>
> Thanks a lot Pavel!
>
> I did a more thorough review of the patch.  I'm attaching a diff (in .txt
> extension) for comment improvement suggestions.  I may have misunderstood
> things so feel free to discard some of it.  I will mention the comment I
> didn't
> understand in this mail.
>
> First, I spotted some problem in the invalidation logic.
>
> + * Assign sinval mark to session variable. This mark probably
> + * signalized, so the session variable was dropped. But this
> + * should be rechecked later against system catalog.
> + */
> +static void
> +pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
>
> You mention that hashvalue can only be zero for commands that can't
> affect session variables (like VACUUM or ANALYZE), but that's not true.
> It can
> also happen in case of sinval queue overflow (see
> InvalidateSystemCaches()).
> So in that case we should trigger a full recheck, with some heuristics on
> how
> to detect that a cached variable is still valid.  Unfortunately the oid can
> wraparound so some other check is needed to make it safe.
>
> Also, even if we get a non-zero hashvalue in the inval callback, we can't
> assume that there weren't any collision in the hash.  So the additional
> check
> should be used there too.
>
> We had a long off-line discussion about this with Pavel yesterday on what
> heuristic to use there.  Unlike other caches where discarding an entry
> when it
> shouldn't have been is not really problematic, the cache here contains the
> real
> variable value so we can't discard it unless the variable was really
> dropped.
> It should be possible to make it work, so I will let Pavel comment on which
> approach he wants to use and what the drawbacks are.  I guess that this
> will be
> the most critical part of this patch to decide whether the approach is
> acceptable or not.
>

I thought more about this issue, and I think it is solvable, although
differently (little bit than we talked about). The check based on oid and
xmin should not be enough for consistency check, because xmin can be
quickly lost when a user executes VACUUM FREEZE or VACUUM FULL.

The consistency of a stored session variable should be checked always when
the session variable is used (for reading) the first time in a
transaction.  When value is created and used in the same transaction, then
the consistency check is not necessary. When consistency check fails, then
stored value is marked as broken and cannot be read. Can be overwritten.

We can believe that session variables based on buildin types are always
consistent.

Composite types should be checked recursively from top to buildin types. It
means we should hold tupledescs for all nested composites. Initially the
check can be very strict.

Last case is consistency check for types owned by some extensions. For this
case we can accept the version number of related extensions. Without change
we can believe so the stored binary data are consistent.

>
> The rest is only minor stylistic comments.
>
> Using -DRAW_EXPRESSION_COVERAGE_TEST I see that T_LetStmt is missing in
> raw_expression_tree_walker.
>

fixed

>
> ALTER and DROP both suggest "IMMUTABLE VARIABLE" as valid completion, while
> it should only be usable in the CREATE [ IMMUTABLE ] VARIABLE form.
>

fixed

>
> +initVariable(Variable *var, Oid varid, bool fast_only)
> +{
> +   var->collation = varform->varcollation;
> +   var->eoxaction = varform->vareoxaction;
> +   var->is_not_null = varform->varisnotnull;
> +   var->is_immutable = varform->varisimmutable;
>
> nit: eoxaction is defined after is_not_null and is_immutable, it would be
> better to keep the initialization order consistent (same in
> VariableCreate).
>

fixed

>
> +   values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum((char)
> varCollation);
> +   values[Anum_pg_variable_vareoxaction - 1] = CharGetDatum(eoxaction);
>
> seems like the char cast is on the wrong variable?
>

fixed

>
> + * [...] We have to hold two separate action lists:
> + * one for dropping the session variable from system catalog, and
> + * another one for resetting its value. Both are necessary, since
> + * dropping a session variable also needs to enforce a reset of
> + * the value.
>
> I don't fully understand that comment.  Maybe you meant that the opposite
> isn't
> true, ie. highlight that a reset should *not* drop the variable thus two
> lists?
>

I tried to describe the issue in the comment. When I have just one action
list, then I had a problem with impossibility to extend this list about
reset action enforced by drop variable when I iterated over this list in
xact time. This issue was solved by using two lists - one for drop and
second for reset and recheck.

>
> +typedef enum SVariableXActAction
> +{
> +   SVAR_ON_COMMIT_DROP,        /* used for ON COMMIT DROP */
> +   SVAR_ON_COMMIT_RESET,       /* used for DROP VARIABLE */
> +   SVAR_RESET,                 /* used for ON TRANSACTION END RESET */
> +   SVAR_RECHECK                /* verify if session variable still exists
> */
> +} SVariableXActAction;
> +
> +typedef struct SVariableXActActionItem
> +{
> +   Oid         varid;          /* varid of session variable */
> +   SVariableXActAction action; /* reset or drop */
>
> the stored action isn't simply "reset or drop", even though the resulting
> action will be a reset or a drop (or a no-op) right?  Since it's storing a
> enum
> define just before, I'd just drop the comment on action, and maybe specify
> that
> SVAR_RECHECK will do appropriate cleanup if the session variable doesn't
> exist.
>
> done

>
> + * Release the variable defined by varid from sessionvars
> + * hashtab.
> + */
> +static void
> +free_session_variable(SVariable svar)
>
> The function name is a bit confusing given the previous function.  Maybe
> this
> one should be called forget_session_variable() instead, or something like
> that?
>
> I think the function comment should also mention that caller is
> responsible for
> making sure that the sessionvars htab exists before calling it, for extra
> clarity, or just add an assert for that.
>
> +static void
> +free_session_variable_varid(Oid varid)
>
> Similary, maybe renaming this function forget_session_variable_by_id()?
>

I don't like "forget" too much - maybe "remove" can be used instead - like
HASH_REMOVE

> +static void
> +create_sessionvars_hashtable(void)
> +{
> +   HASHCTL     ctl;
> +
> +   /* set callbacks */
> +   if (first_time)
> +   {
> +       /* Read sinval messages */
> +       CacheRegisterSyscacheCallback(VARIABLEOID,
> +                                     pg_variable_cache_callback,
> +                                     (Datum) 0);
> +
> +       first_time = false;
> +   }
> +
> +   /* needs its own long lived memory context */
> +   if (SVariableMemoryContext == NULL)
> +   {
> +       SVariableMemoryContext =
> +           AllocSetContextCreate(TopMemoryContext,
> +                                 "session variables",
> +                                 ALLOCSET_START_SMALL_SIZES);
> +   }
>
> As far as I can see the SVariableMemoryContext can be reset but never set
> to
> NULL, so I think the initialization can be done in the first_time case, and
> otherwise asserted that it's not NULL.
>

done

>
> +   if (!isnull && svar->typid != typid)
> +       ereport(ERROR,
> +               (errcode(ERRCODE_DATATYPE_MISMATCH),
> +                errmsg("type \"%s\" of assigned value is different than
> type \"%s\" of session variable \"%s.%
>
> Why testing isnull?  I don't think it's ok to allow NULL::text in an int
> variable for instance.  This isn't valid in other context (like inserting
> in a
> table)
>

changed

>
> +    * result of default expression always). Don't do this check, when
> variable
> +    * is initialized.
> +    */
> +   if (!init_mode &&
>
> I think the last part of the comment is a bit misleading.  Maybe "when
> variable
> is being initialized" (and similary same for the function comment).
>

changed

>
> + * We try not to break the previous value, if something is wrong.
> + *
> + * As side efect this function acquires AccessShareLock on
> + * related session variable until commit.
> + */
> +void
> +SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid)
>
> I don't understand what you mean by "We try not to break the previous
> value, if
> something is wrong".
>

That means, so SetSessionVariable sets a new value or should preserve the
original value.

>
> +   /* Initialize svar when not initialized or when stored value is null */
> +   if (!found)
> +   {
> +       Variable    var;
> +
> +       /* don't need defexpr and acl here */
> +       initVariable(&var, varid, true);
> +       init_session_variable(svar, &var);
> +   }
> +
> +   set_session_variable(svar, value, isNull, typid, false);
>
> Shouldn't the comment be on the set_session_variable() vall rather than on
> the
> !found block?
>

This comment is obsolete,

removed

>
> + * Returns the value of the session variable specified by varid. Check
> correct
> + * result type. Optionally the result can be copied.
> + */
> +Datum
> +GetSessionVariable(Oid varid, bool *isNull, Oid expected_typid, bool copy)
>
> All callers use copy == true, couldn't we get rid of it and say it returns
> a
> copy of the value if any?
>

I replaced it with the new function CopySessionVariableWithTypeCheck.
Probably in almost all situations, the copy will be required. And if not,
we can enhance the API later.

> + * Create new ON_COMMIT_DROP xact action. We have to drop
> + * ON COMMIT DROP variable, although this variable should not
> + * be used. So we need to register this action in CREATE VARIABLE
> + * time.
>
> I don't understand this comment.
>

changed

>
> +AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
> +{
> +   ListCell   *l;
> +
> +   foreach(l, xact_drop_actions)
> +   {
> +       SVariableXActActionItem *xact_ai =
> +                           (SVariableXActActionItem *) lfirst(l);
> +
> +       /* Iterate only over non dropped entries */
> +       if (xact_ai->deleting_subid == InvalidSubTransactionId)
> +       {
> +           Assert(xact_ai->action == SVAR_ON_COMMIT_DROP);
>
> The assert sould probably be in the block above.
>

moved

>
> +            * We want to reset session variable (release it from
> +            * local memory) when RESET is required or when session
> +            * variable was removed explicitly (DROP VARIABLE) or
> +            * implicitly (ON COMMIT DROP). Explicit releasing should
> +            * be done only if the transaction is commited.
> +            */
> +           if ((xact_ai->action == SVAR_RESET) ||
> +               (xact_ai->action == SVAR_ON_COMMIT_RESET &&
> +                xact_ai->deleting_subid == InvalidSubTransactionId &&
> +                isCommit))
> +               free_session_variable_varid(xact_ai->varid);
>
> This chunk is a bit hard to follow.  Also, for SVAR_RESET wouldn't it be
> better
> to only make the svar invalid and keep it in the htab?  If so, this could
> be
> split in two different branches which would be easier to follow.
>

After some experiments, I think it is more simple to remove the svar entry
in htab. It reduces the state space, and variable initialization once per
transaction is not expensive. The problem is in necessary xact action
registration and now I can call it simply just from init_session_variable.
I updated  comments there

>
> +       if (!isCommit &&
> +           xact_ai->creating_subid == mySubid &&
> +           xact_ai->action != SVAR_RESET &&
> +           xact_ai->action != SVAR_RECHECK)
> +       {
> +           /* cur_item must be removed */
> +           xact_reset_actions =
> foreach_delete_current(xact_reset_actions, cur_item);
> +           pfree(xact_ai);
>
> I think that be definition only the SVAR_ON_COMMIT_DROP (cleaning entry
> for a
> dropped session variable) will ever need to be removed there, so we should
> check for that instead of not being something else?
>
>
fixed

>
> +   /*
> +    * Prepare session variables, if not prepared in queryDesc
> +    */
> +   if (queryDesc->num_session_variables > 0)
>

I don't understand that comment.

I changed this comment

>
> +static void
> +svariableStartupReceiver(DestReceiver *self, int operation, TupleDesc
> typeinfo)
> +{
> +   svariableState *myState = (svariableState *) self;
> +   int         natts = typeinfo->natts;
> +   int         outcols = 0;
> +   int         i;
> +
> +   for (i = 0; i < natts; i++)
> +   {
> +       Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
> +
> +       if (attr->attisdropped)
> +           continue;
> +
> +       if (++outcols > 1)
> +           elog(ERROR, "svariable DestReceiver can take only one
> attribute");
> +
> +       myState->typid = attr->atttypid;
> +       myState->typmod = attr->atttypmod;
> +       myState->typlen = attr->attlen;
> +       myState->slot_offset = i;
> +   }
> +
> +   myState->rows = 0;
> +}
>
> Maybe add an initial Assert to make sure that caller did call
> SetVariableDestReceiverParams(), and final check that one attribute was
> found?
>

done

>
> @@ -1794,15 +1840,39 @@ fix_expr_common(PlannerInfo *root, Node *node)
>                 g->cols = cols;
>         }
>     }
> +   else if (IsA(node, Param))
> +   {
> +       Param *p = (Param *) node;
> +
> +       if (p->paramkind == PARAM_VARIABLE)
> +       {
> +           PlanInvalItem *inval_item = makeNode(PlanInvalItem);
> +
> +           /* paramid is still session variable id */
> +           inval_item->cacheId = VARIABLEOID;
> +           inval_item->hashValue = GetSysCacheHashValue1(VARIABLEOID,
> +
>  ObjectIdGetDatum(p->paramvarid));
> +
> +           /* Append this variable to global, register dependency */
> +           root->glob->invalItems = lappend(root->glob->invalItems,
> +                                            inval_item);
> +       }
> +   }
>
> I didn't see any test covering invalidation of cached plan using session
> variables.  Could you add some?  While at it, maybe use different values
> on the
> sesssion_variable.sql tests rather than 100 in many places, so it's easier
> to
> identifier which case broke in case of problem.
>

I created new tests there

>
> +static Node *
> +makeParamSessionVariable(ParseState *pstate,
> +                       Oid varid, Oid typid, int32 typmod, Oid collid,
> +                       char *attrname, int location)
> +{
> [...]
> +   /*
> +    * There are two ways to access session variables - direct, used by
> simple
> +    * plpgsql expressions, where it is not necessary to emulate stability.
> +    * And Buffered access, which is used everywhere else. We should ensure
> +    * stable values, and because session variables are global, then we
> should
> +    * work with copied values instead of directly accessing variables. For
> +    * direct access, the varid is best. For buffered access, we need
> +    * to assign an index to the buffer - later, when we know what
> variables are
> +    * used. Now, we just remember, so we use session variables.
>
> I don't understand the last part, starting with "For buffered access, we
> need...".  Also, the beginning of the comment seems like something more
> general
> and may be moved somewhere, maybe at the beginning of sessionvariable.c?
>

moved to sessionvariable.c and modified.

> +    * stmt->query is SelectStmt node. An tranformation of
> +    * this node doesn't support SetToDefault node. Instead injecting
> +    * of transformSelectStmt or parse state, we can directly
> +    * transform target list here if holds SetToDefault node.
> +    */
> +   if (stmt->set_default)
>
> I don't understand this comment.  Especially since the next
> transformTargetList() will emit SetToDefault node that will be handled
> later in
> that function and then in RewriteQuery.
>

This is messy, sorry. SelectStmt doesn't support SetToDefault. LetStmt
supports it. I reworded.

> +   /*
> +    * rewrite SetToDefaults needs varid in Query structure
> +    */
> +   query->resultVariable = varid;
>
> I also don't understand that comment.  Is is always set just in case
> there's a
> SetToDefault, or something else?
>

This comment is not complete. This value is required by QueryRewriter (for
replacement of the SetToDefault node by defexpr). It is required for
acquiring locks, and for execution.

I rewrote this comment

>
> +   /* translate paramvarid to session variable name */
> +   if (param->paramkind == PARAM_VARIABLE)
> +   {
> +       appendStringInfo(context->buf, "%s",
> +
> generate_session_variable_name(param->paramvarid));
> +       return;
> +   }
>

>  Column | Type | Collation | Nullable | Default | Storage  | Description

this issue was described in other thread

I am sending rebased, updated patches. The type check is not implemented
yet.

Regards

Pavel
From 5589c246320da3708495632ef2feff346595b712 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 11/11] documentation

Documentation for CREATE VARIABLE, DROP VARIABLE and LET commands. Update of
GRANT, REVOKE, DISCARD, ALTER commands related to support of session variables.
---
 doc/src/sgml/advanced.sgml                    |  62 +++++
 doc/src/sgml/catalogs.sgml                    | 158 +++++++++++++
 doc/src/sgml/config.sgml                      |  15 ++
 doc/src/sgml/event-trigger.sgml               |  24 ++
 doc/src/sgml/glossary.sgml                    |  16 ++
 doc/src/sgml/plpgsql.sgml                     |  12 +
 doc/src/sgml/ref/allfiles.sgml                |   4 +
 .../sgml/ref/alter_default_privileges.sgml    |  26 ++-
 doc/src/sgml/ref/alter_variable.sgml          | 179 ++++++++++++++
 doc/src/sgml/ref/comment.sgml                 |   1 +
 doc/src/sgml/ref/create_variable.sgml         | 219 ++++++++++++++++++
 doc/src/sgml/ref/discard.sgml                 |  14 +-
 doc/src/sgml/ref/drop_variable.sgml           | 118 ++++++++++
 doc/src/sgml/ref/grant.sgml                   |  23 ++
 doc/src/sgml/ref/let.sgml                     | 108 +++++++++
 doc/src/sgml/ref/pg_restore.sgml              |  11 +
 doc/src/sgml/ref/revoke.sgml                  |   6 +
 doc/src/sgml/ref/set_role.sgml                |   2 +-
 doc/src/sgml/reference.sgml                   |   4 +
 19 files changed, 992 insertions(+), 10 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 doc/src/sgml/ref/let.sgml

diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..c65b2bb336 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -700,6 +700,68 @@ SELECT name, elevation
   </sect1>

+  <sect1 id="tutorial-session-variables">
+   <title>Session Variables</title>
+
+   <indexterm zone="tutorial-session-variables">
+    <primary>Session variables</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>session variable</primary>
+    <secondary>introduction</secondary>
+   </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>
+    The value of a session variable is set with the <command>LET</command> SQL
+    command.  While session variables share properties with tables, their value
+    cannot be updated with an <command>UPDATE</command> command. The value of a
+    session variable may be retrieved by the <command>SELECT</command> SQL
+    command.
+<programlisting>
+CREATE VARIABLE var1 AS date;
+LET var1 = current_date;
+SELECT var1;
+</programlisting>
+
+    or
+
+<programlisting>
+CREATE VARIABLE public.current_user_id AS integer;
+GRANT READ ON VARIABLE public.current_user_id TO PUBLIC;
+LET current_user_id = (SELECT id FROM users WHERE usename = session_user);
+SELECT current_user_id;
+</programlisting>
+   </para>
+
+   <para>
+    The value of a session variable is local to the current session. Retrieving
+    a variable's value returns either a <literal>NULL</literal> or a default
+    value, unless its value has been set to something else in the current
+    session using the <command>LET</command> command. The content of a variable
+    is not transactional.  This is the same as regular variables in PL
+    languages.
+   </para>
+
+   <para>
+    The session variables can be shadowed by column references in a query. When
+    a query contains identifiers or qualified identifiers that could be used as
+    both a session variable identifiers and as column identifier, then the
+    column identifier is preferred every time. Warnings can be emitted when
+    this situation happens by enabling configuration parameter <xref
+    linkend="guc-session-variables-ambiguity-warning"/>.
+   </para>
+  </sect1>
+
+
   <sect1 id="tutorial-conclusion">
    <title>Conclusion</title>
 
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c00c93dd7b..8da972809b 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>
@@ -14285,4 +14290,157 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
 
  </sect1>
 
+ <sect1 id="catalog-pg-variable">
+  <title><structname>pg_variable</structname></title>
+
+  <indexterm zone="catalog-pg-variable">
+   <primary>pg_variable</primary>
+  </indexterm>
+
+  <para>
+   The table <structname>pg_variable</structname> provides information about
+   session variables.
+  </para>
+
+  <table>
+   <title><structname>pg_variable</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>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>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>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>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>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>varisnotnull</structfield> <type>boolean</type>
+      </para>
+      <para>
+       True if the session variable doesn't allow null value. The default value is false.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>varisimmutable</structfield> <type>boolean</type>
+      </para>
+      <para>
+       True if the variable is immutable (cannot be modified). The default value is false.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vareoxaction</structfield> <type>char</type>
+      </para>
+      <para>
+       Action performed at end of transaction:
+       <literal>n</literal> = no action, <literal>d</literal> = drop the variable,
+       <literal>r</literal> = reset the variable to its default value.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vardefexpr</structfield> <type>pg_node_tree</type>
+      </para>
+      <para>
+       The internal representation of the variable default value
+      </para></entry>
+     </row>
+
+     <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/config.sgml b/doc/src/sgml/config.sgml
index f273fc95b9..83a1faf38f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10218,6 +10218,21 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-session-variables-ambiguity-warning" xreflabel="session_variables_ambiguity_warning">
+      <term><varname>session_variables_ambiguity_warning</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>session_variables_ambiguity_warning</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        When on, a warning is raised when any identifier in a query could be
+        used as both a column identifier, routine variable or a session
+        variable identifier.  The default is <literal>off</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-standard-conforming-strings" xreflabel="standard_conforming_strings">
       <term><varname>standard_conforming_strings</varname> (<type>boolean</type>)
       <indexterm><primary>strings</primary><secondary>standard conforming</secondary></indexterm>
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 9c66f97b0f..36af21f4d6 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -405,6 +405,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>
@@ -693,6 +701,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>
@@ -981,6 +997,14 @@
         <entry align="center"><literal>-</literal></entry>
         <entry align="left"></entry>
        </row>
+       <row>
+        <entry align="left"><literal>DROP VARIABLE</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+        <entry align="left"></entry>
+       </row>
        <row>
         <entry align="left"><literal>DROP VIEW</literal></entry>
         <entry align="center"><literal>X</literal></entry>
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index d6d0a3a814..a46baa0c17 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -1477,6 +1477,22 @@
    </glossdef>
   </glossentry>
 
+  <glossentry id="glossary-session-variable">
+   <glossterm>Session variable</glossterm>
+   <glossdef>
+    <para>
+     A persistent database object that holds a value in session memory.  This
+     memory is not shared across sessions, and after session end, this memory
+     (the value) is released. The access (read or write) to session variables
+     is controlled by access rights similarly to other database object access
+     rights.
+    </para>
+    <para>
+     For more information, see <xref linkend="tutorial-session-variables"/>.
+    </para>
+   </glossdef>
+  </glossentry>
+
   <glossentry id="glossary-shared-memory">
    <glossterm>Shared memory</glossterm>
    <glossdef>
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index d9a74896d5..0b8aff9ce3 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5953,6 +5953,18 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE;
 </programlisting>
     </para>
    </sect3>
+
+   <sect3>
+    <title><command>Session variables and constants</command></title>
+
+    <para>
+     The <application>PL/pgSQL</application> language has no packages, and
+     therefore no package variables or package constants.
+     <productname>PostgreSQL</productname> has session variables and immutable
+     session variables. Session variables can be created by <command>CREATE
+     VARIABLE</command>, as described in <xref linkend="sql-createvariable"/>.
+    </para>
+   </sect3>
   </sect2>
 
   <sect2 id="plpgsql-porting-appendix">
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index e90a0e1f83..24e30fdd97 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -47,6 +47,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY alterType          SYSTEM "alter_type.sgml">
 <!ENTITY alterUser          SYSTEM "alter_user.sgml">
 <!ENTITY alterUserMapping   SYSTEM "alter_user_mapping.sgml">
+<!ENTITY alterVariable      SYSTEM "alter_variable.sgml">
 <!ENTITY alterView          SYSTEM "alter_view.sgml">
 <!ENTITY analyze            SYSTEM "analyze.sgml">
 <!ENTITY begin              SYSTEM "begin.sgml">
@@ -99,6 +100,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createType         SYSTEM "create_type.sgml">
 <!ENTITY createUser         SYSTEM "create_user.sgml">
 <!ENTITY createUserMapping  SYSTEM "create_user_mapping.sgml">
+<!ENTITY createVariable     SYSTEM "create_variable.sgml">
 <!ENTITY createView         SYSTEM "create_view.sgml">
 <!ENTITY deallocate         SYSTEM "deallocate.sgml">
 <!ENTITY declare            SYSTEM "declare.sgml">
@@ -148,6 +150,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropUser           SYSTEM "drop_user.sgml">
 <!ENTITY dropUserMapping    SYSTEM "drop_user_mapping.sgml">
 <!ENTITY dropView           SYSTEM "drop_view.sgml">
+<!ENTITY dropVariable       SYSTEM "drop_variable.sgml">
 <!ENTITY end                SYSTEM "end.sgml">
 <!ENTITY execute            SYSTEM "execute.sgml">
 <!ENTITY explain            SYSTEM "explain.sgml">
@@ -155,6 +158,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY grant              SYSTEM "grant.sgml">
 <!ENTITY importForeignSchema SYSTEM "import_foreign_schema.sgml">
 <!ENTITY insert             SYSTEM "insert.sgml">
+<!ENTITY let                SYSTEM "let.sgml">
 <!ENTITY listen             SYSTEM "listen.sgml">
 <!ENTITY load               SYSTEM "load.sgml">
 <!ENTITY lock               SYSTEM "lock.sgml">
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index f1d54f5aa3..ec85fa39cf 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -50,6 +50,10 @@ GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
     ON SCHEMAS
     TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
 
+GRANT { READ | WRITE | ALL [ PRIVILEGES ] }
+    ON VARIABLES
+    TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
+
 REVOKE [ GRANT OPTION FOR ]
     { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
     [, ...] | ALL [ PRIVILEGES ] }
@@ -81,6 +85,12 @@ REVOKE [ GRANT OPTION FOR ]
     ON SCHEMAS
     FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
     [ CASCADE | RESTRICT ]
+
+REVOKE [ GRANT OPTION FOR ]
+    { { READ | WRITE } [, ...] | ALL [ PRIVILEGES ] }
+    ON VARIABLES
+    FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
+    [ CASCADE | RESTRICT ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -92,14 +102,14 @@ REVOKE [ GRANT OPTION FOR ]
    that will be applied to objects created in the future.  (It does not
    affect privileges assigned to already-existing objects.)  Currently,
    only the privileges for schemas, tables (including views and foreign
-   tables), sequences, functions, and types (including domains) can be
-   altered.  For this command, functions include aggregates and procedures.
-   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..06e7f8cd96
--- /dev/null
+++ b/doc/src/sgml/ref/alter_variable.sgml
@@ -0,0 +1,179 @@
+<!--
+doc/src/sgml/ref/alter_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-altervariable">
+ <indexterm zone="sql-altervariable">
+  <primary>ALTER VARIABLE</primary>
+ </indexterm>
+
+ <indexterm>
+  <primary>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_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>
+   <member><xref linkend="sql-let"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index 23d9029af9..50bfec6f28 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">text</replaceable>'
 
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
new file mode 100644
index 0000000000..2c7be3941c
--- /dev/null
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -0,0 +1,219 @@
+<!--
+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 [ { TEMPORARY | TEMP } ] [ IMMUTABLE ] VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
+    [ NOT NULL ] [ DEFAULT <replaceable class="parameter">default_expr</replaceable> ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
+</synopsis>
+ </refsynopsisdiv>
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   The <command>CREATE VARIABLE</command> command creates a session variable.
+   Session variables, like relations, exist within a schema and their access is
+   controlled via <command>GRANT</command> and <command>REVOKE</command>
+   commands.
+  </para>
+
+  <para>
+   The value of a session variable is local to the current session. Retrieving
+   a session variable's value returns either a NULL or a default value, unless
+   its value is set to something else in the current session with a LET
+   command. The content of a session variable is not transactional. This is the
+   same as regular variables in PL languages.
+  </para>
+
+  <para>
+   Session variables are retrieved by the <command>SELECT</command> SQL
+   command.  Their value is set with the <command>LET</command> SQL command.
+   While session variables share properties with tables, their value cannot be
+   changed with an <command>UPDATE</command> command.
+  </para>
+
+  <note>
+   <para>
+    Inside a query or an expression, the session variable can be shadowed by
+    column or by routine's variable or routine argument. The collision of
+    identifiers can be solved by using qualified identifiers. Session variables
+    can use schema name, columns can use table aliases, routine's variables
+    can use block's labels, and routine's arguments can use routine name.
+   </para>
+  </note>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IMMUTABLE</literal></term>
+    <listitem>
+     <para>
+      The assigned value of the session variable can never be changed.
+      If the session variable doesn't have a default value, then a single
+      initialization is allowed using the <command>LET</command> command. Once
+      done, no other change will be allowed until end of transcation
+      (when session variable was created with clause <literal>ON TRANSACTION
+      END RESET</literal> or until reset of all session variables by
+      <command>DISCARD VARIABLES</command> or until reset of all session
+      objects by command <command>DISCARD ALL</command>.
+     </para>
+
+     <para>
+      When <literal>IMMUTABLE</literal> session variable has assigned
+      default value by using <literal>DEFAULT expr</literal> clause, then
+      the value of session variable cannot be changed ever.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>IF NOT EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the name already exists. A notice is issued in
+      this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name, optionally schema-qualified, of the session variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">data_type</replaceable></term>
+    <listitem>
+     <para>
+      The name, optionally schema-qualified, of the data type of the session
+      variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>COLLATE <replaceable>collation</replaceable></literal></term>
+    <listitem>
+     <para>
+      The <literal>COLLATE</literal> clause assigns a collation to the session
+      variable (which must be of a collatable data type).  If not specified,
+      the data type's default collation is used.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>NOT NULL</literal></term>
+    <listitem>
+     <para>
+      The <literal>NOT NULL</literal> clause forbids setting the session
+      variable to a null value. A session variable created as NOT NULL and
+      without an explicitly declared default value cannot be read until it is
+      initialized by a LET command. This requires the user to explicitly
+      initialize the session variable content before reading it, otherwise an
+      error will be thrown.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DEFAULT <replaceable>default_expr</replaceable></literal></term>
+    <listitem>
+     <para>
+      The <literal>DEFAULT</literal> clause can be used to assign a default
+      value to a session variable. This expression is evaluated when the session
+      variable is accessed for reading first time, and had not assigned any value.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>ON COMMIT DROP</literal>, <literal>ON TRANSACTION END RESET</literal></term>
+    <listitem>
+     <para>
+      The <literal>ON COMMIT DROP</literal> clause specifies the behaviour of a
+      temporary session variable at transaction commit. With this clause, the
+      session variable is dropped at commit time. The clause is only allowed
+      for temporary variables. The <literal>ON TRANSACTION END RESET</literal>
+      clause causes the session variable to be reset to its default value when
+      the transaction is committed or rolled back.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+ </refsect1>
+
+ <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>
+   <member><xref linkend="sql-let"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml
index bf44c523ca..6f90672afa 100644
--- a/doc/src/sgml/ref/discard.sgml
+++ b/doc/src/sgml/ref/discard.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }
+DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP | VARIABLES }
 </synopsis>
  </refsynopsisdiv>
 
@@ -75,6 +75,17 @@ DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>VARIABLES</literal></term>
+    <listitem>
+     <para>
+      Resets the value of all session variables. If a variable
+      is later reused, it is re-initialized to either
+      <literal>NULL</literal> or its default value.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ALL</literal></term>
     <listitem>
@@ -93,6 +104,7 @@ SELECT pg_advisory_unlock_all();
 DISCARD PLANS;
 DISCARD TEMP;
 DISCARD SEQUENCES;
+DISCARD VARIABLES;
 </programlisting></para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml
new file mode 100644
index 0000000000..67988b5fcd
--- /dev/null
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -0,0 +1,118 @@
+<!--
+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>
+   <member><xref linkend="sql-let"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index f744b05b55..d3338b2dfd 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -108,6 +108,11 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
   | CURRENT_ROLE
   | CURRENT_USER
   | SESSION_USER
+
+GRANT { READ | WRITE | ALL [ PRIVILEGES ] }
+    ON VARIABLE <replaceable>variable_name</replaceable> [, ...]
+    TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
+
 </synopsis>
  </refsynopsisdiv>
 
@@ -209,6 +214,24 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>READ</literal></term>
+     <listitem>
+      <para>
+       Allows reading of a session variable.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>WRITE</literal></term>
+     <listitem>
+      <para>
+       Allows setting the value of a session variable.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><literal>ALL PRIVILEGES</literal></term>
      <listitem>
diff --git a/doc/src/sgml/ref/let.sgml b/doc/src/sgml/ref/let.sgml
new file mode 100644
index 0000000000..96e3d8b579
--- /dev/null
+++ b/doc/src/sgml/ref/let.sgml
@@ -0,0 +1,108 @@
+<!--
+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>
+LET <replaceable class="parameter">session_variable</replaceable> = DEFAULT
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   The <command>LET</command> command assigns a value to the specified session
+   variable.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>session_variable</literal></term>
+    <listitem>
+     <para>
+      The name of the session variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>sql_expression</literal></term>
+    <listitem>
+     <para>
+      An SQL expression. The result is cast to the data type of the session
+      variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DEFAULT</literal></term>
+    <listitem>
+     <para>
+      Reset the session variable to its default value, if that is defined.
+      If no explicit default value has been assigned, the session variable
+      is set to NULL.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+
+  <para>
+   Example:
+<programlisting>
+CREATE VARIABLE myvar AS integer;
+LET myvar = 10;
+LET myvar = (SELECT sum(val) FROM tab);
+LET myvar = DEFAULT;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   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/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index 526986eadb..85f29431c1 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -106,6 +106,17 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-A <replaceable class="parameter">schema_variable</replaceable></option></term>
+      <term><option>--variable=<replaceable class="parameter">schema_variable</replaceable></option></term>
+      <listitem>
+       <para>
+        Restore a named session variable only.  Multiple session variables may
+        be specified with multiple <option>-A</option> switches.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-c</option></term>
       <term><option>--clean</option></term>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 62f1971036..f84f43de4f 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -137,6 +137,12 @@ REVOKE [ ADMIN OPTION FOR ]
   | CURRENT_ROLE
   | CURRENT_USER
   | SESSION_USER
+
+REVOKE [ GRANT OPTION FOR ]
+    { { READ | WRITE } [, ...] | ALL [ PRIVILEGES ] }
+    ON VARIABLE <replaceable>variable_name</replaceable> [, ...]
+    FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
+    [ CASCADE | RESTRICT ]
 </synopsis>
  </refsynopsisdiv>
 
diff --git a/doc/src/sgml/ref/set_role.sgml b/doc/src/sgml/ref/set_role.sgml
index 4e02322158..876d5ac7a3 100644
--- a/doc/src/sgml/ref/set_role.sgml
+++ b/doc/src/sgml/ref/set_role.sgml
@@ -98,7 +98,7 @@ RESET ROLE
   </para>
 
   <para>
-   <command>SET ROLE</command> does not process session variables as specified by
+   <command>SET ROLE</command> does not set run-time configuration parameters specified by
    the role's <link linkend="sql-alterrole"><command>ALTER ROLE</command></link> settings;  this only happens during
    login.
   </para>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index a3b743e8c1..f5ec3c987b 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -75,6 +75,7 @@
    &alterType;
    &alterUser;
    &alterUserMapping;
+   &alterVariable;
    &alterView;
    &analyze;
    &begin;
@@ -127,6 +128,7 @@
    &createType;
    &createUser;
    &createUserMapping;
+   &createVariable;
    &createView;
    &deallocate;
    &declare;
@@ -175,6 +177,7 @@
    &dropType;
    &dropUser;
    &dropUserMapping;
+   &dropVariable;
    &dropView;
    &end;
    &execute;
@@ -183,6 +186,7 @@
    &grant;
    &importForeignSchema;
    &insert;
+   &let;
    &listen;
    &load;
    &lock;
-- 
2.36.1

From 4f71b6d3efc5b411b6fac5953ce08bf41a01ad3e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 11:40:29 +0200
Subject: [PATCH 10/11] This patch changes error message "column doesn't exist"
 to message "column or variable doesn't exist"

The error message will be more correct. Today, missing PL/pgSQL variable can be reported. The change has
impact on lot of regress tests not related to session variables, and then it is distributed as separate patch
---
 src/backend/parser/parse_relation.c           |  8 ++--
 src/backend/parser/parse_target.c             |  4 +-
 src/pl/plpgsql/src/expected/plpgsql_array.out |  2 +-
 .../plpgsql/src/expected/plpgsql_record.out   |  4 +-
 src/pl/tcl/expected/pltcl_queries.out         | 12 +++---
 src/test/regress/expected/alter_table.out     | 42 +++++++++----------
 src/test/regress/expected/copy2.out           |  2 +-
 src/test/regress/expected/errors.out          |  8 ++--
 src/test/regress/expected/generated.out       |  4 +-
 src/test/regress/expected/insert_conflict.out |  6 +--
 src/test/regress/expected/join.out            | 20 ++++-----
 src/test/regress/expected/plpgsql.out         | 12 +++---
 src/test/regress/expected/rowtypes.out        |  2 +-
 src/test/regress/expected/rules.out           |  4 +-
 .../regress/expected/session_variables.out    |  2 +-
 src/test/regress/expected/transactions.out    |  4 +-
 src/test/regress/expected/union.out           |  2 +-
 17 files changed, 69 insertions(+), 69 deletions(-)

diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 926dcbf30e..47b5b70bd9 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -3620,8 +3620,8 @@ errorMissingColumn(ParseState *pstate,
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 relname ?
-				 errmsg("column %s.%s does not exist", relname, colname) :
-				 errmsg("column \"%s\" does not exist", colname),
+				 errmsg("column or variable %s.%s does not exist", relname, colname) :
+				 errmsg("column or variable \"%s\" does not exist", colname),
 				 state->rfirst ? closestfirst ?
 				 errhint("Perhaps you meant to reference the column \"%s.%s\".",
 						 state->rfirst->eref->aliasname, closestfirst) :
@@ -3640,8 +3640,8 @@ errorMissingColumn(ParseState *pstate,
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 relname ?
-				 errmsg("column %s.%s does not exist", relname, colname) :
-				 errmsg("column \"%s\" does not exist", colname),
+				 errmsg("column or variable %s.%s does not exist", relname, colname) :
+				 errmsg("column or variable  \"%s\" does not exist", colname),
 				 errhint("Perhaps you meant to reference the column \"%s.%s\" or the column \"%s.%s\".",
 						 state->rfirst->eref->aliasname, closestfirst,
 						 state->rsecond->eref->aliasname, closestsecond),
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 9add612d7e..3f8ba12185 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -786,7 +786,7 @@ transformAssignmentIndirection(ParseState *pstate,
 			if (!typrelid)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("cannot assign to field \"%s\" of column \"%s\" because its type %s is not a composite type",
+						 errmsg("cannot assign to field \"%s\" of column or variable \"%s\" because its type %s is not a composite type",
 								strVal(n), targetName,
 								format_type_be(targetTypeId)),
 						 parser_errposition(pstate, location)));
@@ -795,7 +795,7 @@ transformAssignmentIndirection(ParseState *pstate,
 			if (attnum == InvalidAttrNumber)
 				ereport(ERROR,
 						(errcode(ERRCODE_UNDEFINED_COLUMN),
-						 errmsg("cannot assign to field \"%s\" of column \"%s\" because there is no such column in data type %s",
+						 errmsg("cannot assign to field \"%s\" of column or variable \"%s\" because there is no such column in data type %s",
 								strVal(n), targetName,
 								format_type_be(targetTypeId)),
 						 parser_errposition(pstate, location)));
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index 9e22e56f00..e131febf3d 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -41,7 +41,7 @@ NOTICE:  a = {"(,11)"}, a[1].i = 11
 -- perhaps this ought to work, but for now it doesn't:
 do $$ declare a complex[];
 begin a[1:2].i := array[11,12]; raise notice 'a = %', a; end$$;
-ERROR:  cannot assign to field "i" of column "a" because its type complex[] is not a composite type
+ERROR:  cannot assign to field "i" of column or variable "a" because its type complex[] is not a composite type
 LINE 1: a[1:2].i := array[11,12]
         ^
 QUERY:  a[1:2].i := array[11,12]
diff --git a/src/pl/plpgsql/src/expected/plpgsql_record.out b/src/pl/plpgsql/src/expected/plpgsql_record.out
index 4c5d95c79e..fc95c5fd35 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_record.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_record.out
@@ -134,7 +134,7 @@ ERROR:  record "c" has no field "x"
 CONTEXT:  PL/pgSQL assignment "c.x.q1 = 1"
 PL/pgSQL function inline_code_block line 1 at assignment
 do $$ declare c nested_int8s; begin c.c2.x = 1; end $$;
-ERROR:  cannot assign to field "x" of column "c" because there is no such column in data type two_int8s
+ERROR:  cannot assign to field "x" of column or variable "c" because there is no such column in data type two_int8s
 LINE 1: c.c2.x = 1
         ^
 QUERY:  c.c2.x = 1
@@ -156,7 +156,7 @@ ERROR:  record "c" has no field "x"
 CONTEXT:  PL/pgSQL assignment "b.c.x.q1 = 1"
 PL/pgSQL function inline_code_block line 1 at assignment
 do $$ <<b>> declare c nested_int8s; begin b.c.c2.x = 1; end $$;
-ERROR:  cannot assign to field "x" of column "b" because there is no such column in data type two_int8s
+ERROR:  cannot assign to field "x" of column or variable "b" because there is no such column in data type two_int8s
 LINE 1: b.c.c2.x = 1
         ^
 QUERY:  b.c.c2.x = 1
diff --git a/src/pl/tcl/expected/pltcl_queries.out b/src/pl/tcl/expected/pltcl_queries.out
index 2d922c2333..3b6506d613 100644
--- a/src/pl/tcl/expected/pltcl_queries.out
+++ b/src/pl/tcl/expected/pltcl_queries.out
@@ -246,12 +246,12 @@ ERROR:  type "b" does not exist
 select tcl_eval('spi_prepare a "b {"');
 ERROR:  unmatched open brace in list
 select tcl_error_handling_test($tcl$spi_prepare "select moo" []$tcl$);
-       tcl_error_handling_test        
---------------------------------------
- SQLSTATE: 42703                     +
- condition: undefined_column         +
- cursor_position: 8                  +
- message: column "moo" does not exist+
+             tcl_error_handling_test              
+--------------------------------------------------
+ SQLSTATE: 42703                                 +
+ condition: undefined_column                     +
+ cursor_position: 8                              +
+ message: column or variable "moo" does not exist+
  statement: select moo
 (1 row)
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 5ede56d9b5..3ae23c2a31 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -809,7 +809,7 @@ drop table atacc1;
 create table atacc1 ( test int );
 -- add a check constraint (fails)
 alter table atacc1 add constraint atacc_test1 check (test1>3);
-ERROR:  column "test1" does not exist
+ERROR:  column or variable "test1" does not exist
 HINT:  Perhaps you meant to reference the column "atacc1.test".
 drop table atacc1;
 -- something a little more complicated
@@ -1287,19 +1287,19 @@ select * from atacc1;
 (1 row)
 
 select * from atacc1 order by a;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: select * from atacc1 order by a;
                                       ^
 select * from atacc1 order by "........pg.dropped.1........";
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: select * from atacc1 order by "........pg.dropped.1........"...
                                       ^
 select * from atacc1 group by a;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: select * from atacc1 group by a;
                                       ^
 select * from atacc1 group by "........pg.dropped.1........";
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: select * from atacc1 group by "........pg.dropped.1........"...
                                       ^
 select atacc1.* from atacc1;
@@ -1309,11 +1309,11 @@ select atacc1.* from atacc1;
 (1 row)
 
 select a from atacc1;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: select a from atacc1;
                ^
 select atacc1.a from atacc1;
-ERROR:  column atacc1.a does not exist
+ERROR:  column or variable atacc1.a does not exist
 LINE 1: select atacc1.a from atacc1;
                ^
 select b,c,d from atacc1;
@@ -1323,27 +1323,27 @@ select b,c,d from atacc1;
 (1 row)
 
 select a,b,c,d from atacc1;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: select a,b,c,d from atacc1;
                ^
 select * from atacc1 where a = 1;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: select * from atacc1 where a = 1;
                                    ^
 select "........pg.dropped.1........" from atacc1;
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: select "........pg.dropped.1........" from atacc1;
                ^
 select atacc1."........pg.dropped.1........" from atacc1;
-ERROR:  column atacc1.........pg.dropped.1........ does not exist
+ERROR:  column or variable atacc1.........pg.dropped.1........ does not exist
 LINE 1: select atacc1."........pg.dropped.1........" from atacc1;
                ^
 select "........pg.dropped.1........",b,c,d from atacc1;
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: select "........pg.dropped.1........",b,c,d from atacc1;
                ^
 select * from atacc1 where "........pg.dropped.1........" = 1;
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: select * from atacc1 where "........pg.dropped.1........" = ...
                                    ^
 -- UPDATEs
@@ -1352,7 +1352,7 @@ ERROR:  column "a" of relation "atacc1" does not exist
 LINE 1: update atacc1 set a = 3;
                           ^
 update atacc1 set b = 2 where a = 3;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: update atacc1 set b = 2 where a = 3;
                                       ^
 update atacc1 set "........pg.dropped.1........" = 3;
@@ -1360,7 +1360,7 @@ ERROR:  column "........pg.dropped.1........" of relation "atacc1" does not exis
 LINE 1: update atacc1 set "........pg.dropped.1........" = 3;
                           ^
 update atacc1 set b = 2 where "........pg.dropped.1........" = 3;
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: update atacc1 set b = 2 where "........pg.dropped.1........"...
                                       ^
 -- INSERTs
@@ -1408,11 +1408,11 @@ LINE 1: insert into atacc1 ("........pg.dropped.1........",b,c,d) va...
                             ^
 -- DELETEs
 delete from atacc1 where a = 3;
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 LINE 1: delete from atacc1 where a = 3;
                                  ^
 delete from atacc1 where "........pg.dropped.1........" = 3;
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 LINE 1: delete from atacc1 where "........pg.dropped.1........" = 3;
                                  ^
 delete from atacc1;
@@ -1490,9 +1490,9 @@ ERROR:  column "a" named in key does not exist
 alter table atacc1 add unique("........pg.dropped.1........");
 ERROR:  column "........pg.dropped.1........" named in key does not exist
 alter table atacc1 add check (a > 3);
-ERROR:  column "a" does not exist
+ERROR:  column or variable "a" does not exist
 alter table atacc1 add check ("........pg.dropped.1........" > 3);
-ERROR:  column "........pg.dropped.1........" does not exist
+ERROR:  column or variable "........pg.dropped.1........" does not exist
 create table atacc2 (id int4 unique);
 alter table atacc1 add foreign key (a) references atacc2(id);
 ERROR:  column "a" referenced in foreign key constraint does not exist
@@ -1698,7 +1698,7 @@ select f1 from c1;
 
 alter table c1 drop column f1;
 select f1 from c1;
-ERROR:  column "f1" does not exist
+ERROR:  column or variable "f1" does not exist
 LINE 1: select f1 from c1;
                ^
 HINT:  Perhaps you meant to reference the column "c1.f2".
@@ -1712,7 +1712,7 @@ ERROR:  cannot drop inherited column "f1"
 alter table p1 drop column f1;
 -- c1.f1 is dropped now, since there is no local definition for it
 select f1 from c1;
-ERROR:  column "f1" does not exist
+ERROR:  column or variable "f1" does not exist
 LINE 1: select f1 from c1;
                ^
 HINT:  Perhaps you meant to reference the column "c1.f2".
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index 5f3685e9ef..edff69cd22 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -105,7 +105,7 @@ LINE 1: COPY x TO stdout WHERE a = 1;
 COPY x from stdin WHERE a = 50004;
 COPY x from stdin WHERE a > 60003;
 COPY x from stdin WHERE f > 60003;
-ERROR:  column "f" does not exist
+ERROR:  column or variable "f" does not exist
 LINE 1: COPY x from stdin WHERE f > 60003;
                                 ^
 COPY x from stdin WHERE a = max(x.b);
diff --git a/src/test/regress/expected/errors.out b/src/test/regress/expected/errors.out
index 8c527474da..e53ae451df 100644
--- a/src/test/regress/expected/errors.out
+++ b/src/test/regress/expected/errors.out
@@ -27,7 +27,7 @@ LINE 1: select * from nonesuch;
                       ^
 -- bad name in target list
 select nonesuch from pg_database;
-ERROR:  column "nonesuch" does not exist
+ERROR:  column or variable "nonesuch" does not exist
 LINE 1: select nonesuch from pg_database;
                ^
 -- empty distinct list isn't OK
@@ -37,17 +37,17 @@ LINE 1: select distinct from pg_database;
                         ^
 -- bad attribute name on lhs of operator
 select * from pg_database where nonesuch = pg_database.datname;
-ERROR:  column "nonesuch" does not exist
+ERROR:  column or variable "nonesuch" does not exist
 LINE 1: select * from pg_database where nonesuch = pg_database.datna...
                                         ^
 -- bad attribute name on rhs of operator
 select * from pg_database where pg_database.datname = nonesuch;
-ERROR:  column "nonesuch" does not exist
+ERROR:  column or variable "nonesuch" does not exist
 LINE 1: ...ect * from pg_database where pg_database.datname = nonesuch;
                                                               ^
 -- bad attribute name in select distinct on
 select distinct on (foobar) * from pg_database;
-ERROR:  column "foobar" does not exist
+ERROR:  column or variable "foobar" does not exist
 LINE 1: select distinct on (foobar) * from pg_database;
                             ^
 -- grouping with FOR UPDATE
diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out
index bb4190340e..461da763f1 100644
--- a/src/test/regress/expected/generated.out
+++ b/src/test/regress/expected/generated.out
@@ -55,7 +55,7 @@ LINE 2:     b int GENERATED ALWAYS AS (num_nulls(gtest_err_2c)) STOR...
 DETAIL:  This would cause the generated column to depend on its own value.
 -- invalid reference
 CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STORED);
-ERROR:  column "c" does not exist
+ERROR:  column or variable "c" does not exist
 LINE 1: ..._3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STO...
                                                              ^
 -- generation expression must be immutable
@@ -727,7 +727,7 @@ ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4) STORED;  -- err
 ERROR:  cannot use generated column "b" in column generation expression
 DETAIL:  A generated column cannot reference another generated column.
 ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED;  -- error
-ERROR:  column "z" does not exist
+ERROR:  column or variable "z" does not exist
 ALTER TABLE gtest25 ADD COLUMN c int DEFAULT 42,
   ADD COLUMN x int GENERATED ALWAYS AS (c * 4) STORED;
 ALTER TABLE gtest25 ADD COLUMN d int DEFAULT 101;
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 66d8633e3e..f11bed03d6 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -245,13 +245,13 @@ LINE 1: ...y) do update set fruit = excluded.fruit RETURNING excluded.f...
 HINT:  There is an entry for table "excluded", but it cannot be referenced from this part of the query.
 -- Only suggest <table>.* column when inference element misspelled:
 insert into insertconflicttest values (1, 'Apple') on conflict (keyy) do update set fruit = excluded.fruit;
-ERROR:  column "keyy" does not exist
+ERROR:  column or variable  "keyy" does not exist
 LINE 1: ...nsertconflicttest values (1, 'Apple') on conflict (keyy) do ...
                                                              ^
 HINT:  Perhaps you meant to reference the column "insertconflicttest.key" or the column "excluded.key".
 -- Have useful HINT for EXCLUDED.* RTE within UPDATE:
 insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruitt;
-ERROR:  column excluded.fruitt does not exist
+ERROR:  column or variable excluded.fruitt does not exist
 LINE 1: ... 'Apple') on conflict (key) do update set fruit = excluded.f...
                                                              ^
 HINT:  Perhaps you meant to reference the column "excluded.fruit".
@@ -448,7 +448,7 @@ drop table insertconflicttest;
 create table syscolconflicttest(key int4, data text);
 insert into syscolconflicttest values (1);
 insert into syscolconflicttest values (1) on conflict (key) do update set data = excluded.ctid::text;
-ERROR:  column excluded.ctid does not exist
+ERROR:  column or variable excluded.ctid does not exist
 LINE 1: ...values (1) on conflict (key) do update set data = excluded.c...
                                                              ^
 drop table syscolconflicttest;
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 2538bd6a79..e7bb08c9ea 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -1646,7 +1646,7 @@ SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.i = 1;  -- ok
 (1 row)
 
 SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.t = 'one';  -- error
-ERROR:  column x.t does not exist
+ERROR:  column or variable x.t does not exist
 LINE 1: ...CT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.t = 'one...
                                                              ^
 SELECT * FROM (J1_TBL JOIN J2_TBL USING (i) AS x) AS xx WHERE x.i = 1;  -- error (XXX could use better hint)
@@ -2497,7 +2497,7 @@ select * from t1 left join t2 on (t1.a = t2.a);
 
 -- Test matching of column name with wrong alias
 select t1.x from t1 join t3 on (t1.a = t3.x);
-ERROR:  column t1.x does not exist
+ERROR:  column or variable t1.x does not exist
 LINE 1: select t1.x from t1 join t3 on (t1.a = t3.x);
                ^
 HINT:  Perhaps you meant to reference the column "t3.x".
@@ -4923,19 +4923,19 @@ select * from
 --
 select t1.uunique1 from
   tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, prefer "t1" suggestion
-ERROR:  column t1.uunique1 does not exist
+ERROR:  column or variable t1.uunique1 does not exist
 LINE 1: select t1.uunique1 from
                ^
 HINT:  Perhaps you meant to reference the column "t1.unique1".
 select t2.uunique1 from
   tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, prefer "t2" suggestion
-ERROR:  column t2.uunique1 does not exist
+ERROR:  column or variable t2.uunique1 does not exist
 LINE 1: select t2.uunique1 from
                ^
 HINT:  Perhaps you meant to reference the column "t2.unique1".
 select uunique1 from
   tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, suggest both at once
-ERROR:  column "uunique1" does not exist
+ERROR:  column or variable  "uunique1" does not exist
 LINE 1: select uunique1 from
                ^
 HINT:  Perhaps you meant to reference the column "t1.unique1" or the column "t2.unique1".
@@ -4947,7 +4947,7 @@ select atts.relid::regclass, s.* from pg_stats s join
     a.attrelid::regclass::text join (select unnest(indkey) attnum,
     indexrelid from pg_index i) atts on atts.attnum = a.attnum where
     schemaname != 'pg_catalog';
-ERROR:  column atts.relid does not exist
+ERROR:  column or variable atts.relid does not exist
 LINE 1: select atts.relid::regclass, s.* from pg_stats s join
                ^
 --
@@ -5969,7 +5969,7 @@ lateral (select * from int8_tbl t1,
 
 -- test some error cases where LATERAL should have been used but wasn't
 select f1,g from int4_tbl a, (select f1 as g) ss;
-ERROR:  column "f1" does not exist
+ERROR:  column or variable "f1" does not exist
 LINE 1: select f1,g from int4_tbl a, (select f1 as g) ss;
                                              ^
 HINT:  There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
@@ -5979,7 +5979,7 @@ LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
                                              ^
 HINT:  There is an entry for table "a", but it cannot be referenced from this part of the query.
 select f1,g from int4_tbl a cross join (select f1 as g) ss;
-ERROR:  column "f1" does not exist
+ERROR:  column or variable "f1" does not exist
 LINE 1: select f1,g from int4_tbl a cross join (select f1 as g) ss;
                                                        ^
 HINT:  There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
@@ -6014,7 +6014,7 @@ LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
 create temp table xx1 as select f1 as x1, -f1 as x2 from int4_tbl;
 -- error, can't do this:
 update xx1 set x2 = f1 from (select * from int4_tbl where f1 = x1) ss;
-ERROR:  column "x1" does not exist
+ERROR:  column or variable "x1" does not exist
 LINE 1: ... set x2 = f1 from (select * from int4_tbl where f1 = x1) ss;
                                                                 ^
 HINT:  There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query.
@@ -6034,7 +6034,7 @@ update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1)
 ERROR:  table name "xx1" specified more than once
 -- also errors:
 delete from xx1 using (select * from int4_tbl where f1 = x1) ss;
-ERROR:  column "x1" does not exist
+ERROR:  column or variable "x1" does not exist
 LINE 1: ...te from xx1 using (select * from int4_tbl where f1 = x1) ss;
                                                                 ^
 HINT:  There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 08e42f17dc..6f5f37d58f 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -2600,7 +2600,7 @@ end; $$ language plpgsql;
 -- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION
 -- blocks
 select excpt_test1();
-ERROR:  column "sqlstate" does not exist
+ERROR:  column or variable "sqlstate" does not exist
 LINE 1: sqlstate
         ^
 QUERY:  sqlstate
@@ -2615,7 +2615,7 @@ begin
 end; $$ language plpgsql;
 -- should fail
 select excpt_test2();
-ERROR:  column "sqlstate" does not exist
+ERROR:  column or variable "sqlstate" does not exist
 LINE 1: sqlstate
         ^
 QUERY:  sqlstate
@@ -4629,7 +4629,7 @@ BEGIN
         RAISE NOTICE '%, %', r.roomno, r.comment;
     END LOOP;
 END$$;
-ERROR:  column "foo" does not exist
+ERROR:  column or variable "foo" does not exist
 LINE 1: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomn...
                                         ^
 QUERY:  SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno
@@ -4671,7 +4671,7 @@ begin
   raise notice 'x = %', x;
 end;
 $$;
-ERROR:  column "x" does not exist
+ERROR:  column or variable "x" does not exist
 LINE 1: x + 1
         ^
 QUERY:  x + 1
@@ -4683,7 +4683,7 @@ begin
   raise notice 'x = %, y = %', x, y;
 end;
 $$;
-ERROR:  column "x" does not exist
+ERROR:  column or variable "x" does not exist
 LINE 1: x + 1
         ^
 QUERY:  x + 1
@@ -5696,7 +5696,7 @@ ALTER TABLE alter_table_under_transition_tables
   DROP column name;
 UPDATE alter_table_under_transition_tables
   SET id = id;
-ERROR:  column "name" does not exist
+ERROR:  column or variable "name" does not exist
 LINE 1: (SELECT string_agg(id || '=' || name, ',') FROM d)
                                         ^
 QUERY:  (SELECT string_agg(id || '=' || name, ',') FROM d)
diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out
index a4cc2d8c12..7cfd59fe97 100644
--- a/src/test/regress/expected/rowtypes.out
+++ b/src/test/regress/expected/rowtypes.out
@@ -927,7 +927,7 @@ LINE 1: select text(fullname) from fullname;
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 select fullname.text from fullname;  -- error
-ERROR:  column fullname.text does not exist
+ERROR:  column or variable fullname.text does not exist
 LINE 1: select fullname.text from fullname;
                ^
 -- same, but RECORD instead of named composite type:
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index fc3cde3226..7f657d384c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1191,7 +1191,7 @@ drop rule rules_foorule on rules_foo;
 -- this should fail because f1 is not exposed for unqualified reference:
 create rule rules_foorule as on insert to rules_foo where f1 < 100
 do instead insert into rules_foo2 values (f1);
-ERROR:  column "f1" does not exist
+ERROR:  column or variable "f1" does not exist
 LINE 2: do instead insert into rules_foo2 values (f1);
                                                   ^
 HINT:  There is a column named "f1" in table "old", but it cannot be referenced from this part of the query.
@@ -2714,7 +2714,7 @@ select * from rules_fooview;
 (1 row)
 
 select xmin, * from rules_fooview;  -- fail, views don't have such a column
-ERROR:  column "xmin" does not exist
+ERROR:  column or variable "xmin" does not exist
 LINE 1: select xmin, * from rules_fooview;
                ^
 select reltoastrelid, relkind, relfrozenxid
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 3ff938c9aa..84354bff33 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -246,7 +246,7 @@ SELECT v1;
 
 -- should fail
 LET v1.x = 10;
-ERROR:  cannot assign to field "x" of column "v1" because there is no such column in data type t1
+ERROR:  cannot assign to field "x" of column or variable "v1" because there is no such column in data type t1
 LINE 1: LET v1.x = 10;
             ^
 DROP VARIABLE v1;
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index a46fa5d48a..a8643f5033 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -222,7 +222,7 @@ SELECT * FROM trans_barbaz;	-- should have 1
 BEGIN;
 	SAVEPOINT one;
 		SELECT trans_foo;
-ERROR:  column "trans_foo" does not exist
+ERROR:  column or variable "trans_foo" does not exist
 LINE 1: SELECT trans_foo;
                ^
 	ROLLBACK TO SAVEPOINT one;
@@ -271,7 +271,7 @@ BEGIN;
 	SAVEPOINT one;
 		INSERT INTO savepoints VALUES (5);
 		SELECT trans_foo;
-ERROR:  column "trans_foo" does not exist
+ERROR:  column or variable "trans_foo" does not exist
 LINE 1: SELECT trans_foo;
                ^
 COMMIT;
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 7ac4a9380e..01658f5353 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -907,7 +907,7 @@ ORDER BY q2,q1;
 
 -- This should fail, because q2 isn't a name of an EXCEPT output column
 SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
-ERROR:  column "q2" does not exist
+ERROR:  column or variable "q2" does not exist
 LINE 1: ... int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1...
                                                              ^
 HINT:  There is a column named "q2" in table "*SELECT* 2", but it cannot be referenced from this part of the query.
-- 
2.36.1

From f1de21b3be1a70ed0562a29fa03cb275b590edec Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 09/11] Regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 src/test/regress/expected/misc_sanity.out     |   4 +-
 .../regress/expected/session_variables.out    | 972 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/session_variables.sql    | 739 +++++++++++++
 4 files changed, 1715 insertions(+), 2 deletions(-)
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out
index a57fd142a9..ce9bad7211 100644
--- a/src/test/regress/expected/misc_sanity.out
+++ b/src/test/regress/expected/misc_sanity.out
@@ -60,7 +60,9 @@ ORDER BY 1, 2;
  pg_index                | indpred       | pg_node_tree
  pg_largeobject          | data          | bytea
  pg_largeobject_metadata | lomacl        | aclitem[]
-(11 rows)
+ pg_variable             | varacl        | aclitem[]
+ pg_variable             | vardefexpr    | pg_node_tree
+(13 rows)
 
 -- system catalogs without primary keys
 --
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
new file mode 100644
index 0000000000..3ff938c9aa
--- /dev/null
+++ b/src/test/regress/expected/session_variables.out
@@ -0,0 +1,972 @@
+CREATE SCHEMA svartest;
+SET search_path = svartest;
+CREATE VARIABLE var1 AS integer;
+CREATE TEMP VARIABLE var2 AS text;
+DROP VARIABLE var1, var2;
+-- functional interface
+CREATE VARIABLE var1 AS numeric;
+CREATE ROLE var_test_role;
+GRANT USAGE ON SCHEMA svartest TO var_test_role;
+SET ROLE TO var_test_role;
+-- should fail
+SELECT var1;
+ERROR:  permission denied for session variable var1
+SET ROLE TO DEFAULT;
+GRANT READ ON VARIABLE var1 TO var_test_role;
+SET ROLE TO var_test_role;
+-- should fail
+LET var1 = 10;
+ERROR:  permission denied for session variable var1
+-- should work
+SELECT var1;
+ var1 
+------
+     
+(1 row)
+
+SET ROLE TO DEFAULT;
+GRANT WRITE ON VARIABLE var1 TO var_test_role;
+SET ROLE TO var_test_role;
+-- should work
+LET var1 = 333;
+SET ROLE TO DEFAULT;
+REVOKE ALL ON VARIABLE var1 FROM var_test_role;
+CREATE OR REPLACE FUNCTION secure_var()
+RETURNS int AS $$
+  SELECT svartest.var1::int;
+$$ LANGUAGE sql SECURITY DEFINER;
+SELECT secure_var();
+ secure_var 
+------------
+        333
+(1 row)
+
+SET ROLE TO var_test_role;
+-- should fail
+SELECT svartest.var1;
+ERROR:  permission denied for session variable var1
+-- should work;
+SELECT secure_var();
+ secure_var 
+------------
+        333
+(1 row)
+
+SET ROLE TO DEFAULT;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var1;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Function Scan on pg_catalog.generate_series g
+   Output: v
+   Function Call: generate_series(1, 100)
+   Filter: ((g.v)::numeric = var1)
+(4 rows)
+
+CREATE VIEW schema_var_view AS SELECT var1;
+SELECT * FROM schema_var_view;
+ var1 
+------
+  333
+(1 row)
+
+\c -
+SET search_path = svartest;
+-- should work still, but var will be empty
+SELECT * FROM schema_var_view;
+ var1 
+------
+     
+(1 row)
+
+LET var1 = pi();
+SELECT var1;
+       var1       
+------------------
+ 3.14159265358979
+(1 row)
+
+-- we can see execution plan of LET statement
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+         QUERY PLAN         
+----------------------------
+ SET SESSION VARIABLE
+ Result
+   Output: 3.14159265358979
+(3 rows)
+
+SELECT var1;
+       var1       
+------------------
+ 3.14159265358979
+(1 row)
+
+CREATE VARIABLE var3 AS int;
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+  LET svartest.var3 = COALESCE(svartest.var3 + $1, $1);
+  RETURN var3;
+END;
+$$ LANGUAGE plpgsql;
+SELECT inc(1);
+ inc 
+-----
+   1
+(1 row)
+
+SELECT inc(1);
+ inc 
+-----
+   2
+(1 row)
+
+SELECT inc(1);
+ inc 
+-----
+   3
+(1 row)
+
+SELECT inc(1) FROM generate_series(1,10);
+ inc 
+-----
+   4
+   5
+   6
+   7
+   8
+   9
+  10
+  11
+  12
+  13
+(10 rows)
+
+SET ROLE TO var_test_role;
+-- should fail
+LET var3 = 0;
+ERROR:  permission denied for session variable var3
+SET ROLE TO DEFAULT;
+DROP VIEW schema_var_view;
+DROP VARIABLE var1 CASCADE;
+DROP VARIABLE var3 CASCADE;
+-- composite variables
+CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2));
+CREATE VARIABLE v1 AS sv_xyz;
+CREATE VARIABLE v2 AS sv_xyz;
+\d v1
+\d v2
+LET v1 = (1,2,3.14);
+LET v2 = (10,20,3.14*10);
+-- should work too - there are prepared casts
+LET v1 = (1,2,3.14);
+SELECT v1;
+     v1     
+------------
+ (1,2,3.14)
+(1 row)
+
+SELECT v2;
+      v2       
+---------------
+ (10,20,31.40)
+(1 row)
+
+SELECT (v1).*;
+ x | y |  z   
+---+---+------
+ 1 | 2 | 3.14
+(1 row)
+
+SELECT (v2).*;
+ x  | y  |   z   
+----+----+-------
+ 10 | 20 | 31.40
+(1 row)
+
+SELECT v1.x + v1.z;
+ ?column? 
+----------
+     4.14
+(1 row)
+
+SELECT v2.x + v2.z;
+ ?column? 
+----------
+    41.40
+(1 row)
+
+-- access to composite fields should be safe too
+-- should fail
+SET ROLE TO var_test_role;
+SELECT v2.x;
+ERROR:  permission denied for session variable v2
+SET ROLE TO DEFAULT;
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+REVOKE USAGE ON SCHEMA svartest FROM var_test_role;
+DROP ROLE var_test_role;
+-- scalar variables should not be in conflict with qualified column
+CREATE VARIABLE varx AS text;
+SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class';
+ relname  
+----------
+ pg_class
+(1 row)
+
+-- should fail
+SELECT varx.xxx;
+ERROR:  type text is not composite
+-- variables can be updated under RO transaction
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET varx = 'hello';
+COMMIT;
+SELECT varx;
+ varx  
+-------
+ hello
+(1 row)
+
+DROP VARIABLE varx;
+CREATE TYPE t1 AS (a int, b numeric, c text);
+CREATE VARIABLE v1 AS t1;
+LET v1 = (1, pi(), 'hello');
+SELECT v1;
+             v1             
+----------------------------
+ (1,3.14159265358979,hello)
+(1 row)
+
+LET v1.b = 10.2222;
+SELECT v1;
+        v1         
+-------------------
+ (1,10.2222,hello)
+(1 row)
+
+-- should fail
+LET v1.x = 10;
+ERROR:  cannot assign to field "x" of column "v1" because there is no such column in data type t1
+LINE 1: LET v1.x = 10;
+            ^
+DROP VARIABLE v1;
+DROP TYPE t1;
+-- arrays are supported
+CREATE VARIABLE va1 AS numeric[];
+LET va1 = ARRAY[1.1,2.1];
+LET va1[1] = 10.1;
+SELECT va1;
+    va1     
+------------
+ {10.1,2.1}
+(1 row)
+
+CREATE TYPE ta2 AS (a numeric, b numeric[]);
+CREATE VARIABLE va2 AS ta2;
+LET va2 = (10.1, ARRAY[0.0, 0.0]);
+LET va2.a = 10.2;
+SELECT va2;
+        va2         
+--------------------
+ (10.2,"{0.0,0.0}")
+(1 row)
+
+LET va2.b[1] = 10.3;
+SELECT va2;
+         va2         
+---------------------
+ (10.2,"{10.3,0.0}")
+(1 row)
+
+DROP VARIABLE va1;
+DROP VARIABLE va2;
+DROP TYPE ta2;
+-- default values
+CREATE VARIABLE v1 AS numeric DEFAULT pi();
+LET v1 = v1 * 2;
+SELECT v1;
+        v1        
+------------------
+ 6.28318530717958
+(1 row)
+
+CREATE TYPE t2 AS (a numeric, b text);
+CREATE VARIABLE v2 AS t2 DEFAULT (NULL, 'Hello');
+LET svartest.v2.a = pi();
+SELECT v2;
+            v2            
+--------------------------
+ (3.14159265358979,Hello)
+(1 row)
+
+-- should fail due dependency
+DROP TYPE t2;
+ERROR:  cannot drop type t2 because other objects depend on it
+DETAIL:  session variable v2 depends on type t2
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- should be ok
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+-- tests of alters
+CREATE SCHEMA var_schema1;
+CREATE SCHEMA var_schema2;
+CREATE VARIABLE var_schema1.var1 AS integer;
+LET var_schema1.var1 = 1000;
+SELECT var_schema1.var1;
+ var1 
+------
+ 1000
+(1 row)
+
+ALTER VARIABLE var_schema1.var1 SET SCHEMA var_schema2;
+SELECT var_schema2.var1;
+ var1 
+------
+ 1000
+(1 row)
+
+CREATE ROLE var_test_role;
+ALTER VARIABLE var_schema2.var1 OWNER TO var_test_role;
+SET ROLE TO var_test_role;
+-- should fail, no access to schema var_schema2.var
+SELECT var_schema2.var1;
+ERROR:  permission denied for schema var_schema2
+DROP VARIABLE var_schema2.var1;
+ERROR:  permission denied for schema var_schema2
+SET ROLE TO DEFAULT;
+ALTER VARIABLE var_schema2.var1 SET SCHEMA public;
+SET ROLE TO var_test_role;
+SELECT public.var1;
+ var1 
+------
+ 1000
+(1 row)
+
+ALTER VARIABLE public.var1 RENAME TO var1_renamed;
+SELECT public.var1_renamed;
+ var1_renamed 
+--------------
+         1000
+(1 row)
+
+DROP VARIABLE public.var1_renamed;
+SET ROLE TO DEFAULt;
+-- default rights test
+ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON VARIABLES TO var_test_role;
+CREATE VARIABLE public.var2 AS int;
+SET ROLE TO var_test_role;
+-- should be ok
+LET public.var2 = 100;
+SELECT public.var2;
+ var2 
+------
+  100
+(1 row)
+
+SET ROLE TO DEFAULt;
+DROP VARIABLE public.var2;
+DROP OWNED BY var_test_role;
+DROP ROLE var_test_role;
+CREATE VARIABLE xx AS text DEFAULT 'hello';
+SELECT xx, upper(xx);
+  xx   | upper 
+-------+-------
+ hello | HELLO
+(1 row)
+
+LET xx = 'Hi';
+SELECT xx;
+ xx 
+----
+ Hi
+(1 row)
+
+DROP VARIABLE xx;
+-- ON TRANSACTION END RESET tests
+CREATE VARIABLE t1 AS int DEFAULT -1 ON TRANSACTION END RESET;
+BEGIN;
+  SELECT t1;
+ t1 
+----
+ -1
+(1 row)
+
+  LET t1 = 100;
+  SELECT t1;
+ t1  
+-----
+ 100
+(1 row)
+
+COMMIT;
+SELECT t1;
+ t1 
+----
+ -1
+(1 row)
+
+BEGIN;
+  SELECT t1;
+ t1 
+----
+ -1
+(1 row)
+
+  LET t1 = 100;
+  SELECT t1;
+ t1  
+-----
+ 100
+(1 row)
+
+ROLLBACK;
+SELECT t1;
+ t1 
+----
+ -1
+(1 row)
+
+DROP VARIABLE t1;
+CREATE VARIABLE v1 AS int DEFAULT 0;
+CREATE VARIABLE v2 AS text DEFAULT 'none';
+LET v1 = 100;
+LET v2 = 'Hello';
+SELECT v1, v2;
+ v1  |  v2   
+-----+-------
+ 100 | Hello
+(1 row)
+
+LET v1 = DEFAULT;
+LET v2 = DEFAULT;
+SELECT v1, v2;
+ v1 |  v2  
+----+------
+  0 | none
+(1 row)
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+-- ON COMMIT DROP tests
+-- should be 0 always
+SELECT count(*) FROM pg_variable;
+ count 
+-------
+     0
+(1 row)
+
+CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+SELECT count(*) FROM pg_variable;
+ count 
+-------
+     0
+(1 row)
+
+BEGIN;
+  CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+COMMIT;
+SELECT count(*) FROM pg_variable;
+ count 
+-------
+     0
+(1 row)
+
+BEGIN;
+  CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+ROLLBACK;
+SELECT count(*) FROM pg_variable;
+ count 
+-------
+     0
+(1 row)
+
+-- test on query with workers
+CREATE TABLE svar_test(a int);
+INSERT INTO svar_test SELECT * FROM generate_series(1,1000000);
+ANALYZE svar_test;
+CREATE VARIABLE zero int;
+LET zero = 0;
+-- parallel workers should be used
+EXPLAIN (costs off) SELECT count(*) FROM svar_test WHERE a%10 = zero;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Finalize Aggregate
+   ->  Gather
+         Workers Planned: 2
+         ->  Partial Aggregate
+               ->  Parallel Seq Scan on svar_test
+                     Filter: ((a % 10) = zero)
+(6 rows)
+
+-- result should be 100000
+SELECT count(*) FROM svar_test WHERE a%10 = zero;
+ count  
+--------
+ 100000
+(1 row)
+
+LET zero = (SELECT count(*) FROM svar_test);
+-- result should be 1000000
+SELECT zero;
+  zero   
+---------
+ 1000000
+(1 row)
+
+-- parallel workers should be used
+EXPLAIN (costs off) LET zero = (SELECT count(*) FROM svar_test);
+                        QUERY PLAN                        
+----------------------------------------------------------
+ SET SESSION VARIABLE
+ Result
+   InitPlan 1 (returns $1)
+     ->  Finalize Aggregate
+           ->  Gather
+                 Workers Planned: 2
+                 ->  Partial Aggregate
+                       ->  Parallel Seq Scan on svar_test
+(8 rows)
+
+DROP TABLE svar_test;
+DROP VARIABLE zero;
+-- use variables in prepared statements
+CREATE VARIABLE v AS numeric;
+LET v = 3.14;
+-- use variables in views
+CREATE VIEW vv AS SELECT COALESCE(v, 0) + 1000 AS result;
+SELECT * FROM vv;
+ result  
+---------
+ 1003.14
+(1 row)
+
+-- start a new session
+\c
+SET search_path to svartest;
+SELECT * FROM vv;
+ result 
+--------
+   1000
+(1 row)
+
+LET v = 3.14;
+SELECT * FROM vv;
+ result  
+---------
+ 1003.14
+(1 row)
+
+-- should fail, dependency
+DROP VARIABLE v;
+ERROR:  cannot drop session variable v because other objects depend on it
+DETAIL:  view vv depends on session variable v
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- should be ok
+DROP VARIABLE v CASCADE;
+NOTICE:  drop cascades to view vv
+-- other features
+CREATE VARIABLE dt AS integer DEFAULT 0;
+LET dt = 100;
+SELECT dt;
+ dt  
+-----
+ 100
+(1 row)
+
+DISCARD VARIABLES;
+SELECT dt;
+ dt 
+----
+  0
+(1 row)
+
+DROP VARIABLE dt;
+-- NOT NULL
+CREATE VARIABLE v1 AS int NOT NULL;
+CREATE VARIABLE v2 AS int NOT NULL DEFAULT NULL;
+-- should fail
+SELECT v1;
+ERROR:  null value is not allowed for NOT NULL session variable "svartest.v1"
+DETAIL:  The session variable was not initialized yet.
+SELECT v2;
+ERROR:  null value is not allowed for NOT NULL session variable "svartest.v2"
+LET v1 = NULL;
+ERROR:  null value is not allowed for NOT NULL session variable "svartest.v1"
+LET v2 = NULL;
+ERROR:  null value is not allowed for NOT NULL session variable "svartest.v2"
+LET v1 = DEFAULT;
+ERROR:  null value is not allowed for NOT NULL session variable "svartest.v1"
+LET v2 = DEFAULT;
+ERROR:  null value is not allowed for NOT NULL session variable "svartest.v2"
+-- should be ok
+LET v1 = 100;
+LET v2 = 1000;
+SELECT v1, v2;
+ v1  |  v2  
+-----+------
+ 100 | 1000
+(1 row)
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+CREATE VARIABLE tv AS int;
+CREATE VARIABLE IF NOT EXISTS tv AS int;
+NOTICE:  session variable "tv" already exists, skipping
+DROP VARIABLE tv;
+CREATE IMMUTABLE VARIABLE iv AS int DEFAULT 100;
+SELECT iv;
+ iv  
+-----
+ 100
+(1 row)
+
+-- should fail;
+LET iv = 10000;
+ERROR:  session variable "svartest.iv" is declared IMMUTABLE
+DROP VARIABLE iv;
+-- different order
+CREATE IMMUTABLE VARIABLE iv AS int DEFAULT 100;
+-- should to fail
+LET iv = 10000;
+ERROR:  session variable "svartest.iv" is declared IMMUTABLE
+-- should be ok
+SELECT iv;
+ iv  
+-----
+ 100
+(1 row)
+
+DROP VARIABLE iv;
+CREATE IMMUTABLE VARIABLE iv AS int;
+-- should be ok
+LET iv = NULL;
+-- should fail
+LET iv = NULL;
+ERROR:  session variable "svartest.iv" is declared IMMUTABLE
+DROP VARIABLE iv;
+-- create variable inside plpgsql block
+DO $$
+BEGIN
+  CREATE VARIABLE do_test_svar AS date DEFAULT '2000-01-01';
+END;
+$$;
+SELECT do_test_svar;
+ do_test_svar 
+--------------
+ 01-01-2000
+(1 row)
+
+DROP VARIABLE do_test_svar;
+-- should fail
+CREATE IMMUTABLE VARIABLE xx AS int NOT NULL;
+ERROR:  IMMUTABLE NOT NULL variable requires default expression
+-- REASSIGN OWNED test
+CREATE ROLE var_test_role1;
+CREATE ROLE var_test_role2;
+CREATE VARIABLE xxx_var AS int;
+ALTER VARIABLE xxx_var OWNER TO var_test_role1;
+REASSIGN OWNED BY var_test_role1 to var_test_role2;
+SELECT varowner::regrole FROM pg_variable WHERE varname = 'xxx_var';
+    varowner    
+----------------
+ var_test_role2
+(1 row)
+
+DROP OWNED BY var_test_role1;
+DROP ROLE var_test_role1;
+SELECT count(*) FROM pg_variable WHERE varname = 'xxx_var';
+ count 
+-------
+     1
+(1 row)
+
+DROP OWNED BY var_test_role2;
+DROP ROLE var_test_role2;
+SELECT count(*) FROM pg_variable WHERE varname = 'xxx_var';
+ count 
+-------
+     0
+(1 row)
+
+-- creating, dropping temporary variable
+BEGIN;
+CREATE TEMP VARIABLE tempvar AS INT ON COMMIT DROP;
+LET tempvar = 100;
+SAVEPOINT s1;
+DROP VARIABLE tempvar;
+ROLLBACK TO s1;
+SELECT tempvar;
+ tempvar 
+---------
+     100
+(1 row)
+
+COMMIT;
+-- should to fail
+LET tempvar = 100;
+ERROR:  session variable "tempvar" doesn't exist
+LINE 1: LET tempvar = 100;
+            ^
+BEGIN;
+SAVEPOINT s1;
+CREATE TEMP VARIABLE tempvar AS INT ON COMMIT DROP;
+LET tempvar = 100;
+ROLLBACK TO s1;
+COMMIT;
+-- should to fail
+LET tempvar = 100;
+ERROR:  session variable "tempvar" doesn't exist
+LINE 1: LET tempvar = 100;
+            ^
+CREATE VARIABLE var1 AS int;
+LET var1 = 100;
+BEGIN;
+DROP VARIABLE var1;
+ROLLBACK;
+SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+DROP VARIABLE var1;
+CREATE VARIABLE var1 AS int DEFAULT 100;
+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;
+CREATE TABLE xxtab(avar int);
+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 
+------
+   10
+(1 row)
+
+SET session_variables_ambiguity_warning TO on;
+SELECT xxtab.avar FROM xxtab;
+WARNING:  session variable "xxtab.avar" is shadowed
+LINE 1: SELECT xxtab.avar FROM xxtab;
+               ^
+DETAIL:  Session variables can be shadowed by columns, routine's variables and routine's arguments with same name.
+ avar 
+------
+   10
+(1 row)
+
+SET search_path = svartest;
+CREATE VARIABLE testvar as int;
+-- plpgsql variables are preferred against session variables
+DO $$
+<<myblock>>
+DECLARE testvar int;
+BEGIN
+  -- should be ok without warning
+  LET testvar = 100;
+  -- should be ok without warning
+  testvar := 1000;
+  -- should be ok without warning
+  RAISE NOTICE 'session variable is %', svartest.testvar;
+  -- should be ok without warning
+  RAISE NOTICE 'plpgsql variable is %', myblock.testvar;
+  -- should to print plpgsql variable with warning
+  RAISE NOTICE 'variable is %', testvar;
+END;
+$$;
+NOTICE:  session variable is 100
+NOTICE:  plpgsql variable is 1000
+WARNING:  session variable "testvar" is shadowed
+LINE 1: testvar
+        ^
+DETAIL:  Session variables can be shadowed by columns, routine's variables and routine's arguments with same name.
+QUERY:  testvar
+NOTICE:  variable is 1000
+DROP VARIABLE testvar;
+SET session_variables_ambiguity_warning TO default;
+-- should be ok
+SELECT avar FROM xxtab;
+ avar 
+------
+   10
+(1 row)
+
+CREATE VARIABLE public.avar AS int;
+-- should to fail
+SELECT avar FROM xxtab;
+ avar 
+------
+   10
+(1 row)
+
+-- should be ok
+SELECT public.avar FROM xxtab;
+ avar 
+------
+     
+(1 row)
+
+DROP VARIABLE xxtab;
+SELECT xxtab.avar FROM xxtab;
+ avar 
+------
+   10
+(1 row)
+
+DROP VARIABLE public.avar;
+DROP TYPE xxtype;
+DROP TABLE xxtab;
+-- test of plan cache invalidation
+CREATE VARIABLE xx AS int;
+SET plan_cache_mode = force_generic_plan;
+PREPARE pp AS SELECT xx;
+EXECUTE pp;
+ xx 
+----
+   
+(1 row)
+
+DROP VARIABLE xx;
+CREATE VARIABLE xx AS int;
+-- should to work
+EXECUTE pp;
+ xx 
+----
+   
+(1 row)
+
+DROP VARIABLE xx;
+DEALLOCATE pp;
+SET plan_cache_mode = DEFAULT;
+CREATE ROLE var_test_role;
+CREATE SCHEMA vartest;
+GRANT USAGE ON SCHEMA vartest TO var_test_role;
+CREATE VARIABLE vartest.x AS int;
+CREATE VARIABLE vartest.y AS int;
+LET vartest.x = 100;
+LET vartest.y = 101;
+GRANT READ ON ALL VARIABLES IN SCHEMA vartest TO var_test_role;
+SET ROLE TO var_test_role;
+SELECT vartest.x, vartest.y;
+  x  |  y  
+-----+-----
+ 100 | 101
+(1 row)
+
+SET ROLE TO DEFAULT;
+REVOKE READ ON ALL VARIABLES IN SCHEMA vartest FROM var_test_role;
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT vartest.x;
+ERROR:  permission denied for session variable x
+SELECT vartest.y;
+ERROR:  permission denied for session variable y
+SET ROLE TO DEFAULT;
+DROP VARIABLE vartest.x, vartest.y;
+DROP SCHEMA vartest;
+DROP ROLE var_test_role;
+-- test cached plan
+CREATE VARIABLE v1 AS text;
+CREATE VARIABLE v2 AS int;
+CREATE VARIABLE v3 AS int;
+LET v1 = 'test';
+LET v2 = 10;
+LET v3 = 5;
+PREPARE q1 AS SELECT v1 || i FROM generate_series(1, v2) g(i) WHERE i IN (v2, v3);
+SET plan_cache_mode to force_generic_plan;
+EXECUTE q1;
+ ?column? 
+----------
+ test5
+ test10
+(2 rows)
+
+EXPLAIN EXECUTE q1;
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Function Scan on generate_series g  (cost=0.00..0.14 rows=2 width=32)
+   Filter: (i = ANY (ARRAY[v2, v3]))
+(2 rows)
+
+-- dependecy check
+DROP VARIABLE v3;
+-- recreate v3 again
+CREATE VARIABLE v3 AS int DEFAULT 6;
+-- should to work, the plan should be recreated
+EXECUTE q1;
+ ?column? 
+----------
+ test6
+ test10
+(2 rows)
+
+DEALLOCATE q1;
+-- fill v1 by long text
+LET v1 = repeat(' ', 10000);
+PREPARE q1 AS SELECT length(v1);
+EXECUTE q1;
+ length 
+--------
+  10000
+(1 row)
+
+LET v1 = repeat(' ', 5000);
+EXECUTE q1;
+ length 
+--------
+   5000
+(1 row)
+
+DEALLOCATE q1;
+SET plan_cache_mode to default;
+DROP VARIABLE v1, v2, v3;
+CREATE ROLE var_test_role;
+CREATE VARIABLE public.v1 AS int DEFAULT 0;
+-- check acl when variable is acessed by simple eval expr method
+CREATE OR REPLACE FUNCTION public.fx_var(int)
+RETURNS int AS $$
+DECLARE xx int;
+BEGIN
+  xx := public.v1 + $1;
+  RETURN xx;
+END;
+$$ LANGUAGE plpgsql;
+-- should be ok
+SELECT public.fx_var(0);
+ fx_var 
+--------
+      0
+(1 row)
+
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT public.fx_var(0);
+ERROR:  permission denied for session variable v1
+CONTEXT:  PL/pgSQL function public.fx_var(integer) line 4 at assignment
+SET ROLE TO default;
+GRANT READ ON VARIABLE public.v1 TO var_test_role;
+SET ROLE TO var_test_role;
+-- should be ok
+SELECT public.fx_var(0);
+ fx_var 
+--------
+      0
+(1 row)
+
+SET ROLE TO default;
+REVOKE READ ON VARIABLE public.v1 FROM var_test_role;
+SET ROLE TO var_test_role;
+-- should be fail
+SELECT public.fx_var(0);
+ERROR:  permission denied for session variable v1
+CONTEXT:  PL/pgSQL function public.fx_var(integer) line 4 at assignment
+SET ROLE TO DEFAULT;
+DROP FUNCTION public.fx_var(int);
+DROP VARIABLE public.v1;
+DROP ROLE var_test_role;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 103e11483d..84a09bc40e 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -119,7 +119,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/session_variables.sql b/src/test/regress/sql/session_variables.sql
new file mode 100644
index 0000000000..e728fdb638
--- /dev/null
+++ b/src/test/regress/sql/session_variables.sql
@@ -0,0 +1,739 @@
+CREATE SCHEMA svartest;
+
+SET search_path = svartest;
+
+CREATE VARIABLE var1 AS integer;
+CREATE TEMP VARIABLE var2 AS text;
+
+DROP VARIABLE var1, var2;
+
+-- functional interface
+CREATE VARIABLE var1 AS numeric;
+
+CREATE ROLE var_test_role;
+GRANT USAGE ON SCHEMA svartest TO var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should fail
+SELECT var1;
+
+SET ROLE TO DEFAULT;
+
+GRANT READ ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+-- should fail
+LET var1 = 10;
+-- should work
+SELECT var1;
+
+SET ROLE TO DEFAULT;
+
+GRANT WRITE ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should work
+LET var1 = 333;
+
+SET ROLE TO DEFAULT;
+
+REVOKE ALL ON VARIABLE var1 FROM var_test_role;
+
+CREATE OR REPLACE FUNCTION secure_var()
+RETURNS int AS $$
+  SELECT svartest.var1::int;
+$$ LANGUAGE sql SECURITY DEFINER;
+
+SELECT secure_var();
+
+SET ROLE TO var_test_role;
+
+-- should fail
+SELECT svartest.var1;
+
+-- should work;
+SELECT secure_var();
+
+SET ROLE TO DEFAULT;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var1;
+
+CREATE VIEW schema_var_view AS SELECT var1;
+
+SELECT * FROM schema_var_view;
+
+\c -
+
+SET search_path = svartest;
+
+-- should work still, but var will be empty
+SELECT * FROM schema_var_view;
+
+LET var1 = pi();
+
+SELECT var1;
+
+-- we can see execution plan of LET statement
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+
+SELECT var1;
+
+CREATE VARIABLE var3 AS int;
+
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+  LET svartest.var3 = COALESCE(svartest.var3 + $1, $1);
+  RETURN var3;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT inc(1);
+SELECT inc(1);
+SELECT inc(1);
+
+SELECT inc(1) FROM generate_series(1,10);
+
+SET ROLE TO var_test_role;
+
+-- should fail
+LET var3 = 0;
+
+SET ROLE TO DEFAULT;
+
+DROP VIEW schema_var_view;
+
+DROP VARIABLE var1 CASCADE;
+DROP VARIABLE var3 CASCADE;
+
+-- composite variables
+
+CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2));
+
+CREATE VARIABLE v1 AS sv_xyz;
+CREATE VARIABLE v2 AS sv_xyz;
+
+\d v1
+\d v2
+
+LET v1 = (1,2,3.14);
+LET v2 = (10,20,3.14*10);
+
+-- should work too - there are prepared casts
+LET v1 = (1,2,3.14);
+
+SELECT v1;
+SELECT v2;
+SELECT (v1).*;
+SELECT (v2).*;
+
+SELECT v1.x + v1.z;
+SELECT v2.x + v2.z;
+
+-- access to composite fields should be safe too
+-- should fail
+SET ROLE TO var_test_role;
+
+SELECT v2.x;
+
+SET ROLE TO DEFAULT;
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+REVOKE USAGE ON SCHEMA svartest FROM var_test_role;
+DROP ROLE var_test_role;
+
+-- scalar variables should not be in conflict with qualified column
+CREATE VARIABLE varx AS text;
+SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class';
+
+-- should fail
+SELECT varx.xxx;
+
+-- variables can be updated under RO transaction
+
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET varx = 'hello';
+COMMIT;
+
+SELECT varx;
+
+DROP VARIABLE varx;
+
+CREATE TYPE t1 AS (a int, b numeric, c text);
+
+CREATE VARIABLE v1 AS t1;
+LET v1 = (1, pi(), 'hello');
+SELECT v1;
+LET v1.b = 10.2222;
+SELECT v1;
+
+-- should fail
+LET v1.x = 10;
+
+DROP VARIABLE v1;
+DROP TYPE t1;
+
+-- arrays are supported
+CREATE VARIABLE va1 AS numeric[];
+LET va1 = ARRAY[1.1,2.1];
+LET va1[1] = 10.1;
+SELECT va1;
+
+CREATE TYPE ta2 AS (a numeric, b numeric[]);
+CREATE VARIABLE va2 AS ta2;
+LET va2 = (10.1, ARRAY[0.0, 0.0]);
+LET va2.a = 10.2;
+SELECT va2;
+LET va2.b[1] = 10.3;
+SELECT va2;
+
+DROP VARIABLE va1;
+DROP VARIABLE va2;
+DROP TYPE ta2;
+
+-- default values
+CREATE VARIABLE v1 AS numeric DEFAULT pi();
+LET v1 = v1 * 2;
+SELECT v1;
+
+CREATE TYPE t2 AS (a numeric, b text);
+CREATE VARIABLE v2 AS t2 DEFAULT (NULL, 'Hello');
+LET svartest.v2.a = pi();
+SELECT v2;
+
+-- should fail due dependency
+DROP TYPE t2;
+
+-- should be ok
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+-- tests of alters
+CREATE SCHEMA var_schema1;
+CREATE SCHEMA var_schema2;
+
+CREATE VARIABLE var_schema1.var1 AS integer;
+LET var_schema1.var1 = 1000;
+SELECT var_schema1.var1;
+ALTER VARIABLE var_schema1.var1 SET SCHEMA var_schema2;
+SELECT var_schema2.var1;
+
+CREATE ROLE var_test_role;
+
+ALTER VARIABLE var_schema2.var1 OWNER TO var_test_role;
+SET ROLE TO var_test_role;
+
+-- should fail, no access to schema var_schema2.var
+SELECT var_schema2.var1;
+DROP VARIABLE var_schema2.var1;
+
+SET ROLE TO DEFAULT;
+
+ALTER VARIABLE var_schema2.var1 SET SCHEMA public;
+
+SET ROLE TO var_test_role;
+SELECT public.var1;
+
+ALTER VARIABLE public.var1 RENAME TO var1_renamed;
+
+SELECT public.var1_renamed;
+
+DROP VARIABLE public.var1_renamed;
+
+SET ROLE TO DEFAULt;
+
+-- default rights test
+ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON VARIABLES TO var_test_role;
+
+CREATE VARIABLE public.var2 AS int;
+
+SET ROLE TO var_test_role;
+
+-- should be ok
+LET public.var2 = 100;
+SELECT public.var2;
+
+SET ROLE TO DEFAULt;
+
+DROP VARIABLE public.var2;
+DROP OWNED BY var_test_role;
+
+DROP ROLE var_test_role;
+
+CREATE VARIABLE xx AS text DEFAULT 'hello';
+
+SELECT xx, upper(xx);
+
+LET xx = 'Hi';
+
+SELECT xx;
+
+DROP VARIABLE xx;
+
+-- ON TRANSACTION END RESET tests
+CREATE VARIABLE t1 AS int DEFAULT -1 ON TRANSACTION END RESET;
+
+BEGIN;
+  SELECT t1;
+  LET t1 = 100;
+  SELECT t1;
+COMMIT;
+
+SELECT t1;
+
+BEGIN;
+  SELECT t1;
+  LET t1 = 100;
+  SELECT t1;
+ROLLBACK;
+
+SELECT t1;
+
+DROP VARIABLE t1;
+
+CREATE VARIABLE v1 AS int DEFAULT 0;
+CREATE VARIABLE v2 AS text DEFAULT 'none';
+
+LET v1 = 100;
+LET v2 = 'Hello';
+SELECT v1, v2;
+LET v1 = DEFAULT;
+LET v2 = DEFAULT;
+SELECT v1, v2;
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+-- ON COMMIT DROP tests
+-- should be 0 always
+SELECT count(*) FROM pg_variable;
+
+CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+
+SELECT count(*) FROM pg_variable;
+
+BEGIN;
+  CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+COMMIT;
+
+SELECT count(*) FROM pg_variable;
+
+BEGIN;
+  CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+ROLLBACK;
+
+SELECT count(*) FROM pg_variable;
+
+-- test on query with workers
+CREATE TABLE svar_test(a int);
+INSERT INTO svar_test SELECT * FROM generate_series(1,1000000);
+ANALYZE svar_test;
+CREATE VARIABLE zero int;
+LET zero = 0;
+
+-- parallel workers should be used
+EXPLAIN (costs off) SELECT count(*) FROM svar_test WHERE a%10 = zero;
+
+-- result should be 100000
+SELECT count(*) FROM svar_test WHERE a%10 = zero;
+
+LET zero = (SELECT count(*) FROM svar_test);
+
+-- result should be 1000000
+SELECT zero;
+
+-- parallel workers should be used
+EXPLAIN (costs off) LET zero = (SELECT count(*) FROM svar_test);
+
+DROP TABLE svar_test;
+DROP VARIABLE zero;
+
+-- use variables in prepared statements
+CREATE VARIABLE v AS numeric;
+LET v = 3.14;
+
+-- use variables in views
+CREATE VIEW vv AS SELECT COALESCE(v, 0) + 1000 AS result;
+SELECT * FROM vv;
+
+-- start a new session
+\c
+
+SET search_path to svartest;
+
+SELECT * FROM vv;
+LET v = 3.14;
+SELECT * FROM vv;
+
+-- should fail, dependency
+DROP VARIABLE v;
+
+-- should be ok
+DROP VARIABLE v CASCADE;
+
+-- other features
+CREATE VARIABLE dt AS integer DEFAULT 0;
+
+LET dt = 100;
+SELECT dt;
+
+DISCARD VARIABLES;
+
+SELECT dt;
+
+DROP VARIABLE dt;
+
+-- NOT NULL
+CREATE VARIABLE v1 AS int NOT NULL;
+CREATE VARIABLE v2 AS int NOT NULL DEFAULT NULL;
+
+-- should fail
+SELECT v1;
+SELECT v2;
+LET v1 = NULL;
+LET v2 = NULL;
+LET v1 = DEFAULT;
+LET v2 = DEFAULT;
+
+-- should be ok
+LET v1 = 100;
+LET v2 = 1000;
+SELECT v1, v2;
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+CREATE VARIABLE tv AS int;
+CREATE VARIABLE IF NOT EXISTS tv AS int;
+DROP VARIABLE tv;
+
+CREATE IMMUTABLE VARIABLE iv AS int DEFAULT 100;
+SELECT iv;
+
+-- should fail;
+LET iv = 10000;
+
+DROP VARIABLE iv;
+
+-- different order
+CREATE IMMUTABLE VARIABLE iv AS int DEFAULT 100;
+-- should to fail
+LET iv = 10000;
+-- should be ok
+SELECT iv;
+
+DROP VARIABLE iv;
+
+CREATE IMMUTABLE VARIABLE iv AS int;
+
+-- should be ok
+LET iv = NULL;
+
+-- should fail
+LET iv = NULL;
+
+DROP VARIABLE iv;
+
+-- create variable inside plpgsql block
+DO $$
+BEGIN
+  CREATE VARIABLE do_test_svar AS date DEFAULT '2000-01-01';
+END;
+$$;
+
+SELECT do_test_svar;
+
+DROP VARIABLE do_test_svar;
+
+-- should fail
+CREATE IMMUTABLE VARIABLE xx AS int NOT NULL;
+
+
+
+-- REASSIGN OWNED test
+CREATE ROLE var_test_role1;
+CREATE ROLE var_test_role2;
+
+CREATE VARIABLE xxx_var AS int;
+
+ALTER VARIABLE xxx_var OWNER TO var_test_role1;
+REASSIGN OWNED BY var_test_role1 to var_test_role2;
+
+SELECT varowner::regrole FROM pg_variable WHERE varname = 'xxx_var';
+
+DROP OWNED BY var_test_role1;
+DROP ROLE var_test_role1;
+SELECT count(*) FROM pg_variable WHERE varname = 'xxx_var';
+
+DROP OWNED BY var_test_role2;
+DROP ROLE var_test_role2;
+SELECT count(*) FROM pg_variable WHERE varname = 'xxx_var';
+
+-- creating, dropping temporary variable
+BEGIN;
+
+CREATE TEMP VARIABLE tempvar AS INT ON COMMIT DROP;
+
+LET tempvar = 100;
+
+SAVEPOINT s1;
+
+DROP VARIABLE tempvar;
+
+ROLLBACK TO s1;
+
+SELECT tempvar;
+
+COMMIT;
+
+-- should to fail
+LET tempvar = 100;
+
+BEGIN;
+
+SAVEPOINT s1;
+
+CREATE TEMP VARIABLE tempvar AS INT ON COMMIT DROP;
+
+LET tempvar = 100;
+
+ROLLBACK TO s1;
+
+COMMIT;
+
+-- should to fail
+LET tempvar = 100;
+
+CREATE VARIABLE var1 AS int;
+LET var1 = 100;
+BEGIN;
+DROP VARIABLE var1;
+ROLLBACK;
+SELECT var1;
+
+DROP VARIABLE var1;
+
+CREATE VARIABLE var1 AS int DEFAULT 100;
+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;
+
+CREATE TABLE xxtab(avar int);
+
+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;
+
+SET session_variables_ambiguity_warning TO on;
+
+SELECT xxtab.avar FROM xxtab;
+
+SET search_path = svartest;
+
+CREATE VARIABLE testvar as int;
+
+-- plpgsql variables are preferred against session variables
+DO $$
+<<myblock>>
+DECLARE testvar int;
+BEGIN
+  -- should be ok without warning
+  LET testvar = 100;
+  -- should be ok without warning
+  testvar := 1000;
+  -- should be ok without warning
+  RAISE NOTICE 'session variable is %', svartest.testvar;
+  -- should be ok without warning
+  RAISE NOTICE 'plpgsql variable is %', myblock.testvar;
+  -- should to print plpgsql variable with warning
+  RAISE NOTICE 'variable is %', testvar;
+END;
+$$;
+
+DROP VARIABLE testvar;
+
+SET session_variables_ambiguity_warning TO default;
+
+-- should be ok
+SELECT avar FROM xxtab;
+
+CREATE VARIABLE public.avar AS int;
+
+-- should to fail
+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;
+
+-- test of plan cache invalidation
+CREATE VARIABLE xx AS int;
+
+SET plan_cache_mode = force_generic_plan;
+
+PREPARE pp AS SELECT xx;
+
+EXECUTE pp;
+
+DROP VARIABLE xx;
+
+CREATE VARIABLE xx AS int;
+
+-- should to work
+EXECUTE pp;
+
+DROP VARIABLE xx;
+
+DEALLOCATE pp;
+
+SET plan_cache_mode = DEFAULT;
+
+CREATE ROLE var_test_role;
+
+CREATE SCHEMA vartest;
+
+GRANT USAGE ON SCHEMA vartest TO var_test_role;
+
+CREATE VARIABLE vartest.x AS int;
+CREATE VARIABLE vartest.y AS int;
+
+LET vartest.x = 100;
+LET vartest.y = 101;
+
+GRANT READ ON ALL VARIABLES IN SCHEMA vartest TO var_test_role;
+
+SET ROLE TO var_test_role;
+
+SELECT vartest.x, vartest.y;
+
+SET ROLE TO DEFAULT;
+
+REVOKE READ ON ALL VARIABLES IN SCHEMA vartest FROM var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+SELECT vartest.x;
+SELECT vartest.y;
+
+SET ROLE TO DEFAULT;
+
+DROP VARIABLE vartest.x, vartest.y;
+
+DROP SCHEMA vartest;
+
+DROP ROLE var_test_role;
+
+-- test cached plan
+CREATE VARIABLE v1 AS text;
+CREATE VARIABLE v2 AS int;
+CREATE VARIABLE v3 AS int;
+
+LET v1 = 'test';
+LET v2 = 10;
+LET v3 = 5;
+
+PREPARE q1 AS SELECT v1 || i FROM generate_series(1, v2) g(i) WHERE i IN (v2, v3);
+
+SET plan_cache_mode to force_generic_plan;
+
+EXECUTE q1;
+
+EXPLAIN EXECUTE q1;
+
+-- dependecy check
+DROP VARIABLE v3;
+
+-- recreate v3 again
+CREATE VARIABLE v3 AS int DEFAULT 6;
+
+-- should to work, the plan should be recreated
+EXECUTE q1;
+
+DEALLOCATE q1;
+
+-- fill v1 by long text
+LET v1 = repeat(' ', 10000);
+
+PREPARE q1 AS SELECT length(v1);
+
+EXECUTE q1;
+
+LET v1 = repeat(' ', 5000);
+
+EXECUTE q1;
+
+DEALLOCATE q1;
+
+SET plan_cache_mode to default;
+
+DROP VARIABLE v1, v2, v3;
+
+CREATE ROLE var_test_role;
+
+CREATE VARIABLE public.v1 AS int DEFAULT 0;
+
+-- check acl when variable is acessed by simple eval expr method
+CREATE OR REPLACE FUNCTION public.fx_var(int)
+RETURNS int AS $$
+DECLARE xx int;
+BEGIN
+  xx := public.v1 + $1;
+  RETURN xx;
+END;
+$$ LANGUAGE plpgsql;
+
+-- should be ok
+SELECT public.fx_var(0);
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+SELECT public.fx_var(0);
+
+SET ROLE TO default;
+
+GRANT READ ON VARIABLE public.v1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should be ok
+SELECT public.fx_var(0);
+
+SET ROLE TO default;
+
+REVOKE READ ON VARIABLE public.v1 FROM var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should be fail
+SELECT public.fx_var(0);
+
+SET ROLE TO DEFAULT;
+
+DROP FUNCTION public.fx_var(int);
+
+DROP VARIABLE public.v1;
+
+DROP ROLE var_test_role;
-- 
2.36.1

From c72c0b0c63cce5cb3d45c56635b79c9ef8361b06 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 08/11] typedefs

---
 src/tools/pgindent/typedefs.list | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 4fb746930a..73d957748f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -498,6 +498,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
@@ -881,6 +882,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
+FormData_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
@@ -1401,6 +1403,7 @@ LargeObjectDesc
 LastAttnumInfo
 Latch
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
@@ -1783,6 +1786,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
@@ -2460,6 +2464,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
@@ -2645,6 +2650,9 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariable
+SVariableData
+SVariableXActActionItem
 Syn
 SyncOps
 SyncRepConfigData
@@ -2908,6 +2916,7 @@ Variable
 VariableAssignHook
 VariableCache
 VariableCacheData
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.36.1

From 893d1c608a2a4e30e2826d6f1137b61aad718a2e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/11] Possibility to dump session variables by pg_dump

Enhancing pg_dump about session variables support
---
 src/bin/pg_dump/common.c             |   3 +-
 src/bin/pg_dump/dumputils.c          |   5 +
 src/bin/pg_dump/pg_backup.h          |   2 +
 src/bin/pg_dump/pg_backup_archiver.c |  12 +-
 src/bin/pg_dump/pg_dump.c            | 237 ++++++++++++++++++++++++++-
 src/bin/pg_dump/pg_dump.h            |  25 ++-
 src/bin/pg_dump/pg_dump_sort.c       |   6 +
 src/bin/pg_dump/pg_restore.c         |   9 +-
 src/bin/pg_dump/t/002_pg_dump.pl     |  65 ++++++++
 9 files changed, 359 insertions(+), 5 deletions(-)

diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 794e6e7ce9..97270e0b06 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -263,7 +263,8 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	pg_log_info("reading subscriptions");
 	getSubscriptions(fout);
 
-	free(inhinfo);				/* not needed any longer */
+	pg_log_info("reading variables");
+	getVariables(fout);
 
 	*numTablesPtr = numTables;
 	return tblinfo;
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 3e68dfc78f..81c0cc02e5 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -511,6 +511,11 @@ do { \
 		CONVERT_PRIV('r', "SELECT");
 		CONVERT_PRIV('w', "UPDATE");
 	}
+	else if (strcmp(type, "VARIABLE") == 0)
+	{
+		CONVERT_PRIV('S', "READ");
+		CONVERT_PRIV('W', "WRITE");
+	}
 	else
 		abort();
 
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index fcc5f6bd05..9d675d669c 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -131,12 +131,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 77fe51a3a5..981fe42a0d 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2936,6 +2936,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;
 		}
@@ -3429,6 +3437,7 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te)
 		strcmp(type, "TEXT SEARCH DICTIONARY") == 0 ||
 		strcmp(type, "TEXT SEARCH CONFIGURATION") == 0 ||
 		strcmp(type, "STATISTICS") == 0 ||
+		strcmp(type, "VARIABLE") == 0 ||
 	/* non-schema-specified objects */
 		strcmp(type, "DATABASE") == 0 ||
 		strcmp(type, "PROCEDURAL LANGUAGE") == 0 ||
@@ -3621,7 +3630,8 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 			strcmp(te->desc, "SERVER") == 0 ||
 			strcmp(te->desc, "STATISTICS") == 0 ||
 			strcmp(te->desc, "PUBLICATION") == 0 ||
-			strcmp(te->desc, "SUBSCRIPTION") == 0)
+			strcmp(te->desc, "SUBSCRIPTION") == 0 ||
+			strcmp(te->desc, "VARIABLE") == 0)
 		{
 			PQExpBuffer temp = createPQExpBuffer();
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7cc9c72e49..d9c5bcafd2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -287,6 +287,7 @@ static void dumpPolicy(Archive *fout, const PolicyInfo *polinfo);
 static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo);
 static void dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo);
 static void dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo);
+static void dumpVariable(Archive *fout, const VariableInfo * varinfo);
 static void dumpDatabase(Archive *AH);
 static void dumpDatabaseConfig(Archive *AH, PQExpBuffer outbuf,
 							   const char *dbname, Oid dboid);
@@ -4702,6 +4703,232 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 	return next_possible_free_oid;
 }
 
+/*
+ * getVariables
+ *	  get information about variables
+ */
+void
+getVariables(Archive *fout)
+{
+	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_vardefexpr;
+	int			i_vareoxaction;
+	int			i_varisnotnull;
+	int			i_varisimmutable;
+	int			i_varowner;
+	int			i_varcollation;
+	int			i_varacl;
+	int			i_acldefault;
+	int			i,
+				ntups;
+
+	if (fout->remoteVersion < 150000)
+		return;
+
+	query = createPQExpBuffer();
+
+	resetPQExpBuffer(query);
+
+	/* Get the variables in current database. */
+	appendPQExpBuffer(query,
+					  "SELECT v.tableoid, v.oid, v.varname,\n"
+					  "v.vareoxaction,\n"
+					  "v.varnamespace,\n"
+					  "v.vartype,\n"
+					  "pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n"
+					  "v.varisnotnull,\n"
+					  "v.varisimmutable,\n"
+					  "CASE WHEN v.varcollation <> t.typcollation "
+					  "THEN v.varcollation ELSE 0 END AS varcollation,\n"
+					  "pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n"
+					  "v.varowner,\n"
+					  "v.varacl,\n"
+					  "acldefault('V', v.varowner) AS acldefault\n"
+					  "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_vareoxaction = PQfnumber(res, "vareoxaction");
+	i_vardefexpr = PQfnumber(res, "vardefexpr");
+	i_varisnotnull = PQfnumber(res, "varisnotnull");
+	i_varisimmutable = PQfnumber(res, "varisimmutable");
+	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].vareoxaction = pg_strdup(PQgetvalue(res, i, i_vareoxaction));
+		varinfo[i].varisnotnull = *(PQgetvalue(res, i, i_varisnotnull)) == 't';
+		varinfo[i].varisimmutable = *(PQgetvalue(res, i, i_varisimmutable)) == 't';
+		varinfo[i].varcollation = atooid(PQgetvalue(res, i, i_varcollation));
+
+		varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl));
+		varinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault));
+		varinfo[i].dacl.privtype = 0;
+		varinfo[i].dacl.initprivs = NULL;
+		varinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_varowner));
+
+		/* Decide whether we want to dump it */
+		selectDumpableObject(&(varinfo[i].dobj), fout);
+
+		/* Do not try to dump ACL if no ACL exists. */
+		if (!PQgetisnull(res, i, i_varacl))
+			varinfo[i].dobj.components |= DUMP_COMPONENT_ACL;
+
+		if (PQgetisnull(res, i, i_vardefexpr))
+			varinfo[i].vardefexpr = NULL;
+		else
+			varinfo[i].vardefexpr = pg_strdup(PQgetvalue(res, i, i_vardefexpr));
+
+		if (strlen(varinfo[i].rolname) == 0)
+			pg_log_warning("owner of variable \"%s\" appears to be invalid",
+						   varinfo[i].dobj.name);
+
+		/* Decide whether we want to dump it */
+		selectDumpableObject(&(varinfo[i].dobj), fout);
+
+		vtype = findTypeByOid(varinfo[i].vartype);
+		addObjectDependency(&varinfo[i].dobj, vtype->dobj.dumpId);
+	}
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpVariable
+ *	  dump the definition of the given variables
+ */
+static void
+dumpVariable(Archive *fout, const VariableInfo * varinfo)
+{
+	DumpOptions *dopt = fout->dopt;
+
+	PQExpBuffer delq;
+	PQExpBuffer query;
+	char *qualvarname;
+	const char *vartypname;
+	const char *vardefexpr;
+	const char *vareoxaction;
+	const char *varisimmutable;
+	Oid			varcollation;
+	bool		varisnotnull;
+
+	/* 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;
+	vardefexpr = varinfo->vardefexpr;
+	vareoxaction = varinfo->vareoxaction;
+	varisnotnull = varinfo->varisnotnull;
+	varisimmutable = varinfo->varisimmutable ? "IMMUTABLE " : "";
+	varcollation = varinfo->varcollation;
+
+	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
+					  qualvarname);
+
+	appendPQExpBuffer(query, "CREATE %sVARIABLE %s AS %s",
+					  varisimmutable, qualvarname, vartypname);
+
+	if (OidIsValid(varcollation))
+	{
+		CollInfo   *coll;
+
+		coll = findCollationByOid(varcollation);
+		if (coll)
+			appendPQExpBuffer(query, " COLLATE %s",
+							  fmtQualifiedDumpable(coll));
+	}
+
+	if (varisnotnull)
+		appendPQExpBuffer(query, " NOT NULL");
+
+	if (vardefexpr)
+		appendPQExpBuffer(query, " DEFAULT %s",
+						  vardefexpr);
+
+	if (strcmp(vareoxaction, "d") == 0)
+		appendPQExpBuffer(query, " ON COMMIT DROP");
+	else if (strcmp(vareoxaction, "r") == 0)
+		appendPQExpBuffer(query, " ON TRANSACTION END RESET");
+
+	appendPQExpBuffer(query, ";\n");
+
+	if (varinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
+		ArchiveEntry(fout, varinfo->dobj.catId, varinfo->dobj.dumpId,
+					 ARCHIVE_OPTS(.tag = varinfo->dobj.name,
+								  .namespace = varinfo->dobj.namespace->dobj.name,
+								  .owner = varinfo->rolname,
+								  .description = "VARIABLE",
+								  .section = SECTION_PRE_DATA,
+								  .createStmt = query->data,
+								  .dropStmt = delq->data));
+
+	/* 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, 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,
@@ -9356,7 +9583,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;
 
@@ -9946,6 +10174,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION:
 			dumpSubscription(fout, (const SubscriptionInfo *) dobj);
 			break;
+		case DO_VARIABLE:
+			dumpVariable(fout, (VariableInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -14292,6 +14523,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
 		case DEFACLOBJ_NAMESPACE:
 			type = "SCHEMAS";
 			break;
+		case DEFACLOBJ_VARIABLE:
+			type = "VARIABLE";
+			break;
 		default:
 			/* shouldn't get here */
 			pg_fatal("unrecognized object type in default privileges: %d",
@@ -17838,6 +18072,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 1d21c2906f..ed4eda3382 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,
@@ -82,7 +83,7 @@ typedef enum
 	DO_PUBLICATION,
 	DO_PUBLICATION_REL,
 	DO_PUBLICATION_TABLE_IN_SCHEMA,
-	DO_SUBSCRIPTION
+	DO_SUBSCRIPTION,
 } DumpableObjectType;
 
 /*
@@ -663,6 +664,27 @@ typedef struct _SubscriptionInfo
 	char	   *subpublications;
 } SubscriptionInfo;
 
+/*
+ * The VariableInfo struct is used to represent session variables
+ */
+typedef struct _VariableInfo
+{
+	DumpableObject dobj;
+	DumpableAcl dacl;
+	Oid			vartype;
+	char	   *vartypname;
+	char	   *vareoxaction;
+	char	   *vardefexpr;
+	char	   *varacl;
+	char	   *rvaracl;
+	char	   *initvaracl;
+	char	   *initrvaracl;
+	bool		varisnotnull;
+	bool		varisimmutable;
+	Oid			varcollation;
+	const char *rolname;		/* name of owner, or empty string */
+}			VariableInfo;
+
 /*
  *	common utility functions
  */
@@ -745,5 +767,6 @@ extern void getPublicationNamespaces(Archive *fout);
 extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
 								 int numTables);
 extern void getSubscriptions(Archive *fout);
+extern void getVariables(Archive *fout);
 
 #endif							/* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 5de3241eb4..1a660b8a9f 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -75,6 +75,7 @@ enum dbObjectTypePriorities
 	PRIO_TABLE_ATTACH,
 	PRIO_DUMMY_TYPE,
 	PRIO_ATTRDEF,
+	PRIO_VARIABLE,
 	PRIO_BLOB,
 	PRIO_PRE_DATA_BOUNDARY,		/* boundary! */
 	PRIO_TABLE_DATA,
@@ -116,6 +117,7 @@ static const int dbObjectTypePriority[] =
 	PRIO_TABLE,					/* DO_TABLE */
 	PRIO_TABLE_ATTACH,			/* DO_TABLE_ATTACH */
 	PRIO_ATTRDEF,				/* DO_ATTRDEF */
+	PRIO_VARIABLE,				/* DO_VARIABLE */
 	PRIO_INDEX,					/* DO_INDEX */
 	PRIO_INDEX_ATTACH,			/* DO_INDEX_ATTACH */
 	PRIO_STATSEXT,				/* DO_STATSEXT */
@@ -1508,6 +1510,10 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "POST-DATA BOUNDARY  (ID %d)",
 					 obj->dumpId);
 			return;
+		case DO_VARIABLE:
+			snprintf(buf, bufsize,
+					 "VARIABLE %s  (ID %d OID %u)",
+					 obj->name, obj->dumpId, obj->catId.oid);
 	}
 	/* shouldn't get here */
 	snprintf(buf, bufsize,
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 049a100634..830cde5421 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -103,6 +103,7 @@ main(int argc, char **argv)
 		{"trigger", 1, NULL, 'T'},
 		{"use-list", 1, NULL, 'L'},
 		{"username", 1, NULL, 'U'},
+		{"variable", 1, NULL, 'A'},
 		{"verbose", 0, NULL, 'v'},
 		{"single-transaction", 0, NULL, '1'},
 
@@ -151,7 +152,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1",
+	while ((c = getopt_long(argc, argv, "A:acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1",
 							cmdopts, NULL)) != -1)
 	{
 		switch (c)
@@ -159,6 +160,11 @@ main(int argc, char **argv)
 			case 'a':			/* Dump data only */
 				opts->dataOnly = 1;
 				break;
+			case 'A':			/* vAriable */
+				opts->selTypes = 1;
+				opts->selVariable = 1;
+				simple_string_list_append(&opts->variableNames, optarg);
+				break;
 			case 'c':			/* clean (i.e., drop) schema prior to create */
 				opts->dropSchema = 1;
 				break;
@@ -444,6 +450,7 @@ usage(const char *progname)
 
 	printf(_("\nOptions controlling the restore:\n"));
 	printf(_("  -a, --data-only              restore only the data, no schema\n"));
+	printf(_("  -A, --variable=NAME          restore named session variable\n"));
 	printf(_("  -c, --clean                  clean (drop) database objects before recreating\n"));
 	printf(_("  -C, --create                 create the target database\n"));
 	printf(_("  -e, --exit-on-error          exit on error, default is to continue\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 1f08716f69..a6f310d3d9 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -572,6 +572,16 @@ my %tests = (
 		unlike => { no_privs => 1, },
 	  },
 
+	'ALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT READ ON VARIABLES TO PUBLIC'
+	  => {
+		create_order => 56,
+		create_sql   => 'ALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT READ ON VARIABLES TO PUBLIC;',
+		regexp => qr/^
+			\QALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT READ ON VARIABLE  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
@@ -1327,6 +1337,22 @@ my %tests = (
 		unlike => { exclude_dump_test_schema => 1, },
 	},
 
+	'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,
+		},
+	},
+
 	'COPY test_table' => {
 		create_order => 4,
 		create_sql   => 'INSERT INTO dump_test.test_table (col1) '
@@ -3232,6 +3258,30 @@ my %tests = (
 		},
 	},
 
+	'CREATE VARIABLE test_variable' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE VARIABLE dump_test.variable1 AS integer DEFAULT 0;',
+		regexp => qr/^
+			\QCREATE VARIABLE dump_test.variable1 AS integer DEFAULT 0;\E/xm,
+		like =>
+		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
+	'CREATE IMMUTABLE VARIABLE test_variable' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE IMMUTABLE VARIABLE dump_test.variable2 AS integer DEFAULT 0;',
+		regexp => qr/^
+			\QCREATE IMMUTABLE VARIABLE dump_test.variable2 AS integer DEFAULT 0;\E/xm,
+		like =>
+		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, },
+	},
+
 	'CREATE VIEW test_view' => {
 		create_order => 61,
 		create_sql   => 'CREATE VIEW dump_test.test_view
@@ -3671,6 +3721,21 @@ my %tests = (
 		like => {},
 	},
 
+	'GRANT READ ON VARIABLE dump_test.variable1' => {
+		create_order => 73,
+		create_sql =>
+		  'GRANT READ ON VARIABLE dump_test.variable1 TO regress_dump_test_role;',
+		regexp => qr/^
+			\QGRANT READ 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,
+		},
+	},
+
 	'REFRESH MATERIALIZED VIEW matview' => {
 		regexp => qr/^\QREFRESH MATERIALIZED VIEW dump_test.matview;\E/m,
 		like =>
-- 
2.36.1

From 7ca1b303b6df07ccb75cff215030faa85bf4a422 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/11] Enhancing psql for session variables

\dV and tab complete for session variables related commands
---
 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 | 70 ++++++++++++++++++++++-----
 5 files changed, 162 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index b51d28780b..fc5d793aea 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -947,6 +947,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 1a5d924a23..7ffedf6e61 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4999,6 +4999,102 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
 	return true;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
+listVariables(const char *pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+	static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false, false, false};
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT n.nspname as \"%s\",\n"
+					  "  v.varname as \"%s\",\n"
+					  "  pg_catalog.format_type(v.vartype, v.vartypmod) as \"%s\",\n"
+					  "  (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"
+					  "  NOT v.varisnotnull as \"%s\",\n"
+					  "  NOT v.varisimmutable as \"%s\",\n"
+					  "  pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\",\n"
+					  "  pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n"
+					  "  CASE v.vareoxaction\n"
+					  "    WHEN 'd' THEN 'ON COMMIT DROP'\n"
+					  "    WHEN 'r' THEN 'ON TRANSACTION END RESET' END as \"%s\"\n",
+					  gettext_noop("Schema"),
+					  gettext_noop("Name"),
+					  gettext_noop("Type"),
+					  gettext_noop("Collation"),
+					  gettext_noop("Nullable"),
+					  gettext_noop("Mutable"),
+					  gettext_noop("Default"),
+					  gettext_noop("Owner"),
+					  gettext_noop("Transactional end action"));
+
+	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 7872c71f58..6960c738bd 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -146,4 +146,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 f8ce1a0706..dcdfb20418 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -286,6 +286,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 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 7f0ab5acb9..e68ff1c277 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -960,6 +960,13 @@ static const SchemaQuery Query_for_trigger_of_table = {
 	.refnamespace = "c1.relnamespace",
 };
 
+static const SchemaQuery Query_for_list_of_variables = {
+	.min_server_version = 150000,
+	.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
@@ -1226,6 +1233,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"IMMUTABLE VARIABLE", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER},	/* for CREATE
+																				 * IMMUTABLE VARIABLE ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -1264,6 +1273,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 */
 };
@@ -1675,7 +1685,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",
+		"FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LET",
+		"LISTEN", "LOAD", "LOCK",
 		"MERGE", "MOVE", "NOTIFY", "PREPARE",
 		"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
 		"RESET", "REVOKE", "ROLLBACK",
@@ -1695,7 +1706,7 @@ psql_completion(const char *text, int start, int end)
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\dP", "\\dPi", "\\dPt",
 		"\\drds", "\\dRs", "\\dRp", "\\ds",
-		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dX", "\\dy",
+		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dX", "\\dy", "\\dV",
 		"\\echo", "\\edit", "\\ef", "\\elif", "\\else", "\\encoding",
 		"\\endif", "\\errverbose", "\\ev",
 		"\\f",
@@ -2138,6 +2149,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",
@@ -2639,7 +2653,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"))
@@ -3077,7 +3091,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("IMMUTABLE VARIABLE", "SEQUENCE", "TABLE", "VIEW", "VARIABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
@@ -3384,6 +3398,17 @@ 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("IMMUTABLE"))
+		COMPLETE_WITH("VARIABLE");
+	else if (TailMatches("CREATE", "VARIABLE", MatchAny) ||
+			 TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny) ||
+			 TailMatches("IMMUTABLE", "VARIABLE", MatchAny))
+			COMPLETE_WITH("AS");
+	else if (TailMatches("VARIABLE", MatchAny, "AS"))
+			/* Complete CREATE VARIABLE <name> with AS types */
+			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 */
@@ -3499,7 +3524,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 	else if (Matches("DISCARD"))
-		COMPLETE_WITH("ALL", "PLANS", "SEQUENCES", "TEMP");
+		COMPLETE_WITH("ALL", "PLANS", "SEQUENCES", "TEMP", "VARIABLES");
 
 /* DO */
 	else if (Matches("DO"))
@@ -3626,6 +3651,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);
@@ -3742,7 +3773,8 @@ psql_completion(const char *text, int start, int end)
 		if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
 			COMPLETE_WITH("SELECT", "INSERT", "UPDATE",
 						  "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER",
-						  "EXECUTE", "USAGE", "ALL");
+						  "EXECUTE", "USAGE", "ALL",
+						  "READ", "WRITE");
 		else
 			COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
 									 "GRANT",
@@ -3760,6 +3792,8 @@ psql_completion(const char *text, int start, int end)
 									 "USAGE",
 									 "SET",
 									 "ALTER SYSTEM",
+									 "READ",
+									 "WRITE",
 									 "ALL");
 	}
 
@@ -3801,7 +3835,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("GRANT|REVOKE", MatchAny) ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny))
 	{
-		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
+		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|READ|WRITE|ALL"))
 			COMPLETE_WITH("ON");
 		else if (TailMatches("GRANT", MatchAny))
 			COMPLETE_WITH("TO");
@@ -3824,7 +3858,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",
@@ -3846,7 +3880,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"))
@@ -3854,7 +3889,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");
@@ -3890,6 +3926,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
@@ -4132,7 +4170,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
@@ -4420,6 +4458,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
 
+/* LET --- can be inside EXPLAIN, PREPARE etc */
+	/* If prev. word is LET suggest a list of variables */
+	else if (TailMatches("LET"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables);
+	/* Complete LET <variable> with "=" */
+	else if (TailMatches("LET", MatchAny))
+		COMPLETE_WITH("=");
+
 /* USER MAPPING */
 	else if (Matches("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH("FOR");
@@ -4586,6 +4632,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*"))
-- 
2.36.1

From b2c3a571a6f0af4d5aee755a3fd7cd02a6c4b579 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/11] Support of LET command in PLpgSQL

The support for LET command as PLpgSQL command does assigning of expression
evaluation to session variable. With extra support in PLpgSQL we can implement
SQL LET command like usual PostgreSQL's command (and the assigning of result
to session variable is not side effect of query evaluation).
---
 src/backend/executor/spi.c              |  3 ++
 src/backend/parser/gram.y               |  8 ++++
 src/backend/parser/parser.c             |  3 +-
 src/include/parser/parser.h             |  6 ++-
 src/pl/plpgsql/src/pl_exec.c            | 55 +++++++++++++++++++++++++
 src/pl/plpgsql/src/pl_funcs.c           | 24 +++++++++++
 src/pl/plpgsql/src/pl_gram.y            | 28 ++++++++++++-
 src/pl/plpgsql/src/pl_reserved_kwlist.h |  1 +
 src/pl/plpgsql/src/plpgsql.h            | 14 ++++++-
 9 files changed, 138 insertions(+), 4 deletions(-)

diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 29bc26669b..56b107c60d 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2963,6 +2963,9 @@ _SPI_error_callback(void *arg)
 			case RAW_PARSE_PLPGSQL_ASSIGN3:
 				errcontext("PL/pgSQL assignment \"%s\"", query);
 				break;
+			case RAW_PARSE_PLPGSQL_LET:
+				errcontext("LET statement \"%s\"", query);
+				break;
 			default:
 				errcontext("SQL statement \"%s\"", query);
 				break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 014aa46d03..3ad7174a17 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -893,6 +893,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %token		MODE_PLPGSQL_ASSIGN1
 %token		MODE_PLPGSQL_ASSIGN2
 %token		MODE_PLPGSQL_ASSIGN3
+%token		MODE_PLPGSQL_LET

 /* Precedence: lowest to highest */
@@ -1013,6 +1014,13 @@ parse_toplevel:
 				pg_yyget_extra(yyscanner)->parsetree =
 					list_make1(makeRawStmt((Node *) n, 0));
 			}
+			| MODE_PLPGSQL_LET LetStmt
+			{
+				LetStmt *n = (LetStmt *) $2;
+				n->plpgsql_mode = true;
+				pg_yyget_extra(yyscanner)->parsetree =
+					list_make1(makeRawStmt((Node *) n, 0));
+			}
 		;
 
 /*
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index eee0a29c08..6726c4a30e 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -61,7 +61,8 @@ raw_parser(const char *str, RawParseMode mode)
 			MODE_PLPGSQL_EXPR,	/* RAW_PARSE_PLPGSQL_EXPR */
 			MODE_PLPGSQL_ASSIGN1,	/* RAW_PARSE_PLPGSQL_ASSIGN1 */
 			MODE_PLPGSQL_ASSIGN2,	/* RAW_PARSE_PLPGSQL_ASSIGN2 */
-			MODE_PLPGSQL_ASSIGN3	/* RAW_PARSE_PLPGSQL_ASSIGN3 */
+			MODE_PLPGSQL_ASSIGN3,	/* RAW_PARSE_PLPGSQL_ASSIGN3 */
+			MODE_PLPGSQL_LET    /* RAW_PARSE_PLPGSQL_LET */
 		};
 
 		yyextra.have_lookahead = true;
diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
index 828150f01b..bf5d5acf48 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -33,6 +33,9 @@
  * RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement,
  * and return a one-element List containing a RawStmt node.  "n"
  * gives the number of dotted names comprising the target ColumnRef.
+ *
+ * RAW_PARSE_PLPGSQL_LET: parse a LET statement, and return a
+ * one-element List containing a RawStmt node.
  */
 typedef enum
 {
@@ -41,7 +44,8 @@ typedef enum
 	RAW_PARSE_PLPGSQL_EXPR,
 	RAW_PARSE_PLPGSQL_ASSIGN1,
 	RAW_PARSE_PLPGSQL_ASSIGN2,
-	RAW_PARSE_PLPGSQL_ASSIGN3
+	RAW_PARSE_PLPGSQL_ASSIGN3,
+	RAW_PARSE_PLPGSQL_LET
 } RawParseMode;
 
 /* Values for the backslash_quote GUC */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 7bd2a9fff1..d013273e5b 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -24,6 +24,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
+#include "commands/session_variable.h"
 #include "executor/execExpr.h"
 #include "executor/spi.h"
 #include "executor/tstoreReceiver.h"
@@ -318,6 +319,8 @@ static int	exec_stmt_commit(PLpgSQL_execstate *estate,
 							 PLpgSQL_stmt_commit *stmt);
 static int	exec_stmt_rollback(PLpgSQL_execstate *estate,
 							   PLpgSQL_stmt_rollback *stmt);
+static int	exec_stmt_let(PLpgSQL_execstate *estate,
+						  PLpgSQL_stmt_let *let);
 
 static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
 								 PLpgSQL_function *func,
@@ -2109,6 +2112,10 @@ exec_stmts(PLpgSQL_execstate *estate, List *stmts)
 				rc = exec_stmt_rollback(estate, (PLpgSQL_stmt_rollback *) stmt);
 				break;
 
+			case PLPGSQL_STMT_LET:
+				rc = exec_stmt_let(estate, (PLpgSQL_stmt_let *) stmt);
+				break;
+
 			default:
 				/* point err_stmt to parent, since this one seems corrupt */
 				estate->err_stmt = save_estmt;
@@ -4977,6 +4984,54 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt)
 	return PLPGSQL_RC_OK;
 }
 
+/* ----------
+ * exec_stmt_let			Evaluate an expression and
+ *					put the result into a session variable.
+ * ----------
+ */
+static int
+exec_stmt_let(PLpgSQL_execstate *estate, PLpgSQL_stmt_let *stmt)
+{
+	bool		isNull;
+	Oid			valtype;
+	int32		valtypmod;
+	Datum		value;
+	Oid			varid;
+
+	List	   *plansources;
+	CachedPlanSource *plansource;
+
+	value = exec_eval_expr(estate,
+						   stmt->expr,
+						   &isNull,
+						   &valtype,
+						   &valtypmod);
+
+	/*
+	 * Oid of target session variable is stored in Query structure.
+	 * It is safer to read this value directly from the plan than to
+	 * hold this value in the plpgsql context, because it's not necessary
+	 * to handle invalidation of the cached value. Next operations
+	 * are read only without any allocations, so we can expect that
+	 * retrieving varid from Query should be fast.
+	 */
+	plansources = SPI_plan_get_plan_sources(stmt->expr->plan);
+	if (list_length(plansources) != 1)
+		elog(ERROR, "unexpected length of plansources of query for LET statement");
+
+	plansource = (CachedPlanSource *) linitial(plansources);
+	if (list_length(plansource->query_list) != 1)
+		elog(ERROR, "unexpected length of plansource of query for LET statement");
+
+	varid = linitial_node(Query, plansource->query_list)->resultVariable;
+	if (!OidIsValid(varid))
+		elog(ERROR, "oid of target session variable is not valid");
+
+	SetSessionVariableWithSecurityCheck(varid, value, isNull, valtype);
+
+	return PLPGSQL_RC_OK;
+}
+
 /* ----------
  * exec_assign_expr			Put an expression's result into a variable.
  * ----------
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index 93d9cef06b..8bb7d18e2b 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -288,6 +288,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
 			return "COMMIT";
 		case PLPGSQL_STMT_ROLLBACK:
 			return "ROLLBACK";
+		case PLPGSQL_STMT_LET:
+			return "LET";
 	}
 
 	return "unknown";
@@ -368,6 +370,7 @@ static void free_perform(PLpgSQL_stmt_perform *stmt);
 static void free_call(PLpgSQL_stmt_call *stmt);
 static void free_commit(PLpgSQL_stmt_commit *stmt);
 static void free_rollback(PLpgSQL_stmt_rollback *stmt);
+static void free_let(PLpgSQL_stmt_let *stmt);
 static void free_expr(PLpgSQL_expr *expr);

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

@@ -912,6 +925,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
 		case PLPGSQL_STMT_ROLLBACK:
 			dump_rollback((PLpgSQL_stmt_rollback *) stmt);
 			break;
+		case PLPGSQL_STMT_LET:
+			dump_let((PLpgSQL_stmt_let *) stmt);
+			break;
 		default:
 			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
 			break;
@@ -1588,6 +1604,14 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
 	printf("\n");
 }
 
+static void
+dump_let(PLpgSQL_stmt_let *stmt)
+{
+	dump_ind();
+	dump_expr(stmt->expr);
+	printf("\n");
+}
+
 static void
 dump_expr(PLpgSQL_expr *expr)
 {
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 0b8aea9504..0864f6de55 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -198,7 +198,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %type <stmt>	stmt_return stmt_raise stmt_assert stmt_execsql
 %type <stmt>	stmt_dynexecute stmt_for stmt_perform stmt_call stmt_getdiag
 %type <stmt>	stmt_open stmt_fetch stmt_move stmt_close stmt_null
-%type <stmt>	stmt_commit stmt_rollback
+%type <stmt>	stmt_commit stmt_rollback stmt_let
 %type <stmt>	stmt_case stmt_foreach_a
 
 %type <list>	proc_exceptions
@@ -305,6 +305,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_INTO
 %token <keyword>	K_IS
 %token <keyword>	K_LAST
+%token <keyword>	K_LET
 %token <keyword>	K_LOG
 %token <keyword>	K_LOOP
 %token <keyword>	K_MERGE
@@ -899,6 +900,8 @@ proc_stmt		: pl_block ';'
 						{ $$ = $1; }
 				| stmt_rollback
 						{ $$ = $1; }
+				| stmt_let
+						{ $$ = $1; }
 				;
 
 stmt_perform	: K_PERFORM
@@ -1015,6 +1018,29 @@ stmt_assign		: T_DATUM
 					}
 				;
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
+						pmode = RAW_PARSE_PLPGSQL_LET;
+
+						new = palloc0(sizeof(PLpgSQL_stmt_let));
+						new->cmd_type = PLPGSQL_STMT_LET;
+						new->lineno   = plpgsql_location_to_lineno(@1);
+						new->stmtid = ++plpgsql_curr_compile->nstatements;
+
+						/* Push back the head name to include it in the stmt */
+						plpgsql_push_back_token(K_LET);
+						new->expr = read_sql_construct(';', 0, 0, ";",
+													   pmode,
+													   false, true, true,
+													   NULL, NULL);
+
+						$$ = (PLpgSQL_stmt *)new;
+					}
+				;
+
 stmt_getdiag	: K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
 					{
 						PLpgSQL_stmt_getdiag *new;
diff --git a/src/pl/plpgsql/src/pl_reserved_kwlist.h b/src/pl/plpgsql/src/pl_reserved_kwlist.h
index 9043fbddbc..e68b5d5b26 100644
--- a/src/pl/plpgsql/src/pl_reserved_kwlist.h
+++ b/src/pl/plpgsql/src/pl_reserved_kwlist.h
@@ -40,6 +40,7 @@ PG_KEYWORD("from", K_FROM)
 PG_KEYWORD("if", K_IF)
 PG_KEYWORD("in", K_IN)
 PG_KEYWORD("into", K_INTO)
+PG_KEYWORD("let", K_LET)
 PG_KEYWORD("loop", K_LOOP)
 PG_KEYWORD("not", K_NOT)
 PG_KEYWORD("null", K_NULL)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 4e6ee1c619..fa386297ec 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -127,7 +127,8 @@ typedef enum PLpgSQL_stmt_type
 	PLPGSQL_STMT_PERFORM,
 	PLPGSQL_STMT_CALL,
 	PLPGSQL_STMT_COMMIT,
-	PLPGSQL_STMT_ROLLBACK
+	PLPGSQL_STMT_ROLLBACK,
+	PLPGSQL_STMT_LET
 } PLpgSQL_stmt_type;
 
 /*
@@ -519,6 +520,17 @@ typedef struct PLpgSQL_stmt_assign
 	PLpgSQL_expr *expr;
 } PLpgSQL_stmt_assign;
 
+/*
+ * Let statement
+ */
+typedef struct PLpgSQL_stmt_let
+{
+	PLpgSQL_stmt_type cmd_type;
+	int			lineno;
+	unsigned int stmtid;
+	PLpgSQL_expr *expr;
+} PLpgSQL_stmt_let;
+
 /*
  * PERFORM statement
  */
-- 
2.36.1

From 78c9fba10083b2c0452959ae53d1998b2e606a85 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/11] DISCARD VARIABLES command

---
 src/backend/commands/discard.c | 6 ++++++
 src/backend/parser/gram.y      | 7 ++++++-
 src/backend/tcop/utility.c     | 3 +++
 src/include/nodes/parsenodes.h | 3 ++-
 src/include/tcop/cmdtaglist.h  | 1 +
 5 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/discard.c b/src/backend/commands/discard.c
index c583539e0c..68fe550a71 100644
--- a/src/backend/commands/discard.c
+++ b/src/backend/commands/discard.c
@@ -19,6 +19,7 @@
 #include "commands/discard.h"
 #include "commands/prepare.h"
 #include "commands/sequence.h"
+#include "commands/session_variable.h"
 #include "utils/guc.h"
 #include "utils/portal.h"
 
@@ -48,6 +49,10 @@ DiscardCommand(DiscardStmt *stmt, bool isTopLevel)
 			ResetTempTableNamespace();
 			break;
 
+		case DISCARD_VARIABLES:
+			ResetSessionVariables();
+			break;
+
 		default:
 			elog(ERROR, "unrecognized DISCARD target: %d", stmt->target);
 	}
@@ -75,4 +80,5 @@ DiscardAll(bool isTopLevel)
 	ResetPlanCache();
 	ResetTempTableNamespace();
 	ResetSequenceCaches();
+	ResetSessionVariables();
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3ad7174a17..7b7d39df4b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2115,7 +2115,12 @@ DiscardStmt:
 					n->target = DISCARD_SEQUENCES;
 					$$ = (Node *) n;
 				}
-
+			| DISCARD VARIABLES
+				{
+					DiscardStmt *n = makeNode(DiscardStmt);
+					n->target = DISCARD_VARIABLES;
+					$$ = (Node *) n;
+				}
 		;

diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 926ef1f3c6..5cc2aab083 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -2952,6 +2952,9 @@ CreateCommandTag(Node *parsetree)
 				case DISCARD_SEQUENCES:
 					tag = CMDTAG_DISCARD_SEQUENCES;
 					break;
+				case DISCARD_VARIABLES:
+					tag = CMDTAG_DISCARD_VARIABLES;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index c53b36c0ee..53c2cb7621 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3839,7 +3839,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;
 
 typedef struct DiscardStmt
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 59f144bde1..037538a3fe 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -135,6 +135,7 @@ PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false)
 PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_VARIABLES, "DISCARD VARIABLES", false, false, false)
 PG_CMDTAG(CMDTAG_DO, "DO", false, false, false)
 PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
-- 
2.36.1

From d75db4fa81216c453b14347abf42ed0a9d7181fc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 03/11] LET command

Set session variable value to result of query or result of default expression
---
 src/backend/commands/explain.c           |  16 ++
 src/backend/commands/sessionvariable.c   |  80 +++++++
 src/backend/executor/Makefile            |   1 +
 src/backend/executor/execMain.c          |   1 +
 src/backend/executor/svariableReceiver.c | 154 ++++++++++++
 src/backend/nodes/copyfuncs.c            |  17 ++
 src/backend/nodes/equalfuncs.c           |  14 ++
 src/backend/nodes/nodeFuncs.c            |  10 +
 src/backend/nodes/outfuncs.c             |  16 ++
 src/backend/parser/analyze.c             | 283 +++++++++++++++++++++++
 src/backend/parser/gram.y                |  53 ++++-
 src/backend/parser/parse_agg.c           |   4 +
 src/backend/parser/parse_expr.c          |   4 +
 src/backend/parser/parse_func.c          |   3 +
 src/backend/parser/parse_target.c        |   4 +-
 src/backend/rewrite/rewriteHandler.c     |  34 +++
 src/backend/rewrite/rowsecurity.c        |   8 +-
 src/backend/tcop/dest.c                  |   7 +
 src/backend/tcop/utility.c               |  15 ++
 src/backend/utils/cache/plancache.c      |  15 +-
 src/include/commands/session_variable.h  |   3 +
 src/include/executor/svariableReceiver.h |  25 ++
 src/include/nodes/nodes.h                |   1 +
 src/include/nodes/parsenodes.h           |  15 ++
 src/include/nodes/plannodes.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                  |   3 +-
 29 files changed, 782 insertions(+), 11 deletions(-)
 create mode 100644 src/backend/executor/svariableReceiver.c
 create mode 100644 src/include/executor/svariableReceiver.h

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 5d1f7089da..97160df69d 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -492,6 +492,22 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
 		else
 			ExplainDummyGroup("Notify", NULL, es);
 	}
+	else if (IsA(utilityStmt, LetStmt))
+	{
+		LetStmt	   *letstmt = (LetStmt *) utilityStmt;
+		List	   *rewritten;
+
+		if (es->format == EXPLAIN_FORMAT_TEXT)
+			appendStringInfoString(es->str, "SET SESSION VARIABLE\n");
+		else
+			ExplainDummyGroup("Set Session Variable", NULL, es);
+
+		rewritten = QueryRewrite(castNode(Query, copyObject(letstmt->query)));
+		Assert(list_length(rewritten) == 1);
+		ExplainOneQuery(linitial_node(Query, rewritten),
+						CURSOR_OPT_PARALLEL_OK, NULL, es,
+						queryString, params, queryEnv);
+	}
 	else
 	{
 		if (es->format == EXPLAIN_FORMAT_TEXT)
diff --git a/src/backend/commands/sessionvariable.c b/src/backend/commands/sessionvariable.c
index e26903d135..82f01abb5f 100644
--- a/src/backend/commands/sessionvariable.c
+++ b/src/backend/commands/sessionvariable.c
@@ -23,6 +23,7 @@
 #include "catalog/pg_variable.h"
 #include "commands/session_variable.h"
 #include "executor/executor.h"
+#include "executor/svariableReceiver.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -935,6 +936,85 @@ ResetSessionVariables(void)
 	recheck_sessionvars_is_required = false;
 }
 
+/*
+ * Assign result of evaluated expression to session variable
+ */
+void
+ExecuteLetStmt(ParseState *pstate,
+			   LetStmt *stmt,
+			   ParamListInfo params,
+			   QueryEnvironment *queryEnv,
+			   QueryCompletion *qc)
+{
+	Query	   *query = castNode(Query, stmt->query);
+	List	   *rewritten;
+	DestReceiver *dest;
+	AclResult	aclresult;
+	PlannedStmt *plan;
+	QueryDesc  *queryDesc;
+	Oid			varid = query->resultVariable;
+
+	Assert(OidIsValid(varid));
+
+	/*
+	 * Is it allowed to write to session variable?
+	 */
+	aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));
+
+	/* Create dest receiver for LET */
+	dest = CreateDestReceiver(DestVariable);
+	SetVariableDestReceiverParams(dest, varid);
+
+	/* run rewriter - can be used for replacement of DEFAULT node */
+	query = copyObject(query);
+
+	rewritten = QueryRewrite(query);
+
+	Assert(list_length(rewritten) == 1);
+
+	query = linitial_node(Query, rewritten);
+	Assert(query->commandType == CMD_SELECT);
+
+	/* plan the query */
+	plan = pg_plan_query(query, pstate->p_sourcetext,
+						 CURSOR_OPT_PARALLEL_OK, params);
+
+	/*
+	 * Use a snapshot with an updated command ID to ensure this query sees
+	 * results of any previously executed queries.  (This could only
+	 * matter if the planner executed an allegedly-stable function that
+	 * changed the database contents, but let's do it anyway to be
+	 * parallel to the EXPLAIN code path.)
+	 */
+	PushCopiedSnapshot(GetActiveSnapshot());
+	UpdateActiveSnapshotCommandId();
+
+	/* Create a QueryDesc, redirecting output to our tuple receiver */
+	queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
+								GetActiveSnapshot(), InvalidSnapshot,
+								dest, params, queryEnv, 0);
+
+	/* call ExecutorStart to prepare the plan for execution */
+	ExecutorStart(queryDesc, 0);
+
+	/* run the plan to completion */
+	ExecutorRun(queryDesc, ForwardScanDirection, 2L, true);
+
+	/* save the rowcount if we're given a qc to fill */
+	if (qc)
+		SetQueryCompletion(qc, CMDTAG_LET, queryDesc->estate->es_processed);
+
+	/* and clean up */
+	ExecutorFinish(queryDesc);
+	ExecutorEnd(queryDesc);
+
+	FreeQueryDesc(queryDesc);
+
+	PopActiveSnapshot();
+}
+
 /*
  * Registration of actions to be executed on session variables at transaction
  * end time. We want to drop temporary session variables with clause ON COMMIT
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/execMain.c b/src/backend/executor/execMain.c
index 5e915cb1e7..9ba8096e62 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -51,6 +51,7 @@
 #include "commands/session_variable.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSubplan.h"
+#include "executor/svariableReceiver.h"
 #include "foreign/fdwapi.h"
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
diff --git a/src/backend/executor/svariableReceiver.c b/src/backend/executor/svariableReceiver.c
new file mode 100644
index 0000000000..97710b0d23
--- /dev/null
+++ b/src/backend/executor/svariableReceiver.c
@@ -0,0 +1,154 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.c
+ *	  An implementation of DestReceiver that stores the result value in
+ *	  a session variable.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/svariableReceiver.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/detoast.h"
+#include "executor/svariableReceiver.h"
+#include "commands/session_variable.h"
+
+typedef struct
+{
+	DestReceiver pub;
+	Oid			varid;
+	Oid			typid;
+	int32		typmod;
+	int			typlen;
+	int			slot_offset;
+	int			rows;
+}			svariableState;
+
+
+/*
+ * Prepare to receive tuples from executor.
+ */
+static void
+svariableStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+	svariableState *myState = (svariableState *) self;
+	int			natts = typeinfo->natts;
+	int			outcols = 0;
+	int			i;
+
+	/* Receiver should be initialized by SetVariableDestReceiverParams */
+	Assert(OidIsValid(myState->varid));
+
+	for (i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+
+		if (attr->attisdropped)
+			continue;
+
+		if (++outcols > 1)
+			elog(ERROR, "svariable DestReceiver can take only one attribute");
+
+		myState->typid = attr->atttypid;
+		myState->typmod = attr->atttypmod;
+		myState->typlen = attr->attlen;
+		myState->slot_offset = i;
+	}
+
+	if (outcols == 0)
+		elog(ERROR, "svariable DestReceiver requires one attribute");
+
+	myState->rows = 0;
+}
+
+/*
+ * Receive a tuple from the executor and store it in session variable.
+ */
+static bool
+svariableReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
+{
+	svariableState *myState = (svariableState *) self;
+	Datum		value;
+	bool		isnull;
+	bool		freeval = false;
+
+	/* Make sure the tuple is fully deconstructed */
+	slot_getallattrs(slot);
+
+	value = slot->tts_values[myState->slot_offset];
+	isnull = slot->tts_isnull[myState->slot_offset];
+
+	if (myState->typlen == -1 && !isnull && VARATT_IS_EXTERNAL(DatumGetPointer(value)))
+	{
+		value = PointerGetDatum(detoast_external_attr((struct varlena *)
+													  DatumGetPointer(value)));
+		freeval = true;
+	}
+
+	SetSessionVariable(myState->varid, value, isnull, myState->typid);
+
+	if (freeval)
+		pfree(DatumGetPointer(value));
+
+	return true;
+}
+
+/*
+ * Clean up at end of an executor run
+ */
+static void
+svariableShutdownReceiver(DestReceiver *self)
+{
+	/* Nothing to do. */
+}
+
+/*
+ * Destroy receiver when done with it
+ */
+static void
+svariableDestroyReceiver(DestReceiver *self)
+{
+	pfree(self);
+}
+
+/*
+ * Initially create a DestReceiver object.
+ */
+DestReceiver *
+CreateVariableDestReceiver(void)
+{
+	svariableState *self = (svariableState *) palloc0(sizeof(svariableState));
+
+	self->pub.receiveSlot = svariableReceiveSlot;
+	self->pub.rStartup = svariableStartupReceiver;
+	self->pub.rShutdown = svariableShutdownReceiver;
+	self->pub.rDestroy = svariableDestroyReceiver;
+	self->pub.mydest = DestVariable;
+
+	/*
+	 * Private fields will be set by SetVariableDestReceiverParams and
+	 * svariableStartupReceiver.
+	 */
+	return (DestReceiver *) self;
+}
+
+/*
+ * Set parameters for a VariableDestReceiver.
+ * Should be called right after creating the DestReceiver.
+ */
+void
+SetVariableDestReceiverParams(DestReceiver *self, Oid varid)
+{
+	svariableState *myState = (svariableState *) self;
+
+	Assert(myState->pub.mydest == DestVariable);
+	Assert(OidIsValid(varid));
+
+	myState->varid = varid;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 8885e04417..c07ff1d872 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3830,6 +3830,20 @@ _copySelectStmt(const SelectStmt *from)
 	return newnode;
 }
 
+static LetStmt *
+_copyLetStmt(const LetStmt * from)
+{
+	LetStmt    *newnode = makeNode(LetStmt);
+
+	COPY_NODE_FIELD(target);
+	COPY_NODE_FIELD(query);
+	COPY_SCALAR_FIELD(set_default);
+	COPY_SCALAR_FIELD(plpgsql_mode);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 static SetOperationStmt *
 _copySetOperationStmt(const SetOperationStmt *from)
 {
@@ -6077,6 +6091,9 @@ copyObjectImpl(const void *from)
 		case T_SelectStmt:
 			retval = _copySelectStmt(from);
 			break;
+		case T_LetStmt:
+			retval = _copyLetStmt(from);
+			break;
 		case T_SetOperationStmt:
 			retval = _copySetOperationStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 94e3f42750..8c1dc02664 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1452,6 +1452,17 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	return true;
 }
 
+static bool
+_equalLetStmt(const LetStmt * a, const LetStmt * b)
+{
+	COMPARE_NODE_FIELD(target);
+	COMPARE_NODE_FIELD(query);
+	COMPARE_SCALAR_FIELD(set_default);
+	COMPARE_SCALAR_FIELD(plpgsql_mode);
+
+	return true;
+}
+
 static bool
 _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 {
@@ -3878,6 +3889,9 @@ equal(const void *a, const void *b)
 		case T_SelectStmt:
 			retval = _equalSelectStmt(a, b);
 			break;
+		case T_LetStmt:
+			retval = _equalLetStmt(a, b);
+			break;
 		case T_SetOperationStmt:
 			retval = _equalSetOperationStmt(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3b3ef3a4cd..d754e59486 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -4105,6 +4105,16 @@ raw_expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_LetStmt:
+			{
+				LetStmt *stmt = (LetStmt *) node;
+
+				if (walker(stmt->target, context))
+					return true;
+				if (walker(stmt->query, context))
+					return true;
+			}
+			break;
 		case T_PLAssignStmt:
 			{
 				PLAssignStmt *stmt = (PLAssignStmt *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 040aacbba6..a33bf10fc1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3037,6 +3037,18 @@ _outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outLetStmt(StringInfo str, const LetStmt * node)
+{
+	WRITE_NODE_TYPE("LET");
+
+	WRITE_NODE_FIELD(target);
+	WRITE_NODE_FIELD(query);
+	WRITE_BOOL_FIELD(set_default);
+	WRITE_BOOL_FIELD(plpgsql_mode);
+	WRITE_LOCATION_FIELD(location);
+}
+
 static void
 _outFuncCall(StringInfo str, const FuncCall *node)
 {
@@ -3217,6 +3229,7 @@ _outQuery(StringInfo str, const Query *node)
 			case T_IndexStmt:
 			case T_NotifyStmt:
 			case T_DeclareCursorStmt:
+			case T_LetStmt:
 				WRITE_NODE_FIELD(utilityStmt);
 				break;
 			default:
@@ -4579,6 +4592,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PLAssignStmt:
 				_outPLAssignStmt(str, obj);
 				break;
+			case T_LetStmt:
+				_outLetStmt(str, obj);
+				break;
 			case T_ColumnDef:
 				_outColumnDef(str, obj);
 				break;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index d690d102b0..8b1b5299f9 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,8 +25,11 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -51,6 +54,7 @@
 #include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/queryjumble.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
@@ -84,6 +88,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
@@ -327,6 +333,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:
@@ -400,6 +407,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
 									   (CallStmt *) parseTree);
 			break;
 
+		case T_LetStmt:
+			result = transformLetStmt(pstate,
+									  (LetStmt *) parseTree);
+			break;
+
 		default:
 
 			/*
@@ -442,6 +454,7 @@ analyze_requires_snapshot(RawStmt *parseTree)
 		case T_MergeStmt:
 		case T_SelectStmt:
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			result = true;
 			break;
 
@@ -1628,12 +1641,282 @@ 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;
+	List	   *indirection = NIL;
+	ListCell   *lc;
+	Query	   *selectQuery;
+	int			i = 0;
+	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;
+
+	/* There can't be any outer WITH to worry about */
+	Assert(pstate->p_ctenamespace == NIL);
+
+	names = NamesFromList(stmt->target);
+
+	varid = IdentifyVariable(names, &attrname, true, &not_unique);
+	if (not_unique)
+		ereport(ERROR,
+				(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+				 errmsg("target \"%s\" of LET command is ambiguous",
+						NameListToString(names)),
+				 parser_errposition(pstate, stmt->location)));
+
+	if (!OidIsValid(varid))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("session variable \"%s\" doesn't exist",
+						NameListToString(names)),
+				 parser_errposition(pstate, stmt->location)));
+
+	indirection_start = list_length(names) - (attrname ? 1 : 0);
+	indirection = list_copy_tail(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)));
+
+	if (stmt->set_default && attrname != NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("only session variable (without attribute specification) can be set to default"),
+				 parser_errposition(pstate, stmt->location)));
+
+	aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, NameListToString(names));
+
+	pstate->p_expr_kind = EXPR_KIND_LET_TARGET;
+
+	/*
+	 * The LET statements suppports two syntaxes: LET var = expr and
+	 * LET var = DEFAULT. In first case, the expression is of SelectStmt
+	 * node and it is transformated to query (SELECT) by usual way.
+	 * Second syntax should be transformed differently. It is more cleaner
+	 * do this transformation here (like special case), because we don't
+	 * need to enhance SelectStmt about fields necessary for this
+	 * transformation somwehere in SelectStmt transformation. Isn't to
+	 * necessary to uglify the SelectStmt transformation. This is special
+	 * case of LET statement, not SelectStmt.
+	 */
+	if (stmt->set_default)
+	{
+		selectQuery = makeNode(Query);
+		selectQuery->commandType = CMD_SELECT;
+
+		/*
+		 * ResTarget(SetToDefault) -> TargetEntry(expr(SetToDefault))
+		 * The SetToDefault is replaced by defexpr by rewriter in
+		 * RewriteQuery function.
+		 */
+		selectQuery->targetList = transformTargetList(pstate,
+													  ((SelectStmt *) stmt->query)->targetList,
+													  EXPR_KIND_LET_TARGET);
+	}
+	else
+		selectQuery = transformStmt(pstate, stmt->query);
+
+	/* The grammar should have produced a SELECT */
+	if (!IsA(selectQuery, Query) ||
+		selectQuery->commandType != CMD_SELECT)
+		elog(ERROR, "unexpected non-SELECT command in LET command");
+
+	/*----------
+	 * Generate an expression list for the LET that selects all the
+	 * non-resjunk columns from the subquery.
+	 *----------
+	 */
+	exprList = NIL;
+	foreach(lc, selectQuery->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+		if (tle->resjunk)
+			continue;
+
+		exprList = lappend(exprList, tle->expr);
+	}
+
+	/* don't allow multicolumn result */
+	if (list_length(exprList) != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("expression is not scalar value"),
+				 parser_errposition(pstate,
+									exprLocation((Node *) exprList))));
+
+	exprListCoer = NIL;
+
+	foreach(lc, exprList)
+	{
+		Expr	   *expr = (Expr *) lfirst(lc);
+		Expr	   *coerced_expr;
+		Param	   *param;
+		Oid			exprtypid;
+
+		if (IsA(expr, SetToDefault))
+		{
+			SetToDefault *def = (SetToDefault *) expr;
+
+			def->typeId = typid;
+			def->typeMod = typmod;
+			def->collation = collid;
+		}
+		else if (IsA(expr, Const) && ((Const *) expr)->constisnull)
+		{
+			/* use known type for NULL value */
+			expr = (Expr *) makeNullConst(typid, typmod, collid);
+		}
+
+		/* now we can read type of expression */
+		exprtypid = exprType((Node *) expr);
+
+		param = makeNode(Param);
+		param->paramkind = PARAM_VARIABLE;
+		param->paramvarid = varid;
+		param->paramtype = typid;
+		param->paramtypmod = typmod;
+
+		if (indirection != NULL)
+		{
+			bool		targetIsArray;
+			char	   *targetName;
+
+			targetName = 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,
+											   indirection,
+											   list_head(indirection),
+											   (Node *) expr,
+											   COERCION_PLPGSQL,
+											   stmt->location);
+		}
+		else
+			coerced_expr = (Expr *)
+				coerce_to_target_type(pstate,
+									  (Node *) expr,
+									  exprtypid,
+									  typid, typmod,
+									  COERCION_ASSIGNMENT,
+									  COERCE_IMPLICIT_CAST,
+									  stmt->location);
+
+		if (coerced_expr == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("variable \"%s.%s\" is of type %s,"
+							" but expression is of type %s",
+							get_namespace_name(get_session_variable_namespace(varid)),
+							get_session_variable_name(varid),
+							format_type_be(typid),
+							format_type_be(exprtypid)),
+					 errhint("You will need to rewrite or cast the expression."),
+					 parser_errposition(pstate, exprLocation((Node *) expr))));
+
+		exprListCoer = lappend(exprListCoer, coerced_expr);
+	}
+
+	/*
+	 * Generate query's target list using the computed list of expressions.
+	 */
+	query = makeNode(Query);
+	query->commandType = CMD_SELECT;
+
+	foreach(lc, exprListCoer)
+	{
+		Expr	   *expr = (Expr *) lfirst(lc);
+		TargetEntry *tle;
+
+		tle = makeTargetEntry(expr,
+							  i + 1,
+							  FigureColname((Node *) expr),
+							  false);
+		query->targetList = lappend(query->targetList, tle);
+	}
+
+	/* done building the range table and jointree */
+	query->rtable = pstate->p_rtable;
+	query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+	query->hasTargetSRFs = pstate->p_hasTargetSRFs;
+	query->hasSubLinks = pstate->p_hasSubLinks;
+	query->hasSessionVariables = pstate->p_hasSessionVariables;
+
+	/* This is top query */
+	query->canSetTag = true;
+
+	/*
+	 * Save target session variable id. This value is used
+	 * multiple times: by query rewriter (for getting related
+	 * defexpr), by planner (for acquiring AccessShareLock on
+	 * target variable), and by executor (we need to know
+	 * target variable id).
+	 */
+	query->resultVariable = varid;
+
+	assign_query_collations(pstate, query);
+
+	stmt->query = (Node *) query;
+
+	/*
+	 * When statement is executed as a PlpgSQL LET statement, then we should
+	 * return the query because we don't want to use a utilityStmt wrapper node.
+	 */
+	if (stmt->plpgsql_mode)
+		return query;
+
+	/* represent the command as a utility Query */
+	result = makeNode(Query);
+	result->commandType = CMD_UTILITY;
+	result->utilityStmt = (Node *) stmt;
+
+	return result;
+}
+
 /*
  * transformSetOperationStmt -
  *	  transforms a set-operations tree
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 73904daf8d..014aa46d03 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -316,7 +316,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
@@ -815,7 +816,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY KEYS KEEP
 
 	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 METHOD
@@ -1153,6 +1154,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -11995,6 +11997,7 @@ ExplainableStmt:
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -12025,6 +12028,7 @@ PreparableStmt:
 			| UpdateStmt
 			| DeleteStmt
 			| MergeStmt						/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -12612,6 +12616,49 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY:
+ *				LET STATEMENTS
+ *
+ *****************************************************************************/
+LetStmt:	LET let_target '=' a_expr
+				{
+					LetStmt	   *n = makeNode(LetStmt);
+					SelectStmt *select;
+					ResTarget  *res;
+
+					n->target = $2;
+
+					select = makeNode(SelectStmt);
+					res = makeNode(ResTarget);
+
+					/* Create target list for implicit query */
+					res->name = NULL;
+					res->indirection = NIL;
+					res->val = (Node *) $4;
+					res->location = @4;
+
+					select->targetList = list_make1(res);
+					n->query = (Node *) select;
+
+					n->set_default = IsA($4, SetToDefault);
+
+					n->location = @2;
+					$$ = (Node *) n;
+				}
+		;
+
+let_target:
+			ColId opt_indirection
+				{
+					$$ = list_make1(makeString($1));
+					if ($2)
+						  $$ = list_concat($$,
+										   check_indirection($2, yyscanner));
+				}
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -17939,6 +17986,7 @@ unreserved_keyword:
 			| LARGE_P
 			| LAST_P
 			| LEAKPROOF
+			| LET
 			| LEVEL
 			| LISTEN
 			| LOAD
@@ -18545,6 +18593,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 dbe630a51b..4365692735 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -348,6 +348,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			Assert(false);		/* can't happen */
 			break;
 		case EXPR_KIND_OTHER:
+		case EXPR_KIND_LET_TARGET:
 
 			/*
 			 * Accept aggregate/grouping here; caller must throw error if
@@ -955,6 +956,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_LET_TARGET:
+			err = _("window functions are not allowed in LET statement");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 046884fe77..73d5ef27bc 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -577,6 +577,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
 		case EXPR_KIND_VARIABLE_DEFAULT:
+		case EXPR_KIND_LET_TARGET:
 
 			/* okay */
 			break;
@@ -1977,6 +1978,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:
@@ -3344,6 +3346,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index c189bd1276..c7b99e4991 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2659,6 +2659,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_LET_TARGET:
+			err = _("set-returning functions are not allowed in CALL arguments");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2a1d44b813..9add612d7e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -89,7 +89,9 @@ transformTargetEntry(ParseState *pstate,
 		 * through unmodified.  (transformExpr will throw the appropriate
 		 * error if we're disallowing it.)
 		 */
-		if (exprKind == EXPR_KIND_UPDATE_SOURCE && IsA(node, SetToDefault))
+		if ((exprKind == EXPR_KIND_UPDATE_SOURCE ||
+			 exprKind == EXPR_KIND_LET_TARGET)
+			&& IsA(node, SetToDefault))
 			expr = node;
 		else
 			expr = transformExpr(pstate, node, exprKind);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 29ae27e5e3..08ed6ed7f9 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -25,6 +25,7 @@
 #include "access/table.h"
 #include "catalog/dependency.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/trigger.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -3674,6 +3675,39 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		}
 	}
 
+	/*
+	 * Rewrite SetToDefault by default expression of Let statement.
+	 */
+	if (event == CMD_SELECT && OidIsValid(parsetree->resultVariable))
+	{
+		Oid			resultVariable = parsetree->resultVariable;
+
+		if (list_length(parsetree->targetList) == 1 &&
+			IsA(((TargetEntry *) linitial(parsetree->targetList))->expr, SetToDefault))
+		{
+			Variable		var;
+			TargetEntry	   *tle;
+			TargetEntry	   *newtle;
+			Expr		   *defexpr;
+
+			/* Read session variable metadata with defexpr */
+			initVariable(&var, resultVariable, false);
+
+			if (var.has_defexpr)
+				defexpr = (Expr *) var.defexpr;
+			else
+				defexpr = (Expr *) makeNullConst(var.typid, var.typmod, var.collation);
+
+			tle = (TargetEntry *) linitial(parsetree->targetList);
+			newtle = makeTargetEntry(defexpr,
+									  tle->resno,
+									  pstrdup(tle->resname),
+									  false);
+
+			parsetree->targetList = list_make1(newtle);
+		}
+	}
+
 	/*
 	 * If the statement is an insert, update, delete, or merge, adjust its
 	 * targetlist as needed, and then fire INSERT/UPDATE/DELETE rules on it.
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index a233dd4758..a8351c4499 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -213,10 +213,10 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	}
 
 	/*
-	 * For SELECT, UPDATE and DELETE, add security quals to enforce the USING
-	 * policies.  These security quals control access to existing table rows.
-	 * Restrictive policies are combined together using AND, and permissive
-	 * policies are combined together using OR.
+	 * For SELECT, LET, UPDATE and DELETE, add security quals to enforce the
+	 * USING policies.  These security quals control access to existing table
+	 * rows. Restrictive policies are combined together using AND, and
+	 * permissive policies are combined together using OR.
 	 */
 
 	get_policies_for_relation(rel, commandType, user_id, &permissive_policies,
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index c952cbea8b..9c5339804b 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -37,6 +37,7 @@
 #include "executor/functions.h"
 #include "executor/tqueue.h"
 #include "executor/tstoreReceiver.h"
+#include "executor/svariableReceiver.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "utils/portal.h"
@@ -152,6 +153,9 @@ CreateDestReceiver(CommandDest dest)
 
 		case DestTupleQueue:
 			return CreateTupleQueueDestReceiver(NULL);
+
+		case DestVariable:
+			return CreateVariableDestReceiver();
 	}
 
 	/* should never get here */
@@ -207,6 +211,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
@@ -252,6 +257,7 @@ NullCommand(CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
@@ -295,6 +301,7 @@ ReadyForQuery(CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 030de80295..926ef1f3c6 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -242,6 +242,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 				 * Commands inside the DO block or the called procedure might
@@ -1071,6 +1072,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,
@@ -2197,6 +2203,10 @@ UtilityContainsQuery(Node *parsetree)
 				return UtilityContainsQuery(qry->utilityStmt);
 			return qry;
 
+		case T_LetStmt:
+			qry = castNode(Query, ((LetStmt *) parsetree)->query);
+			return qry;
+
 		default:
 			return NULL;
 	}
@@ -2395,6 +2405,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:
 			{
@@ -3280,6 +3294,7 @@ GetCommandLogLevel(Node *parsetree)
 			break;
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 74daf13a3f..7a3f678f06 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -135,6 +135,7 @@ InitPlanCache(void)
 	CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
 	CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheSysCallback, (Datum) 0);
 	CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheSysCallback, (Datum) 0);
+	CacheRegisterSyscacheCallback(VARIABLEOID, PlanCacheObjectCallback, (Datum) 0);
 }
 
 /*
@@ -1868,12 +1869,24 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
 	 * Recurse into sublink subqueries, too.  But we already did the ones in
 	 * the rtable and cteList.
 	 */
-	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);
+	}
 }
 
 /*
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 7614a5dee2..89466ff824 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -34,6 +34,9 @@ extern Datum GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expect
 extern void SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid);
 extern void SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typid);
 
+extern void ExecuteLetStmt(ParseState *pstate, LetStmt *stmt, ParamListInfo params,
+						    QueryEnvironment *queryEnv, QueryCompletion *qc);
+
 extern void RegisterOnCommitDropSessionVariable(Oid varid);
 
 extern void AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit);
diff --git a/src/include/executor/svariableReceiver.h b/src/include/executor/svariableReceiver.h
new file mode 100644
index 0000000000..1d823a42a8
--- /dev/null
+++ b/src/include/executor/svariableReceiver.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.h
+ *	  prototypes for svariableReceiver.c
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/svariableReceiver.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SVARIABLE_RECEIVER_H
+#define SVARIABLE_RECEIVER_H
+
+#include "tcop/dest.h"
+
+
+extern DestReceiver *CreateVariableDestReceiver(void);
+
+extern void SetVariableDestReceiverParams(DestReceiver *self, Oid varid);
+
+#endif							/* SVARIABLE_RECEIVER_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d49bc66fc0..4387736621 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -447,6 +447,7 @@ typedef enum NodeTag
 	T_AlterCollationStmt,
 	T_CallStmt,
 	T_AlterStatsStmt,
+	T_LetStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 74dbc5a204..c53b36c0ee 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1977,6 +1977,21 @@ typedef struct MergeStmt
 	WithClause *withClause;		/* WITH clause */
 } MergeStmt;
 
+/* ----------------------
+ *		Let Statement
+ * ----------------------
+ */
+typedef struct LetStmt
+{
+	NodeTag		type;
+	List	   *target;			/* target variable */
+	Node	   *query;			/* source expression */
+	bool		set_default;	/* true, when set to DEFAULt is wanted */
+	bool		plpgsql_mode;	/* true, when command will be executed (parsed)
+								 * by plpgsql runtime */
+	int			location;
+}			LetStmt;
+
 /* ----------------------
  *		Select Statement
  *
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 2c536aab9a..540dae00b8 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -44,7 +44,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) */
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index d7cfc9a6f3..40d4c63e05 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -255,6 +255,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 59da340265..28d52b51a7 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -81,7 +81,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_COPY_WHERE,		/* WHERE condition in COPY FROM */
 	EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */
 	EXPR_KIND_CYCLE_MARK,		/* cycle mark value */
-	EXPR_KIND_VARIABLE_DEFAULT	/* default value for session variable */
+	EXPR_KIND_VARIABLE_DEFAULT,	/* default value for session variable */
+	EXPR_KIND_LET_TARGET		/* LET assignment (should be same like UPDATE) */
 } ParseExprKind;

diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index e76bec9fad..59f144bde1 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 3c3eabae67..7f18e07915 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -97,7 +97,8 @@ typedef enum
 	DestCopyOut,				/* results sent to COPY TO code */
 	DestSQLFunction,			/* results sent to SQL-language func mgr */
 	DestTransientRel,			/* results sent to transient relation */
-	DestTupleQueue				/* results sent to tuple queue */
+	DestTupleQueue,				/* results sent to tuple queue */
+	DestVariable				/* results sents to session variable */
 } CommandDest;
 
 /* ----------------
-- 
2.36.1

From c4ba103232ddee1c86ec381e00b24e19c49b311e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/11] session variables

Implementation storage and access routines. Session variables are stored
in session memory inside dedicated hash table. Two levels of an access
are implemented: API level and SQL level. Both levels are implemented
in this commit.

The most difficult part is cleaning (reset) of session variable. The
content of session variable should be cleaned when related session
variable is removed from system catalog. But queue of sinval messages
can be truncated, or current transaction state can disallow an access
to system catalog, so cleaning can be postponed.
---
 src/backend/access/transam/xact.c       |   2 +-
 src/backend/catalog/dependency.c        |   5 +
 src/backend/commands/sessionvariable.c  | 864 +++++++++++++++++++++++-
 src/backend/executor/execExpr.c         |  75 ++
 src/backend/executor/execExprInterp.c   |  11 +
 src/backend/executor/execMain.c         |  56 ++
 src/backend/executor/execParallel.c     | 148 +++-
 src/backend/jit/llvm/llvmjit_expr.c     |   6 +
 src/backend/nodes/copyfuncs.c           |   4 +
 src/backend/nodes/equalfuncs.c          |   3 +
 src/backend/nodes/outfuncs.c            |   6 +
 src/backend/nodes/readfuncs.c           |   4 +
 src/backend/optimizer/plan/planner.c    |   8 +
 src/backend/optimizer/plan/setrefs.c    | 118 +++-
 src/backend/optimizer/util/clauses.c    |  75 +-
 src/backend/parser/analyze.c            |  13 +-
 src/backend/parser/parse_expr.c         | 186 ++++-
 src/backend/tcop/pquery.c               |   3 +
 src/backend/utils/adt/ruleutils.c       |  46 ++
 src/backend/utils/cache/plancache.c     |  19 +-
 src/backend/utils/fmgr/fmgr.c           |  10 +-
 src/backend/utils/misc/guc.c            |  12 +
 src/include/commands/session_variable.h |   7 +
 src/include/executor/execExpr.h         |  10 +
 src/include/executor/execdesc.h         |   4 +
 src/include/nodes/execnodes.h           |  19 +
 src/include/nodes/parsenodes.h          |   2 +
 src/include/nodes/pathnodes.h           |   3 +
 src/include/nodes/plannodes.h           |   2 +
 src/include/nodes/primnodes.h           |  12 +-
 src/include/optimizer/planmain.h        |   2 +
 src/include/parser/parse_expr.h         |   1 +
 src/include/parser/parse_node.h         |   1 +
 33 files changed, 1704 insertions(+), 33 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 21cc02a199..c22c706ca3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2214,7 +2214,7 @@ CommitTransaction(void)
 	 */
 	smgrDoPendingSyncs(true, is_parallel_worker);
 
-	/* Let ON COMMIT DROP */
+	/* Let ON COMMIT DROP or ON TRANSACTION END */
 	AtPreEOXact_SessionVariable_on_xact_actions(true);
 
 	/* close large objects before lower-level cleanup */
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d5d2a294c3..8ffcaabc46 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1889,6 +1889,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(OCLASS_VARIABLE, param->paramvarid, 0,
+							   context->addrs);
+
 		/* A parameter must depend on the parameter's datatype */
 		add_object_address(OCLASS_TYPE, param->paramtype, 0,
 						   context->addrs);
diff --git a/src/backend/commands/sessionvariable.c b/src/backend/commands/sessionvariable.c
index 213a2b9c4c..e26903d135 100644
--- a/src/backend/commands/sessionvariable.c
+++ b/src/backend/commands/sessionvariable.c
@@ -28,17 +28,100 @@
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "storage/lmgr.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 life cycle of temporary session variable can be
+ * Values of session variables are stored in local memory, in
+ * sessionvars hash table. This local memory has to be cleaned,
+ * when:
+ * - a session variable is dropped by the current or another
+ * session
+ * - a user enforce it by using the ON TRANSACTION END RESET
+ * clause. The life cycle of temporary session variable can be
  * limmited by using clause ON COMMIT DROP.
+ *
+ * Although session variables are not transactional, we don't want
+ * (and cannot) clean the entries in sessionvars hash table
+ * immediately, when we get the sinval message. Session variables
+ * usage is protected by heavyweight locks, so there is no risk of
+ * unwanted invalidation due to a drop variable done in a
+ * different session. But it's still possible to drop the session
+ * variable in the current session. Without delayed cleanup we
+ * would lose the value if the drop command is done in a sub
+ * transaction that is then rollbacked. The check of session
+ * variable validity requires access to system catalog, so it can
+ * only be done in transaction state).
+ *
+ * This is why memory cleanup (session variable reset) is
+ * postponed to the end of transaction, and why we need to hold
+ * some actions lists. We have to hold two separate action lists:
+ * one for dropping the session variable from system catalog, and
+ * another one for resetting its value. Using two lists simplifies
+ * implementation. The list with dropped variables should be thrown on
+ * the end of any transaction. Second list with variables that should
+ * be resetted or rechecked (and possibly resetted) should be
+ * preserved to end of some next committed transaction. More, the
+ * reset actions should be stored in second list, bacause processing
+ * of the drop action makes new reset action. But when we iterate
+ * over any list, we cannot to append to this list new fields. So
+ * second list is required.
+ *
+ * We want to support the possibility of resetting a session
+ * variable at the end of transaction, with the ON TRANSACTION END
+ * RESET option. This ensures the initial state of session
+ * variables at the begin of each transaction.  The reset is
+ * implemented as a removal of the session variable from
+ * sessionvars hash table.  This enforce full initialization in
+ * the next usage.  Careful though, this is not same as dropping
+ * the session variable.
+ *
+ * Another functionality is dropping temporary session variable
+ * with the option ON COMMIT DROP.
+ *
+ * There are two different ways how to do final access to session
+ * variables: buffered (indirect) or direct. Buffered access is used
+ * in queries, where we have to ensure an stability of passed values
+ * (then the session variable has same behaviour like external query
+ * parametes, what is consistent with using PL/pgSQL's variables in
+ * embdded queries in PL/pgSQL).
+ *
+ * This is implemented by using aux buffer (an array) that holds an
+ * copy of values of used (in query) session variables. In the final
+ * end, the values from this array is passed as constant (EEOP_CONST).
+ *
+ * Direct access is used by simple expression evaluation (PLpgSQL).
+ * In this case we don't need to ensure the stability of passed
+ * values, and maintaining the buffer with copy of values of session
+ * variables can be useless overhead. In this case we just read the
+ * value of session variable directly (EEOP_PARAM_VARIABLE). This
+ * strategy remove an necessity to modify related PL/pgSQL code to
+ * supports session variables (the reading of session variables is
+ * fully transparent for PL/pgSQL).
+ *
+ * The session variable hold a value across transactions. There
+ * can are two possible problems that should be solved. Sinval
+ * check can be delayed after oid overflow. Second issue is possible
+ * change of format of stored value. So before returning of any
+ * value of session variable, we have to check if returned value
+ * is safe. If not, we will mark the session variable as broken,
+ * and any read of this variable until end of session or rewriting
+ * raises an exception.
  */
 typedef enum SVariableXActAction
 {
 	SVAR_ON_COMMIT_DROP,		/* used for ON COMMIT DROP */
+	SVAR_ON_COMMIT_RESET,		/* used for DROP VARIABLE */
+	SVAR_RESET,					/* used for ON TRANSACTION END RESET */
+	SVAR_RECHECK				/* do appropriate cleanup if the session *
+								 * variable doesn't exist */
 } SVariableXActAction;
 
 typedef struct SVariableXActActionItem
@@ -55,12 +138,618 @@ typedef struct SVariableXActActionItem
 	SubTransactionId deleting_subid;
 }  SVariableXActActionItem;
 
-/* List holds fields of SVariableXActActionItem type */
+/* Both lists hold fields of SVariableXActActionItem type */
 static List *xact_drop_actions = NIL;
+static List *xact_reset_recheck_actions = NIL;
+
+typedef struct SVariableData
+{
+	Oid			varid;			/* pg_variable OID of this sequence (hash key) */
+	Oid			typid;			/* OID of the data type */
+	int16		typlen;
+	bool		typbyval;
+	bool		isnull;
+	bool		freeval;
+	Datum		value;
+
+	bool		is_rowtype;		/* true when variable is composite */
+	bool		is_not_null;	/* don't allow null values */
+	bool		is_immutable;	/* true when variable is immutable */
+	bool		has_defexpr;	/* true when variable has a default value */
+
+	bool		is_valid;		/* true when variable was successfully
+								 * initialized */
+
+	bool		is_broken;		/* true when we cannot to return a value
+								   safely */
+
+	uint32		hashvalue;
+}			SVariableData;
+
+typedef SVariableData * SVariable;
+
+static HTAB *sessionvars = NULL;	/* hash table for session variables */
+static MemoryContext SVariableMemoryContext = NULL;
+
+static bool first_time = true;
+
+static bool sync_sessionvars_all_is_required = false;
+static bool recheck_sessionvars_is_required = false;
 
 static void register_session_variable_xact_action(Oid varid, SVariableXActAction action);
 static void unregister_session_variable_xact_action(Oid varid, SVariableXActAction action);
 
+/*
+ * Releases stored data from session variable, but preserve the hash entry
+ * in sessionvars.
+ */
+static void
+free_session_variable_value(SVariable svar)
+{
+	if (svar->freeval)
+		pfree(DatumGetPointer(svar->value));
+
+	/* Clean current value */
+	svar->value = (Datum) 0;
+	svar->isnull = true;
+	svar->freeval = false;
+
+	/*
+	 * We can mark this session variable as valid when
+	 * it has not default expression, and when null is
+	 * allowed. When it has defexpr, then the content
+	 * will be valid after an assignment or defexp evaluation.
+	 */
+	svar->is_valid = !svar->has_defexpr && !svar->is_not_null;
+}
+
+/*
+ * Release the variable defined by varid from sessionvars
+ * hashtab.
+ */
+static void
+remove_session_variable(SVariable svar)
+{
+	free_session_variable_value(svar);
+
+	if (hash_search(sessionvars,
+					(void *) &svar->varid,
+					HASH_REMOVE,
+					NULL) == NULL)
+		elog(DEBUG1, "hash table corrupted");
+}
+
+/*
+ * Release the variable defined by varid from sessionvars
+ * hashtab.
+ */
+static void
+remove_session_variable_by_id(Oid varid)
+{
+	SVariable svar;
+	bool		found;
+
+	if (!sessionvars)
+		return;
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+										HASH_FIND, &found);
+	if (found)
+		remove_session_variable(svar);
+}
+
+/*
+ * Callback function for session variable invalidation.
+ *
+ * Register SVAR_RECHECK actions on appropriate currently cached
+ * session variables. Those will be processed later, rechecking against the
+ * catalog to detect dropped session variable.
+ */
+static void
+pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
+{
+	/*
+	 * There is no guarantee of sessionvars being initialized, even when
+	 * receiving an invalidation callback, as DISCARD [ ALL | VARIABLES ]
+	 * destroys the hash table entirely.
+	 */
+	if (!sessionvars)
+		return;
+
+	/*
+	 * When we know so all session variables should be synchronized,
+	 * then is useless to continue;
+	 */
+	if (sync_sessionvars_all_is_required)
+		return;
+
+	/*
+	 * Since we can't guarantee the exact session variable from its hashValue,
+	 * we have to iterate over all currently known session variables to find
+	 * the ones with the same hashValue. On second hand, this can save us
+	 * some CPU later, because we don't need to check any used
+	 * session variable (by current session) against system catalog.
+	 */
+	if (hashvalue != 0)
+	{
+		HASH_SEQ_STATUS status;
+		SVariable svar;
+
+		hash_seq_init(&status, sessionvars);
+
+		while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+		{
+			if (svar->hashvalue == hashvalue)
+			{
+				register_session_variable_xact_action(svar->varid, SVAR_RECHECK);
+				recheck_sessionvars_is_required = true;
+			}
+
+			/*
+			 * although it there is low probability, we have to iterate
+			 * over all actively used session variables, because hashvalue
+			 * is not unique identifier.
+			 */
+		}
+	}
+	else
+		sync_sessionvars_all_is_required = true;
+}
+
+/*
+ * When we need to recheck all session variables, then
+ * the most effective method is seq scan over hash tab.
+ * We need to check synchronization request before any
+ * read to be sure so returned data are valid.
+ */
+static void
+sync_sessionvars_all()
+{
+	HASH_SEQ_STATUS status;
+	SVariable svar;
+	ListCell   *l;
+
+	if (!sync_sessionvars_all_is_required &&
+		!recheck_sessionvars_is_required)
+		return;
+
+	/*
+	 * sessionvars is null after DISCARD VARIABLES.
+	 * When we are sure, so there are not any
+	 * active session variable in this session, we
+	 * can reset sync_sessionvars_all flag.
+	 */
+	if (!sessionvars)
+	{
+		sync_sessionvars_all_is_required = false;
+		recheck_sessionvars_is_required = false;
+		return;
+	}
+
+	/*
+	 * This routine is called before any reading. So the
+	 * session should be in transaction state. This is required
+	 * for access to system catalog.
+	 */
+	Assert(IsTransactionState());
+
+	if (sync_sessionvars_all_is_required)
+	{
+		hash_seq_init(&status, sessionvars);
+
+		while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+		{
+			HeapTuple	tp;
+
+			tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+			if (HeapTupleIsValid(tp))
+				ReleaseSysCache(tp);
+			else
+				remove_session_variable(svar);
+		}
+
+		sync_sessionvars_all_is_required = false;
+
+		/* clean list of items that should be rechecked */
+		if (recheck_sessionvars_is_required)
+		{
+			foreach(l, xact_reset_recheck_actions)
+			{
+				SVariableXActActionItem *xact_ai =
+								(SVariableXActActionItem *) lfirst(l);
+
+				if (xact_ai->action == SVAR_RECHECK)
+				{
+					xact_reset_recheck_actions = foreach_delete_current(xact_reset_recheck_actions, l);
+					pfree(xact_ai);
+				}
+			}
+
+			recheck_sessionvars_is_required = false;
+		}
+	}
+	else if (recheck_sessionvars_is_required)
+	{
+		/* recheck_sessionvars_is_required */
+		foreach(l, xact_reset_recheck_actions)
+		{
+			SVariableXActActionItem *xact_ai =
+								(SVariableXActActionItem *) lfirst(l);
+
+			if (xact_ai->action == SVAR_RECHECK)
+			{
+				bool		found;
+
+				svar = (SVariable) hash_search(sessionvars, &xact_ai->varid,
+											HASH_FIND, &found);
+
+				if (found)
+				{
+					HeapTuple	tp;
+
+					tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+					if (HeapTupleIsValid(tp))
+						ReleaseSysCache(tp);
+					else
+						remove_session_variable(svar);
+				}
+
+				xact_reset_recheck_actions = foreach_delete_current(xact_reset_recheck_actions, l);
+				pfree(xact_ai);
+			}
+		}
+
+		recheck_sessionvars_is_required = false;
+	}
+}
+
+/*
+ * Create the hash table for storing session variables
+ */
+static void
+create_sessionvars_hashtable(void)
+{
+	HASHCTL		ctl;
+
+	/* set callbacks */
+	if (first_time)
+	{
+		/* Read sinval messages */
+		CacheRegisterSyscacheCallback(VARIABLEOID,
+									  pg_variable_cache_callback,
+									  (Datum) 0);
+		/* needs its own long lived memory context */
+		SVariableMemoryContext =
+			AllocSetContextCreate(TopMemoryContext,
+								  "session variables",
+								  ALLOCSET_START_SMALL_SIZES);
+
+		first_time = false;
+	}
+
+	Assert(SVariableMemoryContext);
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(SVariableData);
+	ctl.hcxt = SVariableMemoryContext;
+
+	Assert(sessionvars == NULL);
+
+	sessionvars = hash_create("Session variables", 64, &ctl,
+								   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+/*
+ * Assign some content to the session variable. It's copied to
+ * SVariableMemoryContext if necessary.
+ *
+ * init_mode is true, when the value of session variable is being initialized
+ * by default expression or by null. Only in this moment we can allow to
+ * modify immutable variables with default expression.
+ */
+static void
+set_session_variable(SVariable svar, Datum value,
+					 bool isnull, Oid typid,
+					 bool init_mode)
+{
+	MemoryContext oldcxt;
+	Datum		newval = value;
+
+	/* Don't allow assignment of null to NOT NULL variable */
+	if (isnull && svar->is_not_null)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
+
+	if (svar->typid != typid)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type \"%s\" of assigned value is different than type \"%s\" of session variable \"%s.%s\"",
+						format_type_be(typid),
+						format_type_be(svar->typid),
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
+
+	/*
+	 * Don't allow updating of immutable session variable that has assigned
+	 * not null value or has default expression (and then the value should be
+	 * result of default expression always). Don't do this check, when variable
+	 * is being initialized.
+	 */
+	if (!init_mode &&
+		(svar->is_immutable && (svar->is_valid || svar->has_defexpr)))
+		ereport(ERROR,
+				(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
+				 errmsg("session variable \"%s.%s\" is declared IMMUTABLE",
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
+
+	/* copy value to session persistent context */
+	oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+	if (!isnull)
+		newval = datumCopy(value, svar->typbyval, svar->typlen);
+	MemoryContextSwitchTo(oldcxt);
+
+	free_session_variable_value(svar);
+
+	svar->value = newval;
+	svar->isnull = isnull;
+	svar->freeval = newval != value;
+	svar->is_valid = true;
+}
+
+/*
+ * Initialize svar from var
+ * svar - SVariable - holds value
+ * var  - Variable - holds metadata
+ */
+static void
+init_session_variable(SVariable svar, Variable *var)
+{
+	Assert(OidIsValid(var->oid));
+
+	svar->varid = var->oid;
+	svar->typid = var->typid;
+
+	get_typlenbyval(var->typid,
+					&svar->typlen,
+					&svar->typbyval);
+
+	svar->isnull = true;
+	svar->freeval = false;
+	svar->value = (Datum) 0;
+
+	svar->is_rowtype = type_is_rowtype(var->typid);
+	svar->is_not_null = var->is_not_null;
+	svar->is_immutable = var->is_immutable;
+	svar->has_defexpr = var->has_defexpr;
+
+	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
+											ObjectIdGetDatum(var->oid));
+
+	/* the value of variable is not known yet */
+	svar->is_valid = false;
+
+	if (var->eoxaction == VARIABLE_EOX_RESET ||
+			var->eoxaction == VARIABLE_EOX_DROP)
+		register_session_variable_xact_action(var->oid, SVAR_RESET);
+}
+
+/*
+ * Search the given session variable in the hash table. If it doesn't
+ * exist, then insert it (and calculate defexpr if it exists).
+ *
+ * Caller is responsible for doing permission checks.
+ *
+ * As side effect this function acquires AccessShareLock on
+ * related session variable until the end of the transaction.
+ */
+static SVariable
+prepare_variable_for_reading(Oid varid)
+{
+	SVariable svar;
+	Variable	var;
+	bool		found;
+
+	var.oid = InvalidOid;
+
+	if (!sessionvars)
+		create_sessionvars_hashtable();
+
+	/* Protect used session variable against drop until transaction end */
+	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	/* Ensure so all entries in sessionvars hash table are valid */
+	sync_sessionvars_all();
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+										HASH_ENTER, &found);
+
+	/* Return content if it is available and valid */
+	if (found && svar->is_valid)
+		return svar;
+
+	/* We need to load defexpr. */
+	initVariable(&var, varid, false);
+
+	if (!found)
+		init_session_variable(svar, &var);
+
+	/* Raise an error when we cannot initialize variable correctly */
+	if (var.is_not_null && !var.defexpr)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+						get_namespace_name(get_session_variable_namespace(varid)),
+						get_session_variable_name(varid)),
+				 errdetail("The session variable was not initialized yet.")));
+
+	if (svar->has_defexpr)
+	{
+		Datum		value = (Datum) 0;
+		bool		isnull;
+		EState	   *estate = NULL;
+		Expr	   *defexpr;
+		ExprState  *defexprs;
+		MemoryContext oldcxt;
+
+		/* Prepare default expr */
+		estate = CreateExecutorState();
+
+		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		defexpr = expression_planner((Expr *) var.defexpr);
+		defexprs = ExecInitExpr(defexpr, NULL);
+		value = ExecEvalExprSwitchContext(defexprs,
+										  GetPerTupleExprContext(estate),
+										  &isnull);
+
+
+		/* Store result before releasing Executor memory */
+		set_session_variable(svar, value, isnull, svar->typid, true);
+
+		MemoryContextSwitchTo(oldcxt);
+
+		FreeExecutorState(estate);
+	}
+	else
+		set_session_variable(svar, (Datum) 0, true, svar->typid, true);
+
+	return svar;
+}
+
+/*
+ * Store the given value in an SVariable, and cache it if not already present.
+ *
+ * Caller is responsible for doing permission checks.
+ * We try not to break the previous value, if something is wrong.
+ *
+ * As side effect this function acquires AccessShareLock on
+ * related session variable until the end of the transaction.
+ */
+void
+SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid)
+{
+	SVariable svar;
+	bool		found;
+
+	/* Protect used session variable against drop until transaction end */
+	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	if (!sessionvars)
+		create_sessionvars_hashtable();
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+										HASH_ENTER, &found);
+
+	if (!found)
+	{
+		Variable	var;
+
+		/* We don't need to know defexpr here */
+		initVariable(&var, varid, true);
+		init_session_variable(svar, &var);
+	}
+
+	set_session_variable(svar, value, isNull, typid, false);
+}
+
+/*
+ * Wrapper around SetSessionVariable after checking for correct permission.
+ */
+void
+SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typid)
+{
+	AclResult	aclresult;
+
+	/*
+	 * Is possible to write to session variable?
+	 */
+	aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));
+
+	SetSessionVariable(varid, value, isNull, typid);
+}
+
+/*
+ * Returns a copy of value of the session variable specified by varid
+ * Caller is responsible for doing permission checks.
+ */
+Datum
+CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
+{
+	SVariable svar;
+
+	svar = prepare_variable_for_reading(varid);
+	Assert(svar != NULL && svar->is_valid);
+
+	*isNull = svar->isnull;
+	*typid = svar->typid;
+
+	if (!svar->isnull)
+		return datumCopy(svar->value, svar->typbyval, svar->typlen);
+
+	return (Datum) 0;
+}
+
+/*
+ * Returns a copy of value of the session variable specified by varid
+ * with check of expected type. Like previous function, the caller
+ * is responsible for doing permission checks.
+ */
+Datum
+CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
+{
+	SVariable svar;
+
+	svar = prepare_variable_for_reading(varid);
+	Assert(svar != NULL && 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));
+
+	*isNull = svar->isnull;
+
+	if (!svar->isnull)
+		return datumCopy(svar->value, svar->typbyval, svar->typlen);
+
+	return (Datum) 0;
+}
+
+/*
+ * Returns a value of session variable identified by varid with
+ * check of expected type. Like previous function, the called
+ * is reposible for doing permission check.
+ */
+Datum
+GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
+{
+	SVariable svar;
+
+	svar = prepare_variable_for_reading(varid);
+	Assert(svar != NULL && 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));
+
+	*isNull = svar->isnull;
+
+	if (svar->isnull)
+		return (Datum) 0;
+
+	return svar->value;
+}
+
 /*
  * Routines used for manipulation with session variables from
  * SQL level
@@ -197,6 +886,53 @@ RemoveSessionVariable(Oid varid)
 	 * again at end of xact time.
 	 */
 	unregister_session_variable_xact_action(varid, SVAR_ON_COMMIT_DROP);
+
+	/*
+	 * And we want to enforce variable clearning when this transaction or
+	 * subtransaction will be committed (we don't need to wait for
+	 * sinval message). The cleaning action for one session variable
+	 * can be repeated in the action list without causing any problem,
+	 * so we don't need to ensure uniqueness. We need a different action
+	 * from RESET, because RESET is executed on any transaction end,
+	 * but we want to execute cleaning only when the current transaction
+	 * will be committed.
+	 */
+	register_session_variable_xact_action(varid, SVAR_ON_COMMIT_RESET);
+}
+
+/*
+ * Fast drop of the complete content of all session variables hash table.
+ * This is code for DISCARD VARIABLES command. This command
+ * cannot be run inside transaction, so we don't need to handle
+ * end of transaction actions.
+ */
+void
+ResetSessionVariables(void)
+{
+	/* Destroy hash table and reset related memory context */
+	if (sessionvars)
+	{
+		hash_destroy(sessionvars);
+		sessionvars = NULL;
+	}
+
+	/* Release memory allocated by session variables */
+	if (SVariableMemoryContext != NULL)
+		MemoryContextReset(SVariableMemoryContext);
+
+	/*
+	 * There are not any session variables left, so simply trim
+	 * both xact action lists.
+	 */
+	list_free_deep(xact_drop_actions);
+	xact_drop_actions = NIL;
+
+	list_free_deep(xact_reset_recheck_actions);
+	xact_reset_recheck_actions = NIL;
+
+	/* There are not variables to synchronize */
+	sync_sessionvars_all_is_required = false;
+	recheck_sessionvars_is_required = false;
 }
 
 /*
@@ -228,8 +964,10 @@ register_session_variable_xact_action(Oid varid,
 	xact_ai->creating_subid = GetCurrentSubTransactionId();
 	xact_ai->deleting_subid = InvalidSubTransactionId;
 
-	Assert(action == SVAR_ON_COMMIT_DROP);
-	xact_drop_actions = lcons(xact_ai, xact_drop_actions);
+	if (action == SVAR_ON_COMMIT_DROP)
+		xact_drop_actions = lcons(xact_ai, xact_drop_actions);
+	else
+		xact_reset_recheck_actions = lcons(xact_ai, xact_reset_recheck_actions);
 
 	MemoryContextSwitchTo(oldcxt);
 }
@@ -310,6 +1048,95 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 	 */
 	list_free_deep(xact_drop_actions);
 	xact_drop_actions = NIL;
+
+	foreach(l, xact_reset_recheck_actions)
+	{
+		SVariableXActActionItem *xact_ai =
+							(SVariableXActActionItem *) lfirst(l);
+
+		if (xact_ai->action == SVAR_RECHECK)
+		{
+			/*
+			 * We can do the recheck only when the transaction is committed as
+			 * we need to access system catalog. When transaction is
+			 * ROLLBACKed, then we have to postpone the check to next
+			 * transaction. We therefore don't check for a matching
+			 * creating_subid here.
+			 */
+			if (isCommit)
+			{
+				SVariable	svar;
+				bool		found;
+
+				svar = (SVariable) hash_search(sessionvars, &xact_ai->varid,
+											HASH_FIND, &found);
+
+				if (found)
+				{
+					HeapTuple	tp;
+
+					tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+					if (HeapTupleIsValid(tp))
+						ReleaseSysCache(tp);
+					else
+						remove_session_variable(svar);
+				}
+
+				xact_reset_recheck_actions = foreach_delete_current(xact_reset_recheck_actions, l);
+				pfree(xact_ai);
+			}
+		}
+		else
+		{
+			/*
+			 * SVAR_RESET is used for uncoditional reset of session variable on
+			 * on every transaction end. It is used for two cases:
+			 * First case is reset of content of session variable created
+			 * by command with ON TRANSACTION END RESET clause. For this
+			 * case we don't need to clean hashtab entry, but we do it, because
+			 * the session variable initialization is not expensive and it
+			 * is executed only once time per transaction. When we remove
+			 * variable from hashtab, we don't need to maintain special flag for
+			 * forcing xact action reregistration (it is done just inside
+			 * init_session_variable function.
+			 *
+			 * SVAR_RESET is used by variable created with ON COMMIT DROP clause.
+			 * We can clean session memory immediately. We can do it by
+			 * SVAR_RECHECK action too, but recheck requires isCommit=true.
+			 * Cleaning by SVAR_RESET has not this requirment.
+			 */
+			if (xact_ai->action == SVAR_RESET)
+				remove_session_variable_by_id(xact_ai->varid);
+
+			/*
+			 * When we process DROP VARIABLE statement, we create
+			 * SVAR_ON_COMMIT_RESET xact action. We want to process
+			 * this action only when related transaction is commited
+			 * (when DROP VARIABLE statement sucessfully processed).
+			 * We want to preserve variable content, when the transaction
+			 * with DROP VARAIBLE statement was reverted.
+			 */
+			else if (xact_ai->action == SVAR_ON_COMMIT_RESET &&
+					 xact_ai->deleting_subid == InvalidSubTransactionId &&
+					 isCommit)
+				remove_session_variable_by_id(xact_ai->varid);
+
+			/*
+			 * Any non SVAR_RECHECK action can now be removed.  It was either
+			 * explicitly processed, or implicitly due to some ROLLBACK action.
+			 */
+			xact_reset_recheck_actions = foreach_delete_current(xact_reset_recheck_actions, l);
+			pfree(xact_ai);
+		}
+	}
+
+	/*
+	 * Now when isCommit, then we can be sure, so all SVAR_RECHECK items are
+	 * processed.
+	 */
+	if (isCommit)
+		recheck_sessionvars_is_required = false;
 }
 
 /*
@@ -345,4 +1172,33 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 				xact_ai->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId;
 		}
 	}
+
+	/*
+	 * Reset and recheck actions - cleaning memory should be done every time
+	 * (when the variable with short life cycle was used) and then
+	 * cannot be removed from xact action list.
+	 */
+	foreach(cur_item, xact_reset_recheck_actions)
+	{
+		SVariableXActActionItem *xact_ai =
+								  (SVariableXActActionItem *) lfirst(cur_item);
+
+		if (!isCommit &&
+			xact_ai->creating_subid == mySubid &&
+			xact_ai->action == SVAR_ON_COMMIT_DROP)
+		{
+			/* cur_item must be removed */
+			xact_reset_recheck_actions =
+							foreach_delete_current(xact_reset_recheck_actions, cur_item);
+			pfree(xact_ai);
+		}
+		else
+		{
+			/* cur_item must be preserved */
+			if (xact_ai->creating_subid == mySubid)
+				xact_ai->creating_subid = parentSubid;
+			if (xact_ai->deleting_subid == mySubid)
+				xact_ai->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId;
+		}
+	}
 }
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 2831e7978b..13554c1b20 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -33,6 +33,7 @@
 #include "access/nbtree.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_type.h"
+#include "commands/session_variable.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -994,6 +995,80 @@ 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;
+
+							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;
+							}
+
+							if (es_session_variables)
+							{
+								SessionVariableValue *var;
+
+								/*
+								 * 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);
+							}
+							else
+							{
+								AclResult	aclresult;
+								Oid			varid = param->paramvarid;
+								Oid			vartype = param->paramtype;
+
+								/*
+								 * When the expression is evaluated directly
+								 * without query executor start (plpgsql simple
+								 * expr evaluation), then the array es_session_variables
+								 * is null. In this case we need to use direct
+								 * access to session variables. The values are
+								 * not protected by using copy, but it is not
+								 * problem (we don't need to emulate stability
+								 * of the value).
+								 *
+								 * In this case we should to do aclcheck, because
+								 * usual aclcheck from standard_ExecutorStart
+								 * is not executed in this case. Fortunately
+								 * it is just once per transaction.
+								 */
+								aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_READ);
+								if (aclresult != ACLCHECK_OK)
+									aclcheck_error(aclresult, OBJECT_VARIABLE,
+												   get_session_variable_name(varid));
+
+								scratch.opcode = EEOP_PARAM_VARIABLE;
+								scratch.d.vparam.varid = varid;
+								scratch.d.vparam.vartype = vartype;
+								ExprEvalPushStep(state, &scratch);
+							}
+						}
+						break;
+
 					case PARAM_EXTERN:
 
 						/*
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index eaec697bb3..34ddef2b21 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/sequence.h"
+#include "commands/session_variable.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -453,6 +454,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_PARAM_EXEC,
 		&&CASE_EEOP_PARAM_EXTERN,
 		&&CASE_EEOP_PARAM_CALLBACK,
+		&&CASE_EEOP_PARAM_VARIABLE,
 		&&CASE_EEOP_CASE_TESTVAL,
 		&&CASE_EEOP_MAKE_READONLY,
 		&&CASE_EEOP_IOCOERCE,
@@ -1090,6 +1092,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_PARAM_VARIABLE)
+		{
+			/* direct access to session variable (without buffering) */
+			*op->resvalue = GetSessionVariableWithTypeCheck(op->d.vparam.varid,
+															 op->resnull,
+															 op->d.vparam.vartype);
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_CASE_TESTVAL)
 		{
 			/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ef2fd46092..5e915cb1e7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_publication.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
+#include "commands/session_variable.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSubplan.h"
 #include "foreign/fdwapi.h"
@@ -200,6 +201,61 @@ 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->num_session_variables > 0)
+	{
+		/*
+		 * When paralel access to query parameters (including related
+		 * session variables) is required, then related session variables
+		 * are restored (deserilized) in queryDesc already. So just push
+		 * pointer of this array to executor's estate.
+		 */
+		estate->es_session_variables = queryDesc->session_variables;
+		estate->es_num_session_variables = queryDesc->num_session_variables;
+	}
+	else if (queryDesc->plannedstmt->sessionVariables)
+	{
+		ListCell   *lc;
+		int			nSessionVariables;
+		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.
+		 */
+		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 = pg_variable_aclcheck(varid, GetUserId(), ACL_READ);
+			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 = CopySessionVariable(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/execParallel.c b/src/backend/executor/execParallel.c
index f1fd7f7e8b..1e04923788 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -12,8 +12,9 @@
  * workers and ensuring that their state generally matches that of the
  * leader; see src/backend/access/transam/README.parallel for details.
  * However, we must save and restore relevant executor state, such as
- * any ParamListInfo associated with the query, buffer/WAL usage info, and
- * the actual plan to be passed down to the worker.
+ * any ParamListInfo associated with the query, buffer/WAL usage info,
+ * session variables buffer, and the actual plan to be passed down to
+ * the worker.
  *
  * IDENTIFICATION
  *	  src/backend/executor/execParallel.c
@@ -66,6 +67,7 @@
 #define PARALLEL_KEY_QUERY_TEXT		UINT64CONST(0xE000000000000008)
 #define PARALLEL_KEY_JIT_INSTRUMENTATION UINT64CONST(0xE000000000000009)
 #define PARALLEL_KEY_WAL_USAGE			UINT64CONST(0xE00000000000000A)
+#define PARALLEL_KEY_SESSION_VARIABLES	UINT64CONST(0xE00000000000000B)
 
 #define PARALLEL_TUPLE_QUEUE_SIZE		65536
 
@@ -140,6 +142,12 @@ static bool ExecParallelRetrieveInstrumentation(PlanState *planstate,
 /* Helper function that runs in the parallel worker. */
 static DestReceiver *ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc);
 
+/* Helper functions that can pass values of session variables */
+static Size EstimateSessionVariables(EState *estate);
+static void SerializeSessionVariables(EState *estate, char **start_address);
+static SessionVariableValue * RestoreSessionVariables(char **start_address,
+													int *num_session_variables);
+
 /*
  * Create a serialized representation of the plan to be sent to each worker.
  */
@@ -597,6 +605,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	char	   *pstmt_data;
 	char	   *pstmt_space;
 	char	   *paramlistinfo_space;
+	char	   *session_variables_space;
 	BufferUsage *bufusage_space;
 	WalUsage   *walusage_space;
 	SharedExecutorInstrumentation *instrumentation = NULL;
@@ -606,6 +615,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	int			instrumentation_len = 0;
 	int			jit_instrumentation_len = 0;
 	int			instrument_offset = 0;
+	int			session_variables_len = 0;
 	Size		dsa_minsize = dsa_minimum_size();
 	char	   *query_string;
 	int			query_len;
@@ -661,6 +671,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	shm_toc_estimate_chunk(&pcxt->estimator, paramlistinfo_len);
 	shm_toc_estimate_keys(&pcxt->estimator, 1);
 
+	/* Estimate space for serialized session variables. */
+	session_variables_len = EstimateSessionVariables(estate);
+	shm_toc_estimate_chunk(&pcxt->estimator, session_variables_len);
+	shm_toc_estimate_keys(&pcxt->estimator, 1);
+
 	/*
 	 * Estimate space for BufferUsage.
 	 *
@@ -755,6 +770,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMLISTINFO, paramlistinfo_space);
 	SerializeParamList(estate->es_param_list_info, &paramlistinfo_space);
 
+	/* Store serialized session variables. */
+	session_variables_space = shm_toc_allocate(pcxt->toc, session_variables_len);
+	shm_toc_insert(pcxt->toc, PARALLEL_KEY_SESSION_VARIABLES, session_variables_space);
+	SerializeSessionVariables(estate, &session_variables_space);
+
 	/* Allocate space for each worker's BufferUsage; no need to initialize. */
 	bufusage_space = shm_toc_allocate(pcxt->toc,
 									  mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -1402,6 +1422,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 	SharedJitInstrumentation *jit_instrumentation;
 	int			instrument_options = 0;
 	void	   *area_space;
+	char	   *sessionvariable_space;
 	dsa_area   *area;
 	ParallelWorkerContext pwcxt;
 
@@ -1427,6 +1448,14 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 	area_space = shm_toc_lookup(toc, PARALLEL_KEY_DSA, false);
 	area = dsa_attach_in_place(area_space, seg);
 
+	/* Reconstruct session variables. */
+	sessionvariable_space = shm_toc_lookup(toc,
+										  PARALLEL_KEY_SESSION_VARIABLES,
+										  false);
+	queryDesc->session_variables =
+		RestoreSessionVariables(&sessionvariable_space,
+							   &queryDesc->num_session_variables);
+
 	/* Start up the executor */
 	queryDesc->plannedstmt->jitFlags = fpes->jit_flags;
 	ExecutorStart(queryDesc, fpes->eflags);
@@ -1495,3 +1524,118 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 	FreeQueryDesc(queryDesc);
 	receiver->rDestroy(receiver);
 }
+
+/*
+ * Estimate the amount of space required to serialize a
+ * session variable.
+ */
+static Size
+EstimateSessionVariables(EState *estate)
+{
+	int			i;
+	Size		sz = sizeof(int);
+
+	if (estate->es_session_variables == NULL)
+		return sz;
+
+	for (i = 0; i < estate->es_num_session_variables; i++)
+	{
+		SessionVariableValue *svarval;
+		Oid			typeOid;
+		int16		typLen;
+		bool		typByVal;
+
+		svarval = &estate->es_session_variables[i];
+
+		typeOid = svarval->typid;
+
+		sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+		/* space for datum/isnull */
+		Assert(OidIsValid(typeOid));
+		get_typlenbyval(typeOid, &typLen, &typByVal);
+
+		sz = add_size(sz,
+					  datumEstimateSpace(svarval->value, svarval->isnull, typByVal, typLen));
+	}
+
+	return sz;
+}
+
+/*
+ * Serialize a session variables buffer into caller-provided storage.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn.  The details for each parameter
+ * consist of a 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().  The caller is responsible for ensuring that there is
+ * enough storage to store the number of bytes that will be written; use
+ * EstimateSessionVariables to find out how many will be needed.
+ * *start_address is updated to point to the byte immediately following those
+ * written.
+ *
+ * RestoreSessionVariables can be used to recreate a session variable buffer
+ * based on the serialized representation;
+ */
+static void
+SerializeSessionVariables(EState *estate, char **start_address)
+{
+	int			nparams;
+	int			i;
+
+	/* Write number of parameters. */
+	nparams = estate->es_num_session_variables;
+	memcpy(*start_address, &nparams, sizeof(int));
+	*start_address += sizeof(int);
+
+	/* Write each parameter in turn. */
+	for (i = 0; i < nparams; i++)
+	{
+		SessionVariableValue *svarval;
+		Oid			typeOid;
+		int16		typLen;
+		bool		typByVal;
+
+		svarval = &estate->es_session_variables[i];
+		typeOid = svarval->typid;
+
+		/* Write type OID. */
+		memcpy(*start_address, &typeOid, sizeof(Oid));
+		*start_address += sizeof(Oid);
+
+		Assert(OidIsValid(typeOid));
+		get_typlenbyval(typeOid, &typLen, &typByVal);
+
+		datumSerialize(svarval->value, svarval->isnull, typByVal, typLen,
+					   start_address);
+	}
+}
+
+static SessionVariableValue *
+RestoreSessionVariables(char **start_address, int *num_session_variables)
+{
+	SessionVariableValue *session_variables;
+	int			i;
+	int			nparams;
+
+	memcpy(&nparams, *start_address, sizeof(int));
+	*start_address += sizeof(int);
+
+	*num_session_variables = nparams;
+	session_variables = (SessionVariableValue *)
+		palloc(nparams * sizeof(SessionVariableValue));
+
+	for (i = 0; i < nparams; i++)
+	{
+		SessionVariableValue *svarval = &session_variables[i];
+
+		/* Read type OID. */
+		memcpy(&svarval->typid, *start_address, sizeof(Oid));
+		*start_address += sizeof(Oid);
+
+		/* Read datum/isnull. */
+		svarval->value = datumRestore(start_address, &svarval->isnull);
+	}
+
+	return session_variables;
+}
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index b6b6512ef1..3c2ece8d4e 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -1073,6 +1073,12 @@ llvm_compile_expr(ExprState *state)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
+			case EEOP_PARAM_VARIABLE:
+				build_EvalXFunc(b, mod, "ExecEvalParamVariable",
+								v_state, op, v_econtext);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
 			case EEOP_PARAM_CALLBACK:
 				{
 					LLVMTypeRef v_functype;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 176d4dd866..8885e04417 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -106,6 +106,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_NODE_FIELD(invalItems);
 	COPY_NODE_FIELD(paramExecTypes);
 	COPY_NODE_FIELD(utilityStmt);
+	COPY_NODE_FIELD(sessionVariables);
 	COPY_LOCATION_FIELD(stmt_location);
 	COPY_SCALAR_FIELD(stmt_len);
 
@@ -1517,6 +1518,7 @@ _copyParam(const Param *from)
 	COPY_SCALAR_FIELD(paramtype);
 	COPY_SCALAR_FIELD(paramtypmod);
 	COPY_SCALAR_FIELD(paramcollid);
+	COPY_SCALAR_FIELD(paramvarid);
 	COPY_LOCATION_FIELD(location);
 
 	return newnode;
@@ -3687,6 +3689,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(resultVariable);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasWindowFuncs);
 	COPY_SCALAR_FIELD(hasTargetSRFs);
@@ -3697,6 +3700,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(hasForUpdate);
 	COPY_SCALAR_FIELD(hasRowSecurity);
 	COPY_SCALAR_FIELD(isReturn);
+	COPY_SCALAR_FIELD(hasSessionVariables);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
 	COPY_NODE_FIELD(jointree);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b9ef00e328..94e3f42750 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -272,6 +272,7 @@ _equalParam(const Param *a, const Param *b)
 	COMPARE_SCALAR_FIELD(paramtype);
 	COMPARE_SCALAR_FIELD(paramtypmod);
 	COMPARE_SCALAR_FIELD(paramcollid);
+	COMPARE_SCALAR_FIELD(paramvarid);
 	COMPARE_LOCATION_FIELD(location);
 
 	return true;
@@ -1322,6 +1323,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(canSetTag);
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
+	COMPARE_SCALAR_FIELD(resultVariable);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasWindowFuncs);
 	COMPARE_SCALAR_FIELD(hasTargetSRFs);
@@ -1332,6 +1334,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(hasForUpdate);
 	COMPARE_SCALAR_FIELD(hasRowSecurity);
 	COMPARE_SCALAR_FIELD(isReturn);
+	COMPARE_SCALAR_FIELD(hasSessionVariables);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
 	COMPARE_NODE_FIELD(jointree);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ce12915592..040aacbba6 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -331,6 +331,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_NODE_FIELD(invalItems);
 	WRITE_NODE_FIELD(paramExecTypes);
 	WRITE_NODE_FIELD(utilityStmt);
+	WRITE_NODE_FIELD(sessionVariables);
 	WRITE_LOCATION_FIELD(stmt_location);
 	WRITE_INT_FIELD(stmt_len);
 }
@@ -1182,6 +1183,7 @@ _outParam(StringInfo str, const Param *node)
 	WRITE_OID_FIELD(paramtype);
 	WRITE_INT_FIELD(paramtypmod);
 	WRITE_OID_FIELD(paramcollid);
+	WRITE_OID_FIELD(paramvarid);
 	WRITE_LOCATION_FIELD(location);
 }
 
@@ -2437,6 +2439,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(relationOids);
 	WRITE_NODE_FIELD(invalItems);
 	WRITE_NODE_FIELD(paramExecTypes);
+	WRITE_NODE_FIELD(sessionVariables);
 	WRITE_UINT_FIELD(lastPHId);
 	WRITE_UINT_FIELD(lastRowMarkId);
 	WRITE_INT_FIELD(lastPlanNodeId);
@@ -2497,6 +2500,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
 	WRITE_BOOL_FIELD(hasPseudoConstantQuals);
 	WRITE_BOOL_FIELD(hasAlternativeSubPlans);
 	WRITE_BOOL_FIELD(hasRecursion);
+	WRITE_BOOL_FIELD(hasSessionVariables);
 	WRITE_INT_FIELD(wt_param_id);
 	WRITE_BITMAPSET_FIELD(curOuterRels);
 	WRITE_NODE_FIELD(curOuterParams);
@@ -3224,6 +3228,7 @@ _outQuery(StringInfo str, const Query *node)
 		appendStringInfoString(str, " :utilityStmt <>");
 
 	WRITE_INT_FIELD(resultRelation);
+	WRITE_OID_FIELD(resultVariable);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasWindowFuncs);
 	WRITE_BOOL_FIELD(hasTargetSRFs);
@@ -3234,6 +3239,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(hasForUpdate);
 	WRITE_BOOL_FIELD(hasRowSecurity);
 	WRITE_BOOL_FIELD(isReturn);
+	WRITE_BOOL_FIELD(hasSessionVariables);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
 	WRITE_NODE_FIELD(jointree);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6a05b69415..50dcb8345e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -252,6 +252,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
+	READ_OID_FIELD(resultVariable);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasWindowFuncs);
 	READ_BOOL_FIELD(hasTargetSRFs);
@@ -262,6 +263,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(hasForUpdate);
 	READ_BOOL_FIELD(hasRowSecurity);
 	READ_BOOL_FIELD(isReturn);
+	READ_BOOL_FIELD(hasSessionVariables);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
 	READ_NODE_FIELD(jointree);
@@ -668,6 +670,7 @@ _readParam(void)
 	READ_OID_FIELD(paramtype);
 	READ_INT_FIELD(paramtypmod);
 	READ_OID_FIELD(paramcollid);
+	READ_OID_FIELD(paramvarid);
 	READ_LOCATION_FIELD(location);
 
 	READ_DONE();
@@ -1827,6 +1830,7 @@ _readPlannedStmt(void)
 	READ_NODE_FIELD(invalItems);
 	READ_NODE_FIELD(paramExecTypes);
 	READ_NODE_FIELD(utilityStmt);
+	READ_NODE_FIELD(sessionVariables);
 	READ_LOCATION_FIELD(stmt_location);
 	READ_INT_FIELD(stmt_len);
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index a0f2390334..3edb521e44 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -316,6 +316,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	glob->lastPlanNodeId = 0;
 	glob->transientPlan = false;
 	glob->dependsOnRole = false;
+	glob->sessionVariables = NIL;
 
 	/*
 	 * Assess whether it's feasible to use parallel mode for this query. We
@@ -529,6 +530,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->paramExecTypes = glob->paramExecTypes;
 	/* utilityStmt should be null, but we might as well copy it */
 	result->utilityStmt = parse->utilityStmt;
+	result->sessionVariables = glob->sessionVariables;
 	result->stmt_location = parse->stmt_location;
 	result->stmt_len = parse->stmt_len;
 
@@ -683,6 +685,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	 */
 	pull_up_subqueries(root);
 
+	/*
+	 * Check if some subquery uses session variable. Flag hasSessionVariables
+	 * should be true if query or some subquery uses any session variable.
+	 */
+	pull_up_has_session_variables(root);
+
 	/*
 	 * If this is a simple UNION ALL query, flatten it into an appendrel. We
 	 * do this now because it requires applying pull_up_subqueries to the leaf
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9cef92cab2..0a9def182c 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -181,6 +181,8 @@ 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);

 /*****************************************************************************
@@ -1218,6 +1220,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
@@ -1824,8 +1870,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.
@@ -1915,15 +1962,39 @@ fix_expr_common(PlannerInfo *root, Node *node)
 				g->cols = cols;
 		}
 	}
+	else if (IsA(node, Param))
+	{
+		Param *p = (Param *) node;
+
+		if (p->paramkind == PARAM_VARIABLE)
+		{
+			PlanInvalItem *inval_item = makeNode(PlanInvalItem);
+
+			/* paramid is still session variable id */
+			inval_item->cacheId = VARIABLEOID;
+			inval_item->hashValue = GetSysCacheHashValue1(VARIABLEOID,
+														  ObjectIdGetDatum(p->paramvarid));
+
+			/* Append this variable to global, register dependency */
+			root->glob->invalItems = lappend(root->glob->invalItems,
+											 inval_item);
+		}
+	}
 }
 
 /*
  * fix_param_node
  *		Do set_plan_references processing on a Param
+ *		Collect session variables list and replace variable oid by
+ *		index to collected list.
  *
  * If it's a PARAM_MULTIEXPR, replace it with the appropriate Param from
  * root->multiexpr_params; otherwise no change is needed.
  * Just for paranoia's sake, we make a copy of the node in either case.
+ *
+ * If it's a PARAM_VARIABLE, then we collect used session variables in
+ * list root->glob->sessionVariable. We should to assign Param paramvarid
+ * too, and it is position of related session variable in mentioned list.
  */
 static Node *
 fix_param_node(PlannerInfo *root, Param *p)
@@ -1942,6 +2013,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);
 }
 
@@ -2003,7 +2109,10 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
  * replacing Aggref nodes that should be replaced by initplan output Params,
  * choosing the best implementation for AlternativeSubPlans,
  * looking up operator opcode info for OpExpr and related nodes,
- * and adding OIDs from regclass Const nodes into root->glob->relationOids.
+ * adding OIDs from regclass Const nodes into root->glob->relationOids,
+ * and assigning paramvarid to PARAM_VARIABLE params, and collecting
+ * of OIDs of session variables in root->glob->sessionVariables list
+ * (paramvarid is an position of related session variable in this list).
  *
  * 'node': the expression to be modified
  * 'rtoffset': how much to increment varnos by
@@ -2025,7 +2134,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);
 	}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 533df86ff7..ca48b3b74c 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -26,6 +26,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/session_variable.h"
 #include "executor/executor.h"
 #include "executor/functions.h"
 #include "executor/execExpr.h"
@@ -853,16 +854,17 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 
 	/*
 	 * We can't pass Params to workers at the moment either, so they are also
-	 * parallel-restricted, unless they are PARAM_EXTERN Params or are
-	 * PARAM_EXEC Params listed in safe_param_ids, meaning they could be
-	 * either generated within workers or can be computed by the leader and
-	 * then their value can be passed to workers.
+	 * parallel-restricted, unless they are PARAM_EXTERN or PARAM_VARIABLE
+	 * Params or are PARAM_EXEC Params listed in safe_param_ids, meaning they
+	 * could be either generated within workers or can be computed by the
+	 * leader and then their value can be passed to workers.
 	 */
 	else if (IsA(node, Param))
 	{
 		Param	   *param = (Param *) node;
 
-		if (param->paramkind == PARAM_EXTERN)
+		if (param->paramkind == PARAM_EXTERN ||
+			param->paramkind == PARAM_VARIABLE)
 			return false;
 
 		if (param->paramkind != PARAM_EXEC ||
@@ -2284,6 +2286,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 *
@@ -2406,6 +2409,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 = CopySessionVariableWithTypeCheck(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
@@ -4813,22 +4839,45 @@ substitute_actual_parameters_mutator(Node *node,
 {
 	if (node == NULL)
 		return NULL;
+
+	/*
+	 * SQL functions can contain two different kind of params. The nodes with
+	 * paramkind PARAM_EXTERN are related to function's arguments (and should
+	 * be replaced in this step), because this is how we apply the function's
+	 * arguments for an expression.
+	 *
+	 * The nodes with paramkind PARAM_VARIABLE are related to usage of
+	 * session variables. The values of session variables are not passed
+	 * to expression by expression arguments, so it should not be replaced
+	 * here by function's arguments. Although we could substitute params
+	 * related to immutable session variables with default expression by
+	 * this default expression, it is safer to not do it. This way we don't
+	 * have to run security checks here. There can be some performance loss,
+	 * but an access to session variable is fast (and the result of default
+	 * expression is immediately materialized and can be reused).
+	 */
 	if (IsA(node, Param))
 	{
 		Param	   *param = (Param *) node;
 
-		if (param->paramkind != PARAM_EXTERN)
+		if (param->paramkind != PARAM_EXTERN &&
+			  param->paramkind != PARAM_VARIABLE)
 			elog(ERROR, "unexpected paramkind: %d", (int) param->paramkind);
-		if (param->paramid <= 0 || param->paramid > context->nargs)
-			elog(ERROR, "invalid paramid: %d", param->paramid);
 
-		/* Count usage of parameter */
-		context->usecounts[param->paramid - 1]++;
+		if (param->paramkind == PARAM_EXTERN)
+		{
+			if (param->paramid <= 0 || param->paramid > context->nargs)
+				elog(ERROR, "invalid paramid: %d", param->paramid);
 
-		/* Select the appropriate actual arg and replace the Param with it */
-		/* We don't need to copy at this time (it'll get done later) */
-		return list_nth(context->args, param->paramid - 1);
+			/* Count usage of parameter */
+			context->usecounts[param->paramid - 1]++;
+
+			/* Select the appropriate actual arg and replace the Param with it */
+			/* We don't need to copy at this time (it'll get done later) */
+			return list_nth(context->args, param->paramid - 1);
+		}
 	}
+
 	return expression_tree_mutator(node, substitute_actual_parameters_mutator,
 								   (void *) context);
 }
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 1bcb875507..d690d102b0 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -525,6 +525,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasAggs = pstate->p_hasAggs;
 
+	qry->hasSessionVariables = pstate->p_hasSessionVariables;
+
 	assign_query_collations(pstate, qry);
 
 	/* this must be done after collations, for reliable comparison of exprs */
@@ -942,6 +944,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);
 
@@ -1312,8 +1315,9 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
-	/* transform targetlist */
-	qry->targetList = transformTargetList(pstate, stmt->targetList,
+	/* Transform targetlist. */
+	qry->targetList = transformTargetList(pstate,
+										  stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
 
 	/* mark column origins */
@@ -1404,6 +1408,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *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 */
@@ -1878,6 +1884,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *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 */
@@ -2409,6 +2417,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);
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 69e6318b31..046884fe77 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -40,11 +40,12 @@
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 /* GUC parameters */
 bool		Transform_null_equals = false;
-
+bool		session_variables_ambiguity_warning = false;
 
 static Node *transformExprRecurse(ParseState *pstate, Node *expr);
 static Node *transformParamRef(ParseState *pstate, ParamRef *pref);
@@ -101,6 +102,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);

 /*
@@ -506,6 +510,10 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	char	   *relname = NULL;
 	char	   *colname = NULL;
 	ParseNamespaceItem *nsitem;
+	Oid			varid = InvalidOid;
+	char	   *attrname = NULL;
+	bool		not_unique;
+	bool		lockit;
 	int			levels_up;
 	enum
 	{
@@ -851,6 +859,124 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 					 parser_errposition(pstate, cref->location)));
 	}
 
+	/* Protect session variable by AccessShareLock when it is not shadowed. */
+	lockit = (node == NULL);
+
+	/* The col's reference can be a reference to session variable too. */
+	varid = IdentifyVariable(cref->fields, &attrname, lockit, &not_unique);
+
+	/*
+	 * Raise error when reference to session variable is ambiguous and
+	 * and this reference is not shadowed.
+	 */
+	if (!node && not_unique)
+		ereport(ERROR,
+				(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+				 errmsg("session variable reference \"%s\" is ambiguous",
+						NameListToString(cref->fields)),
+				 parser_errposition(pstate, cref->location)));
+
+	if (OidIsValid(varid))
+	{
+		/*
+		 * When Session variables is shadowed by columns or by routine's
+		 * variables or routine's arguments.
+		 */
+		if (node != NULL)
+		{
+			/*
+			 * In this case we can raise a warning, but only when required.
+			 * We should not raise warnings for contexts where usage of session
+			 * variables has not sense, or when we can detect that variable
+			 * doesn't have the wanted field (and then there is no possible
+			 * identifier's collision).
+			 */
+			if (session_variables_ambiguity_warning &&
+				pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET)
+			{
+				Oid			typid;
+				int32		typmod;
+				Oid			collid;
+
+				get_session_variable_type_typmod_collid(varid,
+														&typid, &typmod,
+														&collid);
+
+				/*
+				 * Some cases with ambiguous references can be solved without
+				 * raising a warning. When there is a collision between column
+				 * name (or label) and some session variable name, and when we
+				 * know attribute name, then we can ignore the collision when:
+				 *
+				 *   a) variable is of scalar type (then indirection cannot be
+				 *      applied on this session variable.
+				 *
+				 *   b) when related variable has no field with the given
+				 *      attrname, then indirection cannot be applied on this
+				 *      session variable.
+				 */
+				if (attrname)
+				{
+					if (type_is_rowtype(typid))
+					{
+						TupleDesc	tupdesc;
+						bool		found = false;
+						int			i;
+
+						/* slow part, I hope it will not be to often */
+						tupdesc = lookup_rowtype_tupdesc(typid, typmod);
+						for (i = 0; i < tupdesc->natts; i++)
+						{
+							if (namestrcmp(&(TupleDescAttr(tupdesc, i)->attname), attrname) == 0 &&
+								!TupleDescAttr(tupdesc, i)->attisdropped)
+							{
+								found = true;
+								break;
+							}
+						}
+
+						ReleaseTupleDesc(tupdesc);
+
+						/* There is no composite variable with this field. */
+						if (!found)
+							varid = InvalidOid;
+					}
+					else
+						/* There is no composite variable with this name. */
+						varid = InvalidOid;
+				}
+
+				/*
+				 * Raise warning when session variable reference is still
+				 * visible.
+				 */
+				if (OidIsValid(varid))
+					ereport(WARNING,
+							(errcode(ERRCODE_AMBIGUOUS_COLUMN),
+							 errmsg("session variable \"%s\" is shadowed",
+									NameListToString(cref->fields)),
+							 errdetail("Session variables can be shadowed by columns, routine's variables and routine's arguments with same name."),
+							 parser_errposition(pstate, cref->location)));
+			}
+
+			/* Session variables are always shadowed by any other node. */
+			varid = InvalidOid;
+		}
+		else
+		{
+			Oid			typid;
+			int32		typmod;
+			Oid			collid;
+
+			get_session_variable_type_typmod_collid(varid, &typid, &typmod,
+													&collid);
+
+			node = makeParamSessionVariable(pstate,
+										   varid, typid, typmod, collid,
+										   attrname, cref->location);
+		}
+	}
+
 	/*
 	 * Throw error if no translation found.
 	 */
@@ -885,6 +1011,64 @@ 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(typid, typmod);
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+			if (strcmp(attrname, NameStr(att->attname)) == 0 &&
+				!att->attisdropped)
+			{
+				/* Success, so generate a FieldSelect expression */
+				FieldSelect *fselect = makeNode(FieldSelect);
+
+				fselect->arg = (Expr *) param;
+				fselect->fieldnum = i + 1;
+				fselect->resulttype = att->atttypid;
+				fselect->resulttypmod = att->atttypmod;
+				/* save attribute's collation for parse_collate.c */
+				fselect->resultcollid = att->attcollation;
+
+				ReleaseTupleDesc(tupdesc);
+				return (Node *) fselect;
+			}
+		}
+
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("could not identify column \"%s\" in variable", attrname),
+				 parser_errposition(pstate, location)));
+	}
+
+	return (Node *) param;
+}
+
 static Node *
 transformParamRef(ParseState *pstate, ParamRef *pref)
 {
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 5aa5a350f3..c5cf50687c 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/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c3937a60fd..eab0af8809 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -38,6 +38,7 @@
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/defrem.h"
 #include "commands/tablespace.h"
 #include "common/keywords.h"
@@ -501,6 +502,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);
@@ -8057,6 +8059,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;
+	}
+
 	/*
 	 * If it's an external parameter, see if the outermost namespace provides
 	 * function argument names.
@@ -12727,6 +12737,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 0d6a295674..74daf13a3f 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"
@@ -1891,6 +1892,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
@@ -2022,7 +2037,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 af74fe345e..8d61fc082c 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1902,9 +1902,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/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a7cc49898b..ba3724e29e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1729,6 +1729,18 @@ static struct config_bool ConfigureNamesBool[] =
 		false,
 		NULL, NULL, NULL
 	},
+
+	{
+		{"session_variables_ambiguity_warning", PGC_USERSET, DEVELOPER_OPTIONS,
+			gettext_noop("Raise warning when reference to session variable is ambiguous."),
+			NULL,
+			GUC_NOT_IN_SAMPLE
+		},
+		&session_variables_ambiguity_warning,
+		false,
+		NULL, NULL, NULL
+	},
+
 	{
 		{"db_user_namespace", PGC_SIGHUP, CONN_AUTH_AUTH,
 			gettext_noop("Enables per-database user names."),
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 5434e79f54..7614a5dee2 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -27,6 +27,13 @@ extern void ResetSessionVariables(void);
 extern void RemoveSessionVariable(Oid varid);
 extern ObjectAddress DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt * stmt);
 
+extern Datum CopySessionVariable(Oid varid, bool *isNull, Oid *typid);
+extern Datum CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid);
+extern Datum GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid);
+
+extern void SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid);
+extern void SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typid);
+
 extern void RegisterOnCommitDropSessionVariable(Oid varid);
 
 extern void AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit);
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index e34db8c93c..1111af6920 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -159,6 +159,7 @@ typedef enum ExprEvalOp
 	EEOP_PARAM_EXEC,
 	EEOP_PARAM_EXTERN,
 	EEOP_PARAM_CALLBACK,
+	EEOP_PARAM_VARIABLE,
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -384,6 +385,13 @@ typedef struct ExprEvalStep
 			Oid			paramtype;	/* OID of parameter's datatype */
 		}			param;
 
+		/* for EEOP_PARAM_VARIABLE */
+		struct
+		{
+			Oid			varid;	/* OID of assigned variable */
+			Oid			vartype;	/* OID of parameter's datatype */
+		}			vparam;
+
 		/* for EEOP_PARAM_CALLBACK */
 		struct
 		{
@@ -806,6 +814,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
 							  ExprContext *econtext);
 extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
 								ExprContext *econtext);
+extern void ExecEvalParamVariable(ExprState *state, ExprEvalStep *op,
+								  ExprContext *econtext);
 extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h
index e79e2c001f..dbf4dc7ea0 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/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5728801379..26148a9381 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -575,6 +575,18 @@ typedef struct AsyncRequest
 								 * tuples) */
 } AsyncRequest;
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+	Oid			varid;
+	Oid			typid;
+	bool		isnull;
+	Datum		value;
+}			SessionVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -626,6 +638,13 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
 
+	/* Variables info: */
+	/* number of used session variables */
+	int			es_num_session_variables;
+
+	/* array of copied values of session variables */
+	SessionVariableValue *es_session_variables;
+
 	QueryEnvironment *es_queryEnv;	/* query environment */
 
 	/* Other working state: */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6b825785a7..74dbc5a204 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -134,6 +134,7 @@ typedef struct Query
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
 
+	Oid			resultVariable;	/* target variable of LET statement */
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
 	bool		hasTargetSRFs;	/* has set-returning functions in tlist */
@@ -143,6 +144,7 @@ typedef struct Query
 	bool		hasModifyingCTE;	/* has INSERT/UPDATE/DELETE in WITH */
 	bool		hasForUpdate;	/* FOR [KEY] UPDATE/SHARE was specified */
 	bool		hasRowSecurity; /* rewriter has applied some RLS policy */
+	bool		hasSessionVariables; /* uses session variables */
 
 	bool		isReturn;		/* is a RETURN statement */
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index a6e5db4eec..0915f346df 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -130,6 +130,8 @@ typedef struct PlannerGlobal
 	char		maxParallelHazard;	/* worst PROPARALLEL hazard level */
 
 	PartitionDirectory partition_directory; /* partition descriptors */
+
+	List	   *sessionVariables;	/* list of used session variables */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -351,6 +353,7 @@ struct PlannerInfo
 										 * pseudoconstant = true */
 	bool		hasAlternativeSubPlans; /* true if we've made any of those */
 	bool		hasRecursion;	/* true if planning a recursive WITH item */
+	bool		hasSessionVariables;	/* true if session variables were used */
 
 	/*
 	 * Information about aggregates. Filled by preprocess_aggrefs().
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0ea9a22dfb..2c536aab9a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -86,6 +86,8 @@ typedef struct PlannedStmt
 
 	Node	   *utilityStmt;	/* non-null if this is utility stmt */
 
+	List	   *sessionVariables;	/* list of OIDs for PARAM_VARIABLE Params */
+
 	/* statement location in source string (copied from Query) */
 	int			stmt_location;	/* start location, or -1 if unknown */
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 51505eee85..edb3fb06ca 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -43,7 +43,10 @@ typedef struct Alias
 	List	   *colnames;		/* optional list of column aliases */
 } Alias;
 
-/* What to do at commit time for temporary relations */
+/*
+ * What to do at commit time for temporary relations or
+ * persistent/temporary variable.
+ */
 typedef enum OnCommitAction
 {
 	ONCOMMIT_NOOP,				/* No ON COMMIT clause (do nothing) */
@@ -261,13 +264,17 @@ typedef struct Const
  *				of the `paramid' field contain the SubLink's subLinkId, and
  *				the low-order 16 bits contain the column number.  (This type
  *				of Param is also converted to PARAM_EXEC during planning.)
+ *
+ *		PARAM_VARIABLE:  The parameter is an access to session variable
+ *				paramid holds varid.
  */
 typedef enum ParamKind
 {
 	PARAM_EXTERN,
 	PARAM_EXEC,
 	PARAM_SUBLINK,
-	PARAM_MULTIEXPR
+	PARAM_MULTIEXPR,
+	PARAM_VARIABLE
 } ParamKind;
 
 typedef struct Param
@@ -278,6 +285,7 @@ typedef struct Param
 	Oid			paramtype;		/* pg_type OID of parameter's datatype */
 	int32		paramtypmod;	/* typmod value, if known */
 	Oid			paramcollid;	/* OID of collation, or InvalidOid if none */
+	Oid			paramvarid;		/* OID of session variable if it is used */
 	int			location;		/* token location, or -1 if unknown */
 } Param;
 
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index c4f61c1a09..efa1d62f1e 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -117,4 +117,6 @@ extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
 extern void record_plan_type_dependency(PlannerInfo *root, Oid typid);
 extern bool extract_query_dependencies_walker(Node *node, PlannerInfo *root);
 
+extern void pull_up_has_session_variables(PlannerInfo *root);
+
 #endif							/* PLANMAIN_H */
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index c8e5c57b43..14b0adb948 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -17,6 +17,7 @@
 
 /* GUC parameters */
 extern PGDLLIMPORT bool Transform_null_equals;
+extern PGDLLIMPORT bool session_variables_ambiguity_warning;
 
 extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKind);
 
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 9b042962fc..59da340265 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -212,6 +212,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 */
 
-- 
2.36.1

From eb0a3c0b6ea49cf7345019863742e23dc8c70f16 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/11] Catalogue support for session variables

Implementation new system object class - session variable with new access rights READ, WRITE,
with routines for creating session variable, initialization of session variable from system
catalogue, and lookups routines for identification of session variables.
---
 src/backend/access/transam/xact.c       |  11 +
 src/backend/catalog/Makefile            |   4 +-
 src/backend/catalog/aclchk.c            | 307 +++++++++++++++++++
 src/backend/catalog/dependency.c        |  13 +-
 src/backend/catalog/namespace.c         | 374 ++++++++++++++++++++++++
 src/backend/catalog/objectaddress.c     | 126 +++++++-
 src/backend/catalog/pg_shdepend.c       |   2 +
 src/backend/catalog/pg_variable.c       | 245 ++++++++++++++++
 src/backend/commands/Makefile           |   1 +
 src/backend/commands/alter.c            |   9 +
 src/backend/commands/dropcmds.c         |   4 +
 src/backend/commands/event_trigger.c    |   6 +
 src/backend/commands/seclabel.c         |   1 +
 src/backend/commands/sessionvariable.c  | 348 ++++++++++++++++++++++
 src/backend/commands/tablecmds.c        |   1 +
 src/backend/nodes/copyfuncs.c           |  20 ++
 src/backend/nodes/equalfuncs.c          |  19 ++
 src/backend/parser/gram.y               | 144 ++++++++-
 src/backend/parser/parse_agg.c          |   2 +
 src/backend/parser/parse_expr.c         |   5 +
 src/backend/parser/parse_func.c         |   1 +
 src/backend/tcop/utility.c              |  16 +
 src/backend/utils/adt/acl.c             |  21 ++
 src/backend/utils/cache/lsyscache.c     |  90 ++++++
 src/backend/utils/cache/syscache.c      |  23 ++
 src/backend/utils/fmgr/fmgr.c           |   6 +-
 src/include/catalog/dependency.h        |   5 +-
 src/include/catalog/namespace.h         |   6 +
 src/include/catalog/pg_default_acl.h    |   1 +
 src/include/catalog/pg_proc.dat         |   3 +
 src/include/catalog/pg_variable.h       | 105 +++++++
 src/include/commands/session_variable.h |  36 +++
 src/include/nodes/nodes.h               |   1 +
 src/include/nodes/parsenodes.h          |  24 +-
 src/include/parser/kwlist.h             |   2 +
 src/include/parser/parse_node.h         |   1 +
 src/include/tcop/cmdtaglist.h           |   3 +
 src/include/utils/acl.h                 |  10 +-
 src/include/utils/lsyscache.h           |   8 +
 src/include/utils/syscache.h            |   6 +-
 40 files changed, 1992 insertions(+), 18 deletions(-)
 create mode 100644 src/backend/catalog/pg_variable.c
 create mode 100644 src/backend/commands/sessionvariable.c
 create mode 100644 src/include/catalog/pg_variable.h
 create mode 100644 src/include/commands/session_variable.h

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 47d80b0d25..21cc02a199 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -36,6 +36,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/session_variable.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "common/pg_prng.h"
@@ -2213,6 +2214,9 @@ CommitTransaction(void)
 	 */
 	smgrDoPendingSyncs(true, is_parallel_worker);
 
+	/* Let ON COMMIT DROP */
+	AtPreEOXact_SessionVariable_on_xact_actions(true);
+
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
@@ -2789,6 +2793,9 @@ AbortTransaction(void)
 	AtAbort_Portals();
 	smgrDoPendingSyncs(false, is_parallel_worker);
 	AtEOXact_LargeObject(false);
+
+	/* 'false' means it's abort */
+	AtPreEOXact_SessionVariable_on_xact_actions(false);
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
@@ -4974,6 +4981,8 @@ CommitSubTransaction(void)
 	AtEOSubXact_SPI(true, s->subTransactionId);
 	AtEOSubXact_on_commit_actions(true, s->subTransactionId,
 								  s->parent->subTransactionId);
+	AtEOSubXact_SessionVariable_on_xact_actions(true, s->subTransactionId,
+												s->parent->subTransactionId);
 	AtEOSubXact_Namespace(true, s->subTransactionId,
 						  s->parent->subTransactionId);
 	AtEOSubXact_Files(true, s->subTransactionId,
@@ -5138,6 +5147,8 @@ AbortSubTransaction(void)
 		AtEOSubXact_SPI(false, s->subTransactionId);
 		AtEOSubXact_on_commit_actions(false, s->subTransactionId,
 									  s->parent->subTransactionId);
+		AtEOSubXact_SessionVariable_on_xact_actions(false, s->subTransactionId,
+													s->parent->subTransactionId);
 		AtEOSubXact_Namespace(false, s->subTransactionId,
 							  s->parent->subTransactionId);
 		AtEOSubXact_Files(false, s->subTransactionId,
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 89a0221ec9..f89e12eaf2 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
 
@@ -72,7 +73,8 @@ CATALOG_HEADERS := \
 	pg_collation.h pg_parameter_acl.h pg_partitioned_table.h \
 	pg_range.h pg_transform.h \
 	pg_sequence.h pg_publication.h pg_publication_namespace.h \
-	pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
+	pg_publication_rel.h pg_subscription.h pg_subscription_rel.h \
+	pg_variable.h
 
 GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 5f1726c095..745ca712f1 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -59,6 +59,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
@@ -114,6 +115,7 @@ static void ExecGrant_Namespace(InternalGrant *grantStmt);
 static void ExecGrant_Tablespace(InternalGrant *grantStmt);
 static void ExecGrant_Type(InternalGrant *grantStmt);
 static void ExecGrant_Parameter(InternalGrant *grantStmt);
+static void ExecGrant_Variable(InternalGrant *grantStmt);
 
 static void SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames);
 static void SetDefaultACL(InternalDefaultACL *iacls);
@@ -265,6 +267,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 */
@@ -509,6 +514,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);
@@ -614,6 +623,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 		case OBJECT_PARAMETER_ACL:
 			ExecGrant_Parameter(istmt);
 			break;
+		case OBJECT_VARIABLE:
+			ExecGrant_Variable(istmt);
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
@@ -804,6 +816,16 @@ 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);
@@ -893,6 +915,33 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 					table_close(rel, AccessShareLock);
 				}
 				break;
+			case OBJECT_VARIABLE:
+				{
+					ScanKeyData key;
+					Relation	rel;
+					TableScanDesc scan;
+					HeapTuple	tuple;
+
+					ScanKeyInit(&key,
+								Anum_pg_variable_varnamespace,
+								BTEqualStrategyNumber, F_OIDEQ,
+								ObjectIdGetDatum(namespaceId));
+
+					rel = table_open(VariableRelationId, AccessShareLock);
+					scan = table_beginscan_catalog(rel, 1, &key);
+
+					while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+					{
+						Oid			oid = ((Form_pg_proc) GETSTRUCT(tuple))->oid;
+
+						objects = lappend_oid(objects, oid);
+					}
+
+					table_endscan(scan);
+					table_close(rel, AccessShareLock);
+				}
+				break;
+
 			default:
 				/* should not happen */
 				elog(ERROR, "unrecognized GrantStmt.objtype: %d",
@@ -1052,6 +1101,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);
@@ -1249,6 +1302,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
 				this_privileges = ACL_ALL_RIGHTS_SCHEMA;
 			break;
 
+		case OBJECT_VARIABLE:
+			objtype = DEFACLOBJ_VARIABLE;
+			if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
+				this_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			break;
+
 		default:
 			elog(ERROR, "unrecognized objtype: %d",
 				 (int) iacls->objtype);
@@ -1482,6 +1541,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",
@@ -1542,6 +1604,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;
@@ -3421,6 +3486,129 @@ ExecGrant_Parameter(InternalGrant *istmt)
 	table_close(relation, RowExclusiveLock);
 }
 
+static void
+ExecGrant_Variable(InternalGrant *istmt)
+{
+	Relation	relation;
+	ListCell   *cell;
+
+	if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
+		istmt->privileges = ACL_ALL_RIGHTS_VARIABLE;
+
+	relation = table_open(VariableRelationId, RowExclusiveLock);
+
+	foreach(cell, istmt->objects)
+	{
+		Oid			varId = lfirst_oid(cell);
+		Form_pg_variable pg_variable_tuple;
+		Datum		aclDatum;
+		bool		isNull;
+		AclMode		avail_goptions;
+		AclMode		this_privileges;
+		Acl		   *old_acl;
+		Acl		   *new_acl;
+		Oid			grantorId;
+		Oid			ownerId;
+		HeapTuple	tuple;
+		HeapTuple	newtuple;
+		Datum		values[Natts_pg_variable];
+		bool		nulls[Natts_pg_variable];
+		bool		replaces[Natts_pg_variable];
+		int			noldmembers;
+		int			nnewmembers;
+		Oid		   *oldmembers;
+		Oid		   *newmembers;
+
+		tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varId));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for session variable %u", varId);
+
+		pg_variable_tuple = (Form_pg_variable) GETSTRUCT(tuple);
+
+		/*
+		 * Get owner ID and working copy of existing ACL. If there's no ACL,
+		 * substitute the proper default.
+		 */
+		ownerId = pg_variable_tuple->varowner;
+		aclDatum = SysCacheGetAttr(VARIABLEOID, tuple, Anum_pg_variable_varacl,
+								   &isNull);
+		if (isNull)
+		{
+			old_acl = acldefault(OBJECT_VARIABLE, ownerId);
+			/* There are no old member roles according to the catalogs */
+			noldmembers = 0;
+			oldmembers = NULL;
+		}
+		else
+		{
+			old_acl = DatumGetAclPCopy(aclDatum);
+			/* Get the roles mentioned in the existing ACL */
+			noldmembers = aclmembers(old_acl, &oldmembers);
+		}
+
+		/* Determine ID to do the grant as, and available grant options */
+		select_best_grantor(GetUserId(), istmt->privileges,
+							old_acl, ownerId,
+							&grantorId, &avail_goptions);
+
+		/*
+		 * Restrict the privileges to what we can actually grant, and emit the
+		 * standards-mandated warning and error messages.
+		 */
+		this_privileges =
+			restrict_and_check_grant(istmt->is_grant, avail_goptions,
+									 istmt->all_privs, istmt->privileges,
+									 varId, grantorId, OBJECT_VARIABLE,
+									 NameStr(pg_variable_tuple->varname),
+									 0, NULL);
+
+		/*
+		 * Generate new ACL.
+		 */
+		new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
+									   istmt->grant_option, istmt->behavior,
+									   istmt->grantees, this_privileges,
+									   grantorId, ownerId);
+
+		/*
+		 * We need the members of both old and new ACLs so we can correct the
+		 * shared dependency information.
+		 */
+		nnewmembers = aclmembers(new_acl, &newmembers);
+
+		/* finished building new ACL value, now insert it */
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, false, sizeof(nulls));
+		MemSet(replaces, false, sizeof(replaces));
+
+		replaces[Anum_pg_variable_varacl - 1] = true;
+		values[Anum_pg_variable_varacl - 1] = PointerGetDatum(new_acl);
+
+		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
+									 nulls, replaces);
+
+		CatalogTupleUpdate(relation, &newtuple->t_self, newtuple);
+
+		/* Update initial privileges for extensions */
+		recordExtensionInitPriv(varId, VariableRelationId, 0, new_acl);
+
+		/* Update the shared dependency ACL info */
+		updateAclDependencies(VariableRelationId, varId, 0,
+							  ownerId,
+							  noldmembers, oldmembers,
+							  nnewmembers, newmembers);
+
+		ReleaseSysCache(tuple);
+
+		pfree(new_acl);
+
+		/* prevent error when processing duplicate objects */
+		CommandCounterIncrement();
+	}
+
+	table_close(relation, RowExclusiveLock);
+}
+
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3457,6 +3645,10 @@ string_to_privilege(const char *privname)
 		return ACL_ALTER_SYSTEM;
 	if (strcmp(privname, "rule") == 0)
 		return 0;				/* ignore old RULE privileges */
+	if (strcmp(privname, "read") == 0)
+		return ACL_READ;
+	if (strcmp(privname, "write") == 0)
+		return ACL_WRITE;
 	ereport(ERROR,
 			(errcode(ERRCODE_SYNTAX_ERROR),
 			 errmsg("unrecognized privilege type \"%s\"", privname)));
@@ -3496,6 +3688,10 @@ privilege_to_string(AclMode privilege)
 			return "SET";
 		case ACL_ALTER_SYSTEM:
 			return "ALTER SYSTEM";
+		case ACL_READ:
+			return "READ";
+		case ACL_WRITE:
+			return "WRITE";
 		default:
 			elog(ERROR, "unrecognized privilege: %d", (int) privilege);
 	}
@@ -3622,6 +3818,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;
@@ -3733,6 +3932,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;
@@ -3881,6 +4083,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return pg_type_aclmask(table_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return pg_variable_aclmask(table_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized objtype: %d",
 				 (int) objtype);
@@ -4864,6 +5068,66 @@ pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how)
 	return result;
 }
 
+/*
+ * Exported routine for examining a user's privileges for a variable.
+ */
+AclMode
+pg_variable_aclmask(Oid var_oid, Oid roleid, AclMode mask, AclMaskHow how)
+{
+	AclMode		result;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+	Acl		   *acl;
+	Oid			ownerId;
+
+	Form_pg_variable varForm;
+
+	/* Bypass permission checks for superusers */
+	if (superuser_arg(roleid))
+		return mask;
+
+	/*
+	 * Must get the variables's tuple from pg_variable
+	 */
+	tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(var_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("variable with OID %u does not exist",
+						var_oid)));
+	varForm = (Form_pg_variable) GETSTRUCT(tuple);
+
+	/*
+	 * Now get the variable's owner and ACL from the tuple
+	 */
+	ownerId = varForm->varowner;
+
+	aclDatum = SysCacheGetAttr(VARIABLEOID, tuple,
+							   Anum_pg_variable_varacl, &isNull);
+	if (isNull)
+	{
+		/* No ACL, so build default ACL */
+		acl = acldefault(OBJECT_VARIABLE, ownerId);
+		aclDatum = (Datum) 0;
+	}
+	else
+	{
+		/* detoast rel's ACL if necessary */
+		acl = DatumGetAclP(aclDatum);
+	}
+
+	result = aclmask(acl, roleid, ownerId, mask, how);
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
 /*
  * Exported routine for checking a user's access privileges to a column
  *
@@ -5164,6 +5428,18 @@ pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
 		return ACLCHECK_NO_PRIV;
 }
 
+/*
+ * Exported routine for checking a user's access privileges to a variable
+ */
+AclResult
+pg_variable_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
+{
+	if (pg_variable_aclmask(type_oid, roleid, mode, ACLMASK_ANY) != 0)
+		return ACLCHECK_OK;
+	else
+		return ACLCHECK_NO_PRIV;
+}
+
 /*
  * Ownership check for a relation (specified by OID).
  */
@@ -5781,6 +6057,33 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid)
 	return has_privs_of_role(roleid, ownerId);
 }
 
+/*
+ * Ownership check for a session variable (specified by OID).
+ */
+bool
+pg_variable_ownercheck(Oid db_oid, Oid roleid)
+{
+	HeapTuple	tuple;
+	Oid			ownerId;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return true;
+
+	tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(db_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_DATABASE),
+				 errmsg("session variable with OID %u does not exist", db_oid)));
+
+	ownerId = ((Form_pg_variable) GETSTRUCT(tuple))->varowner;
+
+	ReleaseSysCache(tuple);
+
+	return has_privs_of_role(roleid, ownerId);
+}
+
+
 /*
  * Check whether specified role has CREATEROLE privilege (or is a superuser)
  *
@@ -5909,6 +6212,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 de10923391..d5d2a294c3 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -64,12 +64,15 @@
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
 #include "commands/policy.h"
 #include "commands/publicationcmds.h"
+#include "commands/schemacmds.h"
+#include "commands/session_variable.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
 #include "commands/trigger.h"
@@ -185,7 +188,8 @@ static const Oid object_classes[] = {
 	PublicationRelationId,		/* OCLASS_PUBLICATION */
 	PublicationRelRelationId,	/* OCLASS_PUBLICATION_REL */
 	SubscriptionRelationId,		/* OCLASS_SUBSCRIPTION */
-	TransformRelationId			/* OCLASS_TRANSFORM */
+	TransformRelationId,		/* OCLASS_TRANSFORM */
+	VariableRelationId			/* OCLASS_VARIABLE */
 };

@@ -1502,6 +1506,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			DropObjectById(object);
 			break;
 
+		case OCLASS_VARIABLE:
+			RemoveSessionVariable(object->objectId);
+			break;
+
 			/*
 			 * These global object types are not supported here.
 			 */
@@ -2884,6 +2892,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case TransformRelationId:
 			return OCLASS_TRANSFORM;
+
+		case VariableRelationId:
+			return OCLASS_VARIABLE;
 	}
 
 	/* shouldn't get here */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index fafb9349cc..8300aea87b 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -39,6 +39,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
@@ -764,6 +765,69 @@ RelationIsVisible(Oid relid)
 	return visible;
 }
 
+/*
+ * VariableIsVisible
+ *		Determine whether a variable (identified by OID) is visible in the
+ *		current search path. Visible means "would be found by searching
+ *		for the unqualified variable name".
+ */
+bool
+VariableIsVisible(Oid varid)
+{
+	HeapTuple	vartup;
+	Form_pg_variable varform;
+	Oid			varnamespace;
+	bool		visible;
+
+	vartup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+	if (!HeapTupleIsValid(vartup))
+		elog(ERROR, "cache lookup failed for 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
@@ -2843,6 +2907,305 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
+/*
+ * Returns oid of session variable specified by possibly qualified identifier.
+ *
+ * If not found, returns InvalidOid if missing_ok, else throws error.
+ */
+Oid
+LookupVariable(const char *nspname, const char *varname, bool missing_ok)
+{
+	Oid			namespaceId;
+	Oid			varoid = InvalidOid;
+	ListCell   *l;
+
+	if (nspname)
+	{
+		namespaceId = LookupExplicitNamespace(nspname, missing_ok);
+
+		/*
+		 * If nspname is not a known namespace, then nspname.varname cannot be
+		 * any usable session variable.
+		 */
+		if (!OidIsValid(namespaceId))
+			return InvalidOid;
+
+		varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+								 PointerGetDatum(varname),
+								 ObjectIdGetDatum(namespaceId));
+	}
+	else
+	{
+		/* Iterate over schemas in search_path */
+		recomputeNamespacePath();
+
+		foreach(l, activeSearchPath)
+		{
+			namespaceId = lfirst_oid(l);
+
+			varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+									 PointerGetDatum(varname),
+									 ObjectIdGetDatum(namespaceId));
+
+			if (OidIsValid(varoid))
+				break;
+		}
+	}
+
+	if (!OidIsValid(varoid) && !missing_ok)
+	{
+		if (nspname)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("session variable \"%s.%s\" does not exist",
+							nspname, varname)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("session variable \"%s\" does not exist",
+							varname)));
+	}
+
+	return varoid;
+}
+
+/*
+ * The input list contains names with indirection expressions used as the left
+ * part of LET statement. The following routine returns a new list with only
+ * initial strings (names) - without indirection expressions.
+ */
+List *
+NamesFromList(List *names)
+{
+	ListCell   *l;
+	List	   *result = NIL;
+
+	foreach(l, names)
+	{
+		Node	   *n = lfirst(l);
+
+		if (IsA(n, String))
+		{
+			result = lappend(result, n);
+		}
+		else
+			break;
+	}
+
+	return result;
+}
+
+/*
+ * IdentifyVariable - try to find variable identified by list of names.
+ *
+ * Before this call we don't know, how these fields should be mapped to
+ * schema name, variable name and attribute name. In this routine
+ * we try to apply passed names to all possible combinations of schema name,
+ * variable name and attribute name, and we count valid combinations.
+ *
+ * Returns oid of identified variable. When last field of names list is
+ * identified as an attribute, then output attrname argument is set to
+ * an string of this field.
+ *
+ * When there is not any valid combination, then we are sure, so the
+ * list of names cannot to identify any session variable. In this case
+ * we return InvalidOid.
+ *
+ * We can find more valid combination than one.
+ * Example: users can have session variable x in schema y, and
+ * session variable y with attribute x inside some schema from
+ * search path. In this situation the meaning of expression "y"."x"
+ * is ambiguous. In this case this routine returns InvalidOid, and
+ * sets the output parameter "not_unique" to true. This parameter is
+ * used for more meaningfull error message.
+ *
+ * When lockit is true, then AccessShareLock is created on related
+ * session variable. The lock will be kept for the whole transaction.
+ */
+Oid
+IdentifyVariable(List *names, char **attrname, bool lockit, bool *not_unique)
+{
+	Node	   *field1 = NULL;
+	Node	   *field2 = NULL;
+	Node	   *field3 = NULL;
+	Node	   *field4 = NULL;
+	char	   *a = NULL;
+	char	   *b = NULL;
+	char	   *c = NULL;
+	char	   *d = NULL;
+	Oid			varoid_without_attr = InvalidOid;
+	Oid			varoid_with_attr = InvalidOid;
+	Oid			varid = InvalidOid;
+
+	*not_unique = false;
+	*attrname = NULL;
+
+	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))
+			{
+				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. Second node can be star. In
+				 * this case, the only allowed possibility is "variable"."*".
+				 */
+				varoid_without_attr = LookupVariable(a, b, true);
+				varoid_with_attr = LookupVariable(NULL, a, true);
+			}
+			else
+			{
+				Assert(IsA(field2, A_Star));
+
+				/*
+				 * Session variables doesn't support unboxing by star syntax. But
+				 * this syntax have to be calculated here, because can come from
+				 * non session variables related expressions.
+				 */
+				return InvalidOid;
+			}
+
+			if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+			{
+				*not_unique = true;
+				return InvalidOid;
+			}
+			else if (OidIsValid(varoid_without_attr))
+			{
+				*attrname = NULL;
+				varid = varoid_without_attr;
+			}
+			else
+			{
+				*attrname = b;
+				varid = varoid_with_attr;
+			}
+			break;
+
+		case 3:
+			field1 = linitial(names);
+			field2 = lsecond(names);
+			field3 = lthird(names);
+
+			Assert(IsA(field1, String));
+			Assert(IsA(field2, String));
+
+			a = strVal(field1);
+			b = strVal(field2);
+
+			if (IsA(field3, String))
+			{
+				c = strVal(field3);
+
+				/*
+				 * a.b.c can mean "catalog"."schema"."variable" or
+				 * "schema"."variable"."field", Check both variants, and returns
+				 * InvalidOid with not_unique flag, when both interpretations are
+				 * possible. When third node is star, the only possible
+				 * interpretation is "schema"."variable"."*".
+				 */
+				varoid_without_attr = LookupVariable(b, c, true);
+				varoid_with_attr = LookupVariable(a, b, true);
+			}
+			else
+			{
+				Assert(IsA(field3, A_Star));
+				return InvalidOid;
+			}
+
+			if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+			{
+				*not_unique = true;
+				return InvalidOid;
+			}
+			else if (OidIsValid(varoid_without_attr))
+			{
+
+				/*
+				 * In this case, "a" is used as catalog name - check it.
+				 */
+				if (strcmp(a, get_database_name(MyDatabaseId)) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("cross-database references are not implemented: %s",
+									NameListToString(names))));
+
+				varid = varoid_without_attr;
+			}
+			else
+			{
+				*attrname = c;
+				varid = varoid_with_attr;
+			}
+			break;
+
+		case 4:
+			field1 = linitial(names);
+			field2 = lsecond(names);
+			field3 = lthird(names);
+			field4 = lfourth(names);
+
+			Assert(IsA(field1, String));
+			Assert(IsA(field2, String));
+			Assert(IsA(field3, String));
+
+			a = strVal(field1);
+			b = strVal(field2);
+			c = strVal(field3);
+
+			/*
+			 * In this case, "a" is used as catalog name - check it.
+			 */
+			if (strcmp(a, get_database_name(MyDatabaseId)) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cross-database references are not implemented: %s",
+								NameListToString(names))));
+
+			if (IsA(field4, String))
+			{
+				d = strVal(field4);
+			}
+			else
+			{
+				Assert(IsA(field4, A_Star));
+				return InvalidOid;
+			}
+
+			*attrname = d;
+			varid = LookupVariable(b, c, true);
+			break;
+
+		default:
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("improper qualified name (too many dotted names): %s",
+							NameListToString(names))));
+			break;
+	}
+
+	if (OidIsValid(varid) && lockit)
+		LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	return varid;
+}
 
 /*
  * DeconstructQualifiedName
@@ -4660,3 +5023,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 8377b4f7d4..f741d91634 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -63,6 +63,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"
@@ -618,6 +619,20 @@ static const ObjectPropertyType ObjectProperty[] =
 		OBJECT_USER_MAPPING,
 		false
 	},
+	{
+		"session variable",
+		VariableRelationId,
+		VariableObjectIndexId,
+		VARIABLEOID,
+		VARIABLENAMENSP,
+		Anum_pg_variable_oid,
+		Anum_pg_variable_varname,
+		Anum_pg_variable_varnamespace,
+		Anum_pg_variable_varowner,
+		Anum_pg_variable_varacl,
+		OBJECT_VARIABLE,
+		true
+	}
 };
 
 /*
@@ -850,6 +865,10 @@ static const struct object_type_map
 	/* OCLASS_STATISTIC_EXT */
 	{
 		"statistics object", OBJECT_STATISTIC_EXT
+	},
+	/* OCLASS_VARIABLE */
+	{
+		"session variable", OBJECT_VARIABLE
 	}
 };
 
@@ -875,6 +894,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,
@@ -1144,6 +1164,9 @@ get_object_address(ObjectType objtype, Node *object,
 															 missing_ok);
 				address.objectSubId = 0;
 				break;
+			case OBJECT_VARIABLE:
+				address = get_object_address_variable(castNode(List, object), missing_ok);
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				/* placate compiler, in case it thinks elog might return */
@@ -2020,16 +2043,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)));
 	}
 
 	/*
@@ -2114,6 +2141,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
  */
@@ -2304,6 +2349,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:
@@ -2614,6 +2660,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
 							   NameListToString(castNode(List, object)));
 			break;
+		case OBJECT_VARIABLE:
+			if (!pg_variable_ownercheck(address.objectId, roleid))
+				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
+							   NameListToString(castNode(List, object)));
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) objtype);
@@ -3515,6 +3566,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
+				Form_pg_variable varform;
+
+				tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for 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 OCLASS_TSPARSER:
 			{
 				HeapTuple	tup;
@@ -3825,6 +3902,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)
@@ -4597,6 +4684,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			appendStringInfoString(&buffer, "transform");
 			break;
 
+		case OCLASS_VARIABLE:
+			appendStringInfoString(&buffer, "session variable");
+			break;
+
 			/*
 			 * There's intentionally no default: case here; we want the
 			 * compiler to warn if a new OCLASS hasn't been handled above.
@@ -5661,6 +5752,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 						appendStringInfoString(&buffer,
 											   " on schemas");
 						break;
+					case DEFACLOBJ_VARIABLE:
+						appendStringInfoString(&buffer,
+											   " on session variables");
+						break;
 				}
 
 				if (objname)
@@ -5904,6 +5999,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
+				Form_pg_variable varform;
+
+				tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for 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;
+			}
+
 			/*
 			 * There's intentionally no default: case here; we want the
 			 * compiler to warn if a new OCLASS hasn't been handled above.
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 3e8fa008b9..cb58d9a0fd 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -46,6 +46,7 @@
 #include "catalog/pg_ts_dict.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "commands/alter.h"
 #include "commands/collationcmds.h"
 #include "commands/conversioncmds.h"
@@ -1593,6 +1594,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 				case DatabaseRelationId:
 				case TSConfigRelationId:
 				case TSDictionaryRelationId:
+				case VariableRelationId:
 					{
 						Oid			classId = sdepForm->classid;
 						Relation	catalog;
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
new file mode 100644
index 0000000000..cd85d4e700
--- /dev/null
+++ b/src/backend/catalog/pg_variable.c
@@ -0,0 +1,245 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.c
+ *		session variables
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		src/backend/catalog/pg_variable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
+#include "storage/lmgr.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+/*
+ * Fetch attributes (without acl) of session variable from the syscache.
+ * We don't work with acl directly, so we don't need to read it here.
+ * Skip deserialization of defexpr when fast_only is true.
+ */
+void
+initVariable(Variable *var, Oid varid, bool fast_only)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	Datum		defexpr_datum;
+	bool		defexpr_isnull;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	var->oid = varid;
+	var->name = pstrdup(NameStr(varform->varname));
+	var->namespace = varform->varnamespace;
+	var->typid = varform->vartype;
+	var->typmod = varform->vartypmod;
+	var->owner = varform->varowner;
+	var->collation = varform->varcollation;
+	var->is_immutable = varform->varisimmutable;
+	var->is_not_null = varform->varisnotnull;
+	var->eoxaction = varform->vareoxaction;
+
+	/* Get defexpr */
+	defexpr_datum = SysCacheGetAttr(VARIABLEOID,
+									tup,
+									Anum_pg_variable_vardefexpr,
+									&defexpr_isnull);
+
+	var->has_defexpr = !defexpr_isnull;
+
+	/*
+	 * Deserialize defexpr only when it is requested.
+	 * We need to deserialize Node with default expression,
+	 * only when we read from session variable, and this
+	 * session variable has not assigned value, and this
+	 * session variable has default expression. For other
+	 * cases, we skip skip this operation.
+	 */
+	if (!fast_only && !defexpr_isnull)
+		var->defexpr = stringToNode(TextDatumGetCString(defexpr_datum));
+	else
+		var->defexpr = NULL;
+
+	ReleaseSysCache(tup);
+}
+
+/*
+ * Create entry in pg_variable table
+ */
+ObjectAddress
+VariableCreate(const char *varName,
+			   Oid varNamespace,
+			   Oid varType,
+			   int32 varTypmod,
+			   Oid varOwner,
+			   Oid varCollation,
+			   Node *varDefexpr,
+			   VariableEOXAction eoxaction,
+			   bool is_not_null,
+			   bool if_not_exists,
+			   bool is_immutable)
+{
+	Acl		   *varacl;
+	NameData	varname;
+	bool		nulls[Natts_pg_variable];
+	Datum		values[Natts_pg_variable];
+	Relation	rel;
+	HeapTuple	tup;
+	TupleDesc	tupdesc;
+	ObjectAddress myself,
+				referenced;
+	ObjectAddresses *addrs;
+	Oid			varid;
+
+	AssertArg(varName);
+	AssertArg(OidIsValid(varNamespace));
+	AssertArg(OidIsValid(varType));
+	AssertArg(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, VariableObjectIndexId, Anum_pg_variable_oid);
+
+	values[Anum_pg_variable_oid - 1] = ObjectIdGetDatum(varid);
+	values[Anum_pg_variable_varname - 1] = NameGetDatum(&varname);
+	values[Anum_pg_variable_varnamespace - 1] = ObjectIdGetDatum(varNamespace);
+	values[Anum_pg_variable_vartype - 1] = ObjectIdGetDatum(varType);
+	values[Anum_pg_variable_vartypmod - 1] = Int32GetDatum(varTypmod);
+	values[Anum_pg_variable_varowner - 1] = ObjectIdGetDatum(varOwner);
+	values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation);
+	values[Anum_pg_variable_varisnotnull - 1] = BoolGetDatum(is_not_null);
+	values[Anum_pg_variable_varisimmutable - 1] = BoolGetDatum(is_immutable);
+	values[Anum_pg_variable_vareoxaction - 1] = CharGetDatum(eoxaction);
+
+	/* varacl will be determined later */
+
+	if (varDefexpr)
+		values[Anum_pg_variable_vardefexpr - 1] = CStringGetTextDatum(nodeToString(varDefexpr));
+	else
+		nulls[Anum_pg_variable_vardefexpr - 1] = true;
+
+	tupdesc = RelationGetDescr(rel);
+
+	varacl = get_user_default_acl(OBJECT_VARIABLE, varOwner,
+								  varNamespace);
+
+	if (varacl != NULL)
+		values[Anum_pg_variable_varacl - 1] = PointerGetDatum(varacl);
+	else
+		nulls[Anum_pg_variable_varacl - 1] = true;
+
+	tup = heap_form_tuple(tupdesc, values, nulls);
+	CatalogTupleInsert(rel, tup);
+	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);
+	}
+
+	/* dependency on default expr */
+	if (varDefexpr)
+		recordDependencyOnExpr(&myself, (Node *) varDefexpr,
+							   NIL, DEPENDENCY_NORMAL);
+
+	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);
+
+	/*
+	 * For temporary variables, we need to create a new end of xact action to
+	 * ensure deletion from catalog.
+	 */
+	if (eoxaction == VARIABLE_EOX_DROP)
+	{
+		Assert(isTempNamespace(varNamespace));
+
+		RegisterOnCommitDropSessionVariable(myself.objectId);
+	}
+
+	heap_freetuple(tup);
+
+	/* Post creation hook for new function */
+	InvokeObjectPostCreateHook(VariableRelationId, varid, 0);
+
+	table_close(rel, RowExclusiveLock);
+
+	return myself;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91..41f98f8ba0 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -48,6 +48,7 @@ OBJS = \
 	proclang.o \
 	publicationcmds.o \
 	schemacmds.o \
+	sessionvariable.o \
 	seclabel.o \
 	sequence.o \
 	statscmds.o \
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 5456b8222b..e8244f5cd6 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -40,6 +40,7 @@
 #include "catalog/pg_ts_dict.h"
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
+#include "catalog/pg_variable.h"
 #include "commands/alter.h"
 #include "commands/collationcmds.h"
 #include "commands/conversioncmds.h"
@@ -141,6 +142,10 @@ report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
 			Assert(OidIsValid(nspOid));
 			msgfmt = gettext_noop("text search configuration \"%s\" already exists in schema \"%s\"");
 			break;
+		case VariableRelationId:
+			Assert(OidIsValid(nspOid));
+			msgfmt = gettext_noop("session variable \"%s\" already exists in schema \"%s\"");
+			break;
 		default:
 			elog(ERROR, "unsupported object class %u", classId);
 			break;
@@ -393,6 +398,7 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_PUBLICATION:
 		case OBJECT_SUBSCRIPTION:
+		case OBJECT_VARIABLE:
 			{
 				ObjectAddress address;
 				Relation	catalog;
@@ -536,6 +542,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_VARIABLE:
 			{
 				Relation	catalog;
 				Relation	relation;
@@ -626,6 +633,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 		case OCLASS_TSDICT:
 		case OCLASS_TSTEMPLATE:
 		case OCLASS_TSCONFIG:
+		case OCLASS_VARIABLE:
 			{
 				Relation	catalog;
 
@@ -885,6 +893,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 		case OBJECT_TABLESPACE:
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSCONFIGURATION:
+		case OBJECT_VARIABLE:
 			{
 				Relation	catalog;
 				Relation	relation;
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index c9b5732448..e526143bdc 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -481,6 +481,10 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 			msg = gettext_noop("publication \"%s\" does not exist, skipping");
 			name = strVal(object);
 			break;
+		case OBJECT_VARIABLE:
+			msg = gettext_noop("session variable \"%s\" does not exist, skipping");
+			name = NameListToString(castNode(List, object));
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d", (int) objtype);
 			break;
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 4642527881..d32112ae5f 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -992,6 +992,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_TYPE:
 		case OBJECT_USER_MAPPING:
+		case OBJECT_VARIABLE:
 		case OBJECT_VIEW:
 			return true;
 
@@ -1057,6 +1058,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_PUBLICATION_REL:
 		case OCLASS_SUBSCRIPTION:
 		case OCLASS_TRANSFORM:
+		case OCLASS_VARIABLE:
 			return true;
 
 			/*
@@ -2054,6 +2056,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:
@@ -2137,6 +2141,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 7ae19b98bb..9b11374535 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/sessionvariable.c b/src/backend/commands/sessionvariable.c
new file mode 100644
index 0000000000..213a2b9c4c
--- /dev/null
+++ b/src/backend/commands/sessionvariable.c
@@ -0,0 +1,348 @@
+/*-------------------------------------------------------------------------
+ *
+ * sessionvariable.c
+ *	  session variable creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/sessionvariable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "miscadmin.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/xact.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
+#include "executor/executor.h"
+#include "optimizer/optimizer.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_type.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+/*
+ * The life cycle of temporary session variable can be
+ * limmited by using clause ON COMMIT DROP.
+ */
+typedef enum SVariableXActAction
+{
+	SVAR_ON_COMMIT_DROP,		/* used for ON COMMIT DROP */
+} SVariableXActAction;
+
+typedef struct SVariableXActActionItem
+{
+	Oid			varid;			/* varid of session variable */
+	SVariableXActAction action;	/* reset or drop */
+
+	/*
+	 * creating_subid is the ID of the creating subxact. If the action was
+	 * unregistered during the current transaction, deleting_subid is the ID of
+	 * the deleting subxact, otherwise InvalidSubTransactionId.
+	 */
+	SubTransactionId creating_subid;
+	SubTransactionId deleting_subid;
+}  SVariableXActActionItem;
+
+/* List holds fields of SVariableXActActionItem type */
+static List *xact_drop_actions = NIL;
+
+static void register_session_variable_xact_action(Oid varid, SVariableXActAction action);
+static void unregister_session_variable_xact_action(Oid varid, SVariableXActAction action);
+
+/*
+ * Routines used for manipulation with session variables from
+ * SQL level
+ */
+
+/*
+ * Creates new variable - entry in pg_catalog.pg_variable table
+ *
+ * Used by CREATE VARIABLE command
+ */
+ObjectAddress
+DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
+{
+	Oid			namespaceid;
+	AclResult	aclresult;
+	Oid			typid;
+	int32		typmod;
+	Oid			varowner = GetUserId();
+	Oid			collation;
+	Oid			typcollation;
+	ObjectAddress variable;
+
+	Node	   *cooked_default = NULL;
+
+	/*
+	 * Check consistency of arguments
+	 */
+	if (stmt->eoxaction == VARIABLE_EOX_DROP
+		&& stmt->variable->relpersistence != RELPERSISTENCE_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("ON COMMIT DROP can only be used on temporary variables")));
+
+	if (stmt->is_not_null && stmt->is_immutable && !stmt->defexpr)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("IMMUTABLE NOT NULL variable requires default expression")));
+
+	namespaceid =
+		RangeVarGetAndCheckCreationNamespace(stmt->variable, NoLock, NULL);
+
+	typenameTypeIdAndMod(pstate, stmt->typeName, &typid, &typmod);
+	typcollation = get_typcollation(typid);
+
+	aclresult = pg_type_aclcheck(typid, GetUserId(), ACL_USAGE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error_type(aclresult, typid);
+
+	if (stmt->collClause)
+		collation = LookupCollation(pstate,
+									stmt->collClause->collname,
+									stmt->collClause->location);
+	else
+		collation = typcollation;;
+
+	/* Complain if COLLATE is applied to an uncollatable type */
+	if (OidIsValid(collation) && !OidIsValid(typcollation))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("collations are not supported by type %s",
+						format_type_be(typid)),
+				 parser_errposition(pstate, stmt->collClause->location)));
+
+	if (stmt->defexpr)
+	{
+		cooked_default = transformExpr(pstate, stmt->defexpr,
+									   EXPR_KIND_VARIABLE_DEFAULT);
+
+		cooked_default = coerce_to_specific_type(pstate,
+												 cooked_default, typid, "DEFAULT");
+		assign_expr_collations(pstate, cooked_default);
+	}
+
+	variable = VariableCreate(stmt->variable->relname,
+							  namespaceid,
+							  typid,
+							  typmod,
+							  varowner,
+							  collation,
+							  cooked_default,
+							  stmt->eoxaction,
+							  stmt->is_not_null,
+							  stmt->if_not_exists,
+							  stmt->is_immutable);
+
+	/*
+	 * We must bump the command counter to make the newly-created variable
+	 * tuple visible for any other operations.
+	 */
+	CommandCounterIncrement();
+
+	return variable;
+}
+
+/*
+ * Create new ON_COMMIT_DROP xact action. We have to drop
+ * ON COMMIT DROP variable from system catalogue on commit
+ * time always, so we have to register ON_COMMIT_DROP
+ * action immediately when variable is created.
+ */
+void
+RegisterOnCommitDropSessionVariable(Oid varid)
+{
+	register_session_variable_xact_action(varid, SVAR_ON_COMMIT_DROP);
+}
+
+/*
+ * Drop variable by OID. This routine doesn't try to remove
+ * the value of session variable immediately. It will be
+ * removed on transaction end in sync_sessionvars_xact_callback
+ * routine. This routine manipulate just with system catalog.
+ */
+void
+RemoveSessionVariable(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);
+
+	/*
+	 * We removed entry from catalog already, we should not do it
+	 * again at end of xact time.
+	 */
+	unregister_session_variable_xact_action(varid, SVAR_ON_COMMIT_DROP);
+}
+
+/*
+ * Registration of actions to be executed on session variables at transaction
+ * end time. We want to drop temporary session variables with clause ON COMMIT
+ * DROP, or we want to reset values of session variables with clause ON
+ * TRANSACTION END RESET or we want to clean (reset) local memory allocated by
+ * values of dropped session variables.
+ */
+
+/*
+ * Register a session variable xact action.
+ */
+static void
+register_session_variable_xact_action(Oid varid,
+									  SVariableXActAction action)
+{
+	SVariableXActActionItem *xact_ai;
+	MemoryContext oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+	xact_ai = (SVariableXActActionItem *)
+							palloc(sizeof(SVariableXActActionItem));
+
+	xact_ai->varid = varid;
+	xact_ai->action = action;
+
+	xact_ai->creating_subid = GetCurrentSubTransactionId();
+	xact_ai->deleting_subid = InvalidSubTransactionId;
+
+	Assert(action == SVAR_ON_COMMIT_DROP);
+	xact_drop_actions = lcons(xact_ai, xact_drop_actions);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Unregister an action on a given session variable from action list. In this
+ * moment, the action is just marked as deleted by setting deleting_subid. The
+ * calling even might be rollbacked, in which case we should not lose this
+ * action.
+ */
+static void
+unregister_session_variable_xact_action(Oid varid,
+									SVariableXActAction action)
+{
+	ListCell   *l;
+
+	Assert(action == SVAR_ON_COMMIT_DROP);
+
+	foreach(l, xact_drop_actions)
+	{
+		SVariableXActActionItem *xact_ai =
+					(SVariableXActActionItem *) lfirst(l);
+
+		if (xact_ai->varid == varid && xact_ai->action == action)
+			xact_ai->deleting_subid = GetCurrentSubTransactionId();
+	}
+}
+
+/*
+ * Perform ON TRANSACTION END RESET or ON COMMIT DROP
+ * and COMMIT/ROLLBACK of transaction session variables.
+ */
+void
+AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
+{
+	ListCell   *l;
+
+	foreach(l, xact_drop_actions)
+	{
+		SVariableXActActionItem *xact_ai =
+							(SVariableXActActionItem *) lfirst(l);
+
+		Assert(xact_ai->action == SVAR_ON_COMMIT_DROP);
+
+		/* Iterate only over entries that are still pending */
+		if (xact_ai->deleting_subid == InvalidSubTransactionId)
+		{
+
+			/*
+			 * ON COMMIT DROP is allowed only for temp session
+			 * variables. So we should explicitly delete only when
+			 * current transaction was committed. When it's rollback,
+			 * then session variable is removed automatically.
+			 */
+			if (isCommit)
+			{
+				ObjectAddress object;
+
+				object.classId = VariableRelationId;
+				object.objectId = xact_ai->varid;
+				object.objectSubId = 0;
+
+				/*
+				 * Since this is an automatic drop, rather than one
+				 * directly initiated by the user, we pass the
+				 * PERFORM_DELETION_INTERNAL flag.
+				 */
+				performDeletion(&object, DROP_CASCADE,
+								PERFORM_DELETION_INTERNAL |
+								PERFORM_DELETION_QUIETLY);
+			}
+		}
+	}
+
+	/*
+	 * Any drop action left is an entry that was unregistered and not
+	 * rollbacked, so we can simply remove them.
+	 */
+	list_free_deep(xact_drop_actions);
+	xact_drop_actions = NIL;
+}
+
+/*
+ * Post-subcommit or post-subabort cleanup of xact action list.
+ *
+ * During subabort, we can immediately remove entries created during this
+ * subtransaction. During subcommit, just transfer entries marked during
+ * this subtransaction as being the parent's responsibility.
+ */
+void
+AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySubid,
+											SubTransactionId parentSubid)
+{
+	ListCell   *cur_item;
+
+	foreach(cur_item, xact_drop_actions)
+	{
+		SVariableXActActionItem *xact_ai =
+								  (SVariableXActActionItem *) lfirst(cur_item);
+
+		if (!isCommit && xact_ai->creating_subid == mySubid)
+		{
+			/* cur_item must be removed */
+			xact_drop_actions = foreach_delete_current(xact_drop_actions, cur_item);
+			pfree(xact_ai);
+		}
+		else
+		{
+			/* cur_item must be preserved */
+			if (xact_ai->creating_subid == mySubid)
+				xact_ai->creating_subid = parentSubid;
+			if (xact_ai->deleting_subid == mySubid)
+				xact_ai->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId;
+		}
+	}
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2de0ebacec..0c60048978 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12685,6 +12685,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_PUBLICATION_REL:
 			case OCLASS_SUBSCRIPTION:
 			case OCLASS_TRANSFORM:
+			case OCLASS_VARIABLE:
 
 				/*
 				 * We don't expect any of these sorts of objects to depend on
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 51d630fa89..176d4dd866 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -5453,6 +5453,23 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
 	return newnode;
 }
 
+static CreateSessionVarStmt *
+_copyCreateSessionVarStmt(const CreateSessionVarStmt *from)
+{
+	CreateSessionVarStmt *newnode = makeNode(CreateSessionVarStmt);
+
+	COPY_NODE_FIELD(variable);
+	COPY_NODE_FIELD(typeName);
+	COPY_NODE_FIELD(collClause);
+	COPY_NODE_FIELD(defexpr);
+	COPY_SCALAR_FIELD(eoxaction);
+	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(is_not_null);
+	COPY_SCALAR_FIELD(is_immutable);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					extensible.h copy functions
  * ****************************************************************
@@ -6404,6 +6421,9 @@ copyObjectImpl(const void *from)
 		case T_DropSubscriptionStmt:
 			retval = _copyDropSubscriptionStmt(from);
 			break;
+		case  T_CreateSessionVarStmt:
+			retval = _copyCreateSessionVarStmt(from);
+			break;
 		case T_A_Expr:
 			retval = _copyA_Expr(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e747e1667d..b9ef00e328 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2744,6 +2744,22 @@ _equalDropSubscriptionStmt(const DropSubscriptionStmt *a,
 	return true;
 }
 
+static bool
+_equalCreateSessionVarStmt(const CreateSessionVarStmt *a,
+						  const CreateSessionVarStmt *b)
+{
+	COMPARE_NODE_FIELD(variable);
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_NODE_FIELD(collClause);
+	COMPARE_NODE_FIELD(defexpr);
+	COMPARE_SCALAR_FIELD(eoxaction);
+	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(is_not_null);
+	COMPARE_SCALAR_FIELD(is_immutable);
+
+	return true;
+}
+
 static bool
 _equalCreatePolicyStmt(const CreatePolicyStmt *a, const CreatePolicyStmt *b)
 {
@@ -4207,6 +4223,9 @@ equal(const void *a, const void *b)
 		case T_DropSubscriptionStmt:
 			retval = _equalDropSubscriptionStmt(a, b);
 			break;
+		case T_CreateSessionVarStmt:
+			retval = _equalCreateSessionVarStmt(a, b);
+			break;
 		case T_A_Expr:
 			retval = _equalA_Expr(a, b);
 			break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 969c9c158f..73904daf8d 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 "nodes/makefuncs.h"
@@ -304,8 +305,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
 		CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt
 		CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
-		CreateSchemaStmt CreateSeqStmt CreateStmt CreateStatsStmt CreateTableSpaceStmt
-		CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
+		CreateSchemaStmt CreateSessionVarStmt CreateSeqStmt CreateStmt CreateStatsStmt
+		CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
 		CreateAssertionStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
 		CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
 		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
@@ -478,6 +479,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	 OptTemp
 %type <ival>	 OptNoLog
 %type <oncommit> OnCommitOption
+%type <ival>	 OnEOXActionOption
 
 %type <ival>	for_locking_strength
 %type <node>	for_locking_item
@@ -646,6 +648,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <partboundspec> PartitionBoundSpec
 %type <list>		hash_partbound
 %type <defelt>		hash_partbound_elem
+%type <node>		OptSessionVarDefExpr
+%type <boolean>		OptNotNull OptImmutable

 %type <node>		json_format_clause_opt
@@ -852,8 +856,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
 
@@ -1111,6 +1115,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -5094,6 +5099,69 @@ create_extension_opt_item:
 				}
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY :
+ *				CREATE VARIABLE varname [AS] type
+ *
+ *****************************************************************************/
+
+CreateSessionVarStmt:
+			CREATE OptTemp OptImmutable VARIABLE qualified_name opt_as Typename opt_collate_clause OptNotNull OptSessionVarDefExpr OnEOXActionOption
+				{
+					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
+					$5->relpersistence = $2;
+					n->is_immutable = $3;
+					n->variable = $5;
+					n->typeName = $7;
+					n->collClause = (CollateClause *) $8;
+					n->is_not_null = $9;
+					n->defexpr = $10;
+					n->eoxaction = $11;
+					n->if_not_exists = false;
+					$$ = (Node *) n;
+				}
+			| CREATE OptTemp OptImmutable VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OptNotNull OptSessionVarDefExpr OnEOXActionOption
+				{
+					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
+					$8->relpersistence = $2;
+					n->is_immutable = $3;
+					n->variable = $8;
+					n->typeName = $10;
+					n->collClause = (CollateClause *) $11;
+					n->is_not_null = $12;
+					n->defexpr = $13;
+					n->eoxaction = $14;
+					n->if_not_exists = true;
+					$$ = (Node *) n;
+				}
+		;
+
+OptSessionVarDefExpr: DEFAULT b_expr					{ $$ = $2; }
+			| /* EMPTY */							{ $$ = NULL; }
+		;
+
+/*
+ * Temporary session variables can be dropped on successful
+ * transaction end like tables.  RESET can only be forced on
+ * transaction end. Since the session variables are not
+ * transactional, we have to handle ROLLBACK too.
+ * The clause ON TRANSACTION END is clearer than some
+ * ON COMMIT ROLLBACK RESET clause.
+ */
+OnEOXActionOption:  ON COMMIT DROP					{ $$ = VARIABLE_EOX_DROP; }
+			| ON TRANSACTION END_P RESET			{ $$ = VARIABLE_EOX_RESET; }
+			| /*EMPTY*/								{ $$ = VARIABLE_EOX_NOOP; }
+		;
+
+OptNotNull: NOT NULL_P								{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
+OptImmutable: IMMUTABLE								{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
@@ -6871,6 +6939,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; }
 		;
 
 /*
@@ -7747,6 +7816,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));
@@ -7792,6 +7869,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;
+				}
 		;

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

@@ -9763,6 +9849,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
+			| ALTER VARIABLE any_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_VARIABLE;
+					n->object = (Node *) $3;
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER VARIABLE IF_P EXISTS any_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_VARIABLE;
+					n->object = (Node *) $5;
+					n->newname = $8;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+
 		;
 
 opt_column: COLUMN
@@ -10124,6 +10229,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
+			| ALTER VARIABLE any_name SET SCHEMA name
+				{
+					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+					n->objectType = OBJECT_VARIABLE;
+					n->object = (Node *) $3;
+					n->newschema = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER VARIABLE IF_P EXISTS any_name SET SCHEMA name
+				{
+					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+					n->objectType = OBJECT_VARIABLE;
+					n->object = (Node *) $5;
+					n->newschema = $8;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+
 		;
 
 /*****************************************************************************
@@ -10403,6 +10527,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;
+				}
 		;

@@ -17975,6 +18107,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18621,6 +18755,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3ef9e8ee5e..dbe630a51b 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -471,6 +471,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 
 			if (isAgg)
 				err = _("aggregate functions are not allowed in DEFAULT expressions");
@@ -915,6 +916,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("window functions are not allowed in DEFAULT expressions");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 0dc2fc472e..69e6318b31 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"
@@ -567,6 +568,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_COPY_WHERE:
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_VARIABLE_DEFAULT:
+
 			/* okay */
 			break;
 
@@ -1798,6 +1801,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("cannot use subquery in DEFAULT expression");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
@@ -3130,6 +3134,7 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "CHECK";
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			return "DEFAULT";
 		case EXPR_KIND_INDEX_EXPRESSION:
 			return "index expression";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index f71a682cd6..c189bd1276 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2620,6 +2620,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("set-returning functions are not allowed in DEFAULT expressions");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6a5bcded55..030de80295 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -49,6 +49,7 @@
 #include "commands/proclang.h"
 #include "commands/publicationcmds.h"
 #include "commands/schemacmds.h"
+#include "commands/session_variable.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
 #include "commands/subscriptioncmds.h"
@@ -189,6 +190,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:
@@ -1393,6 +1395,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				}
 				break;
 
+			case T_CreateSessionVarStmt:
+				address = DefineSessionVariable(pstate, (CreateSessionVarStmt *) parsetree);
+				break;
+
 				/*
 				 * ************* object creation / destruction **************
 				 */
@@ -2332,6 +2338,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;
@@ -2640,6 +2649,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;
 			}
@@ -3216,6 +3228,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 772c04155c..430f161af6 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -315,6 +315,12 @@ aclparse(const char *s, AclItem *aip)
 			case ACL_ALTER_SYSTEM_CHR:
 				read = ACL_ALTER_SYSTEM;
 				break;
+			case ACL_READ_CHR:
+				read = ACL_READ;
+				break;
+			case ACL_WRITE_CHR:
+				read = ACL_WRITE;
+				break;
 			case 'R':			/* ignore old RULE privileges */
 				read = 0;
 				break;
@@ -807,6 +813,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 objtype: %d", (int) objtype);
 			world_default = ACL_NO_RIGHTS;	/* keep compiler quiet */
@@ -904,6 +914,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
 		case 'T':
 			objtype = OBJECT_TYPE;
 			break;
+		case 'V':
+			objtype = OBJECT_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype abbreviation: %c", objtypec);
 	}
@@ -1624,6 +1637,10 @@ convert_priv_string(text *priv_type_text)
 		return ACL_ALTER_SYSTEM;
 	if (pg_strcasecmp(priv_type, "RULE") == 0)
 		return 0;				/* ignore old RULE privileges */
+	if (pg_strcasecmp(priv_type, "READ") == 0)
+		return ACL_READ;
+	if (pg_strcasecmp(priv_type, "WRITE") == 0)
+		return ACL_WRITE;
 
 	ereport(ERROR,
 			(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1722,6 +1739,10 @@ convert_aclright_to_string(int aclright)
 			return "SET";
 		case ACL_ALTER_SYSTEM:
 			return "ALTER SYSTEM";
+		case ACL_READ:
+			return "READ";
+		case ACL_WRITE:
+			return "WRITE";
 		default:
 			elog(ERROR, "unrecognized aclright: %d", aclright);
 			return NULL;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1b7e11b93e..4459b18287 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3578,3 +3579,92 @@ get_index_isclustered(Oid index_oid)
 
 	return isclustered;
 }
+
+/*				---------- PG_VARIABLE CACHE ----------				 */
+
+
+/*
+ * get_varname_varid
+ *		Given name and namespace of variable, look up the OID.
+ */
+Oid
+get_varname_varid(const char *varname, Oid varnamespace)
+{
+	return GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+						   PointerGetDatum(varname),
+						   ObjectIdGetDatum(varnamespace));
+}
+
+/*
+ * get_session_variable_name
+ *		Returns a palloc'd copy of the name of a given session variable.
+ */
+char *
+get_session_variable_name(Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	char	   *varname;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	varname = pstrdup(NameStr(varform->varname));
+
+	ReleaseSysCache(tup);
+
+	return varname;
+}
+
+/*
+ * get_session_variable_namespace
+ *		Returns the pg_namespace OID associated with a given session variable.
+ */
+Oid
+get_session_variable_namespace(Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	Oid			varnamespace;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	varnamespace = varform->varnamespace;
+
+	ReleaseSysCache(tup);
+
+	return varnamespace;
+}
+
+/*
+ * Returns the type, typmod and collid of the given session variable.
+ */
+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/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 1912b12146..7b16f75d1f 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -75,6 +75,7 @@
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "lib/qunique.h"
 #include "utils/catcache.h"
 #include "utils/rel.h"
@@ -1037,6 +1038,28 @@ static const struct cachedesc cacheinfo[] = {
 			0
 		},
 		2
+	},
+	{VariableRelationId,		/* VARIABLENAMENSP */
+		VariableNameNspIndexId,
+		2,
+		{
+			Anum_pg_variable_varname,
+			Anum_pg_variable_varnamespace,
+			0,
+			0
+		},
+		8
+	},
+	{VariableRelationId,		/* VARIABLEOID */
+		VariableObjectIndexId,
+		1,
+		{
+			Anum_pg_variable_oid,
+			0,
+			0,
+			0
+		},
+		8
 	}
 };
 
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index a9dd068095..af74fe345e 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1896,9 +1896,9 @@ get_call_expr_arg_stable(Node *expr, int argnum)
 	arg = (Node *) list_nth(args, argnum);
 
 	/*
-	 * Either a true Const or an external Param will have a value that doesn't
-	 * change during the execution of the query.  In future we might want to
-	 * consider other cases too, e.g. now().
+	 * Either a true Const or an external Param or variable will have a value
+	 * that doesn't change during the execution of the query.  In future we
+	 * might want to consider other cases too, e.g. now().
 	 */
 	if (IsA(arg, Const))
 		return true;
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index d027075a4c..423864b9a2 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -126,10 +126,11 @@ typedef enum ObjectClass
 	OCLASS_PUBLICATION_NAMESPACE,	/* pg_publication_namespace */
 	OCLASS_PUBLICATION_REL,		/* pg_publication_rel */
 	OCLASS_SUBSCRIPTION,		/* pg_subscription */
-	OCLASS_TRANSFORM			/* pg_transform */
+	OCLASS_TRANSFORM,			/* pg_transform */
+	OCLASS_VARIABLE				/* pg_variable */
 } ObjectClass;
 
-#define LAST_OCLASS		OCLASS_TRANSFORM
+#define LAST_OCLASS		OCLASS_VARIABLE
 
 /* flag bits for performDeletion/performMultipleDeletions: */
 #define PERFORM_DELETION_INTERNAL			0x0001	/* internal action */
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 1bc55c01a5..c5d445b9e1 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -96,6 +96,8 @@ extern Oid	TypenameGetTypid(const char *typname);
 extern Oid	TypenameGetTypidExtended(const char *typname, bool temp_ok);
 extern bool TypeIsVisible(Oid typid);
 
+extern bool VariableIsVisible(Oid varid);
+
 extern FuncCandidateList FuncnameGetCandidates(List *names,
 											   int nargs, List *argnames,
 											   bool expand_variadic,
@@ -164,6 +166,10 @@ extern void SetTempNamespaceState(Oid tempNamespaceId,
 								  Oid tempToastNamespaceId);
 extern void ResetTempTableNamespace(void);
 
+extern List *NamesFromList(List *names);
+extern Oid	LookupVariable(const char *nspname, const char *varname, bool missing_ok);
+extern Oid	IdentifyVariable(List *names, char **attrname, bool lockit, bool *not_unique);
+
 extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context);
 extern OverrideSearchPath *CopyOverrideSearchPath(OverrideSearchPath *path);
 extern bool OverrideSearchPathMatchesCurrent(OverrideSearchPath *path);
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index 2a79155636..672c571562 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -66,6 +66,7 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_default_acl_oid_index, 828, DefaultAclOidIndexId, o
 #define DEFACLOBJ_FUNCTION		'f' /* function */
 #define DEFACLOBJ_TYPE			'T' /* type */
 #define DEFACLOBJ_NAMESPACE		'n' /* namespace */
+#define DEFACLOBJ_VARIABLE		'V' /* variable */
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87aa571a33..6f8994802b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6274,6 +6274,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..3dd4455921
--- /dev/null
+++ b/src/include/catalog/pg_variable.h
@@ -0,0 +1,105 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.h
+ *	  definition of session variables system catalog (pg_variables)
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_variable.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_VARIABLE_H
+#define PG_VARIABLE_H
+
+#include "catalog/genbki.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_variable_d.h"
+#include "utils/acl.h"
+
+/* ----------------
+ *		pg_variable definition.  cpp turns this into
+ *		typedef struct FormData_pg_variable
+ * ----------------
+ */
+CATALOG(pg_variable,9222,VariableRelationId)
+{
+	Oid			oid;			/* oid */
+	NameData	varname;		/* variable name */
+	Oid			varnamespace;	/* OID of namespace containing variable class */
+	Oid			vartype;		/* OID of entry in pg_type for variable's type */
+	int32		vartypmod;		/* typmode for variable's type */
+	Oid			varowner;		/* class owner */
+	Oid			varcollation;	/* variable collation */
+	bool		varisnotnull;	/* Don't allow NULL */
+	bool		varisimmutable;	/* Don't allow changes */
+	char		vareoxaction;	/* action on transaction end */
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+
+	/* list of expression trees for variable default (NULL if none) */
+	pg_node_tree vardefexpr BKI_DEFAULT(_null_);
+
+	aclitem		varacl[1] BKI_DEFAULT(_null_);	/* access permissions */
+
+#endif
+} FormData_pg_variable;
+
+typedef enum VariableEOXAction
+{
+	VARIABLE_EOX_NOOP = 'n',	/* NOOP */
+	VARIABLE_EOX_DROP = 'd',	/* ON COMMIT DROP */
+	VARIABLE_EOX_RESET = 'r',	/* ON TRANSACTION END RESET */
+}			VariableEOXAction;
+
+/* ----------------
+ *		Form_pg_variable corresponds to a pointer to a tuple with
+ *		the format of pg_variable relation.
+ * ----------------
+ */
+typedef FormData_pg_variable *Form_pg_variable;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_variable_oid_index, 9223, VariableOidIndexId, on pg_variable using btree(oid oid_ops));
+#define VariableObjectIndexId 9223
+
+DECLARE_UNIQUE_INDEX(pg_variable_varname_nsp_index, 9224, VariableNameNspIndexId,  on pg_variable using btree(varname name_ops, varnamespace oid_ops));
+#define VariableNameNspIndexId  9224
+
+typedef struct Variable
+{
+	Oid			oid;
+	char	   *name;
+	Oid			namespace;
+	Oid			typid;
+	int32		typmod;
+	Oid			owner;
+	Oid			collation;
+	bool		is_not_null;
+	bool		is_immutable;
+	VariableEOXAction eoxaction;
+	bool		has_defexpr;
+	Node	   *defexpr;
+} Variable;
+
+extern void initVariable(Variable *var,
+						 Oid varid,
+						 bool fast_only);
+extern ObjectAddress VariableCreate(const char *varName,
+									Oid varNamespace,
+									Oid varType,
+									int32 varTypmod,
+									Oid varOwner,
+									Oid varCollation,
+									Node *varDefexpr,
+									VariableEOXAction eoxaction,
+									bool is_not_null,
+									bool if_not_exists,
+									bool is_immutable);
+
+#endif							/* PG_VARIABLE_H */
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
new file mode 100644
index 0000000000..5434e79f54
--- /dev/null
+++ b/src/include/commands/session_variable.h
@@ -0,0 +1,36 @@
+/*-------------------------------------------------------------------------
+ *
+ * sessionvariable.h
+ *	  prototypes for sessionvariable.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/session_variable.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SESSIONVARIABLE_H
+#define SESSIONVARIABLE_H
+
+#include "catalog/objectaddress.h"
+#include "catalog/pg_variable.h"
+#include "nodes/params.h"
+#include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
+#include "tcop/cmdtag.h"
+#include "utils/queryenvironment.h"
+
+extern void ResetSessionVariables(void);
+extern void RemoveSessionVariable(Oid varid);
+extern ObjectAddress DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt * stmt);
+
+extern void RegisterOnCommitDropSessionVariable(Oid varid);
+
+extern void AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit);
+extern void AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySubid,
+											SubTransactionId parentSubid);
+
+#endif
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b3b407579b..d49bc66fc0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -372,6 +372,7 @@ typedef enum NodeTag
 	T_CreateTableAsStmt,
 	T_CreateSeqStmt,
 	T_AlterSeqStmt,
+	T_CreateSessionVarStmt,
 	T_VariableSetStmt,
 	T_VariableShowStmt,
 	T_DiscardStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 73f635b455..6b825785a7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -94,7 +94,9 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
 #define ACL_CONNECT		(1<<11) /* for databases */
 #define ACL_SET			(1<<12) /* for configuration parameters */
 #define ACL_ALTER_SYSTEM (1<<13)	/* for configuration parameters */
-#define N_ACL_RIGHTS	14		/* 1 plus the last 1<<x */
+#define ACL_READ		(1<<14) /* for variables */
+#define ACL_WRITE		(1<<15) /* for variables */
+#define N_ACL_RIGHTS	16		/* 1 plus the last 1<<x */
 #define ACL_NO_RIGHTS	0
 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
 #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
@@ -2183,6 +2185,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -3307,6 +3310,25 @@ 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;
+	Node	   *defexpr;		/* default expression */
+	char		eoxaction;		/* on commit action */
+	bool		if_not_exists;	/* do nothing if it already exists */
+	bool		is_not_null;	/* Disallow nulls */
+	bool		is_immutable;	/* Don't allow changes */
+}			CreateSessionVarStmt;
+
+
 /* ----------------------
  *		Create Function Statement
  * ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ae35f03251..d7cfc9a6f3 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -479,6 +479,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("variables", VARIABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index cf9c759025..9b042962fc 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -81,6 +81,7 @@ typedef enum ParseExprKind
 	EXPR_KIND_COPY_WHERE,		/* WHERE condition in COPY FROM */
 	EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */
 	EXPR_KIND_CYCLE_MARK,		/* cycle mark value */
+	EXPR_KIND_VARIABLE_DEFAULT	/* default value for session variable */
 } ParseExprKind;

diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 2b1163ce33..e76bec9fad 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 48f7d72add..ecdc49e59a 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -148,9 +148,11 @@ typedef struct ArrayType Acl;
 #define ACL_CONNECT_CHR			'c'
 #define ACL_SET_CHR				's'
 #define ACL_ALTER_SYSTEM_CHR	'A'
+#define ACL_READ_CHR			'S' /* 'R' is occupated by old RULE priv */
+#define ACL_WRITE_CHR			'W'
 
 /* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTcsA"
+#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTcsASW"
 
 /*
  * Bitmasks defining "all rights" for each supported object type
@@ -168,6 +170,7 @@ typedef struct ArrayType Acl;
 #define ACL_ALL_RIGHTS_SCHEMA		(ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE	(ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE			(ACL_USAGE)
+#define ACL_ALL_RIGHTS_VARIABLE		(ACL_READ|ACL_WRITE)
 
 /* operation codes for pg_*_aclmask */
 typedef enum
@@ -266,7 +269,8 @@ extern AclMode pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
 										 AclMode mask, AclMaskHow how);
 extern AclMode pg_type_aclmask(Oid type_oid, Oid roleid,
 							   AclMode mask, AclMaskHow how);
-
+extern AclMode pg_variable_aclmask(Oid var_oid, Oid roleid,
+								   AclMode mask, AclMaskHow how);
 extern AclResult pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum,
 									   Oid roleid, AclMode mode);
 extern AclResult pg_attribute_aclcheck_ext(Oid table_oid, AttrNumber attnum,
@@ -291,6 +295,7 @@ extern AclResult pg_tablespace_aclcheck(Oid spc_oid, Oid roleid, AclMode mode);
 extern AclResult pg_foreign_data_wrapper_aclcheck(Oid fdw_oid, Oid roleid, AclMode mode);
 extern AclResult pg_foreign_server_aclcheck(Oid srv_oid, Oid roleid, AclMode mode);
 extern AclResult pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode);
+extern AclResult pg_variable_aclcheck(Oid type_oid, Oid roleid, AclMode mode);
 
 extern void aclcheck_error(AclResult aclerr, ObjectType objtype,
 						   const char *objectname);
@@ -327,6 +332,7 @@ extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
 extern bool pg_publication_ownercheck(Oid pub_oid, Oid roleid);
 extern bool pg_subscription_ownercheck(Oid sub_oid, Oid roleid);
 extern bool pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid);
+extern bool pg_variable_ownercheck(Oid stat_oid, Oid roleid);
 extern bool has_createrole_privilege(Oid roleid);
 extern bool has_bypassrls_privilege(Oid roleid);
 
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a9..faac8ed8e7 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -132,6 +132,7 @@ extern char get_func_prokind(Oid funcid);
 extern bool get_func_leakproof(Oid funcid);
 extern RegProcedure get_func_support(Oid funcid);
 extern Oid	get_relname_relid(const char *relname, Oid relnamespace);
+extern Oid	get_varname_varid(const char *varname, Oid varnamespace);
 extern char *get_rel_name(Oid relid);
 extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
@@ -199,6 +200,13 @@ extern bool get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
 extern bool get_index_isclustered(Oid index_oid);
 
+extern char *get_session_variable_name(Oid varid);
+extern Oid get_session_variable_namespace(Oid varid);
+extern void get_session_variable_type_typmod_collid(Oid varid,
+												   Oid *typid,
+												   int32 *typmod,
+												   Oid *collid);
+
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
 #define type_is_array_domain(typid)  (get_base_element_type(typid) != InvalidOid)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 4463ea66be..3d18dc88ac 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -113,9 +113,11 @@ enum SysCacheIdentifier
 	TYPENAMENSP,
 	TYPEOID,
 	USERMAPPINGOID,
-	USERMAPPINGUSERSERVER
+	USERMAPPINGUSERSERVER,
+	VARIABLENAMENSP,
+	VARIABLEOID
 
-#define SysCacheSize (USERMAPPINGUSERSERVER + 1)
+#define SysCacheSize (VARIABLEOID + 1)
 };
 
 extern void InitCatalogCache(void);
-- 
2.36.1

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 21 Jun 2022 22:24:14 +0200

fixed tabcomplete reported by patch tester

Regards

Pavel
From 5f2b8e572705f04f92dab202a6363d7aa6def220 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 09/11] Regress tests for session variables

 
 
 # ----------

From ed6e0970aa20835de790c2ea1bcaa07543de0a04 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 08/11] typedefs

---

From 6d1f3fc4c199873a66e5d4b375478e7e9cba3857 Mon Sep 17 00:00:00 2001

 

 select a from atacc1;

 

 
 
 -- should fail
 LET v1.x = 10;

From 006873c538d23175e14b5a52fb97d63608961556 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/11] Possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

---

 

 
 /*

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

+static void

+static void

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

From 723a93d402f0d9b10af28378a604d2e3f54b81f5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 11/11] documentation

 
 
  </sect1>

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
 
+     <varlistentry>

   <para>

 
+/*

+static LetStmt *
 
+static bool
 
+static void

 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 	/*

 

 
 /* ----------------
-- 
2.36.1

From 8227a1eafc7896b60a622f021680ef9bd32b0307 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/11] Enhancing psql for session variables

 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 7f0ab5acb9..a0be0955be 100644

 /*
+	{"IMMUTABLE VARIABLE", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER},	/* for CREATE

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

 

 
 /*

 
 
+/*
 
 /*

 
 /*
 
 						/*

+	/*

 

 

 
 
 	return newnode;
 
 	return true;

 
 	READ_DONE();

 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*

+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

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

 

 
 
 	/*

 
 
 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.36.1

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*
 
 				/*

+static bool

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 
 	ereport(ERROR,
 
 	return isclustered;
 }
+

 	/*

 

 

 
+

 
 /*

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 5 Jul 2022 08:42:09 +0200

Hi

fresh rebase + type check. Before returning any value, the related type is
checked if it is valid still

Regards

Pavel
From 9d200edfc961934b7383780aba016414b334ef00 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 12/12] documentation

 
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 25b02c4e37..fd114e8975 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>
@@ -14307,4 +14312,157 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
 
  </sect1>
 
index 37fd80388c..96a78a1351 100644

index cf387dfc3f..271429d1e8 100644

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
 
+     <varlistentry>

   <para>

From 827c4e15828533b94f88372a256bab176a3086f5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 08/12] Possibility to dump session variables by pg_dump

 
 
index 6e501a5413..e6615cae13 100644
@@ -504,6 +504,11 @@ do { \

 	int			useDB;
index 233198afc0..8d47869ba8 100644
@@ -2934,6 +2934,14 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
@@ -3419,6 +3427,7 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te)
@@ -3611,7 +3620,8 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 
index c871cb727d..fddcae2c77 100644
@@ -4700,6 +4701,232 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
+	if (fout->remoteVersion < 160000)
@@ -9354,7 +9581,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -9944,6 +10172,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION:
@@ -14287,6 +14518,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -17828,6 +18062,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From e129b32b1445ad079cf2122ec0d353a5aca9bff0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 11:40:29 +0200
Subject: [PATCH 11/12] This patch changes error message "column doesn't exist"
 to message "column or variable doesn't exist"

 

 select a from atacc1;

 

 
index 3c3fd3613c..b574afa3af 100644
 
 -- should fail
 LET v1.x = 10;

From f15357f6c449aa8d4b4b32abbeff3f0795b51c95 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 09/12] typedefs

---

From d2d80ec0dcf3ace1cee182b721a0cbd35d5e369b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 10/12] Regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 src/test/regress/expected/misc_sanity.out     |   4 +-
 .../regress/expected/session_variables.out    | 999 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/session_variables.sql    | 766 ++++++++++++++
 4 files changed, 1769 insertions(+), 2 deletions(-)
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

index 0000000000..3c3fd3613c
@@ -0,0 +1,999 @@
+CREATE TYPE public.svar_test_type AS (a int, b int, c numeric);
+CREATE VARIABLE public.svar AS public.svar_test_type;
+LET public.svar = ROW(10,20,30);
+SELECT public.svar;
+    svar    
+ (10,20,30)
+ALTER TYPE public.svar_test_type DROP ATTRIBUTE c;
+SELECT public.svar;
+ERROR:  format of stored value of "public.svar" session variable can be outdated
+ALTER TYPE public.svar_test_type ADD ATTRIBUTE c int;
+-- should to fail too (different type, different generation number);
+SELECT public.svar;
+ERROR:  format of stored value of "public.svar" session variable can be outdated
+LET public.svar = ROW(10,20,30);
+-- should be ok again for new value
+SELECT public.svar;
+    svar    
+ (10,20,30)
+DROP VARIABLE public.svar;
+DROP TYPE public.svar_test_type;
 
 # ----------
index 0000000000..044d034ea8
@@ -0,0 +1,766 @@
+CREATE TYPE public.svar_test_type AS (a int, b int, c numeric);
+
+CREATE VARIABLE public.svar AS public.svar_test_type;
+
+LET public.svar = ROW(10,20,30);
+
+SELECT public.svar;
+
+ALTER TYPE public.svar_test_type DROP ATTRIBUTE c;
+
+-- should to fail
+SELECT public.svar;
+
+ALTER TYPE public.svar_test_type ADD ATTRIBUTE c int;
+
+-- should to fail too (different type, different generation number);
+SELECT public.svar;
+
+LET public.svar = ROW(10,20,30);
+
+-- should be ok again for new value
+SELECT public.svar;
+
+DROP VARIABLE public.svar;
+
+DROP TYPE public.svar_test_type;
-- 
2.36.1

From 28a792c5f687d93da4346120f52665ad34c0d2d5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 07/12] Enhancing psql for session variables

diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index c562c04afe..85c77307b8 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -944,6 +944,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 88d92a08ae..27525ab7c9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4996,6 +4996,102 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
 	return true;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index c5cafe6f4b..e62c1c55b6 100644
@@ -962,6 +962,13 @@ static const SchemaQuery Query_for_trigger_of_table = {

 /*
@@ -1219,6 +1226,8 @@ static const pgsql_thing_t words_after_create[] = {
@@ -1257,6 +1266,7 @@ static const pgsql_thing_t words_after_create[] = {
@@ -1668,7 +1678,8 @@ psql_completion(const char *text, int start, int end)
@@ -1688,7 +1699,7 @@ psql_completion(const char *text, int start, int end)
@@ -2128,6 +2139,9 @@ psql_completion(const char *text, int start, int end)
@@ -2629,7 +2643,7 @@ psql_completion(const char *text, int start, int end)
@@ -3067,7 +3081,7 @@ psql_completion(const char *text, int start, int end)
@@ -3374,6 +3388,17 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS */
@@ -3489,7 +3514,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
@@ -3616,6 +3641,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 */
@@ -3732,7 +3763,8 @@ psql_completion(const char *text, int start, int end)
@@ -3750,6 +3782,8 @@ psql_completion(const char *text, int start, int end)
 									 "USAGE",
 									 "SET",
 									 "ALTER SYSTEM",
+									 "READ",
+									 "WRITE",
 									 "ALL");
 	}
 
@@ -3791,7 +3825,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("GRANT|REVOKE", MatchAny) ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny))
 	{
-		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
+		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|READ|WRITE|ALL"))
 			COMPLETE_WITH("ON");
 		else if (TailMatches("GRANT", MatchAny))
 			COMPLETE_WITH("TO");
@@ -3814,7 +3848,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",
@@ -3836,7 +3870,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"))
@@ -3844,7 +3879,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");
@@ -3880,6 +3916,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
@@ -4122,7 +4160,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -4410,6 +4448,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
 
@@ -4576,6 +4622,8 @@ psql_completion(const char *text, int start, int end)

From dde21f2c37bfd727e97fae2cba8756444b715eab Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 06/12] DISCARD VARIABLES command

---

 

index 15565b3dce..77effbf081 100644
@@ -3840,7 +3840,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;

From 4a0419b2db96134e97a74c5f59ca06776f20344c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 05/12] Support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 1982049cb551e67ea8ea8cceceeee56850998079 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 04/12] LET command

index aa2d7eab4d..6b649a55d1 100644
@@ -1572,6 +1573,85 @@ ResetSessionVariables(void)
 
+/*

+static LetStmt *
 
+static bool
index 4cb1744da6..1fdf9f3c61 100644
@@ -4097,6 +4097,16 @@ raw_expression_tree_walker(Node *node,
index 7ba2a11784..d5d387baf3 100644
@@ -3041,6 +3041,18 @@ _outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
 
+static void
@@ -3221,6 +3233,7 @@ _outQuery(StringInfo str, const Query *node)
@@ -4583,6 +4596,9 @@ outNode(StringInfo str, const void *obj)

 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

index 9f12436fc6..7f21ad4c9e 100644
@@ -448,6 +448,7 @@ typedef enum NodeTag
 	T_AlterCollationStmt,
 	T_CallStmt,
 	T_AlterStatsStmt,
+	T_LetStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index c244201c27..15565b3dce 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1978,6 +1978,21 @@ typedef struct MergeStmt
 	WithClause *withClause;		/* WITH clause */
 } MergeStmt;
 
index 1208f3de0d..686efa9b15 100644

 

 /* ----------------
-- 
2.36.1

From 778b5faebe898b0b02f68f9344fb6cecfa1fe5fa Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/12] typecheck - check of consistency of format of stored
 value.

The lifecycle of value of session variable can be long. In this time
the composite type can changed, or extension that defines the type
can be upgraded. In this case we cannot to use stored value.

Session variables holds a own evidence of fingerprints for any
used type. When some possible not compatible change is detected
(the version of extension is changed), the related stored type's
generation number is increased. The copy of genaration number is
stored in session variable data. Before any read of session variable,
the stored generation number is compared with current type generation
number. When it is different, the read is canceled.
---
 src/backend/commands/sessionvariable.c | 777 ++++++++++++++++++++++---
 1 file changed, 707 insertions(+), 70 deletions(-)

diff --git a/src/backend/commands/sessionvariable.c b/src/backend/commands/sessionvariable.c
index e26903d135..aa2d7eab4d 100644
--- a/src/backend/commands/sessionvariable.c
+++ b/src/backend/commands/sessionvariable.c
@@ -14,12 +14,15 @@
  */
 #include "postgres.h"
 #include "miscadmin.h"
+#include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_extension.h"
+#include "catalog/pg_type.h"
 #include "catalog/pg_variable.h"
 #include "commands/session_variable.h"
 #include "executor/executor.h"
@@ -29,14 +32,18 @@
 #include "parser/parse_expr.h"
 #include "parser/parse_type.h"
 #include "rewrite/rewriteHandler.h"
+#include "storage/itemptr.h"
 #include "storage/lmgr.h"
+#include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 /*
  * Values of session variables are stored in local memory, in
@@ -111,10 +118,54 @@
  * check can be delayed after oid overflow. Second issue is possible
  * change of format of stored value. So before returning of any
  * value of session variable, we have to check if returned value
- * is safe. If not, we will mark the session variable as broken,
- * and any read of this variable until end of session or rewriting
- * raises an exception.
+ * is safe.
+ *
+ * Scalar types should be buildin, domain, or come from extensions.
+ * Buildin types should be valid every time. For other cases we
+ * can check xmin, and if xmin is different, then we can invalidate
+ * the value. For types from extension we can check xmin of pg_extension
+ * related record. For domains we should to force cast to domain
+ * type to reaply all constraints before returning the value.
  */
+struct SVariableTypeDataField;
+
+typedef struct SVaribleTypeData
+{
+	Oid			typid;
+	int64		gennum;			/* generation number helps with change detection */
+	LocalTransactionId verified_lxid; /* lxid of transaction when the typ fingerprint
+									   * was created or verified */
+	int16		typlen;
+	bool		typbyval;
+	bool		is_domain;
+	bool		is_rowtype;
+
+	struct SVaribleTypeData *base_type;
+	int64		base_type_gennum;
+	void	   *domain_check_extra;
+	LocalTransactionId domain_check_extra_lxid;
+
+	TransactionId	typ_xmin;	/* xmin of related tuple of pg_type */
+	ItemPointerData typ_tid;
+
+	Oid			extid;			/* OID of owner extension if exists */
+	TransactionId ext_xmin;		/* xmin of related tuple of pg_extension */
+	ItemPointerData ext_tid;
+	char	   *extname;
+	char	   *extversion;
+
+	int			natts;			/* number of attributies of composite type */
+	struct SvariableTypeDataField *attrs;		/* array of attributies */
+} SVariableTypeData;
+
+typedef SVariableTypeData * SVariableType;
+
+typedef struct SvariableTypeDataField
+{
+	SVariableType svartype;
+	int64		gennum;
+} SvariableTypeDataField;
+
 typedef enum SVariableXActAction
 {
 	SVAR_ON_COMMIT_DROP,		/* used for ON COMMIT DROP */
@@ -145,14 +196,14 @@ static List *xact_reset_recheck_actions = NIL;
 typedef struct SVariableData
 {
 	Oid			varid;			/* pg_variable OID of this sequence (hash key) */
-	Oid			typid;			/* OID of the data type */
-	int16		typlen;
-	bool		typbyval;
+
+	SVariableType svartype;
+	int64		gennum;
+
 	bool		isnull;
 	bool		freeval;
 	Datum		value;
 
-	bool		is_rowtype;		/* true when variable is composite */
 	bool		is_not_null;	/* don't allow null values */
 	bool		is_immutable;	/* true when variable is immutable */
 	bool		has_defexpr;	/* true when variable has a default value */
@@ -160,15 +211,15 @@ typedef struct SVariableData
 	bool		is_valid;		/* true when variable was successfully
 								 * initialized */
 
-	bool		is_broken;		/* true when we cannot to return a value
-								   safely */
-
 	uint32		hashvalue;
 }			SVariableData;
 
 typedef SVariableData * SVariable;
 
-static HTAB *sessionvars = NULL;	/* hash table for session variables */
+static HTAB *sessionvars = NULL;		/* hash table for session variables */
+static HTAB *sessionvars_types = NULL;	/* hash table for type fingerprints of session
+										 * variables */
+
 static MemoryContext SVariableMemoryContext = NULL;
 
 static bool first_time = true;
@@ -176,9 +227,531 @@ static bool first_time = true;
 static bool sync_sessionvars_all_is_required = false;
 static bool recheck_sessionvars_is_required = false;
 
+static SVariableType get_svariabletype(Oid typid);
+static int64 get_svariable_valid_type_gennum(SVariableType svt);
+
 static void register_session_variable_xact_action(Oid varid, SVariableXActAction action);
 static void unregister_session_variable_xact_action(Oid varid, SVariableXActAction action);
 
+
+/*
+ * In this case we know, so fast comparing fails.
+ */
+static bool
+svariabletypes_equals(SVariableType svt1, SVariableType svt2)
+{
+	Assert(svt1->typid == svt2->typid);
+
+	/*
+	 * for trustworthy check we need to know base type, extension,
+	 * or composite fields. Just typlen, and typbyval is not enough
+	 * trustworthy, because (-1, false) is very common. For type
+	 * identity check we need two independent attributies.
+	 */
+	if (!(OidIsValid(svt1->extid) ||
+		  svt1->base_type ||
+		  svt1->is_rowtype))
+		return false;
+
+	if (svt1->typlen != svt2->typlen)
+		return false;
+
+	if (svt1->typbyval != svt2->typbyval)
+		return false;
+
+	if (svt1->is_domain != svt2->is_domain)
+		return false;
+
+	if (svt1->is_rowtype != svt2->is_rowtype)
+		return false;
+
+	if (svt1->is_domain)
+	{
+		if (svt1->base_type->typid != svt2->base_type->typid)
+			return false;
+
+		if (svt1->base_type_gennum != svt2->base_type_gennum)
+			return false;
+	}
+
+	if (OidIsValid(svt1->extid))
+	{
+		if (svt1->extid != svt2->extid)
+			return false;
+
+		if (strcmp(svt1->extname, svt2->extname) != 0)
+			return false;
+
+		if (strcmp(svt1->extversion, svt2->extversion) != 0)
+			return false;
+	}
+
+	if (svt1->natts > 0)
+	{
+		int		i;
+
+		if (svt1->natts != svt2->natts)
+			return false;
+
+		for (i = 0; i < svt1->natts; i++)
+		{
+			if (svt1->attrs[i].svartype->typid != svt2->attrs[i].svartype->typid)
+				return false;
+
+			if (svt1->attrs[i].gennum != svt2->attrs[i].gennum)
+				return false;
+		}
+	}
+
+	return true;
+}
+
+/*
+ * Update fields used for fast check
+ */
+static void
+svariabletype_refresh(SVariableType svt1, SVariableType svt2)
+{
+	svt1->typ_xmin = svt2->typ_xmin;
+	svt1->typ_tid = svt2->typ_tid;
+
+	svt1->ext_xmin = svt2->ext_xmin;
+	svt1->ext_tid = svt2->ext_tid;
+}
+
+/*
+ * Update all fields and increase generation number
+ */
+static void
+svariabletype_update(SVariableType svt1, SVariableType svt2)
+{
+	int		gennum = svt1->gennum;
+
+	if (OidIsValid(svt1->extid))
+	{
+		pfree(svt1->extname);
+		pfree(svt1->extversion);
+	}
+
+	if (svt1->natts > 0)
+		pfree(svt1->attrs);
+
+	memcpy(svt1, svt2, sizeof(SVariableTypeData));
+
+	svt1->gennum = gennum + 1;
+}
+
+/*
+ * When type owner's extension is not changed, then we can belive
+ * so type is still valid. For this check we need to hold few
+ * information about extension in memory. We can do fast check
+ * based on xmin, tid or slow check on oid, name and version.
+ */
+static void
+svariabletype_assign_extension(SVariableType svt, Oid extid)
+{
+	Relation	pg_extension;
+	ScanKeyData entry[1];
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	Form_pg_extension ext;
+	bool		isnull;
+	Datum		datum;
+	MemoryContext oldcxt;
+
+	Assert(OidIsValid(extid));
+
+	/* There's no syscache for pg_extension, so do it the hard way */
+	pg_extension = table_open(ExtensionRelationId, AccessShareLock);
+
+	ScanKeyInit(&entry[0],
+				Anum_pg_extension_oid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(extid));
+
+	scan = systable_beginscan(pg_extension,
+							  ExtensionOidIndexId, true,
+							  NULL, 1, entry);
+
+	tuple = systable_getnext(scan);
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("extension with OID %u does not exist", svt->extid)));
+
+	ext = (Form_pg_extension) GETSTRUCT(tuple);
+	svt->extid = extid;
+	svt->ext_xmin = HeapTupleHeaderGetRawXmin(tuple->t_data);
+	svt->ext_tid = tuple->t_self;
+
+	datum = heap_getattr(tuple, Anum_pg_extension_extversion,
+						 RelationGetDescr(pg_extension), &isnull);
+
+	if (isnull)
+		elog(ERROR, "extversion is null");
+
+	oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+
+	svt->extversion = TextDatumGetCString(datum);
+	svt->extname = pstrdup(NameStr(ext->extname));
+
+	MemoryContextSwitchTo(oldcxt);
+
+	systable_endscan(scan);
+	table_close(pg_extension, AccessShareLock);
+}
+
+static bool
+svariabletype_verify_ext_fast(SVariableType svt)
+{
+	Relation	pg_extension;
+	ScanKeyData entry[1];
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	bool		result = true;
+
+	Assert(OidIsValid(svt->extid));
+
+	/* There's no syscache for pg_extension, so do it the hard way */
+	pg_extension = table_open(ExtensionRelationId, AccessShareLock);
+
+	ScanKeyInit(&entry[0],
+				Anum_pg_extension_oid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(svt->extid));
+
+	scan = systable_beginscan(pg_extension,
+							  ExtensionOidIndexId, true,
+							  NULL, 1, entry);
+
+	tuple = systable_getnext(scan);
+	if (HeapTupleIsValid(tuple))
+	{
+		if (svt->ext_xmin != HeapTupleHeaderGetRawXmin(tuple->t_data) ||
+			!ItemPointerEquals(&svt->ext_tid, &tuple->t_self))
+			result = false;
+	}
+	else
+		result = false;
+
+	systable_endscan(scan);
+	table_close(pg_extension, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * We hold data like typlen, typbyval for usual purposes. More
+ * we hold xmin, tid, extid like type's fingerprint. This is
+ * used later for type verification stored value of session variable.
+ */
+static void
+svariabletype_init(SVariableType svt, HeapTuple tuple, Oid typid)
+{
+	Form_pg_type typ;
+
+	memset(svt, 0, sizeof(SVariableTypeData));
+
+	svt->typid = typid;
+	svt->gennum = 1;
+
+	typ = (Form_pg_type) GETSTRUCT(tuple);
+
+	/* save basic attributtes */
+	svt->typlen = typ->typlen;
+	svt->typbyval = typ->typbyval;
+
+	/* save info about type */
+	svt->typ_xmin = HeapTupleHeaderGetRawXmin(tuple->t_data);
+	svt->typ_tid = tuple->t_self;
+
+	if (typ->typtype == TYPTYPE_DOMAIN)
+	{
+		Oid			basetypid;
+
+		svt->is_domain = true;
+		basetypid = getBaseType(typid);
+
+		svt->base_type = get_svariabletype(basetypid);
+		svt->base_type_gennum = get_svariable_valid_type_gennum(svt->base_type);
+		svt->is_rowtype = svt->base_type->is_rowtype;
+	}
+	else
+	{
+		svt->is_domain = false;
+		svt->is_rowtype = typ->typtype == TYPTYPE_COMPOSITE;
+	}
+
+	/*
+	 * Store fingerprints of fields of composite types.
+	 * Probably buildin types should not be changed, but
+	 * just be safe, and store fingerprints for all composite
+	 * types (including buildin composite types).
+	*/
+	if (svt->is_rowtype)
+	{
+		Oid			rowtypid;
+		TupleDesc	tupdesc;
+		int			i;
+		int			natts = 0;
+
+		if (svt->is_domain)
+			rowtypid = svt->base_type->typid;
+		else
+			rowtypid = svt->typid;
+
+		tupdesc = lookup_rowtype_tupdesc(rowtypid, -1);
+
+		svt->attrs = MemoryContextAlloc(SVariableMemoryContext,
+											 tupdesc->natts * sizeof(SvariableTypeDataField));
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			SVariableType field_svartype;
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+			if (!attr->attisdropped)
+			{
+				field_svartype = get_svariabletype(attr->atttypid);
+
+				svt->attrs[natts].svartype = field_svartype;
+				svt->attrs[natts++].gennum = get_svariable_valid_type_gennum(field_svartype);
+			}
+		}
+
+		svt->natts = natts;
+
+		ReleaseTupleDesc(tupdesc);
+	}
+
+	svt->domain_check_extra_lxid = InvalidLocalTransactionId;
+
+	svt->verified_lxid = MyProc->lxid;
+
+	/* try to find related extension */
+	svt->extid = getExtensionOfObject(TypeRelationId, typid);
+
+	if (OidIsValid(svt->extid))
+		svariabletype_assign_extension(svt, svt->extid);
+}
+
+/*
+ * the field check can ignore dropped fields
+ */
+static bool
+svartype_verify_composite_fast(SVariableType svt)
+{
+	bool	result = true;
+	TupleDesc	tupdesc;
+	int		i;
+	int		attrn = 0;
+
+	Assert(svt);
+	Assert(svt->is_rowtype);
+
+	tupdesc = lookup_rowtype_tupdesc_noerror(svt->typid, -1, true);
+	if (!tupdesc)
+		return false;
+
+	/* only not dropped attributies are stored */
+	for (i = 0; i < svt->natts; i++)
+	{
+		Form_pg_attribute attr;
+
+		/* skip dropped attributies */
+		while (attrn < tupdesc->natts)
+		{
+			attr = TupleDescAttr(tupdesc, attrn++);
+
+			if (!attr->attisdropped)
+				break;
+		}
+	
+		if (attrn < tupdesc->natts)
+		{
+			SVariableType field_svt = svt->attrs[i].svartype;
+			int64		field_gennum = svt->attrs[i].gennum;
+			int64		gennum;
+
+			if (field_svt->typid != attr->atttypid)
+			{
+				result = false;
+				break;
+			}
+			else if (field_gennum != field_svt->gennum)
+			{
+				result = false;
+				break;
+			}
+
+			gennum = get_svariable_valid_type_gennum(field_svt);
+			if (field_gennum != gennum)
+			{
+				result = false;
+				break;
+			}
+		}
+		else
+		{
+			result = false;
+			break;
+		}
+	}
+
+	/* now only dropped columns can be allowed */
+	while (attrn < tupdesc->natts)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, attrn++);
+
+		if (!attr->attisdropped)
+		{
+			result = false;
+			break;
+		}
+	}
+
+	ReleaseTupleDesc(tupdesc);
+
+	return result;
+}
+
+/*
+ * Check type fingerprint and if it is not valid, then does an update, and
+ * increase generation number. We can trust just to buildin types. Composite
+ * types are checked recusively until we iterarate to buildin types. External
+ * types are valid if related record is without change, or version string is
+ * equal. Although the record can be untouched, we need to check extension record,
+ * because format can be changed by extension updade.
+ */
+static int64
+get_svariable_valid_type_gennum(SVariableType svt)
+{
+	HeapTuple	tuple;
+	bool		fast_check = true;
+
+	Assert(svt);
+
+	/* Buildin scalar objects are trustworthy */
+	if (svt->typid < FirstNormalObjectId && !svt->is_rowtype)
+		return svt->gennum;
+
+	/* don't repeat check in one transaction */
+	if (svt->verified_lxid == MyProc->lxid)
+		return svt->gennum;
+
+	/*
+	 * First we check the type record. If it was not changed, then
+	 * we can trust to stored fingerprint. In this case we can do
+	 * fast check of extension (because the format of type can be
+	 * changed when extension was updated). When fast check of type
+	 * fails, we have to run slow check.
+	 */
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(svt->typid));
+	if (!(svt->typ_xmin != HeapTupleHeaderGetRawXmin(tuple->t_data) &&
+		  ItemPointerEquals(&svt->typ_tid, &tuple->t_self)))
+		fast_check = false;
+
+	if (fast_check && OidIsValid(svt->extid))
+	{
+		if (!svariabletype_verify_ext_fast(svt))
+			fast_check = false;
+	}
+
+	/* When type or extension records are up to date, check base type */
+	if (fast_check && svt->is_domain)
+	{
+		if (svt->base_type_gennum != svt->base_type->gennum)
+		{
+			fast_check = false;
+		}
+		else if (get_svariable_valid_type_gennum(svt->base_type) !=
+			  svt->base_type_gennum)
+		{
+			fast_check = false;
+		}
+	}
+
+	if (fast_check && svt->is_rowtype)
+	{
+		if (!svartype_verify_composite_fast(svt))
+			fast_check = false;
+	}
+
+	if (!fast_check)
+	{
+		SVariableTypeData nsvtd;
+
+		/*
+		 * Slow check. We create new SVariableType value. Compare it with
+		 * previous value, and if it is different, then we replace old by
+		 * new and we increase gennum
+		 */
+
+		svariabletype_init(&nsvtd, tuple, svt->typid);
+
+		if (svariabletypes_equals(svt, &nsvtd))
+			svariabletype_refresh(svt, &nsvtd);
+		else
+			svariabletype_update(svt, &nsvtd);
+	}
+
+	ReleaseSysCache(tuple);
+
+	svt->verified_lxid = MyProc->lxid;
+
+	return svt->gennum;
+}
+
+static SVariableType
+get_svariabletype(Oid typid)
+{
+	SVariableType svt;
+	bool		found;
+
+	Assert(sessionvars_types);
+
+	svt = (SVariableType) hash_search(sessionvars_types, &typid,
+										   HASH_ENTER, &found);
+
+	if (!found)
+	{
+		HeapTuple	tuple;
+
+		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for type %u", typid);
+
+		svariabletype_init(svt, tuple, typid);
+
+		ReleaseSysCache(tuple);
+	}
+
+	return svt;
+}
+
+/*
+ * Returns true, when type of stored value is still valid
+ */
+static bool
+session_variable_use_valid_type(SVariable svar)
+{
+	Assert(svar);
+	Assert(svar->svartype);
+
+	/*
+	 * when referenced type is not valid of obsolete, the
+	 * value is stored in maybe not up the data format.
+	 */
+	if (svar->gennum != svar->svartype->gennum)
+		return false;
+
+	/* enforce type verification, get fresh generation number */
+	if (svar->svartype->gennum != get_svariable_valid_type_gennum(svar->svartype))
+		return false;
+
+	return true;
+}
+
 /*
  * Releases stored data from session variable, but preserve the hash entry
  * in sessionvars.
@@ -409,9 +982,10 @@ sync_sessionvars_all()
  * Create the hash table for storing session variables
  */
 static void
-create_sessionvars_hashtable(void)
+create_sessionvars_hashtables(void)
 {
-	HASHCTL		ctl;
+	HASHCTL		vars_ctl;
+	HASHCTL		types_ctl;
 
 	/* set callbacks */
 	if (first_time)
@@ -431,14 +1005,24 @@ create_sessionvars_hashtable(void)
 
 	Assert(SVariableMemoryContext);
 
-	memset(&ctl, 0, sizeof(ctl));
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(SVariableData);
-	ctl.hcxt = SVariableMemoryContext;
+	memset(&vars_ctl, 0, sizeof(vars_ctl));
+	vars_ctl.keysize = sizeof(Oid);
+	vars_ctl.entrysize = sizeof(SVariableData);
+	vars_ctl.hcxt = SVariableMemoryContext;
 
 	Assert(sessionvars == NULL);
 
-	sessionvars = hash_create("Session variables", 64, &ctl,
+	sessionvars = hash_create("Session variables", 64, &vars_ctl,
+								   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+
+	memset(&types_ctl, 0, sizeof(types_ctl));
+	types_ctl.keysize = sizeof(Oid);
+	types_ctl.entrysize = sizeof(SVariableTypeData);
+	types_ctl.hcxt = SVariableMemoryContext;
+
+	Assert(sessionvars_types == NULL);
+
+	sessionvars_types = hash_create("Session variable types", 64, &types_ctl,
 								   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
 }
 
@@ -466,15 +1050,19 @@ set_session_variable(SVariable svar, Datum value,
 						get_namespace_name(get_session_variable_namespace(svar->varid)),
 						get_session_variable_name(svar->varid))));
 
-	if (svar->typid != typid)
+	Assert(svar->svartype);
+
+	if (svar->svartype->typid != typid)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("type \"%s\" of assigned value is different than type \"%s\" of session variable \"%s.%s\"",
 						format_type_be(typid),
-						format_type_be(svar->typid),
+						format_type_be(svar->svartype->typid),
 						get_namespace_name(get_session_variable_namespace(svar->varid)),
 						get_session_variable_name(svar->varid))));
 
+	svar->gennum = get_svariable_valid_type_gennum(svar->svartype);
+
 	/*
 	 * Don't allow updating of immutable session variable that has assigned
 	 * not null value or has default expression (and then the value should be
@@ -492,7 +1080,10 @@ set_session_variable(SVariable svar, Datum value,
 	/* copy value to session persistent context */
 	oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
 	if (!isnull)
-		newval = datumCopy(value, svar->typbyval, svar->typlen);
+		newval = datumCopy(value,
+						   svar->svartype->typbyval,
+						   svar->svartype->typlen);
+
 	MemoryContextSwitchTo(oldcxt);
 
 	free_session_variable_value(svar);
@@ -514,17 +1105,13 @@ init_session_variable(SVariable svar, Variable *var)
 	Assert(OidIsValid(var->oid));
 
 	svar->varid = var->oid;
-	svar->typid = var->typid;
-
-	get_typlenbyval(var->typid,
-					&svar->typlen,
-					&svar->typbyval);
+	svar->svartype = get_svariabletype(var->typid);
+	svar->gennum = get_svariable_valid_type_gennum(svar->svartype);
 
+	svar->value = (Datum) 0;
 	svar->isnull = true;
 	svar->freeval = false;
-	svar->value = (Datum) 0;
 
-	svar->is_rowtype = type_is_rowtype(var->typid);
 	svar->is_not_null = var->is_not_null;
 	svar->is_immutable = var->is_immutable;
 	svar->has_defexpr = var->has_defexpr;
@@ -559,7 +1146,7 @@ prepare_variable_for_reading(Oid varid)
 	var.oid = InvalidOid;
 
 	if (!sessionvars)
-		create_sessionvars_hashtable();
+		create_sessionvars_hashtables();
 
 	/* Protect used session variable against drop until transaction end */
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
@@ -571,54 +1158,97 @@ prepare_variable_for_reading(Oid varid)
 										HASH_ENTER, &found);
 
 	/* Return content if it is available and valid */
-	if (found && svar->is_valid)
-		return svar;
+	if (!found || !svar->is_valid)
+	{
+		/* We need to load defexpr. */
+		initVariable(&var, varid, false);
+
+		if (!found)
+			init_session_variable(svar, &var);
+
+		/* Raise an error when we cannot initialize variable correctly */
+		if (var.is_not_null && !var.defexpr)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+							get_namespace_name(get_session_variable_namespace(varid)),
+							get_session_variable_name(varid)),
+					 errdetail("The session variable was not initialized yet.")));
+
+		if (svar->has_defexpr)
+		{
+			Datum		value = (Datum) 0;
+			bool		isnull;
+			EState	   *estate = NULL;
+			Expr	   *defexpr;
+			ExprState  *defexprs;
+			MemoryContext oldcxt;
 
-	/* We need to load defexpr. */
-	initVariable(&var, varid, false);
+			/* Prepare default expr */
+			estate = CreateExecutorState();
+
+			oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+			defexpr = expression_planner((Expr *) var.defexpr);
+			defexprs = ExecInitExpr(defexpr, NULL);
+			value = ExecEvalExprSwitchContext(defexprs,
+											  GetPerTupleExprContext(estate),
+											  &isnull);
 
-	if (!found)
-		init_session_variable(svar, &var);
 
-	/* Raise an error when we cannot initialize variable correctly */
-	if (var.is_not_null && !var.defexpr)
+			/* Store result before releasing Executor memory */
+			set_session_variable(svar, value, isnull, svar->svartype->typid, true);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			FreeExecutorState(estate);
+		}
+		else
+			set_session_variable(svar, (Datum) 0, true, svar->svartype->typid, true);
+	}
+
+	/*
+	 * Check if stored value has still valid type. Now, we just to
+	 * raise error. Future versions can try to convert stored to
+	 * current binary format instead (composite types).
+	 */
+	if (!session_variable_use_valid_type(svar))
 		ereport(ERROR,
-				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-				 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("format of stored value of \"%s.%s\" session variable can be outdated",
 						get_namespace_name(get_session_variable_namespace(varid)),
-						get_session_variable_name(varid)),
-				 errdetail("The session variable was not initialized yet.")));
+						get_session_variable_name(varid))));
 
-	if (svar->has_defexpr)
+	/*
+	 * Although the value of domain type should be valid (it is
+	 * checked when it is assigned to session variable), we have to
+	 * check related constraints anytime. It can be more expensive
+	 * than in PL/pgSQL. PL/pgSQL forces domain checks when value
+	 * is assigned to the variable or when value is returned from
+	 * function. Fortunately, domain types manage cache of constraints by
+	 * self.
+	 */
+	if (svar->svartype->is_domain)
 	{
-		Datum		value = (Datum) 0;
-		bool		isnull;
-		EState	   *estate = NULL;
-		Expr	   *defexpr;
-		ExprState  *defexprs;
-		MemoryContext oldcxt;
-
-		/* Prepare default expr */
-		estate = CreateExecutorState();
+		MemoryContext oldcxt = CurrentMemoryContext;
 
-		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+		/*
+		 * Store domain_check extra in CurTransactionContext. When we are
+		 * in other transaction, the domain_check_extra cache is not valid.
+		 */
+		if (svar->svartype->domain_check_extra_lxid != MyProc->lxid)
+			svar->svartype->domain_check_extra = NULL;
 
-		defexpr = expression_planner((Expr *) var.defexpr);
-		defexprs = ExecInitExpr(defexpr, NULL);
-		value = ExecEvalExprSwitchContext(defexprs,
-										  GetPerTupleExprContext(estate),
-										  &isnull);
+		domain_check(svar->value,
+					 svar->isnull,
+					 svar->svartype->typid,
+					 &svar->svartype->domain_check_extra,
+					 CurTransactionContext);
 
-
-		/* Store result before releasing Executor memory */
-		set_session_variable(svar, value, isnull, svar->typid, true);
+		svar->svartype->domain_check_extra_lxid = MyProc->lxid;
 
 		MemoryContextSwitchTo(oldcxt);
-
-		FreeExecutorState(estate);
 	}
-	else
-		set_session_variable(svar, (Datum) 0, true, svar->typid, true);
 
 	return svar;
 }
@@ -642,7 +1272,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid)
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
 
 	if (!sessionvars)
-		create_sessionvars_hashtable();
+		create_sessionvars_hashtables();
 
 	svar = (SVariable) hash_search(sessionvars, &varid,
 										HASH_ENTER, &found);
@@ -690,10 +1320,12 @@ CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 	Assert(svar != NULL && svar->is_valid);
 
 	*isNull = svar->isnull;
-	*typid = svar->typid;
+	*typid = svar->svartype->typid;
 
 	if (!svar->isnull)
-		return datumCopy(svar->value, svar->typbyval, svar->typlen);
+		return datumCopy(svar->value,
+						 svar->svartype->typbyval,
+						 svar->svartype->typlen);
 
 	return (Datum) 0;
 }
@@ -711,7 +1343,7 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	svar = prepare_variable_for_reading(varid);
 	Assert(svar != NULL && svar->is_valid);
 
-	if (expected_typid != svar->typid)
+	if (expected_typid != svar->svartype->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));
@@ -719,7 +1351,9 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	*isNull = svar->isnull;
 
 	if (!svar->isnull)
-		return datumCopy(svar->value, svar->typbyval, svar->typlen);
+		return datumCopy(svar->value,
+						 svar->svartype->typbyval,
+						 svar->svartype->typlen);
 
 	return (Datum) 0;
 }
@@ -737,7 +1371,7 @@ GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	svar = prepare_variable_for_reading(varid);
 	Assert(svar != NULL && svar->is_valid);
 
-	if (expected_typid != svar->typid)
+	if (expected_typid != svar->svartype->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));
@@ -914,6 +1548,9 @@ ResetSessionVariables(void)
 	{
 		hash_destroy(sessionvars);
 		sessionvars = NULL;
+
+		hash_destroy(sessionvars_types);
+		sessionvars_types = NULL;
 	}
 
 	/* Release memory allocated by session variables */
-- 
2.36.1

From b5b12f98bab4ff02f80dac59c458956869fcd5c1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/12] session variables

index 2d46a978cd..5878cca024 100644

 
 
 /*

 
 
+/*
 
 /*

 
 /*
 
 						/*

+	/*

 

 

 
 
 	return newnode;
 
 	return true;
index 4315c53080..7ba2a11784 100644
@@ -335,6 +335,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
@@ -1186,6 +1187,7 @@ _outParam(StringInfo str, const Param *node)
 
@@ -2441,6 +2443,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
@@ -2501,6 +2504,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
@@ -3228,6 +3232,7 @@ _outQuery(StringInfo str, const Query *node)
 
@@ -3238,6 +3243,7 @@ _outQuery(StringInfo str, const Query *node)
 
 	READ_DONE();
 
index 06ad856eac..6f93fa0997 100644
 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*

+/*
 
index 26cf4fa9a0..c9a3b6b2b5 100644
@@ -8054,6 +8056,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
@@ -12724,6 +12734,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 	/*
 
 /*
 
 	return false;
 }
index 0328029d43..4dc9a87a58 100644

 

 
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
 	/* Other working state: */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b1b1b0ff7a..c244201c27 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -135,6 +135,7 @@ typedef struct Query
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
 
@@ -144,6 +145,7 @@ typedef struct Query

index b88cfb8dc0..fb9ff59eb0 100644

@@ -356,6 +358,7 @@ struct PlannerInfo
 
 	/*
index d5c0ebe859..1208f3de0d 100644

index 732c00c098..1fc7cdaa1e 100644
 
@@ -293,13 +296,17 @@ typedef struct Const
 
 typedef struct Param
@@ -310,6 +317,7 @@ typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.36.1

From fee1c80180c02b7ca75889bc4c9144d7dcfa4567 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/12] Catalogue support for session variables

 src/backend/utils/adt/acl.c             |  20 +-
 40 files changed, 1990 insertions(+), 19 deletions(-)

index bd60b55574..2d46a978cd 100644

 

 
 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
 
index 2d21db4690..d6c2591b28 100644
 
 /*

 	/*
@@ -2113,6 +2140,24 @@ textarray_to_strvaluelist(ArrayType *arr)
 	return list;
 }
 
+/*
@@ -2300,6 +2345,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
@@ -2610,6 +2656,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
@@ -3511,6 +3562,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
@@ -3821,6 +3898,16 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
@@ -4593,6 +4680,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 
+		case OCLASS_VARIABLE:
@@ -5657,6 +5748,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
@@ -5900,6 +5995,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
 
index f46f86474a..27a8139c9c 100644
@@ -991,6 +991,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 
@@ -1056,6 +1057,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_PUBLICATION_REL:
 		case OCLASS_SUBSCRIPTION:
 		case OCLASS_TRANSFORM:
+		case OCLASS_VARIABLE:
 			return true;
 
 			/*
@@ -2053,6 +2055,8 @@ stringify_grant_objtype(ObjectType objtype)
 			return "TABLESPACE";
 		case OBJECT_TYPE:
 			return "TYPE";
+		case OBJECT_VARIABLE:
+			return "VARIABLE";
@@ -2136,6 +2140,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 
 			/*
 
 				/*

+static bool

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
+		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 b7fd3bcf05..70c38e5908 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -314,6 +314,12 @@ aclparse(const char *s, AclItem *aip)
 			case ACL_ALTER_SYSTEM_CHR:
 				read = ACL_ALTER_SYSTEM;
 				break;
+			case ACL_READ_CHR:
+				read = ACL_READ;
+				break;
+			case ACL_WRITE_CHR:
+				read = ACL_WRITE;
+				break;
 			case 'R':			/* ignore old RULE privileges */
 				read = 0;
 				break;
@@ -806,6 +812,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 objtype: %d", (int) objtype);
 			world_default = ACL_NO_RIGHTS;	/* keep compiler quiet */
@@ -903,6 +913,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
 		case 'T':
 			objtype = OBJECT_TYPE;
 			break;
+		case 'V':
+			objtype = OBJECT_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype abbreviation: %c", objtypec);
 	}
@@ -1588,6 +1601,8 @@ makeaclitem(PG_FUNCTION_ARGS)
 		{"CONNECT", ACL_CONNECT},
 		{"SET", ACL_SET},
 		{"ALTER SYSTEM", ACL_ALTER_SYSTEM},
+		{"READ", ACL_READ},
+		{"WRITE", ACL_WRITE},
 		{"RULE", 0},			/* ignore old RULE privileges */
 		{NULL, 0}
 	};
@@ -1605,7 +1620,6 @@ makeaclitem(PG_FUNCTION_ARGS)
 	PG_RETURN_ACLITEM_P(result);
 }
 
-
 /*
  * convert_any_priv_string: recognize privilege strings for has_foo_privilege
  *
@@ -1696,6 +1710,10 @@ convert_aclright_to_string(int aclright)
 			return "SET";
 		case ACL_ALTER_SYSTEM:
 			return "ALTER SYSTEM";
+		case ACL_READ:
+			return "READ";
+		case ACL_WRITE:
+			return "WRITE";
 		default:
 
 	return isclustered;
 }
+

 	/*

 

 
index a77b293723..df5ba708ae 100644
 
index 7ce1fc4deb..9f12436fc6 100644
@@ -373,6 +373,7 @@ typedef enum NodeTag
index f93d866548..b1b1b0ff7a 100644
@@ -2184,6 +2186,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -3308,6 +3311,25 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+

 
 /*

---

From: Julien Rouhaud <[email-masked]>
Date: Tue, 5 Jul 2022 18:50:32 +0800

Hi Pavel,

> checked if it is valid still

Great news, thanks a lot for keeping working on it!  I'm still in PTO since
last Friday, but I'm planning to start reviewing this patch as soon as I come
back.  It might take a while as my knowledge of this patch are a bit blurry but
hopefully it shouldn't take too long.

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 5 Jul 2022 13:19:42 +0200

> hopefully it shouldn't take too long.

Thank you

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 6 Jul 2022 22:30:31 +0200

Hi

> checked if it is valid still

This set of patches should to help me with investigation of regress test
fail reported by cfbot

Regards

Pavel

> Pavel
From 2ce358f208b4c45664e08737131939e03ff6b273 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 09/13] typedefs

---

diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c7f550e822..9b1ef163f6 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -498,6 +498,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
@@ -881,6 +882,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
+FormData_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
@@ -1403,6 +1405,7 @@ LargeObjectDesc
 LastAttnumInfo
 Latch
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
@@ -1785,6 +1788,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
@@ -2462,6 +2466,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
@@ -2647,6 +2652,9 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariable
+SVariableData
+SVariableXActActionItem
 Syn
 SyncOps
 SyncRepConfigData
@@ -2910,6 +2918,7 @@ Variable
 VariableAssignHook
 VariableCache
 VariableCacheData
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.36.1

From f978d19731fd9086d80222b1dc8ee50d44bb8191 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 12/13] documentation

 
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4f3f375a84..0de67f1179 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>
@@ -14321,4 +14326,157 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
 
  </sect1>

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
 
+     <varlistentry>

   <para>

From 7880c57e6e1d6bb47a92fc5cb4b15add0a679c24 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Wed, 6 Jul 2022 22:25:16 +0200
Subject: [PATCH 13/13] This commit should to help to find and reason why
 regress test fails on windows server 2019 reported by cfbot

---
 src/backend/commands/sessionvariable.c        |   35 +-
 .../regress/expected/session_variables.out    | 1006 +----------------
 src/test/regress/sql/session_variables.sql    |  742 +-----------
 3 files changed, 67 insertions(+), 1716 deletions(-)

diff --git a/src/backend/commands/sessionvariable.c b/src/backend/commands/sessionvariable.c
index 6b649a55d1..63d91c2e39 100644
--- a/src/backend/commands/sessionvariable.c
+++ b/src/backend/commands/sessionvariable.c
@@ -554,10 +554,12 @@ svartype_verify_composite_fast(SVariableType svt)
 	if (!tupdesc)
 		return false;
 
+	elog(NOTICE, "svartype_verify_composite_fast: %d %d", svt->natts, tupdesc->natts);
+
 	/* only not dropped attributies are stored */
 	for (i = 0; i < svt->natts; i++)
 	{
-		Form_pg_attribute attr;
+		Form_pg_attribute attr = NULL;
 
 		/* skip dropped attributies */
 		while (attrn < tupdesc->natts)
@@ -566,9 +568,11 @@ svartype_verify_composite_fast(SVariableType svt)
 
 			if (!attr->attisdropped)
 				break;
+			else
+				elog(NOTICE, "detected dropped column pos1");
 		}
-	
-		if (attrn < tupdesc->natts)
+
+		if (attr && !attr->attisdropped)
 		{
 			SVariableType field_svt = svt->attrs[i].svartype;
 			int64		field_gennum = svt->attrs[i].gennum;
@@ -576,11 +580,13 @@ svartype_verify_composite_fast(SVariableType svt)
 
 			if (field_svt->typid != attr->atttypid)
 			{
+				elog(NOTICE, "svartype_verify_composite_fast exit11: %u %u", field_svt->typid, attr->atttypid);
 				result = false;
 				break;
 			}
 			else if (field_gennum != field_svt->gennum)
 			{
+				elog(NOTICE, "svartype_verify_composite_fast exit12: %ld %ld", field_gennum, field_svt->gennum);
 				result = false;
 				break;
 			}
@@ -588,6 +594,7 @@ svartype_verify_composite_fast(SVariableType svt)
 			gennum = get_svariable_valid_type_gennum(field_svt);
 			if (field_gennum != gennum)
 			{
+				elog(NOTICE, "svartype_verify_composite_fast exit13: %ld %ld, ", field_gennum, gennum);
 				result = false;
 				break;
 			}
@@ -599,6 +606,10 @@ svartype_verify_composite_fast(SVariableType svt)
 		}
 	}
 
+
+	elog(NOTICE, "svartype_verify_composite_fast3, result: %d", result);
+
+
 	/* now only dropped columns can be allowed */
 	while (attrn < tupdesc->natts)
 	{
@@ -606,13 +617,19 @@ svartype_verify_composite_fast(SVariableType svt)
 
 		if (!attr->attisdropped)
 		{
+			elog(NOTICE, "svartype_verify_composite_fast exit14");
 			result = false;
 			break;
 		}
+		else
+			elog(NOTICE, "detected dropped column pos2");
+
 	}
 
 	ReleaseTupleDesc(tupdesc);
 
+	elog(NOTICE, "svartype_verify_composite_fast, result: %d", result);
+
 	return result;
 }
 
@@ -648,9 +665,14 @@ get_svariable_valid_type_gennum(SVariableType svt)
 	 * fails, we have to run slow check.
 	 */
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(svt->typid));
-	if (!(svt->typ_xmin != HeapTupleHeaderGetRawXmin(tuple->t_data) &&
+	if (!(svt->typ_xmin == HeapTupleHeaderGetRawXmin(tuple->t_data) &&
 		  ItemPointerEquals(&svt->typ_tid, &tuple->t_self)))
+	{
+		elog(NOTICE, "fast check disabled 01");
 		fast_check = false;
+	}
+
+	elog(NOTICE, "get_svariable_valid_type_gennum (1), fast_check: %d", fast_check);
 
 	if (fast_check && OidIsValid(svt->extid))
 	{
@@ -682,6 +704,8 @@ get_svariable_valid_type_gennum(SVariableType svt)
 	{
 		SVariableTypeData nsvtd;
 
+		elog(NOTICE, "slow check");
+
 		/*
 		 * Slow check. We create new SVariableType value. Compare it with
 		 * previous value, and if it is different, then we replace old by
@@ -690,6 +714,9 @@ get_svariable_valid_type_gennum(SVariableType svt)
 
 		svariabletype_init(&nsvtd, tuple, svt->typid);
 
+elog(NOTICE, "slow check %d %d", svt->natts, nsvtd.natts);
+
+
 		if (svariabletypes_equals(svt, &nsvtd))
 			svariabletype_refresh(svt, &nsvtd);
 		else
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index b574afa3af..74c64623ce 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -1,979 +1,21 @@
-CREATE SCHEMA svartest;
-SET search_path = svartest;
-CREATE VARIABLE var1 AS integer;
-CREATE TEMP VARIABLE var2 AS text;
-DROP VARIABLE var1, var2;
--- functional interface
-CREATE VARIABLE var1 AS numeric;
-CREATE ROLE var_test_role;
-GRANT USAGE ON SCHEMA svartest TO var_test_role;
-SET ROLE TO var_test_role;
--- should fail
-SELECT var1;
-ERROR:  permission denied for session variable var1
-SET ROLE TO DEFAULT;
-GRANT READ ON VARIABLE var1 TO var_test_role;
-SET ROLE TO var_test_role;
--- should fail
-LET var1 = 10;
-ERROR:  permission denied for session variable var1
--- should work
-SELECT var1;
- var1 
-------
-     
-(1 row)
-
-SET ROLE TO DEFAULT;
-GRANT WRITE ON VARIABLE var1 TO var_test_role;
-SET ROLE TO var_test_role;
--- should work
-LET var1 = 333;
-SET ROLE TO DEFAULT;
-REVOKE ALL ON VARIABLE var1 FROM var_test_role;
-CREATE OR REPLACE FUNCTION secure_var()
-RETURNS int AS $$
-  SELECT svartest.var1::int;
-$$ LANGUAGE sql SECURITY DEFINER;
-SELECT secure_var();
- secure_var 
-------------
-        333
-(1 row)
-
-SET ROLE TO var_test_role;
--- should fail
-SELECT svartest.var1;
-ERROR:  permission denied for session variable var1
--- should work;
-SELECT secure_var();
- secure_var 
-------------
-        333
-(1 row)
-
-SET ROLE TO DEFAULT;
-EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var1;
-                  QUERY PLAN                   
------------------------------------------------
- Function Scan on pg_catalog.generate_series g
-   Output: v
-   Function Call: generate_series(1, 100)
-   Filter: ((g.v)::numeric = var1)
-(4 rows)
-
-CREATE VIEW schema_var_view AS SELECT var1;
-SELECT * FROM schema_var_view;
- var1 
-------
-  333
-(1 row)
-
-\c -
-SET search_path = svartest;
--- should work still, but var will be empty
-SELECT * FROM schema_var_view;
- var1 
-------
-     
-(1 row)
-
-LET var1 = pi();
-SELECT var1;
-       var1       
-------------------
- 3.14159265358979
-(1 row)
-
--- we can see execution plan of LET statement
-EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
-         QUERY PLAN         
-----------------------------
- SET SESSION VARIABLE
- Result
-   Output: 3.14159265358979
-(3 rows)
-
-SELECT var1;
-       var1       
-------------------
- 3.14159265358979
-(1 row)
-
-CREATE VARIABLE var3 AS int;
-CREATE OR REPLACE FUNCTION inc(int)
-RETURNS int AS $$
-BEGIN
-  LET svartest.var3 = COALESCE(svartest.var3 + $1, $1);
-  RETURN var3;
-END;
-$$ LANGUAGE plpgsql;
-SELECT inc(1);
- inc 
------
-   1
-(1 row)
-
-SELECT inc(1);
- inc 
------
-   2
-(1 row)
-
-SELECT inc(1);
- inc 
------
-   3
-(1 row)
-
-SELECT inc(1) FROM generate_series(1,10);
- inc 
------
-   4
-   5
-   6
-   7
-   8
-   9
-  10
-  11
-  12
-  13
-(10 rows)
-
-SET ROLE TO var_test_role;
--- should fail
-LET var3 = 0;
-ERROR:  permission denied for session variable var3
-SET ROLE TO DEFAULT;
-DROP VIEW schema_var_view;
-DROP VARIABLE var1 CASCADE;
-DROP VARIABLE var3 CASCADE;
--- composite variables
-CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2));
-CREATE VARIABLE v1 AS sv_xyz;
-CREATE VARIABLE v2 AS sv_xyz;
-\d v1
-\d v2
-LET v1 = (1,2,3.14);
-LET v2 = (10,20,3.14*10);
--- should work too - there are prepared casts
-LET v1 = (1,2,3.14);
-SELECT v1;
-     v1     
-------------
- (1,2,3.14)
-(1 row)
-
-SELECT v2;
-      v2       
----------------
- (10,20,31.40)
-(1 row)
-
-SELECT (v1).*;
- x | y |  z   
----+---+------
- 1 | 2 | 3.14
-(1 row)
-
-SELECT (v2).*;
- x  | y  |   z   
-----+----+-------
- 10 | 20 | 31.40
-(1 row)
-
-SELECT v1.x + v1.z;
- ?column? 
-----------
-     4.14
-(1 row)
-
-SELECT v2.x + v2.z;
- ?column? 
-----------
-    41.40
-(1 row)
-
--- access to composite fields should be safe too
--- should fail
-SET ROLE TO var_test_role;
-SELECT v2.x;
-ERROR:  permission denied for session variable v2
-SET ROLE TO DEFAULT;
-DROP VARIABLE v1;
-DROP VARIABLE v2;
-REVOKE USAGE ON SCHEMA svartest FROM var_test_role;
-DROP ROLE var_test_role;
--- scalar variables should not be in conflict with qualified column
-CREATE VARIABLE varx AS text;
-SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class';
- relname  
-----------
- pg_class
-(1 row)
-
--- should fail
-SELECT varx.xxx;
-ERROR:  type text is not composite
--- variables can be updated under RO transaction
-BEGIN;
-SET TRANSACTION READ ONLY;
-LET varx = 'hello';
-COMMIT;
-SELECT varx;
- varx  
--------
- hello
-(1 row)
-
-DROP VARIABLE varx;
-CREATE TYPE t1 AS (a int, b numeric, c text);
-CREATE VARIABLE v1 AS t1;
-LET v1 = (1, pi(), 'hello');
-SELECT v1;
-             v1             
-----------------------------
- (1,3.14159265358979,hello)
-(1 row)
-
-LET v1.b = 10.2222;
-SELECT v1;
-        v1         
--------------------
- (1,10.2222,hello)
-(1 row)
-
--- should fail
-LET v1.x = 10;
-ERROR:  cannot assign to field "x" of column or variable "v1" because there is no such column in data type t1
-LINE 1: LET v1.x = 10;
-            ^
-DROP VARIABLE v1;
-DROP TYPE t1;
--- arrays are supported
-CREATE VARIABLE va1 AS numeric[];
-LET va1 = ARRAY[1.1,2.1];
-LET va1[1] = 10.1;
-SELECT va1;
-    va1     
-------------
- {10.1,2.1}
-(1 row)
-
-CREATE TYPE ta2 AS (a numeric, b numeric[]);
-CREATE VARIABLE va2 AS ta2;
-LET va2 = (10.1, ARRAY[0.0, 0.0]);
-LET va2.a = 10.2;
-SELECT va2;
-        va2         
---------------------
- (10.2,"{0.0,0.0}")
-(1 row)
-
-LET va2.b[1] = 10.3;
-SELECT va2;
-         va2         
----------------------
- (10.2,"{10.3,0.0}")
-(1 row)
-
-DROP VARIABLE va1;
-DROP VARIABLE va2;
-DROP TYPE ta2;
--- default values
-CREATE VARIABLE v1 AS numeric DEFAULT pi();
-LET v1 = v1 * 2;
-SELECT v1;
-        v1        
-------------------
- 6.28318530717958
-(1 row)
-
-CREATE TYPE t2 AS (a numeric, b text);
-CREATE VARIABLE v2 AS t2 DEFAULT (NULL, 'Hello');
-LET svartest.v2.a = pi();
-SELECT v2;
-            v2            
---------------------------
- (3.14159265358979,Hello)
-(1 row)
-
--- should fail due dependency
-DROP TYPE t2;
-ERROR:  cannot drop type t2 because other objects depend on it
-DETAIL:  session variable v2 depends on type t2
-HINT:  Use DROP ... CASCADE to drop the dependent objects too.
--- should be ok
-DROP VARIABLE v1;
-DROP VARIABLE v2;
--- tests of alters
-CREATE SCHEMA var_schema1;
-CREATE SCHEMA var_schema2;
-CREATE VARIABLE var_schema1.var1 AS integer;
-LET var_schema1.var1 = 1000;
-SELECT var_schema1.var1;
- var1 
-------
- 1000
-(1 row)
-
-ALTER VARIABLE var_schema1.var1 SET SCHEMA var_schema2;
-SELECT var_schema2.var1;
- var1 
-------
- 1000
-(1 row)
-
-CREATE ROLE var_test_role;
-ALTER VARIABLE var_schema2.var1 OWNER TO var_test_role;
-SET ROLE TO var_test_role;
--- should fail, no access to schema var_schema2.var
-SELECT var_schema2.var1;
-ERROR:  permission denied for schema var_schema2
-DROP VARIABLE var_schema2.var1;
-ERROR:  permission denied for schema var_schema2
-SET ROLE TO DEFAULT;
-ALTER VARIABLE var_schema2.var1 SET SCHEMA public;
-SET ROLE TO var_test_role;
-SELECT public.var1;
- var1 
-------
- 1000
-(1 row)
-
-ALTER VARIABLE public.var1 RENAME TO var1_renamed;
-SELECT public.var1_renamed;
- var1_renamed 
---------------
-         1000
-(1 row)
-
-DROP VARIABLE public.var1_renamed;
-SET ROLE TO DEFAULt;
--- default rights test
-ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON VARIABLES TO var_test_role;
-CREATE VARIABLE public.var2 AS int;
-SET ROLE TO var_test_role;
--- should be ok
-LET public.var2 = 100;
-SELECT public.var2;
- var2 
-------
-  100
-(1 row)
-
-SET ROLE TO DEFAULt;
-DROP VARIABLE public.var2;
-DROP OWNED BY var_test_role;
-DROP ROLE var_test_role;
-CREATE VARIABLE xx AS text DEFAULT 'hello';
-SELECT xx, upper(xx);
-  xx   | upper 
--------+-------
- hello | HELLO
-(1 row)
-
-LET xx = 'Hi';
-SELECT xx;
- xx 
-----
- Hi
-(1 row)
-
-DROP VARIABLE xx;
--- ON TRANSACTION END RESET tests
-CREATE VARIABLE t1 AS int DEFAULT -1 ON TRANSACTION END RESET;
-BEGIN;
-  SELECT t1;
- t1 
-----
- -1
-(1 row)
-
-  LET t1 = 100;
-  SELECT t1;
- t1  
------
- 100
-(1 row)
-
-COMMIT;
-SELECT t1;
- t1 
-----
- -1
-(1 row)
-
-BEGIN;
-  SELECT t1;
- t1 
-----
- -1
-(1 row)
-
-  LET t1 = 100;
-  SELECT t1;
- t1  
------
- 100
-(1 row)
-
-ROLLBACK;
-SELECT t1;
- t1 
-----
- -1
-(1 row)
-
-DROP VARIABLE t1;
-CREATE VARIABLE v1 AS int DEFAULT 0;
-CREATE VARIABLE v2 AS text DEFAULT 'none';
-LET v1 = 100;
-LET v2 = 'Hello';
-SELECT v1, v2;
- v1  |  v2   
------+-------
- 100 | Hello
-(1 row)
-
-LET v1 = DEFAULT;
-LET v2 = DEFAULT;
-SELECT v1, v2;
- v1 |  v2  
-----+------
-  0 | none
-(1 row)
-
-DROP VARIABLE v1;
-DROP VARIABLE v2;
--- ON COMMIT DROP tests
--- should be 0 always
-SELECT count(*) FROM pg_variable;
- count 
--------
-     0
-(1 row)
-
-CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
-SELECT count(*) FROM pg_variable;
- count 
--------
-     0
-(1 row)
-
-BEGIN;
-  CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
-COMMIT;
-SELECT count(*) FROM pg_variable;
- count 
--------
-     0
-(1 row)
-
-BEGIN;
-  CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
-ROLLBACK;
-SELECT count(*) FROM pg_variable;
- count 
--------
-     0
-(1 row)
-
--- test on query with workers
-CREATE TABLE svar_test(a int);
-INSERT INTO svar_test SELECT * FROM generate_series(1,1000000);
-ANALYZE svar_test;
-CREATE VARIABLE zero int;
-LET zero = 0;
--- parallel workers should be used
-EXPLAIN (costs off) SELECT count(*) FROM svar_test WHERE a%10 = zero;
-                    QUERY PLAN                    
---------------------------------------------------
- Finalize Aggregate
-   ->  Gather
-         Workers Planned: 2
-         ->  Partial Aggregate
-               ->  Parallel Seq Scan on svar_test
-                     Filter: ((a % 10) = zero)
-(6 rows)
-
--- result should be 100000
-SELECT count(*) FROM svar_test WHERE a%10 = zero;
- count  
---------
- 100000
-(1 row)
-
-LET zero = (SELECT count(*) FROM svar_test);
--- result should be 1000000
-SELECT zero;
-  zero   
----------
- 1000000
-(1 row)
-
--- parallel workers should be used
-EXPLAIN (costs off) LET zero = (SELECT count(*) FROM svar_test);
-                        QUERY PLAN                        
-----------------------------------------------------------
- SET SESSION VARIABLE
- Result
-   InitPlan 1 (returns $1)
-     ->  Finalize Aggregate
-           ->  Gather
-                 Workers Planned: 2
-                 ->  Partial Aggregate
-                       ->  Parallel Seq Scan on svar_test
-(8 rows)
-
-DROP TABLE svar_test;
-DROP VARIABLE zero;
--- use variables in prepared statements
-CREATE VARIABLE v AS numeric;
-LET v = 3.14;
--- use variables in views
-CREATE VIEW vv AS SELECT COALESCE(v, 0) + 1000 AS result;
-SELECT * FROM vv;
- result  
----------
- 1003.14
-(1 row)
-
--- start a new session
-\c
-SET search_path to svartest;
-SELECT * FROM vv;
- result 
---------
-   1000
-(1 row)
-
-LET v = 3.14;
-SELECT * FROM vv;
- result  
----------
- 1003.14
-(1 row)
-
--- should fail, dependency
-DROP VARIABLE v;
-ERROR:  cannot drop session variable v because other objects depend on it
-DETAIL:  view vv depends on session variable v
-HINT:  Use DROP ... CASCADE to drop the dependent objects too.
--- should be ok
-DROP VARIABLE v CASCADE;
-NOTICE:  drop cascades to view vv
--- other features
-CREATE VARIABLE dt AS integer DEFAULT 0;
-LET dt = 100;
-SELECT dt;
- dt  
------
- 100
-(1 row)
-
-DISCARD VARIABLES;
-SELECT dt;
- dt 
-----
-  0
-(1 row)
-
-DROP VARIABLE dt;
--- NOT NULL
-CREATE VARIABLE v1 AS int NOT NULL;
-CREATE VARIABLE v2 AS int NOT NULL DEFAULT NULL;
--- should fail
-SELECT v1;
-ERROR:  null value is not allowed for NOT NULL session variable "svartest.v1"
-DETAIL:  The session variable was not initialized yet.
-SELECT v2;
-ERROR:  null value is not allowed for NOT NULL session variable "svartest.v2"
-LET v1 = NULL;
-ERROR:  null value is not allowed for NOT NULL session variable "svartest.v1"
-LET v2 = NULL;
-ERROR:  null value is not allowed for NOT NULL session variable "svartest.v2"
-LET v1 = DEFAULT;
-ERROR:  null value is not allowed for NOT NULL session variable "svartest.v1"
-LET v2 = DEFAULT;
-ERROR:  null value is not allowed for NOT NULL session variable "svartest.v2"
--- should be ok
-LET v1 = 100;
-LET v2 = 1000;
-SELECT v1, v2;
- v1  |  v2  
------+------
- 100 | 1000
-(1 row)
-
-DROP VARIABLE v1;
-DROP VARIABLE v2;
-CREATE VARIABLE tv AS int;
-CREATE VARIABLE IF NOT EXISTS tv AS int;
-NOTICE:  session variable "tv" already exists, skipping
-DROP VARIABLE tv;
-CREATE IMMUTABLE VARIABLE iv AS int DEFAULT 100;
-SELECT iv;
- iv  
------
- 100
-(1 row)
-
--- should fail;
-LET iv = 10000;
-ERROR:  session variable "svartest.iv" is declared IMMUTABLE
-DROP VARIABLE iv;
--- different order
-CREATE IMMUTABLE VARIABLE iv AS int DEFAULT 100;
--- should to fail
-LET iv = 10000;
-ERROR:  session variable "svartest.iv" is declared IMMUTABLE
--- should be ok
-SELECT iv;
- iv  
------
- 100
-(1 row)
-
-DROP VARIABLE iv;
-CREATE IMMUTABLE VARIABLE iv AS int;
--- should be ok
-LET iv = NULL;
--- should fail
-LET iv = NULL;
-ERROR:  session variable "svartest.iv" is declared IMMUTABLE
-DROP VARIABLE iv;
--- create variable inside plpgsql block
-DO $$
-BEGIN
-  CREATE VARIABLE do_test_svar AS date DEFAULT '2000-01-01';
-END;
-$$;
-SELECT do_test_svar;
- do_test_svar 
---------------
- 01-01-2000
-(1 row)
-
-DROP VARIABLE do_test_svar;
--- should fail
-CREATE IMMUTABLE VARIABLE xx AS int NOT NULL;
-ERROR:  IMMUTABLE NOT NULL variable requires default expression
--- REASSIGN OWNED test
-CREATE ROLE var_test_role1;
-CREATE ROLE var_test_role2;
-CREATE VARIABLE xxx_var AS int;
-ALTER VARIABLE xxx_var OWNER TO var_test_role1;
-REASSIGN OWNED BY var_test_role1 to var_test_role2;
-SELECT varowner::regrole FROM pg_variable WHERE varname = 'xxx_var';
-    varowner    
-----------------
- var_test_role2
-(1 row)
-
-DROP OWNED BY var_test_role1;
-DROP ROLE var_test_role1;
-SELECT count(*) FROM pg_variable WHERE varname = 'xxx_var';
- count 
--------
-     1
-(1 row)
-
-DROP OWNED BY var_test_role2;
-DROP ROLE var_test_role2;
-SELECT count(*) FROM pg_variable WHERE varname = 'xxx_var';
- count 
--------
-     0
-(1 row)
-
--- creating, dropping temporary variable
-BEGIN;
-CREATE TEMP VARIABLE tempvar AS INT ON COMMIT DROP;
-LET tempvar = 100;
-SAVEPOINT s1;
-DROP VARIABLE tempvar;
-ROLLBACK TO s1;
-SELECT tempvar;
- tempvar 
----------
-     100
-(1 row)
-
-COMMIT;
--- should to fail
-LET tempvar = 100;
-ERROR:  session variable "tempvar" doesn't exist
-LINE 1: LET tempvar = 100;
-            ^
-BEGIN;
-SAVEPOINT s1;
-CREATE TEMP VARIABLE tempvar AS INT ON COMMIT DROP;
-LET tempvar = 100;
-ROLLBACK TO s1;
-COMMIT;
--- should to fail
-LET tempvar = 100;
-ERROR:  session variable "tempvar" doesn't exist
-LINE 1: LET tempvar = 100;
-            ^
-CREATE VARIABLE var1 AS int;
-LET var1 = 100;
-BEGIN;
-DROP VARIABLE var1;
-ROLLBACK;
-SELECT var1;
- var1 
-------
-  100
-(1 row)
-
-DROP VARIABLE var1;
-CREATE VARIABLE var1 AS int DEFAULT 100;
-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;
-CREATE TABLE xxtab(avar int);
-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 
-------
-   10
-(1 row)
-
-SET session_variables_ambiguity_warning TO on;
-SELECT xxtab.avar FROM xxtab;
-WARNING:  session variable "xxtab.avar" is shadowed
-LINE 1: SELECT xxtab.avar FROM xxtab;
-               ^
-DETAIL:  Session variables can be shadowed by columns, routine's variables and routine's arguments with same name.
- avar 
-------
-   10
-(1 row)
-
-SET search_path = svartest;
-CREATE VARIABLE testvar as int;
--- plpgsql variables are preferred against session variables
-DO $$
-<<myblock>>
-DECLARE testvar int;
-BEGIN
-  -- should be ok without warning
-  LET testvar = 100;
-  -- should be ok without warning
-  testvar := 1000;
-  -- should be ok without warning
-  RAISE NOTICE 'session variable is %', svartest.testvar;
-  -- should be ok without warning
-  RAISE NOTICE 'plpgsql variable is %', myblock.testvar;
-  -- should to print plpgsql variable with warning
-  RAISE NOTICE 'variable is %', testvar;
-END;
-$$;
-NOTICE:  session variable is 100
-NOTICE:  plpgsql variable is 1000
-WARNING:  session variable "testvar" is shadowed
-LINE 1: testvar
-        ^
-DETAIL:  Session variables can be shadowed by columns, routine's variables and routine's arguments with same name.
-QUERY:  testvar
-NOTICE:  variable is 1000
-DROP VARIABLE testvar;
-SET session_variables_ambiguity_warning TO default;
--- should be ok
-SELECT avar FROM xxtab;
- avar 
-------
-   10
-(1 row)
-
-CREATE VARIABLE public.avar AS int;
--- should to fail
-SELECT avar FROM xxtab;
- avar 
-------
-   10
-(1 row)
-
--- should be ok
-SELECT public.avar FROM xxtab;
- avar 
-------
-     
-(1 row)
-
-DROP VARIABLE xxtab;
-SELECT xxtab.avar FROM xxtab;
- avar 
-------
-   10
-(1 row)
-
-DROP VARIABLE public.avar;
-DROP TYPE xxtype;
-DROP TABLE xxtab;
--- test of plan cache invalidation
-CREATE VARIABLE xx AS int;
-SET plan_cache_mode = force_generic_plan;
-PREPARE pp AS SELECT xx;
-EXECUTE pp;
- xx 
-----
-   
-(1 row)
-
-DROP VARIABLE xx;
-CREATE VARIABLE xx AS int;
--- should to work
-EXECUTE pp;
- xx 
-----
-   
-(1 row)
-
-DROP VARIABLE xx;
-DEALLOCATE pp;
-SET plan_cache_mode = DEFAULT;
-CREATE ROLE var_test_role;
-CREATE SCHEMA vartest;
-GRANT USAGE ON SCHEMA vartest TO var_test_role;
-CREATE VARIABLE vartest.x AS int;
-CREATE VARIABLE vartest.y AS int;
-LET vartest.x = 100;
-LET vartest.y = 101;
-GRANT READ ON ALL VARIABLES IN SCHEMA vartest TO var_test_role;
-SET ROLE TO var_test_role;
-SELECT vartest.x, vartest.y;
-  x  |  y  
------+-----
- 100 | 101
-(1 row)
-
-SET ROLE TO DEFAULT;
-REVOKE READ ON ALL VARIABLES IN SCHEMA vartest FROM var_test_role;
-SET ROLE TO var_test_role;
--- should to fail
-SELECT vartest.x;
-ERROR:  permission denied for session variable x
-SELECT vartest.y;
-ERROR:  permission denied for session variable y
-SET ROLE TO DEFAULT;
-DROP VARIABLE vartest.x, vartest.y;
-DROP SCHEMA vartest;
-DROP ROLE var_test_role;
--- test cached plan
-CREATE VARIABLE v1 AS text;
-CREATE VARIABLE v2 AS int;
-CREATE VARIABLE v3 AS int;
-LET v1 = 'test';
-LET v2 = 10;
-LET v3 = 5;
-PREPARE q1 AS SELECT v1 || i FROM generate_series(1, v2) g(i) WHERE i IN (v2, v3);
-SET plan_cache_mode to force_generic_plan;
-EXECUTE q1;
- ?column? 
-----------
- test5
- test10
-(2 rows)
-
-EXPLAIN EXECUTE q1;
-                              QUERY PLAN                               
------------------------------------------------------------------------
- Function Scan on generate_series g  (cost=0.00..0.14 rows=2 width=32)
-   Filter: (i = ANY (ARRAY[v2, v3]))
-(2 rows)
-
--- dependecy check
-DROP VARIABLE v3;
--- recreate v3 again
-CREATE VARIABLE v3 AS int DEFAULT 6;
--- should to work, the plan should be recreated
-EXECUTE q1;
- ?column? 
-----------
- test6
- test10
-(2 rows)
-
-DEALLOCATE q1;
--- fill v1 by long text
-LET v1 = repeat(' ', 10000);
-PREPARE q1 AS SELECT length(v1);
-EXECUTE q1;
- length 
---------
-  10000
-(1 row)
-
-LET v1 = repeat(' ', 5000);
-EXECUTE q1;
- length 
---------
-   5000
-(1 row)
-
-DEALLOCATE q1;
-SET plan_cache_mode to default;
-DROP VARIABLE v1, v2, v3;
-CREATE ROLE var_test_role;
-CREATE VARIABLE public.v1 AS int DEFAULT 0;
--- check acl when variable is acessed by simple eval expr method
-CREATE OR REPLACE FUNCTION public.fx_var(int)
-RETURNS int AS $$
-DECLARE xx int;
-BEGIN
-  xx := public.v1 + $1;
-  RETURN xx;
-END;
-$$ LANGUAGE plpgsql;
--- should be ok
-SELECT public.fx_var(0);
- fx_var 
---------
-      0
-(1 row)
-
-SET ROLE TO var_test_role;
--- should to fail
-SELECT public.fx_var(0);
-ERROR:  permission denied for session variable v1
-CONTEXT:  PL/pgSQL function public.fx_var(integer) line 4 at assignment
-SET ROLE TO default;
-GRANT READ ON VARIABLE public.v1 TO var_test_role;
-SET ROLE TO var_test_role;
--- should be ok
-SELECT public.fx_var(0);
- fx_var 
---------
-      0
-(1 row)
-
-SET ROLE TO default;
-REVOKE READ ON VARIABLE public.v1 FROM var_test_role;
-SET ROLE TO var_test_role;
--- should be fail
-SELECT public.fx_var(0);
-ERROR:  permission denied for session variable v1
-CONTEXT:  PL/pgSQL function public.fx_var(integer) line 4 at assignment
-SET ROLE TO DEFAULT;
-DROP FUNCTION public.fx_var(int);
-DROP VARIABLE public.v1;
-DROP ROLE var_test_role;
 CREATE TYPE public.svar_test_type AS (a int, b int, c numeric);
 CREATE VARIABLE public.svar AS public.svar_test_type;
 LET public.svar = ROW(10,20,30);
 SELECT public.svar;
+NOTICE:  get_svariable_valid_type_gennum (1), fast_check: 1
+NOTICE:  svartype_verify_composite_fast: 3 3
+NOTICE:  svartype_verify_composite_fast3, result: 1
+NOTICE:  svartype_verify_composite_fast, result: 1
+    svar    
+------------
+ (10,20,30)
+(1 row)
+
+SELECT public.svar;
+NOTICE:  get_svariable_valid_type_gennum (1), fast_check: 1
+NOTICE:  svartype_verify_composite_fast: 3 3
+NOTICE:  svartype_verify_composite_fast3, result: 1
+NOTICE:  svartype_verify_composite_fast, result: 1
     svar    
 ------------
  (10,20,30)
@@ -982,14 +24,34 @@ SELECT public.svar;
 ALTER TYPE public.svar_test_type DROP ATTRIBUTE c;
 -- should to fail
 SELECT public.svar;
+NOTICE:  get_svariable_valid_type_gennum (1), fast_check: 1
+NOTICE:  svartype_verify_composite_fast: 3 3
+NOTICE:  detected dropped column pos1
+NOTICE:  svartype_verify_composite_fast3, result: 0
+NOTICE:  svartype_verify_composite_fast, result: 0
+NOTICE:  slow check
+NOTICE:  slow check 3 2
 ERROR:  format of stored value of "public.svar" session variable can be outdated
 ALTER TYPE public.svar_test_type ADD ATTRIBUTE c int;
 -- should to fail too (different type, different generation number);
 SELECT public.svar;
 ERROR:  format of stored value of "public.svar" session variable can be outdated
 LET public.svar = ROW(10,20,30);
+NOTICE:  get_svariable_valid_type_gennum (1), fast_check: 1
+NOTICE:  svartype_verify_composite_fast: 2 4
+NOTICE:  svartype_verify_composite_fast3, result: 1
+NOTICE:  detected dropped column pos2
+NOTICE:  svartype_verify_composite_fast exit14
+NOTICE:  svartype_verify_composite_fast, result: 0
+NOTICE:  slow check
+NOTICE:  slow check 2 3
 -- should be ok again for new value
 SELECT public.svar;
+NOTICE:  get_svariable_valid_type_gennum (1), fast_check: 1
+NOTICE:  svartype_verify_composite_fast: 3 4
+NOTICE:  detected dropped column pos1
+NOTICE:  svartype_verify_composite_fast3, result: 1
+NOTICE:  svartype_verify_composite_fast, result: 1
     svar    
 ------------
  (10,20,30)
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 044d034ea8..73294b29b4 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -1,743 +1,3 @@
-CREATE SCHEMA svartest;
-
-SET search_path = svartest;
-
-CREATE VARIABLE var1 AS integer;
-CREATE TEMP VARIABLE var2 AS text;
-
-DROP VARIABLE var1, var2;
-
--- functional interface
-CREATE VARIABLE var1 AS numeric;
-
-CREATE ROLE var_test_role;
-GRANT USAGE ON SCHEMA svartest TO var_test_role;
-
-SET ROLE TO var_test_role;
-
--- should fail
-SELECT var1;
-
-SET ROLE TO DEFAULT;
-
-GRANT READ ON VARIABLE var1 TO var_test_role;
-
-SET ROLE TO var_test_role;
--- should fail
-LET var1 = 10;
--- should work
-SELECT var1;
-
-SET ROLE TO DEFAULT;
-
-GRANT WRITE ON VARIABLE var1 TO var_test_role;
-
-SET ROLE TO var_test_role;
-
--- should work
-LET var1 = 333;
-
-SET ROLE TO DEFAULT;
-
-REVOKE ALL ON VARIABLE var1 FROM var_test_role;
-
-CREATE OR REPLACE FUNCTION secure_var()
-RETURNS int AS $$
-  SELECT svartest.var1::int;
-$$ LANGUAGE sql SECURITY DEFINER;
-
-SELECT secure_var();
-
-SET ROLE TO var_test_role;
-
--- should fail
-SELECT svartest.var1;
-
--- should work;
-SELECT secure_var();
-
-SET ROLE TO DEFAULT;
-
-EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var1;
-
-CREATE VIEW schema_var_view AS SELECT var1;
-
-SELECT * FROM schema_var_view;
-
-\c -
-
-SET search_path = svartest;
-
--- should work still, but var will be empty
-SELECT * FROM schema_var_view;
-
-LET var1 = pi();
-
-SELECT var1;
-
--- we can see execution plan of LET statement
-EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
-
-SELECT var1;
-
-CREATE VARIABLE var3 AS int;
-
-CREATE OR REPLACE FUNCTION inc(int)
-RETURNS int AS $$
-BEGIN
-  LET svartest.var3 = COALESCE(svartest.var3 + $1, $1);
-  RETURN var3;
-END;
-$$ LANGUAGE plpgsql;
-
-SELECT inc(1);
-SELECT inc(1);
-SELECT inc(1);
-
-SELECT inc(1) FROM generate_series(1,10);
-
-SET ROLE TO var_test_role;
-
--- should fail
-LET var3 = 0;
-
-SET ROLE TO DEFAULT;
-
-DROP VIEW schema_var_view;
-
-DROP VARIABLE var1 CASCADE;
-DROP VARIABLE var3 CASCADE;
-
--- composite variables
-
-CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2));
-
-CREATE VARIABLE v1 AS sv_xyz;
-CREATE VARIABLE v2 AS sv_xyz;
-
-\d v1
-\d v2
-
-LET v1 = (1,2,3.14);
-LET v2 = (10,20,3.14*10);
-
--- should work too - there are prepared casts
-LET v1 = (1,2,3.14);
-
-SELECT v1;
-SELECT v2;
-SELECT (v1).*;
-SELECT (v2).*;
-
-SELECT v1.x + v1.z;
-SELECT v2.x + v2.z;
-
--- access to composite fields should be safe too
--- should fail
-SET ROLE TO var_test_role;
-
-SELECT v2.x;
-
-SET ROLE TO DEFAULT;
-
-DROP VARIABLE v1;
-DROP VARIABLE v2;
-
-REVOKE USAGE ON SCHEMA svartest FROM var_test_role;
-DROP ROLE var_test_role;
-
--- scalar variables should not be in conflict with qualified column
-CREATE VARIABLE varx AS text;
-SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class';
-
--- should fail
-SELECT varx.xxx;
-
--- variables can be updated under RO transaction
-
-BEGIN;
-SET TRANSACTION READ ONLY;
-LET varx = 'hello';
-COMMIT;
-
-SELECT varx;
-
-DROP VARIABLE varx;
-
-CREATE TYPE t1 AS (a int, b numeric, c text);
-
-CREATE VARIABLE v1 AS t1;
-LET v1 = (1, pi(), 'hello');
-SELECT v1;
-LET v1.b = 10.2222;
-SELECT v1;
-
--- should fail
-LET v1.x = 10;
-
-DROP VARIABLE v1;
-DROP TYPE t1;
-
--- arrays are supported
-CREATE VARIABLE va1 AS numeric[];
-LET va1 = ARRAY[1.1,2.1];
-LET va1[1] = 10.1;
-SELECT va1;
-
-CREATE TYPE ta2 AS (a numeric, b numeric[]);
-CREATE VARIABLE va2 AS ta2;
-LET va2 = (10.1, ARRAY[0.0, 0.0]);
-LET va2.a = 10.2;
-SELECT va2;
-LET va2.b[1] = 10.3;
-SELECT va2;
-
-DROP VARIABLE va1;
-DROP VARIABLE va2;
-DROP TYPE ta2;
-
--- default values
-CREATE VARIABLE v1 AS numeric DEFAULT pi();
-LET v1 = v1 * 2;
-SELECT v1;
-
-CREATE TYPE t2 AS (a numeric, b text);
-CREATE VARIABLE v2 AS t2 DEFAULT (NULL, 'Hello');
-LET svartest.v2.a = pi();
-SELECT v2;
-
--- should fail due dependency
-DROP TYPE t2;
-
--- should be ok
-DROP VARIABLE v1;
-DROP VARIABLE v2;
-
--- tests of alters
-CREATE SCHEMA var_schema1;
-CREATE SCHEMA var_schema2;
-
-CREATE VARIABLE var_schema1.var1 AS integer;
-LET var_schema1.var1 = 1000;
-SELECT var_schema1.var1;
-ALTER VARIABLE var_schema1.var1 SET SCHEMA var_schema2;
-SELECT var_schema2.var1;
-
-CREATE ROLE var_test_role;
-
-ALTER VARIABLE var_schema2.var1 OWNER TO var_test_role;
-SET ROLE TO var_test_role;
-
--- should fail, no access to schema var_schema2.var
-SELECT var_schema2.var1;
-DROP VARIABLE var_schema2.var1;
-
-SET ROLE TO DEFAULT;
-
-ALTER VARIABLE var_schema2.var1 SET SCHEMA public;
-
-SET ROLE TO var_test_role;
-SELECT public.var1;
-
-ALTER VARIABLE public.var1 RENAME TO var1_renamed;
-
-SELECT public.var1_renamed;
-
-DROP VARIABLE public.var1_renamed;
-
-SET ROLE TO DEFAULt;
-
--- default rights test
-ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON VARIABLES TO var_test_role;
-
-CREATE VARIABLE public.var2 AS int;
-
-SET ROLE TO var_test_role;
-
--- should be ok
-LET public.var2 = 100;
-SELECT public.var2;
-
-SET ROLE TO DEFAULt;
-
-DROP VARIABLE public.var2;
-DROP OWNED BY var_test_role;
-
-DROP ROLE var_test_role;
-
-CREATE VARIABLE xx AS text DEFAULT 'hello';
-
-SELECT xx, upper(xx);
-
-LET xx = 'Hi';
-
-SELECT xx;
-
-DROP VARIABLE xx;
-
--- ON TRANSACTION END RESET tests
-CREATE VARIABLE t1 AS int DEFAULT -1 ON TRANSACTION END RESET;
-
-BEGIN;
-  SELECT t1;
-  LET t1 = 100;
-  SELECT t1;
-COMMIT;
-
-SELECT t1;
-
-BEGIN;
-  SELECT t1;
-  LET t1 = 100;
-  SELECT t1;
-ROLLBACK;
-
-SELECT t1;
-
-DROP VARIABLE t1;
-
-CREATE VARIABLE v1 AS int DEFAULT 0;
-CREATE VARIABLE v2 AS text DEFAULT 'none';
-
-LET v1 = 100;
-LET v2 = 'Hello';
-SELECT v1, v2;
-LET v1 = DEFAULT;
-LET v2 = DEFAULT;
-SELECT v1, v2;
-
-DROP VARIABLE v1;
-DROP VARIABLE v2;
-
--- ON COMMIT DROP tests
--- should be 0 always
-SELECT count(*) FROM pg_variable;
-
-CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
-
-SELECT count(*) FROM pg_variable;
-
-BEGIN;
-  CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
-COMMIT;
-
-SELECT count(*) FROM pg_variable;
-
-BEGIN;
-  CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
-ROLLBACK;
-
-SELECT count(*) FROM pg_variable;
-
--- test on query with workers
-CREATE TABLE svar_test(a int);
-INSERT INTO svar_test SELECT * FROM generate_series(1,1000000);
-ANALYZE svar_test;
-CREATE VARIABLE zero int;
-LET zero = 0;
-
--- parallel workers should be used
-EXPLAIN (costs off) SELECT count(*) FROM svar_test WHERE a%10 = zero;
-
--- result should be 100000
-SELECT count(*) FROM svar_test WHERE a%10 = zero;
-
-LET zero = (SELECT count(*) FROM svar_test);
-
--- result should be 1000000
-SELECT zero;
-
--- parallel workers should be used
-EXPLAIN (costs off) LET zero = (SELECT count(*) FROM svar_test);
-
-DROP TABLE svar_test;
-DROP VARIABLE zero;
-
--- use variables in prepared statements
-CREATE VARIABLE v AS numeric;
-LET v = 3.14;
-
--- use variables in views
-CREATE VIEW vv AS SELECT COALESCE(v, 0) + 1000 AS result;
-SELECT * FROM vv;
-
--- start a new session
-\c
-
-SET search_path to svartest;
-
-SELECT * FROM vv;
-LET v = 3.14;
-SELECT * FROM vv;
-
--- should fail, dependency
-DROP VARIABLE v;
-
--- should be ok
-DROP VARIABLE v CASCADE;
-
--- other features
-CREATE VARIABLE dt AS integer DEFAULT 0;
-
-LET dt = 100;
-SELECT dt;
-
-DISCARD VARIABLES;
-
-SELECT dt;
-
-DROP VARIABLE dt;
-
--- NOT NULL
-CREATE VARIABLE v1 AS int NOT NULL;
-CREATE VARIABLE v2 AS int NOT NULL DEFAULT NULL;
-
--- should fail
-SELECT v1;
-SELECT v2;
-LET v1 = NULL;
-LET v2 = NULL;
-LET v1 = DEFAULT;
-LET v2 = DEFAULT;
-
--- should be ok
-LET v1 = 100;
-LET v2 = 1000;
-SELECT v1, v2;
-
-DROP VARIABLE v1;
-DROP VARIABLE v2;
-
-CREATE VARIABLE tv AS int;
-CREATE VARIABLE IF NOT EXISTS tv AS int;
-DROP VARIABLE tv;
-
-CREATE IMMUTABLE VARIABLE iv AS int DEFAULT 100;
-SELECT iv;
-
--- should fail;
-LET iv = 10000;
-
-DROP VARIABLE iv;
-
--- different order
-CREATE IMMUTABLE VARIABLE iv AS int DEFAULT 100;
--- should to fail
-LET iv = 10000;
--- should be ok
-SELECT iv;
-
-DROP VARIABLE iv;
-
-CREATE IMMUTABLE VARIABLE iv AS int;
-
--- should be ok
-LET iv = NULL;
-
--- should fail
-LET iv = NULL;
-
-DROP VARIABLE iv;
-
--- create variable inside plpgsql block
-DO $$
-BEGIN
-  CREATE VARIABLE do_test_svar AS date DEFAULT '2000-01-01';
-END;
-$$;
-
-SELECT do_test_svar;
-
-DROP VARIABLE do_test_svar;
-
--- should fail
-CREATE IMMUTABLE VARIABLE xx AS int NOT NULL;
-
-
-
--- REASSIGN OWNED test
-CREATE ROLE var_test_role1;
-CREATE ROLE var_test_role2;
-
-CREATE VARIABLE xxx_var AS int;
-
-ALTER VARIABLE xxx_var OWNER TO var_test_role1;
-REASSIGN OWNED BY var_test_role1 to var_test_role2;
-
-SELECT varowner::regrole FROM pg_variable WHERE varname = 'xxx_var';
-
-DROP OWNED BY var_test_role1;
-DROP ROLE var_test_role1;
-SELECT count(*) FROM pg_variable WHERE varname = 'xxx_var';
-
-DROP OWNED BY var_test_role2;
-DROP ROLE var_test_role2;
-SELECT count(*) FROM pg_variable WHERE varname = 'xxx_var';
-
--- creating, dropping temporary variable
-BEGIN;
-
-CREATE TEMP VARIABLE tempvar AS INT ON COMMIT DROP;
-
-LET tempvar = 100;
-
-SAVEPOINT s1;
-
-DROP VARIABLE tempvar;
-
-ROLLBACK TO s1;
-
-SELECT tempvar;
-
-COMMIT;
-
--- should to fail
-LET tempvar = 100;
-
-BEGIN;
-
-SAVEPOINT s1;
-
-CREATE TEMP VARIABLE tempvar AS INT ON COMMIT DROP;
-
-LET tempvar = 100;
-
-ROLLBACK TO s1;
-
-COMMIT;
-
--- should to fail
-LET tempvar = 100;
-
-CREATE VARIABLE var1 AS int;
-LET var1 = 100;
-BEGIN;
-DROP VARIABLE var1;
-ROLLBACK;
-SELECT var1;
-
-DROP VARIABLE var1;
-
-CREATE VARIABLE var1 AS int DEFAULT 100;
-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;
-
-CREATE TABLE xxtab(avar int);
-
-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;
-
-SET session_variables_ambiguity_warning TO on;
-
-SELECT xxtab.avar FROM xxtab;
-
-SET search_path = svartest;
-
-CREATE VARIABLE testvar as int;
-
--- plpgsql variables are preferred against session variables
-DO $$
-<<myblock>>
-DECLARE testvar int;
-BEGIN
-  -- should be ok without warning
-  LET testvar = 100;
-  -- should be ok without warning
-  testvar := 1000;
-  -- should be ok without warning
-  RAISE NOTICE 'session variable is %', svartest.testvar;
-  -- should be ok without warning
-  RAISE NOTICE 'plpgsql variable is %', myblock.testvar;
-  -- should to print plpgsql variable with warning
-  RAISE NOTICE 'variable is %', testvar;
-END;
-$$;
-
-DROP VARIABLE testvar;
-
-SET session_variables_ambiguity_warning TO default;
-
--- should be ok
-SELECT avar FROM xxtab;
-
-CREATE VARIABLE public.avar AS int;
-
--- should to fail
-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;
-
--- test of plan cache invalidation
-CREATE VARIABLE xx AS int;
-
-SET plan_cache_mode = force_generic_plan;
-
-PREPARE pp AS SELECT xx;
-
-EXECUTE pp;
-
-DROP VARIABLE xx;
-
-CREATE VARIABLE xx AS int;
-
--- should to work
-EXECUTE pp;
-
-DROP VARIABLE xx;
-
-DEALLOCATE pp;
-
-SET plan_cache_mode = DEFAULT;
-
-CREATE ROLE var_test_role;
-
-CREATE SCHEMA vartest;
-
-GRANT USAGE ON SCHEMA vartest TO var_test_role;
-
-CREATE VARIABLE vartest.x AS int;
-CREATE VARIABLE vartest.y AS int;
-
-LET vartest.x = 100;
-LET vartest.y = 101;
-
-GRANT READ ON ALL VARIABLES IN SCHEMA vartest TO var_test_role;
-
-SET ROLE TO var_test_role;
-
-SELECT vartest.x, vartest.y;
-
-SET ROLE TO DEFAULT;
-
-REVOKE READ ON ALL VARIABLES IN SCHEMA vartest FROM var_test_role;
-
-SET ROLE TO var_test_role;
-
--- should to fail
-SELECT vartest.x;
-SELECT vartest.y;
-
-SET ROLE TO DEFAULT;
-
-DROP VARIABLE vartest.x, vartest.y;
-
-DROP SCHEMA vartest;
-
-DROP ROLE var_test_role;
-
--- test cached plan
-CREATE VARIABLE v1 AS text;
-CREATE VARIABLE v2 AS int;
-CREATE VARIABLE v3 AS int;
-
-LET v1 = 'test';
-LET v2 = 10;
-LET v3 = 5;
-
-PREPARE q1 AS SELECT v1 || i FROM generate_series(1, v2) g(i) WHERE i IN (v2, v3);
-
-SET plan_cache_mode to force_generic_plan;
-
-EXECUTE q1;
-
-EXPLAIN EXECUTE q1;
-
--- dependecy check
-DROP VARIABLE v3;
-
--- recreate v3 again
-CREATE VARIABLE v3 AS int DEFAULT 6;
-
--- should to work, the plan should be recreated
-EXECUTE q1;
-
-DEALLOCATE q1;
-
--- fill v1 by long text
-LET v1 = repeat(' ', 10000);
-
-PREPARE q1 AS SELECT length(v1);
-
-EXECUTE q1;
-
-LET v1 = repeat(' ', 5000);
-
-EXECUTE q1;
-
-DEALLOCATE q1;
-
-SET plan_cache_mode to default;
-
-DROP VARIABLE v1, v2, v3;
-
-CREATE ROLE var_test_role;
-
-CREATE VARIABLE public.v1 AS int DEFAULT 0;
-
--- check acl when variable is acessed by simple eval expr method
-CREATE OR REPLACE FUNCTION public.fx_var(int)
-RETURNS int AS $$
-DECLARE xx int;
-BEGIN
-  xx := public.v1 + $1;
-  RETURN xx;
-END;
-$$ LANGUAGE plpgsql;
-
--- should be ok
-SELECT public.fx_var(0);
-
-SET ROLE TO var_test_role;
-
--- should to fail
-SELECT public.fx_var(0);
-
-SET ROLE TO default;
-
-GRANT READ ON VARIABLE public.v1 TO var_test_role;
-
-SET ROLE TO var_test_role;
-
--- should be ok
-SELECT public.fx_var(0);
-
-SET ROLE TO default;
-
-REVOKE READ ON VARIABLE public.v1 FROM var_test_role;
-
-SET ROLE TO var_test_role;
-
--- should be fail
-SELECT public.fx_var(0);
-
-SET ROLE TO DEFAULT;
-
-DROP FUNCTION public.fx_var(int);
-
-DROP VARIABLE public.v1;
-
-DROP ROLE var_test_role;
-
 CREATE TYPE public.svar_test_type AS (a int, b int, c numeric);
 
 CREATE VARIABLE public.svar AS public.svar_test_type;
@@ -746,6 +6,8 @@ LET public.svar = ROW(10,20,30);
 
 SELECT public.svar;
 
+SELECT public.svar;
+
 ALTER TYPE public.svar_test_type DROP ATTRIBUTE c;
 
 -- should to fail
-- 
2.36.1

From 7051aefc89750267a978f8fd082f91a70ed26a96 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 10/13] Regress tests for session variables

 
 
 # ----------

From 98092e85b86900b55b3bb53fd296ad5afe0a8640 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 11:40:29 +0200
Subject: [PATCH 11/13] This patch changes error message "column doesn't exist"
 to message "column or variable doesn't exist"

 

 select a from atacc1;

 

index 7ec3d2688f..982f31aa33 100644
@@ -2715,7 +2715,7 @@ select * from rules_fooview;
 (1 row)

 -- should fail
 LET v1.x = 10;

From d5a30132a5a5abf83d9df914c2bb5d4477b0be0b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 07/13] Enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From f248383e1aadc3fbfda0a80dc43d742124e03669 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 08/13] Possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 617b06d87fdbdba765d219e535356aef44a0c294 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 06/13] DISCARD VARIABLES command

---

 

index 79c50a45a8..5702e4753b 100644

From 0c5a07a330bc8ae6d248d69d8dad3fdf7a907e26 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 05/13] Support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 5c0bc4d61d6f201f9e25747b894c1e378e858630 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 04/13] LET command

index e29c2ae206..e807ad98c6 100644
 
+/*
 
index b834e8fe73..ce5dfd166b 100644
@@ -3831,6 +3831,20 @@ _copySelectStmt(const SelectStmt *from)
 	return newnode;
 }
 
+static LetStmt *
@@ -6078,6 +6092,9 @@ copyObjectImpl(const void *from)
index c92235d6ac..61d60988ce 100644
@@ -1468,6 +1468,17 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	return true;
 }
 
+static bool
@@ -3894,6 +3905,9 @@ equal(const void *a, const void *b)
 
+static void

 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:
 
index ffe1caafc8..79c50a45a8 100644
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 /*
 
 /*

 
 	/*

 

 
 /* ----------------
-- 
2.36.1

From 48a047a6ecb1a8eb2d2adacddca376312f6acf3b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/13] typecheck - check of consistency of format of stored
 value.

 
 /*

 

 
 
+
+/*
 
 	/* set callbacks */
 	if (first_time)

 

 

 
 	svar->varid = var->oid;

 
 	if (!sessionvars)

 
 
-	if (!found)

-	if (svar->has_defexpr)
+	/*

 
-

 	return svar;
 }
 
 	if (!sessionvars)

 	*isNull = svar->isnull;
-	*typid = svar->typid;
 
 	if (!svar->isnull)
 
 	return (Datum) 0;
 }

 	if (!svar->isnull)
 
 	return (Datum) 0;
 }

From 05de3cfe7563daff6ff783262f164c6c307fe257 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/13] session variables

 

 
 /*

 
 
+/*
 
 /*

 
 /*
index 1f65daf203..dbfb813338 100644
 
 						/*
index 80c5e04dd5..33fea8148f 100644

+	/*

 

 

index ce76f9b539..b834e8fe73 100644

 	return newnode;
@@ -3688,6 +3690,7 @@ _copyQuery(const Query *from)
@@ -3698,6 +3701,7 @@ _copyQuery(const Query *from)
index 1ace246ce6..c92235d6ac 100644
@@ -288,6 +288,7 @@ _equalParam(const Param *a, const Param *b)
 
 	return true;
@@ -1338,6 +1339,7 @@ _equalQuery(const Query *a, const Query *b)
@@ -1348,6 +1350,7 @@ _equalQuery(const Query *a, const Query *b)

 
 	READ_DONE();

 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*

+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 extern void AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit);
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index fa2d9be4c9..433d052585 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -161,6 +161,7 @@ typedef enum ExprEvalOp
 	EEOP_PARAM_EXEC,
 	EEOP_PARAM_EXTERN,
 	EEOP_PARAM_CALLBACK,
+	EEOP_PARAM_VARIABLE,
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -386,6 +387,13 @@ typedef struct ExprEvalStep
 			Oid			paramtype;	/* OID of parameter's datatype */
 		}			param;
 
@@ -819,6 +827,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,

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

 

 
 
 	/*

 
 
 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.36.1

From 068ff16ff8db768399a6bdd74ae1caeb95d1db4c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/13] Catalogue support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*
 
 				/*
 				 * We don't expect any of these sorts of objects to depend on
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 706d283a92..ce76f9b539 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -5454,6 +5454,23 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
 	return newnode;
 }
 
@@ -6405,6 +6422,9 @@ copyObjectImpl(const void *from)
index fccc0b4a18..1ace246ce6 100644
@@ -2760,6 +2760,22 @@ _equalDropSubscriptionStmt(const DropSubscriptionStmt *a,
 	return true;
 }
 
+static bool
@@ -4223,6 +4239,9 @@ equal(const void *a, const void *b)

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)
 
index 6b0a865262..ffe1caafc8 100644

 
-
 /*
 
 	return isclustered;
 }
+

 	/*

 

 
index 2e41f4d9e8..46b6354a51 100644

 
+

 
 /*

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 7 Jul 2022 07:49:47 +0200

st 6. 7. 2022 v 22:30 odesílatel Pavel Stehule <[email-masked]>
napsal:

>> fresh rebase + type check. Before returning any value, the related type
>> is checked if it is valid still
>>
>

next step

 
 
  </sect1>

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
 
+     <varlistentry>

   <para>

 
 
 # ----------

---

 

 select a from atacc1;

 

 
 
 -- should fail
 LET v1.x = 10;

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From 204a7115b7c65c3141c02cf2a1383c604c3c468d Mon Sep 17 00:00:00 2001

---
 src/backend/commands/sessionvariable.c        |   51 +-
 .../regress/expected/session_variables.out    | 1015 +----------------
 src/test/regress/sql/session_variables.sql    |  742 +-----------
 3 files changed, 91 insertions(+), 1717 deletions(-)

diff --git a/src/backend/commands/sessionvariable.c b/src/backend/commands/sessionvariable.c
index 6b649a55d1..edbd420056 100644
--- a/src/backend/commands/sessionvariable.c
+++ b/src/backend/commands/sessionvariable.c
@@ -242,6 +242,7 @@ static bool
 svariabletypes_equals(SVariableType svt1, SVariableType svt2)
 {
 	Assert(svt1->typid == svt2->typid);
+	Assert(svt1 != svt2);
 
 	/*
 	 * for trustworthy check we need to know base type, extension,
@@ -287,7 +288,9 @@ svariabletypes_equals(SVariableType svt1, SVariableType svt2)
 			return false;
 	}
 
-	if (svt1->natts > 0)
+	elog(NOTICE, "svariabletypes_equals, svt1->natts: %d, svt2->natts: %d", svt1->natts, svt2->natts);
+
+	if (svt1->natts > 0 || svt2->natts > 0)
 	{
 		int		i;
 
@@ -304,6 +307,8 @@ svariabletypes_equals(SVariableType svt1, SVariableType svt2)
 		}
 	}
 
+	elog(NOTICE, "svariabletypes_equals, result true");
+
 	return true;
 }
 
@@ -554,10 +559,12 @@ svartype_verify_composite_fast(SVariableType svt)
 	if (!tupdesc)
 		return false;

 		/* skip dropped attributies */
 		while (attrn < tupdesc->natts)
@@ -566,9 +573,11 @@ svartype_verify_composite_fast(SVariableType svt)
 
 			if (!attr->attisdropped)
 				break;
+			else
@@ -576,11 +585,13 @@ svartype_verify_composite_fast(SVariableType svt)
 
 			if (field_svt->typid != attr->atttypid)
 			{
+				elog(NOTICE, "svartype_verify_composite_fast exit11: %u %u", field_svt->typid, attr->atttypid);
 				result = false;
 				break;
 			}
 			else if (field_gennum != field_svt->gennum)
 			{
+				elog(NOTICE, "svartype_verify_composite_fast exit12: %ld %ld", field_gennum, field_svt->gennum);
 				result = false;
 				break;
 			}
@@ -588,6 +599,7 @@ svartype_verify_composite_fast(SVariableType svt)
 			gennum = get_svariable_valid_type_gennum(field_svt);
 			if (field_gennum != gennum)
 			{
+				elog(NOTICE, "svartype_verify_composite_fast exit13: %ld %ld, ", field_gennum, gennum);
 				result = false;
 				break;
 			}
@@ -599,6 +611,10 @@ svartype_verify_composite_fast(SVariableType svt)
 		}
 	}
 
+
+	elog(NOTICE, "svartype_verify_composite_fast3, result: %d", result);
+
+
 	/* now only dropped columns can be allowed */
 	while (attrn < tupdesc->natts)
 	{
@@ -606,13 +622,19 @@ svartype_verify_composite_fast(SVariableType svt)
 
 		if (!attr->attisdropped)
 		{

 
@@ -648,9 +670,14 @@ get_svariable_valid_type_gennum(SVariableType svt)
 
 	if (fast_check && OidIsValid(svt->extid))
 	{
@@ -682,6 +709,8 @@ get_svariable_valid_type_gennum(SVariableType svt)
 	{
 		SVariableTypeData nsvtd;
 
+		elog(NOTICE, "slow check");
+
 		/*
 		 * Slow check. We create new SVariableType value. Compare it with
 		 * previous value, and if it is different, then we replace old by
@@ -690,16 +719,28 @@ get_svariable_valid_type_gennum(SVariableType svt)

+elog(NOTICE, "slow check %d %d", svt->natts, nsvtd.natts);
+
+
 		if (svariabletypes_equals(svt, &nsvtd))
+		{
 			svariabletype_refresh(svt, &nsvtd);
+elog(NOTICE, "svariables are equal");
+		}
 		else
+		{
 			svariabletype_update(svt, &nsvtd);
+elog(NOTICE, "svariables are not equal");
+
+		}
 	}
 
 	ReleaseSysCache(tuple);
 
 	svt->verified_lxid = MyProc->lxid;
 
+elog(NOTICE, "returned fresh gennum: %ld", svt->gennum);
+
 	return svt->gennum;
 }
 
index b574afa3af..a57498cc16 100644
@@ -1,979 +1,23 @@
+NOTICE:  returned fresh gennum: 1
+NOTICE:  returned fresh gennum: 1
@@ -982,14 +26,41 @@ SELECT public.svar;
+NOTICE:  svariabletypes_equals, svt1->natts: 3, svt2->natts: 2
+NOTICE:  svariables are not equal
+NOTICE:  returned fresh gennum: 2
+NOTICE:  svariabletypes_equals, svt1->natts: 2, svt2->natts: 3
+NOTICE:  svariables are not equal
+NOTICE:  returned fresh gennum: 3
+NOTICE:  returned fresh gennum: 3

 SELECT public.svar;
 
+SELECT public.svar;
+
 
 -- should to fail
-- 
2.36.1

---

 

 
 /*

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

+static void

+static void

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

 
+/*

+static LetStmt *
 
+static bool
 
+static void

 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 	/*

 

 
 /* ----------------
-- 
2.36.1

 
 /*

 

 
 
+
+/*
 
 	/* set callbacks */
 	if (first_time)

 

 

 
 	svar->varid = var->oid;

 
 	if (!sessionvars)

 
 
-	if (!found)

-	if (svar->has_defexpr)
+	/*

 
-

 	return svar;
 }
 
 	if (!sessionvars)

 	*isNull = svar->isnull;
-	*typid = svar->typid;
 
 	if (!svar->isnull)
 
 	return (Datum) 0;
 }

 	if (!svar->isnull)
 
 	return (Datum) 0;
 }

 

 
 /*

 
 
+/*
 
 /*

 
 /*
 
 						/*

+	/*

 

 

 
 
 	return newnode;
 
 	return true;

 
 	READ_DONE();

 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*

+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

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

 

 
 
 	/*

 
 
 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.36.1

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*
 
 				/*

+static bool

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 
-
 /*
 
 	return isclustered;
 }
+

 	/*

 

 

 
+

 
 /*

---

From: Justin Pryzby <[email-masked]>
Date: Thu, 7 Jul 2022 01:43:11 -0500

> This set of patches should to help me with investigation of regress test

Do you know you can do the same as what cfbot does under your own github
account ?  Please see: src/tools/ci/README

-- 
Justin

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 7 Jul 2022 08:52:17 +0200

> account ?  Please see: src/tools/ci/README

I didn't know it. I am sorry for sending garbage to the mailing list.

Thank you for information

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 7 Jul 2022 14:30:10 +0200

Hi

I hope I fixed an broken test on msvc

Regards

Pavel
From bbd27414498b913c3825e4691ad63997766e1c38 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 12/12] documentation

 
 
  </sect1>

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
 
+     <varlistentry>

   <para>

From eb31900121a5732af10d39dd777908679a288aad Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 10/12] Regress tests for session variables

 
 
 # ----------

From 670d7b3d650f3331b507f4d46be8eb22e9f10151 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 08/12] Possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
index f317f0a681..8a260fe3ee 100644
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 45ee291d5ebfea6a6a718c6e388df02b1c498078 Mon Sep 17 00:00:00 2001

 

 select a from atacc1;

 

 
 
 -- should fail
 LET v1.x = 10;

From b465aa7a5e82d9f115ae11baefac4553e66ab880 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 09/12] typedefs

---

index 34a76ceb60..6d451e0983 100644

From 7bad739de941f15f2dac890f337c68a22bc7a732 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 06/12] DISCARD VARIABLES command

---

 
index 6f683949ef..389d0590d3 100644

index cd99cf90a5..c4e23befb8 100644

From be9a0f9f163aa995772852c4b36de396ce54bb67 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 07/12] Enhancing psql for session variables

 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index e572f585ef..49c983b241 100644

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From daaa8a308afce2f818cf8e251c4c714a74a3c302 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 05/12] Support of LET command in PLpgSQL

index d24e565f44..6f683949ef 100644

 /*

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

+static void

+static void

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

From 632318b9aee819d35aea14547026e8bc7e2a73db Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/12] typecheck - check of consistency of format of stored
 value.

Session variables holds a own evidence of fingerprints for any
used type. When some possible not compatible change is detected
(the version of extension is changed), the related stored type's
generation number is increased. The copy of genaration number is
stored in session variable data. Before any read of session variable,
the stored generation number is compared with current type generation
number. When it is different, the read is canceled.
---
 src/backend/commands/sessionvariable.c | 799 ++++++++++++++++++++++---
 1 file changed, 729 insertions(+), 70 deletions(-)

index e26903d135..7e5a9b03d3 100644
 
 /*
@@ -111,10 +118,55 @@
+	char	   *typname;
@@ -145,14 +197,14 @@ static List *xact_reset_recheck_actions = NIL;
 
@@ -160,15 +212,15 @@ typedef struct SVariableData

 
 
 static bool first_time = true;
@@ -176,9 +228,552 @@ static bool first_time = true;
 static bool sync_sessionvars_all_is_required = false;
 static bool recheck_sessionvars_is_required = false;

+
+/*
+	 * trustworthy, because (-1, false) is very common. In this case
+	 * we can check only the name (for other cases we don't check the
+	 * name, because the name can be altered).
+		if (strcmp(svt1->typname, svt2->typname) != 0)
+svariabletype_free(SVariableType svt)
+	pfree(svt->typname);
+		pfree(svt->extname);
+		pfree(svt->extversion);
+	if (svt->natts > 0)
+		pfree(svt->attrs);
+	svariabletype_free(svt1);
+	svt->typname = pstrdup(NameStr(typ->typname));
+			if (field_gennum != field_svt->gennum)
+			if (field_gennum != get_svariable_valid_type_gennum(field_svt))
+			svariabletype_free(&nsvtd);
+	if (svar->gennum != get_svariable_valid_type_gennum(svar->svartype))
@@ -409,9 +1004,10 @@ sync_sessionvars_all()
 
 	/* set callbacks */
 	if (first_time)
@@ -431,14 +1027,24 @@ create_sessionvars_hashtable(void)

 

@@ -466,15 +1072,19 @@ set_session_variable(SVariable svar, Datum value,
 						get_namespace_name(get_session_variable_namespace(svar->varid)),
 						get_session_variable_name(svar->varid))));

@@ -492,7 +1102,10 @@ set_session_variable(SVariable svar, Datum value,
 
 	free_session_variable_value(svar);
@@ -514,17 +1127,13 @@ init_session_variable(SVariable svar, Variable *var)
 	Assert(OidIsValid(var->oid));
 
 	svar->varid = var->oid;

@@ -559,7 +1168,7 @@ prepare_variable_for_reading(Oid varid)
 	var.oid = InvalidOid;
 
 	if (!sessionvars)
 
 	/* Protect used session variable against drop until transaction end */
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
@@ -571,54 +1180,97 @@ prepare_variable_for_reading(Oid varid)
 										HASH_ENTER, &found);

 
-	if (!found)

-	if (svar->has_defexpr)
+	/*

 
-

 	return svar;
 }
@@ -642,7 +1294,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid)
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
 
 	if (!sessionvars)
 
 	svar = (SVariable) hash_search(sessionvars, &varid,
 										HASH_ENTER, &found);
@@ -690,10 +1342,12 @@ CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 	Assert(svar != NULL && svar->is_valid);
 
 	*isNull = svar->isnull;
-	*typid = svar->typid;
 
 	if (!svar->isnull)
 
 	return (Datum) 0;
 }
@@ -711,7 +1365,7 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	svar = prepare_variable_for_reading(varid);
 	Assert(svar != NULL && svar->is_valid);
 
@@ -719,7 +1373,9 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	*isNull = svar->isnull;
 
 	if (!svar->isnull)
 
 	return (Datum) 0;
 }
@@ -737,7 +1393,7 @@ GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	svar = prepare_variable_for_reading(varid);
 	Assert(svar != NULL && svar->is_valid);
 
@@ -914,6 +1570,9 @@ ResetSessionVariables(void)

From a6171cb865b56ad62824e9f2a0ea7db28ad9d97a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 04/12] LET command

index 7e5a9b03d3..58d0424eef 100644
@@ -1594,6 +1595,85 @@ ResetSessionVariables(void)
 
+/*
 
index 1c72c62195..978af5e7e6 100644
 
+static LetStmt *
index 5a1f37d10b..d2d1b6090f 100644
 
+static bool
index 73eab01aa5..50f811cfb0 100644
 
+static void

 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
index 2454eb129b..d24e565f44 100644

 

 
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 	/*
index 98debdfe58..cd99cf90a5 100644

 

 
 /* ----------------
-- 
2.36.1

From f86a08c7d0618344654a2cf18e25d1661e906dd6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/12] session variables

index 18a3edcfac..b8d9d95465 100644

 
 
 /*

 
 
+/*
 
 /*

 
 /*
index c8d7145fe3..f908645669 100644
 
 						/*
index 9fcb6be414..79e4078124 100644
@@ -455,6 +456,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
@@ -1092,6 +1094,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}

+	/*

 

 

index 91cac3b187..1c72c62195 100644

 	return newnode;
index ce797d4b74..5a1f37d10b 100644
 
 	return true;
index 05f27f044b..73eab01aa5 100644

index 014b3005af..27e1ba8749 100644
 
 	READ_DONE();

 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*

+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

index 1e3f1bbee8..3ac7add98f 100644

@@ -814,6 +822,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,

 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
index 63bef3029a..98debdfe58 100644

 

 
 	/*

 
 
 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.36.1

From 6b7a3ddf72311ee3a046a44ff8469f3ae8a17588 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/12] Catalogue support for session variables

index 116de1175b..18a3edcfac 100644

 

 
 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*
index ef5b34a312..1666d205b5 100644
@@ -12686,6 +12686,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 				/*
index 8313b5e5a7..91cac3b187 100644
 
index 449352639f..ce797d4b74 100644
 
+static bool
index 0523013f53..2454eb129b 100644

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 
-
 /*
 
 	return isclustered;
 }
+

 	/*

 

 
 
index 5f6d65b5c4..63bef3029a 100644

+

 
 /*

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 9 Jul 2022 20:57:00 +0200

Hi

fresh rebase

Regards

Pavel
From 4dcb10dc3b425253b3114255a5fb264c7f329750 Mon Sep 17 00:00:00 2001

 

 select a from atacc1;

index 1f0df6b7d9..f7578fea25 100644

@@ -4956,19 +4956,19 @@ select * from
@@ -4980,7 +4980,7 @@ select atts.relid::regclass, s.* from pg_stats s join
@@ -6002,7 +6002,7 @@ lateral (select * from int8_tbl t1,
 
@@ -6012,7 +6012,7 @@ LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
@@ -6047,7 +6047,7 @@ LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
@@ -6067,7 +6067,7 @@ update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1)

 -- should fail
 LET v1.x = 10;

From 13f660afd25b6e2d422bf1397b66a71bf9787194 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 09/12] typedefs

---

From 227bc465c5d12ad1ed516e1b61af2ad8c7420c19 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 08/12] Possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
index e4fdb6b75b..876af04224 100644
@@ -4707,6 +4708,232 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
@@ -9361,7 +9588,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -9951,6 +10179,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION:
@@ -14294,6 +14525,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -17835,6 +18069,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From dbbaf68a515dfd65e7a4d09e6e99d00cf4f99c55 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 10/12] Regress tests for session variables

 
 
 # ----------

From 5dad648926694267ef96e71703e9633e0cc8760b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 12/12] documentation

 
 
  </sect1>

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
 
+     <varlistentry>

   <para>

From 1a7d49708a6bc0227d0b6050019e888cb1cf0c26 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 07/12] Enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From 7bf8920b128c41370ab41f3b506cdeb90e90eb22 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 06/12] DISCARD VARIABLES command

---

 

index f3b8ba0777..28e2e4f6a3 100644
@@ -3855,7 +3855,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;

From b3165969425afda9c61e4066738f5daf4b6a3271 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 05/12] Support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From fd86cb909872bc836c4b9970b54135da433a3a04 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 04/12] LET command

 src/backend/nodes/outfuncs.c             |   1 +
 26 files changed, 735 insertions(+), 11 deletions(-)

+/*
 
index 86f72df766..b5cab1ee8c 100644
@@ -453,6 +453,7 @@ _outQuery(StringInfo str, const Query *node)
index 1853d21303..15312e1730 100644

 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
index 850b477a12..7139b7bb11 100644
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

index 374f7785c7..f3b8ba0777 100644
@@ -1991,6 +1991,21 @@ typedef struct MergeStmt
 
index 1643215f93..2110ef8aae 100644
@@ -49,7 +49,7 @@ typedef struct PlannedStmt
 
 	NodeTag		type;

 

 /* ----------------
-- 
2.36.1

From 54a8fb3e0cb3243fb485cb46624060fb83213e78 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/12] typecheck - check of consistency of format of stored
 value.

 
 /*

 

 
 
+
+/*
 
 	/* set callbacks */
 	if (first_time)

 

 

 
 	svar->varid = var->oid;

 
 	if (!sessionvars)

 
 
-	if (!found)

-	if (svar->has_defexpr)
+	/*

 
-

 	return svar;
 }
 
 	if (!sessionvars)

 	*isNull = svar->isnull;
-	*typid = svar->typid;
 
 	if (!svar->isnull)
 
 	return (Datum) 0;
 }

 	if (!svar->isnull)
 
 	return (Datum) 0;
 }

From d76a34676f0ba08011f445454e2658da4afc93a0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/12] session variables

 src/backend/nodes/outfuncs.c            |   2 +
 src/backend/nodes/readfuncs.c           |   2 +
 src/include/nodes/pathnodes.h           |   4 +
 31 files changed, 1692 insertions(+), 33 deletions(-)

 

 /*

 
 
+/*
 
 /*

 
 /*
 
 						/*

+	/*

 

 

index 9e70bbb4d6..86f72df766 100644
@@ -464,6 +464,7 @@ _outQuery(StringInfo str, const Query *node)
 
@@ -474,6 +475,7 @@ _outQuery(StringInfo str, const Query *node)
index 67b0f798c1..08e23516b7 100644
@@ -255,6 +255,7 @@ _readQuery(void)
@@ -265,6 +266,7 @@ _readQuery(void)
 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 
index 8ed2c4b8c7..1853d21303 100644

 

 

 

 

index 7062b75444..850b477a12 100644
 
 /* GUC parameters */

 /*

+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 17c42d4a88..f398a22cc5 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -581,6 +581,18 @@ typedef struct AsyncRequest
 								 * tuples) */
 } AsyncRequest;
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+	Oid			varid;
+	Oid			typid;
+	bool		isnull;
+	Datum		value;
+}			SessionVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -632,6 +644,13 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
 
+	/* Variables info: */
 
 	/* Other working state: */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index df4dfd60a7..374f7785c7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -140,6 +140,7 @@ typedef struct Query
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
 
@@ -149,6 +150,7 @@ typedef struct Query

diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 44ffc73f15..e46e2756f6 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -157,6 +157,8 @@ typedef struct PlannerGlobal
 
 	/* partition descriptors */
 	PartitionDirectory partition_directory pg_node_attr(read_write_ignore);
+
+	List	   *sessionVariables;	/* list of used session variables */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -438,6 +440,8 @@ struct PlannerInfo
 	/* 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 6ed765cbe4..1643215f93 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -91,6 +91,8 @@ typedef struct PlannedStmt

index 1fc2fbffa3..e408a727cb 100644
 
@@ -305,13 +308,17 @@ typedef struct Const
 
 typedef struct Param
@@ -322,6 +329,7 @@ typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.36.1

From f97d55b2b1139d399479f702a8dfe8b33544bfff Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/12] Catalogue support for session variables

 37 files changed, 1950 insertions(+), 19 deletions(-)

 

 

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*
 
 				/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)
index efcf1cd5ab..7062b75444 100644

 
 
-
 /*
 
 	return isclustered;
 }
+

 	/*

 

 
 
index 0b6a7bb365..df4dfd60a7 100644
@@ -2197,6 +2199,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -3323,6 +3326,25 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+

 
 /*

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 21 Jul 2022 08:16:19 +0200

Hi

new update of session variable;s implementation

- fresh rebase
- new possibility to trace execution with DEBUG1 notification
- new SRF function pg_debug_show_used_session_variables that returns
content of sessionvars hashtab
- redesign of work with list of variables for reset, recheck, on commit
drop, on commit reset

Regards

Pavel
From f7212cb587221b2ca595131d1bb0efafb186e476 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 19 Jul 2022 09:45:34 +0200
Subject: [PATCH 13/13] debug func

---
 src/backend/commands/sessionvariable.c | 48 ++++++++++++++++++++++++++
 src/include/catalog/pg_proc.dat        |  7 ++++
 2 files changed, 55 insertions(+)

diff --git a/src/backend/commands/sessionvariable.c b/src/backend/commands/sessionvariable.c
index 50ae95903d..fa994a4879 100644
--- a/src/backend/commands/sessionvariable.c
+++ b/src/backend/commands/sessionvariable.c
@@ -27,6 +27,7 @@
 #include "commands/session_variable.h"
 #include "executor/executor.h"
 #include "executor/svariableReceiver.h"
+#include "funcapi.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -2001,3 +2002,50 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 		}
 	}
 }
+
+/*
+ * pg_debug_show_used_session_variables - designed for testing
+ *
+ * returns content of session vars
+ */
+Datum
+pg_debug_show_used_session_variables(PG_FUNCTION_ARGS)
+{
+#define NUM_PG_DEBUG_SHOW_USED_SESSION_VARIABLES_ATTS 9
+
+	SetSingleFuncCall(fcinfo, 0);
+
+	if (sessionvars)
+	{
+		ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+		HASH_SEQ_STATUS status;
+		SVariable svar;
+
+		/* Ensure so all entries in sessionvars hash table are valid */
+		sync_sessionvars_all();
+
+		hash_seq_init(&status, sessionvars);
+
+		while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+		{
+			Datum		values[NUM_PG_DEBUG_SHOW_USED_SESSION_VARIABLES_ATTS];
+			bool		nulls[NUM_PG_DEBUG_SHOW_USED_SESSION_VARIABLES_ATTS];
+
+			memset(nulls, 0, sizeof(nulls));
+
+			values[0] = ObjectIdGetDatum(svar->varid);
+			values[1] = PointerGetDatum(cstring_to_text(get_namespace_name(get_session_variable_namespace(svar->varid))));
+			values[2] = PointerGetDatum(cstring_to_text(get_session_variable_name(svar->varid)));
+			values[3] = ObjectIdGetDatum(svar->svartype->typid);
+			values[4] = PointerGetDatum(cstring_to_text(svar->svartype->typname));
+			values[5] = BoolGetDatum(svar->is_valid);
+			values[6] = BoolGetDatum(session_variable_use_valid_type(svar));
+			values[7] = BoolGetDatum(pg_variable_aclcheck(svar->varid, GetUserId(), ACL_READ) == ACLCHECK_OK);
+			values[8] = BoolGetDatum(pg_variable_aclcheck(svar->varid, GetUserId(), ACL_WRITE) == ACLCHECK_OK);
+
+			tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+		}
+	}
+
+	return (Datum) 0;
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 46b6354a51..7909b3d6a8 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11889,4 +11889,11 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+{ oid => '8488', descr => 'debug list of used session variables',
+  proname => 'pg_debug_show_used_session_variables', prorows => '1000', proretset => 't',
+  provolatile => 's', prorettype => 'record', proargtypes => '',
+  proallargtypes => '{oid,text,text,oid,text,bool,bool,bool,bool}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o}',
+  proargnames => '{varid,schema,name,typid,typname,has_value,has_valid_value,can_read,can_write}',
+  prosrc => 'pg_debug_show_used_session_variables' },
 ]
-- 
2.36.1

From a295141e55df998771368f109272b6943d63d2ed Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 09/13] typedefs

---

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

index 4e635561aa..8f91b33ee5 100644
@@ -3635,8 +3635,8 @@ errorMissingColumn(ParseState *pstate,
@@ -3655,8 +3655,8 @@ errorMissingColumn(ParseState *pstate,
index 41cb04f4b6..97402e0293 100644
 
index e3dac1699c..eecf355e5f 100644

 select a from atacc1;

index e1d9d971d6..3463f81df9 100644

 

 -- should fail
 LET v1.x = 10;

From e5ebbd632bb9738c679b5d4d71d9d376794d65ea Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 10/13] Regress tests for session variables

 
 
 # ----------

From eba91f3549d3ce61fdfdc1668b5bb7810afee138 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 12/13] documentation

 doc/src/sgml/catalogs.sgml                    |   5 +
 18 files changed, 838 insertions(+), 9 deletions(-)

 
index a186e35f00..6f4de6f9d6 100644
index e2d728e0c4..e889f1a319 100644

 

 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
 
+     <varlistentry>

From 1c2f821e05b5dee51a4d1e1363dc81cbec637d84 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 08/13] Possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
index f9c51d1e67..18d6d211a3 100644
@@ -4718,6 +4719,232 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
@@ -9372,7 +9599,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -9962,6 +10190,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION:
 			dumpSubscription(fout, (const SubscriptionInfo *) dobj);
 			break;
+		case DO_VARIABLE:
+			dumpVariable(fout, (VariableInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -14305,6 +14536,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
 		case DEFACLOBJ_NAMESPACE:
 			type = "SCHEMAS";
 			break;
+		case DEFACLOBJ_VARIABLE:
+			type = "VARIABLE";
+			break;
 		default:
 			/* shouldn't get here */
 			pg_fatal("unrecognized object type in default privileges: %d",
@@ -17846,6 +18080,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 69ee939d44..8d719bd35e 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,
@@ -82,7 +83,7 @@ typedef enum
 	DO_PUBLICATION,
 	DO_PUBLICATION_REL,
 	DO_PUBLICATION_TABLE_IN_SCHEMA,
-	DO_SUBSCRIPTION
+	DO_SUBSCRIPTION,
 } DumpableObjectType;
 
 /*
@@ -664,6 +665,27 @@ typedef struct _SubscriptionInfo
 	char	   *subpublications;
 } SubscriptionInfo;
 
+/*
@@ -746,5 +768,6 @@ extern void getPublicationNamespaces(Archive *fout);
 
 #endif							/* PG_DUMP_H */

 
index b10e1c4c0d..097dfa46bf 100644

@@ -3254,6 +3280,30 @@ my %tests = (
 		},
 	},
 
@@ -3693,6 +3743,21 @@ my %tests = (
 		like => {},
 	},

From 4fdce37a51b564c40d2d06d75828334ded4a3692 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 07/13] Enhancing psql for session variables

diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 0955142215..3c373f30ed 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -944,6 +944,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 327a69487b..ccfee98307 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5065,6 +5065,102 @@ error_return:
 	return false;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 92207d2e16..ee93218c26 100644

 /*
@@ -2129,6 +2140,9 @@ psql_completion(const char *text, int start, int end)
@@ -2630,7 +2644,7 @@ psql_completion(const char *text, int start, int end)
@@ -3068,7 +3082,7 @@ psql_completion(const char *text, int start, int end)
@@ -3375,6 +3389,17 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS */
@@ -3490,7 +3515,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
@@ -3617,6 +3642,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 */
@@ -3733,7 +3764,8 @@ psql_completion(const char *text, int start, int end)
@@ -3751,6 +3783,8 @@ psql_completion(const char *text, int start, int end)
 									 "USAGE",
 									 "SET",
 									 "ALTER SYSTEM",
+									 "READ",
+									 "WRITE",
 									 "ALL");
 	}
 
@@ -3792,7 +3826,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("GRANT|REVOKE", MatchAny) ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny))
 	{
-		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
+		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|READ|WRITE|ALL"))
 			COMPLETE_WITH("ON");
 		else if (TailMatches("GRANT", MatchAny))
 			COMPLETE_WITH("TO");
@@ -3815,7 +3849,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",
@@ -3837,7 +3871,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"))
@@ -3845,7 +3880,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");
@@ -3881,6 +3917,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
@@ -4123,7 +4161,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -4411,6 +4449,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
 
@@ -4577,6 +4623,8 @@ psql_completion(const char *text, int start, int end)

From 184f1d865fb358c6268ea127815f6695d0fd7d86 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 05/13] Support of LET command in PLpgSQL

index 38028f7840..b1ae49a5bb 100644

 /*

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

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
index 088768867d..3aeb7badf8 100644
 
 /*
 
+/*
+ * Let statement
+ */

From 0562f4571543cca9cf52610541e7b9c8acd21065 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 06/13] DISCARD VARIABLES command

---

 
index b1ae49a5bb..05c0a064fa 100644

index 6340ee7070..dd29ff7932 100644
@@ -3856,7 +3856,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;

From 59c71d741d7c1c1864c2d98ca3ac8595f4cc7493 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 04/13] LET command

 24 files changed, 731 insertions(+), 11 deletions(-)

index c079758a8e..50ae95903d 100644
@@ -1668,6 +1669,85 @@ ResetSessionVariables(void)
 	sync_sessionvars_all_is_required = false;
 }
 
+/*
 
index a013031d22..aa9d90b10e 100644

 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
index 2554a1dc83..38028f7840 100644

 
@@ -12028,6 +12030,7 @@ ExplainableStmt:
 
 /*****************************************************************************
@@ -12058,6 +12061,7 @@ PreparableStmt:
 			| UpdateStmt
 			| DeleteStmt
 			| MergeStmt						/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -12645,6 +12649,49 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -17928,6 +17975,7 @@ unreserved_keyword:
@@ -18534,6 +18582,7 @@ bare_label_keyword:
 
 			/*
 
 			/*
index dbfca3b037..c07f62344a 100644
 
 			/* okay */
 			break;
@@ -1967,6 +1968,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:
@@ -3334,6 +3336,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 
 			/*
index 16a0fe59e2..41cb04f4b6 100644
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*
index c6e7670306..6340ee7070 100644
@@ -1988,6 +1988,21 @@ typedef struct MergeStmt
 
index e03727e19f..7108203d2b 100644
 
 	NodeTag		type;

 
index d07f5b0584..43fd9ca887 100644

 /* ----------------
-- 
2.36.1

From 35bc6188511cb747377f341032ce08d69b356303 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/13] typecheck - check of consistency of format of stored
 value.

Session variables holds a own evidence of fingerprints for any
used type. When some possible not compatible change is detected
(the version of extension is changed), the related stored type's
generation number is increased. The copy of genaration number is
stored in session variable data. Before any read of session variable,
the stored generation number is compared with current type generation
number. When it is different, the read is canceled.
---
 src/backend/commands/sessionvariable.c | 256 ++++++++++++++++++-------
 1 file changed, 189 insertions(+), 67 deletions(-)

index e8d096ff3f..c079758a8e 100644
 
 /*
@@ -154,14 +206,14 @@ static List *xact_recheck_varids = NIL;
 
@@ -176,13 +228,19 @@ typedef struct SVariableData

 

 
@@ -966,9 +1024,10 @@ sync_sessionvars_all()
  * Create the hash table for storing session variables
  */
 static void
-create_sessionvars_hashtable(void)
+create_sessionvars_hashtables(void)
 {
-	HASHCTL		ctl;
+	HASHCTL		vars_ctl;
+	HASHCTL		types_ctl;
 
 	/* set callbacks */
 	if (first_time)
@@ -988,14 +1047,24 @@ create_sessionvars_hashtable(void)

 

@@ -1023,15 +1092,19 @@ set_session_variable(SVariable svar, Datum value,
 						get_namespace_name(get_session_variable_namespace(svar->varid)),
 						get_session_variable_name(svar->varid))));

@@ -1049,7 +1122,10 @@ set_session_variable(SVariable svar, Datum value,
 
 	free_session_variable_value(svar);
@@ -1071,17 +1147,13 @@ init_session_variable(SVariable svar, Variable *var)
 	Assert(OidIsValid(var->oid));
 
 	svar->varid = var->oid;

@@ -1125,7 +1197,7 @@ prepare_variable_for_reading(Oid varid)
 	var.oid = InvalidOid;
 
 	if (!sessionvars)
 
 	/* Protect used session variable against drop until transaction end */
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
@@ -1141,54 +1213,97 @@ prepare_variable_for_reading(Oid varid)
 					 varid);

 
-	if (!found)

-	if (svar->has_defexpr)
+	/*

 

 	return svar;
 }
@@ -1212,7 +1327,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid)
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
 
 	if (!sessionvars)
 
 	/* Ensure so all entries in sessionvars hash table are valid */
 	sync_sessionvars_all();
@@ -1267,10 +1382,12 @@ CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 	Assert(svar != NULL && svar->is_valid);
 
 	*isNull = svar->isnull;
-	*typid = svar->typid;
 
 	if (!svar->isnull)
 
 	return (Datum) 0;
 }
@@ -1288,7 +1405,7 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	svar = prepare_variable_for_reading(varid);
 	Assert(svar != NULL && svar->is_valid);
 
@@ -1296,7 +1413,9 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	*isNull = svar->isnull;
 
 	if (!svar->isnull)
 
 	return (Datum) 0;
 }
@@ -1314,7 +1433,7 @@ GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	svar = prepare_variable_for_reading(varid);
 	Assert(svar != NULL && svar->is_valid);
 
@@ -1518,6 +1637,9 @@ ResetSessionVariables(void)

From e881108e702313091233687c2bd7fde0adc33709 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/13] session variables

The most difficult part is cleaning (reset) of session variable. The
content of session variable should be cleaned when related session
variable is removed from system catalog. But queue of sinval messages
can be truncated, or current transaction state can disallow an access
to system catalog, so cleaning can be postponed.
---
 src/backend/access/transam/xact.c      |    2 +-
 src/backend/catalog/dependency.c       |    5 +
 src/backend/commands/sessionvariable.c | 1557 +++++++++++++++++++++++-
 src/backend/executor/execExpr.c        |   75 ++
 src/backend/executor/execExprInterp.c  |   11 +
 src/backend/executor/execMain.c        |   56 +
 src/backend/executor/execParallel.c    |  148 ++-
 src/backend/jit/llvm/llvmjit_expr.c    |    6 +
 src/backend/optimizer/plan/planner.c   |    8 +
 src/backend/optimizer/plan/setrefs.c   |  118 +-
 src/backend/optimizer/util/clauses.c   |   75 +-
 src/backend/parser/analyze.c           |   13 +-
 src/backend/parser/parse_expr.c        |  186 ++-
 src/backend/tcop/pquery.c              |    3 +
 src/backend/utils/adt/ruleutils.c      |   46 +
 src/backend/utils/cache/plancache.c    |   19 +-
 src/backend/utils/fmgr/fmgr.c          |   10 +-
 src/backend/utils/misc/guc.c           |   12 +
 src/include/executor/execExpr.h        |   10 +
 src/include/executor/execdesc.h        |    4 +
 src/include/nodes/execnodes.h          |   19 +
 src/include/nodes/parsenodes.h         |    2 +
 src/include/nodes/pathnodes.h          |    4 +
 src/include/nodes/plannodes.h          |    2 +
 src/include/nodes/primnodes.h          |   12 +-
 src/include/optimizer/planmain.h       |    2 +
 src/include/parser/parse_expr.h        |    1 +
 src/include/parser/parse_node.h        |    1 +
 28 files changed, 2332 insertions(+), 75 deletions(-)

 
 	/* close large objects before lower-level cleanup */
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 22988b0bd6..120640bc26 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1895,6 +1895,11 @@ find_expr_references_walker(Node *node,
 	{
 		Param	   *param = (Param *) node;
 
index 7c57a61179..e8d096ff3f 100644
@@ -28,38 +28,1304 @@
 
 /*
+static List *xact_on_commit_drop_actions = NIL;
+static List *xact_on_commit_reset_actions = NIL;
+ * the ON COMMIT DROP and ON TRANSACTION END RESET variables
+ * are purged from memory every time.
+static List *xact_reset_varids = NIL;
+ * Holds list variable's id that that should be
+ * checked against system catalog if still live.
+static List *xact_recheck_varids = NIL;
+	bool		eox_reset;		/* true, when lifecycle is limitted by transaction */
+ * Returns human readable name of SVariableXActAction value.
+static const char *
+SvariableXActActionName(SVariableXActAction action)
+	switch (action)
+		case SVAR_ON_COMMIT_DROP:
+			return "ON COMMIT DROP";
+		case SVAR_ON_COMMIT_RESET:
+			return "ON COMMIT RESET";
+			elog(ERROR, "unknown SVariableXActAction action %d",
+						action);
+	elog(DEBUG1, "session variable (oid:%u) is removing from memory",
+				  svar->varid);
+				 * We don't need to execute recheck for variables,
+				 * that will be surelly purged from memory at eox
+				if (!svar->eox_reset)
+					MemoryContext oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+					elog(DEBUG1, "session variable (oid:%u) should be rechecked (forced by sinval)",
+								 svar->varid);
+					xact_recheck_varids = lappend_oid(xact_recheck_varids, svar->varid);
+		!xact_recheck_varids)
+		if (xact_recheck_varids)
+			list_free(xact_recheck_varids);
+			xact_recheck_varids = NIL;
+	else if (xact_recheck_varids)
+		foreach(l, xact_recheck_varids)
+			Oid		varid = lfirst_oid(l);
+		list_free(xact_recheck_varids);
+		xact_recheck_varids = NIL;
-typedef enum SVariableXActAction
-	SVAR_ON_COMMIT_DROP,		/* used for ON COMMIT DROP */
-} SVariableXActAction;
+	HASHCTL		ctl;
 
-typedef struct SVariableXActActionItem
+	/* set callbacks */
+	if (first_time)
+	{
-	Oid			varid;			/* varid of session variable */
-	SVariableXActAction action;	/* reset or drop */
 
 	/*
-	 * creating_subid is the ID of the creating subxact. If the action was
-	 * unregistered during the current transaction, deleting_subid is the ID of
-	 * the deleting subxact, otherwise InvalidSubTransactionId.
+	 * Don't allow updating of immutable session variable that has assigned
+	 * not null value or has default expression (and then the value should be
+	 * result of default expression always). Don't do this check, when variable
+	 * is being initialized.
 	 */
-	SubTransactionId creating_subid;
-	SubTransactionId deleting_subid;
-}  SVariableXActActionItem;
+	if (!init_mode &&
+		(svar->is_immutable && (svar->is_valid || svar->has_defexpr)))
+		ereport(ERROR,
+				(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
+				 errmsg("session variable \"%s.%s\" is declared IMMUTABLE",
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
 
-static List *xact_drop_actions = NIL;
 
-static void register_session_variable_xact_action(Oid varid, SVariableXActAction action);
-static void unregister_session_variable_xact_action(Oid varid, SVariableXActAction action);
+	svar->eox_reset = var->eoxaction == VARIABLE_EOX_RESET ||
+					  var->eoxaction == VARIABLE_EOX_DROP;
+	if (svar->eox_reset)
+		MemoryContext oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+		xact_reset_varids = lappend_oid(xact_reset_varids, var->oid);
+		elog(DEBUG1, "session variable (oid:%u) has new entry in memory (emitted by READ)",
+					 varid);
+		elog(DEBUG1, "session variable (oid:%u) has new entry in memory (emitted by WRITE)",
+					 varid);
 
 /*
  * Routines used for manipulation with session variables from
@@ -146,6 +1412,22 @@ DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 							  stmt->if_not_exists,
 							  stmt->is_immutable);
 
+	elog(DEBUG1, "record for session variable \"%s\" (oid:%d) was created in pg_variable",
+				stmt->variable->relname, variable.objectId);
+
+	/*
+	 * For temporary variables, we need to create a new end of xact action to
+	 * ensure deletion from catalog.
+	 */
+	if (stmt->eoxaction == VARIABLE_EOX_DROP)
+	{
+		Assert(isTempNamespace(namespaceid));
+
+		register_session_variable_xact_action(variable.objectId,
+											  SVAR_ON_COMMIT_DROP);
+	}
+
+
 	/*
 	 * We must bump the command counter to make the newly-created variable
 	 * tuple visible for any other operations.
@@ -156,17 +1438,26 @@ DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 }
 
 /*
- * Drop variable by OID. This routine doesn't try to remove
- * the value of session variable immediately. It will be
- * removed on transaction end in sync_sessionvars_xact_callback
- * routine. This routine manipulate just with system catalog.
+ * Drop variable by OID. This routine can be called by directly
+ * command DROP VARIABLE or indirectly for TEMP ON COMMIT DROP
+ * variables.
+ *
+ * In first case we want to postpone memory purging to commit or
+ * abort time. In second case, the menory will be purged by
+ * processing xact_reset_varids list. In second case, the calling
+ * of unregistration of SVAR_ON_COMMIT_DROP action is useless, but
+ * we are not able to detect which case is executed.
  */
 void
 RemoveSessionVariable(Oid varid)
 {
+	SVariable svar;
+	bool		found;
 	Relation	rel;
 	HeapTuple	tup;
 
+	elog(DEBUG1, "session variable (oid:%u) is deleted from pg_variable", varid);
+
 	rel = table_open(VariableRelationId, RowExclusiveLock);
 
 	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
@@ -181,17 +1472,84 @@ RemoveSessionVariable(Oid varid)
 	table_close(rel, RowExclusiveLock);
 
 	/*
-	 * We removed entry from catalog already, we should not do it
-	 * again at end of xact time.
+	 * We removed entry from catalog already, we must not do it
+	 * again at end of xact time
 	 */
 	unregister_session_variable_xact_action(varid, SVAR_ON_COMMIT_DROP);
+
+	if (sessionvars)
+	{
+		svar = (SVariable) hash_search(sessionvars, &varid,
+											HASH_FIND, &found);
+
+		/*
+		 * For variables that are not purged by default we need to
+		 * register SVAR_ON_COMMIT_RESET action. This action can
+		 * be reverted by ABORT of DROP VARIABLE command.
+		 */
+		if (found && !svar->eox_reset)
+		{
+			/*
+			 * And we want to enforce variable clearning when this transaction or
+			 * subtransaction will be committed (we don't need to wait for
+			 * sinval message). The cleaning action for one session variable
+			 * can be repeated in the action list without causing any problem,
+			 * so we don't need to ensure uniqueness. We need a different action
+			 * from RESET, because RESET is executed on any transaction end,
+			 * but we want to execute cleaning only when the current transaction
+			 * will be committed.
+			 */
+			register_session_variable_xact_action(varid, SVAR_ON_COMMIT_RESET);
+		}
+	}
+}
+
+/*
+ * Fast drop of the complete content of all session variables hash table.
+ * This is code for DISCARD VARIABLES command. This command
+ * cannot be run inside transaction, so we don't need to handle
+ * end of transaction actions.
+ */
+void
+ResetSessionVariables(void)
+{
+	/* Destroy hash table and reset related memory context */
+	if (sessionvars)
+	{
+		hash_destroy(sessionvars);
+		sessionvars = NULL;
+	}
+
+	/* Release memory allocated by session variables */
+	if (SVariableMemoryContext != NULL)
+		MemoryContextReset(SVariableMemoryContext);
+
+	/*
+	 * There are not any session variables left, so simply trim
+	 * both xact action lists.
+	 */
+	list_free_deep(xact_on_commit_drop_actions);
+	xact_on_commit_drop_actions = NIL;
+
+	list_free_deep(xact_on_commit_reset_actions);
+	xact_on_commit_reset_actions = NIL;
+
+	/* We should clean xact_reset_varids */
+	list_free(xact_reset_varids);
+	xact_reset_varids = NIL;
+
+	/* we should clean xact_recheck_varids */
+	list_free(xact_recheck_varids);
+	xact_recheck_varids = NIL;
+
+	/* There are not variables to synchronize */
+	sync_sessionvars_all_is_required = false;
 }
 
 /*
  * Registration of actions to be executed on session variables at transaction
  * end time. We want to drop temporary session variables with clause ON COMMIT
- * DROP, or we want to reset values of session variables with clause ON
- * TRANSACTION END RESET or we want to clean (reset) local memory allocated by
+ * DROP, or we want to clean (reset) local memory allocated by
  * values of dropped session variables.
  */
 
@@ -205,6 +1563,9 @@ register_session_variable_xact_action(Oid varid,
 	SVariableXActActionItem *xact_ai;
 	MemoryContext oldcxt;
 
+	elog(DEBUG1, "SVariableXActAction \"%s\" is registered for session variable (oid:%u)",
+						SvariableXActActionName(action), varid);
+
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 
 	xact_ai = (SVariableXActActionItem *)
@@ -216,8 +1577,10 @@ register_session_variable_xact_action(Oid varid,
 	xact_ai->creating_subid = GetCurrentSubTransactionId();
 	xact_ai->deleting_subid = InvalidSubTransactionId;
 
-	Assert(action == SVAR_ON_COMMIT_DROP);
-	xact_drop_actions = lcons(xact_ai, xact_drop_actions);
+	if (action == SVAR_ON_COMMIT_DROP)
+		xact_on_commit_drop_actions = lcons(xact_ai, xact_on_commit_drop_actions);
+	else
+		xact_on_commit_reset_actions = lcons(xact_ai, xact_on_commit_reset_actions);
 
 	MemoryContextSwitchTo(oldcxt);
 }
@@ -234,9 +1597,10 @@ unregister_session_variable_xact_action(Oid varid,
 {
 	ListCell   *l;
 
-	Assert(action == SVAR_ON_COMMIT_DROP);
+	elog(DEBUG1, "SVariableXActAction \"%s\" is unregistered for session variable (oid:%u)",
+						SvariableXActActionName(action), varid);
 
-	foreach(l, xact_drop_actions)
+	foreach(l, xact_on_commit_drop_actions)
 	{
 		SVariableXActActionItem *xact_ai =
 					(SVariableXActActionItem *) lfirst(l);
@@ -255,27 +1619,41 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 {
 	ListCell   *l;
 
-	foreach(l, xact_drop_actions)
+	/*
+	 * Clean memory for all eox_reset variables. Do it
+	 * first, it reduces enhancing action lists about
+	 * RECHECK action.
+	 */
+	foreach(l, xact_reset_varids)
 	{
-		SVariableXActActionItem *xact_ai =
-							(SVariableXActActionItem *) lfirst(l);
+		remove_session_variable_by_id(lfirst_oid(l));
+	}
 
-		Assert(xact_ai->action == SVAR_ON_COMMIT_DROP);
+	/* We can clean xact_reset_varids */
+	list_free(xact_reset_varids);
+	xact_reset_varids = NIL;
 
-		/* Iterate only over entries that are still pending */
-		if (xact_ai->deleting_subid == InvalidSubTransactionId)
+	if (isCommit && xact_on_commit_drop_actions)
+	{
+		foreach(l, xact_on_commit_drop_actions)
 		{
+			SVariableXActActionItem *xact_ai =
+								(SVariableXActActionItem *) lfirst(l);
 
-			/*
-			 * ON COMMIT DROP is allowed only for temp session
-			 * variables. So we should explicitly delete only when
-			 * current transaction was committed. When it's rollback,
-			 * then session variable is removed automatically.
-			 */
-			if (isCommit)
+			Assert(xact_ai->action == SVAR_ON_COMMIT_DROP);
+
+			/* Iterate only over entries that are still pending */
+			if (xact_ai->deleting_subid == InvalidSubTransactionId)
 			{
 				ObjectAddress object;
 
+				/*
+				 * ON COMMIT DROP is allowed only for temp session
+				 * variables. So we should explicitly delete only when
+				 * current transaction was committed. When it's rollback,
+				 * then session variable is removed automatically.
+				 */
+
 				object.classId = VariableRelationId;
 				object.objectId = xact_ai->varid;
 				object.objectSubId = 0;
@@ -285,6 +1663,9 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 				 * directly initiated by the user, we pass the
 				 * PERFORM_DELETION_INTERNAL flag.
 				 */
+				elog(DEBUG1, "session variable (oid:%u) will be deleted (forced by SVAR_ON_COMMIT_DROP action)",
+							  xact_ai->varid);
+
 				performDeletion(&object, DROP_CASCADE,
 								PERFORM_DELETION_INTERNAL |
 								PERFORM_DELETION_QUIETLY);
@@ -296,8 +1677,63 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 	 * Any drop action left is an entry that was unregistered and not
 	 * rollbacked, so we can simply remove them.
 	 */
-	list_free_deep(xact_drop_actions);
-	xact_drop_actions = NIL;
+	list_free_deep(xact_on_commit_drop_actions);
+	xact_on_commit_drop_actions = NIL;
+
+	if (isCommit && xact_recheck_varids)
+	{
+		Assert(sessionvars);
+
+		foreach(l, xact_recheck_varids)
+		{
+			SVariable	svar;
+			bool		found;
+			Oid		varid = lfirst_oid(l);
+
+			svar = (SVariable) hash_search(sessionvars, &varid,
+										   HASH_FIND, &found);
+
+			if (found)
+			{
+				HeapTuple	tp;
+
+				tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+				if (HeapTupleIsValid(tp))
+					ReleaseSysCache(tp);
+				else
+					remove_session_variable(svar);
+			}
+		}
+
+		list_free(xact_recheck_varids);
+		xact_recheck_varids = NIL;
+	}
+
+	if (isCommit && xact_on_commit_reset_actions)
+	{
+		foreach(l, xact_on_commit_reset_actions)
+		{
+			SVariableXActActionItem *xact_ai =
+								(SVariableXActActionItem *) lfirst(l);
+
+			Assert(xact_ai->action == SVAR_ON_COMMIT_RESET);
+
+			/*
+			 * When we process DROP VARIABLE statement, we create
+			 * SVAR_ON_COMMIT_RESET xact action. We want to process
+			 * this action only when related transaction is commited
+			 * (when DROP VARIABLE statement sucessfully processed).
+			 * We want to preserve variable content, when the transaction
+			 * with DROP VARAIBLE statement was reverted.
+			 */
+			if (xact_ai->deleting_subid == InvalidSubTransactionId)
+				remove_session_variable_by_id(xact_ai->varid);
+		}
+	}
+
+	list_free_deep(xact_on_commit_reset_actions);
+	xact_on_commit_reset_actions = NIL;
 }
 
 /*
@@ -313,7 +1749,7 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 {
 	ListCell   *cur_item;
 
-	foreach(cur_item, xact_drop_actions)
+	foreach(cur_item, xact_on_commit_drop_actions)
 	{
 		SVariableXActActionItem *xact_ai =
 								  (SVariableXActActionItem *) lfirst(cur_item);
@@ -321,7 +1757,36 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 		if (!isCommit && xact_ai->creating_subid == mySubid)
 		{
 			/* cur_item must be removed */
-			xact_drop_actions = foreach_delete_current(xact_drop_actions, cur_item);
+			xact_on_commit_drop_actions = foreach_delete_current(xact_on_commit_drop_actions, cur_item);
+			pfree(xact_ai);
+		}
+		else
+		{
+	foreach(cur_item, xact_on_commit_reset_actions)
+			xact_on_commit_reset_actions =
+							foreach_delete_current(xact_on_commit_reset_actions, cur_item);
 			pfree(xact_ai);
 
 						/*
index 723770fda0..d237003f48 100644

+	/*

 

 

 
 	/*

+	/*
index 1cb0abdbc1..c7edc4e75a 100644

+/*
@@ -1831,8 +1877,9 @@ copyVar(Var *var)
@@ -1922,15 +1969,39 @@ fix_expr_common(PlannerInfo *root, Node *node)
 
 /*
  * fix_param_node
@@ -1949,6 +2020,41 @@ fix_param_node(PlannerInfo *root, Param *p)
 
@@ -2010,7 +2116,10 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
@@ -2032,7 +2141,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
 
 	/*

 
 				/*

 
index 6688c2a865..a013031d22 100644

 

 

 

 

index 78ec6972b9..dbfca3b037 100644
 
 /* GUC parameters */

 /*
@@ -841,6 +849,124 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
@@ -875,6 +1001,64 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
 
index 513bf0f026..cd5aa10fc5 100644
 
@@ -12728,6 +12738,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 	/*
 
 /*
 
 	return false;
 }
index af4a1c3068..8ac883aa44 100644

 
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 01b1727fc0..e8509d4c73 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -590,6 +590,18 @@ typedef struct AsyncRequest
 								 * tuples) */
 } AsyncRequest;
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+	Oid			varid;
+	Oid			typid;
+	bool		isnull;
+	Datum		value;
+}			SessionVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -641,6 +653,13 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
 
+	/* Variables info: */
 
 	/* Other working state: */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a81f6e5491..c6e7670306 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -138,6 +138,7 @@ typedef struct Query
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
 
@@ -147,6 +148,7 @@ typedef struct Query

index e2081db4ed..7e81342e2b 100644

 /* macro for fetching the Plan associated with a SubPlan node */
@@ -444,6 +446,8 @@ struct PlannerInfo
 	/* true if planning a recursive WITH item */
 	bool		hasRecursion;
 
index dca2a21e7a..e03727e19f 100644

 
 
 typedef struct Param

 
 /* GUC parameters */

index 4d65a9e9e8..d07f5b0584 100644

-- 
2.36.1

From e42eaf2db599c25fd9acdf501cd493f05aa08ae3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/13] Catalogue support for session variables

 src/backend/catalog/pg_variable.c       | 233 +++++++++++++++
 src/backend/commands/sessionvariable.c  | 336 +++++++++++++++++++++
 src/include/commands/session_variable.h |  44 +++
 37 files changed, 1934 insertions(+), 19 deletions(-)

 

 
index 17ff617fba..241c6a8d3c 100644
 
@@ -1246,6 +1299,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
 
+		case OBJECT_VARIABLE:
@@ -1477,6 +1536,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
@@ -1537,6 +1599,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
@@ -3369,6 +3434,129 @@ ExecGrant_Parameter(InternalGrant *istmt)
 
+static void
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3405,6 +3593,10 @@ string_to_privilege(const char *privname)
 		return ACL_ALTER_SYSTEM;
 	if (strcmp(privname, "rule") == 0)
 		return 0;				/* ignore old RULE privileges */
+	if (strcmp(privname, "read") == 0)
+		return ACL_READ;
+	if (strcmp(privname, "write") == 0)
+		return ACL_WRITE;
 	ereport(ERROR,
 			(errcode(ERRCODE_SYNTAX_ERROR),
 			 errmsg("unrecognized privilege type \"%s\"", privname)));
@@ -3444,6 +3636,10 @@ privilege_to_string(AclMode privilege)
 			return "SET";
 		case ACL_ALTER_SYSTEM:
 			return "ALTER SYSTEM";
+		case ACL_READ:
+			return "READ";
+		case ACL_WRITE:
+			return "WRITE";
 		default:
 			elog(ERROR, "unrecognized privilege: %d", (int) privilege);
 	}
@@ -3570,6 +3766,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;
@@ -3681,6 +3880,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;
@@ -3829,6 +4031,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return pg_type_aclmask(table_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return pg_variable_aclmask(table_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized objtype: %d",
 				 (int) objtype);
@@ -4812,6 +5016,66 @@ pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how)
 	return result;
 }
 
+/*
@@ -5112,6 +5376,18 @@ pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
 		return ACLCHECK_NO_PRIV;
 }
 
+/*
@@ -5729,6 +6005,33 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid)
 
+/*
@@ -5857,6 +6160,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 
+		case OBJECT_VARIABLE:
index cf9ceddff1..22988b0bd6 100644

+		case OCLASS_VARIABLE:
+			RemoveSessionVariable(object->objectId);
+			break;
+
 			/*
 			 * These global object types are not supported here.
 			 */
@@ -2890,6 +2898,9 @@ getObjectClass(const ObjectAddress *object)

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
 
index 6080ff8f5f..f130d0230c 100644
 
 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index 0000000000..7d217d8714
@@ -0,0 +1,233 @@
 
index 26157eb4e3..b55b3b0528 100644
@@ -480,6 +480,10 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
index eef3e5d56e..b947fb7b55 100644

 			/*
@@ -2048,6 +2050,8 @@ stringify_grant_objtype(ObjectType objtype)
 			return "TABLESPACE";
 		case OBJECT_TYPE:
 			return "TYPE";
+		case OBJECT_VARIABLE:
+			return "VARIABLE";
@@ -2131,6 +2135,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 
 			/*
index 0000000000..7c57a61179
@@ -0,0 +1,336 @@
index 7fbee0c1f7..cdf1199f52 100644
@@ -12650,6 +12650,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 				/*
index d649a1b8d1..2554a1dc83 100644

 

@@ -1111,6 +1115,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -5104,6 +5109,69 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6881,6 +6949,7 @@ object_type_any_name:
 
 /*
@@ -7757,6 +7826,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7802,6 +7879,14 @@ privilege_target:

@@ -7969,6 +8054,7 @@ defacl_privilege_target:

@@ -9796,6 +9882,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10157,6 +10262,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10436,6 +10560,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17964,6 +18096,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18610,6 +18744,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)
index 9f567f4bf4..78ec6972b9 100644
 
@@ -1788,6 +1791,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;
@@ -3120,6 +3124,7 @@ ParseExprKindName(ParseExprKind exprKind)

index 6fa58dd8eb..33248bcf8e 100644
 
-
 /*
 
 	return isclustered;
 }
+

 	/*

 

 
 
index 0000000000..2d2c436730
@@ -0,0 +1,44 @@
index 98fe1abaa2..a81f6e5491 100644
@@ -2194,6 +2196,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -3320,6 +3323,25 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+
index 962ebf65de..4d65a9e9e8 100644

 
 /*

---

From: Erik Rijkers <[email-masked]>
Date: Thu, 21 Jul 2022 09:09:47 +0200

Hi Pavel,

I don't know exactly what failed but the docs (html/pdf) don't build:

cd ~/pg_stuff/pg_sandbox/pgsql.schema_variables/doc/src/sgml

$ make html
/usr/bin/xmllint --path . --noout --valid postgres.sgml
postgres.sgml:374: element link: validity error : IDREF attribute 
linkend references an unknown ID "catalog-pg-variable"
make: *** [Makefile:135: html-stamp] Error 4

Erik Rijkers

---

From: Julien Rouhaud <[email-masked]>
Date: Thu, 21 Jul 2022 15:34:05 +0800

Hi,

> > - redesign of work with list of variables for reset, recheck, on commit

Thanks for working on those!  I will keep reviewing the patchset.

> postgres.sgml:374: element link: validity error : IDREF attribute linkend
> references an unknown ID "catalog-pg-variable"

Apparently most of the changes in catalogs.sgml didn't survive the last rebase.

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 22 Jul 2022 10:58:25 +0200

> > +  <title><structname>pg_variable</structname></title>

should be fixed now

Thank you for  check

Regards

Pavel
From 2507acfc1b24c0fff6b4c85d74db708ebd4b4a54 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 13/13] documentation

 18 files changed, 991 insertions(+), 9 deletions(-)

 
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index a186e35f00..de424fc56c 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>
@@ -9594,4 +9599,157 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </table>
  </sect1>

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
index 47bd7dbda0..ed34d80376 100644
 
+     <varlistentry>
 
2.37.1

From 2bec58dca66c2893a8d3a82b84b3d718cfbb271e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 11:40:29 +0200
Subject: [PATCH 12/13] This patch changes error message "column doesn't exist"
 to message "column or variable doesn't exist"

index 8d832efc62..5018269a07 100644
@@ -3638,8 +3638,8 @@ errorMissingColumn(ParseState *pstate,
@@ -3658,8 +3658,8 @@ errorMissingColumn(ParseState *pstate,

 
 select a from atacc1;

 

 
 
 -- should fail
 LET v1.x = 10;
 
2.37.1

From 84a3a853bfc347b6c17b98b28244efe31ba26d78 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 09/13] Possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 
 
2.37.1

From e3adbb83cdd5fd125d83fa00f208234b0214ae0a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 10/13] typedefs

---

2.37.1

From 019c850418be8f8cbfa7ea24c177a756cea83a17 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 11/13] Regress tests for session variables

 
 
 # ----------
2.37.1

From 1f5accb476dec8a7da3a46485096555ceb590cf6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 08/13] Enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*
 
2.37.1

From 7a20851b879ac027a3c49a2d6e4494878514d197 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 07/13] DISCARD VARIABLES command

---

 
index 8ffbce0bdb..b22d174892 100644
@@ -2116,7 +2116,12 @@ DiscardStmt:

2.37.1

From c2ab0a5f4fa2b54b5914cc02400acfcd020f3048 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 06/13] Support of LET command in PLpgSQL

index 69bb1abf29..8ffbce0bdb 100644
@@ -894,6 +894,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -1014,6 +1015,13 @@ parse_toplevel:
 
 /*

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

+static void

+static void

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

From 92e3a9b6359b1a9157ca12687a3ff7ee754dd231 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 19 Jul 2022 09:45:34 +0200
Subject: [PATCH 04/13] debug func

---
 src/backend/commands/sessionvariable.c | 49 ++++++++++++++++++++++++++
 src/include/catalog/pg_proc.dat        |  7 ++++
 2 files changed, 56 insertions(+)

index 3eed6f33dc..df1916a164 100644
@@ -26,6 +26,8 @@
@@ -1914,3 +1916,50 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 
2.37.1

From 018c66d79b436bce72aa53fb963754c2f070d247 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 05/13] LET command

 src/backend/commands/sessionvariable.c   |  79 +++++++
 25 files changed, 733 insertions(+), 11 deletions(-)

index df1916a164..de256784b6 100644
@@ -1668,6 +1668,85 @@ ResetSessionVariables(void)
 
+/*

 
 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
index a1a2695750..69bb1abf29 100644
@@ -458,6 +458,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
@@ -816,7 +817,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY KEYS KEEP

 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
@@ -1154,6 +1155,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -12035,6 +12037,7 @@ ExplainableStmt:
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -12065,6 +12068,7 @@ PreparableStmt:
 			| UpdateStmt
 			| DeleteStmt
 			| MergeStmt						/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -12652,6 +12656,49 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -17935,6 +17982,7 @@ unreserved_keyword:
@@ -18541,6 +18589,7 @@ bare_label_keyword:
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 83b8afcd6c..87bc93f60d 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -38,4 +38,7 @@ extern void AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit);
 extern void AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySubid,
 											SubTransactionId parentSubid);
 
 #endif

 	NodeTag		type;

 

 /* ----------------
-- 
2.37.1

From a12615c3fa7aa165780760381ef557b46358cc4c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/13] typecheck - check of consistency of format of stored
 value.

Session variables holds a own evidence of fingerprints for any
used type. When some possible not compatible change is detected
(the version of extension is changed), the related stored type's
generation number is increased. The copy of genaration number is
stored in session variable data. Before any read of session variable,
the stored generation number is compared with current type generation
number. When it is different, the read is canceled.
---
 src/backend/commands/sessionvariable.c | 795 ++++++++++++++++++++++---
 1 file changed, 728 insertions(+), 67 deletions(-)

index fc3fba6103..3eed6f33dc 100644
 
 /*
@@ -153,14 +205,14 @@ static List *xact_recheck_varids = NIL;
 
@@ -175,13 +227,19 @@ typedef struct SVariableData

 

 
@@ -203,6 +261,545 @@ SvariableXActActionName(SVariableXActAction action)
 	}
 }
 
+/*
@@ -426,9 +1023,10 @@ sync_sessionvars_all()
 
 	/* set callbacks */
 	if (first_time)
@@ -448,14 +1046,24 @@ create_sessionvars_hashtable(void)

 

@@ -483,15 +1091,19 @@ set_session_variable(SVariable svar, Datum value,
 						get_namespace_name(get_session_variable_namespace(svar->varid)),
 						get_session_variable_name(svar->varid))));

@@ -509,7 +1121,10 @@ set_session_variable(SVariable svar, Datum value,
 
 	free_session_variable_value(svar);
@@ -531,17 +1146,13 @@ init_session_variable(SVariable svar, Variable *var)
 	Assert(OidIsValid(var->oid));
 
 	svar->varid = var->oid;

@@ -585,7 +1196,7 @@ prepare_variable_for_reading(Oid varid)
 	var.oid = InvalidOid;
 
 	if (!sessionvars)
 
 	/* Protect used session variable against drop until transaction end */
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
@@ -601,54 +1212,97 @@ prepare_variable_for_reading(Oid varid)
 					 varid);

 
-	if (!found)

-	if (svar->has_defexpr)
+	/*

 

 	return svar;
 }
@@ -672,7 +1326,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid)
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
 
 	if (!sessionvars)
 
 	/* Ensure so all entries in sessionvars hash table are valid */
 	sync_sessionvars_all();
@@ -727,10 +1381,12 @@ CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 	Assert(svar != NULL && svar->is_valid);
 
 	*isNull = svar->isnull;
-	*typid = svar->typid;
 
 	if (!svar->isnull)
 
 	return (Datum) 0;
 }
@@ -748,7 +1404,7 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	svar = prepare_variable_for_reading(varid);
 	Assert(svar != NULL && svar->is_valid);
 
@@ -756,7 +1412,9 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	*isNull = svar->isnull;
 
 	if (!svar->isnull)
 
 	return (Datum) 0;
 }
@@ -774,7 +1432,7 @@ GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	svar = prepare_variable_for_reading(varid);
 	Assert(svar != NULL && svar->is_valid);
 
@@ -977,6 +1635,9 @@ ResetSessionVariables(void)
 
2.37.1

From 894bfde39b33b03b1d951b18469e1eb3c27b1f7b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/13] session variables

 src/backend/commands/sessionvariable.c | 963 ++++++++++++++++++++++++-
 src/backend/optimizer/plan/setrefs.c   | 118 ++-
 28 files changed, 1754 insertions(+), 59 deletions(-)

 
 
index 3094bab82a..fc3fba6103 100644
@@ -28,17 +28,97 @@
 
 /*
 
 typedef struct SVariableXActActionItem
@@ -54,12 +134,659 @@ typedef struct SVariableXActActionItem
 	SubTransactionId deleting_subid;
 }  SVariableXActActionItem;

 
+/*
@@ -170,14 +897,21 @@ DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 }
 
 /*
 
@@ -197,17 +931,84 @@ RemoveSessionVariable(Oid varid)
 	table_close(rel, RowExclusiveLock);
 
 	/*
 
 /*
 
@@ -221,6 +1022,9 @@ register_session_variable_xact_action(Oid varid,
 	SVariableXActActionItem *xact_ai;
 	MemoryContext oldcxt;

 	xact_ai = (SVariableXActActionItem *)
@@ -231,8 +1035,10 @@ register_session_variable_xact_action(Oid varid,
 	xact_ai->creating_subid = GetCurrentSubTransactionId();
 	xact_ai->deleting_subid = InvalidSubTransactionId;

 	MemoryContextSwitchTo(oldcxt);
 }
@@ -251,7 +1057,10 @@ unregister_session_variable_xact_action(Oid varid,
 
 	Assert(action == SVAR_ON_COMMIT_DROP);
 
@@ -270,25 +1079,39 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 {
 	ListCell   *l;

 
-			/*
 
+				/*
@@ -312,8 +1135,61 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 
 /*
@@ -329,7 +1205,33 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 {
 	ListCell   *cur_item;
 
@@ -337,7 +1239,8 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 
 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*

+/*
 
index 32fffa472c..aa6dc4d66d 100644
@@ -502,6 +503,7 @@ static char *generate_function_name(Oid funcid, int nargs,
@@ -8086,6 +8088,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
@@ -12760,6 +12770,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

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

 

 

 

 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.37.1

From 70b5b4c6525b4664427010f571f895ccee0e6089 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/13] Catalogue support for session variables

 src/backend/commands/sessionvariable.c  | 352 ++++++++++++++++++++++
 src/include/commands/session_variable.h |  41 +++
 37 files changed, 1947 insertions(+), 19 deletions(-)

 

 

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*
index 0000000000..3094bab82a
@@ -0,0 +1,352 @@
+		if (xact_ai->varid == varid)
 
 				/*
index 0a874a04aa..a1a2695750 100644
@@ -479,6 +480,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
@@ -647,6 +649,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 %type <node>		json_format_clause_opt
@@ -853,8 +857,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UESCAPE UNBOUNDED UNCONDITIONAL UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING

 
@@ -1112,6 +1116,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -5111,6 +5116,69 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6888,6 +6956,7 @@ object_type_any_name:
 
 /*
@@ -7764,6 +7833,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7809,6 +7886,14 @@ privilege_target:

@@ -7976,6 +8061,7 @@ defacl_privilege_target:

@@ -9803,6 +9889,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10164,6 +10269,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10443,6 +10567,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17971,6 +18103,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18617,6 +18751,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)

 
 
-
 /*
 
 	return isclustered;
 }
+

 	/*

 

 
 
index 0000000000..83b8afcd6c
@@ -0,0 +1,41 @@

+

 
 /*

 

 
2.37.1

---

From: Julien Rouhaud <[email-masked]>
Date: Sun, 24 Jul 2022 19:12:52 +0800

Hi,

> > > +  <title><structname>pg_variable</structname></title>

Thanks!  I confirm that the documentation compiles now.

As mentioned off-list, I still think that the main comment in sessionvariable.c
needs to be adapted to the new approach.  At the very least it still refers to
the previous 2 lists, but as far as I can see there are now 4 lists:

+ /* Both lists hold fields of SVariableXActActionItem type */
+ static List *xact_on_commit_drop_actions = NIL;
+ static List *xact_on_commit_reset_actions = NIL;
+
+ /*
+  * the ON COMMIT DROP and ON TRANSACTION END RESET variables
+  * are purged from memory every time.
+  */
+ static List *xact_reset_varids = NIL;
+
+ /*
+  * Holds list variable's id that that should be
+  * checked against system catalog if still live.
+  */
+ static List *xact_recheck_varids = NIL;

Apart from that, I'm not sure how much of the previous behavior changed.

It would be easier to review the new patchset having some up to date general
description of the approach.  If that's overall the same, just implemented
slightly differently I will just go ahead and dig into the patchset (although
the comments will still have to be changed eventually).

Also, one of the things that changes since the last version is:

@@ -1980,15 +1975,13 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
     */
    foreach(cur_item, xact_on_commit_reset_actions)
    {
        SVariableXActActionItem *xact_ai =
                                  (SVariableXActActionItem *) lfirst(cur_item);

-       if (!isCommit &&
-           xact_ai->creating_subid == mySubid &&
-           xact_ai->action == SVAR_ON_COMMIT_DROP)
+       if (!isCommit && xact_ai->creating_subid == mySubid)

We previously discussed this off-line, but for some quick context the test was
buggy as it wasn't possible to have an SVAR_ON_COMMIT_DROP action in the
xact_on_commit_reset_actions list.  However I don't see any change in the
regression tests since the last version and the tests are all green in both
versions.

It means that was fixed but there's no test covering it.  The local memory
management is probably the hardest part of this patchset, so I'm a bit worried
if there's nothing that can catch a bug leading to leaked values or entries in
some processing list.  Do you think it's possible to add some test that would
have caught the previous bug?

---

From: Erik Rijkers <[email-masked]>
Date: Sun, 24 Jul 2022 15:39:32 +0200

On 7/22/22 10:58, Pavel Stehule wrote:
> 
> čt 21. 7. 2022 v 9:34 odesílatel Julien Rouhaud <[email-masked] 
> <mailto:[email-masked]>> napsal:
>  
 > [v20220722] patches

Hi Pavel,

Thanks, docs now build.

Attached a few small text-changes.

Also, the pg_restore-doc still has the old name  'schema_variable' 
instead of session_variable:

-A schema_variable
--variable=schema_variable

Surely those should be changed as well.

Erik Rijkers

--- ./doc/src/sgml/ref/alter_variable.sgml.orig	2022-07-22 12:18:47.905664314 +0200
+++ ./doc/src/sgml/ref/alter_variable.sgml	2022-07-22 12:34:52.928275143 +0200
@@ -28,7 +28,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-ALTER VARIABLE <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable class="parameter">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
+ALTER VARIABLE <replaceable class="parameter">name</replaceable> 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>
--- ./doc/src/sgml/ref/create_variable.sgml.orig	2022-07-22 12:18:13.877300499 +0200
+++ ./doc/src/sgml/ref/create_variable.sgml	2022-07-24 14:52:50.587010931 +0200
@@ -58,10 +58,10 @@
   <note>
    <para>
     Inside a query or an expression, the session variable can be shadowed by
-    column or by routine's variable or routine argument. The collision of
-    identifiers can be solved by using qualified identifiers. Session variables
-    can use schema name, columns can use table aliases, routine's variables
-    can use block's labels, and routine's arguments can use routine name.
+    column or by routine's variable or routine argument. Such collisions of
+    identifiers can be resolved by using qualified identifiers. Session variables
+    can use schema name, columns can use table aliases, routine variables
+    can use block labels, and routine arguments can use the routine name.
    </para>
   </note>
  </refsect1>
@@ -74,21 +74,16 @@
     <term><literal>IMMUTABLE</literal></term>
     <listitem>
      <para>
-      The assigned value of the session variable can never be changed.
-      If the session variable doesn't have a default value, then a single
+      The assigned value of the session variable can not be changed.
+      Only if the session variable doesn't have a default value, a single
       initialization is allowed using the <command>LET</command> command. Once
-      done, no other change will be allowed until end of transcation
-      (when session variable was created with clause <literal>ON TRANSACTION
-      END RESET</literal> or until reset of all session variables by
-      <command>DISCARD VARIABLES</command> or until reset of all session
+      done, no further change is allowed until end of transaction
+      if the session variable was created with clause <literal>ON TRANSACTION
+      END RESET</literal>, or until reset of all session variables by
+      <command>DISCARD VARIABLES</command>, or until reset of all session
       objects by command <command>DISCARD ALL</command>.
      </para>
 
-     <para>
-      When <literal>IMMUTABLE</literal> session variable has assigned
-      default value by using <literal>DEFAULT expr</literal> clause, then
-      the value of session variable cannot be changed ever.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -152,7 +147,7 @@
      <para>
       The <literal>DEFAULT</literal> clause can be used to assign a default
       value to a session variable. This expression is evaluated when the session
-      variable is accessed for reading first time, and had not assigned any value.
+      variable is first accessed for reading and had not yet been assigned a value.
      </para>
     </listitem>
    </varlistentry>
--- ./doc/src/sgml/ref/let.sgml.orig	2022-07-22 12:04:25.096021723 +0200
+++ ./doc/src/sgml/ref/let.sgml	2022-07-24 15:05:39.038636565 +0200
@@ -58,7 +58,7 @@
     <term><literal>sql_expression</literal></term>
     <listitem>
      <para>
-      An SQL expression. The result is cast to the data type of the session
+      An SQL expression, in parentheses. The result must be of the same data type as the session
       variable.
      </para>
     </listitem>
@@ -69,7 +69,7 @@
     <listitem>
      <para>
       Reset the session variable to its default value, if that is defined.
-      If no explicit default value has been assigned, the session variable
+      If no explicit default value has been declared, the session variable
       is set to NULL.
      </para>
     </listitem>

---

From: Justin Pryzby <[email-masked]>
Date: Sun, 24 Jul 2022 14:09:59 -0500

Hi Erik,

> Attached a few small text-changes.

When you send patches like this, could you rename them to something other than
*.patch and *.diff ?

Otherwise, cfbot tries to apply *only* your patches to master, which fails due
to missing the original patches that your changes are on top of, and makes it
look like the author's patch needs to be rebased.
http://cfbot.cputube.org/pavel-stehule.html - Apply patches: FAILED

Alternately, (especially if your patch fixes a bug), you can resend the
author's patches, rebased, as [1.patch, ..., N.patch] plus your changes as
N+1.patch.  Then, cfbot tests your patches, and the author can easily review
and then integrate your changes.  (This is especially nice if the patches
currently need to be rebased, and you can make the cfbot pass at the same time
as sending fixes).

Cheers,

-- 
Justin

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 27 Jul 2022 21:59:18 +0200

Hi

> have caught the previous bug?

I am sending an updated patch. I had to modify sinval message handling.
Previous implementation was not robust and correct (there was some
possibility, so value stored in session's variable was lost after aborted
drop variable. There are new regress tests requested by Julien and some
others describing the mentioned issue. I rewrote the implementation's
description part in sessionvariable.c.

Erik's patches are merged. Thank you for them.

Regards

Pavel
From ac13093f39d256bcb1f33126e09a05d4c7ba1c38 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 12/12] documentation

 doc/src/sgml/ref/alter_variable.sgml          | 179 +++++++++++++++
 doc/src/sgml/ref/create_variable.sgml         | 214 ++++++++++++++++++
 18 files changed, 986 insertions(+), 9 deletions(-)

 

 

 
 
   <para>
index 0000000000..d2036351e5
 
index 0000000000..77cbbbafca
@@ -0,0 +1,214 @@
 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
index 0000000000..210aca3b95
index 47bd7dbda0..39cb595647 100644
 
+     <varlistentry>
+      <term><option>-A <replaceable class="parameter">session_variable</replaceable></option></term>
+      <term><option>--variable=<replaceable class="parameter">session_variable</replaceable></option></term>
+      <listitem>
+       <para>

From eb4a5255a10c0aeb08eaaa70ceb5632a882822f9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 09/12] typedefs

---

index 35c9f1efce..400746a13a 100644
@@ -2916,6 +2924,7 @@ Variable
 VariableAssignHook
 VariableCache
 VariableCacheData
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.37.1

From ec4d7ec2302a9bd702708eb41ab5cff0b7c6394d Mon Sep 17 00:00:00 2001

 
index d63f4f1cba..ea02f1580b 100644

 select a from atacc1;

 

 
index 71a8063b3e..48db60809a 100644
 
 -- should fail
 LET v1.x = 10;

From a4f2ea1b82b1ebee604df48558f0114e3f10de1b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 08/12] Possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 1871b6737a7498339b1ef445f8fadd20c5c8e4e9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 10/12] Regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 src/test/regress/expected/misc_sanity.out     |    4 +-
 .../regress/expected/session_variables.out    | 1074 +++++++++++++++++
 src/test/regress/parallel_schedule            |    2 +-
 src/test/regress/sql/session_variables.sql    |  823 +++++++++++++
 4 files changed, 1901 insertions(+), 2 deletions(-)
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

index 0000000000..71a8063b3e
@@ -0,0 +1,1074 @@
+CREATE VARIABLE public.svar AS int;
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
+ schema | name | removed 
+--------+------+---------
+(0 rows)
+LET public.svar = 100;
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
+ schema | name | removed 
+--------+------+---------
+ public | svar | f
+-- value should be in memory
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
+ schema | name | removed 
+--------+------+---------
+ public | svar | t
+-- value should be in memory
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() WHERE schema = 'public' and name = 'svar';
+ schema | name | removed 
+--------+------+---------
+ public | svar | f
+-- value should be in memory
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() WHERE schema = 'public' and name = 'svar';
+ schema | name | removed 
+--------+------+---------
+ public | svar | t
+-- the memory should be clean;
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() WHERE schema = 'public' and name = 'svar';
+ schema | name | removed 
+--------+------+---------
+(0 rows)
+CREATE VARIABLE public.svar AS int;
+LET public.svar = 100;
+-- the memory should be clean;
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() WHERE schema = 'public' and name = 'svar';
+ schema | name | removed 
+--------+------+---------
+(0 rows)
+CREATE VARIABLE public.svar AS int;
+LET public.svar = 100;
+-- repeated aborted transaction
+BEGIN; DROP VARIABLE public.svar; ROLLBACK;
+BEGIN; DROP VARIABLE public.svar; ROLLBACK;
+BEGIN; DROP VARIABLE public.svar; ROLLBACK;
+-- the value should be still available
 
 # ----------
index 0000000000..bba2f44ec9
@@ -0,0 +1,823 @@
+CREATE VARIABLE public.svar AS int;
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
+LET public.svar = 100;
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
+-- value should be in memory
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
+-- value should be in memory
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() WHERE schema = 'public' and name = 'svar';
+-- value should be in memory
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() WHERE schema = 'public' and name = 'svar';
+
+COMMIT;
+
+-- the memory should be clean;
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() WHERE schema = 'public' and name = 'svar';
+
+BEGIN;
+
+CREATE VARIABLE public.svar AS int;
+
+LET public.svar = 100;
+
+ROLLBACK;
+
+-- the memory should be clean;
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() WHERE schema = 'public' and name = 'svar';
+
+CREATE VARIABLE public.svar AS int;
+
+LET public.svar = 100;
+
+-- repeated aborted transaction
+BEGIN; DROP VARIABLE public.svar; ROLLBACK;
+BEGIN; DROP VARIABLE public.svar; ROLLBACK;
+BEGIN; DROP VARIABLE public.svar; ROLLBACK;
+
+-- the value should be still available
+SELECT public.svar;
-- 
2.37.1

From 3e26df841e2634aae8aba2621ed56a8b7e5524ee Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 06/12] DISCARD VARIABLES command

---

 
index 68b607339c..0d8f4f2809 100644
@@ -2139,7 +2139,12 @@ DiscardStmt:

From cc209813cf2c062b54d23b6f20d17808c7d46df1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 05/12] Support of LET command in PLpgSQL

index 8bbd640c3d..68b607339c 100644
@@ -898,6 +898,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -1018,6 +1019,13 @@ parse_toplevel:
 
 /*

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

+static void

+static void

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

From dbb8a8cf7b0d2947443f52021adff2a9e8bfd7e4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 07/12] Enhancing psql for session variables

index a81bd3307b..e1fd815b9b 100644
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index f265e043e9..c38e419dac 100644

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From a0ed20d0a85f3b857e7dd94d19859f8423675683 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 04/12] LET command

index f5c1c39eeb..d6d0adf176 100644
@@ -1759,6 +1759,85 @@ ResetSessionVariables(void)
 	xact_recheck_requests = NIL;
 }
 
+/*

 
 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
index facbd5f475..8bbd640c3d 100644
@@ -461,6 +461,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
@@ -820,7 +821,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY KEYS KEEP

 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
@@ -1158,6 +1159,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -12003,6 +12005,7 @@ ExplainableStmt:
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -12033,6 +12036,7 @@ PreparableStmt:
 			| UpdateStmt
 			| DeleteStmt
 			| MergeStmt						/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -12620,6 +12624,49 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -17903,6 +17950,7 @@ unreserved_keyword:
@@ -18509,6 +18557,7 @@ bare_label_keyword:
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 	NodeTag		type;

 

 /* ----------------
-- 
2.37.1

From d15a1b4298545b40f5d7c90a80f1da722b64b3f3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/12] typecheck - check of consistency of format of stored
 value.

Session variables holds a own evidence of fingerprints for any
used type. When some possible not compatible change is detected
(the version of extension is changed), the related stored type's
generation number is increased. The copy of genaration number is
stored in session variable data. Before any read of session variable,
the stored generation number is compared with current type generation
number. When it is different, the read is canceled.
---
 src/backend/commands/sessionvariable.c | 776 +++++++++++++++++++++++--
 1 file changed, 720 insertions(+), 56 deletions(-)

index f1ff97f0b4..f5c1c39eeb 100644
@@ -31,14 +34,18 @@
 
 /*
@@ -151,6 +158,46 @@
  * support session variables (the reading of session variables is
  * fully transparent for PL/pgSQL).
@@ -222,7 +269,6 @@ typedef struct SVariableData
 	bool		freeval;
 	Datum		value;
 
@@ -237,12 +283,18 @@ typedef struct SVariableData

 

@@ -264,6 +316,545 @@ SvariableXActActionName(SVariableXActAction action)
 	}
 }
 
+/*
@@ -508,9 +1099,10 @@ sync_sessionvars_all()
 
 	/* set callbacks */
 	if (first_time)
@@ -530,14 +1122,24 @@ create_sessionvars_hashtable(void)

 

@@ -565,15 +1167,19 @@ set_session_variable(SVariable svar, Datum value,
 						get_namespace_name(get_session_variable_namespace(svar->varid)),
 						get_session_variable_name(svar->varid))));

@@ -591,7 +1197,10 @@ set_session_variable(SVariable svar, Datum value,
 
 	free_session_variable_value(svar);
@@ -622,11 +1231,10 @@ init_session_variable(SVariable svar, Variable *var)
 	svar->svartype = get_svariabletype(var->typid);
 	svar->gennum = get_svariable_valid_type_gennum(svar->svartype);

@@ -672,7 +1280,7 @@ prepare_variable_for_reading(Oid varid)
 	var.oid = InvalidOid;
 
 	if (!sessionvars)
 
 	/* Protect used session variable against drop until transaction end */
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
@@ -684,54 +1292,103 @@ prepare_variable_for_reading(Oid varid)
 										HASH_ENTER, &found);

 
-	if (!found)
+			elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new entry in memory (emitted by READ)",
+						 svar->nsname, svar->name,

-	if (svar->has_defexpr)
+	/*

 

 	return svar;
 }
@@ -755,7 +1412,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid)
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
 
 	if (!sessionvars)
 
 	/* Ensure so all entries in sessionvars hash table are valid */
 	sync_sessionvars_all();
@@ -810,10 +1467,12 @@ CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 	Assert(svar != NULL && svar->is_valid);
 
 	*isNull = svar->isnull;
-	*typid = svar->typid;
 
 	if (!svar->isnull)
 
 	return (Datum) 0;
 }
@@ -831,7 +1490,7 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	svar = prepare_variable_for_reading(varid);
 	Assert(svar != NULL && svar->is_valid);
 
@@ -839,7 +1498,9 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	*isNull = svar->isnull;
 
 	if (!svar->isnull)
 
 	return (Datum) 0;
 }
@@ -857,7 +1518,7 @@ GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 	svar = prepare_variable_for_reading(varid);
 	Assert(svar != NULL && svar->is_valid);
 
@@ -1070,6 +1731,9 @@ ResetSessionVariables(void)

From dc4907bc34ad12c9e9042e24783979b2756b6739 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/12] session variables

 src/backend/commands/sessionvariable.c | 1134 +++++++++++++++++++++++-
 src/backend/parser/parse_expr.c        |  186 +++-
 src/include/catalog/pg_proc.dat        |    7 +
 29 files changed, 1929 insertions(+), 62 deletions(-)

index 9b97729405..45f7ef0ec1 100644

 	/* close large objects before lower-level cleanup */
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 6adb7c1ee2..1844d5c6f0 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1904,6 +1904,11 @@ find_expr_references_walker(Node *node,
 	{
 		Param	   *param = (Param *) node;
 
index 3094bab82a..f1ff97f0b4 100644
@@ -23,22 +23,138 @@
 
 /*
- * The life cycle of temporary session variable can be
- * limmited by using clause ON COMMIT DROP.
+ * Values of session variables are stored in local memory, in
+ * sessionvars hash table in binary format and the life cycle of
+ * session variable can be longer than transaction. Then we
+ * need to solve following issues:
+ *
+ * - We need to purge memory when variable is dropped (in current
+ *   transaction in current session or by other sessions (other
+ *   users). There is a request to protect content against
+ *   possibly aborted the DROP VARIABLE command. So the purging should
+ *   not be executed immediately. It should be postponed until the end
+ *   of the transaction.
+ *
+ * - The session variable can be dropped explicitly (by DROP VARIABLE)
+ *   command or implicitly (by using ON COMMIT DROP clause). The
+ *   purging memory at transaction end can be requested implicitly
+ *   too (by using the ON TRANSACTION END clause).
+ *
+ * - We need to ensure that the returned value is valid. The stored
+ *   value can be invalid from different reasons:
+ *
+ *   a) format change - we doesn't support change of format for
+ *      session variable, but format can be changed by ALTER TYPE
+ *      operation or by owner's extension update. We store fingerprint
+ *      of any type used by session variables, and before any
+ *      read we check if this fingerprint is still valid (see
+ *      SVariableType related operations). Built-in non composite
+ *      types are trusty every time.
+ *
+ *      For other cases we can check xmin, and if xmin is different,
+ *      then we check the name. For types from extension we
+ *      can check xmin of pg_extension related record (and if it
+ *      is different we can check extension's name and extension's
+ *      version). For domains we should to force cast to domain type
+ *      to reply all constraints before returning the value.
+ *
+ *   b) the variable can be dropped - we don't purge memory
+ *      when the variable is dropped immediately, so theoretically
+ *      we can return dropped value. The protection against this
+ *      issue is based on invisibility of dropped session variables
+ *      in system catalogue. The DROP commands invalidates related
+ *      cached plans too, so it should work with prepared statements.
+ *      Theoretically there is possible access to value of dropped
+ *      variable by using known oid, but we don't try to solve this
+ *      issue now (it is possible only when session variables API
+ *      is used from extensions). The implementation watches sinval
+ *      messages, and in message processing check the consistency
+ *      of stored values in hash table against system catalogue.
+ *      This check should be done after end of any transaction, but
+ *      because for this check we need not aborted state, the check
+ *      can be postponed to next transaction (when the variable
+ *      will be used). Unfortunately, this check can be postponed
+ *      to the future and between there can be oid overflow. We
+ *      cannot to detect oid overflow, and then theoretically there is
+ *      the possibility to find in catalogue a variable with same
+ *      oid like oid of dropped value. But as protection against
+ *      returning invalid value we can (and we do) check the used
+ *      type, and when the type is not valid, then we don't allow
+ *      returns any value. We cannot to check name or schema, because
+ *      we suppport ALTER RENAME and ALTER SET SCHEMA. So, although
+ *      there is an risk to return value of dropped variable (instead
+ *      of new calculated default expr or NULL), there should not
+ *      be risk of crash due to invalid format.
+ *      
+ * The values of session variables are stored in hash table
+ * sessionvars, the fingerprints of used types are stored in
+ * hash table sessionvars_types. We maintain four queues
+ * (implemented as list of variable oid, list of RecheckVariableRequests
+ * (requests are created in reaction on sinval) and lists
+ * of actions). List of actions are used whe we need to calculate
+ * the final transaction state of an entry's creator's
+ * subtransaction. List of session variables are used, when we
+ * don't need to do this calculation (we know, so. every values
+ * of session variables created with clauses ON COMMIT DROP or
+ * ON TRANSACTION END RESET will be purged from memory), but
+ * ON COMMIT DROP action or ON COMMIT RESET action can be done only
+ * when related sub transaction is committed (we don't want to lost
+ * value after aborted DROP VARIABLE command, we don't want to delete
+ * record from system catalogue after aborted CREATE VARIABLE command,
+ * or we don't want to drop record from system catalog implicitly
+ * when TEMP ON COMMIT DROP variable was successfully dropped).
+ * Recheck of session's variable existence should be executed
+ * only when some operation over session's variable is executed
+ * first time in transaction or at the end of transaction. It
+ * should not be executed more times inside transaction.
+ *
+ * Although the variable's reset doesn't need full delete from
+ * sessionvars hash table, we do. It is the most simple solution,
+ * and it reduces possible state's space (and reduces sessionvars
+ * bloating).
+ *
+ * There are two different ways to do final access to session
+ * variables: buffered (indirect) or direct. Buffered access is used
+ * in queries, where we have to ensure an stability of passed values
+ * (then the session variable has same behaviour like external query
+ * parameters, what is consistent with using PL/pgSQL's variables in
+ * embedded queries in PL/pgSQL).
+ *
+ * This is implemented by using an aux buffer (an array) that holds a
+ * copy of values of used (in query) session variables. In the final
+ * end, the values from this array are passed as constant (EEOP_CONST).
+ *
+ * Direct access is used by simple expression evaluation (PLpgSQL).
+ * In this case we don't need to ensure the stability of passed
+ * values, and maintaining the buffer with copies of values of session
+ * variables can be useless overhead. In this case we just read the
+ * value of the session variable directly (EEOP_PARAM_VARIABLE). This
+ * strategy removes the necessity to modify related PL/pgSQL code to
+ * support session variables (the reading of session variables is
+ * fully transparent for PL/pgSQL).
  */
 typedef enum SVariableXActAction
 {
 	SVAR_ON_COMMIT_DROP,		/* used for ON COMMIT DROP */
+	SVAR_ON_COMMIT_RESET,		/* used for DROP VARIABLE */
 } SVariableXActAction;
 
 typedef struct SVariableXActActionItem
@@ -54,12 +170,706 @@ typedef struct SVariableXActActionItem
 	SubTransactionId deleting_subid;
 }  SVariableXActActionItem;
 
-/* List holds fields of SVariableXActActionItem type */
-static List *xact_drop_actions = NIL;
+/* Both lists hold fields of SVariableXActActionItem type */
+static List *xact_on_commit_drop_actions = NIL;
+static List *xact_on_commit_reset_actions = NIL;
+
+/*
+ * the ON COMMIT DROP and ON TRANSACTION END RESET variables
+ * are purged from memory every time.
+ */
+static List *xact_reset_varids = NIL;
+
+/*
+ * When the session variable is dropped we need to purge memory. The
+ * session variable can be dropped by current session, but it can be
+ * dropped by other's sessions too, so we have to watc sinval message.
+ * But because we don't want to purge memory immediately, we need to
+ * hold list of possibly dropped session variables and at the end of
+ * transaction, we check session variables from this list against system
+ * catalogue. This check can be postponed into next transaction if
+ * current transactions is in aborted state. When the check is postponed
+ * to the next transaction, then have to be executed just once time, and
+ * the fields of list created in current transaction have to be ignored
+ * (the sinval message can come first before sync_sessionvars_all call,
+ * and then we have to skip items created by current transaction, because
+ * we want to variables when the related operation (that emmits sinval)
+ * is in final state (at the end of related transaction).
+ * Saved lxid in synced_lxid is safeguard against repeated useles
+ * recheck inside one transaction.
+ */
+typedef struct RecheckVariableRequest
+{
+	Oid			varid;
+	LocalTransactionId lxid;
+} RecheckVariableRequest;
+
+static List *xact_recheck_requests = NIL;
+
+static LocalTransactionId synced_lxid = InvalidLocalTransactionId;
+
+typedef struct SVariableData
+{
+	Oid			varid;			/* pg_variable OID of this sequence (hash key) */
+
+	char	   *name;		/* for debug purposes */
+	char	   *nsname;		/* for debug purposes */
+
+	SVariableType svartype;
+	int64		gennum;
+
+	bool		isnull;
+	bool		freeval;
+	Datum		value;
+
+	bool		is_rowtype;		/* true when variable is composite */
+	bool		is_not_null;	/* don't allow null values */
+	bool		is_immutable;	/* true when variable is immutable */
+	bool		has_defexpr;	/* true when variable has a default value */
+
+	bool		is_valid;		/* true when variable was successfully
+								 * initialized */
+
+	uint32		hashvalue;
+
+	bool		eox_reset;		/* true, when lifecycle is limitted by transaction */
+}			SVariableData;
+
+typedef SVariableData * SVariable;
+
+static HTAB *sessionvars = NULL;	/* hash table for session variables */
+static MemoryContext SVariableMemoryContext = NULL;
+
+static bool first_time = true;
+

+/*
+ * Returns human readable name of SVariableXActAction value.
+ */
+static const char *
+SvariableXActActionName(SVariableXActAction action)
+{
+	switch (action)
+	{
+		case SVAR_ON_COMMIT_DROP:
+			return "ON COMMIT DROP";
+		case SVAR_ON_COMMIT_RESET:
+			return "ON COMMIT RESET";
+		default:
+			elog(ERROR, "unknown SVariableXActAction action %d",
+						action);
+	}
+}
+
+/*
+ * Releases stored data from session variable, but preserve the hash entry
+ * in sessionvars.
+ */
+static void
+free_session_variable_value(SVariable svar)
+{
+	if (svar->freeval)
+		pfree(DatumGetPointer(svar->value));
+
+	/* Clean current value */
+	svar->value = (Datum) 0;
+	svar->isnull = true;
+	svar->freeval = false;
+
+	/*
+	 * We can mark this session variable as valid when
+	 * it has not default expression, and when null is
+	 * allowed. When it has defexpr, then the content
+	 * will be valid after an assignment or defexp evaluation.
+	 */
+	svar->is_valid = !svar->has_defexpr && !svar->is_not_null;
+}
+
+/*
+ * Release the variable defined by varid from sessionvars
+ * hashtab.
+ */
+static void
+remove_session_variable(SVariable svar)
+{
+	free_session_variable_value(svar);
+
+	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) is removing from memory",
+				 svar->nsname, svar->name,
+				 svar->varid);
+
+	if (hash_search(sessionvars,
+					(void *) &svar->varid,
+					HASH_REMOVE,
+					NULL) == NULL)
+		elog(DEBUG1, "hash table corrupted");
+}
+
+/*
+ * Release the variable defined by varid from sessionvars
+ * hashtab.
+ */
+static void
+remove_session_variable_by_id(Oid varid)
+{
+	SVariable svar;
+	bool		found;
+
+	if (!sessionvars)
+		return;
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+										HASH_FIND, &found);
+	if (found)
+		remove_session_variable(svar);
+}
+
+/*
+ * Callback function for session variable invalidation.
+ */
+static void
+pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
+{
+	HASH_SEQ_STATUS status;
+	SVariable svar;
+
+	/*
+	 * There is no guarantee of sessionvars being initialized, even when
+	 * receiving an invalidation callback, as DISCARD [ ALL | VARIABLES ]
+	 * destroys the hash table entirely.
+	 */
+	if (!sessionvars)
+		return;
+
+	/*
+	 * When the hashvalue is not specified, then we have to recheck all
+	 * currently used session variables. Since we can't guarantee the exact
+	 * session variable from its hashValue, we have to iterate over all
+	 * items of hash table.
+	 */
+	hash_seq_init(&status, sessionvars);
+
+	while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+	{
+		if (hashvalue == 0 || svar->hashvalue == hashvalue)
+		{
+			/*
+			 * We don't need to execute recheck for variables,
+			 * that will be surelly purged from memory at eox
+			 * time.
+			 */
+			if (!svar->eox_reset)
+			{
+				RecheckVariableRequest *req = NULL;
+				ListCell *l;
+
+				/*
+				 * The recheck request should be postponed to the end of current
+				 * transaction if there are request recheck from previous
+				 * transaction. This ensure so we don't purge memory too
+				 * early, when we process sinval from previous aborted
+				 * transaction. Possible test case:
+				 *
+				 * LET var = X;
+				 * BEGIN DROP VAR var; ROOLBACK;
+				 * BEGIN DROP VAR var ROLLBACK;
+				 * SELECT var; -- should be X
+				 *
+				 * Without postponing, the call of sync_sessionvars_all
+				 * in second transaction purge (badly) memory, because
+				 * sinval message from prrevious aborted transaction is
+				 * processed and in this time related record from pg_variable
+				 * is removed. So when we find request recheck from prev
+				 * transaction, we just update lxid.
+				 */
+				foreach (l, xact_recheck_requests)
+				{
+					req = (RecheckVariableRequest *) lfirst(l);
+					if (req->varid == svar->varid)
+					{
+						if (req->lxid != MyProc->lxid)
+						{
+							req->lxid = MyProc->lxid;
+
+							elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) should be rechecked (forced by sinval)"
+										 " (found recheck request from previous transaction)",
+									 svar->nsname, svar->name,
+									 svar->varid);
+
+							break;
+						}
+					}
+				}
+
+				if (!req)
+				{
+					MemoryContext oldcxt;
+
+					oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+					req = (RecheckVariableRequest *) palloc(sizeof(RecheckVariableRequest));
+
+					req->varid = svar->varid;
+					req->lxid = MyProc->lxid;
+
+					elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) should be rechecked (forced by sinval)",
+								 svar->nsname, svar->name,
+								 svar->varid);
+
+					xact_recheck_requests = lappend(xact_recheck_requests, req);
+
+					MemoryContextSwitchTo(oldcxt);
+				}
+			}
+		}
+
+		/*
+		 * although it there is low probability, we have to iterate
+		 * over all actively used session variables, because hashvalue
+		 * is not unique identifier.
+		 */
+	}
+}
+
+/*
+ * When we need to recheck all session variables, then
+ * the most effective method is seq scan over hash tab.
+ * We need to check synchronization request before any
+ * read to be sure so returned data are valid.
+ */
+static void
+sync_sessionvars_all()
+{
+	SVariable svar;
+	ListCell   *l;
+
+	if (synced_lxid == MyProc->lxid)
+		return;
+
+	if (!xact_recheck_requests)
+		return;
+
+	/*
+	 * sessionvars is null after DISCARD VARIABLES.
+	 * When we are sure, so there are not any
+	 * active session variable in this session.
+	 */
+	if (!sessionvars)
+	{
+		list_free_deep(xact_recheck_requests);
+		xact_recheck_requests = NIL;
+		return;
+	}
+
+	/*
+	 * This routine is called before any reading. So the
+	 * session should be in transaction state. This is required
+	 * for access to system catalog.
+	 */
+	Assert(IsTransactionState());
+
+	foreach(l, xact_recheck_requests)
+	{
+		RecheckVariableRequest *req;
+		bool	found;
+
+		req = (RecheckVariableRequest *) lfirst(l);
+
+		if (req->lxid != MyProc->lxid)
+		{
+			svar = (SVariable) hash_search(sessionvars, &req->varid,
+											HASH_FIND, &found);
+
+			if (found)
+			{
+				HeapTuple	tp;
+
+				tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(req->varid));
+
+				if (HeapTupleIsValid(tp))
+					ReleaseSysCache(tp);
+				else
+					remove_session_variable(svar);
+			}
+
+			xact_recheck_requests = foreach_delete_current(xact_recheck_requests, l);
+			pfree(req);
+		}
+	}
+
+	/* Don't repeat this check in this transaction more time */
+	synced_lxid = MyProc->lxid;
+}
+
+/*
+ * Create the hash table for storing session variables
+ */
+static void
+create_sessionvars_hashtable(void)
+{
+	HASHCTL		ctl;
+
+	/* set callbacks */
+	if (first_time)
+	{
+		/* Read sinval messages */
+		CacheRegisterSyscacheCallback(VARIABLEOID,
+									  pg_variable_cache_callback,
+									  (Datum) 0);
+		/* needs its own long lived memory context */
+		SVariableMemoryContext =
+			AllocSetContextCreate(TopMemoryContext,
+								  "session variables",
+								  ALLOCSET_START_SMALL_SIZES);
+
+		first_time = false;
+	}
+
+	Assert(SVariableMemoryContext);
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(SVariableData);
+	ctl.hcxt = SVariableMemoryContext;
+
+	Assert(sessionvars == NULL);
+
+	sessionvars = hash_create("Session variables", 64, &ctl,
+								   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+/*
+ * Assign some content to the session variable. It's copied to
+ * SVariableMemoryContext if necessary.
+ *
+ * init_mode is true, when the value of session variable is being initialized
+ * by default expression or by null. Only in this moment we can allow to
+ * modify immutable variables with default expression.
+ */
+static void
+set_session_variable(SVariable svar, Datum value,
+					 bool isnull, Oid typid,
+					 bool init_mode)
+{
+	MemoryContext oldcxt;
+	Datum		newval = value;
+
+	/* Don't allow assignment of null to NOT NULL variable */
+	if (isnull && svar->is_not_null)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
+
+	if (svar->typid != typid)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type \"%s\" of assigned value is different than type \"%s\" of session variable \"%s.%s\"",
+						format_type_be(typid),
+						format_type_be(svar->typid),
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
+
+	/*
+	 * Don't allow updating of immutable session variable that has assigned
+	 * not null value or has default expression (and then the value should be
+	 * result of default expression always). Don't do this check, when variable
+	 * is being initialized.
+	 */
+	if (!init_mode &&
+		(svar->is_immutable && (svar->is_valid || svar->has_defexpr)))
+		ereport(ERROR,
+				(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
+				 errmsg("session variable \"%s.%s\" is declared IMMUTABLE",
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
+
+	/* copy value to session persistent context */
+	oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+	if (!isnull)
+		newval = datumCopy(value, svar->typbyval, svar->typlen);
+	MemoryContextSwitchTo(oldcxt);
+
+	free_session_variable_value(svar);
+
+	svar->value = newval;
+	svar->isnull = isnull;
+	svar->freeval = newval != value;
+	svar->is_valid = true;
+}
+
+/*
+ * Initialize svar from var
+ * svar - SVariable - holds value
+ * var  - Variable - holds metadata
+ */
+static void
+init_session_variable(SVariable svar, Variable *var)
+{
+	MemoryContext oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+	Assert(OidIsValid(var->oid));
+
+	svar->varid = var->oid;
+
+	svar->name = pstrdup(var->name);
+	svar->nsname = get_namespace_name(var->namespace);
+
+	svar->svartype = get_svariabletype(var->typid);
+	svar->gennum = get_svariable_valid_type_gennum(svar->svartype);
+
+	svar->isnull = true;
+	svar->freeval = false;
+	svar->value = (Datum) 0;
+
+	svar->is_rowtype = type_is_rowtype(var->typid);
+	svar->is_not_null = var->is_not_null;
+	svar->is_immutable = var->is_immutable;
+	svar->has_defexpr = var->has_defexpr;
+
+	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
+											ObjectIdGetDatum(var->oid));
+
+	/* the value of variable is not known yet */
+	svar->is_valid = false;
+
+	svar->eox_reset = var->eoxaction == VARIABLE_EOX_RESET ||
+					  var->eoxaction == VARIABLE_EOX_DROP;
+
+	if (svar->eox_reset)
+	{
+
+		MemoryContext oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		xact_reset_varids = lappend_oid(xact_reset_varids, var->oid);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Search the given session variable in the hash table. If it doesn't
+ * exist, then insert it (and calculate defexpr if it exists).
+ *
+ * Caller is responsible for doing permission checks.
+ *
+ * As side effect this function acquires AccessShareLock on
+ * related session variable until the end of the transaction.
+ */
+static SVariable
+prepare_variable_for_reading(Oid varid)
+{
+	SVariable svar;
+	Variable	var;
+	bool		found;
+
+	var.oid = InvalidOid;
+
+	if (!sessionvars)
+		create_sessionvars_hashtable();
+
+	/* Protect used session variable against drop until transaction end */
+	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	/* Ensure so all entries in sessionvars hash table are valid */
+	sync_sessionvars_all();
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+										HASH_ENTER, &found);
+
+	/* Return content if it is available and valid */
+	if (found && svar->is_valid)
+		return svar;
+
+	/* We need to load defexpr. */
+	initVariable(&var, varid, false);
+
+	if (!found)
+		init_session_variable(svar, &var);
+
+	/* Raise an error when we cannot initialize variable correctly */
+	if (var.is_not_null && !var.defexpr)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+						get_namespace_name(get_session_variable_namespace(varid)),
+						get_session_variable_name(varid)),
+				 errdetail("The session variable was not initialized yet.")));
+
+	if (svar->has_defexpr)
+	{
+		Datum		value = (Datum) 0;
+		bool		isnull;
+		EState	   *estate = NULL;
+		Expr	   *defexpr;
+		ExprState  *defexprs;
+		MemoryContext oldcxt;
+
+		/* Prepare default expr */
+		estate = CreateExecutorState();
+
+		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		defexpr = expression_planner((Expr *) var.defexpr);
+		defexprs = ExecInitExpr(defexpr, NULL);
+		value = ExecEvalExprSwitchContext(defexprs,
+										  GetPerTupleExprContext(estate),
+										  &isnull);
+
+
+		/* Store result before releasing Executor memory */
+		set_session_variable(svar, value, isnull, svar->typid, true);
+
+		MemoryContextSwitchTo(oldcxt);
+
+		FreeExecutorState(estate);
+	}
+	else
+		set_session_variable(svar, (Datum) 0, true, svar->typid, true);
+
+	return svar;
+}
+
+/*
+ * Store the given value in an SVariable, and cache it if not already present.
+ *
+ * Caller is responsible for doing permission checks.
+ * We try not to break the previous value, if something is wrong.
+ *
+ * As side effect this function acquires AccessShareLock on
+ * related session variable until the end of the transaction.
+ */
+void
+SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid)
+{
+	SVariable svar;
+	bool		found;
+
+	/* Protect used session variable against drop until transaction end */
+	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	if (!sessionvars)
+		create_sessionvars_hashtable();
+
+	/* Ensure so all entries in sessionvars hash table are valid */
+	sync_sessionvars_all();
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+										HASH_ENTER, &found);
+
+	if (!found)
+	{
+		Variable	var;
+
+		/* We don't need to know defexpr here */
+		initVariable(&var, varid, true);
+		init_session_variable(svar, &var);
+
+		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new entry in memory (emitted by WRITE)",
+					 svar->nsname, svar->name,
+					 varid);
+	}
+
+	set_session_variable(svar, value, isNull, typid, false);
+}
+
+/*
+ * Wrapper around SetSessionVariable after checking for correct permission.
+ */
+void
+SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typid)
+{
+	AclResult	aclresult;
+
+	/*
+	 * Is possible to write to session variable?
+	 */
+	aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));
+
+	SetSessionVariable(varid, value, isNull, typid);
+}
+
+/*
+ * Returns a copy of value of the session variable specified by varid
+ * Caller is responsible for doing permission checks.
+ */
+Datum
+CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
+{
+	SVariable svar;
+
+	svar = prepare_variable_for_reading(varid);
+	Assert(svar != NULL && svar->is_valid);
+
+	*isNull = svar->isnull;
+	*typid = svar->typid;
+
+	if (!svar->isnull)
+		return datumCopy(svar->value, svar->typbyval, svar->typlen);
+
+	return (Datum) 0;
+}
+
+/*
+ * Returns a copy of value of the session variable specified by varid
+ * with check of expected type. Like previous function, the caller
+ * is responsible for doing permission checks.
+ */
+Datum
+CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
+{
+	SVariable svar;
+
+	svar = prepare_variable_for_reading(varid);
+	Assert(svar != NULL && 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));
+
+	*isNull = svar->isnull;
+
+	if (!svar->isnull)
+		return datumCopy(svar->value, svar->typbyval, svar->typlen);
+
+	return (Datum) 0;
+}
+
+/*
+ * Returns a value of session variable identified by varid with
+ * check of expected type. Like previous function, the called
+ * is reposible for doing permission check.
+ */
+Datum
+GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
+{
+	SVariable svar;
+
+	svar = prepare_variable_for_reading(varid);
+	Assert(svar != NULL && 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));
+
+	*isNull = svar->isnull;
+
+	if (svar->isnull)
+		return (Datum) 0;
+
+	return svar->value;
+}
+
 /*
  * Routines used for manipulation with session variables from
  * SQL level
@@ -170,19 +980,24 @@ DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 }
 
 /*
 
-	elog(DEBUG1, "session variable (oid:%u) is deleted from pg_variable", varid);
-
 	rel = table_open(VariableRelationId, RowExclusiveLock);
 
 	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
@@ -190,6 +1005,18 @@ RemoveSessionVariable(Oid varid)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for variable %u", varid);
 
+	if (message_level_is_interesting(DEBUG1))
+	{
+		Form_pg_variable varform;
+
+		varform = (Form_pg_variable) GETSTRUCT(tup);
+
+		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) is deleted from pg_variable",
+					 get_namespace_name(varform->varnamespace),
+					 NameStr(varform->varname),
+					 varid);
+	}
+
 	CatalogTupleDelete(rel, &tup->t_self);
 
 	ReleaseSysCache(tup);
@@ -197,17 +1024,81 @@ RemoveSessionVariable(Oid varid)
 	table_close(rel, RowExclusiveLock);
 
 	/*
+	/* we should clean xact_recheck_requests */
+	list_free_deep(xact_recheck_requests);
+	xact_recheck_requests = NIL;
 }
 
 /*
 
@@ -221,6 +1112,9 @@ register_session_variable_xact_action(Oid varid,
 	SVariableXActActionItem *xact_ai;
 	MemoryContext oldcxt;

 	xact_ai = (SVariableXActActionItem *)
@@ -231,8 +1125,10 @@ register_session_variable_xact_action(Oid varid,
 	xact_ai->creating_subid = GetCurrentSubTransactionId();
 	xact_ai->deleting_subid = InvalidSubTransactionId;

 	MemoryContextSwitchTo(oldcxt);
 }
@@ -251,7 +1147,10 @@ unregister_session_variable_xact_action(Oid varid,

@@ -270,25 +1169,39 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 {
 	ListCell   *l;

 
-			/*
 
+				/*
+				 * ON COMMIT DROP is allowed only for temp session
+				 * variables. So we should explicitly delete only when
+				 * current transaction was committed. When it's rollback,
+				 * then session variable is removed automatically.
+				 */
+
 				object.classId = VariableRelationId;
 				object.objectId = xact_ai->varid;
 				object.objectSubId = 0;
@@ -312,8 +1225,63 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 	 * Any drop action left is an entry that was unregistered and not
 	 * rollbacked, so we can simply remove them.
 	 */
-	list_free_deep(xact_drop_actions);
-	xact_drop_actions = NIL;
+	list_free_deep(xact_on_commit_drop_actions);
+	xact_on_commit_drop_actions = NIL;
+
+	if (isCommit && xact_recheck_requests)
+	{
+		Assert(sessionvars);
+
+		foreach(l, xact_recheck_requests)
+		{
+			SVariable	svar;
+			bool		found;
+			RecheckVariableRequest *req;
+
+			req = (RecheckVariableRequest *) lfirst(l);
+
+			svar = (SVariable) hash_search(sessionvars, &req->varid,
+										   HASH_FIND, &found);
+
+			if (found)
+			{
+				HeapTuple	tp;
+
+				tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+				if (HeapTupleIsValid(tp))
+					ReleaseSysCache(tp);
+				else
+					remove_session_variable(svar);
+			}
+		}
+
+		list_free_deep(xact_recheck_requests);
+		xact_recheck_requests = NIL;
+	}
+
+	if (isCommit && xact_on_commit_reset_actions)
+	{
+		foreach(l, xact_on_commit_reset_actions)
+		{
+			SVariableXActActionItem *xact_ai =
+								(SVariableXActActionItem *) lfirst(l);
+
+			/*
+			 * When we process DROP VARIABLE statement, we create
+			 * SVAR_ON_COMMIT_RESET xact action. We want to process
+			 * this action only when related transaction is commited
+			 * (when DROP VARIABLE statement sucessfully processed).
+			 * We want to preserve variable content, when the transaction
+			 * with DROP VARAIBLE statement was reverted.
+			 */
+			if (xact_ai->deleting_subid == InvalidSubTransactionId)
+				remove_session_variable_by_id(xact_ai->varid);
+		}
+	}
+
+	list_free_deep(xact_on_commit_reset_actions);
+	xact_on_commit_reset_actions = NIL;
 }
 
 /*
@@ -329,7 +1297,33 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 {
 	ListCell   *cur_item;
 
@@ -337,7 +1331,8 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
@@ -350,3 +1345,76 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
+#define NUM_PG_DEBUG_SHOW_USED_SESSION_VARIABLES_ATTS 10
+			 * Sessionvars can hold data of variables removed from catalogue,
+			 * (and not purged) and then namespacename and name cannot be read
+			 * from catalogue.
+			values[1] = PointerGetDatum(cstring_to_text(svar->nsname));
+			values[2] = PointerGetDatum(cstring_to_text(svar->name));
+			/* check if session variable is visible in system catalogue */
+				values[5] = BoolGetDatum(false);
+				values[6] = BoolGetDatum(svar->is_valid);
+				values[7] = BoolGetDatum(session_variable_use_valid_type(svar));
+				values[8] = BoolGetDatum(pg_variable_aclcheck(svar->varid, GetUserId(), ACL_READ) == ACLCHECK_OK);
+				values[9] = BoolGetDatum(pg_variable_aclcheck(svar->varid, GetUserId(), ACL_WRITE) == ACLCHECK_OK);
+				values[5] = BoolGetDatum(true);
+				nulls[6] = true;
+				nulls[7] = true;
+				nulls[8] = true;
+				nulls[9] = true;
 
 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*

+/*
 
index d575aa0066..83500cbff6 100644

+/*

 	/*
 
 /*
 
 	return false;
 }
index 46b6354a51..94b7ff599a 100644
 
+{ oid => '8488', descr => 'debug list of used session variables',
+  proname => 'pg_debug_show_used_session_variables', prorows => '1000', proretset => 't',
+  provolatile => 's', prorettype => 'record', proargtypes => '',
+  proallargtypes => '{oid,text,text,oid,text,bool,bool,bool,bool,bool}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{varid,schema,name,typid,typname,removed,has_value,has_valid_value,can_read,can_write}',
+  prosrc => 'pg_debug_show_used_session_variables' },
 ]
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 1e3f1bbee8..3ac7add98f 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -161,6 +161,7 @@ typedef enum ExprEvalOp
 	EEOP_PARAM_EXEC,
 	EEOP_PARAM_EXTERN,
 	EEOP_PARAM_CALLBACK,
+	EEOP_PARAM_VARIABLE,

 

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

 

 

 

 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.37.1

From 856ef26ffa87a911472f23de4ffeddede4a74737 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/12] Catalogue support for session variables

index ce1417b8f0..9b97729405 100644

@@ -4986,6 +4993,8 @@ CommitSubTransaction(void)
@@ -5150,6 +5159,8 @@ AbortSubTransaction(void)

 

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:
index e119674b1f..6adb7c1ee2 100644
@@ -186,7 +189,8 @@ static const Oid object_classes[] = {

@@ -1505,6 +1509,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			DropObjectById(object);
 			break;
 
+		case OCLASS_VARIABLE:
+			RemoveSessionVariable(object->objectId);
+			break;
+
 			/*
 			 * These global object types are not supported here.
 			 */
@@ -2958,6 +2966,9 @@ getObjectClass(const ObjectAddress *object)

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*
 
 				/*
index f9037761f9..facbd5f475 100644
@@ -482,6 +483,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
@@ -651,6 +653,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 %type <node>		json_format_clause_opt
@@ -857,8 +861,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UESCAPE UNBOUNDED UNCONDITIONAL UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING

 
@@ -1116,6 +1120,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -5124,6 +5129,69 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6901,6 +6969,7 @@ object_type_any_name:
 
 /*
@@ -7777,6 +7846,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7822,6 +7899,14 @@ privilege_target:

@@ -7989,6 +8074,7 @@ defacl_privilege_target:

@@ -9771,6 +9857,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10132,6 +10237,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10411,6 +10535,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17939,6 +18071,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18585,6 +18719,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)

 
 
-
 /*
 
 	return isclustered;
 }
+

 	/*

 

 

 
+

 
 /*

---

From: Julien Rouhaud <[email-masked]>
Date: Mon, 1 Aug 2022 12:53:58 +0800

Hi,

> description part in sessionvariable.c.

Thanks a lot, that's very helpful!

I looked at the new description and I'm not sure that I understand the need for
the "format change" code that tries to detect whether the underlying types was
modified.  It seems quite fragile, wouldn't it be better to have the same
behavior as for relation (detect and prevent such changes in the first place),
since both cases share the same requirements about underlying data types?  For
instance, it should be totally acceptable to drop an attribute from a custom
data type if a session variable is using it, same as if a table is using it but
as is it would be rejected for session variables.

While at it, the new comments contain a lot of non breakable spaces rather than
normal spaces.  I also just realized that there's a sessionvariable.c while the
header is named session_variable.h.

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 1 Aug 2022 08:24:14 +0200

po 1. 8. 2022 v 6:54 odesílatel Julien Rouhaud <[email-masked]> napsal:

> > > > > Apparently most of the changes in catalogs.sgml didn't survive the
> last
> > > > > rebase.
> > > @@ -1980,15 +1975,13 @@
> AtEOSubXact_SessionVariable_on_xact_actions(bool
> > > isCommit, SubTransactionId mySu
> > >      */
> > > We previously discussed this off-line, but for some quick context the
> test
> > > was

This is the first implementation and my strategy is "to be safe and to be
strict". I did tests I know, so the test of compatibility of composite
types can be more tolerant. But I use this test to test my identity against
oid overflow, and I don't feel comfortable if I write this test too
tolerantly. For implementation of a more precious test I need to save a
signature of attributes. So the test should not be done just on
compatibility of types from TupleDesc, but it should to check attributes
oid's. I had an idea to implement it in the next stage, and for this stage
just to require compatibility of the vector of types.

Can this enhanced check be implemented later or do you think so it should
be implemented now? I'll check how much new code it needs.

> header is named session_variable.h.

My bad - I used gmail as a spellchecker, and it wrote some white spaces
there :-/

should be fixed now
From 0f6b57f066d9d82e2c889279c0c1e367934fe964 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 08/12] Possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
index da6605175a..9c0b7fe1e1 100644
@@ -4741,6 +4742,232 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
@@ -9395,7 +9622,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -9985,6 +10213,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION:
@@ -14328,6 +14559,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -17869,6 +18103,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 726e09783917ef6f24f35dc27a117b7a810cba2a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 12/12] documentation

 
index cd2cc37aeb..aed6198b56 100644

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
 
+     <varlistentry>

From 1275d902e651e17f2f755694978666295fad2d7b Mon Sep 17 00:00:00 2001

 

 select a from atacc1;

 

 
 
 -- should fail
 LET v1.x = 10;

From 44f66873aeafe5f71d38d7e6b48b6b9ba66d2d3a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 09/12] typedefs

---

From 742100e31b2c44965c3a537348587ff836e209cd Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 10/12] Regress tests for session variables

 
 
 # ----------

From 0099a971402397eaeb5053fba1b6f519c36f9fd8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 06/12] DISCARD VARIABLES command

---

 

From 334e1f3025e549c79155cc4b4793953b1fe69c9a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 07/12] Enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From c8bd75f49fc0dd6a0d1486daa2bf8e8695f41799 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 05/12] Support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 7336ddbe0b2384681f7f62a32014b134120de7bc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 04/12] LET command

index 13c449e6c6..a0feacf90b 100644
 
+/*

 
 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
index a121876ac2..c214c3d9a9 100644
 
 			/* okay */
 			break;
@@ -3342,6 +3344,8 @@ ParseExprKindName(ParseExprKind exprKind)
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 	NodeTag		type;

 

 /* ----------------
-- 
2.37.1

From 5757ff1ca30f7b36e71fc744cce2f751fde99b95 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/12] typecheck - check of consistency of format of stored
 value.

index 0ec4b92b82..13c449e6c6 100644
 
 /*

 

 
 
+/*
 
 	/* set callbacks */
 	if (first_time)

 

 

 

 	if (!sessionvars)

 
 
-	if (!found)

-	if (svar->has_defexpr)
+	/*

 

 	return svar;
 }
 
 	if (!sessionvars)

 	*isNull = svar->isnull;
-	*typid = svar->typid;
 
 	if (!svar->isnull)
 
 	return (Datum) 0;
 }

 	if (!svar->isnull)
 
 	return (Datum) 0;
 }

From 64d7a8817d3616b96552ed7c46db2c0c740038a3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/12] session variables

 

index 3094bab82a..0ec4b92b82 100644
 
 /*

 
 
+/*
 
 /*

 
 
 	ReleaseSysCache(tup);
 
 	/*
 
 /*

 

 

 
 
-			/*
 
+				/*
 
 /*

 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

index 4b942fd440..a121876ac2 100644
 
 /* GUC parameters */

 /*

+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }
index 5055904005..e9d26f783b 100644
@@ -11897,4 +11897,11 @@

 

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

 

 

 

 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.37.1

From 6e629438c0964632de8a2361708dc4146059cd3a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/12] Catalogue support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*
index d22dd44712..73f9f5c318 100644
@@ -12659,6 +12659,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 				/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)
index 059cb7097c..4b942fd440 100644
 
@@ -3128,6 +3132,7 @@ ParseExprKindName(ParseExprKind exprKind)

 
-
 /*
 
 	return isclustered;
 }
+

 	/*

 

 
index be47583122..5055904005 100644

 
+

 
 /*

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 19 Aug 2022 15:57:29 +0200

Hi

I am sending fresh update

- enhanced work with composite types - now the used composite type can be
enhanced, reduced and stored value is converted to expected format
- enhancing find_composite_type_dependencies to support session variables,
so the type of any field of used composite type cannot be changed

Regards

Pavel
From 00b2a9ec1cf2af5f57a02fd34726133305e9dcba Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 13/13] documentation

 
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2ce539aaf0..71b3861ece 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>
@@ -9603,4 +9608,157 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </table>
  </sect1>
 
index a5cd4e44c7..6bf0f59998 100644
@@ -10239,6 +10239,21 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>
 
index f1235a2c9f..a8f303b474 100644

 

   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
 
+     <varlistentry>
 
2.37.2

From 6444fc4c6f9f73f18914ba852569093aff8c0d2e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 08/13] Possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 
 
2.37.2

From 9fd739fcadc4df194c4823bc4a50da3468b39898 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 09/13] typedefs

---

2.37.2

From b37ab19a64b7c991aa095dacdd839c1270cee31b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 07/13] Enhancing psql for session variables

 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 62a39779b9..2780c2239c 100644

 /*
@@ -2635,7 +2649,7 @@ psql_completion(const char *text, int start, int end)
@@ -3073,7 +3087,7 @@ psql_completion(const char *text, int start, int end)
@@ -3380,6 +3394,17 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS */
@@ -3495,7 +3520,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
@@ -3622,6 +3647,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 */
@@ -3738,7 +3769,8 @@ psql_completion(const char *text, int start, int end)
@@ -3756,6 +3788,8 @@ psql_completion(const char *text, int start, int end)
 									 "USAGE",
 									 "SET",
 									 "ALTER SYSTEM",
+									 "READ",
+									 "WRITE",
 									 "ALL");
 	}
 
@@ -3797,7 +3831,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("GRANT|REVOKE", MatchAny) ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny))
 	{
-		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
+		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|READ|WRITE|ALL"))
 			COMPLETE_WITH("ON");
 		else if (TailMatches("GRANT", MatchAny))
 			COMPLETE_WITH("TO");
@@ -3820,7 +3854,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",
@@ -3842,7 +3876,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"))
@@ -3850,7 +3885,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");
@@ -3886,6 +3922,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
@@ -4128,7 +4166,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -4416,6 +4454,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
 
@@ -4582,6 +4628,8 @@ psql_completion(const char *text, int start, int end)
2.37.2

From a17911f3450b7aecb4a94ba4d2f8af333d802f52 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 10/13] Regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 src/test/regress/expected/misc_sanity.out     |    4 +-
 .../regress/expected/session_variables.out    | 1135 +++++++++++++++++
 src/test/regress/parallel_schedule            |    2 +-
 src/test/regress/sql/session_variables.sql    |  864 +++++++++++++
 4 files changed, 2003 insertions(+), 2 deletions(-)
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

index 0000000000..8c4fafe5e1
@@ -0,0 +1,1135 @@
+ (10,20)
+ (10,20,)
+CREATE TYPE public.svar_test_type AS (a int, b int);
+ALTER TYPE public.svar_test_type DROP ATTRIBUTE b;
+CREATE TYPE public.svar_test_type AS (a int, b int);
+CREATE VARIABLE public.svar2 AS public.svar_test_type;
+LET public.svar = (10, 20);
+ (10,20,)
+LET public.svar2 = (10, 20, 30);
+ALTER TYPE public.svar_test_type DROP ATTRIBUTE b;
+ (10,)
+SELECT public.svar2;
+  svar2  
+ (10,30)
+DROP VARIABLE public.svar2;
 
 # ----------
index 0000000000..e74d8bfd6a
@@ -0,0 +1,864 @@
+CREATE TYPE public.svar_test_type AS (a int, b int);
+ALTER TYPE public.svar_test_type DROP ATTRIBUTE b;
+CREATE TYPE public.svar_test_type AS (a int, b int);
+CREATE VARIABLE public.svar2 AS public.svar_test_type;
+LET public.svar = (10, 20);
+LET public.svar2 = (10, 20, 30);
+ALTER TYPE public.svar_test_type DROP ATTRIBUTE b;
+SELECT public.svar2;
+DROP VARIABLE public.svar2;
2.37.2

From 3a2adb3b3abcf3bab9a5db498a7dbd483f9f03de Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 06/13] DISCARD VARIABLES command

---

 

index fb1e958151..6048460d82 100644
@@ -3857,7 +3857,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;
 
index 357989b52f..09bd4d467f 100644
2.37.2

From 9373efc999a59de5051695c182008ccb19ff7f0a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 05/13] Support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 5f7be59ef36dab8a4c89eb07943c26135f9071d5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 04/13] LET command

index e078456b19..3a5f51cbc3 100644
index ff6a2f5c9e..635d50860f 100644
@@ -2038,6 +2038,85 @@ ResetSessionVariables(void)
 
+/*
 
index c334daae39..1e9221ed66 100644
@@ -4103,6 +4103,16 @@ raw_expression_tree_walker(Node *node,

 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
index abe43015c5..f0e6a62d64 100644
 
 			/* okay */
 			break;
 
 			/*
 			 * 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 005bc0400f..21fbf939f2 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2660,6 +2660,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_LET_TARGET:
+			err = _("set-returning functions are not allowed in CALL arguments");
+			break;
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*
 
index b24d2bd9f9..fb1e958151 100644

 	NodeTag		type;

 

index 6c1be5e9d4..357989b52f 100644
 
 /* ----------------
-- 
2.37.2

From 67996708743cd376175fec6469b2c697fb5d43c5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/13] typecheck - check of consistency of format of stored
 value.

Session variables holds a own evidence of fingerprints for any
used type. When some possible not compatible change is detected
(the version of extension is changed), the related stored type's
generation number is increased. The copy of genaration number is
stored in session variable data. Before any read of session variable,
the stored generation number is compared with current type generation
number. When it is different, the read is canceled.
---
 src/backend/commands/sessionvariable.c | 1151 +++++++++++++++++++++---
 1 file changed, 1048 insertions(+), 103 deletions(-)

index d1300e7de3..ff6a2f5c9e 100644
 
 /*
  * Values of session variables are stored in local memory, in
@@ -66,7 +73,7 @@
  *   operation or by owner's extension update. We store fingerprint
  *   of any type used by session variables, and before any
  *   read we check if this fingerprint is still valid (see
- *   SVariableType related operations). Built-in non composite
+ *   SVariableType related operations). Built-in non row
  *   types are trusty every time.
  *
  *   For other cases we can check xmin, and if xmin is different,
@@ -76,6 +83,18 @@
  *   version). For domains we should to force cast to domain type
  *   to reply all constraints before returning the value.
  *
+ *   We have not too much possibilities for check of identity
+ *   of row types (against oid's overflow) because for
+ *   custom types, the value of relid is constant offset
+ *   from type's oid. But it can work well for row types
+ *   based on tables. On second hand we can every time to convert
+ *   stored value to expected (current) tupledesc (if it is
+ *   possible. Unfortunatelly we cannot to use already prepared
+ *   conversions (based on mapping of undeleted fields or based
+ *   on names). In this case we expect so target tupledesc is
+ *   just modified original tupledesc. So mapping should be done
+ *   really just on position.
+ *
  *  b) the variable can be dropped - we don't purge memory
  *   when the variable is dropped immediately, so theoretically
  *   we can return dropped value. The protection against this
@@ -151,6 +170,58 @@
  * support session variables (the reading of session variables is
  * fully transparent for PL/pgSQL).
  */
+struct SVariableTypeDataField;
+
+typedef struct SVaribleTypeData
+{
+	Oid			typid;
+	Oid			relid;			/* OID of relation containing this attribute *
+								 * used for check of oid's overflow */
+
+	int64		gennum;			/* generation number helps with change detection */
+	LocalTransactionId verified_lxid; /* lxid of transaction when the typ fingerprint
+									   * was created or verified */
+	int			natts;
+	struct		SVariableTypeDataField *attrs;
+
+	int16		typlen;
+	bool		typbyval;
+	bool		is_domain;
+	bool		is_rowtype;
+	char	   *typname;
+
+	struct		SVaribleTypeData *base_type;
+	int64		base_type_gennum;
+	void	   *domain_check_extra;		/* domain check routine */
+	LocalTransactionId domain_check_extra_lxid; /* lxid when value of domain's type *
+												 * was checked */
+
+	TransactionId	typ_xmin;	/* xmin of related tuple of pg_type */
+	ItemPointerData typ_tid;
+
+	Oid			extid;			/* OID of owner extension if exists */
+	TransactionId ext_xmin;		/* xmin of related tuple of pg_extension */
+	ItemPointerData ext_tid;
+	char	   *extname;
+	char	   *extversion;
+
+} SVariableTypeData;
+
+typedef SVariableTypeData * SVariableType;
+
+/*
+ * Following structure is used as type's finger print for
+ * fields of row type. It helps with fast type identity
+ * check for row value.
+ */
+typedef struct SVariableTypeDataField
+{
+	SVariableType svartype;
+	int64		gennum;
+	int32		typmod;
+	bool		isdropped;
+} SVariableTypeDataField;
+
 typedef enum SVariableXActAction
 {
 	SVAR_ON_COMMIT_DROP,		/* used for ON COMMIT DROP */
@@ -211,18 +282,38 @@ static LocalTransactionId synced_lxid = InvalidLocalTransactionId;
 typedef struct SVariableData
 {
 	Oid			varid;			/* pg_variable OID of this sequence (hash key) */
+	Oid			relid;			/* used for fast row type's identity check */
 
-	char	   *name;		/* for debug purposes */
-	char	   *nsname;		/* for debug purposes */
+	/*
+	 * Next two items can be used just for debug purposes.
+	 * The stored nsnane and name can be obsolete, when schema
+	 * or variable name are altered. This behaviour can be
+	 * acceptable for debug purposes (and it is necessary, because
+	 * sometimes we cannot to access to catalogue.
+	 */
+	char	   *name;			/* session variable name (at time of initialization) */
+	char	   *nsname;			/* session variable schema name */
 
 	SVariableType svartype;
 	int64		gennum;
 
+	/*
+	 * We need to store details about fields, because
+	 * we can convert old row value to new format,
+	 * when the difference is just dropped or new
+	 * fields. But for converted fields we need to
+	 * check type too.
+	 */
+	int			natts;
+	struct		SVariableTypeDataField *attrs;
+
 	bool		isnull;
 	bool		freeval;
 	Datum		value;
+	TupleDesc	tupdesc;		/* for row type we save actual tupdesc *
+								 * at save time, for conversion to uptime *
+								 * tupdesc in read time */
 
@@ -230,19 +321,27 @@ typedef struct SVariableData
 
-	uint32		hashvalue;
+	uint32		hashvalue;		/* used for pairing sinval message */
 
 	bool		eox_reset;		/* true, when lifecycle is limitted by transaction */
 }			SVariableData;

 

+static void init_session_variable(SVariable svar, Variable *var);
 
@@ -264,12 +363,572 @@ SvariableXActActionName(SVariableXActAction action)
 	}
 }
 
+/*
+	 * or row fields. Just typlen, and typbyval is not enough
+	if (svt1->is_rowtype)
+		if (svt1->relid != svt2->relid)
+			if (svt1->attrs[i].isdropped != svt2->attrs[i].isdropped)
+			if (!svt1->attrs[i].isdropped)
+				if (svt1->attrs[i].typmod != svt2->attrs[i].typmod)
+	svt->relid = typ->typrelid;
+	 * Store fingerprints of fields of row types.
+											 tupdesc->natts * sizeof(SVariableTypeDataField));
+				svt->attrs[i].svartype = field_svartype;
+				svt->attrs[i].gennum = get_svariable_valid_type_gennum(field_svartype);
+				svt->attrs[i].typmod = attr->atttypmod;
+				svt->attrs[i].isdropped = false;
+				svt->attrs[i].isdropped = true;
+		svt->natts = tupdesc->natts;
+	if (svt->natts == tupdesc->natts)
+			if (svt->relid != attr->attrelid)
+			if (!svt->attrs[i].isdropped)
+				if (svt->attrs[i].typmod != attr->atttypmod)
- * in sessionvars.
+ * in sessionvars. When we replace row value by new value with same type
+ * fingerprint, we can keep field desc data.
  */
 static void
-free_session_variable_value(SVariable svar)
+free_session_variable_value(SVariable svar, bool keep_desc_fields)
 {
 	if (svar->freeval)
 		pfree(DatumGetPointer(svar->value));
@@ -279,6 +938,21 @@ free_session_variable_value(SVariable svar)
 	svar->isnull = true;
 	svar->freeval = false;
 
+	if (!keep_desc_fields)
+	{
+		if (svar->natts > 0)
+		{
+			pfree(svar->attrs);
+			svar->natts = 0;
+		}
+
+		if (svar->tupdesc)
+		{
+			FreeTupleDesc(svar->tupdesc);
+			svar->tupdesc = NULL;
+		}
+	}
+
 	/*
 	 * We can mark this session variable as valid when
 	 * it has not default expression, and when null is
@@ -295,7 +969,7 @@ free_session_variable_value(SVariable svar)
 static void
 remove_session_variable(SVariable svar)
 {
-	free_session_variable_value(svar);
+	free_session_variable_value(svar, false);
 
 	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) is removing from memory",
 				 svar->nsname, svar->name,
@@ -508,9 +1182,10 @@ sync_sessionvars_all()
  * Create the hash table for storing session variables
  */
 static void
-create_sessionvars_hashtable(void)
+create_sessionvars_hashtables(void)
 {
-	HASHCTL		ctl;
+	HASHCTL		vars_ctl;
+	HASHCTL		types_ctl;
 
 	/* set callbacks */
 	if (first_time)
@@ -530,14 +1205,24 @@ create_sessionvars_hashtable(void)

 

@@ -548,31 +1233,56 @@ create_sessionvars_hashtable(void)
  * init_mode is true, when the value of session variable is being initialized
  * by default expression or by null. Only in this moment we can allow to
  * modify immutable variables with default expression.
+ *
+ * This routine have to do successfull change or leave memory without
+ * change.
  */
 static void
 set_session_variable(SVariable svar, Datum value,
 					 bool isnull, Oid typid,
 					 bool init_mode)
 {
-	MemoryContext oldcxt;
+	TupleDesc	tupdesc = NULL;
+	TupleDesc	newtupdesc = NULL;
 	Datum		newval = value;
+	SVariableData _svardata, *_svar;
+	bool		fresh_svar = false;
+
+	Assert(svar && svar->svartype);
+
+	/* We have to be sure so cached data are still valid. */
+	if (svar->svartype->typid == typid)
+	{
+		if (!session_variable_use_valid_type(svar))
+			fresh_svar = true;
+	}
+	else
+		fresh_svar = true;
+
+	if (fresh_svar)
+	{
+		Variable	var;
+
+		initVariable(&var, svar->varid, true);
+		init_session_variable(&_svardata, &var);
+		_svar = &_svardata;
+	}
+	else
+	{
+		/* We can trust the cached entry */
+		Assert(svar->svartype->typid == typid);
+		Assert(svar->gennum == svar->svartype->gennum);
+
+		_svar = svar;
+	}
 
 	/* Don't allow assignment of null to NOT NULL variable */
-	if (isnull && svar->is_not_null)
+	if (isnull && _svar->is_not_null)
 		ereport(ERROR,
 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
 				 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
-						get_namespace_name(get_session_variable_namespace(svar->varid)),
-						get_session_variable_name(svar->varid))));
-
-	if (svar->typid != typid)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATATYPE_MISMATCH),
-				 errmsg("type \"%s\" of assigned value is different than type \"%s\" of session variable \"%s.%s\"",
-						format_type_be(typid),
-						format_type_be(svar->typid),
-						get_namespace_name(get_session_variable_namespace(svar->varid)),
-						get_session_variable_name(svar->varid))));
+						get_namespace_name(get_session_variable_namespace(_svar->varid)),
+						get_session_variable_name(_svar->varid))));
 
 	/*
 	 * Don't allow updating of immutable session variable that has assigned
@@ -581,25 +1291,57 @@ set_session_variable(SVariable svar, Datum value,
 	 * is being initialized.
 	 */
 	if (!init_mode &&
-		(svar->is_immutable && (svar->is_valid || svar->has_defexpr)))
+		(_svar->is_immutable &&
+		 (_svar->is_valid || _svar->has_defexpr)))
 		ereport(ERROR,
 				(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
 				 errmsg("session variable \"%s.%s\" is declared IMMUTABLE",
-						get_namespace_name(get_session_variable_namespace(svar->varid)),
-						get_session_variable_name(svar->varid))));
+						get_namespace_name(get_session_variable_namespace(_svar->varid)),
+						get_session_variable_name(_svar->varid))));
+
+	/* We don't need tupdesc for NULL value */
+	if (!isnull &&
+		 _svar->svartype->is_rowtype && !_svar->tupdesc)
+		tupdesc = lookup_rowtype_tupdesc(_svar->svartype->typid, -1);
 
 	/* copy value to session persistent context */
-	oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
 	if (!isnull)
-		newval = datumCopy(value, svar->typbyval, svar->typlen);
-	MemoryContextSwitchTo(oldcxt);
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
 
-	free_session_variable_value(svar);
+		newval = datumCopy(value,
+						   _svar->svartype->typbyval,
+						   _svar->svartype->typlen);
 
-	svar->value = newval;
-	svar->isnull = isnull;
-	svar->freeval = newval != value;
-	svar->is_valid = true;
+		if (tupdesc)
+		{
+			newtupdesc = palloc(TupleDescSize(tupdesc));
+			TupleDescCopy(newtupdesc, tupdesc);
+		}
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* When we have new svar, we want full clean of old svar */
+	free_session_variable_value(svar, !fresh_svar);
+
+	_svar->value = newval;
+	if (newtupdesc)
+		_svar->tupdesc = newtupdesc;
+
+	_svar->isnull = isnull;
+	_svar->freeval = newval != value;
+	_svar->is_valid = true;
+
+	if (fresh_svar)
+		memcpy(svar, _svar, sizeof(SVariableData));
+
+	if (tupdesc)
+		ReleaseTupleDesc(tupdesc);
+
+	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new value",
+				 svar->nsname, svar->name,
+				 svar->varid);
 }
 
 /*
@@ -621,12 +1363,23 @@ init_session_variable(SVariable svar, Variable *var)
 
 	svar->svartype = get_svariabletype(var->typid);
 	svar->gennum = get_svariable_valid_type_gennum(svar->svartype);
+	svar->relid = svar->svartype->relid;
+
+	svar->natts = svar->svartype->natts;
+	if (svar->natts > 0)
+	{
+		size_t		bytes = svar->natts * sizeof(SVariableTypeDataField);
 
+		svar->attrs = palloc(bytes);
+		memcpy(svar->attrs, svar->svartype->attrs, bytes);
+	}
+	else
+		svar->attrs = NULL;
+
+	svar->value = (Datum) 0;
 	svar->isnull = true;
 	svar->freeval = false;
-	svar->value = (Datum) 0;
 
@@ -650,9 +1403,128 @@ init_session_variable(SVariable svar, Variable *var)
 
+	svar->tupdesc = NULL;
+
 
+/*
+ * Try to convert the composite value to the current expected format. This
+ * routine is called when we know so expected composite format is not identical
+ * with composite format of saved value. If there are difference just in
+ * number (and positions) of dropped fields, we can try to build new
+ * composite value, where dropped fields are replaced by NULLs. Unfortunately
+ * we cannot to use TupleConversionMap infra, because this conversion should
+ * ba based on position (not on position of not dropped fields).
+ */
+static Datum
+get_rowtype_value_in_expected_format(SVariable svar)
+{
+	SVariableType target_svt = svar->svartype;
+	Datum		value = (Datum) 0;
+	int		i;
+
+	Assert(svar->natts > 0 && target_svt->is_rowtype);
+	Assert(svar->relid == target_svt->relid);
+
+	/*
+	 * Before conversion we need to refresh assigned target type.
+	 * We don't update gennum in svariable, so the conversion will
+	 * be forced every time until value update.
+	 */
+	get_svariable_valid_type_gennum(target_svt);
+
+	/* check if we can do this transformation */
+	for (i = 0; i < target_svt->natts; i++)
+	{
+		if (i >= svar->natts)
+			continue;
+
+		if (target_svt->attrs[i].isdropped || svar->attrs[i].isdropped)
+			continue;
+
+		if (svar->attrs[i].svartype->typid != target_svt->attrs[i].svartype->typid ||
+			svar->attrs[i].typmod != target_svt->attrs[i].typmod)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("record type of stored value of \"%s.%s\" does not match expected record type",
+								get_namespace_name(get_session_variable_namespace(svar->varid)),
+								get_session_variable_name(svar->varid)),
+						 errdetail("Expected format of %d field is %s.",
+								   i,
+								   format_type_with_typemod(target_svt->attrs[i].svartype->typid,
+															target_svt->attrs[i].typmod))));
+
+		if (svar->attrs[i].gennum != target_svt->attrs[i].svartype->gennum)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("format of stored value of %d field of \"%s.%s\" session variable can be outdated",
+							i,
+							get_namespace_name(get_session_variable_namespace(svar->varid)),
+							get_session_variable_name(svar->varid))));
+	}
+
+	/* We finish checks. When stored value is null, we can leave */
+	if (!svar->isnull)
+	{
+		Datum	   *invalues;
+		bool	   *inisnull;
+		Datum	   *outvalues;
+		bool	   *outisnull;
+		TupleDesc  outtupdesc;
+		HeapTuple	tuple;
+		HeapTupleHeader td;
+		HeapTupleData tmptup;
+
+		td = DatumGetHeapTupleHeader(svar->value);
+		tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+		tmptup.t_data = td;
+
+		/* returns fresh tupdesc */
+		outtupdesc = lookup_rowtype_tupdesc(target_svt->typid, -1);
+
+		Assert(outtupdesc->natts == target_svt->natts);
+		Assert(svar->tupdesc->natts == svar->natts);
+
+		invalues = palloc(svar->natts * sizeof(Datum));
+		inisnull = palloc(svar->natts * sizeof(bool));
+
+		outvalues = palloc(target_svt->natts * sizeof(Datum));
+		outisnull = palloc(target_svt->natts * sizeof(bool));
+
+		heap_deform_tuple(&tmptup, svar->tupdesc, invalues, inisnull);
+
+		for (i = 0; i < target_svt->natts; i++)
+		{
+			if (i > svar->natts)
+				outisnull[i] = true;
+
+			if (svar->attrs[i].isdropped || target_svt->attrs[i].isdropped)
+				outisnull[i] = true;
+
+			outvalues[i] = invalues[i];
+			outisnull[i] = inisnull[i];
+		}
+
+		tuple = heap_form_tuple(outtupdesc, outvalues, outisnull);
+		value = heap_copy_tuple_as_datum(tuple, outtupdesc);
+
+		heap_freetuple(tuple);
+
+		ReleaseTupleDesc(outtupdesc);
+
+		pfree(invalues);
+		pfree(inisnull);
+		pfree(outvalues);
+		pfree(outisnull);
+	}
+
+	elog(DEBUG1, "returned value of record type's session variable \"%s.%s\" was converted",
+				 svar->nsname, svar->name);
+
+	return value;
+}
+
 /*
  * Search the given session variable in the hash table. If it doesn't
  * exist, then insert it (and calculate defexpr if it exists).
@@ -663,7 +1535,7 @@ init_session_variable(SVariable svar, Variable *var)
  * related session variable until the end of the transaction.
  */
 static SVariable
-prepare_variable_for_reading(Oid varid)
+prepare_variable_for_reading(Oid varid, Datum *value, bool *isnull)
 {
 	SVariable svar;
 	Variable	var;
@@ -672,7 +1544,7 @@ prepare_variable_for_reading(Oid varid)
 	var.oid = InvalidOid;
 
 	if (!sessionvars)
 
 	/* Protect used session variable against drop until transaction end */
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
@@ -684,54 +1556,120 @@ prepare_variable_for_reading(Oid varid)
 										HASH_ENTER, &found);

 
-	if (!found)
 
-		ereport(ERROR,
-						get_namespace_name(get_session_variable_namespace(varid)),
 
-	if (svar->has_defexpr)
-	{

 

-		MemoryContextSwitchTo(oldcxt);
+			MemoryContextSwitchTo(oldcxt);
 
-		FreeExecutorState(estate);
+			FreeExecutorState(estate);
+		}
+		else
+			set_session_variable(svar, (Datum) 0, true, svar->svartype->typid, true);
+	}
+
+	*value = svar->value;
+	*isnull = svar->isnull;
+
+	/*
+	 * Check if stored value has still valid type.
+	 */
+	if (!session_variable_use_valid_type(svar))
+	{
+		/*
+		 * We can try to convert composite value to target type. We don't
+		 * try to save converted value, because an change of target composite
+		 * type can be aborted, and still the life cycle of session variable
+		 * is short.
+		 */
+		if (svar->natts > 0 && svar->svartype->natts > 0 &&
+			svar->relid == svar->svartype->relid)
+		{
+			*value = get_rowtype_value_in_expected_format(svar);
+		}
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("format of stored value of \"%s.%s\" session variable can be outdated",
+							get_namespace_name(get_session_variable_namespace(varid)),
+							get_session_variable_name(varid))));
+		}
+	}
+
+	/*
+	 * Although the value of domain type should be valid (it is
+	 * checked when it is assigned to session variable), we have to
+	 * check related constraints anytime. It can be more expensive
+	 * than in PL/pgSQL. PL/pgSQL forces domain checks when value
+	 * is assigned to the variable or when value is returned from
+	 * function. Fortunately, domain types manage cache of constraints by
+	 * self.
+	 */
+	if (svar->svartype->is_domain)
+	{
+		MemoryContext oldcxt = CurrentMemoryContext;
+
+		/*
+		 * Store domain_check extra in CurTransactionContext. When we are
+		 * in other transaction, the domain_check_extra cache is not valid.
+		 */
+		if (svar->svartype->domain_check_extra_lxid != MyProc->lxid)
+			svar->svartype->domain_check_extra = NULL;
+
+		domain_check(svar->value,
+					 svar->isnull,
+					 svar->svartype->typid,
+					 &svar->svartype->domain_check_extra,
+					 CurTransactionContext);
+
+		svar->svartype->domain_check_extra_lxid = MyProc->lxid;
+
+		MemoryContextSwitchTo(oldcxt);
 	}
-	else
-		set_session_variable(svar, (Datum) 0, true, svar->typid, true);
 
 	return svar;
 }
@@ -755,7 +1693,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid)
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
 
 	if (!sessionvars)
 
 	/* Ensure so all entries in sessionvars hash table are valid */
 	sync_sessionvars_all();
@@ -804,18 +1742,21 @@ SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typ
 Datum
 CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 {
-	SVariable svar;
+	SVariable	svar;
+	Datum		result = (Datum) 0;
 
-	svar = prepare_variable_for_reading(varid);
+	svar = prepare_variable_for_reading(varid, &result, isNull);
 	Assert(svar != NULL && svar->is_valid);
 
-	*isNull = svar->isnull;
-	*typid = svar->typid;
+	*typid = svar->svartype->typid;
 
-	if (!svar->isnull)
-		return datumCopy(svar->value, svar->typbyval, svar->typlen);
+	/* force copy of not null (not yet copy) value */
+	if (!isNull && svar->value == result)
+		result = datumCopy(svar->value,
+						   svar->svartype->typbyval,
+						   svar->svartype->typlen);
 
-	return (Datum) 0;
+	return (Datum) result;
 }
 
 /*
@@ -826,22 +1767,25 @@ CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 Datum
 CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 {
-	SVariable svar;
+	SVariable	svar;
+	Datum		result = (Datum) 0;
 
-	svar = prepare_variable_for_reading(varid);
+	svar = prepare_variable_for_reading(varid, &result, isNull);
 	Assert(svar != NULL && svar->is_valid);

 	*isNull = svar->isnull;
 
-	if (!svar->isnull)
-		return datumCopy(svar->value, svar->typbyval, svar->typlen);
+	if (!*isNull && svar->value == result )
+		result = datumCopy(svar->value,
+						 svar->svartype->typbyval,
+						 svar->svartype->typlen);
 
-	return (Datum) 0;
+	return (Datum) result;
 }
 
 /*
@@ -852,22 +1796,18 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 Datum
 GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 {
-	SVariable svar;
+	SVariable	svar;
+	Datum		result = (Datum) 0;
 
-	svar = prepare_variable_for_reading(varid);
+	svar = prepare_variable_for_reading(varid, &result, isNull);
 	Assert(svar != NULL && svar->is_valid);

-	*isNull = svar->isnull;
-
-	if (svar->isnull)
-		return (Datum) 0;
-
-	return svar->value;
+	return result;
 }
 
 /*
@@ -1070,6 +2010,9 @@ ResetSessionVariables(void)
 	{
 		hash_destroy(sessionvars);
 		sessionvars = NULL;
+
+		hash_destroy(sessionvars_types);
+		sessionvars_types = NULL;
 	}
 
 	/* Release memory allocated by session variables */
@@ -1371,6 +2314,8 @@ pg_debug_show_used_session_variables(PG_FUNCTION_ARGS)
 
 		while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
 		{
+			char	   *name;
+			char	   *nsname;
 			Datum		values[NUM_PG_DEBUG_SHOW_USED_SESSION_VARIABLES_ATTS];
 			bool		nulls[NUM_PG_DEBUG_SHOW_USED_SESSION_VARIABLES_ATTS];
 			HeapTuple	tp;
-- 
2.37.2

From 4d10ad431049beb963d3b121a8fdf89009c2710e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/13] session variables

 src/backend/commands/sessionvariable.c | 1145 +++++++++++++++++++++++-
 29 files changed, 1940 insertions(+), 62 deletions(-)

index e72fd74145..355f62ae0f 100644

 	/* close large objects before lower-level cleanup */
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 6255c2c72f..2b70faa173 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1909,6 +1909,11 @@ find_expr_references_walker(Node *node,
 	{
 		Param	   *param = (Param *) node;
 
index 3094bab82a..d1300e7de3 100644
 
 /*

 
 
+/*
 
 /*

 
 
 	ReleaseSysCache(tup);
 
 	/*
 
 /*

 

 

 
 
-			/*
 
+				/*
 
 /*
 
@@ -350,3 +1345,87 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
+				/* When we see data in catalogue */
+				nsname = get_namespace_name(get_session_variable_namespace(svar->varid));
+				name = get_session_variable_name(svar->varid);
+				 * When session variable was removed from catalogue, but still it
+				 * in memory. The memory was not purged yet.
+				nsname = svar->nsname;
+				name = svar->name;
+			values[1] = PointerGetDatum(cstring_to_text(nsname));
+			values[2] = PointerGetDatum(cstring_to_text(name));
index d0a57c7aae..89e6727893 100644
 
 						/*
index 636794ca6f..ce32fe0c25 100644
@@ -1094,6 +1096,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}

+	/*

 

 
 
index bd3965143d..dca5942b7a 100644
 
index cf9e0a74db..97e1038656 100644
@@ -317,6 +317,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/*
@@ -530,6 +531,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
@@ -685,6 +687,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	 */
 	pull_up_subqueries(root);
 
+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

index aa1b1a76dc..abe43015c5 100644
 
 /* GUC parameters */

 /*

+/*
 
index 8964f73b92..84b5c8732a 100644

+/*

 	/*
 
 /*
 
 	return false;
 }
index 9fbbfb1be5..f687019037 100644
@@ -1731,6 +1731,18 @@ static struct config_bool ConfigureNamesBool[] =
 
index c8ef917ffe..9e06ca7a3b 100644
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -388,6 +389,13 @@ typedef struct ExprEvalStep
 			Oid			paramtype;	/* OID of parameter's datatype */
 		}			param;
 
@@ -824,6 +832,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,

 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
index 978464658d..b24d2bd9f9 100644

 
index 294cfe9c47..5a79a4d591 100644

 /* macro for fetching the Plan associated with a SubPlan node */
@@ -459,6 +461,8 @@ struct PlannerInfo
 	/* true if planning a recursive WITH item */
 	bool		hasRecursion;

 
index 3aa96bb685..29c2266c2b 100644

 typedef struct Param
 
index 1566f435b3..2608735731 100644

 /* GUC parameters */

 
 
-- 
2.37.2

From 200973f36b8b2b6beb5e775566440e78d041e5a2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/13] Catalogue support for session variables

 src/backend/commands/tablecmds.c        |  27 +-
 src/backend/utils/cache/lsyscache.c     |  89 ++++++
 37 files changed, 1971 insertions(+), 20 deletions(-)

index 50f092d7eb..e72fd74145 100644

 

index 17ff617fba..928b4e266e 100644

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
+ * Exported routine for checking a user's access privileges to a variable
+ */
+AclResult
+pg_variable_aclcheck(Oid var_oid, Oid roleid, AclMode mode)
+{
+	if (pg_variable_aclmask(var_oid, roleid, mode, ACLMASK_ANY) != 0)
+		return ACLCHECK_OK;
+	else
+		return ACLCHECK_NO_PRIV;
+}
+
 /*
  * Ownership check for a relation (specified by OID).
  */
@@ -5729,6 +6005,33 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid)
 	return has_privs_of_role(roleid, ownerId);
 }
 
+/*
 
+		case OBJECT_VARIABLE:
index 39768fa22b..6255c2c72f 100644
@@ -65,12 +65,15 @@
@@ -188,7 +191,8 @@ static const Oid object_classes[] = {

@@ -1508,6 +1512,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			DropObjectById(object);
 			break;
 
+		case OCLASS_VARIABLE:
+			RemoveSessionVariable(object->objectId);
+			break;
+
 			/*
 			 * These global object types are not supported here.
 			 */
@@ -2966,6 +2974,9 @@ getObjectClass(const ObjectAddress *object)

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
 
index 798c1a2d1e..8317e7096f 100644
@@ -64,6 +64,7 @@
@@ -633,6 +634,20 @@ static const ObjectPropertyType ObjectProperty[] =
 
 /*
@@ -869,6 +884,10 @@ static const struct object_type_map
 	/* OCLASS_STATISTIC_EXT */
 	{
 		"statistics object", OBJECT_STATISTIC_EXT
+	},
+	/* OCLASS_VARIABLE */
+	{
+		"session variable", OBJECT_VARIABLE
 	}
 };
 
@@ -894,6 +913,7 @@ static ObjectAddress get_object_address_attrdef(ObjectType objtype,
 												bool missing_ok);
@@ -1163,6 +1183,9 @@ get_object_address(ObjectType objtype, Node *object,
@@ -2039,16 +2062,20 @@ get_object_address_defacl(List *object, bool missing_ok)
 
 	/*
@@ -2132,6 +2159,24 @@ textarray_to_strvaluelist(ArrayType *arr)
 	return list;
 }
 
+/*
@@ -2319,6 +2364,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
@@ -2629,6 +2675,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
@@ -3530,6 +3581,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
@@ -3882,6 +3959,16 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
@@ -4658,6 +4745,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 
+		case OCLASS_VARIABLE:
@@ -5765,6 +5856,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
@@ -6008,6 +6103,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index f2f227f887..de34174a92 100644
@@ -47,6 +47,7 @@
@@ -1604,6 +1605,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
index 55219bb097..c39a278758 100644
 
@@ -886,6 +894,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 		case OBJECT_TABLESPACE:
index 635d05405e..8739703c39 100644

 			/*
@@ -2049,6 +2051,8 @@ stringify_grant_objtype(ObjectType objtype)
 			return "TABLESPACE";
 		case OBJECT_TYPE:
 			return "TYPE";
+		case OBJECT_VARIABLE:
+			return "VARIABLE";
@@ -2132,6 +2136,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 
 			/*
index 9be04c8a1e..f711e749b3 100644
@@ -47,6 +47,7 @@
 #include "catalog/pg_tablespace.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
@@ -6435,6 +6436,8 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
  * Eventually, we'd like to propagate the check or rewrite operation
  * into such tables, but for now, just error out if we find any.
  *
+ * Check if the type "typeOid" is used as type of some session variable too.
+ *
  * Caller should provide either the associated relation of a rowtype,
  * or a type name (not both) for use in the error message, if any.
  *
@@ -6496,9 +6499,30 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
+		/* Don't allow change of type usedby session's variable */
+		if (pg_depend->classid == VariableRelationId)
+		{
+			Oid		varid = pg_depend->objid;
+
+			if (origTypeName)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it",
+								origTypeName,
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+		}
+
 		/* Else, ignore dependees that aren't user columns of relations */
 		/* (we assume system columns are never of interesting types) */
-		if (pg_depend->classid != RelationRelationId ||
+		else if (pg_depend->classid != RelationRelationId ||
 			pg_depend->objsubid <= 0)
 			continue;
 
@@ -12682,6 +12706,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_PUBLICATION_REL:
 			case OCLASS_SUBSCRIPTION:
 			case OCLASS_TRANSFORM:
+			case OCLASS_VARIABLE:
 
 				/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)
index fabb5f7207..aa1b1a76dc 100644
 
index 827989f379..005bc0400f 100644
@@ -2621,6 +2621,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)

 
-
 /*
  * convert_any_priv_string: recognize privilege strings for has_foo_privilege
  *
@@ -1696,6 +1710,10 @@ convert_aclright_to_string(int aclright)
 			return "SET";
 		case ACL_ALTER_SYSTEM:
 			return "ALTER SYSTEM";
+		case ACL_READ:
+			return "READ";
+		case ACL_WRITE:
+			return "WRITE";
 		default:
 			elog(ERROR, "unrecognized aclright: %d", aclright);
 			return NULL;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a16a63f495..c73d491eca 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -36,6 +36,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"
@@ -3679,3 +3680,91 @@ get_subscription_name(Oid subid, bool missing_ok)
 
 	return subname;
 }
+

 	/*
index 729c4c46c0..e7b74e3de3 100644
@@ -127,10 +127,11 @@ typedef enum ObjectClass

 

 
 
index b376031856..978464658d 100644
 
@@ -3321,6 +3324,25 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+

index 9e94f44c5f..6c1be5e9d4 100644
index 48f7d72add..9e19ad4321 100644

 /*
 
+extern AclResult pg_variable_aclcheck(Oid var_oid, Oid roleid, AclMode mode);

diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 50f0288305..8e2b733bb2 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -132,6 +132,7 @@ extern char get_func_prokind(Oid funcid);
 extern bool get_func_leakproof(Oid funcid);
 extern RegProcedure get_func_support(Oid funcid);
 extern Oid	get_relname_relid(const char *relname, Oid relnamespace);
+extern Oid	get_varname_varid(const char *varname, Oid varnamespace);
 extern char *get_rel_name(Oid relid);
 extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
@@ -203,6 +204,13 @@ 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);

 
2.37.2

From a9246c6b03cc1940f2f4e6184cbc1092be818974 Mon Sep 17 00:00:00 2001

index f6b740df0a..b3bee39457 100644
@@ -3667,8 +3667,8 @@ errorMissingColumn(ParseState *pstate,
@@ -3687,8 +3687,8 @@ errorMissingColumn(ParseState *pstate,

 
 select a from atacc1;

index 2ed2e542a4..d36caf367e 100644
 
@@ -2528,7 +2528,7 @@ select * from t1 left join t2 on (t1.a = t2.a);
 
@@ -4987,19 +4987,19 @@ select * from
@@ -5011,7 +5011,7 @@ select atts.relid::regclass, s.* from pg_stats s join
@@ -6033,7 +6033,7 @@ lateral (select * from int8_tbl t1,
 
@@ -6043,7 +6043,7 @@ LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
@@ -6078,7 +6078,7 @@ LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
@@ -6098,7 +6098,7 @@ update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1)
 
index b16ad6b8f2..a2f04e9807 100644
 
 -- should fail
 LET v1.x = 10;
 
2.37.2

From 67cb5f51823058307133dad27cae219b4a755fc1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Aug 2022 15:52:22 +0200
Subject: [PATCH 11/13] fix

---
 .../regress/expected/session_variables.out    | 15 +++++++++++++
 src/test/regress/sql/session_variables.sql    | 21 +++++++++++++++++++
 2 files changed, 36 insertions(+)

diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index 8c4fafe5e1..b16ad6b8f2 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -1133,3 +1133,18 @@ SELECT public.svar2;
 DROP VARIABLE public.svar;
 DROP VARIABLE public.svar2;
 DROP TYPE public.svar_test_type;
+-- The composite type cannot be changed when it is used
+CREATE TYPE public.svar_type AS (a int, b int);
+CREATE VARIABLE public.svar AS public.svar_type;
+-- should to fail
+ALTER TYPE public.svar_type ALTER ATTRIBUTE b TYPE numeric;
+ERROR:  cannot alter type "svar_type" because session variable "public.svar" uses it
+DROP VARIABLE public.svar;
+CREATE TYPE public.svar_type2 AS (a int, b int, c public.svar_type);
+CREATE VARIABLE public.svar AS public.svar_type2;
+-- should to fail
+ALTER TYPE public.svar_type ALTER ATTRIBUTE b TYPE numeric;
+ERROR:  cannot alter type "svar_type" because session variable "public.svar" uses it
+DROP VARIABLE public.svar;
+DROP TYPE public.svar_type2;
+DROP TYPE public.svar_type;
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index e74d8bfd6a..e2e463b0e7 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -862,3 +862,24 @@ SELECT public.svar2;
 DROP VARIABLE public.svar;
 DROP VARIABLE public.svar2;
 DROP TYPE public.svar_test_type;
+
+-- The composite type cannot be changed when it is used
+CREATE TYPE public.svar_type AS (a int, b int);
+
+CREATE VARIABLE public.svar AS public.svar_type;
+
+-- should to fail
+ALTER TYPE public.svar_type ALTER ATTRIBUTE b TYPE numeric;
+
+DROP VARIABLE public.svar;
+
+CREATE TYPE public.svar_type2 AS (a int, b int, c public.svar_type);
+
+CREATE VARIABLE public.svar AS public.svar_type2;
+
+-- should to fail
+ALTER TYPE public.svar_type ALTER ATTRIBUTE b TYPE numeric;
+
+DROP VARIABLE public.svar;
+DROP TYPE public.svar_type2;
+DROP TYPE public.svar_type;
-- 
2.37.2

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 19 Aug 2022 17:29:45 +0200

> so the type of any field of used composite type cannot be changed

update - fix cpp check

> Pavel
From 8eea0ebefbd0886a3646fb9e2871d309286422c3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 13/13] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
 
+     <varlistentry>

From 468d9fe82ddf7edf1de1cdae689faf3daec4b8fb Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 19 Aug 2022 15:52:22 +0200
Subject: [PATCH 11/13] fix

---

From 1a123c2d3e7c56ffd2e724b279553fd893742f1a Mon Sep 17 00:00:00 2001

 

 select a from atacc1;

 

 
 
 -- should fail
 LET v1.x = 10;

From 588f52771ef440e58dfaced408c1edf7d0f431d8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 10/13] Regress tests for session variables

 
 
 # ----------

From 2a4b15ea1383f31c11e2ec39bfa263bafc9c8d23 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 09/13] typedefs

---

From 329a728b547a885fb0caec51b49bc965d4a0b3ae Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 08/13] Possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 053fbc146919554e4bb1e2a72c29b8dc881a6dc9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 06/13] DISCARD VARIABLES command

---

 

From e14d133afe66aec4edafc74719dd640de4868fb6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 07/13] Enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From 4365303ef17a0a030c55a1e915984a850b45ce54 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 05/13] Support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 9fb11fcf0da42af9ef5b33d2e66af835bc410916 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 04/13] LET command

index ffa6052146..70361d66b8 100644
 
+/*

 
 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 	NodeTag		type;

 

 /* ----------------
-- 
2.37.2

From 0878f34bda11c63c40b1df48a3b2d1158eff0e97 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/13] typecheck - check of consistency of format of stored
 value.

index 204582ecbc..ffa6052146 100644
 
 /*

 	SVariableType svartype;
 	int64		gennum;
 
+	/*

-	uint32		hashvalue;

 

 
 
+/*
 
+	if (!keep_desc_fields)
+	{
+		if (svar->natts > 0)
+		{
+			pfree(svar->attrs);
+			svar->natts = 0;
+		}
+
+		if (svar->tupdesc)
+		{

 	/* set callbacks */
 	if (first_time)

 

 
 
 	/*

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

 
 
+	svar->tupdesc = NULL;
+
 
+/*
 
 	if (!sessionvars)

 
 
-	if (!found)

-	if (svar->has_defexpr)
-	{

 

 
 
 	return svar;
 }
 
 	if (!sessionvars)

 
 
-	if (!svar->isnull)
 
-	return (Datum) 0;
+	return (Datum) result;
 }
 
 /*

 
 	*isNull = svar->isnull;
 
-	if (!svar->isnull)
 
-	return (Datum) 0;
+	return (Datum) result;
 }
 
 /*

 
 
 /*

From e11b88c1585d80d876e6f3f660a9e41e0c6d0493 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/13] session variables

 

index 3094bab82a..204582ecbc 100644
 
 /*

 
 
+/*
+	svar->name = pstrdup(var->varname);
+	svar->nsname = get_namespace_name(var->varnamespace);
 
 /*

 
 
 	ReleaseSysCache(tup);
 
 	/*
 
 /*

 

 

 
 
-			/*
 
+				/*
 
 /*

 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*

+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

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

 

 

 

 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.37.2

From 08f00fbc8b8a78e69c4637802e88728babce28a0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/13] Catalogue support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index 0000000000..e8655d71ec
+	var->varname = pstrdup(NameStr(varform->varname));
+	var->varnamespace = varform->varnamespace;

 
 			/*
 
 			/*

 
 				/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 
-
 /*
 
 	return subname;
 }
+

 	/*

 

 
 
index 0000000000..faa8310620

+

 
 /*

---

From: Alvaro Herrera <[email-masked]>
Date: Fri, 19 Aug 2022 22:53:52 +0200

>  						 state->rfirst->eref->aliasname, closestfirst) :

This is in your patch 12.  I wonder -- if relname is not null, then
surely this is a column and not a variable, right?  So only the second
errmsg() here should be changed, and the first one should remain as in
the original.

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/

---

From: Erik Rijkers <[email-masked]>
Date: Sat, 20 Aug 2022 15:32:20 +0200

>> so the type of any field of used composite type cannot be changed

v20220819-2-0001-Catalogue-support-for-session-variables.patch
v20220819-2-0002-session-variables.patch
v20220819-2-0003-typecheck-check-of-consistency-of-format-of-stored-v.patch
v20220819-2-0004-LET-command.patch
v20220819-2-0005-Support-of-LET-command-in-PLpgSQL.patch
v20220819-2-0006-DISCARD-VARIABLES-command.patch
v20220819-2-0007-Enhancing-psql-for-session-variables.patch
v20220819-2-0008-Possibility-to-dump-session-variables-by-pg_dump.patch
v20220819-2-0009-typedefs.patch
v20220819-2-0010-Regress-tests-for-session-variables.patch
v20220819-2-0011-fix.patch
v20220819-2-0012-This-patch-changes-error-message-column-doesn-t-exis.patch
v20220819-2-0013-documentation.patch

make check  fails as a result of the errors in the attached 
session_variables.out.

Erik

---

From: Erik Rijkers <[email-masked]>
Date: Sat, 20 Aug 2022 15:36:42 +0200

> make check  fails as a result of the errors in the attached 

Sorry, that should have been this diffs file, of course (attached).

Erik

diff -U3 /home/aardvark/pg_stuff/pg_sandbox/pgsql.schema_variables/src/test/regress/expected/session_variables.out /home/aardvark/pg_stuff/pg_sandbox/pgsql.schema_variables/src/test/regress/results/session_variables.out
--- /home/aardvark/pg_stuff/pg_sandbox/pgsql.schema_variables/src/test/regress/expected/session_variables.out	2022-08-20 15:03:24.767103554 +0200
+++ /home/aardvark/pg_stuff/pg_sandbox/pgsql.schema_variables/src/test/regress/results/session_variables.out	2022-08-20 15:10:47.679172066 +0200
@@ -990,9 +990,9 @@
 ALTER TYPE public.svar_test_type ADD ATTRIBUTE c int;
 -- should to fail too (different type, different generation number);
 SELECT public.svar;
-   svar   
-----------
- (10,20,)
+   svar    
+-----------
+ (10,20,0)
 (1 row)
 
 LET public.svar = ROW(10,20,30);
@@ -1111,9 +1111,9 @@
 LET public.svar = (10, 20);
 ALTER TYPE public.svar_test_type ADD ATTRIBUTE c int;
 SELECT public.svar;
-   svar   
-----------
- (10,20,)
+    svar    
+------------
+ (10,20,16)
 (1 row)
 
 LET public.svar2 = (10, 20, 30);

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 20 Aug 2022 15:41:14 +0200

> Sorry, that should have been this diffs file, of course (attached).

It looks like some problem with not well initialized memory, but I have no
idea how it is possible. What are your configure options?

---

From: Erik Rijkers <[email-masked]>
Date: Sat, 20 Aug 2022 15:55:07 +0200

> idea how it is possible. What are your configure options?

I compiled both assert-enable and 'normal', and I only just noticed that 
the assert-enable one did pass tests normally.

Below is the config that produced the failing tests:

./configure 
--prefix=/home/aardvark/pg_stuff/pg_installations/pgsql.schema_variables 
--bindir=/home/aardvark/pg_stuff/pg_installations/pgsql.schema_variables/bin.fast 
--libdir=/home/aardvark/pg_stuff/pg_installations/pgsql.schema_variables/lib.fast 
--with-pgport=6986 --quiet --enable-depend --with-openssl --with-perl 
--with-libxml --with-libxslt --with-zlib  --enable-tap-tests 
--with-extra-version=_0820_schema_variables_1509    --with-lz4  --with-icu

(debian 9, gcc 12.2.0)

---

From: Julien Rouhaud <[email-masked]>
Date: Sat, 20 Aug 2022 22:35:05 +0800

On Sat, Aug 20, 2022 at 03:55:07PM +0200, Erik Rijkers wrote:
> I compiled both assert-enable and 'normal', and I only just noticed that the
> assert-enable one did pass tests normally.
>
>
> --prefix=/home/aardvark/pg_stuff/pg_installations/pgsql.schema_variables --bindir=/home/aardvark/pg_stuff/pg_installations/pgsql.schema_variables/bin.fast --libdir=/home/aardvark/pg_stuff/pg_installations/pgsql.schema_variables/lib.fast

I also tried locally (didn't look at the patch yet), with debug/assert enabled,
and had similar error:

diff -dU10 /Users/rjuju/git/postgresql/src/test/regress/expected/session_variables.out /Users/rjuju/git/pg/pgmaster_debug/src/test/regress/results/session_variables.out
--- /Users/rjuju/git/postgresql/src/test/regress/expected/session_variables.out	2022-08-20 22:25:17.000000000 +0800
+++ /Users/rjuju/git/pg/pgmaster_debug/src/test/regress/results/session_variables.out	2022-08-20 22:30:50.000000000 +0800
@@ -983,23 +983,23 @@
 -- should to fail
 SELECT public.svar;
   svar
 ---------
  (10,20)
 (1 row)

+ (10,20,2139062142)
 (1 row)

@@ -1104,31 +1104,31 @@
 (1 row)

 DROP VARIABLE public.svar;
 DROP TYPE public.svar_test_type;
 CREATE TYPE public.svar_test_type AS (a int, b int);
 CREATE VARIABLE public.svar AS public.svar_test_type;
 CREATE VARIABLE public.svar2 AS public.svar_test_type;
 LET public.svar = (10, 20);
 ALTER TYPE public.svar_test_type ADD ATTRIBUTE c int;
 SELECT public.svar;
-   svar
-----------
- (10,20,)
+    svar
+------------
+ (10,20,16)
 (1 row)

 LET public.svar2 = (10, 20, 30);
 ALTER TYPE public.svar_test_type DROP ATTRIBUTE b;
 SELECT public.svar;
- svar
--------
- (10,)
+  svar
+---------
+ (10,16)
 (1 row)

 SELECT public.svar2;
   svar2
 ---------
  (10,30)
 (1 row)

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 20 Aug 2022 20:09:20 +0200

Hi

so 20. 8. 2022 v 16:35 odesílatel Julien Rouhaud <[email-masked]>
napsal:

> diff -dU10
> /Users/rjuju/git/postgresql/src/test/regress/expected/session_variables.out
> /Users/rjuju/git/pg/pgmaster_debug/src/test/regress/results/session_variables.out
> ---
> /Users/rjuju/git/postgresql/src/test/regress/expected/session_variables.out
> 2022-08-20 22:25:17.000000000 +0800
> +++
> /Users/rjuju/git/pg/pgmaster_debug/src/test/regress/results/session_variables.out
>  2022-08-20 22:30:50.000000000 +0800
> @@ -983,23 +983,23 @@
>  -- should to fail
>  SELECT public.svar;
>    svar
>  ---------
>   (10,20)
>  (1 row)
>

I hope so I found this error. It should be fixed

Regards

Pavel
From d4959dfc06df877379e8a1cf6dfe01e9b7d080a6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 12/12] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
 
+     <varlistentry>

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

The error message will be more correct. Today, missing PL/pgSQL variable can be reported. The change has
impact on lot of regress tests not related to session variables, and then it is distributed as separate patch
---
 src/backend/parser/parse_relation.c           |  4 +-
 src/backend/parser/parse_target.c             |  4 +-
 src/pl/plpgsql/src/expected/plpgsql_array.out |  2 +-
 .../plpgsql/src/expected/plpgsql_record.out   |  4 +-
 src/pl/tcl/expected/pltcl_queries.out         | 12 +++---
 src/test/regress/expected/alter_table.out     | 38 +++++++++----------
 src/test/regress/expected/copy2.out           |  2 +-
 src/test/regress/expected/errors.out          |  8 ++--
 src/test/regress/expected/generated.out       |  4 +-
 src/test/regress/expected/insert_conflict.out |  2 +-
 src/test/regress/expected/join.out            | 10 ++---
 src/test/regress/expected/plpgsql.out         | 12 +++---
 src/test/regress/expected/rules.out           |  4 +-
 .../regress/expected/session_variables.out    |  2 +-
 src/test/regress/expected/transactions.out    |  4 +-
 src/test/regress/expected/union.out           |  2 +-
 16 files changed, 57 insertions(+), 57 deletions(-)

index f6b740df0a..6aed2c5438 100644
@@ -3668,7 +3668,7 @@ errorMissingColumn(ParseState *pstate,
 				 errmsg("column %s.%s does not exist", relname, colname) :
@@ -3688,7 +3688,7 @@ errorMissingColumn(ParseState *pstate,
 				 errmsg("column %s.%s does not exist", relname, colname) :
 
index d63f4f1cba..927bbd3e58 100644
 
@@ -1309,7 +1309,7 @@ select atacc1.* from atacc1;
 (1 row)
 
 select a from atacc1;
@@ -1323,15 +1323,15 @@ select b,c,d from atacc1;
 (1 row)
 
@@ -1339,11 +1339,11 @@ ERROR:  column atacc1.........pg.dropped.1........ does not exist
 
index 66d8633e3e..1ad53ec761 100644
@@ -245,7 +245,7 @@ LINE 1: ...y) do update set fruit = excluded.fruit RETURNING excluded.f...
index 2ed2e542a4..dbeeafa9c3 100644
@@ -4999,7 +4999,7 @@ LINE 1: select t2.uunique1 from

 
 -- should fail
 LET v1.x = 10;

From 437618496dfbf97a8f0eed49e3805f56f78a49e7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 10/12] Regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 src/test/regress/expected/misc_sanity.out     |    4 +-
 .../regress/expected/session_variables.out    | 1150 +++++++++++++++++
 src/test/regress/parallel_schedule            |    2 +-
 src/test/regress/sql/session_variables.sql    |  885 +++++++++++++
 4 files changed, 2039 insertions(+), 2 deletions(-)
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

index 0000000000..b16ad6b8f2
@@ -0,0 +1,1150 @@
 
 # ----------
index 0000000000..e2e463b0e7
@@ -0,0 +1,885 @@

From ba9729af1b2a07466f7d2bce808babcd50e59c78 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 08/12] Possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 28ee92857153c1dfd04e1f17dfe57643b7ac8221 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 09/12] typedefs

---

From ef557d1dd482759eee051abefeed52b14f82ef27 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 07/12] Enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From 3c16c5f98bb729aa3198d2e24bf7cb9c084fd2d2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 06/12] DISCARD VARIABLES command

---

 

From f19bda864c8a8a9ccfd35a341205279c9ed977b1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 04/12] LET command

index 0960afe4f9..49bf7eef57 100644
@@ -2041,6 +2041,85 @@ ResetSessionVariables(void)
 
+/*

 
 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 	NodeTag		type;

 

 /* ----------------
-- 
2.37.2

From 292e6f83715155a0ec9a21d661177d379060b09c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 05/12] Support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 2c2a1d28dfcb59dca8b0f20e7c6c1ac0e9f6c109 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/12] typecheck - check of consistency of format of stored
 value.

Session variables holds a own evidence of fingerprints for any
used type. When some possible not compatible change is detected
(the version of extension is changed), the related stored type's
generation number is increased. The copy of genaration number is
stored in session variable data. Before any read of session variable,
the stored generation number is compared with current type generation
number. When it is different, the read is canceled.
---
 src/backend/commands/sessionvariable.c | 1154 +++++++++++++++++++++---
 1 file changed, 1051 insertions(+), 103 deletions(-)

index 204582ecbc..0960afe4f9 100644
 
 /*

 	SVariableType svartype;
 	int64		gennum;
 
+	/*

-	uint32		hashvalue;

 

 
 
+/*
 
+	if (!keep_desc_fields)
+	{
+		if (svar->natts > 0)
+		{
+			pfree(svar->attrs);
+			svar->natts = 0;
+		}
+
+		if (svar->tupdesc)
+		{

 	/* set callbacks */
 	if (first_time)

 

 
 
 	/*

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

 
@@ -650,9 +1403,131 @@ init_session_variable(SVariable svar, Variable *var)
 
+	svar->tupdesc = NULL;
+
 
+/*
+			else if (svar->attrs[i].isdropped || target_svt->attrs[i].isdropped)
@@ -663,7 +1538,7 @@ init_session_variable(SVariable svar, Variable *var)
@@ -672,7 +1547,7 @@ prepare_variable_for_reading(Oid varid)
 	var.oid = InvalidOid;
 
 	if (!sessionvars)
 
 	/* Protect used session variable against drop until transaction end */
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
@@ -684,54 +1559,120 @@ prepare_variable_for_reading(Oid varid)
 										HASH_ENTER, &found);

 
-	if (!found)

-	if (svar->has_defexpr)
-	{

 

 
 
 	return svar;
 }
@@ -755,7 +1696,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid)
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
 
 	if (!sessionvars)
 
 	/* Ensure so all entries in sessionvars hash table are valid */
 	sync_sessionvars_all();
@@ -804,18 +1745,21 @@ SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typ
 Datum
 CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 {
-	SVariable svar;
+	SVariable	svar;
+	Datum		result = (Datum) 0;

 
-	if (!svar->isnull)
 
-	return (Datum) 0;
+	return (Datum) result;
 }
 
 /*
@@ -826,22 +1770,25 @@ CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 Datum
 CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 {
-	SVariable svar;
+	SVariable	svar;
+	Datum		result = (Datum) 0;

 
 	*isNull = svar->isnull;
 
-	if (!svar->isnull)
 
-	return (Datum) 0;
+	return (Datum) result;
 }
 
 /*
@@ -852,22 +1799,18 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 Datum
 GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 {
-	SVariable svar;
+	SVariable	svar;
+	Datum		result = (Datum) 0;

 
 
 /*
@@ -1070,6 +2013,9 @@ ResetSessionVariables(void)
 	{
 		hash_destroy(sessionvars);
 		sessionvars = NULL;
+
+		hash_destroy(sessionvars_types);
+		sessionvars_types = NULL;
 	}
 
 	/* Release memory allocated by session variables */
@@ -1371,6 +2317,8 @@ pg_debug_show_used_session_variables(PG_FUNCTION_ARGS)

From bfb86896df3b6da65fb51828a91d81f30ab9f3df Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/12] session variables

 

 
 /*

 
 
+/*
 
 /*

 
 
 	ReleaseSysCache(tup);
 
 	/*
 
 /*

 

 

 
 
-			/*
 
+				/*
 
 /*

 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*

+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

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

 

 

 

 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.37.2

From e6259695dd96ba821f4d429300a84d8f02212814 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/12] Catalogue support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*

 
 				/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 
-
 /*
 
 	return subname;
 }
+

 	/*

 

 

 
+

 
 /*

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 20 Aug 2022 20:10:39 +0200

> errmsg() here should be changed, and the first one should remain as in

Yes, it should work. I changed it in today patch

Thank you for tip

Regards

Pavel

---

From: Erik Rijkers <[email-masked]>
Date: Sat, 20 Aug 2022 20:44:49 +0200

Op 20-08-2022 om 20:09 schreef Pavel Stehule:
>  > [patches v20220820-1-0001 -> 0012]

I'm afraid it still gives the same errors during  'make check', and 
again only errors when compiling  without  --enable-cassert

Thanks,

Erik

diff -U3 /home/aardvark/pg_stuff/pg_sandbox/pgsql.schema_variables/src/test/regress/expected/session_variables.out /home/aardvark/pg_stuff/pg_sandbox/pgsql.schema_variables/src/test/regress/results/session_variables.out
--- /home/aardvark/pg_stuff/pg_sandbox/pgsql.schema_variables/src/test/regress/expected/session_variables.out	2022-08-20 20:12:34.558069463 +0200
+++ /home/aardvark/pg_stuff/pg_sandbox/pgsql.schema_variables/src/test/regress/results/session_variables.out	2022-08-20 20:21:38.028404785 +0200
@@ -990,9 +990,9 @@
 ALTER TYPE public.svar_test_type ADD ATTRIBUTE c int;
 -- should to fail too (different type, different generation number);
 SELECT public.svar;
-   svar   
-----------
- (10,20,)
+   svar    
+-----------
+ (10,20,0)
 (1 row)
 
@@ -1111,17 +1111,17 @@

 SELECT public.svar2;

---

From: Julien Rouhaud <[email-masked]>
Date: Sun, 21 Aug 2022 12:36:21 +0800

On Sat, Aug 20, 2022 at 08:44:49PM +0200, Erik Rijkers wrote:
> I'm afraid it still gives the same errors during  'make check', and again
> only errors when compiling  without  --enable-cassert

It still fails for me for both --enable-cassert and --disable-cassert, with a
different number of errors though.

The cfbot is green, but it's unclear to me which version was applied on the
last run.  AFAICS there's no log available for the branch creation if it
succeeds.

--enable-cassert:

--disable-cassert:

+ (10,20,32)
 (1 row)

@@ -1104,31 +1104,31 @@
 (1 row)

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 21 Aug 2022 07:51:07 +0200

>  ALTER TYPE public.svar_test_type ADD ATTRIBUTE c int;

I got the same result, when I did build without assertions, so I can debug
it now.

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 21 Aug 2022 09:54:03 +0200

should be fixed now

Thank you for check

Pavel
From db74b51b46825a95b5b26b9589383ba2397ff62a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 12/12] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+    <varlistentry>
 
+     <varlistentry>

From 807632f82d4f0c81ae75c7fca7b6c373f3dc3322 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 10/12] Regress tests for session variables

 
 
 # ----------

From 17abe4f8ed7bac0a7e0e1230ce6619f601233222 Mon Sep 17 00:00:00 2001

 

 select a from atacc1;

 

 -- should fail
 LET v1.x = 10;

From eb5a061b05bf55161730665552be175b4df0f15c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 09/12] typedefs

---

From f536d8897feb843cc0db1d94e0c0f888142c2af3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 08/12] Possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
index 2c68915732..c2f7949461 100644
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 071616fea6f8876837f8de4f7990266347eed15c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 07/12] Enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From bbf771bf8ec061f21a1300cbc4efa36fb62c1165 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 06/12] DISCARD VARIABLES command

---

 

From 7a5ff4cf32bb9036400f3ab56f8a7944718d39f3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 05/12] Support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 0c4d3e9fc16e6e5c7e5ac15f92ec605f467e9bf0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 04/12] LET command

index 3da6cda211..9b4f9482a4 100644
 
+/*

 
 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 	NodeTag		type;

 

 /* ----------------
-- 
2.37.2

From 175c6db7235c0e435fb325f27338d0778250e38a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/12] typecheck - check of consistency of format of stored
 value.

index 204582ecbc..3da6cda211 100644
 
 /*

 	SVariableType svartype;
 	int64		gennum;
 
+	/*

-	uint32		hashvalue;

 

 
 
+/*
 
+	if (!keep_desc_fields)
+	{
+		if (svar->natts > 0)
+		{
+			pfree(svar->attrs);
+			svar->natts = 0;
+		}
+
+		if (svar->tupdesc)
+		{

 	/* set callbacks */
 	if (first_time)

 

 
 
 	/*

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

 
 
+	svar->tupdesc = NULL;
+
 
+/*
 
 	if (!sessionvars)

 
 
-	if (!found)

-	if (svar->has_defexpr)
-	{

 

 
 
 	return svar;
 }
 
 	if (!sessionvars)

 
 
-	if (!svar->isnull)
 
-	return (Datum) 0;
+	return (Datum) result;
 }
 
 /*

 
 	*isNull = svar->isnull;
 
-	if (!svar->isnull)
 
-	return (Datum) 0;
+	return (Datum) result;
 }
 
 /*

 
 
 /*

From 7812d11bd149d2541e0c43e94f6587568a24139d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/12] session variables

 

 
 /*

 
 
+/*
 
 /*

 
 
 	ReleaseSysCache(tup);
 
 	/*
 
 /*

 

 

 
 
-			/*
 
+				/*
 
 /*

 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*

+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

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

 

 

 

 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.37.2

From cd2518a1b41f2211e801d785066461dc5cecd703 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/12] Catalogue support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*

 
 				/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 
-
 /*
 
 	return subname;
 }
+

 	/*

 

 

 
+

 
 /*

---

From: Erik Rijkers <[email-masked]>
Date: Sun, 21 Aug 2022 10:15:11 +0200

> should be fixed now> 

Yep, all tests OK now.
Thanks!

Erik

---

From: Julien Rouhaud <[email-masked]>
Date: Mon, 22 Aug 2022 15:33:48 +0800

Hi Pavel,

> should be fixed now

I started reviewing the patchset, beginning with 0001 (at least the parts that
don't substantially change later) and have a few comments.

- you define new AclMode READ and WRITE.  Those bits are precious and I don't
  think it's ok to consume 2 bits for session variables, especially since those
  are the last two bits available since the recent GUC access control patch
  (ACL_SET and ACL_ALTER_SYSTEM).  Maybe we could existing INSERT and UPDATE
  privileges instead, like it's done for sequences?

- make check and make-check-world don't pass with this test only.  Given that
  the split is mostly done to ease review and probably not intended to be
  committed this way, we probably shouldn't spend efforts to clean up the split
  apart from making sure that each patch compiles cleanly on its own.  But in
  this case it brought my attention to misc_sanity.sql test.  Looking at patch
  0010, I see:

This is the test for relations with varlena columns without TOAST table.  I
don't think that's correct to add those exceptions, and there should be a TOAST
table declared for pg_variable too, as noted in the comment above that query.

- nitpicking: s/catalogue/catalog/

Some other comments on other patches while testing things around:

- For sessionvariable.c (in 0002), I see that there are still all the comments
  and code about checking type validity based on a generation number and other
  heuristics.  I still fail to understand why this is needed at all as the
  stored datum should remain compatible as long as we prevent the few
  incompatible DDL that are also prevented when there's a relation dependency.
  As an example, I try to quickly disable all that code with the following:

diff --git a/src/backend/commands/sessionvariable.c b/src/backend/commands/sessionvariable.c
index 9b4f9482a4..7c8808dc46 100644
--- a/src/backend/commands/sessionvariable.c
+++ b/src/backend/commands/sessionvariable.c
@@ -794,6 +794,8 @@ svartype_verify_composite_fast(SVariableType svt)
 static int64
 get_svariable_valid_type_gennum(SVariableType svt)
 {
+   return 1;
+
    HeapTuple   tuple;
    bool        fast_check = true;

@@ -905,6 +907,8 @@ get_svariabletype(Oid typid)
 static bool
 session_variable_use_valid_type(SVariable svar)
 {
+   return true;
+
    Assert(svar);
    Assert(svar->svartype);

And session_variable.sql regression test still works just fine.  Am I missing
something?

While at it, the initial comment should probably say "free local memory" rather
than "purge memory".

- doc are missing for GRANT/REVOKE ... ON ALL VARIABLES

- plpgsql.sgml:
+   <sect3>

I don't think it's ok to use "constant" as an alias for immutable session
variable as immutable session variable can actually be changed.

Similarly, in catalogs.sgml:

I think there should be a note and a link to the corresponding part in
create_variable.sgml to explain what exactly is an immutable variable, as the
implemented behavior (for nullable immutable variable) is somewhat unexpected.

- other nitpicking: pg_variable and struct Variable seems a bit inconsistent.
  For instance one uses vartype and vartypmod and the other typid and typmod,
  while both use varname and varnamespace.  I think we should avoid discrepancy
  here.

Also, there's a sessionvariable.c and a session_variable.h.  Let's use
session_variable.[ch], as it seems more readable?

-typedef patch: missing SVariableTypeData, some commits need a pgindent, e.g:

(and multiple others)

---

From: Justin Pryzby <[email-masked]>
Date: Mon, 22 Aug 2022 09:05:43 -0500

> +INSERT INTO svar_test SELECT * FROM generate_series(1,1000000);

When I looked at this, I noticed this huge table.

I don't think you should create such a large table just for this.

To exercise parallel workers with a smaller table, decrease
min_parallel_table_scan_size and others as done in other regression tests.

-- 
Justin

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 22 Aug 2022 20:43:39 +0200

> min_parallel_table_scan_size and others as done in other regression tests.
I fixed it.

Thank you for tip

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 22 Aug 2022 21:13:39 +0200

>   privileges instead, like it's done for sequences?
I have not a strong opinion about it.  AclMode is uint32 - so I think there
are still 15bites reserved. I think so UPDATE and SELECT rights can work,
but maybe it is better to use separate rights WRITE, READ to be stronger
signalized so the variable is not the relation. On other hand large objects
use ACL_UPDATE, ACL_SELECT too, and it works. So I am neutral in this
question. Has somebody here some opinion on this point? If not I'll modify
the patch like Julien proposes.

Regards

Pavel

---

From: Julien Rouhaud <[email-masked]>
Date: Tue, 23 Aug 2022 09:57:37 +0800

> the patch like Julien proposes.

Actually no, because AclMode is also used to store the grant option part.  The
comment before AclMode warns about it:

 * The present representation of AclItem limits us to 16 distinct rights,
 * even though AclMode is defined as uint32.  See utils/acl.h.

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 23 Aug 2022 11:29:29 +0200

út 23. 8. 2022 v 3:57 odesílatel Julien Rouhaud <[email-masked]> napsal:

> > >   think it's ok to consume 2 bits for session variables, especially
> since
> > > those

I missed this. I changed ACL to your proposal in today's patch

Thank you for your corrections.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 24 Aug 2022 08:37:09 +0200

Hi

>   privileges instead, like it's done for sequences?

changed - now ACL_SELECT and ACL_UPDATE are used

> diff --git a/src/test/regress/expected/misc_sanity.out
> b/src/test/regress/expected/misc_sanity.out

fixed

>   As an example, I try to quickly disable all that code with the following:

I am not able to test (in this situation) the situation where gennum is
increased, but I think it is possible, and there are few situations where
dependency is not enough. But maybe my thoughts are too pessimistic, and
this aparate is not necessary.

1. update of binary custom type - the dependency allows an extension
update, and after update the binary format can be changed. Now I think this
part is useless, because although the extension can be updated, the dll
cannot be unloaded, so the loaded implementation of custom session type
will be the same until session end.

2. altering composite type - the generation number reduces overhead with
checking compatibility of stored value and expected value. With gennum I
need to run compatibility checks just once per transaction. When the gennum
is the same, I can return data without any conversion.

3. I try to use gennum for detection of oid overflow. The value is stored
in the session memory context in the hash table. The related memory can be
cleaned at transaction end (when memory is deleted) and when I can read
system catalog (transaction is not aborted). When a transaction is aborted,
then I cannot read the system catalog, and I have to postpone cleaning to
the next usage of the session variable. Theoretically, the session can be
inactive for a longer time and the system catalog can be changed a lot (and
the oid counter can be restarted).

I am checking:

3.1 if variable with oid still exists

3.2 if the variable has assigned type with same oid

3.3. if type fingerprint is same - and I can expect so the type with same
oid is same type

3.2 and 3.3 are safe guard for cases where oid is restarted, and I cannot
believe the consistency of values stored in memory.

This is a very different situation than for example temporary tables. Every
temp table for every session has its own entry in the system catalog, so
protection based on dependency can work. But record of session variable is
shared - It is protected inside transaction, but session variables are
living in session. Without transaction there is not any lock on the item in
pg_variable, so I can drop the session variable although the value is
stored in session memory in some other session. After dropping the related
plans are resetted, but the stored value itself stays in memory and can be
accessed - if some future variable takes the same oid. With gennum I have
3x checks - that should ensure that the returned value should be always
binary valid.

Now, I am thinking about another, maybe more simple identity check, and it
should to work and it can less code than solution based on type's
fingerprints

I can introduce a 64bit sequence and I can store the value of seq in
pg_variable record. Then the identity check can be just savedoid = oid and
savedseqnum = seqnum

What do you think about this idea? The overhead of that can be reduced,
because for on transaction commit drop or on transaction end reset session
variables we don't need it.

> And session_variable.sql regression test still works just fine.  Am I

the regress test doesn't try to reset oid counter

changed

done

> +    <title><command>Session variables and constants</command></title>
rewroted just to "Session variables"

done

>   while both use varname and varnamespace.  I think we should avoid

I did it because I needed to rename the namespace field, but the prefix var
is not the best. I don't think so using same names like pg_variable in
Variable is good idea (due fields like varisnotnull, varisimmutable), but I
can the rename varnane and varnamespace to name and namespaceid, what is
better than varname, and varnamespace.

> session_variable.[ch], as it seems more readable?

renamed

> +static Query *transformLetStmt(ParseState *pstate,

I fixed these.

Thank you for comments

Pavel
From 9628d9b98ea647bf0ba40190d8c75f040719738d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 12/12] documentation

 doc/src/sgml/ref/grant.sgml                   |   9 +
 doc/src/sgml/ref/revoke.sgml                  |   7 +
 18 files changed, 973 insertions(+), 9 deletions(-)

 
index 2ce539aaf0..bad9984d44 100644
 
+       True if the variable is <link linkend="sql-createvariable-immutable">immutable</link> (cannot be modified). The default value is false.

index cf387dfc3f..3dfe4c8a2e 100644
+    <title><command>Session variables</command></title>
 
index f1d54f5aa3..354991b849 100644
 
+GRANT { SELECT | UPDATE | ALL [ PRIVILEGES ] }
+    ON VARIABLES
+    { { SELECT | UPDATE } [, ...] | ALL [ PRIVILEGES ] }

   <para>
 
index 0000000000..70c87968ce
+   <varlistentry id="sql-createvariable-immutable">
 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
index 2fd0f34d55..f596e27d8c 100644
@@ -108,6 +108,15 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
+GRANT { SELECT | UPDATE | ALL [ PRIVILEGES ] }
+       | ALL VARIABLES IN SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...] }
+    [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]

+     <varlistentry>
+      <term><option>-A <replaceable class="parameter">session_variable</replaceable></option></term>
+      <term><option>--variable=<replaceable class="parameter">session_variable</replaceable></option></term>
+      <listitem>
+       <para>
+        Restore a named session variable only.  Multiple session variables may
+        be specified with multiple <option>-A</option> switches.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-c</option></term>
       <term><option>--clean</option></term>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 16e840458c..6deeb423fd 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -137,6 +137,13 @@ REVOKE [ ADMIN 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>

From be57ccedcecd58dc5514bca0405cfa8afc7f462d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 11:40:29 +0200
Subject: [PATCH 11/12] this patch changes error message "column doesn't exist"
 to message "column or variable doesn't exist"

 

 select a from atacc1;

 
 
index e4992c67c1..d0ae8522af 100644
 
 -- should fail
 LET v1.x = 10;

From 109802568f0e35d278e39355f013dedc109068a1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 09/12] typedefs

---
 src/tools/pgindent/typedefs.list | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 35c9f1efce..ed29bebc59 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -498,6 +498,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
@@ -881,6 +882,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
+FormData_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
@@ -1403,6 +1405,7 @@ LargeObjectDesc
 LastAttnumInfo
 Latch
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
@@ -1785,6 +1788,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
@@ -2462,6 +2466,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
@@ -2647,6 +2652,12 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariable
+SVariableData
+SVariableType
+SVariableTypeData
+SVariableTypeDataField
+SVariableXActActionItem
 Syn
 SyncOps
 SyncRepConfigData
@@ -2916,6 +2927,7 @@ Variable
 VariableAssignHook
 VariableCache
 VariableCacheData
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.37.2

From 7e6fcc61d5dd52e0df11835b3c51dc7a3914591a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 08/12] possibility to dump session variables by pg_dump

 src/bin/pg_dump/dumputils.c          |   6 +
 9 files changed, 360 insertions(+), 5 deletions(-)

 
 	*numTablesPtr = numTables;
 	return tblinfo;
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 6e501a5413..dc1f0e1ba8 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -504,6 +504,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();

 	int			useDB;
 
index ca4ad07004..e62a5b0cba 100644
 
+/*
+ * getVariables
 
@@ -14327,6 +14558,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
+			type = "VARIABLES";
@@ -17868,6 +18102,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 
index b10e1c4c0d..e5cbb50722 100644
 
+	'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
@@ -1327,6 +1337,22 @@ my %tests = (
 		unlike => { exclude_dump_test_schema => 1, },
 	},

 
+	'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,
+		},
+	},
+
 	'REFRESH MATERIALIZED VIEW matview' => {
 		regexp => qr/^\QREFRESH MATERIALIZED VIEW dump_test.matview;\E/m,
 		like =>
-- 
2.37.2

From 440283ff0f3a19df66b63ab06e435b81403c1a4e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 10/12] regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 .../regress/expected/session_variables.out    | 1274 +++++++++++++++++
 src/test/regress/parallel_schedule            |    2 +-
 src/test/regress/sql/session_variables.sql    |  947 ++++++++++++
 3 files changed, 2222 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

index 0000000000..e4992c67c1
@@ -0,0 +1,1274 @@
+GRANT SELECT ON VARIABLE var1 TO var_test_role;
+GRANT UPDATE ON VARIABLE var1 TO var_test_role;
+-- 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;
+INSERT INTO svar_test SELECT * FROM generate_series(1,1000);
+--------------------------------------------
+ Aggregate
+(5 rows)
+-- result should be 100
+-- result should be 1000
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers_per_gather;
+DETAIL:  Session variables can be shadowed by columns, routine's variables and routine's arguments with the same name.
+DETAIL:  Session variables can be shadowed by columns, routine's variables and routine's arguments with the same name.
+GRANT SELECT ON ALL VARIABLES IN SCHEMA vartest TO var_test_role;
+REVOKE SELECT ON ALL VARIABLES IN SCHEMA vartest FROM var_test_role;
+GRANT SELECT ON VARIABLE public.v1 TO var_test_role;
+REVOKE SELECT ON VARIABLE public.v1 FROM var_test_role;
+-- The variable can be shadowed by table or by alias
+CREATE TYPE public.svar_type AS (a int, b int, c int);
+CREATE TABLE public.svar (a int, b int);
+INSERT INTO public.svar VALUES(10, 20);
+LET public.svar = (100, 200, 300);
+-- show table
+SELECT * FROM public.svar;
+ a  | b  
+----+----
+ 10 | 20
+SELECT svar.a FROM public.svar;
+ a  
+SELECT svar.* FROM public.svar;
+ a  | b  
+----+----
+ 10 | 20
+-- show variable
+ (100,200,300)
+SELECT public.svar.c;
+  c  
+ 300
+SELECT (public.svar).*;
+  a  |  b  |  c  
+-----+-----+-----
+ 100 | 200 | 300
+-- 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
+-- again with warnings
+SELECT * FROM public.svar;
+ a  | b  
+----+----
+ 10 | 20
+SELECT svar.a FROM public.svar;
+ a  
+SELECT svar.* FROM public.svar;
+ a  | b  
+----+----
+ 10 | 20
+-- show variable
+ (100,200,300)
+SELECT public.svar.c;
+  c  
+ 300
+SELECT (public.svar).*;
+  a  |  b  |  c  
+-----+-----+-----
+ 100 | 200 | 300
+-- the variable is shadowed, raise error
+SELECT public.svar.c FROM public.svar;
+WARNING:  session variable "public.svar" is shadowed
+LINE 1: SELECT public.svar.c FROM public.svar;
+DETAIL:  Session variables can be shadowed by tables or table's aliases with the same name.
+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
+SET session_variables_ambiguity_warning TO DEFAULT;
+DROP TABLE public.svar;
 
 # ----------
index 0000000000..743951d85d
@@ -0,0 +1,947 @@
+GRANT SELECT ON VARIABLE var1 TO var_test_role;
+GRANT UPDATE ON VARIABLE var1 TO var_test_role;
+-- 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;
+INSERT INTO svar_test SELECT * FROM generate_series(1,1000);
+-- result should be 100
+-- result should be 1000
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers_per_gather;
+GRANT SELECT ON ALL VARIABLES IN SCHEMA vartest TO var_test_role;
+REVOKE SELECT ON ALL VARIABLES IN SCHEMA vartest FROM var_test_role;
+GRANT SELECT ON VARIABLE public.v1 TO var_test_role;
+REVOKE SELECT ON VARIABLE public.v1 FROM var_test_role;
+-- The variable can be shadowed by table or by alias
+CREATE TYPE public.svar_type AS (a int, b int, c int);
+CREATE TABLE public.svar (a int, b int);
+INSERT INTO public.svar VALUES(10, 20);
+LET public.svar = (100, 200, 300);
+-- show table
+SELECT * FROM public.svar;
+SELECT svar.a FROM public.svar;
+SELECT svar.* FROM public.svar;
+-- show variable
+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;
+-- again with warnings
+SELECT * FROM public.svar;
+SELECT svar.a FROM public.svar;
+SELECT svar.* FROM public.svar;
+-- show variable
+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;
+SET session_variables_ambiguity_warning TO DEFAULT;
+DROP TABLE public.svar;

From 8495952252656d0c3599be197992fb0954c5e377 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 06/12] DISCARD VARIABLES command

---

 
index 739689a896..876464075f 100644

index 3accd22ad2..8b28baf7fe 100644

From 610cd9a369135c0285328997441347848efd6bdd Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 05/12] support of LET command in PLpgSQL

index 10fc302991..739689a896 100644

 /*

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

+static void

+static void

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

From 5840f31d35d6ea3fea2999a68acea7a70f876374 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 07/12] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From a7f5e1e29dfb4c8efc139e67e6790fa66e7a6db2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 04/12] LET command

 src/backend/commands/session_variable.c  |  85 ++++++-
 25 files changed, 736 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index e078456b19..3a5f51cbc3 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -492,6 +492,22 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
 		else
 			ExplainDummyGroup("Notify", NULL, es);
 	}
+	else if (IsA(utilityStmt, LetStmt))
+	{
+		LetStmt	   *letstmt = (LetStmt *) utilityStmt;
+		List	   *rewritten;
+
+		if (es->format == EXPLAIN_FORMAT_TEXT)
+			appendStringInfoString(es->str, "SET SESSION VARIABLE\n");
+		else
+			ExplainDummyGroup("Set Session Variable", NULL, es);
+
+		rewritten = QueryRewrite(castNode(Query, copyObject(letstmt->query)));
+		Assert(list_length(rewritten) == 1);
+		ExplainOneQuery(linitial_node(Query, rewritten),
+						CURSOR_OPT_PARALLEL_OK, NULL, es,
+						queryString, params, queryEnv);
+	}
 	else
 	{
 		if (es->format == EXPLAIN_FORMAT_TEXT)
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index fd6a85d780..7f4cfa2150 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -1731,7 +1731,7 @@ SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typ
 	/*
 	 * Is possible to write to session variable?
 	 */
-	aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+	aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_UPDATE);
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));

+/*
+	aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_UPDATE);
@@ -2350,8 +2429,8 @@ pg_debug_show_used_session_variables(PG_FUNCTION_ARGS)
 
 				values[5] = BoolGetDatum(false);
 				values[6] = BoolGetDatum(svar->is_valid);
-				values[8] = BoolGetDatum(pg_variable_aclcheck(svar->varid, GetUserId(), ACL_READ) == ACLCHECK_OK);
-				values[9] = BoolGetDatum(pg_variable_aclcheck(svar->varid, GetUserId(), ACL_WRITE) == ACLCHECK_OK);
+				values[8] = BoolGetDatum(pg_variable_aclcheck(svar->varid, GetUserId(), ACL_SELECT) == ACLCHECK_OK);
+				values[9] = BoolGetDatum(pg_variable_aclcheck(svar->varid, GetUserId(), ACL_UPDATE) == ACLCHECK_OK);
 			}
 			else
 			{
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 11118d0ce0..71248a34f2 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -76,6 +76,7 @@ OBJS = \
 	nodeWindowAgg.o \
 	nodeWorktablescan.o \
 	spi.o \
+	svariableReceiver.o \
 	tqueue.o \
 	tstoreReceiver.o
 
index 68e748eb73..00836a809a 100644
index a013031d22..11c0d8444e 100644

+							   LetStmt *stmt);
 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+transformLetStmt(ParseState *pstate, LetStmt *stmt)
+	aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_UPDATE);
index 396d49fa0b..10fc302991 100644

 
@@ -12005,6 +12007,7 @@ ExplainableStmt:
 
 /*****************************************************************************
@@ -12035,6 +12038,7 @@ PreparableStmt:
 			| UpdateStmt
 			| DeleteStmt
 			| MergeStmt						/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -12622,6 +12626,49 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -17905,6 +17952,7 @@ unreserved_keyword:
@@ -18511,6 +18559,7 @@ bare_label_keyword:
 
 			/*
 
 			/*
index 9022122e16..d7a8ec22cc 100644
 
 			/* okay */
 			break;
@@ -1994,6 +1995,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:
@@ -3369,6 +3371,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*
 
index b1d4e82062..3accd22ad2 100644
@@ -1986,6 +1986,21 @@ typedef struct MergeStmt

 	NodeTag		type;

 

 /* ----------------
-- 
2.37.2

From 6125894dc0a1f8dab63bc73f95f673a735e07be6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Jul 2022 06:06:19 +0200
Subject: [PATCH 03/12] typecheck - check of consistency of format of stored
 value.

Session variables holds a own evidence of fingerprints for any
used type. When some possible not compatible change is detected
(the version of extension is changed), the related stored type's
generation number is increased. The copy of genaration number is
stored in session variable data. Before any read of session variable,
the stored generation number is compared with current type generation
number. When it is different, the read is canceled.
---
 src/backend/commands/session_variable.c | 1142 +++++++++++++++++++++--
 1 file changed, 1039 insertions(+), 103 deletions(-)

diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index eb8e40115f..fd6a85d780 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -14,12 +14,15 @@
  */
 #include "postgres.h"
 #include "miscadmin.h"
 
 /*
@@ -163,6 +170,58 @@
+typedef SVariableTypeData *SVariableType;
@@ -223,18 +282,38 @@ static LocalTransactionId synced_lxid = InvalidLocalTransactionId;
 
+	 * sometimes we cannot to access to catalog.
 
 	SVariableType svartype;
 	int64		gennum;
 
+	/*
 
@@ -242,19 +321,27 @@ typedef struct SVariableData
 
-	uint32		hashvalue;

 typedef SVariableData *SVariable;

 
@@ -276,12 +363,572 @@ SvariableXActActionName(SVariableXActAction action)
 	}
 }
 
+/*
@@ -291,6 +938,21 @@ free_session_variable_value(SVariable svar)
 	svar->isnull = true;
 	svar->freeval = false;
 
+	if (!keep_desc_fields)
+	{
+		if (svar->natts > 0)
+		{
+			pfree(svar->attrs);
+			svar->natts = 0;
+		}
+
+		if (svar->tupdesc)
+		{
@@ -307,7 +969,7 @@ free_session_variable_value(SVariable svar)
 
@@ -520,9 +1182,10 @@ sync_sessionvars_all()
 
 	/* set callbacks */
 	if (first_time)
@@ -542,14 +1205,24 @@ create_sessionvars_hashtable(void)

 

@@ -560,31 +1233,56 @@ create_sessionvars_hashtable(void)

 	/*
@@ -593,25 +1291,57 @@ set_session_variable(SVariable svar, Datum value,

 
-	svar->value = newval;
-	svar->isnull = isnull;
 
 /*
@@ -633,12 +1363,23 @@ init_session_variable(SVariable svar, Variable *var)

 
@@ -662,9 +1403,131 @@ init_session_variable(SVariable svar, Variable *var)
 
+	svar->tupdesc = NULL;
+
 
+/*
@@ -675,7 +1538,7 @@ init_session_variable(SVariable svar, Variable *var)
@@ -684,7 +1547,7 @@ prepare_variable_for_reading(Oid varid)
 	var.oid = InvalidOid;
 
 	if (!sessionvars)
 
 	/* Protect used session variable against drop until transaction end */
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
@@ -696,54 +1559,120 @@ prepare_variable_for_reading(Oid varid)
 										HASH_ENTER, &found);

 
-	if (!found)

-	if (svar->has_defexpr)
-	{

 

 
 
 	return svar;
 }
@@ -767,7 +1696,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid)
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
 
 	if (!sessionvars)
 
 	/* Ensure so all entries in sessionvars hash table are valid */
 	sync_sessionvars_all();
@@ -816,18 +1745,21 @@ SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typ
 Datum
 CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 {
-	SVariable svar;
+	SVariable	svar;
+	Datum		result = (Datum) 0;

 
-	if (!svar->isnull)
 
-	return (Datum) 0;
+	return (Datum) result;
 }
 
 /*
@@ -838,22 +1770,25 @@ CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 Datum
 CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 {
-	SVariable svar;
+	SVariable	svar;
+	Datum		result = (Datum) 0;

 
 	*isNull = svar->isnull;
 
-	if (!svar->isnull)
 
-	return (Datum) 0;
+	return (Datum) result;
 }
 
 /*
@@ -864,22 +1799,18 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 Datum
 GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 {
-	SVariable svar;
+	SVariable	svar;
+	Datum		result = (Datum) 0;

 
 
 /*
@@ -1082,6 +2013,9 @@ ResetSessionVariables(void)
 	{
 		hash_destroy(sessionvars);
 		sessionvars = NULL;
+
+		hash_destroy(sessionvars_types);
+		sessionvars_types = NULL;
 	}
 
 	/* Release memory allocated by session variables */
@@ -1383,6 +2317,8 @@ pg_debug_show_used_session_variables(PG_FUNCTION_ARGS)

From c7778a41dae41ed53682302fb6067808f5008b30 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/12] session variables

 src/backend/commands/session_variable.c | 1157 ++++++++++++++++++++++-
 src/backend/parser/parse_expr.c         |  213 ++++-
 29 files changed, 1979 insertions(+), 62 deletions(-)

 
 
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 3094bab82a..eb8e40115f 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -23,22 +23,150 @@
 
 /*
+ * - We need to free local memory when variable is dropped (in current
+ *  b) the variable can be dropped - we don't free local memory
+ *   in system catalog. The DROP commands invalidates related
+ *   of stored values in hash table against system catalog.
+ *   the possibility to find in catalog a variable with same
+ * record from system catalog after aborted CREATE VARIABLE command,
 
 typedef struct SVariableXActActionItem
@@ -54,12 +182,706 @@ typedef struct SVariableXActActionItem
 	SubTransactionId deleting_subid;
 }  SVariableXActActionItem;
 
+ * When the session variable is dropped we need to free local memory. The
+ * But because we don't want to free local memory immediately, we need to
+ * catalog. This check can be postponed into next transaction if
+typedef SVariableData *SVariable;

+/*
+				 * transaction. This ensure so we don't free local memory too
+	svar->nsname = get_namespace_name(var->namespaceid);
@@ -170,19 +992,24 @@ DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 }
 
 /*

 	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
@@ -190,6 +1017,18 @@ RemoveSessionVariable(Oid varid)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for variable %u", varid);

 	ReleaseSysCache(tup);
@@ -197,17 +1036,81 @@ RemoveSessionVariable(Oid varid)
 	table_close(rel, RowExclusiveLock);
 
 	/*
 
 /*
 
@@ -221,6 +1124,9 @@ register_session_variable_xact_action(Oid varid,
 	SVariableXActActionItem *xact_ai;
 	MemoryContext oldcxt;

 	xact_ai = (SVariableXActActionItem *)
@@ -231,8 +1137,10 @@ register_session_variable_xact_action(Oid varid,
 	xact_ai->creating_subid = GetCurrentSubTransactionId();
 	xact_ai->deleting_subid = InvalidSubTransactionId;

 	MemoryContextSwitchTo(oldcxt);
 }
@@ -251,7 +1159,10 @@ unregister_session_variable_xact_action(Oid varid,

@@ -270,25 +1181,39 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 {
 	ListCell   *l;

 
-			/*
 
+				/*
@@ -312,8 +1237,63 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 
 /*
@@ -329,7 +1309,33 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 {
 	ListCell   *cur_item;
 
@@ -337,7 +1343,8 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
@@ -350,3 +1357,87 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
+			/* check if session variable is visible in system catalog */
+			 * Sessionvars can hold data of variables removed from catalog,
+			 * from catalog.
+				/* When we see data in catalog */
+				 * When session variable was removed from catalog, but still it
index d0a57c7aae..6720f6182d 100644
+								aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_SELECT);
 
 						/*
 
index ef2fd46092..68e748eb73 100644
 
+	/*
+			aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_SELECT);
index f1fd7f7e8b..25f8388a55 100644

 
+static SessionVariableValue *RestoreSessionVariables(char **start_address,

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

index aa1b1a76dc..9022122e16 100644
 
 /* GUC parameters */

 /*
@@ -841,6 +849,151 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
+	/* Protect session variable by AccessShareLock when it is not shadowed. */
+	lockit = (node == NULL);
+
+	/* The col's reference can be a reference to session variable too. */
+	varid = IdentifyVariable(cref->fields, &attrname, lockit, &not_unique);
+
+	/*
+	 * Raise error when reference to session variable is ambiguous and
+	 * and this reference is not shadowed.
+	 */
+	if (!node && not_unique)
+		ereport(ERROR,
+				(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+				 errmsg("session variable reference \"%s\" is ambiguous",
+						NameListToString(cref->fields)),
+				 parser_errposition(pstate, cref->location)));
+
+	if (OidIsValid(varid))
+	{
+		/*
+		 * When session variables is shadowed by columns or by routine's
+		 * variables or routine's arguments. The valid RTE, but without
+		 * valid column reference can shadow session variables too. We don't
+		 * want to mask relation related error, that can be very probably
+		 * user's typo error. We disallow case:
+		 *
+		 *  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;
+		 *
+		 * In this case the variable foo (reference to field c) is not
+		 * exactly shadowed, but this case can be pretty messy and can
+		 * hide unwanted user's error. We can disallow this case, and
+		 * we do it.
+		 */
+		if (node ||
+			(!node && relname && (crerr == CRERR_NO_COLUMN)))
+		{
+			/*
+			 * In this case we can raise a warning, but only when required.
+			 * We should not raise warnings for contexts where usage of session
+			 * variables has not sense, or when we can detect that variable
+			 * doesn't have the wanted field (and then there is no possible
+			 * identifier's collision).
+			 */
+			if (session_variables_ambiguity_warning &&
+				pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET)
+			{
+				/*
+				 * Some cases with ambiguous references can be solved without
+				 * raising a warning. When there is a collision between column
+				 * name (or label) and some session variable name, and when we
+				 * know attribute name, then we can ignore the collision when:
+				 *
+				 *   a) variable is of scalar type (then indirection cannot be
+				 *      applied on this session variable.
+				 *
+				 *   b) when related variable has no field with the given
+				 *      attrname, then indirection cannot be applied on this
+				 *      session variable.
+				 */
+				if (attrname && node)
+				{
+					Oid			typid;
+					int32		typmod;
+					Oid			collid;
+
+					get_session_variable_type_typmod_collid(varid,
+															&typid, &typmod,
+															&collid);
+
+					if (type_is_rowtype(typid))
+					{
+						TupleDesc	tupdesc;
+						bool		found = false;
+						int			i;
+
+						/* slow part, I hope it will not be to often */
+						tupdesc = lookup_rowtype_tupdesc(typid, typmod);
+						for (i = 0; i < tupdesc->natts; i++)
+						{
+							if (namestrcmp(&(TupleDescAttr(tupdesc, i)->attname), attrname) == 0 &&
+								!TupleDescAttr(tupdesc, i)->attisdropped)
+							{
+								found = true;
+								break;
+							}
+						}
+
+						ReleaseTupleDesc(tupdesc);
+
+						/* There is no composite variable with this field. */
+						if (!found)
+							varid = InvalidOid;
+					}
+					else
+						/* There is no composite variable with this name. */
+						varid = InvalidOid;
+				}
+
+				/*
+				 * Raise warning when session variable reference is still
+				 * visible.
+				 */
+				if (OidIsValid(varid))
+				{
+					if (node)
+						ereport(WARNING,
+								(errcode(ERRCODE_AMBIGUOUS_COLUMN),
+								 errmsg("session variable \"%s\" is shadowed",
+										NameListToString(cref->fields)),
+								 errdetail("Session variables can be shadowed by columns, routine's variables and routine's arguments with the same name."),
+								 parser_errposition(pstate, cref->location)));
+					else
+						/* session variable is shadowed by RTE */
+						ereport(WARNING,
+								(errcode(ERRCODE_AMBIGUOUS_COLUMN),
+								 errmsg("session variable \"%s.%s\" is shadowed",
+										get_namespace_name(get_session_variable_namespace(varid)),
+										get_session_variable_name(varid)),
+								 errdetail("Session variables can be shadowed by tables or table's aliases with the same name."),
+								 parser_errposition(pstate, cref->location)));
+				}
+			}
+
+			/* Session variables are always shadowed by any other node. */
+			varid = InvalidOid;
+		}
+		else
+		{
+			Oid			typid;
+			int32		typmod;
+			Oid			collid;
+
+			get_session_variable_type_typmod_collid(varid, &typid, &typmod,
+													&collid);
+
+			node = makeParamSessionVariable(pstate,
+										   varid, typid, typmod, collid,
+										   attrname, cref->location);
+		}
+	}
+
 	/*
 	 * Throw error if no translation found.
 	 */
@@ -875,6 +1028,64 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
 	/* Other working state: */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a10f3b2454..b1d4e82062 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -136,6 +136,7 @@ typedef struct Query
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
 
@@ -145,6 +146,7 @@ typedef struct Query

 

 

 
 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.37.2

From 701a2c104fb74d8531501fc434f41e0ffc890acf Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/12] catalog support for session variables

catalog, and lookups routines for identification of session variables.
 src/backend/catalog/aclchk.c            | 299 +++++++++++++++++++
 src/backend/commands/session_variable.c | 352 ++++++++++++++++++++++
 src/backend/utils/adt/acl.c             |   8 +-
 src/include/catalog/pg_variable.h       | 107 +++++++
 src/include/nodes/parsenodes.h          |  20 ++
 src/include/utils/acl.h                 |   6 +-
 37 files changed, 1947 insertions(+), 18 deletions(-)
 create mode 100644 src/backend/commands/session_variable.c

 

 
index 17ff617fba..b88ff346ec 100644

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3570,6 +3758,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;
@@ -3681,6 +3872,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;
@@ -3829,6 +4023,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return pg_type_aclmask(table_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return pg_variable_aclmask(table_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized objtype: %d",
 				 (int) objtype);
@@ -4812,6 +5008,66 @@ pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how)
 	return result;
 }
 
+/*
@@ -5112,6 +5368,18 @@ pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
 		return ACLCHECK_NO_PRIV;
 }
 
+/*
@@ -5729,6 +5997,33 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid)
 
+/*
@@ -5857,6 +6152,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index 0000000000..5ce6e9462f
+	var->namespaceid = varform->varnamespace;
index 48f7348f91..642454c745 100644
+	session_variable.o \

 
 			/*
 
 			/*
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
new file mode 100644
+++ b/src/backend/commands/session_variable.c

 
 				/*
index c8bd66dd54..396d49fa0b 100644

 

 
 
 /*

@@ -7991,6 +8076,7 @@ defacl_privilege_target:

@@ -9773,6 +9859,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10134,6 +10239,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10413,6 +10537,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17941,6 +18073,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18587,6 +18721,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)

 
index 3e045da31f..70eb1e8e1a 100644
@@ -806,6 +806,10 @@ acldefault(ObjectType objtype, Oid ownerId)
@@ -903,6 +907,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
@@ -1605,7 +1612,6 @@ makeaclitem(PG_FUNCTION_ARGS)
 
-
 /*
 
 	return subname;
 }
+
index eec644ec84..b6d756a056 100644

 	/*

 

 
 
index 0000000000..fbce5aa8e7
@@ -0,0 +1,107 @@
+DECLARE_TOAST(pg_variable, 9223, 9224);
+DECLARE_UNIQUE_INDEX_PKEY(pg_variable_oid_index, 9225, VariableOidIndexId, on pg_variable using btree(oid oid_ops));
+#define VariableObjectIndexId 9225
+DECLARE_UNIQUE_INDEX(pg_variable_varname_nsp_index, 9226, VariableNameNspIndexId,  on pg_variable using btree(varname name_ops, varnamespace oid_ops));
+#define VariableNameNspIndexId  9226
index b376031856..a10f3b2454 100644
@@ -2194,6 +2194,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -3321,6 +3322,25 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+

index 3d6411197c..d19176d157 100644
@@ -168,6 +168,7 @@ typedef struct ArrayType Acl;
+#define ACL_ALL_RIGHTS_VARIABLE		(ACL_SELECT|ACL_UPDATE)
 
@@ -267,7 +268,8 @@ extern AclMode pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
@@ -292,6 +294,7 @@ extern AclResult pg_tablespace_aclcheck(Oid spc_oid, Oid roleid, AclMode mode);
 
@@ -328,6 +331,7 @@ extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);

---

From: Erik Rijkers <[email-masked]>
Date: Wed, 24 Aug 2022 10:04:45 +0200

> [v20220824-1-*.patch]

Hi Pavel,

I noticed just now that variable assignment (i.e., LET) unexpectedly 
(for me anyway) cast the type of the input value. Surely that's wrong? 
The documentation says clearly enough:

'The result must be of the same data type as the session variable.'

Example:

create variable x integer;
let x=1.5;
select x, pg_typeof(x);
  x | pg_typeof
---+-----------
  2 | integer
(1 row)

Is this correct?

If such casts (there are several) are intended then the text of the 
documentation should be changed.

Thanks,

Erik

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 25 Aug 2022 19:40:57 +0200

> create variable x integer;

yes - the behave is designed like plpgsql assignment or SQL assignment

 (2022-08-25 19:35:35) postgres=# do $$
postgres$# declare i int;
postgres$# begin
postgres$#   i := 1.5;
postgres$#   raise notice '%', i;
postgres$# end;
postgres$# $$;
NOTICE:  2
DO

(2022-08-25 19:38:10) postgres=# create table foo1(a int);
CREATE TABLE
(2022-08-25 19:38:13) postgres=# insert into foo1 values(1.5);
INSERT 0 1
(2022-08-25 19:38:21) postgres=# select * from foo1;
┌───┐
│ a │
╞═══╡
│ 2 │
└───┘
(1 row)

There are the same rules as in SQL.

This sentence is not good - the value should be casteable to the target
type.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 25 Aug 2022 19:49:38 +0200

Hi

>   As an example, I try to quickly disable all that code with the following:
I am sending an alternative implementation based on using own int8 sequence
as protection against unwanted oid equation of different session's
variables.

This code is much shorter, and, I think better, but now, the creating
sequence in bootstrap time is dirty. Maybe instead the sequence can be used
64bite timestamp or some else - it needs a unique combination of oid, 8byte.

Regards

Pavel
From c54e3d7270f30cb21fd7ae07063f09c3aac4416b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 11/11] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+     <varlistentry>

From 335c0ca9bde2188080f9e89934537e533708ebbe Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 08/11] typedefs

---

From 659687ce3f5db326a160ea05fea8b67cd8e3eb6a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 11:40:29 +0200
Subject: [PATCH 10/11] this patch changes error message "column doesn't exist"
 to message "column or variable doesn't exist"

 

 select a from atacc1;

 

 -- should fail
 LET v1.x = 10;

From efd9b10de3ea5f6270f1eb070341791707c46c4b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/11] possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From af48fe15898ec66f32b80b8f4984ca76b416674a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 09/11] regress tests for session variables

 
 # ----------

From 6e7a79e24278d1878bfaa7dd4eb862e6aaeb3fbf Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/11] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From bfbbe35b4cbb748b563a551af26639ef239d76de Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/11] DISCARD VARIABLES command

---

 

From 0c026d115bb9bf1d730a0aa678df4226bbc7abe4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/11] support of LET command in PLpgSQL

 
 /*

index 7bd2a9fff1..3acbf75723 100644

+			case PLPGSQL_STMT_LET:
 
+/* ----------
+	SetSessionVariableWithSecurityCheck(varid, value, isNull);
 
 	return "unknown";

+static void

+static void

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

From 5ab8e714d877ce9a298500d89496a38e05647b53 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 03/11] LET command

index 9f344e70f0..5b0db94e85 100644
@@ -1085,7 +1085,7 @@ SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull)
 
@@ -1393,6 +1393,85 @@ ResetSessionVariables(void)
 	xact_recheck_requests = NIL;
 }
 
+/*
@@ -1701,8 +1780,8 @@ pg_debug_show_used_session_variables(PG_FUNCTION_ARGS)

index 0000000000..2cd5e67aa6
+	SetSessionVariable(myState->varid, value, isnull);

 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*
index 8d64853831..6b95f66a3a 100644

 
 	NodeTag		type;

 

 /* ----------------
-- 
2.37.2

From 707c82d0dcbd0f76b303a4546b9aba08c59ea34c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/11] session variables

 src/backend/commands/session_variable.c | 1445 ++++++++++++++++++++++-
 src/backend/parser/parse_expr.c         |  213 +++-
 src/include/commands/session_variable.h |    4 +-
 30 files changed, 2269 insertions(+), 64 deletions(-)

 
 
index 3094bab82a..9f344e70f0 100644
@@ -14,31 +14,157 @@
 
 /*
+ *   oid like oid of dropped value. We protect against unwanted
+ *   reusing oid by secondary 8 byte sequence.
 
 typedef struct SVariableXActActionItem
@@ -54,12 +180,991 @@ typedef struct SVariableXActActionItem
 	SubTransactionId deleting_subid;
 }  SVariableXActActionItem;
 
+	int64		luid;			/* used for session's variable identity check */
+	Oid			basetypeid;

+/*
+ * Returns human readable name of SVariableXActAction value.
+ */
+static const char *
+SvariableXActActionName(SVariableXActAction action)
+{
+	switch (action)
+	{
+		case SVAR_ON_COMMIT_DROP:
+			return "ON COMMIT DROP";
+		case SVAR_ON_COMMIT_RESET:
+			return "ON COMMIT RESET";
+		default:
+			elog(ERROR, "unknown SVariableXActAction action %d",
+						action);
+	}
+}
+
+/*
+ * Releases stored data from session variable, but preserve the hash entry
+ * in sessionvars. When we replace row value by new value with same type
+ * fingerprint, we can keep field desc data.
+ */
+static void
+free_session_variable_value(SVariable svar)
+{
+	if (svar->freeval)
+	{
+		pfree(DatumGetPointer(svar->value));
+		svar->freeval = false;
+	}
+
+	if (svar->tupdesc)
+	{
+		FreeTupleDesc(svar->tupdesc);
+		svar->tupdesc = NULL;
+	}
+
+	/*
+	 * We can mark this session variable as valid when
+	 * it has not default expression, and when null is
+	 * allowed. When it has defexpr, then the content
+	 * will be valid after an assignment or defexp evaluation.
+	 */
+	svar->is_valid = !svar->has_defexpr && !svar->is_not_null;
+
+	/* Clean current value */
+	svar->value = (Datum) 0;
+	svar->isnull = true;
+}
+
+/*
+ * Release the variable defined by varid from sessionvars
+ * hashtab.
+ */
+static void
+remove_session_variable(SVariable svar)
+{
+	free_session_variable_value(svar);
+
+	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) is removing from memory",
+				 svar->nsname, svar->name,
+				 svar->varid);
+
+	if (hash_search(sessionvars,
+					(void *) &svar->varid,
+					HASH_REMOVE,
+					NULL) == NULL)
+		elog(DEBUG1, "hash table corrupted");
+}
+
+/*
+ * Release the variable defined by varid from sessionvars
+ * hashtab.
+ */
+static void
+remove_session_variable_by_id(Oid varid)
+{
+	SVariable svar;
+	bool		found;
+
+	if (!sessionvars)
+		return;
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+										HASH_FIND, &found);
+	if (found)
+		remove_session_variable(svar);
+}
+
+/*
+ * Callback function for session variable invalidation.
+ */
+static void
+pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
+{
+	HASH_SEQ_STATUS status;
+	SVariable svar;
+
+	/*
+	 * There is no guarantee of sessionvars being initialized, even when
+	 * receiving an invalidation callback, as DISCARD [ ALL | VARIABLES ]
+	 * destroys the hash table entirely.
+	 */
+	if (!sessionvars)
+		return;
+
+	/*
+	 * When the hashvalue is not specified, then we have to recheck all
+	 * currently used session variables. Since we can't guarantee the exact
+	 * session variable from its hashValue, we have to iterate over all
+	 * items of hash table.
+	 */
+	hash_seq_init(&status, sessionvars);
+
+	while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+	{
+		if (hashvalue == 0 || svar->hashvalue == hashvalue)
+		{
+			/*
+			 * We don't need to execute recheck for variables,
+			 * that will be surelly purged from memory at eox
+			 * time.
+			 */
+			if (!svar->eox_reset)
+			{
+				RecheckVariableRequest *req = NULL;
+				ListCell *l;
+
+				/*
+				 * The recheck request should be postponed to the end of current
+				 * transaction if there are request recheck from previous
+				 * transaction. This ensure so we don't free local memory too
+				 * early, when we process sinval from previous aborted
+				 * transaction. Possible test case:
+				 *
+				 * LET var = X;
+				 * BEGIN DROP VAR var; ROOLBACK;
+				 * BEGIN DROP VAR var ROLLBACK;
+				 * SELECT var; -- should be X
+				 *
+				 * Without postponing, the call of sync_sessionvars_all
+				 * in second transaction purge (badly) memory, because
+				 * sinval message from prrevious aborted transaction is
+				 * processed and in this time related record from pg_variable
+				 * is removed. So when we find request recheck from prev
+				 * transaction, we just update lxid.
+				 */
+				foreach (l, xact_recheck_requests)
+				{
+					req = (RecheckVariableRequest *) lfirst(l);
+					if (req->varid == svar->varid)
+					{
+						if (req->lxid != MyProc->lxid)
+						{
+							req->lxid = MyProc->lxid;
+
+							elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) should be rechecked (forced by sinval)"
+										 " (found recheck request from previous transaction)",
+									 svar->nsname, svar->name,
+									 svar->varid);
+
+							break;
+						}
+					}
+				}
+
+				if (!req)
+				{
+					MemoryContext oldcxt;
+
+					oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+					req = (RecheckVariableRequest *) palloc(sizeof(RecheckVariableRequest));
+
+					req->varid = svar->varid;
+					req->lxid = MyProc->lxid;
+
+					elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) should be rechecked (forced by sinval)",
+								 svar->nsname, svar->name,
+								 svar->varid);
+
+					xact_recheck_requests = lappend(xact_recheck_requests, req);
+
+					MemoryContextSwitchTo(oldcxt);
+				}
+			}
+		}
+
+		/*
+		 * although it there is low probability, we have to iterate
+		 * over all actively used session variables, because hashvalue
+		 * is not unique identifier.
+		 */
+	}
+}
+
+/*
+ * The possible invalidated variables (in memory) are checked
+ * against system catalog. This routine is called before
+ * any read or any write from/to session variable, but it
+ * should be called only once per transaction. The synced_lxid
+ * is used as protection against repeated call.
+ */
+static void
+sync_sessionvars_all()
+{
+	SVariable svar;
+	ListCell   *l;
+
+	if (synced_lxid == MyProc->lxid)
+		return;
+
+	if (!xact_recheck_requests)
+		return;
+
+	/*
+	 * sessionvars is null after DISCARD VARIABLES.
+	 * When we are sure, so there are not any
+	 * active session variable in this session.
+	 */
+	if (!sessionvars)
+	{
+		list_free_deep(xact_recheck_requests);
+		xact_recheck_requests = NIL;
+		return;
+	}
+
+	/*
+	 * This routine is called before any reading. So the
+	 * session should be in transaction state. This is required
+	 * for access to system catalog.
+	 */
+	Assert(IsTransactionState());
+
+	foreach(l, xact_recheck_requests)
+	{
+		RecheckVariableRequest *req;
+		bool	found;
+
+		req = (RecheckVariableRequest *) lfirst(l);
+
+		if (req->lxid != MyProc->lxid)
+		{
+			svar = (SVariable) hash_search(sessionvars, &req->varid,
+											HASH_FIND, &found);
+
+			if (found)
+			{
+				HeapTuple	tp;
+
+				tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(req->varid));
+
+				if (HeapTupleIsValid(tp))
+				{
+					Form_pg_variable varform = (Form_pg_variable) GETSTRUCT(tp);
+
+					/*
+					 * In this case, the oid cannot be used as unique identifier,
+					 * because after oid's reset the oid can be used for new
+					 * other session variable. We do second check against 64bit
+					 * unique identifier.
+					 */
+					varform = (Form_pg_variable) GETSTRUCT(tp);
+					if (svar->luid != varform->luid)
+						remove_session_variable(svar);
+
+					ReleaseSysCache(tp);
+				}
+				else
+					remove_session_variable(svar);
+			}
+
+			xact_recheck_requests = foreach_delete_current(xact_recheck_requests, l);
+			pfree(req);
+		}
+	}
+
+	/* Don't repeat this check in this transaction more time */
+	synced_lxid = MyProc->lxid;
+}
+
+/*
+ * Create the hash table for storing session variables
+ */
+static void
+create_sessionvars_hashtables(void)
+{
+	HASHCTL		vars_ctl;
+
+	/* set callbacks */
+	if (first_time)
+	{
+		/* Read sinval messages */
+		CacheRegisterSyscacheCallback(VARIABLEOID,
+									  pg_variable_cache_callback,
+									  (Datum) 0);
+		/* needs its own long lived memory context */
+		SVariableMemoryContext =
+			AllocSetContextCreate(TopMemoryContext,
+								  "session variables",
+								  ALLOCSET_START_SMALL_SIZES);
+
+		first_time = false;
+	}
+
+	Assert(SVariableMemoryContext);
+
+	memset(&vars_ctl, 0, sizeof(vars_ctl));
+	vars_ctl.keysize = sizeof(Oid);
+	vars_ctl.entrysize = sizeof(SVariableData);
+	vars_ctl.hcxt = SVariableMemoryContext;
+
+	Assert(sessionvars == NULL);
+
+	sessionvars = hash_create("Session variables", 64, &vars_ctl,
+								   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+/*
+ * Assign some content to the session variable. It's copied to
+ * SVariableMemoryContext if necessary.
+ *
+ * init_mode is true, when the value of session variable is being initialized
+ * by default expression or by null. Only in this moment we can allow to
+ * modify immutable variables with default expression.
+ *
+ * This routine have to do successfull change or leave memory without
+ * change.
+ */
+static void
+set_session_variable(SVariable svar, Datum value, bool isnull, bool init_mode)
+{
+	TupleDesc	tupdesc = NULL;
+	TupleDesc	newtupdesc = NULL;
+	Datum		newval = value;
+
+	Assert(svar && OidIsValid(svar->typid));
+
+	/* Don't allow assignment of null to NOT NULL variable */
+	if (isnull && svar->is_not_null)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
+
+	/*
+	 * Don't allow updating of immutable session variable that has assigned
+	 * not null value or has default expression (and then the value should be
+	 * result of default expression always). Don't do this check, when variable
+	 * is being initialized.
+	 */
+	if (!init_mode &&
+		(svar->is_immutable &&
+		 (svar->is_valid || svar->has_defexpr)))
+		ereport(ERROR,
+				(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
+				 errmsg("session variable \"%s.%s\" is declared IMMUTABLE",
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
+
+	/* For row type not null value we need to store current tupdesc */
+	if (!isnull && svar->is_rowtype)
+		tupdesc = lookup_rowtype_tupdesc(svar->typid, -1);
+
+	PG_TRY();
+	{
+		/* copy value to session persistent context */
+		if (!isnull)
+		{
+			MemoryContext oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+
+			newval = datumCopy(value, svar->typbyval, svar->typlen);
+
+			if (tupdesc)
+			{
+				newtupdesc = palloc(TupleDescSize(tupdesc));
+				TupleDescCopy(newtupdesc, tupdesc);
+			}
+
+			MemoryContextSwitchTo(oldcxt);
+		}
+	}
+	PG_CATCH();
+	{
+		/* Don't allow memory leaks in persistent context */
+		if (newval != value)
+			pfree(DatumGetPointer(newval));
+
+		if (newtupdesc)
+			ReleaseTupleDesc(newtupdesc);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	free_session_variable_value(svar);
+
+	svar->value = newval;
+	svar->tupdesc = newtupdesc;
+
+	svar->isnull = isnull;
+	svar->freeval = newval != value;
+	svar->is_valid = true;
+
+	if (tupdesc)
+		ReleaseTupleDesc(tupdesc);
+
+	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new value",
+				 svar->nsname, svar->name,
+				 svar->varid);
+}
+
+/*
+ * Initialize svar from var
+ * svar - SVariable - holds value
+ * var  - Variable - holds metadata
+ */
+static void
+init_session_variable(SVariable svar, Variable *var)
+{
+	Form_pg_type typ;
+	HeapTuple	tp;
+	MemoryContext oldcxt;
+
+	Assert(OidIsValid(var->oid));
+
+	svar->varid = var->oid;
+	svar->luid = var->luid;
+
+	svar->typid = var->typid;
+
+	tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(var->typid));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for type %u", var->typid);
+
+	typ = (Form_pg_type) GETSTRUCT(tp);
+
+	svar->typlen = typ->typlen;
+	svar->typbyval = typ->typbyval;
+
+	if (typ->typtype == TYPTYPE_DOMAIN)
+	{
+		svar->is_domain = true;
+		svar->basetypeid = getBaseType(var->typid);
+		svar->is_rowtype = get_typtype(svar->basetypeid) == TYPTYPE_COMPOSITE;
+	}
+	else
+	{
+		svar->is_domain = false;
+		svar->basetypeid = InvalidOid;
+		svar->is_rowtype = typ->typtype == TYPTYPE_COMPOSITE;
+	}
+
+	ReleaseSysCache(tp);
+
+	svar->domain_check_extra = NULL;
+	svar->domain_check_extra_lxid = InvalidLocalTransactionId;
+
+	svar->value = (Datum) 0;
+	svar->isnull = true;
+	svar->freeval = false;
+	svar->tupdesc = NULL;
+
+	svar->is_not_null = var->is_not_null;
+	svar->is_immutable = var->is_immutable;
+	svar->has_defexpr = var->has_defexpr;
+
+	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
+											ObjectIdGetDatum(var->oid));
+
+	/* the value of variable is not known yet */
+	svar->is_valid = false;
+
+	svar->eox_reset = var->eoxaction == VARIABLE_EOX_RESET ||
+					  var->eoxaction == VARIABLE_EOX_DROP;
+
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+	svar->name = pstrdup(var->name);
+	svar->nsname = get_namespace_name(var->namespaceid);
+
+	if (svar->eox_reset)
+		xact_reset_varids = lappend_oid(xact_reset_varids, var->oid);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Try to convert the composite value to the current expected format.
+ * We have to do check, if stored value is binary compatible with
+ * expected tupdesc. We do conversion to expected format if not.
+ * Unfortunately we cannot to use TupleConversionMap infra, because
+ * this conversion should be based on position (not on position
+ * of not dropped fields).
+ */
+static Datum
+get_rowtype_value(SVariable svar)
+{
+	TupleDesc	tupdesc;
+	Datum	   *invalues;
+	bool	   *inisnull;
+	Datum	   *outvalues;
+	bool	   *outisnull;
+	HeapTuple	tuple;
+	HeapTupleHeader td;
+	HeapTupleData tmptup;
+	int		i;
+	Datum		value;
+
+	Assert(svar->is_rowtype);
+
+	if (svar->isnull)
+		return (Datum) 0;
+
+	Assert(svar->tupdesc);
+
+	tupdesc = lookup_rowtype_tupdesc(svar->typid, -1);
+
+	/* check if we need conversion */
+	if (svar->tupdesc->natts == tupdesc->natts)
+	{
+		bool	needs_conversion = false;
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+			Form_pg_attribute svar_attr = TupleDescAttr(svar->tupdesc, i);
+
+			if (svar_attr->attisdropped != attr->attisdropped)
+			{
+				needs_conversion = true;
+				break;
+			}
+
+			if (!svar_attr->attisdropped)
+			{
+				Assert(!attr->attisdropped);
+
+				if (svar_attr->atttypid != attr->atttypid ||
+					svar_attr->atttypmod != attr->atttypmod)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("record type of stored value of \"%s.%s\" does not match expected record type",
+								get_namespace_name(get_session_variable_namespace(svar->varid)),
+								get_session_variable_name(svar->varid)),
+						 errdetail("Expected format of %d field is %s.",
+								   i,
+								   format_type_with_typemod(attr->atttypid, attr->atttypmod))));
+			}
+
+			if (!needs_conversion)
+			{
+				ReleaseTupleDesc(tupdesc);
+				return svar->value;
+			}
+		}
+	}
+
+	td = DatumGetHeapTupleHeader(svar->value);
+	tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+	tmptup.t_data = td;
+
+	invalues = palloc(svar->tupdesc->natts * sizeof(Datum));
+	inisnull = palloc(svar->tupdesc->natts * sizeof(bool));
+
+	outvalues = palloc(tupdesc->natts * sizeof(Datum));
+	outisnull = palloc(tupdesc->natts * sizeof(bool));
+
+	heap_deform_tuple(&tmptup, svar->tupdesc, invalues, inisnull);
+
+	/* check if we can do this transformation */
+	for (i = 0; i < tupdesc->natts; i++)
+	{
+		if (i >= svar->tupdesc->natts)
+		{
+			outvalues[i] = (Datum) 0;
+			outisnull[i] = true;
+		}
+		else
+		{
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+			Form_pg_attribute svar_attr = TupleDescAttr(svar->tupdesc, i);
+
+			if (!svar_attr->attisdropped && !attr->attisdropped)
+			{
+				if (svar_attr->atttypid != attr->atttypid ||
+					svar_attr->atttypmod != attr->atttypmod)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("record type of stored value of \"%s.%s\" does not match expected record type",
+								get_namespace_name(get_session_variable_namespace(svar->varid)),
+								get_session_variable_name(svar->varid)),
+						 errdetail("Expected format of %d field is %s.",
+								   i,
+								   format_type_with_typemod(attr->atttypid, attr->atttypmod))));
+
+				outvalues[i] = invalues[i];
+				outisnull[i] = inisnull[i];
+			}
+			else
+			{
+				outvalues[i] = (Datum) 0;
+				outisnull[i] = true;
+			}
+		}
+	}
+
+	tuple = heap_form_tuple(tupdesc, outvalues, outisnull);
+	value = heap_copy_tuple_as_datum(tuple, tupdesc);
+
+	heap_freetuple(tuple);
+
+	ReleaseTupleDesc(tupdesc);
+
+	pfree(invalues);
+	pfree(inisnull);
+	pfree(outvalues);
+	pfree(outisnull);
+
+	elog(DEBUG1, "returned value of record type's session variable \"%s.%s\" was converted",
+				 svar->nsname, svar->name);
+
+	return value;
+}
+
+/*
+ * Search the given session variable in the hash table. If it doesn't
+ * exist, then insert it (and calculate defexpr if it exists).
+ *
+ * Caller is responsible for doing permission checks.
+ *
+ * As side effect this function acquires AccessShareLock on
+ * related session variable until the end of the transaction.
+ */
+static SVariable
+prepare_variable_for_reading(Oid varid, Datum *value, bool *isnull)
+{
+	SVariable svar;
+	Variable	var;
+	bool		found;
+
+	var.oid = InvalidOid;
+
+	if (!sessionvars)
+		create_sessionvars_hashtables();
+
+	/* Protect used session variable against drop until transaction end */
+	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	/* Ensure so all entries in sessionvars hash table are valid */
+	sync_sessionvars_all();
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+										HASH_ENTER, &found);
+
+	/* Return content if it is available and valid */
+	if (!found || !svar->is_valid)
+	{
+		/* We need to load defexpr. */
+		initVariable(&var, varid, false);
+
+		if (!found)
+		{
+			init_session_variable(svar, &var);
+
+			elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new entry in memory (emitted by READ)",
+						 svar->nsname, svar->name,
+						 varid);
+		}
+
+		/* Raise an error when we cannot initialize variable correctly */
+		if (var.is_not_null && !var.defexpr)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+							get_namespace_name(get_session_variable_namespace(varid)),
+							get_session_variable_name(varid)),
+					 errdetail("The session variable was not initialized yet.")));
+
+		if (svar->has_defexpr)
+		{
+			Datum		value = (Datum) 0;
+			bool		isnull;
+			EState	   *estate = NULL;
+			Expr	   *defexpr;
+			ExprState  *defexprs;
+			MemoryContext oldcxt;
+
+			/* Prepare default expr */
+			estate = CreateExecutorState();
+
+			oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+			defexpr = expression_planner((Expr *) var.defexpr);
+			defexprs = ExecInitExpr(defexpr, NULL);
+			value = ExecEvalExprSwitchContext(defexprs,
+											  GetPerTupleExprContext(estate),
+											  &isnull);
+
+
+			/* Store result before releasing Executor memory */
+			set_session_variable(svar, value, isnull, true);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			FreeExecutorState(estate);
+		}
+		else
+			set_session_variable(svar, (Datum) 0, true, true);
+	}
+
+	/* row type can needs conversion */
+	if (!svar->isnull && svar->is_rowtype)
+		*value = get_rowtype_value(svar);
+	else
+		*value = svar->value;
+
+	*isnull = svar->isnull;
+
+	/*
+	 * Although the value of domain type should be valid (it is
+	 * checked when it is assigned to session variable), we have to
+	 * check related constraints anytime. It can be more expensive
+	 * than in PL/pgSQL. PL/pgSQL forces domain checks when value
+	 * is assigned to the variable or when value is returned from
+	 * function. Fortunately, domain types manage cache of constraints by
+	 * self.
+	 */
+	if (svar->is_domain)
+	{
+		MemoryContext oldcxt = CurrentMemoryContext;
+
+		/*
+		 * Store domain_check extra in CurTransactionContext. When we are
+		 * in other transaction, the domain_check_extra cache is not valid.
+		 */
+		if (svar->domain_check_extra_lxid != MyProc->lxid)
+			svar->domain_check_extra = NULL;
+
+		domain_check(svar->value, svar->isnull,
+					 svar->typid, &svar->domain_check_extra,
+					 CurTransactionContext);
+
+		svar->domain_check_extra_lxid = MyProc->lxid;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	return svar;
+}
+
+/*
+ * Store the given value in an SVariable, and cache it if not already present.
+ *
+ * Caller is responsible for doing permission checks.
+ * We try not to break the previous value, if something is wrong.
+ *
+ * As side effect this function acquires AccessShareLock on
+ * related session variable until the end of the transaction.
+ */
+void
+SetSessionVariable(Oid varid, Datum value, bool isNull)
+{
+	SVariable svar;
+	bool		found;
+
+	/* Protect used session variable against drop until transaction end */
+	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	if (!sessionvars)
+		create_sessionvars_hashtables();
+
+	/* Ensure so all entries in sessionvars hash table are valid */
+	sync_sessionvars_all();
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+										HASH_ENTER, &found);
+
+	if (!found)
+	{
+		Variable	var;
+
+		/* We don't need to know defexpr here */
+		initVariable(&var, varid, true);
+		init_session_variable(svar, &var);
+
+		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new entry in memory (emitted by WRITE)",
+					 svar->nsname, svar->name,
+					 varid);
+	}
+
+	set_session_variable(svar, value, isNull, false);
+}
+
+/*
+ * Wrapper around SetSessionVariable after checking for correct permission.
+ */
+void
+SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull)
+{
+	AclResult	aclresult;
+
+	/*
+	 * Is possible to write to session variable?
+	 */
+	aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));
+
+	SetSessionVariable(varid, value, isNull);
+}
+
+/*
+ * Returns a copy of value of the session variable specified by varid
+ * Caller is responsible for doing permission checks.
+ */
+Datum
+CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
+{
+	SVariable	svar;
+	Datum		result = (Datum) 0;
+
+	svar = prepare_variable_for_reading(varid, &result, isNull);
+	Assert(svar != NULL && svar->is_valid);
+
+	*typid = svar->typid;
+
+	/* force copy of not null (not yet copy) value */
+	if (!isNull && svar->value == result)
+		result = datumCopy(svar->value, svar->typbyval, svar->typlen);
+
+	return (Datum) result;
+}
+
+/*
+ * Returns a copy of value of the session variable specified by varid
+ * with check of expected type. Like previous function, the caller
+ * is responsible for doing permission checks.
+ */
+Datum
+CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
+{
+	SVariable	svar;
+	Datum		result = (Datum) 0;
+
+	svar = prepare_variable_for_reading(varid, &result, isNull);
+	Assert(svar != NULL && 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));
+
+	*isNull = svar->isnull;
+
+	if (!*isNull && svar->value == result )
+		result = datumCopy(svar->value,
+						 svar->typbyval,
+						 svar->typlen);
+
+	return (Datum) result;
+}
+
+/*
+ * Returns a value of session variable identified by varid with
+ * check of expected type. Like previous function, the called
+ * is reposible for doing permission check.
+ */
+Datum
+GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
+{
+	SVariable	svar;
+	Datum		result = (Datum) 0;
+
+	svar = prepare_variable_for_reading(varid, &result, isNull);
+	Assert(svar != NULL && 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 result;
+}
+
 /*
  * Routines used for manipulation with session variables from
  * SQL level
@@ -170,19 +1275,24 @@ DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 }
 
 /*

 	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
@@ -190,6 +1300,18 @@ RemoveSessionVariable(Oid varid)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for variable %u", varid);

 	ReleaseSysCache(tup);
@@ -197,17 +1319,84 @@ RemoveSessionVariable(Oid varid)
 	table_close(rel, RowExclusiveLock);
 
 	/*
 
 /*
 
@@ -221,6 +1410,9 @@ register_session_variable_xact_action(Oid varid,
 	SVariableXActActionItem *xact_ai;
 	MemoryContext oldcxt;

 	xact_ai = (SVariableXActActionItem *)
@@ -231,8 +1423,10 @@ register_session_variable_xact_action(Oid varid,
 	xact_ai->creating_subid = GetCurrentSubTransactionId();
 	xact_ai->deleting_subid = InvalidSubTransactionId;

 	MemoryContextSwitchTo(oldcxt);
 }
@@ -251,7 +1445,10 @@ unregister_session_variable_xact_action(Oid varid,

@@ -270,25 +1467,39 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 {
 	ListCell   *l;

 
-			/*
 
+				/*
@@ -312,8 +1523,63 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 
 /*
@@ -329,7 +1595,7 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 {
 	ListCell   *cur_item;
 
@@ -337,7 +1603,7 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
@@ -349,4 +1615,117 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
+			values[3] = ObjectIdGetDatum(svar->typid);
+				values[4] = PointerGetDatum(cstring_to_text(format_type_be(svar->typid)));
+				nulls[4] = true;
 
 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*

+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }
index 5055904005..e3bde3a948 100644
 
+{ oid => '8488', descr => 'debug list of used session variables',
+  proname => 'pg_debug_show_used_session_variables', prorows => '1000', proretset => 't',
+  provolatile => 's', prorettype => 'record', proargtypes => '',
+  proallargtypes => '{oid,text,text,oid,text,bool,bool,bool,bool}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o}',
+  proargnames => '{varid,schema,name,typid,typname,removed,has_value,can_read,can_write}',
+  prosrc => 'pg_debug_show_used_session_variables' },
 ]
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 83b8afcd6c..8d64853831 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -31,8 +31,8 @@ extern Datum CopySessionVariable(Oid varid, bool *isNull, Oid *typid);
 extern Datum CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid);
 extern Datum GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid);
 
-extern void SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid);
-extern void SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typid);
+extern void SetSessionVariable(Oid varid, Datum value, bool isNull);
+extern void SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull);

 

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

 

 

 

 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.37.2

From 8ba8d15e1e5902fd524a547ba51faeb7ab7e7fc3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/11] catalog support for session variables

 src/backend/catalog/pg_variable.c       | 240 +++++++++++++++
 src/backend/catalog/system_views.sql    |   2 +
 src/include/catalog/pg_variable.h       | 133 +++++++++
 src/test/regress/expected/oidjoins.out  |   4 +
 39 files changed, 1986 insertions(+), 18 deletions(-)

 

 

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index 0000000000..eb09641dc1
@@ -0,0 +1,240 @@
+#include "commands/sequence.h"
+	var->luid = varform->luid;
+	Oid			seqoid;
+	seqoid = get_relname_relid("pg_variable_luid", PG_CATALOG_NAMESPACE);
+	values[Anum_pg_variable_luid - 1] = Int64GetDatum(nextval_internal(seqoid, false));
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 5a844b63a1..209a5ebb98 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1311,3 +1311,5 @@ CREATE VIEW pg_stat_subscription_stats AS
         ss.stats_reset
     FROM pg_subscription as s,
          pg_stat_get_subscription_stats(s.oid) as ss;
+CREATE SEQUENCE pg_variable_luid AS int8;

 
 			/*
 
 			/*

 
 				/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 
-
 /*
 
 	return subname;
 }
+

 	/*

 

 
 
index 0000000000..6789d0fcd2
@@ -0,0 +1,133 @@
+	/* OID of entry in pg_type for variable's type */
+	Oid			vartype BKI_LOOKUP(pg_type);
+	/* long unique id */
+	int64		luid;
+	/* variable name */
+	/* OID of namespace containing variable class */
+	Oid			varnamespace BKI_LOOKUP(pg_namespace);
+	/* typmode for variable's type */
+	int32		vartypmod;
+	/* variable owner */
+	Oid			varowner BKI_LOOKUP(pg_authid);
+	/* variable collation */
+	Oid			varcollation BKI_LOOKUP_OPT(pg_collation);
+	/* Don't allow NULL */
+	/* Don't allow changes */
+	/* action on transaction end */
+	char		vareoxaction;
+	/* access permissions */
+	aclitem		varacl[1] BKI_DEFAULT(_null_);
+DECLARE_UNIQUE_INDEX(pg_variable_varname_luid_index, 9227, VariableLuidIndexId,  on pg_variable using btree(luid int8_ops));
+#define VariableLuidIndexId  9227
+	int64		luid;

+

 

 
 
 extern void InitCatalogCache(void);
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}
-- 
2.37.2

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 27 Aug 2022 13:17:45 +0200

Hi

after some thinking I think that instead of sequence I can use LSN. The
combination oid, LSN should be unique forever

Regards

Pavel
From c17d0cb74faba02551e3326bd5f14249f092825b Mon Sep 17 00:00:00 2001

 

 select a from atacc1;

 

 -- should fail
 LET v1.x = 10;

From c730ccc9a618ed0bdb0d6245ed80fb1afceab613 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 11/11] documentation

 
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 00f833d210..e5294be4a2 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>
@@ -9613,4 +9618,157 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </table>
  </sect1>

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
index dea19cd348..e2191b7767 100644

+     <varlistentry>
index 4fd4bfb3d7..0579b4599d 100644
@@ -137,6 +137,13 @@ REVOKE [ { ADMIN | INHERIT } OPTION FOR ]

From 1ae1f56a10203638e3868cf31a6689c4b1f0d3b3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/11] possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
index d25709ad5f..7ed54325ae 100644
 
+/*
+ * getVariables
 
@@ -14361,6 +14592,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -17902,6 +18136,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 
index 2873b662fb..747308c894 100644

@@ -3263,6 +3289,30 @@ my %tests = (
 		},
 	},
 
@@ -3702,6 +3752,21 @@ my %tests = (
 		like => {},
 	},

From 9d8d155015746c5d5e795220a69444fc5ed1b656 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 08/11] typedefs

---

diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a4a4e356e5..3016c4e23f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -499,6 +499,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
@@ -882,6 +883,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
+FormData_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
@@ -1404,6 +1406,7 @@ LargeObjectDesc
 LastAttnumInfo
 Latch
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
@@ -1786,6 +1789,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
@@ -2464,6 +2468,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
@@ -2649,6 +2654,9 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariable
+SVariableData
+SVariableXActActionItem
 Syn
 SyncOps
 SyncRepConfigData
@@ -2918,6 +2926,7 @@ Variable
 VariableAssignHook
 VariableCache
 VariableCacheData
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.37.2

From f7160c9a68939270f8b11d9dd3be15d5212647c4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 09/11] regress tests for session variables

 
 # ----------

From d3ee3d6dcf170668f02429002a444dbe9087529e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/11] DISCARD VARIABLES command

---

 
index 98c6a5a56b..93cd17a192 100644
@@ -2142,7 +2142,12 @@ DiscardStmt:

index 724b74acb5..6c81f12dc7 100644
index fbfab32d9a..c9bf8434ea 100644

From 3832a4f01b1c21b837ed36041d3e76e69442dbf4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/11] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From bb42f00294a4c18a2604555dc1c738ec98ba634d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 03/11] LET command

index 546e67de8f..db2801011a 100644
@@ -1082,7 +1082,7 @@ SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull)
 
@@ -1390,6 +1390,85 @@ ResetSessionVariables(void)
 	xact_recheck_requests = NIL;
 }
 
+/*
@@ -1692,8 +1771,8 @@ pg_debug_show_used_session_variables(PG_FUNCTION_ARGS)

 

+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
index 0dcaa43492..1589c00a0e 100644
@@ -464,6 +464,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
@@ -823,7 +824,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY KEYS KEEP

 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
@@ -1161,6 +1162,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -12036,6 +12038,7 @@ ExplainableStmt:
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -12066,6 +12069,7 @@ PreparableStmt:
 			| UpdateStmt
 			| DeleteStmt
 			| MergeStmt						/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -12653,6 +12657,49 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -17936,6 +17983,7 @@ unreserved_keyword:
@@ -18542,6 +18590,7 @@ bare_label_keyword:
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
index b2a7237430..36c6b2d63a 100644
 
 	/*

 		case DestTupleQueue:
 
index 58f5c2b69c..724b74acb5 100644
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 /*
 
 /*
 
index a53bb6d52a..fbfab32d9a 100644

 	NodeTag		type;

 

 /* ----------------
-- 
2.37.2

From a9a89795bef28ab71bbcc6abf50ee4f1d836f06f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/11] support of LET command in PLpgSQL

index 1589c00a0e..98c6a5a56b 100644
@@ -901,6 +901,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -1021,6 +1022,13 @@ parse_toplevel:
 
 /*

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

+static void

+static void

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

From c868f5ad89e6a520d62e9d59185ad8dc39fe318c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/11] session variables

 src/backend/commands/session_variable.c | 1462 ++++++++++++++++++++++-
 30 files changed, 2273 insertions(+), 77 deletions(-)

 
 
index 3094bab82a..546e67de8f 100644
@@ -14,51 +14,1153 @@
 
 /*
+ * sessionvars. We maintain four queues (implemented as list
+ * of variable oid, list of RecheckVariableRequests
+	 * The session variable is identified by oid. The oid is unique in catalog.
+	 * Unfortunately, the cleaning memory can be postponed to begin of next
+	 * transaction in session, and theoreticaly, there can be different session
+	 * variable with same oid. So we need another extra identifier that helps
+	 * with identity check. We can use LSN number in session variable create
+	 * time. The value of session variable (in memory) is valid, when there is
+	 * an record in pg_variable with same oid and same create_lsn.
+	XLogRecPtr	create_lsn;		/* used for session's variable identity check */
+	elog(DEBUG1, "pg_variable_cache_callback %u %u", cacheid, hashvalue);
+ * Returns true, when the entry in pg_variable is valid for SVariable
+is_session_variable_valid(SVariable svar)
+	bool		result = false;
+		 * In this case, the only oid cannot be used as unique identifier,
+		if (svar->create_lsn == ((Form_pg_variable) GETSTRUCT(tp))->create_lsn)
+			result = true;
+	elog(DEBUG1, "effective call of sync_sessionvars_all()");
+		bool		remove_var = false;
+				 * In this case, the only oid cannot be used as unique identifier,
+				if (svar->create_lsn != ((Form_pg_variable) GETSTRUCT(tp))->create_lsn)
+					remove_var = true;
+				 * Free memory only when recheck request is from some
+				 * previous transaction. Current transaction can be aborted.
+					remove_var = true;
+			if (remove_var)
+		if (!found || remove_var)
+	svar->create_lsn = var->create_lsn;

 	/*

 
 /*
  * Routines used for manipulation with session variables from
@@ -170,19 +1272,24 @@ DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 }
 
 /*

 	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
@@ -190,6 +1297,18 @@ RemoveSessionVariable(Oid varid)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for variable %u", varid);

 	ReleaseSysCache(tup);
@@ -197,17 +1316,84 @@ RemoveSessionVariable(Oid varid)
 	table_close(rel, RowExclusiveLock);
 
 	/*
 
 /*
 
@@ -221,6 +1407,9 @@ register_session_variable_xact_action(Oid varid,
 	SVariableXActActionItem *xact_ai;
 	MemoryContext oldcxt;

 	xact_ai = (SVariableXActActionItem *)
@@ -231,8 +1420,10 @@ register_session_variable_xact_action(Oid varid,
 	xact_ai->creating_subid = GetCurrentSubTransactionId();
 	xact_ai->deleting_subid = InvalidSubTransactionId;

 	MemoryContextSwitchTo(oldcxt);
 }
@@ -251,7 +1442,10 @@ unregister_session_variable_xact_action(Oid varid,

@@ -270,25 +1464,39 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 {
 	ListCell   *l;

 
-			/*
 
+				/*
@@ -312,8 +1520,57 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
+				if (!is_session_variable_valid(svar))
 
 /*
@@ -329,7 +1586,33 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 {
 	ListCell   *cur_item;
 
@@ -337,7 +1620,8 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
@@ -350,3 +1634,89 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 
 						/*

+	/*

 

 

index 079bd0bfdf..220d38523c 100644
 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*

+/*
 
index 9959f6910e..e1dec6f315 100644
@@ -8084,6 +8086,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
@@ -12758,6 +12768,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

 

+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
index 275355a271..a53bb6d52a 100644

 

 

 
 
 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.37.2

From ae73e551090c4c2884d5bd7751b3338ca9d93d0e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/11] catalog support for session variables

 src/backend/catalog/pg_variable.c       | 236 +++++++++++++++
 src/include/catalog/pg_variable.h       | 130 ++++++++
 38 files changed, 1977 insertions(+), 18 deletions(-)

 

 

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index 0000000000..f2d0875258
@@ -0,0 +1,236 @@
+#include "utils/pg_lsn.h"
+	var->create_lsn = varform->create_lsn;
+	values[Anum_pg_variable_create_lsn - 1] = LSNGetDatum(GetXLogInsertRecPtr());

 
 			/*
 
 			/*
index dacc989d85..d7418e088e 100644

 
 				/*
index b5ab9d9c9a..0dcaa43492 100644
@@ -485,6 +486,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
@@ -654,6 +656,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 %type <node>		json_format_clause_opt
@@ -860,8 +864,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UESCAPE UNBOUNDED UNCONDITIONAL UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING

 
@@ -1119,6 +1123,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -5127,6 +5132,69 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6904,6 +6972,7 @@ object_type_any_name:
 
 /*
@@ -7780,6 +7849,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7825,6 +7902,14 @@ privilege_target:

@@ -8022,6 +8107,7 @@ defacl_privilege_target:

@@ -9804,6 +9890,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10165,6 +10270,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10444,6 +10568,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17972,6 +18104,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -18618,6 +18752,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)
 
index aa00815787..58f5c2b69c 100644

index ea28da26a8..4ead85e538 100644
 
-
 /*
 
 	return subname;
 }
+

 	/*

 

 
 
index 0000000000..f664d5b061
@@ -0,0 +1,130 @@
+	/* Used for identity check [oid, create_lsn] */
+	XLogRecPtr	create_lsn;
+	XLogRecPtr	create_lsn;
index 469a5c46f6..275355a271 100644

+

---

From: Julien Rouhaud <[email-masked]>
Date: Mon, 29 Aug 2022 17:00:12 +0800

Hi,

> combination oid, LSN should be unique forever

Yeah I was about suggesting doing that instead of a sequence, so +1 for that
approach!

I've been spending a bit of time trying to improve the test coverage on the
protection for concurrently deleted and recreated variables, and thought that a
new isolation test should be enough.  I'm attaching a diff (in .txt extension)
that could be applied to 009-regress-tests-for-session-variables.patch, but
while working on that I discovered a few problems.

First, the pg_debug_show_used_session_variables() function reports what's
currently locally known, but there's no guarantee that
AcceptInvalidationMessages() will be called prior to its execution.  For
instance if you're in a transaction and already hold a lock on the function and
execute it again.

It therefore means that it can display that a locally cached variable isn't
dropped and still holds a value, while it's not the case.  While it may be
surprising, I think that's still the wanted behavior as you want to know what
is the cache state.  FTR this is tested in the last permutation in the attached
patch (although the expected output contains the up-to-date information, so you
can see the failure).

But if invalidation are processed when calling the function, the behavior seems
surprising as far as I can see the cleanup seems to be done in 2 steps: mark t
he hash entry as removed and then remove the hash entry.  For instance:

(conn 1) CREATE VARIABLE myvar AS text;
(conn 1) LET myvar = 'something';
(conn 2) DROP VARIABLE myvar;
(conn 1) SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
 schema | name  | removed
--------+-------+---------
 public | myvar | t
(1 row)

(conn 1) SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
 schema | name | removed
--------+------+---------
(0 rows)

Why are two steps necessary here, and is that really wanted?

Finally, I noticed that it's quite easy to get cache lookup failures when using
transactions.  AFAICS it's because the current code first checks in the local
cache (which often isn't immediately invalidated when in a transaction),
returns an oid (of an already dropped variable), then the code acquires a lock
on that non-existent variable, which internally accepts invalidation after the
lock is acquired.  The rest of the code can then fail with some "cache lookup
error" in the various functions as the invalidation has now been processed.
This is also tested in the attached isolation test.

I think that using a retry approach based on SharedInvalidMessageCounter change
detection, like RangeVarGetRelidExtended(), in IdentifyVariable() should be
enough to fix that class of problem, but maybe some other general functions
would need similar protection too.

While looking at the testing, I also noticed that the main regression tests
comments are now outdated since the new (and more permissive) approach for
dropped variable detection.  For instance:

+ ALTER TYPE public.svar_test_type DROP ATTRIBUTE c;
+ -- should to fail
+ SELECT public.svar;
+   svar   
+ ---------
+  (10,20)
+ (1 row)
+ 
+ ALTER TYPE public.svar_test_type ADD ATTRIBUTE c int;
+ -- should to fail too (different type, different generation number);
+ SELECT public.svar;
+    svar   
+ ----------
+  (10,20,)
+ (1 row)

I'm also unsure if this one is showing a broken behavior or not:

+ CREATE VARIABLE public.avar AS int;
+ -- should to fail
+ SELECT avar FROM xxtab;
+  avar
+ ------
+    10
+ (1 row)
+ 
+ -- should be ok
+ SELECT public.avar FROM xxtab;
+  avar
+ ------
+ 
+ (1 row)

For reference, with the code as-is I get the following diff when testing the
attached isolation test:

--- /Users/rjuju/git/postgresql/src/test/isolation/expected/session-variable.out	2022-08-29 15:41:11.000000000 +0800
+++ /Users/rjuju/git/pg/pgmaster_debug/src/test/isolation/output_iso/results/session-variable.out	2022-08-29 15:42:17.000000000 +0800
@@ -16,21 +16,21 @@
 step let: LET myvar = 'test';
 step val: SELECT myvar;
 myvar
 -----
 test
 (1 row)

 step s1: BEGIN;
 step drop: DROP VARIABLE myvar;
 step val: SELECT myvar;
-ERROR:  column or variable "myvar" does not exist
+ERROR:  cache lookup failed for session variable 16386
 step sr1: ROLLBACK;

 starting permutation: let val dbg drop create dbg val
 step let: LET myvar = 'test';
 step val: SELECT myvar;
 myvar
 -----
 test
 (1 row)

@@ -68,20 +68,16 @@
 schema|name |removed
 ------+-----+-------
 public|myvar|f
 (1 row)

 step drop: DROP VARIABLE myvar;
 step create: CREATE VARIABLE myvar AS text
 step dbg: SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
 schema|name |removed
 ------+-----+-------
-public|myvar|t
+public|myvar|f
 (1 row)

 step val: SELECT myvar;
-myvar
------
-
-(1 row)
-
+ERROR:  cache lookup failed for session variable 16389
 step sr1: ROLLBACK;

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 31 Aug 2022 06:23:38 +0200

> create variable x integer;

I changed this

 @@ -58,8 +58,9 @@ LET <replaceable
class="parameter">session_variable</replaceable> = DEFAULT
     <term><literal>sql_expression</literal></term>
     <listitem>
      <para>
-      An SQL expression, in parentheses. The result must be of the same
data type as the session
-      variable.
+      An SQL expression (can be subquery in parenthesis). The result must
+      be of castable to the same data type as the session variable (in
+      implicit or assignment context).
      </para>
     </listitem>
    </varlistentry>

is it ok?

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 1 Sep 2022 20:17:31 +0200

Hi

> pg_debug_show_used_session_variables();

The value is removed in the first command, but at the end of transaction.
pg_debug_show_used_session_variables is called before, and at this moment
the variable should be in memory.

I enhanced pg_debug_show_used_session_variables about debug output for
start and end, and you can see it.

(2022-08-30 19:38:49) postgres=# set client_min_messages to debug1;
SET
(2022-08-30 19:38:55) postgres=# CREATE VARIABLE myvar AS text;
DEBUG:  record for session variable "myvar" (oid:16390) was created in
pg_variable
CREATE VARIABLE
(2022-08-30 19:39:03) postgres=# LET myvar = 'something';
DEBUG:  session variable "public.myvar" (oid:16390) has new entry in memory
(emitted by WRITE)
DEBUG:  session variable "public.myvar" (oid:16390) has new value
LET
(2022-08-30 19:39:11) postgres=# SELECT schema, name, removed FROM
pg_debug_show_used_session_variables();
DEBUG:  pg_variable_cache_callback 84 2941368844
DEBUG:  session variable "public.myvar" (oid:16390) should be rechecked
(forced by sinval)
DEBUG:  pg_debug_show_used_session_variables start
DEBUG:  effective call of sync_sessionvars_all()
DEBUG:  pg_debug_show_used_session_variables end
DEBUG:  session variable "public.myvar" (oid:16390) is removing from memory
┌────────┬───────┬─────────┐
│ schema │ name  │ removed │
╞════════╪═══════╪═════════╡
│ public │ myvar │ t       │
└────────┴───────┴─────────┘
(1 row)

(2022-08-30 19:39:32) postgres=# SELECT schema, name, removed FROM
pg_debug_show_used_session_variables();
DEBUG:  pg_debug_show_used_session_variables start
DEBUG:  pg_debug_show_used_session_variables end
┌────────┬──────┬─────────┐
│ schema │ name │ removed │
╞════════╪══════╪═════════╡
└────────┴──────┴─────────┘
(0 rows)

But I missed call sync_sessionvars_all in the drop variable. If I execute
this routine there I can fix this behavior and the cleaning in
sync_sessionvars_all can be more aggressive.

After change

(2022-08-31 06:25:54) postgres=# let x = 10;
LET
(2022-08-31 06:25:59) postgres=# SELECT schema, name, removed FROM
pg_debug_show_used_session_variables();
┌────────┬──────┬─────────┐
│ schema │ name │ removed │
╞════════╪══════╪═════════╡
│ public │ x    │ f       │
└────────┴──────┴─────────┘
(1 row)

-- after drop in other session

(2022-08-31 06:26:00) postgres=# SELECT schema, name, removed FROM
pg_debug_show_used_session_variables();
┌────────┬──────┬─────────┐
│ schema │ name │ removed │
╞════════╪══════╪═════════╡
└────────┴──────┴─────────┘
(0 rows)

> would need similar protection too.

I did it, and with this change it passed the isolation test. Thank you for
your important help!

> + ALTER TYPE public.svar_test_type DROP ATTRIBUTE c;
the comments are obsolete, fixed

fixed

> /Users/rjuju/git/postgresql/src/test/isolation/expected/session-variable.out
>       2022-08-29 15:41:11.000000000 +0800
> +++
> /Users/rjuju/git/pg/pgmaster_debug/src/test/isolation/output_iso/results/session-variable.out
>      2022-08-29 15:42:17.000000000 +0800
> @@ -16,21 +16,21 @@
attached updated patches

Regards

Pavel
From acff01c966114e2847e785446e46cac22bc54dbe Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 11/11] documentation

 doc/src/sgml/ref/let.sgml                     | 109 +++++++++
 18 files changed, 974 insertions(+), 9 deletions(-)

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
 
index 0000000000..c5d1e4d962
@@ -0,0 +1,109 @@
 
+     <varlistentry>

From 21a549da240dd389e75ef47d8e4bb968591fb1a0 Mon Sep 17 00:00:00 2001

 src/backend/parser/parse_expr.c               |  2 +-
 src/backend/parser/parse_relation.c           |  9 +++--
 src/backend/parser/parse_target.c             |  8 +++-
 18 files changed, 66 insertions(+), 60 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index c757a7b2fe..3711707b48 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -501,7 +501,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
  * Returns true, when expression of kind allows using of
  * session variables.
  */
-static bool
+bool
 expr_kind_allows_session_variables(ParseExprKind p_expr_kind)
 {
 	switch (p_expr_kind)
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index f6b740df0a..e7d791469c 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -27,6 +27,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_enr.h"
+#include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_type.h"
 #include "parser/parsetree.h"
@@ -3666,9 +3667,9 @@ errorMissingColumn(ParseState *pstate,
 		 */
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
-				 relname ?
+				 relname || !expr_kind_allows_session_variables(pstate->p_expr_kind) ?
 				 errmsg("column %s.%s does not exist", relname, colname) :
-				 errmsg("column \"%s\" does not exist", colname),
+				 errmsg("column or variable \"%s\" does not exist", colname),
 				 state->rfirst ? closestfirst ?
 				 errhint("Perhaps you meant to reference the column \"%s.%s\".",
 						 state->rfirst->eref->aliasname, closestfirst) :
@@ -3686,9 +3687,9 @@ errorMissingColumn(ParseState *pstate,
 
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
-				 relname ?
+				 relname || !expr_kind_allows_session_variables(pstate->p_expr_kind) ?
 				 errmsg("column %s.%s does not exist", relname, colname) :
-				 errmsg("column \"%s\" does not exist", colname),
+				 errmsg("column or variable  \"%s\" does not exist", colname),
 				 errhint("Perhaps you meant to reference the column \"%s.%s\" or the column \"%s.%s\".",
 						 state->rfirst->eref->aliasname, closestfirst,
 						 state->rsecond->eref->aliasname, closestsecond),
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 41cb04f4b6..3caa300373 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -786,7 +786,9 @@ transformAssignmentIndirection(ParseState *pstate,
 			if (!typrelid)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("cannot assign to field \"%s\" of column \"%s\" because its type %s is not a composite type",
+						 errmsg(expr_kind_allows_session_variables(pstate->p_expr_kind) ?
+								 "cannot assign to field \"%s\" of column or variable \"%s\" because its type %s is not a composite type" :
+								 "cannot assign to field \"%s\" of column \"%s\" because its type %s is not a composite type",
 								strVal(n), targetName,
 								format_type_be(targetTypeId)),
 						 parser_errposition(pstate, location)));
@@ -795,7 +797,9 @@ transformAssignmentIndirection(ParseState *pstate,
 			if (attnum == InvalidAttrNumber)
 				ereport(ERROR,
 						(errcode(ERRCODE_UNDEFINED_COLUMN),
-						 errmsg("cannot assign to field \"%s\" of column \"%s\" because there is no such column in data type %s",
+						 errmsg(expr_kind_allows_session_variables(pstate->p_expr_kind) ?
+								 "cannot assign to field \"%s\" of column or variable \"%s\" because there is no such column in data type %s" :
+								 "cannot assign to field \"%s\" of column \"%s\" because there is no such column in data type %s",
 								strVal(n), targetName,
 								format_type_be(targetTypeId)),
 						 parser_errposition(pstate, location)));
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index 14b0adb948..76d6e46001 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,5 +22,6 @@ extern PGDLLIMPORT bool session_variables_ambiguity_warning;
 extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKind);
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
+extern bool expr_kind_allows_session_variables(ParseExprKind p_expr_kind);
 
 #endif							/* PARSE_EXPR_H */
 
index d63f4f1cba..c1571b1646 100644
+ERROR:  column (null).test1 does not exist

 select a from atacc1;
 
+ERROR:  column (null).a does not exist
+ERROR:  column (null).........pg.dropped.1........ does not exist
 
index bb4190340e..3db63e02b1 100644
+ERROR:  column (null).c does not exist
+ERROR:  column (null).z does not exist
index 66d8633e3e..2eb5df94e3 100644
+ERROR:  column (null).keyy does not exist

index 55fb9ecb5e..5e1e8c5dd0 100644
 
 -- should fail
 LET v1.x = 10;

From 9a9b6dd54f4d2b491823502fc67d9d4e6a72cb37 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/11] possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 7d6494c45b32ed0ccda57a2a9c68b2be04ca970d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 08/11] typedefs

---

diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8ad112c44d..2558d0fc52 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -499,6 +499,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
@@ -882,6 +883,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
+FormData_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
@@ -1403,6 +1405,7 @@ LargeObjectDesc
 LastAttnumInfo
 Latch
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
@@ -1787,6 +1790,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
@@ -2465,6 +2469,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
@@ -2649,6 +2654,9 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariable
+SVariableData
+SVariableXActActionItem
 Syn
 SyncOps
 SyncRepConfigData
@@ -2918,6 +2926,7 @@ Variable
 VariableAssignHook
 VariableCache
 VariableCacheData
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.37.2

From a40be3b7f52eceb650c54cc72d41d998f2db7ec2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 09/11] regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 .../isolation/expected/session-variable.out   |   86 ++
 src/test/isolation/isolation_schedule         |    1 +
 .../isolation/specs/session-variable.spec     |   34 +
 .../regress/expected/session_variables.out    | 1280 +++++++++++++++++
 src/test/regress/parallel_schedule            |    2 +-
 src/test/regress/sql/session_variables.sql    |  948 ++++++++++++
 6 files changed, 2350 insertions(+), 1 deletion(-)
 create mode 100644 src/test/isolation/expected/session-variable.out
 create mode 100644 src/test/isolation/specs/session-variable.spec
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

diff --git a/src/test/isolation/expected/session-variable.out b/src/test/isolation/expected/session-variable.out
new file mode 100644
index 0000000000..b0059910a1
--- /dev/null
+++ b/src/test/isolation/expected/session-variable.out
@@ -0,0 +1,86 @@
+Parsed test spec with 2 sessions
+
+starting permutation: let val drop val
+step let: LET myvar = 'test';
+step val: SELECT myvar;
+myvar
+-----
+test 
+(1 row)
+
+step drop: DROP VARIABLE myvar;
+step val: SELECT myvar;
+ERROR:  column or variable "myvar" does not exist
+
+starting permutation: let val s1 drop val sr1
+step let: LET myvar = 'test';
+step val: SELECT myvar;
+myvar
+-----
+test 
+(1 row)
+
+step s1: BEGIN;
+step drop: DROP VARIABLE myvar;
+step val: SELECT myvar;
+ERROR:  column or variable "myvar" does not exist
+step sr1: ROLLBACK;
+
+starting permutation: let val dbg drop create dbg val
+step let: LET myvar = 'test';
+step val: SELECT myvar;
+myvar
+-----
+test 
+(1 row)
+
+step dbg: SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
+schema|name |removed
+------+-----+-------
+public|myvar|f      
+(1 row)
+
+step drop: DROP VARIABLE myvar;
+step create: CREATE VARIABLE myvar AS text
+step dbg: SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
+schema|name|removed
+------+----+-------
+(0 rows)
+
+step val: SELECT myvar;
+myvar
+-----
+     
+(1 row)
+
+
+starting permutation: let val s1 dbg drop create dbg val sr1
+step let: LET myvar = 'test';
+step val: SELECT myvar;
+myvar
+-----
+test 
+(1 row)
+
+step s1: BEGIN;
+step dbg: SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
+schema|name |removed
+------+-----+-------
+public|myvar|f      
+(1 row)
+
+step drop: DROP VARIABLE myvar;
+step create: CREATE VARIABLE myvar AS text
+step dbg: SELECT schema, name, removed FROM pg_debug_show_used_session_variables();
+schema|name |removed
+------+-----+-------
+public|myvar|f      
+(1 row)
+
+step val: SELECT myvar;
+myvar
+-----
+     
+(1 row)
+
+step sr1: ROLLBACK;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 529a4cbd4d..96ecf93e32 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -107,3 +107,4 @@ test: cluster-conflict-partition
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: session-variable
diff --git a/src/test/isolation/specs/session-variable.spec b/src/test/isolation/specs/session-variable.spec
new file mode 100644
index 0000000000..a138f0449e
--- /dev/null
+++ b/src/test/isolation/specs/session-variable.spec
@@ -0,0 +1,34 @@
+#
+
+setup
+{
+    CREATE VARIABLE myvar AS text;
+}
+
+teardown
+{
+    DROP VARIABLE IF EXISTS myvar;
+}
+
+session s1
+step s1		{ BEGIN; }
+step let	{ LET myvar = 'test'; }
+step val	{ SELECT myvar; }
+step dbg	{ SELECT schema, name, removed FROM pg_debug_show_used_session_variables(); }
+step sr1	{ ROLLBACK; }
+
+
+session s2
+step drop		{ DROP VARIABLE myvar; }
+step create		{ CREATE VARIABLE myvar AS text }
+
+# Concurrent drop of a known variable should lead to an error
+permutation let val drop val
+# Same, but with an explicit transaction
+permutation let val s1 drop val sr1
+# Concurrent drop/create of a known variable should lead to empty variable
+permutation let val dbg drop create dbg val
+# Concurrent drop/create of a known variable should lead to empty variable
+# We need a transaction to make sure that we won't accept invalidation when
+# calling the dbg step after the concurrent drop
+permutation let val s1 dbg drop create dbg val sr1
index 0000000000..55fb9ecb5e
@@ -0,0 +1,1280 @@
+-- variable is shadowed
+LINE 1: testvar := 1000
+QUERY:  testvar := 1000
+-- should be ok, see the table
 
 # ----------
index 0000000000..23d0ae716c
@@ -0,0 +1,948 @@
+-- variable is shadowed
+-- should be ok, see the table

From 8c7b369e625421b5c17a10e5f042697997dbe0c0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/11] DISCARD VARIABLES command

---

 

From 80c3d23bf9bb338234312ff1fc0800a3b09f5f11 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/11] enhancing psql for session variables

index 61188d96f2..0df9c43088 100644
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From 8ff9c10c8c9f0055338a41c1ab1a281d819e4fc9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/11] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From a2936266b94919b1e703bdf06cdaac26adfa5037 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 03/11] LET command

index b0c40c1ee8..9c4a0575e7 100644
@@ -986,7 +986,7 @@ SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull)
 
@@ -1302,6 +1302,85 @@ ResetSessionVariables(void)
 	xact_recheck_varids = NIL;
 }
 
+/*
@@ -1604,8 +1683,8 @@ pg_debug_show_used_session_variables(PG_FUNCTION_ARGS)

index a013031d22..7eb30a101a 100644

 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+	varid = IdentifyVariable(names, &attrname, &not_unique);

 

 
 
 			/*
 
 			/*
index d761995397..c757a7b2fe 100644
@@ -599,6 +599,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
 			/* okay */
 			break;
@@ -2015,6 +2016,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:
@@ -3390,6 +3392,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 	NodeTag		type;

 

 /* ----------------
-- 
2.37.2

From 5b7d4b7445ee449e3f566c291d80f6401aaf7df5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/11] session variables

 src/backend/commands/session_variable.c | 1348 ++++++++++++++++++++++-
 src/backend/parser/parse_expr.c         |  234 +++-
 30 files changed, 2194 insertions(+), 63 deletions(-)

 
 
index 3094bab82a..b0c40c1ee8 100644
@@ -14,31 +14,104 @@
 
 /*
 
 typedef struct SVariableXActActionItem
@@ -54,12 +127,945 @@ typedef struct SVariableXActActionItem
 	SubTransactionId deleting_subid;
 }  SVariableXActActionItem;
 
+ * to the next transaction, then have to be executed just once time.

+/*
+			xact_recheck_varids = list_append_unique_oid(xact_recheck_varids,
+						 svar->nsname, svar->name, svar->varid);
+	if (!xact_recheck_varids)
@@ -170,18 +1176,31 @@ DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 }
 
 /*
 
-	elog(DEBUG1, "session variable (oid:%u) is deleted from pg_variable", varid);
+	/*
+	 * We want to run initial sessionvars cache synchronization
+	 * before dropping. Then we are sure, so initial sync newer
+	 * process variables dropped in current transactions. Then
+	 * we can do initial sync more agressive.
+	 */
+	sync_sessionvars_all();

@@ -190,6 +1209,18 @@ RemoveSessionVariable(Oid varid)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for variable %u", varid);

 	ReleaseSysCache(tup);
@@ -197,17 +1228,84 @@ RemoveSessionVariable(Oid varid)
 	table_close(rel, RowExclusiveLock);
 
 	/*
 
 /*
 
@@ -221,6 +1319,9 @@ register_session_variable_xact_action(Oid varid,
 	SVariableXActActionItem *xact_ai;
 	MemoryContext oldcxt;

 	xact_ai = (SVariableXActActionItem *)
@@ -231,8 +1332,10 @@ register_session_variable_xact_action(Oid varid,
 	xact_ai->creating_subid = GetCurrentSubTransactionId();
 	xact_ai->deleting_subid = InvalidSubTransactionId;

 	MemoryContextSwitchTo(oldcxt);
 }
@@ -251,7 +1354,10 @@ unregister_session_variable_xact_action(Oid varid,

@@ -270,25 +1376,39 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 {
 	ListCell   *l;

 
-			/*
 
+				/*
@@ -312,8 +1432,55 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 
 /*
@@ -329,7 +1496,7 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 {
 	ListCell   *cur_item;
 
@@ -337,7 +1504,7 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
@@ -349,4 +1516,121 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
+	elog(DEBUG1, "pg_debug_show_used_session_variables start");
+	elog(DEBUG1, "pg_debug_show_used_session_variables end");
 
 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

index aa1b1a76dc..d761995397 100644
 
 /* GUC parameters */

 /*
@@ -493,6 +497,32 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	return result;
 }
 
+/*
+ * Returns true, when expression of kind allows using of
+ * session variables.
+ */
+static bool
+expr_kind_allows_session_variables(ParseExprKind p_expr_kind)
+{
+	switch (p_expr_kind)
+	{
+		case EXPR_KIND_JOIN_USING:
+		case EXPR_KIND_CHECK_CONSTRAINT:
+		case EXPR_KIND_DOMAIN_CHECK:
+		case EXPR_KIND_INDEX_EXPRESSION:
+		case EXPR_KIND_INDEX_PREDICATE:
+		case EXPR_KIND_COLUMN_DEFAULT:
+		case EXPR_KIND_TRIGGER_WHEN:
+		case EXPR_KIND_PARTITION_BOUND:
+		case EXPR_KIND_PARTITION_EXPRESSION:
+		case EXPR_KIND_GENERATED_COLUMN:
+		return false;
+
+		default:
+			return true;
+	}
+}
+
 /*
  * Transform a ColumnRef.
  *
@@ -841,6 +871,150 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 					 parser_errposition(pstate, cref->location)));
 	}
 
+	/*
+	 * There are contexts where session's variables are not allowed.
+	 * The question is if we want to identify session's variables in
+	 * these contexts? The code can be more simple, when we don't do
+	 * it, but then we cannot to raise maybe useful message like
+	 * "you cannot to use session variables here". On second hand, in
+	 * this case the warnings about session's variable shadowing can
+	 * be messy.
+	 */
+	if (expr_kind_allows_session_variables(pstate->p_expr_kind))
+	{
+		Oid			varid = InvalidOid;
+		Oid			typid;
+		Oid			collid;
+		int32		typmod;
+		char	   *attrname = NULL;
+		bool		not_unique;
+
+		/*
+		 * Session variables are shadowed by columns, routine's variables or
+		 * routine's arguments ever. We don't want to use session variable
+		 * when it is not exactly shadowed, but RTE is valid like:
+		 *
+		 *  CREATE TYPE T AS (c int);
+		 *  CREATE VARIABLE foo AS T;
+		 *  CREATE TABLE foo(a int, b int);
+		 *
+		 *  SELECT foo.a, foo.b, foo.c FROM foo;
+		 *
+		 * This case can be messy and then we disallow it. When we know, so
+		 * possible variable will be shadowed, we try to identify variable
+		 * only when session_variables_ambiguity_warning is requested.
+		 */
+		if (node ||
+			(!node && relname && crerr == CRERR_NO_COLUMN))
+		{
+			/*
+			 * In this path we just try (if it is wanted) detect if session
+			 * variable is shadowed.
+			 */
+			if (session_variables_ambiguity_warning)
+			{
+				varid = IdentifyVariable(cref->fields, &attrname, &not_unique);
+
+				/*
+				 * Some cases with ambiguous references can be solved without
+				 * raising a warning. When there is a collision between column
+				 * name (or label) and some session variable name, and when we
+				 * know attribute name, then we can ignore the collision when:
+				 *
+				 *   a) variable is of scalar type (then indirection cannot be
+				 *      applied on this session variable.
+				 *
+				 *   b) when related variable has no field with the given
+				 *      attrname, then indirection cannot be applied on this
+				 *      session variable.
+				 */
+				if (OidIsValid(varid) && attrname && node)
+				{
+					get_session_variable_type_typmod_collid(varid,
+															&typid, &typmod,
+															&collid);
+
+					if (type_is_rowtype(typid))
+					{
+						TupleDesc	tupdesc;
+						bool		found = false;
+						int			i;
+
+						/* slow part, I hope it will not be to often */
+						tupdesc = lookup_rowtype_tupdesc(typid, typmod);
+						for (i = 0; i < tupdesc->natts; i++)
+						{
+							if (namestrcmp(&(TupleDescAttr(tupdesc, i)->attname), attrname) == 0 &&
+								!TupleDescAttr(tupdesc, i)->attisdropped)
+							{
+								found = true;
+								break;
+							}
+						}
+
+						ReleaseTupleDesc(tupdesc);
+
+						/* There is no composite variable with this field. */
+						if (!found)
+							varid = InvalidOid;
+					}
+					else
+						/* There is no composite variable with this name. */
+						varid = InvalidOid;
+				}
+
+				/*
+				 * Raise warning when session variable reference is still
+				 * visible.
+				 */
+				if (OidIsValid(varid))
+				{
+					if (node)
+						ereport(WARNING,
+								(errcode(ERRCODE_AMBIGUOUS_COLUMN),
+								 errmsg("session variable \"%s\" is shadowed",
+										NameListToString(cref->fields)),
+								 errdetail("Session variables can be shadowed by columns, routine's variables and routine's arguments with the same name."),
+								 parser_errposition(pstate, cref->location)));
+					else
+						/* session variable is shadowed by RTE */
+						ereport(WARNING,
+								(errcode(ERRCODE_AMBIGUOUS_COLUMN),
+								 errmsg("session variable \"%s.%s\" is shadowed",
+										get_namespace_name(get_session_variable_namespace(varid)),
+										get_session_variable_name(varid)),
+								 errdetail("Session variables can be shadowed by tables or table's aliases with the same name."),
+								 parser_errposition(pstate, cref->location)));
+				}
+			}
+		}
+		else
+		{
+			varid = IdentifyVariable(cref->fields, &attrname, &not_unique);
+
+			if (OidIsValid(varid))
+			{
+				Oid			typid;
+				int32		typmod;
+				Oid			collid;
+
+				if (not_unique)
+					ereport(ERROR,
+							(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+							 errmsg("session variable reference \"%s\" is ambiguous",
+									NameListToString(cref->fields)),
+							 parser_errposition(pstate, cref->location)));
+
+				get_session_variable_type_typmod_collid(varid, &typid, &typmod,
+														&collid);
+
+				node = makeParamSessionVariable(pstate,
+											   varid, typid, typmod, collid,
+											   attrname, cref->location);
+			}
+		}
+	}
+
 	/*
 	 * Throw error if no translation found.
 	 */
@@ -875,6 +1049,64 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

 

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

 

 

 

 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.37.2

From 84706c4a7e3ff8e652090a91d1924813abcddc77 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/11] catalog support for session variables

 src/backend/catalog/aclchk.c            | 299 +++++++++++++++++
 src/backend/catalog/namespace.c         | 422 ++++++++++++++++++++++++
 src/backend/catalog/objectaddress.c     | 126 ++++++-
 src/backend/catalog/pg_variable.c       | 236 +++++++++++++
 src/backend/commands/session_variable.c | 352 ++++++++++++++++++++
 src/backend/parser/gram.y               | 144 +++++++-
 src/backend/utils/cache/lsyscache.c     |  89 +++++
 38 files changed, 2025 insertions(+), 18 deletions(-)

 

 

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
index fafb9349cc..4296f1d900 100644
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
@@ -2843,6 +2907,353 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
+/*
+ * Returns oid of session variable specified by possibly qualified identifier.
+ *
+ * If not found, returns InvalidOid if missing_ok, else throws error.
+ */
+Oid
+LookupVariable(const char *nspname, const char *varname, bool missing_ok)
+{
+	Oid			namespaceId;
+	Oid			varoid = InvalidOid;
+	ListCell   *l;
+
+	if (nspname)
+	{
+		namespaceId = LookupExplicitNamespace(nspname, missing_ok);
+
+		/*
+		 * If nspname is not a known namespace, then nspname.varname cannot be
+		 * any usable session variable.
+		 */
+		if (!OidIsValid(namespaceId))
+			return InvalidOid;
+
+		varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+								 PointerGetDatum(varname),
+								 ObjectIdGetDatum(namespaceId));
+	}
+	else
+	{
+		/* Iterate over schemas in search_path */
+		recomputeNamespacePath();
+
+		foreach(l, activeSearchPath)
+		{
+			namespaceId = lfirst_oid(l);
+
+			varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+									 PointerGetDatum(varname),
+									 ObjectIdGetDatum(namespaceId));
+
+			if (OidIsValid(varoid))
+				break;
+		}
+	}
+
+	if (!OidIsValid(varoid) && !missing_ok)
+	{
+		if (nspname)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("session variable \"%s.%s\" does not exist",
+							nspname, varname)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("session variable \"%s\" does not exist",
+							varname)));
+	}
+
+	return varoid;
+}
+
+/*
+ * The input list contains names with indirection expressions used as the left
+ * part of LET statement. The following routine returns a new list with only
+ * initial strings (names) - without indirection expressions.
+ */
+List *
+NamesFromList(List *names)
+{
+	ListCell   *l;
+	List	   *result = NIL;
+
+	foreach(l, names)
+	{
+		Node	   *n = lfirst(l);
+
+		if (IsA(n, String))
+		{
+			result = lappend(result, n);
+		}
+		else
+			break;
+	}
+
+	return result;
+}
+
+/*
+ * IdentifyVariable - try to find variable identified by list of names.
+ *
+ * Before this call we don't know, how these fields should be mapped to
+ * schema name, variable name and attribute name. In this routine
+ * we try to apply passed names to all possible combinations of schema name,
+ * variable name and attribute name, and we count valid combinations.
+ *
+ * Returns oid of identified variable. When last field of names list is
+ * identified as an attribute, then output attrname argument is set to
+ * an string of this field.
+ *
+ * When there is not any valid combination, then we are sure, so the
+ * list of names cannot to identify any session variable. In this case
+ * we return InvalidOid.
+ *
+ * We can find more valid combination than one.
+ * Example: users can have session variable x in schema y, and
+ * session variable y with attribute x inside some schema from
+ * search path. In this situation the meaning of expression "y"."x"
+ * is ambiguous. In this case this routine returns InvalidOid, and
+ * sets the output parameter "not_unique" to true. This parameter is
+ * used for more meaningfull error message.
+ *
+ * The AccessShareLock is created on related session variable. The lock
+ * will be kept for the whole transaction.
+ */
+Oid
+IdentifyVariable(List *names, char **attrname, bool *not_unique)
+{
+	Node	   *field1 = NULL;
+	Node	   *field2 = NULL;
+	Node	   *field3 = NULL;
+	Node	   *field4 = NULL;
+	char	   *a = NULL;
+	char	   *b = NULL;
+	char	   *c = NULL;
+	char	   *d = NULL;
+	Oid			varoid_without_attr = InvalidOid;
+	Oid			varoid_with_attr = InvalidOid;
+	Oid			varid = InvalidOid;
+	Oid			old_varid = InvalidOid;
+	uint64		inval_count;
+	bool		retry = false;
+
+	*not_unique = false;
+	*attrname = NULL;
+
+	/*
+	 * 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 (;;)
+	{
+		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))
+				{
+					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. Second node can be star. In
+					 * this case, the only allowed possibility is "variable"."*".
+					 */
+					varoid_without_attr = LookupVariable(a, b, true);
+					varoid_with_attr = LookupVariable(NULL, a, true);
+				}
+				else
+				{
+					Assert(IsA(field2, A_Star));
+
+					/*
+					 * Session variables doesn't support unboxing by star syntax. But
+					 * this syntax have to be calculated here, because can come from
+					 * non session variables related expressions.
+					 */
+					return InvalidOid;
+				}
+
+				if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+				{
+					*not_unique = true;
+					return InvalidOid;
+				}
+				else if (OidIsValid(varoid_without_attr))
+				{
+					*attrname = NULL;
+					varid = varoid_without_attr;
+				}
+				else
+				{
+					*attrname = b;
+					varid = varoid_with_attr;
+				}
+				break;
+
+			case 3:
+				field1 = linitial(names);
+				field2 = lsecond(names);
+				field3 = lthird(names);
+
+				Assert(IsA(field1, String));
+				Assert(IsA(field2, String));
+
+				a = strVal(field1);
+				b = strVal(field2);
+
+				if (IsA(field3, String))
+				{
+					c = strVal(field3);
+
+					/*
+					 * a.b.c can mean "catalog"."schema"."variable" or
+					 * "schema"."variable"."field", Check both variants, and returns
+					 * InvalidOid with not_unique flag, when both interpretations are
+					 * possible. When third node is star, the only possible
+					 * interpretation is "schema"."variable"."*".
+					 */
+					varoid_without_attr = LookupVariable(b, c, true);
+					varoid_with_attr = LookupVariable(a, b, true);
+				}
+				else
+				{
+					Assert(IsA(field3, A_Star));
+					return InvalidOid;
+				}
+
+				if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+				{
+					*not_unique = true;
+					return InvalidOid;
+				}
+				else if (OidIsValid(varoid_without_attr))
+				{
+
+					/*
+					 * In this case, "a" is used as catalog name - check it.
+					 */
+					if (strcmp(a, get_database_name(MyDatabaseId)) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cross-database references are not implemented: %s",
+										NameListToString(names))));
+
+					varid = varoid_without_attr;
+				}
+				else
+				{
+					*attrname = c;
+					varid = varoid_with_attr;
+				}
+				break;
+
+			case 4:
+				field1 = linitial(names);
+				field2 = lsecond(names);
+				field3 = lthird(names);
+				field4 = lfourth(names);
+
+				Assert(IsA(field1, String));
+				Assert(IsA(field2, String));
+				Assert(IsA(field3, String));
+
+				a = strVal(field1);
+				b = strVal(field2);
+				c = strVal(field3);
+
+				/*
+				 * In this case, "a" is used as catalog name - check it.
+				 */
+				if (strcmp(a, get_database_name(MyDatabaseId)) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("cross-database references are not implemented: %s",
+									NameListToString(names))));
+
+				if (IsA(field4, String))
+				{
+					d = strVal(field4);
+				}
+				else
+				{
+					Assert(IsA(field4, A_Star));
+					return InvalidOid;
+				}
+
+				*attrname = d;
+				varid = LookupVariable(b, c, true);
+				break;
+
+			default:
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("improper qualified name (too many dotted names): %s",
+								NameListToString(names))));
+				break;
+		}
+
+		/*
+		 * 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 relation 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 relation.  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;
+	}
+
+	return varid;
+}
 
 /*
  * DeconstructQualifiedName
@@ -4660,3 +5071,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*

 
 				/*

 

 
 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
index fd71a9b13e..cf3d3d3109 100644
 
-
 /*
 
 	return subname;
 }
+

 	/*

index 1bc55c01a5..fda11c8548 100644

+extern Oid	IdentifyVariable(List *names, char **attrname, bool *not_unique);

 

+

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 2 Sep 2022 07:42:00 +0200

Hi

yesterday, I did few stupid errors. Fixed now.

rebased today

Regards

Pavel
From 081516fd4305a5f27a7c88799d3e4f483043dc55 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 11/11] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+     <varlistentry>

From 9e890f9598c22645cbda51b089b50e48e54da092 Mon Sep 17 00:00:00 2001

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

index d7dbbc8fe5..056665d391 100644
@@ -439,7 +439,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
index f44937a8bb..43b660eba2 100644
@@ -3667,7 +3668,9 @@ errorMissingColumn(ParseState *pstate,
+				 (!expr_kind_allows_session_variables(pstate->p_expr_kind) ?
+				  errmsg("column \"%s\" does not exist", colname) :
+				  errmsg("column or variable \"%s\" does not exist", colname)),
@@ -3687,7 +3690,9 @@ errorMissingColumn(ParseState *pstate,
+				 (!expr_kind_allows_session_variables(pstate->p_expr_kind) ?
+				  errmsg("column \"%s\" does not exist", colname) :
+				  errmsg("column or variable  \"%s\" does not exist", colname)),
index a0fef7e6d4..6e06259a48 100644

 
index d63f4f1cba..95a52e3400 100644

 select a from atacc1;

 

 -- should fail
 LET v1.x = 10;

From a5b50811c55ec006ee0008d7a739b9e98992eee7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 08/11] typedefs

---

diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5b3b305963..6e4e1e3fc5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -499,6 +499,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
@@ -882,6 +883,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
+FormData_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
@@ -1389,6 +1391,7 @@ LargeObjectDesc
 LastAttnumInfo
 Latch
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
@@ -1773,6 +1776,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
@@ -2451,6 +2455,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
@@ -2635,6 +2640,9 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariable
+SVariableData
+SVariableXActActionItem
 Syn
 SyncOps
 SyncRepConfigData
@@ -2903,6 +2911,7 @@ Variable
 VariableAssignHook
 VariableCache
 VariableCacheData
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.37.2

From 4776d06c955a86ffb4a785b0c981abc8857681fc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/11] possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 6bdb498692fe966f219cc594240a1752320f7ac0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 09/11] regress tests for session variables

index 9f644a0c1b..7426b39ab3 100644
@@ -119,7 +119,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath
 
 # ----------

From 6f2f53cd77cd41beb6a7a0fff16953e0a169c3da Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/11] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From e863fc8625c0828f863be77c10cff17c5b92e41f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/11] DISCARD VARIABLES command

---

 
index 2b57daa264..745f2cbbf6 100644
@@ -2027,7 +2027,12 @@ DiscardStmt:

index 52b2a037f6..c421b92816 100644
@@ -3568,7 +3568,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;

From 9c096f4edecf8c7ac3bf4ab2aebf61600fe1b03c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/11] support of LET command in PLpgSQL

index bd3a81bf16..2b57daa264 100644
@@ -798,6 +798,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -906,6 +907,13 @@ parse_toplevel:
 
 /*
index 50227cc098..77037797f2 100644

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

+static void

+static void

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

From f52328cb83ce3770ba2b468fdca4b107f2933f64 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 03/11] LET command

index 053d2ca5ae..1e93cb1523 100644
index fc570a8ecc..d5febfef75 100644

+/*

index 3bac350bf5..1b5dfa174a 100644
@@ -3822,6 +3822,16 @@ raw_expression_tree_walker(Node *node,

 
+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
index 1b8b874164..bd3a81bf16 100644
@@ -309,7 +309,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
@@ -720,7 +721,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY

 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
@@ -1046,6 +1047,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -11921,6 +11923,7 @@ ExplainableStmt:
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -11951,6 +11954,7 @@ PreparableStmt:
 			| UpdateStmt
 			| DeleteStmt
 			| MergeStmt						/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -12538,6 +12542,49 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -16908,6 +16955,7 @@ unreserved_keyword:
@@ -17477,6 +17525,7 @@ bare_label_keyword:
 
 			/*
 
 			/*
index d8287ac134..d7dbbc8fe5 100644
@@ -537,6 +537,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
 			/* okay */
 			break;
@@ -1953,6 +1954,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:
@@ -3328,6 +3330,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 
 			/*
index 4e1593d900..a0fef7e6d4 100644
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*
 
index 8e15d40963..52b2a037f6 100644
@@ -1699,6 +1699,21 @@ typedef struct MergeStmt

 	NodeTag		type;

 
index dfe3cfc437..84266519b3 100644
@@ -237,6 +237,7 @@ PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD, BARE_LABEL)

 /* ----------------
-- 
2.37.2

From b0a64f8345b01eb34af3288456cb6b496094b111 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/11] session variables

 

index 3094bab82a..fc570a8ecc 100644
 
 /*

 
 
+/*
 
 /*

 

 	ReleaseSysCache(tup);
 
 	/*
 
 /*

 

 

 
 
-			/*
 
+				/*
 
 /*
 
index 25a94bbaaa..953b139058 100644
 
 						/*
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 9b9bbf00a9..bcf5489cc2 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -59,6 +59,7 @@
 #include "access/heaptoast.h"
 #include "catalog/pg_type.h"
 #include "commands/sequence.h"
+#include "commands/session_variable.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -446,6 +447,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_PARAM_EXEC,
 		&&CASE_EEOP_PARAM_EXTERN,
 		&&CASE_EEOP_PARAM_CALLBACK,
+		&&CASE_EEOP_PARAM_VARIABLE,
 		&&CASE_EEOP_CASE_TESTVAL,
 		&&CASE_EEOP_MAKE_READONLY,
 		&&CASE_EEOP_IOCOERCE,
@@ -1082,6 +1084,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}

+	/*

 

 
 
index 95d0807bdd..c5f824c0bd 100644

 	/*

+	/*

+/*
 
 /*
  * fix_param_node
 
index 5c54171fee..859bf83391 100644
@@ -810,16 +811,17 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 
 	/*

@@ -2229,6 +2231,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
@@ -2351,6 +2354,29 @@ eval_const_expressions_mutator(Node *node,
 
 				/*
@@ -4735,22 +4761,45 @@ substitute_actual_parameters_mutator(Node *node,

 

 

 

 

 

index 3527b78695..d8287ac134 100644
@@ -37,11 +37,12 @@
 #include "utils/date.h"
 
 /* GUC parameters */
 
@@ -83,6 +84,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,

 /*
@@ -431,6 +435,32 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	return result;
 }
 
+/*
@@ -779,6 +809,150 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
+	/*
@@ -813,6 +987,64 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
 
index 6594a9aac7..04f4b42fb6 100644
@@ -496,6 +497,7 @@ static char *generate_function_name(Oid funcid, int nargs,
@@ -8073,6 +8075,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
@@ -12087,6 +12097,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 	/*
 
 /*
 
 	return false;
 }
index a757ade8bc..e3c1af3082 100644
@@ -11823,4 +11823,11 @@

 
 extern void AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit);
 extern void AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySubid,
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index e14f15d435..c7137a9b12 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -158,6 +158,7 @@ typedef enum ExprEvalOp
 	EEOP_PARAM_EXEC,
 	EEOP_PARAM_EXTERN,
 	EEOP_PARAM_CALLBACK,
+	EEOP_PARAM_VARIABLE,
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -382,6 +383,13 @@ typedef struct ExprEvalStep
 			Oid			paramtype;	/* OID of parameter's datatype */
 		}			param;
 
@@ -741,6 +749,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,

 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
index 7c33a05c51..8e15d40963 100644

 

 

index 40661334bb..75d8daf267 100644
 
@@ -296,13 +299,17 @@ typedef struct Const
 
 typedef struct Param
@@ -313,6 +320,7 @@ typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.37.2

From b193ec9852730578f0cdfbb1207b9ab3f6cb70ba Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/11] catalog support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*

 
 				/*
index 0492ff9a66..1b8b874164 100644
@@ -297,8 +298,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 /*
@@ -757,8 +761,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UESCAPE UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING

 
@@ -1004,6 +1008,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -5012,6 +5017,69 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6789,6 +6857,7 @@ object_type_any_name:
 
 /*
@@ -7665,6 +7734,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7710,6 +7787,14 @@ privilege_target:

@@ -7907,6 +7992,7 @@ defacl_privilege_target:

@@ -9689,6 +9775,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10050,6 +10155,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10329,6 +10453,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -16936,6 +17068,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -17545,6 +17679,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)
index 7aaf1c673f..3527b78695 100644
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
@@ -505,6 +506,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
@@ -1726,6 +1729,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;
@@ -3066,6 +3070,7 @@ ParseExprKindName(ParseExprKind exprKind)

 
-
 /*
 
 	return subname;
 }
+

 	/*

 

 
index a07e737a33..a757ade8bc 100644
 
index 6958306a7d..7c33a05c51 100644
@@ -1907,6 +1907,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -3034,6 +3035,25 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+
index 9a7cc0c6bd..dfe3cfc437 100644
@@ -453,6 +453,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)

---

From: Julien Rouhaud <[email-masked]>
Date: Sat, 3 Sep 2022 23:00:51 +0800

Hi,

> rebased today

After some off-list discussion with Pavel, I'm sending some proposal patches
(in .txt extension) to apply to the last patchset.

To sum up, when a session issues a DROP VARIABLE, the session will receive an
sinval notification for its own drop and we don't want to process it
immediately as we need to hold the value in case the transaction is rollbacked.
The current patch avoided that by forcing a single processing of sinval per
transaction, and forcing it before dropping the variable.  It works but it
seems to me that postponing all but the first VARIABLEOID sinval to the next
transaction is a big hammer, and the sooner we can free some memory the better.

For an alternative approach the attached patch store the lxid in the SVariable
itself when dropping a currently set variable, so we can process all sinval and
simply defer to the next transaction the memory cleanup of the variable(s) we
know we just dropped.  What do you think of that approach?

As I was working on some changes I also made a pass on session_variable.c.  I
tried to improve a bit some comments, and also got rid of the "first_time"
variable.  The name wasn't really great, and AFAICS it can be replaced by
testing whether the memory context has been created yet or not.

But once that done I noticed the get_rowtype_value() function.  I don't think
this function is necessary as the core code already knows how to deal with
stored datum when the underlying composite type was modified.  I tried to
bypass that function and always simply return the stored value and all the
tests run fine.  Is there really any cases when this extra code is needed?

FTR I tried to do a bunch of additional testing using relation as base type for
variable, as you can do more with those than plain composite types, but it
still always works just fine.

However, while doing so I noticed that find_composite_type_dependencies()
failed to properly handle dependencies on relation (plain tables, matviews and
partitioned tables).  I'm also adding 2 additional patches to fix this corner
case and add an additional regression test for the plain table case.

---

From: Julien Rouhaud <[email-masked]>
Date: Sun, 4 Sep 2022 12:31:20 +0800

> case and add an additional regression test for the plain table case.

I forgot to mention this chunk:

+	/*

I agree that storing the domain_check_extra in the transaction context sounds
sensible, but the memory context handling is not quite right.

Looking at domain_check, it doesn't change the current memory context, so as-is
all the code related to oldcxt is unnecessary.

Some other callers like expandedrecord.c do switch to a short lived context to
limit the lifetime of the possible leak by the expression evaluation, but I
don't think that's an option here.

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 4 Sep 2022 21:27:02 +0200

> > tests run fine.  Is there really any cases when this extra code is

Yes, it can works because there is not visible difference between NULL and
dropped columns, and real number of attributes is saved in HeapTupleHeader

so I removed this function and related code

removed

> don't think that's an option here.

merged your patches, big thanks

Regards

Pavel
From bead1a6b8e94efa459c63bb382a1836e29712e2b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 09/11] regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 .../isolation/expected/session-variable.out   |   86 ++
 src/test/isolation/isolation_schedule         |    1 +
 .../isolation/specs/session-variable.spec     |   34 +
 .../regress/expected/session_variables.out    | 1285 +++++++++++++++++
 src/test/regress/parallel_schedule            |    2 +-
 src/test/regress/sql/session_variables.sql    |  954 ++++++++++++
 6 files changed, 2361 insertions(+), 1 deletion(-)
 create mode 100644 src/test/isolation/expected/session-variable.out
 create mode 100644 src/test/isolation/specs/session-variable.spec
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

index 0000000000..fe47339110
+# Test session variables memory cleanup for sinval
index 0000000000..a4163e781a
@@ -0,0 +1,1285 @@
+-- test for dependency on relation
+CREATE VARIABLE v_table AS svar_test;
+ALTER TABLE svar_test ALTER COLUMN a TYPE text;
+ERROR:  cannot alter table "svar_test" because session variable "svartest.v_table" uses it
+DROP VARIABLE v_table;
 
 # ----------
index 0000000000..d05c28d3eb
@@ -0,0 +1,954 @@
+-- test for dependency on relation
+CREATE VARIABLE v_table AS svar_test;
+ALTER TABLE svar_test ALTER COLUMN a TYPE text;
+DROP VARIABLE v_table;

From 1e92fb72846be53fe99c2b6e10dc722afbdf8199 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:52:58 +0200
Subject: [PATCH 08/11] typedefs

---

From fe2fe55145326bc64b6d1ac80521fc0f88320749 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 11/11] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+     <varlistentry>

From fffa8d21b374c9c9503e56f3490b401afec0f660 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/11] possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 8de47d0192e7a83c8fc09f4c4fbd62f0a74f045e Mon Sep 17 00:00:00 2001

 

 
 
 select a from atacc1;

 
 
index a4163e781a..99e433a762 100644
 
 -- should fail
 LET v1.x = 10;

From 0e56d5fb78ad50c9780883c64b642a05fb35e594 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/11] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From 6e7cc20981a41e12c32933b28f45e573d4bd477f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/11] DISCARD VARIABLES command

---

 

From 0a9c89b4398c9ab599cbdf2590ac2554b5cb41c8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/11] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From c5ea8024bcaaa1e63492070e0a9d6aa36e835e28 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 03/11] LET command

index f4a794a0fa..4a79ba8307 100644
@@ -792,7 +792,7 @@ SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull)
 
@@ -1128,6 +1128,85 @@ ResetSessionVariables(void)
 	xact_recheck_varids = NIL;
 }
 
+/*
@@ -1430,8 +1509,8 @@ pg_debug_show_used_session_variables(PG_FUNCTION_ARGS)

 

+		case T_LetStmt:
 
 			/*

 
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 	NodeTag		type;

 

 /* ----------------
-- 
2.37.2

From bbed39665dbc70d7468505990bd43e3bd13e7aa1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 11 Apr 2022 20:32:05 +0200
Subject: [PATCH 02/11] session variables

 src/backend/commands/session_variable.c | 1174 ++++++++++++++++++++++-
 src/backend/parser/parse_expr.c         |  234 ++++-
 30 files changed, 2020 insertions(+), 63 deletions(-)

 
 
index 3094bab82a..f4a794a0fa 100644
@@ -14,31 +14,105 @@
 
 /*
- * The life cycle of temporary session variable can be
- * limmited by using clause ON COMMIT DROP.
+ * Values of session variables are stored in the backend local memory,
+ * in sessionvars hash table in binary format, in a dedicated memory
+ * context SVariableMemoryContext.  A session variable value can stay
+ * valid for longer than a transaction.  To make sure that the
+ * underlying memory is freed, we need to solve following issues:
+ *
+ * - We need to free local memory when the variable is droppedn,
+ *  whether in the current transaction in the current session or by
+ *  other sessions (other users).  To protect the content against
+ *  possibly rollbacked DROP VARIABLE commands, the memory shouldn't
+ *  be freed immediately but be postponed until the end of the
+ *  transaction.
+ *
+ * - The session variable can be dropped explicitly (by DROP VARIABLE
+ *  command) or implicitly (using ON COMMIT DROP clause), and the
+ *  value can be implicitly removed (using the ON TRANSACTION END
+ *  clause).  In all those cases the memory should be freed at the
+ *  transaction end.
+ *
+ * To achieve that, we maintain 4 queues (implemented as list
+ * of variable oid, list of RecheckVariableRequests
+ * (requests are created in reaction on sinval) and lists
+ * of actions). List of actions are used whe we need to calculate
+ * the final transaction state of an entry's creator's
+ * subtransaction. List of session variables are used, when we
+ * don't need to do this calculation (we know, so. every values
+ * of session variables created with clauses ON COMMIT DROP or
+ * ON TRANSACTION END RESET will be purged from memory), but
+ * ON COMMIT DROP action or ON COMMIT RESET action can be done only
+ * when related sub transaction is committed (we don't want to lost
+ * value after aborted DROP VARIABLE command, we don't want to delete
+ * record from system catalog after aborted CREATE VARIABLE command,
+ * or we don't want to drop record from system catalog implicitly
+ * when TEMP ON COMMIT DROP variable was successfully dropped).
+ * Recheck of session's variable existence should be executed
+ * only when some operation over session's variable is executed
+ * first time in transaction or at the end of transaction. It
+ * should not be executed more times inside transaction.
+ *
+ * Although the variable's reset doesn't need full delete from
+ * sessionvars hash table, we do. It is the most simple solution,
+ * and it reduces possible state's space (and reduces sessionvars
+ * bloating).
+ *
+ * There are two different ways to do final access to session
+ * variables: buffered (indirect) or direct. Buffered access is used
+ * in queries, where we have to ensure an stability of passed values
+ * (then the session variable has same behaviour like external query
+ * parameters, what is consistent with using PL/pgSQL's variables in
+ * embedded queries in PL/pgSQL).
+ *
+ * This is implemented by using an aux buffer (an array) that holds a
+ * copy of values of used (in query) session variables. In the final
+ * end, the values from this array are passed as constant (EEOP_CONST).
+ *
+ * Direct access is used by simple expression evaluation (PLpgSQL).
+ * In this case we don't need to ensure the stability of passed
+ * values, and maintaining the buffer with copies of values of session
+ * variables can be useless overhead. In this case we just read the
+ * value of the session variable directly (EEOP_PARAM_VARIABLE). This
+ * strategy removes the necessity to modify related PL/pgSQL code to
+ * support session variables (the reading of session variables is
+ * fully transparent for PL/pgSQL).
  */
 typedef enum SVariableXActAction
 {
 	SVAR_ON_COMMIT_DROP,		/* used for ON COMMIT DROP */
+	SVAR_ON_COMMIT_RESET,		/* used for DROP VARIABLE */
 } SVariableXActAction;
 
 typedef struct SVariableXActActionItem
@@ -54,12 +128,763 @@ typedef struct SVariableXActActionItem
 	SubTransactionId deleting_subid;
 }  SVariableXActActionItem;
 
+ * dropped by other's sessions too, so we have to watch sinval message.
+ * current transactions is in aborted state.
+	char	   *name;			/* session variable name (at time of
+								   initialization) */
+	 * Top level local transaction id of the last transaction that dropped the
+	 * variable if any.  We need this information to avoid freeing memory for
+	 * variable dropped by the local backend that may be rollbacked.
+	LocalTransactionId drop_lxid;

+/*
+		 * over all locally set session variables, because hashvalue
+ * any read or any write from/to session variable.
+	 * This routine is called before any reading, so the
+		 * Remove invalid variables, but don't touch variables that were
+		 * dropped by the current top level local transaction, as there's no
+		 * guarantee that the transaction will be committed.  Such variables
+		 * will be removed in the next transaction if needed.
+			 * If this is a variable dropped by the current transaction, ignore
+			 * it and keep the oid to recheck in the next transaction.
+			if (svar->drop_lxid == MyProc->lxid)
+		xact_recheck_varids = foreach_delete_current(xact_recheck_varids, l);
+	Assert(!sessionvars);
+	if (!SVariableMemoryContext)
+		/* We need our own long lived memory context */
+	Datum		result;
+	/* force copy of not null value */
+		*isNull = false;
+		result = (Datum) 0;
+		*isNull = true;
+	Datum		result;
+		*isNull = false;
+		result = (Datum) 0;
+		*isNull = true;
@@ -170,18 +995,31 @@ DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 }
 
 /*

 
@@ -190,6 +1028,18 @@ RemoveSessionVariable(Oid varid)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for variable %u", varid);

 	ReleaseSysCache(tup);
@@ -197,17 +1047,91 @@ RemoveSessionVariable(Oid varid)
 	table_close(rel, RowExclusiveLock);
 
 	/*
-	 * We removed entry from catalog already, we should not do it
-	 * again at end of xact time.
+	 * We removed entry from catalog already, we must not do it
+	 * again at end of xact time
 	 */
 	unregister_session_variable_xact_action(varid, SVAR_ON_COMMIT_DROP);
+
+	if (sessionvars)
+	{
+		svar = (SVariable) hash_search(sessionvars, &varid,
+											HASH_FIND, &found);
+
+		if (found)
+		{
+			/* Save the current top level local transaction id to make sure we
+			 * don't automatically remove the local variable storage in
+			 * sync_sessionvars_all, as the DROP VARIABLE will send an
+			 * invalidation message.
+			 */
+			Assert(LocalTransactionIdIsValid(MyProc->lxid));
+			svar->drop_lxid = MyProc->lxid;
+
+			/*
+			 * For variables that are not purged by default we need to register
+			 * an SVAR_ON_COMMIT_RESET action to free the local memory for this
+			 * variable when this transaction or subtransaction is committed
+			 * (we don't need to wait for sinval message).  The cleaning action
+			 * for one session variable can be repeated in the action list
+			 * without causing any problem, so we don't need to ensure
+			 * uniqueness.  We need a different action from RESET, because
+			 * RESET is executed on any transaction end, but we want to execute
+			 * cleaning only when the current transaction will be committed.
+			 * This action can be reverted by ABORT of DROP VARIABLE command.
+			 */
+			if (!svar->eox_reset)
+				register_session_variable_xact_action(varid,
+													  SVAR_ON_COMMIT_RESET);
+		}
+	}
+}
+
+/*
+ * Fast drop of the complete content of all session variables hash table.
+ * This is code for DISCARD VARIABLES command. This command
+ * cannot be run inside transaction, so we don't need to handle
+ * end of transaction actions.
+ */
+void
+ResetSessionVariables(void)
+{
+	/* Destroy hash table and reset related memory context */
+	if (sessionvars)
+	{
+		hash_destroy(sessionvars);
+		sessionvars = NULL;
+
+		hash_destroy(sessionvars_types);
+		sessionvars_types = NULL;
+	}
+
+	/* Release memory allocated by session variables */
+	if (SVariableMemoryContext != NULL)
+		MemoryContextReset(SVariableMemoryContext);
+
+	/*
+	 * There are not any session variables left, so simply trim
+	 * both xact action lists.
+	 */
+	list_free_deep(xact_on_commit_drop_actions);
+	xact_on_commit_drop_actions = NIL;
+
+	list_free_deep(xact_on_commit_reset_actions);
+	xact_on_commit_reset_actions = NIL;
+
+	/* We should clean xact_reset_varids */
+	list_free(xact_reset_varids);
+	xact_reset_varids = NIL;
+
+	/* we should clean xact_recheck_varids */
+	list_free(xact_recheck_varids);
+	xact_recheck_varids = NIL;
 }
 
 /*
 
@@ -221,6 +1145,9 @@ register_session_variable_xact_action(Oid varid,
 	SVariableXActActionItem *xact_ai;
 	MemoryContext oldcxt;

 	xact_ai = (SVariableXActActionItem *)
@@ -231,8 +1158,10 @@ register_session_variable_xact_action(Oid varid,
 	xact_ai->creating_subid = GetCurrentSubTransactionId();
 	xact_ai->deleting_subid = InvalidSubTransactionId;

 	MemoryContextSwitchTo(oldcxt);
 }
@@ -251,7 +1180,10 @@ unregister_session_variable_xact_action(Oid varid,

@@ -270,25 +1202,39 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 {
 	ListCell   *l;

 
-			/*
 
+				/*
@@ -312,8 +1258,55 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 
 /*
@@ -329,7 +1322,33 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 {
 	ListCell   *cur_item;
 
@@ -337,7 +1356,8 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
@@ -350,3 +1370,93 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 
 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }
index c336698ad5..afcf3afb56 100644
@@ -1728,6 +1728,18 @@ static struct config_bool ConfigureNamesBool[] =

 

 

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

 

 

 

 typedef struct Param

 
 /* GUC parameters */

 
 
-- 
2.37.2

From e41e14ead1dc2a499e7f0f6360635ecb9b20581a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/11] catalog support for session variables

 src/backend/commands/tablecmds.c        |  43 ++-
 38 files changed, 2041 insertions(+), 18 deletions(-)

 

 

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*
index dacc989d85..65e1c1e670 100644
@@ -6496,9 +6499,46 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
+		/* Don't allow change of type usedby session's variable */
+		if (pg_depend->classid == VariableRelationId)
+		{
+			Oid		varid = pg_depend->objid;
+
+			if (origTypeName)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it",
+								origTypeName,
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter foreign table \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_RELATION ||
+					origRelation->rd_rel->relkind == RELKIND_MATVIEW ||
+					origRelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter table \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+		}
+
 		/* Else, ignore dependees that aren't user columns of relations */
 		/* (we assume system columns are never of interesting types) */
-		if (pg_depend->classid != RelationRelationId ||
+		else if (pg_depend->classid != RelationRelationId ||
 			pg_depend->objsubid <= 0)
 			continue;
 
@@ -12682,6 +12722,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_PUBLICATION_REL:
 			case OCLASS_SUBSCRIPTION:
 			case OCLASS_TRANSFORM:
+			case OCLASS_VARIABLE:
 
 				/*

 /*

 

 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 

-
 /*
 
 	return subname;
 }
+

 	/*

 

 

 
+

---

From: Julien Rouhaud <[email-masked]>
Date: Tue, 6 Sep 2022 18:23:12 +0800

On Tue, Sep 06, 2022 at 08:43:59AM +0200, Pavel Stehule wrote:
> Hi
>
> After talking with Julian I removed "debug" fields name and nsname from
> SVariable structure. When it is possible it is better to read these fields
> from catalog without risk of obsoletely or necessity to refresh these
> fields. In other cases we display only oid of variable instead name and
> nsname (It is used just for debug purposes).

Thanks!  I'm just adding back the forgotten Cc list.

---

From: Julien Rouhaud <[email-masked]>
Date: Thu, 8 Sep 2022 15:18:43 +0800

Hi,

> Thanks!  I'm just adding back the forgotten Cc list.

About the last change:

+static void
+pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
+{
[...]
+           elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) should be rechecked (forced by sinval)",
+                        get_namespace_name(get_session_variable_namespace(svar->varid)),
+                        get_session_variable_name(svar->varid),
+                        svar->varid);

There's no guarantee that the variable still exists in cache (for variables
dropped in the current transaction), or even that the callback is called while
in a transaction state, so we should only display the oid here.

FTR just to be sure I ran all the new regression tests (with this fix) with CCA
and log_min_messages = DEBUG1 and it didn't hit any problem, so it doesn't seem
that there's any other issue hidden somewhere.

Other than that I don't see any remaining problems in session_variable.c.  I
still have a few nitpicking comments though:

+static SVariable

The comment and code is a bit misleading, as it's not immediately obvious that
set_session_variable() doesn't rely on the current memory contex for
allocations.  Simply moving the MemoryContextSwitchTo() before the
set_session_variable() would be better.

AFAICS basetypeid isn't needed anymore.

Is it possible to merge both in a single list?  I don't think that there's much
to gain trying to separate those.  They shouldn't contain a lot of entries, and
they're usually scanned at the same time anyway.

This is especially important as one of the tricky parts of this patchset is
maintaining those lists across subtransactions, and since both have the same
heuristics all the related code is duplicated.

I see that in AtPreEOXact_SessionVariable_on_xact_actions() both lists are
handled interleaved with the xact_recheck_varids, but I don't see any reason
why we couldn't process both action lists first and then process the rechecks.
I did a quick test and don't see any failure in the regression tests.

+void

I looks like a layering violation to have (part of) the code for CREATE
VARIABLE in pg_variable.[ch] and the code for DROP VARIABLE in
session_variable.[ch].

I think it was done mostly because it was the initial sync_sessionvars_all()
that was responsible to avoid cleaning up memory for variables dropped in the
current transaction, but that's not a requirement anymore.  So I don't see
anything preventing us from moving RemoveSessionVariable() in pg_variable, and
export some function in session_variable to do the additional work for properly
maintaining the hash table if needed (with that knowledge held in
session_variable, not in pg_variable).  You should only need to pass the oid of
the variable and the eoxaction.

Simlarly, why not move DefineSessionVariable() in pg_variable and expose some
API in session_variable to register the needed SVAR_ON_COMMIT_DROP action?

Also, while not a problem I don't think that the CommandCounterIncrement() is
necessary in DefineSessionVariable().  CREATE VARIABLE is a single operation
and you can't have anything else running in the same ProcessUtility() call.
And since cd3e27464cc you have the guarantee that a CommandCounterIncrement()
will happen at the end of the utility command processing.

While at it, maybe it would be good to add some extra tests in
src/test/modules/test_extensions.  I'm thinking a version 1.0 that creates a
variable and initialize the value (and and extra step after creating the
extension to make sure that the value is really set), and an upgrade to 2.0
that creates a temp variable on commit drop, that has to fail due to the
dependecy on the extension.

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 10 Sep 2022 22:12:03 +0200

fixed

changed

removed

> I did a quick test and don't see any failure in the regression tests.

Originally it was not possible, because there was no xact_reset_varids
list, and without this list the processing
ON_COMMIT_DROP started DROP VARIABLE command, and there was a request for
ON_COMMIT_RESET action.
Now, it is possible, because in RemoveSessionVariable is conditional
execution:

<--><--><-->if (!svar->eox_reset)
<--><--><--><-->register_session_variable_xact_action(varid,
<--><--><--><--><--><--><--><--><--><--><--><--><-->  SVAR_ON_COMMIT_RESET);
<--><-->}

So when we process ON_COMMIT_DROP actions, we know that the reset will not
be processed by ON_COMMIT_RESET action,
and then these lists can be merged.

so I merged these two lists to one

> the variable and the eoxaction.

I am not sure if the proposed change helps. With it I need to break
encapsulation. Now, all implementation details are hidden in
session_variable.c.

I understand that the operation Define and Remove are different from
operations Set and Get, but all are commands, and all need access to
sessionvars and some lists.

removed

> dependecy on the extension.

In updated patches I replaced used cacheMemoryContext by
TopTransactionContext what is more correct (I think)

Regards

Pavel
From bcf0f8e84f24857fdffccede34830e1f552488cb Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 

 
index d85f89bf30..cf73e44732 100644

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+     <varlistentry>
 
2.37.3

From 8dede8089a9a6940d88f8196584594f3c09cd2fe Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 11:40:29 +0200
Subject: [PATCH 09/10] this patch changes error message "column doesn't exist"
 to message "column or variable doesn't exist"

index 31e3c8fe6c..f2b4d5fa2b 100644

 

 select a from atacc1;

 
index 9dd137415e..9b4403eb7d 100644
@@ -2710,7 +2710,7 @@ select * from rules_fooview;
 (1 row)
 
index d411f0440d..5b9096b598 100644
 
 -- should fail
 LET v1.x = 10;
 
2.37.3

From a49aca45c15eb66d1a1a1fa70af3b2c19322ad17 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 .../isolation/expected/session-variable.out   |   86 ++
 src/test/isolation/isolation_schedule         |    1 +
 .../isolation/specs/session-variable.spec     |   34 +
 .../regress/expected/session_variables.out    | 1287 +++++++++++++++++
 src/test/regress/parallel_schedule            |    2 +-
 src/test/regress/sql/session_variables.sql    |  959 ++++++++++++
 6 files changed, 2368 insertions(+), 1 deletion(-)
 create mode 100644 src/test/isolation/expected/session-variable.out
 create mode 100644 src/test/isolation/specs/session-variable.spec
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

index 0000000000..d411f0440d
@@ -0,0 +1,1287 @@
+select oid as varid from pg_variable where varname = 'svar' and varnamespace = 'public'::regnamespace \gset
+        |      | t
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() f WHERE f.varid = :varid;
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() f WHERE f.varid = :varid;;
+        |      | t
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() WHERE f.varid = :varid;
+ERROR:  missing FROM-clause entry for table "f"
+LINE 1: ...FROM pg_debug_show_used_session_variables() WHERE f.varid = ...
+select oid as varid from pg_variable where varname = 'svar' and varnamespace = 'public'::regnamespace \gset
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() f WHERE f.varid = :varid;
+\unset varid
 
 # ----------
index 0000000000..d32bb5b042
@@ -0,0 +1,959 @@
+select oid as varid from pg_variable where varname = 'svar' and varnamespace = 'public'::regnamespace \gset
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() f WHERE f.varid = :varid;
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() f WHERE f.varid = :varid;;
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() WHERE f.varid = :varid;
+select oid as varid from pg_variable where varname = 'svar' and varnamespace = 'public'::regnamespace \gset
+SELECT schema, name, removed FROM pg_debug_show_used_session_variables() f WHERE f.varid = :varid;
+\unset varid
2.37.3

From ce3fee0fc23b2e8d064952d94dc1eeffe0e7db3d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 src/tools/pgindent/typedefs.list     |   1 +
 10 files changed, 361 insertions(+), 5 deletions(-)

 

 	int			useDB;
 
index 67b6d9079e..813f3f5c7c 100644
 
+/*
+ * getVariables
+dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
index 69ee939d44..5c6acf84f6 100644
 
 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 
 
index 522986a730..8b7e281bcf 100644
@@ -2913,6 +2913,7 @@ Variable
 VariableAssignHook
 VariableCache
 VariableCacheData
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.37.3

From 5853d2ae2942d4f61710eb171b4afa91822f801e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

index a141146e70..83738b10f3 100644
index c645d66418..a958853c6d 100644
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index a7eccc75d2..f3c97be80e 100644

 /*
+	{"IMMUTABLE VARIABLE", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE IMMUTABLE
+																					 * VARIABLE ... */

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*
 
2.37.3

From fefbdeefbb64d18892933749ac210b0d09bb3409 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 
index 8d640c6b98..28cf51a95e 100644
@@ -2024,7 +2024,12 @@ DiscardStmt:

index 323354ea16..6d0bf1eba5 100644
 
2.37.3

From f592415f88b8ce21382c67a2ea3793ee68d496e5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

 src/tools/pgindent/typedefs.list        |  1 +
 10 files changed, 139 insertions(+), 4 deletions(-)

index 4f6b608712..8d640c6b98 100644
@@ -795,6 +795,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -903,6 +904,13 @@ parse_toplevel:
 
 /*

index 7bd2a9fff1..6032a3d6ea 100644

+			case PLPGSQL_STMT_LET:
 
+/* ----------
+	 * Oid of target session variable is stored in Query structure. It is
+	 * safer to read this value directly from the plan than to hold this value
+	 * in the plpgsql context, because it's not necessary to handle
+	 * invalidation of the cached value. Next operations are read only without
+	 * any allocations, so we can expect that retrieving varid from Query
+	 * should be fast.
 
 	return "unknown";

+static void

+static void
index f7cf2b4b89..e1c9c8f7c8 100644
@@ -195,7 +195,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 
 %type <list>	proc_exceptions
@@ -302,6 +302,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_INTO
 %token <keyword>	K_IS
 %token <keyword>	K_LAST
+%token <keyword>	K_LET
 %token <keyword>	K_LOG
 %token <keyword>	K_LOOP
 %token <keyword>	K_MERGE
@@ -896,6 +897,8 @@ proc_stmt		: pl_block ';'
 						{ $$ = $1; }
 				| stmt_rollback
 						{ $$ = $1; }
+				| stmt_let
+						{ $$ = $1; }
 				;
 
 stmt_perform	: K_PERFORM
@@ -1012,6 +1015,29 @@ stmt_assign		: T_DATUM
 					}
 				;
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
+typedef struct PLpgSQL_stmt_let
+{
+	PLpgSQL_stmt_type cmd_type;
+	int			lineno;
+	unsigned int stmtid;
+	PLpgSQL_expr *expr;
+} PLpgSQL_stmt_let;
+
 /*
  * PERFORM statement
  */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d58d6473cb..522986a730 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1776,6 +1776,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
-- 
2.37.3

From baacf2f7d5db6e2067ee96044a521aee8910e033 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 9 Sep 2022 14:03:15 +0200
Subject: [PATCH 03/10] LET command

Set session variable value to result of query or result of default expression
---
 src/backend/commands/explain.c           |  16 ++
 src/backend/commands/session_variable.c  |  83 +++++++
 src/backend/executor/Makefile            |   1 +
 src/backend/executor/execMain.c          |   1 +
 src/backend/executor/svariableReceiver.c | 154 +++++++++++++
 src/backend/nodes/nodeFuncs.c            |  10 +
 src/backend/parser/analyze.c             | 281 +++++++++++++++++++++++
 src/backend/parser/gram.y                |  53 ++++-
 src/backend/parser/parse_agg.c           |   4 +
 src/backend/parser/parse_expr.c          |   4 +
 src/backend/parser/parse_func.c          |   3 +
 src/backend/parser/parse_target.c        |   4 +-
 src/backend/rewrite/rewriteHandler.c     |  34 +++
 src/backend/rewrite/rowsecurity.c        |   8 +-
 src/backend/tcop/dest.c                  |   7 +
 src/backend/tcop/utility.c               |  15 ++
 src/backend/utils/cache/plancache.c      |  12 +
 src/include/commands/session_variable.h  |   3 +
 src/include/executor/svariableReceiver.h |  25 ++
 src/include/nodes/parsenodes.h           |  16 ++
 src/include/nodes/pathnodes.h            |   1 +
 src/include/nodes/plannodes.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                  |   3 +-
 src/tools/pgindent/typedefs.list         |   2 +
 27 files changed, 737 insertions(+), 10 deletions(-)
 create mode 100644 src/backend/executor/svariableReceiver.c
 create mode 100644 src/include/executor/svariableReceiver.h

index 053d2ca5ae..5d127d8d2c 100644
index b75f888a46..9e0c548f59 100644
@@ -21,19 +21,23 @@
 #include "storage/proc.h"
 #include "utils/memutils.h"
 #include "utils/typcache.h"
 
@@ -1091,6 +1095,85 @@ ResetSessionVariables(void)
 	xact_recheck_varids = NIL;
 }
 
+/*
+	 * 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.)
 
index e260d20007..6a8c166806 100644
index 0000000000..ed029d1600
index 3bac350bf5..28f8c7a46d 100644
index 702d875d0d..f643966de8 100644

 
+		case T_LetStmt:
 
 			/*
 
@@ -1634,6 +1647,274 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+	 * The LET statements suppports two syntaxes: LET var = expr and LET var =
+	 * DEFAULT. In first case, the expression is of SelectStmt node and it is
+	 * transformated to query (SELECT) by usual way. Second syntax should be
+	 * transformed differently. It is more cleaner do this transformation here
+	 * (like special case), because we don't need to enhance SelectStmt about
+	 * fields necessary for this transformation somwehere in SelectStmt
+	 * transformation. Isn't to necessary to uglify the SelectStmt
+	 * transformation. This is special case of LET statement, not SelectStmt.
+		 * ResTarget(SetToDefault) -> TargetEntry(expr(SetToDefault)) The
+		 * SetToDefault is replaced by defexpr by rewriter in RewriteQuery
+		 * function.
+	 * Save target session variable id. This value is used multiple times: by
+	 * query rewriter (for getting related defexpr), by planner (for acquiring
+	 * AccessShareLock on target variable), and by executor (we need to know
+	 * return the query because we don't want to use a utilityStmt wrapper
+	 * node.
index 27e145d173..4f6b608712 100644
@@ -306,7 +306,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -454,6 +454,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
@@ -717,7 +718,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY

 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
@@ -1043,6 +1044,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -11918,6 +11920,7 @@ ExplainableStmt:
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -11948,6 +11951,7 @@ PreparableStmt:
 			| UpdateStmt
 			| DeleteStmt
 			| MergeStmt						/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -12535,6 +12539,49 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -16905,6 +16952,7 @@ unreserved_keyword:
@@ -17474,6 +17522,7 @@ bare_label_keyword:
 
 			/*
 
 			/*
index 58d27ca965..31e3c8fe6c 100644
 
 			/* okay */
 			break;
@@ -1951,6 +1952,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:
@@ -3326,6 +3328,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 
 			/*
index 29ae27e5e3..4a9fd3691c 100644
 
+	/*
 
 	/*

 		case DestTupleQueue:

 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index fc71d27adb..bab0eb73a2 100644
 
 /*
@@ -1875,6 +1876,17 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
 						  (void *) &acquire,
 
 /*
index b810cb6d57..ca1125004f 100644
 
index 69639be467..323354ea16 100644
 
@@ -1698,6 +1699,21 @@ typedef struct MergeStmt
 
+/* ----------------------
+ *		Let Statement
+ * ----------------------
+ */
+typedef struct LetStmt
+{
+	NodeTag		type;
+	List	   *target;			/* target variable */
+	Node	   *query;			/* source expression */
+	bool		set_default;	/* true, when set to DEFAULt is wanted */
+	bool		plpgsql_mode;	/* true, when command will be executed
+								 * (parsed) by plpgsql runtime */
+	int			location;
+} LetStmt;
+
 /* ----------------------
  *		Select Statement
  *
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 5a79a4d591..567f9402ac 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -463,6 +463,7 @@ struct PlannerInfo
 
 	/* 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 e03727e19f..7108203d2b 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -49,7 +49,7 @@ typedef struct PlannedStmt
 
 	NodeTag		type;

 

 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f452d7e6d4..d58d6473cb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1391,6 +1391,7 @@ LargeObjectDesc
 LastAttnumInfo
 Latch
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
@@ -2638,6 +2639,7 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+svariableState
 SVariable
 SVariableData
 SVariableXActAction
-- 
2.37.3

From deffd51312027994d500b7dc782453c207751f6f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 9 Sep 2022 10:16:13 +0200
Subject: [PATCH 02/10] session variables

The most difficult part is cleaning (reset) of session variable. The
content of session variable should be cleaned when related session
variable is removed from system catalog. But queue of sinval messages
can be truncated, or current transaction state can disallow an access
to system catalog, so cleaning can be postponed.
---
 src/backend/access/transam/xact.c       |    2 +-
 src/backend/catalog/dependency.c        |    5 +
 src/backend/commands/session_variable.c | 1154 +++++++++++++++++++++--
 src/backend/executor/execExpr.c         |   75 ++
 src/backend/executor/execExprInterp.c   |   11 +
 src/backend/executor/execMain.c         |   56 ++
 src/backend/executor/execParallel.c     |  148 ++-
 src/backend/jit/llvm/llvmjit_expr.c     |    6 +
 src/backend/optimizer/plan/planner.c    |    8 +
 src/backend/optimizer/plan/setrefs.c    |  118 ++-
 src/backend/optimizer/util/clauses.c    |   75 +-
 src/backend/parser/analyze.c            |    9 +
 src/backend/parser/parse_expr.c         |  232 ++++-
 src/backend/tcop/pquery.c               |    3 +
 src/backend/utils/adt/ruleutils.c       |   46 +
 src/backend/utils/cache/plancache.c     |   22 +-
 src/backend/utils/fmgr/fmgr.c           |   10 +-
 src/backend/utils/misc/guc.c            |   12 +
 src/include/catalog/pg_proc.dat         |    7 +
 src/include/commands/session_variable.h |    4 +-
 src/include/executor/execExpr.h         |   10 +
 src/include/executor/execdesc.h         |    4 +
 src/include/nodes/execnodes.h           |   19 +
 src/include/nodes/parsenodes.h          |    1 +
 src/include/nodes/pathnodes.h           |    4 +
 src/include/nodes/plannodes.h           |    2 +
 src/include/nodes/primnodes.h           |   12 +-
 src/include/optimizer/planmain.h        |    2 +
 src/include/parser/parse_expr.h         |    1 +
 src/include/parser/parse_node.h         |    1 +
 src/include/utils/lsyscache.h           |    8 +-
 src/tools/pgindent/typedefs.list        |    3 +
 32 files changed, 1977 insertions(+), 93 deletions(-)

 
 
index bb70603dbe..b75f888a46 100644
@@ -18,30 +18,100 @@
 #include "utils/memutils.h"
 
 /*
 
 typedef struct SVariableXActActionItem
 {
 	Oid			varid;			/* varid of session variable */
+	SVariableXActAction action;
 
 	/*
 	 * creating_subid is the ID of the creating subxact. If the action was
@@ -52,12 +122,739 @@ typedef struct SVariableXActActionItem
 	SubTransactionId deleting_subid;
 } SVariableXActActionItem;
 
-/* List holds fields of SVariableXActActionItem type */
-static List *xact_drop_actions = NIL;
+/* list holds fields of SVariableXActActionItem type */
+static List *xact_on_commit_actions = NIL;
+
+/*
+ * the ON COMMIT DROP and ON TRANSACTION END RESET variables
+ * are purged from memory every time.
+ */
+static List *xact_reset_varids = NIL;
+
+/*
+ * When the session variable is dropped we need to free local memory. The
+ * session variable can be dropped by current session, but it can be
+ * dropped by other's sessions too, so we have to watch sinval message.
+ * But because we don't want to free local memory immediately, we need to
+ * hold list of possibly dropped session variables and at the end of
+ * transaction, we check session variables from this list against system
+ * catalog. This check can be postponed into next transaction if
+ * current transactions is in aborted state.
+ */
+static List *xact_recheck_varids = NIL;
+
+typedef struct SVariableData
+{
+	Oid			varid;			/* pg_variable OID of this sequence (hash key) */
+
+	/*
+	 * The session variable is identified by oid. The oid is unique in
+	 * catalog. Unfortunately, the cleaning memory can be postponed to begin
+	 * of next transaction in session, and theoreticaly, there can be
+	 * different session variable with same oid. So we need another extra
+	 * identifier that helps with identity check. We can use LSN number in
+	 * session variable create time. The value of session variable (in memory)
+	 * is valid, when there is an record in pg_variable with same oid and same
+	 * create_lsn.
+	 */
+	XLogRecPtr	create_lsn;		/* used for session's variable identity check */
+
+	bool		isnull;
+	bool		freeval;
+	Datum		value;
+
+	Oid			typid;
+	int16		typlen;
+	bool		typbyval;
+
+	bool		is_domain;
+	void	   *domain_check_extra;
+	LocalTransactionId domain_check_extra_lxid;
+
+	/*
+	 * Top level local transaction id of the last transaction that dropped the
+	 * variable if any.  We need this information to avoid freeing memory for
+	 * variable dropped by the local backend that may be rollbacked.
+	 */
+	LocalTransactionId drop_lxid;
+
+	bool		is_not_null;	/* don't allow null values */
+	bool		is_immutable;	/* true when variable is immutable */
+	bool		has_defexpr;	/* true when variable has a default value */
+
+	bool		is_valid;		/* true when variable was successfully
+								 * initialized */
+
+	uint32		hashvalue;		/* used for pairing sinval message */
+
+	bool		eox_reset;		/* true, when lifecycle is limitted by
+								 * transaction */
+} SVariableData;
+
+typedef SVariableData *SVariable;
+
+static HTAB *sessionvars = NULL;	/* hash table for session variables */
+static HTAB *sessionvars_types = NULL;	/* hash table for type fingerprints of
+										 * session variables */
+
+static MemoryContext SVariableMemoryContext = NULL;
+
+static void init_session_variable(SVariable svar, Variable *var);

+/*
+	 * We can mark this session variable as valid when it has not default
+	 * expression, and when null is allowed. When it has defexpr, then the
+	 * content will be valid after an assignment or defexp evaluation.
+	 * In this moment, the session variable is not in catalog, so only saved
+	 * oid can be displayed.
+	 * session variable from its hashValue, we have to iterate over all items
+	 * of hash table.
+		 * although it there is low probability, we have to iterate over all
+		 * locally set session variables, because hashvalue is not unique
+		 * identifier.
+		 * because after oid's reset the oid can be used for new other session
+		 * variable. We do second check against 64bit unique identifier.
+	 * sessionvars is null after DISCARD VARIABLES. When we are sure, so there
+	 * are not any active session variable in this session.
+	 * This routine is called before any reading, so the session should be in
+	 * transaction state. This is required for access to system catalog.
+			 * If this is a variable dropped by the current transaction,
+			 * ignore it and keep the oid to recheck in the next transaction.
+	 * variable is being initialized.
+	svar->is_domain = (typ->typtype == TYPTYPE_DOMAIN);
+	oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+	 * Although the value of domain type should be valid (it is checked when
+	 * it is assigned to session variable), we have to check related
+	 * constraints anytime. It can be more expensive than in PL/pgSQL.
+	 * PL/pgSQL forces domain checks when value is assigned to the variable or
+	 * when value is returned from function. Fortunately, domain types manage
+	 * cache of constraints by self.
+		 * Store domain_check extra in TopTransactionContext. When we are in
+		 * other transaction, the domain_check_extra cache is not valid.
+					 TopTransactionContext);
@@ -158,28 +955,35 @@ DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 											  SVAR_ON_COMMIT_DROP);
 	}
 
-	/*
-	 * We must bump the command counter to make the newly-created variable
-	 * tuple visible for any other operations.
-	 */
-	CommandCounterIncrement();
-
 	return variable;
 }
 
 /*
 
-	elog(DEBUG1, "session variable (oid:%u) is deleted from pg_variable", varid);
+	/*
+	 * We want to run initial sessionvars cache synchronization before
+	 * dropping. Then we are sure, so initial sync newer process variables
+	 * dropped in current transactions. Then we can do initial sync more
+	 * agressive.
+	 */
+	sync_sessionvars_all();

@@ -188,6 +992,18 @@ RemoveSessionVariable(Oid varid)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for variable %u", varid);

 	ReleaseSysCache(tup);
@@ -195,22 +1011,91 @@ RemoveSessionVariable(Oid varid)
 	table_close(rel, RowExclusiveLock);
 
 	/*
-	 * We removed entry from catalog already, we should not do it again at end
-	 * of xact time.
+	 * We removed entry from catalog already, we must not do it again at end
+	 * of xact time
 	 */
 	unregister_session_variable_xact_action(varid, SVAR_ON_COMMIT_DROP);
+
+	if (sessionvars)
+	{
+		svar = (SVariable) hash_search(sessionvars, &varid,
+									   HASH_FIND, &found);
+
+		if (found)
+		{
+			/*
+			 * Save the current top level local transaction id to make sure we
+			 * don't automatically remove the local variable storage in
+			 * sync_sessionvars_all, as the DROP VARIABLE will send an
+			 * invalidation message.
+			 */
+			Assert(LocalTransactionIdIsValid(MyProc->lxid));
+			svar->drop_lxid = MyProc->lxid;
+
+			/*
+			 * For variables that are not purged by default we need to
+			 * register an SVAR_ON_COMMIT_RESET action to free the local
+			 * memory for this variable when this transaction or
+			 * subtransaction is committed (we don't need to wait for sinval
+			 * message).  The cleaning action for one session variable can be
+			 * repeated in the action list without causing any problem, so we
+			 * don't need to ensure uniqueness.  We need a different action
+			 * from RESET, because RESET is executed on any transaction end,
+			 * but we want to execute cleaning only when the current
+			 * transaction will be committed. This action can be reverted by
+			 * ABORT of DROP VARIABLE command.
+			 */
+			if (!svar->eox_reset)
+				register_session_variable_xact_action(varid,
+													  SVAR_ON_COMMIT_RESET);
+		}
+	}
 }
 
 /*
- * Registration of actions to be executed on session variables at transaction
- * end time. We want to drop temporary session variables with clause ON COMMIT
- * DROP, or we want to reset values of session variables with clause ON
- * TRANSACTION END RESET or we want to clean (reset) local memory allocated by
- * values of dropped session variables.
+ * Fast drop of the complete content of all session variables hash table.
+ * This is code for DISCARD VARIABLES command. This command
+ * cannot be run inside transaction, so we don't need to handle
+ * end of transaction actions.
  */
+void
+ResetSessionVariables(void)
+{
+	/* Destroy hash table and reset related memory context */
+	if (sessionvars)
+	{
+		hash_destroy(sessionvars);
+		sessionvars = NULL;
+
+		hash_destroy(sessionvars_types);
+		sessionvars_types = NULL;
+	}
+
+	/* Release memory allocated by session variables */
+	if (SVariableMemoryContext != NULL)
+		MemoryContextReset(SVariableMemoryContext);
+
+	/*
+	 * There are not any session variables left, so simply trim both xact
+	 * action lists.
+	 */
+	list_free_deep(xact_on_commit_actions);
+	xact_on_commit_actions = NIL;
+
+	/* We should clean xact_reset_varids */
+	list_free(xact_reset_varids);
+	xact_reset_varids = NIL;
+
+	/* we should clean xact_recheck_varids */
+	list_free(xact_recheck_varids);
+	xact_recheck_varids = NIL;
+}
 
 /*
- * Register a session variable xact action.
+ * Registration of actions to be executed on session variables at transaction
+ * end time. We want to drop temporary session variables with clause ON COMMIT
+ * DROP, or we want to clean (reset) local memory allocated by
+ * values of dropped session variables.
  */
 static void
 register_session_variable_xact_action(Oid varid,
@@ -219,18 +1104,21 @@ register_session_variable_xact_action(Oid varid,
 	SVariableXActActionItem *xact_ai;
 	MemoryContext oldcxt;
 
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	elog(DEBUG1, "SVariableXActAction \"%s\" is registered for session variable (oid:%u)",
+		 SvariableXActActionName(action), varid);
+
+	oldcxt = MemoryContextSwitchTo(TopTransactionContext);
 
 	xact_ai = (SVariableXActActionItem *)
 		palloc(sizeof(SVariableXActActionItem));
 
 	xact_ai->varid = varid;

-	Assert(action == SVAR_ON_COMMIT_DROP);
-	xact_drop_actions = lcons(xact_ai, xact_drop_actions);
+	xact_on_commit_actions = lcons(xact_ai, xact_on_commit_actions);
 
 	MemoryContextSwitchTo(oldcxt);
 }
@@ -247,14 +1135,15 @@ unregister_session_variable_xact_action(Oid varid,
 {
 	ListCell   *l;

-	foreach(l, xact_drop_actions)
+	foreach(l, xact_on_commit_actions)
 	{
 		SVariableXActActionItem *xact_ai =
 		(SVariableXActActionItem *) lfirst(l);
 
-		if (xact_ai->varid == varid)
+		if (xact_ai->action == action && xact_ai->varid == varid)
 			xact_ai->deleting_subid = GetCurrentSubTransactionId();
 	}
 }
@@ -268,40 +1157,68 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 {
 	ListCell   *l;
 
-	foreach(l, xact_drop_actions)
+	/*
+	 * Clean memory for all eox_reset variables. Do it first, it reduces
+	 * enhancing action lists about RECHECK action.
+	 */
+	foreach(l, xact_reset_varids)
 	{
-		SVariableXActActionItem *xact_ai =
-		(SVariableXActActionItem *) lfirst(l);
+		remove_session_variable_by_id(lfirst_oid(l));
+	}
 
-		/* Iterate only over entries that are still pending */
-		if (xact_ai->deleting_subid == InvalidSubTransactionId)
+	/* We can clean xact_reset_varids */
+	list_free(xact_reset_varids);
+	xact_reset_varids = NIL;
+
+	if (isCommit && xact_on_commit_actions)
+	{
+		foreach(l, xact_on_commit_actions)
 		{
+			SVariableXActActionItem *xact_ai =
+			(SVariableXActActionItem *) lfirst(l);
 
-			/*
-			 * ON COMMIT DROP is allowed only for temp session variables. So
-			 * we should explicitly delete only when current transaction was
-			 * committed. When it's rollback, then session variable is removed
-			 * automatically.
-			 */
-			if (isCommit)
+			/* Iterate only over entries that are still pending */
+			if (xact_ai->deleting_subid == InvalidSubTransactionId)
 			{
-				ObjectAddress object;
+				if (xact_ai->action == SVAR_ON_COMMIT_DROP)
+				{
+					ObjectAddress object;
 
-				object.classId = VariableRelationId;
-				object.objectId = xact_ai->varid;
-				object.objectSubId = 0;
+					/*
+					 * ON COMMIT DROP is allowed only for temp session variables.
+					 * So we should explicitly delete only when current
+					 * transaction was committed. When it's rollback, then session
+					 * variable is removed automatically.
+					 */
 
-				/*
-				 * Since this is an automatic drop, rather than one directly
-				 * initiated by the user, we pass the
-				 * PERFORM_DELETION_INTERNAL flag.
-				 */
-				elog(DEBUG1, "session variable (oid:%u) will be deleted (forced by SVAR_ON_COMMIT_DROP action)",
-					 xact_ai->varid);
+					object.classId = VariableRelationId;
+					object.objectId = xact_ai->varid;
+					object.objectSubId = 0;
 
-				performDeletion(&object, DROP_CASCADE,
-								PERFORM_DELETION_INTERNAL |
-								PERFORM_DELETION_QUIETLY);
+					/*
+					 * Since this is an automatic drop, rather than one directly
+					 * initiated by the user, we pass the
+					 * PERFORM_DELETION_INTERNAL flag.
+					 */
+					elog(DEBUG1, "session variable (oid:%u) will be deleted (forced by SVAR_ON_COMMIT_DROP action)",
+						 xact_ai->varid);
+
+					performDeletion(&object, DROP_CASCADE,
+									PERFORM_DELETION_INTERNAL |
+									PERFORM_DELETION_QUIETLY);
+				}
+				else
+				{
+					/*
+					 * When we process DROP VARIABLE statement, we create
+					 * SVAR_ON_COMMIT_RESET xact action. We want to process this
+					 * action only when related transaction is commited (when DROP
+					 * VARIABLE statement sucessfully processed). We want to preserve
+					 * variable content, when the transaction with DROP VARAIBLE
+					 * statement was reverted.
+					 */
+					remove_session_variable_by_id(xact_ai->varid);
+				}
 			}
 		}
 	}
@@ -310,8 +1227,32 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 	 * Any drop action left is an entry that was unregistered and not
 	 * rollbacked, so we can simply remove them.
 	 */
-	list_free_deep(xact_drop_actions);
-	xact_drop_actions = NIL;
+	list_free_deep(xact_on_commit_actions);
+	xact_on_commit_actions = NIL;
+
+	if (isCommit && xact_recheck_varids)
+	{
+		Assert(sessionvars);
+
+		foreach(l, xact_recheck_varids)
+		{
+			SVariable	svar;
+			bool		found;
+			Oid			varid = lfirst_oid(l);
+
+			svar = (SVariable) hash_search(sessionvars, &varid,
+										   HASH_FIND, &found);
+
+			if (found)
+			{
+				if (!is_session_variable_valid(svar))
+					remove_session_variable(svar);
+			}
+		}
+
+		list_free(xact_recheck_varids);
+		xact_recheck_varids = NIL;
+	}
 }
 
 /*
@@ -327,7 +1268,7 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 {
 	ListCell   *cur_item;
 
+	foreach(cur_item, xact_on_commit_actions)
@@ -335,7 +1276,7 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
+			xact_on_commit_actions = foreach_delete_current(xact_on_commit_actions, cur_item);
@@ -348,3 +1289,98 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
+				values[1] = PointerGetDatum(
+											cstring_to_text(
+															get_namespace_name(varform->varnamespace)));
+				values[2] = PointerGetDatum(
+											cstring_to_text(NameStr(varform->varname)));
+				values[4] = PointerGetDatum(
+											cstring_to_text(format_type_be(svar->typid)));
+				values[8] = BoolGetDatum(
+										 pg_variable_aclcheck(svar->varid, GetUserId(), ACL_SELECT) == ACLCHECK_OK);
+				values[9] = BoolGetDatum(
+										 pg_variable_aclcheck(svar->varid, GetUserId(), ACL_UPDATE) == ACLCHECK_OK);
+				 * When session variable was removed from catalog, but still
+				 * it in memory. The memory was not purged yet.
+				nulls[1] = true;
+				nulls[2] = true;
 
 						/*
index 9b9bbf00a9..8f01ebfe65 100644
 
index ef2fd46092..e260d20007 100644
 
+	/*
+	 * 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->num_session_variables > 0)
+	{
+		/*
+		 * When paralel access to query parameters (including related session
+		 * variables) is required, then related session variables are restored
+		 * (deserilized) in queryDesc already. So just push pointer of this
+		 * array to executor's estate.
+		 */
+		estate->es_session_variables = queryDesc->session_variables;
+		estate->es_num_session_variables = queryDesc->num_session_variables;
+	}
+	else if (queryDesc->plannedstmt->sessionVariables)
+	{
+		ListCell   *lc;
+		int			nSessionVariables;
+		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.
+		 */
+		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 = pg_variable_aclcheck(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 = CopySessionVariable(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/execParallel.c b/src/backend/executor/execParallel.c
index f1fd7f7e8b..c26c4ddb5c 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -12,8 +12,9 @@
  * workers and ensuring that their state generally matches that of the
  * leader; see src/backend/access/transam/README.parallel for details.
  * However, we must save and restore relevant executor state, such as
- * any ParamListInfo associated with the query, buffer/WAL usage info, and
- * the actual plan to be passed down to the worker.
+ * any ParamListInfo associated with the query, buffer/WAL usage info,
+ * session variables buffer, and the actual plan to be passed down to
+ * the worker.
  *
  * IDENTIFICATION
  *	  src/backend/executor/execParallel.c
@@ -66,6 +67,7 @@
 #define PARALLEL_KEY_QUERY_TEXT		UINT64CONST(0xE000000000000008)
 #define PARALLEL_KEY_JIT_INSTRUMENTATION UINT64CONST(0xE000000000000009)
 #define PARALLEL_KEY_WAL_USAGE			UINT64CONST(0xE00000000000000A)
+#define PARALLEL_KEY_SESSION_VARIABLES	UINT64CONST(0xE00000000000000B)

 

 

 
 	/*

+	/*
index 1cb0abdbc1..12c0364f32 100644

+/*
 
 /*
  * fix_param_node
+		 * Now, we can actualize list of session variables, and we can
+		 * complete paramid parameter.
 
index 5c54171fee..3a5523e283 100644
 
 	/*

 
 				/*
 				 * Not replaceable, so just copy the Param (no need to
@@ -4735,22 +4761,45 @@ substitute_actual_parameters_mutator(Node *node,
 {
 	if (node == NULL)
 		return NULL;
+
+	/*
+	 * SQL functions can contain two different kind of params. The nodes with
+	 * paramkind PARAM_EXTERN are related to function's arguments (and should
+	 * be replaced in this step), because this is how we apply the function's
+	 * arguments for an expression.
+	 *
+	 * The nodes with paramkind PARAM_VARIABLE are related to usage of session
+	 * variables. The values of session variables are not passed to expression
+	 * by expression arguments, so it should not be replaced here by
+	 * function's arguments. Although we could substitute params related to
+	 * immutable session variables with default expression by this default
+	 * expression, it is safer to not do it. This way we don't have to run
+	 * security checks here. There can be some performance loss, but an access
+	 * to session variable is fast (and the result of default expression is
+	 * immediately materialized and can be reused).
+	 */
 	if (IsA(node, Param))
 	{
 		Param	   *param = (Param *) node;

 
index 6688c2a865..702d875d0d 100644

 

@@ -1404,6 +1407,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 							   (LockingClause *) lfirst(l), false);
 	}

 	/* this must be done after collations, for reliable comparison of exprs */
@@ -1622,6 +1627,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

 

 

index 3527b78695..58d27ca965 100644
 
 /* GUC parameters */

 /*
 
+/*
@@ -779,6 +809,148 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
+	/*
+	 * There are contexts where session's variables are not allowed. The
+	 * question is if we want to identify session's variables in these
+	 * contexts? The code can be more simple, when we don't do it, but then we
+	 * cannot to raise maybe useful message like "you cannot to use session
+	 * variables here". On second hand, in this case the warnings about
+	 * session's variable shadowing can be messy.
+	 */
+		 * CREATE TYPE T AS (c int); CREATE VARIABLE foo AS T; CREATE TABLE
+		 * foo(a int, b int);
@@ -813,6 +985,64 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
 
index 2b7b1b0c0f..e3beae0bea 100644

+/*
index 0d6a295674..fc71d27adb 100644
 
@@ -1867,7 +1868,8 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
@@ -1891,6 +1893,20 @@ ScanQueryWalker(Node *node, bool *acquire)
 
 	/*
 	 * Do NOT recurse into Query nodes, because ScanQueryForLocks already
@@ -2022,7 +2038,9 @@ PlanCacheRelCallback(Datum arg, Oid relid)
 
 /*
 
 	return false;
 }
index 55bf998511..68a69004e0 100644
 
index 0daf6f41ec..b810cb6d57 100644

 

 
index 01b1727fc0..493b9dda65 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
index 52bfd673c3..69639be467 100644
@@ -145,6 +145,7 @@ typedef struct Query

 

 

 
 typedef struct Param

 
 /* GUC parameters */

 
 
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 8e2b733bb2..387fbcb52c 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -205,11 +205,11 @@ 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_namespace(Oid varid);
 extern void get_session_variable_type_typmod_collid(Oid varid,
-												   Oid *typid,
-												   int32 *typmod,
-												   Oid *collid);
+													Oid *typid,
+													int32 *typmod,
+													Oid *collid);
 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 73dae3719b..f452d7e6d4 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2453,6 +2453,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
@@ -2637,6 +2638,8 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariable
+SVariableData
 SVariableXActAction
 SVariableXActActionItem
 Syn
-- 
2.37.3

From e42489611ad099a2227f81a5ed9f1d6624f26415 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/10] catalog support for session variables

 src/backend/catalog/namespace.c         | 425 ++++++++++++++++++++++++
 src/backend/catalog/pg_variable.c       | 235 +++++++++++++
 src/backend/commands/session_variable.c | 350 +++++++++++++++++++
 src/backend/commands/tablecmds.c        |  45 ++-
 src/tools/pgindent/typedefs.list        |   4 +
 39 files changed, 2046 insertions(+), 19 deletions(-)

 

 

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
index fafb9349cc..441ebde25a 100644
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
@@ -2843,6 +2907,356 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
+/*
+					 * a.b can mean "schema"."variable" or "variable"."field",
+					 * Check both variants, and returns InvalidOid with
+					 * not_unique flag, when both interpretations are
+					 * possible. Second node can be star. In this case, the
+					 * only allowed possibility is "variable"."*".
+					 * Session variables doesn't support unboxing by star
+					 * syntax. But this syntax have to be calculated here,
+					 * because can come from non session variables related
+					 * "schema"."variable"."field", Check both variants, and
+					 * returns InvalidOid with not_unique flag, when both
+					 * interpretations are possible. When third node is star,
+					 * the only possible interpretation is
+					 * "schema"."variable"."*".
 
 /*
  * DeconstructQualifiedName
@@ -4660,3 +5074,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index 0000000000..49b23d9291
@@ -0,0 +1,235 @@
+	 * Deserialize defexpr only when it is requested. We need to deserialize
+	 * Node with default expression, only when we read from session variable,
+	 * and this session variable has not assigned value, and this session
+	 * variable has default expression. For other cases, we skip skip this
+	 * operation.

 
 			/*
 
 			/*
index 0000000000..bb70603dbe
@@ -0,0 +1,350 @@
+#include "utils/memutils.h"
+	 * unregistered during the current transaction, deleting_subid is the ID
+	 * of the deleting subxact, otherwise InvalidSubTransactionId.
+	 * We removed entry from catalog already, we should not do it again at end
+	 * of xact time.
+			 * ON COMMIT DROP is allowed only for temp session variables. So
+			 * we should explicitly delete only when current transaction was
+			 * committed. When it's rollback, then session variable is removed
+			 * automatically.
+				 * Since this is an automatic drop, rather than one directly
+				 * initiated by the user, we pass the
index e3233a8f38..27b382e26a 100644
@@ -6496,10 +6499,47 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
-			pg_depend->objsubid <= 0)
+				 pg_depend->objsubid <= 0)
 			continue;
 
 		rel = relation_open(pg_depend->objid, AccessShareLock);
@@ -12694,6 +12734,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_PUBLICATION_REL:
 			case OCLASS_SUBSCRIPTION:
 			case OCLASS_TRANSFORM:
+			case OCLASS_VARIABLE:
 
 				/*
index ea33784316..27e145d173 100644
@@ -294,8 +295,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -475,6 +476,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
@@ -644,6 +646,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 /*
@@ -754,8 +758,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UESCAPE UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING

 
@@ -1001,6 +1005,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -5009,6 +5014,69 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6786,6 +6854,7 @@ object_type_any_name:
 
 /*
@@ -7662,6 +7731,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7707,6 +7784,14 @@ privilege_target:

@@ -7904,6 +7989,7 @@ defacl_privilege_target:

@@ -9686,6 +9772,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10047,6 +10152,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10326,6 +10450,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -16933,6 +17065,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -17542,6 +17676,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)

 

-
 /*
 
 	return subname;
 }
+

 	/*

 

 
 
index 0000000000..b40b2d5f31
index 0000000000..0daf6f41ec
+extern ObjectAddress DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt);
index 6958306a7d..52bfd673c3 100644

+

 

 
 
index 5b3b305963..73dae3719b 100644
@@ -2635,6 +2637,8 @@ SupportRequestRows
+SVariableXActAction
+SVariableXActActionItem
 Syn
 SyncOps
 SyncRepConfigData
-- 
2.37.3

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 11 Sep 2022 21:29:49 +0200

Hi

changes:

- some minor cleaning
- refactoring of RemoveSessionVariable  - move part of code to pg_variable.c

Regards

Pavel
From c0fa112a1c66dbe62ef94fda2a6f3d1b48824387 Mon Sep 17 00:00:00 2001

 

 
 
 select a from atacc1;

 

 -- should fail
 LET v1.x = 10;

From 7ad40567592c485adfefcec6a2a5745253f3373f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+     <varlistentry>

From a309b0e3ef58ac69259d470580881b68010a7f64 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

 
 # ----------

From be6a3d676090d42247b7b61a62537fb49fe2351a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index f3465adb85..519c863202 100644

 /*
@@ -2644,7 +2658,7 @@ psql_completion(const char *text, int start, int end)
@@ -3082,7 +3096,7 @@ psql_completion(const char *text, int start, int end)
@@ -3389,6 +3403,17 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS */
@@ -3504,7 +3529,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
@@ -3631,6 +3656,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 */
@@ -3747,7 +3778,8 @@ psql_completion(const char *text, int start, int end)
@@ -3765,6 +3797,8 @@ psql_completion(const char *text, int start, int end)
 									 "USAGE",
 									 "SET",
 									 "ALTER SYSTEM",
+									 "READ",
+									 "WRITE",
 									 "ALL");
 	}
 
@@ -3806,7 +3840,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("GRANT|REVOKE", MatchAny) ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny))
 	{
-		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
+		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|READ|WRITE|ALL"))
 			COMPLETE_WITH("ON");
 		else if (TailMatches("GRANT", MatchAny))
 			COMPLETE_WITH("TO");
@@ -3829,7 +3863,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",
@@ -3851,7 +3885,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"))
@@ -3859,7 +3894,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");
@@ -3895,6 +3931,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
@@ -4137,7 +4175,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -4425,6 +4463,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
 
@@ -4591,6 +4637,8 @@ psql_completion(const char *text, int start, int end)

From bd2684814464346a146492649fd0734a97bafd5b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 5138df2e2583275663ef30aca61c4b82f067f982 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 

From 4ad15bfc29ba41f3203527c3c9a7192d077c77ef Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 80849f44514411dc166d3568ef1dd7e478892450 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 9 Sep 2022 14:03:15 +0200
Subject: [PATCH 03/10] LET command

index ed0e40618a..b47ee68cb5 100644
@@ -20,19 +20,23 @@
 
 /*
@@ -1060,6 +1064,85 @@ ResetSessionVariables(void)
 	xact_recheck_varids = NIL;
 }
 
+/*

 
 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 

 	NodeTag		type;

 

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

From cfacf60c3a7e0a4e1398f3b32ff4ba59a66010dc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 11 Sep 2022 20:56:40 +0200
Subject: [PATCH 02/10] session variables

 src/backend/commands/session_variable.c | 1127 ++++++++++++++++++++++-
 32 files changed, 1959 insertions(+), 84 deletions(-)

 
 
index 4ac6ecf730..ed0e40618a 100644
@@ -21,27 +21,95 @@
 
 /*

 	/*
 	 * creating_subid is the ID of the creating subxact. If the action was
@@ -52,12 +120,728 @@ typedef struct SVariableXActActionItem
 	SubTransactionId deleting_subid;
 } SVariableXActActionItem;

 
+/*
+	get_typlenbyval(var->typid, &svar->typlen, &svar->typbyval);
+	svar->is_domain = (get_typtype(var->typid) == TYPTYPE_DOMAIN);
@@ -162,33 +946,125 @@ DefineSessionVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 }
 
 /*
+	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) will be deleted",
 	VariableDrop(varid);
 
 	 * We removed entry from catalog already, we must not do it again at end
 	 * of xact time
 
 /*
+	 * There are not any session variables left, so simply trim xact
+	 * action list, and other lists.
 
 /*
@@ -197,18 +1073,21 @@ register_session_variable_xact_action(Oid varid,

 
 	xact_ai->varid = varid;

 
 	MemoryContextSwitchTo(oldcxt);
 }
@@ -225,14 +1104,15 @@ unregister_session_variable_xact_action(Oid varid,
 {
 	ListCell   *l;

 
-		if (xact_ai->varid == varid)
+		if (xact_ai->action == action && xact_ai->varid == varid)
 			xact_ai->deleting_subid = GetCurrentSubTransactionId();
 	}
 }
@@ -246,40 +1126,68 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 {
 	ListCell   *l;

 
-			/*

-				/*
 
@@ -288,8 +1196,32 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 
 /*
@@ -305,7 +1237,7 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 {
 	ListCell   *cur_item;
 
@@ -313,7 +1245,7 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
@@ -326,3 +1258,98 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySu
 
 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 
 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

 

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

 

 

 
 
 typedef struct Param

 
 /* GUC parameters */

 

 

From c332ecb5f2a2c92f0bd4d9435dfcb5d5d28effaa Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/10] catalog support for session variables

 src/backend/catalog/pg_variable.c       | 258 ++++++++++++++
 src/backend/commands/session_variable.c | 328 ++++++++++++++++++
 src/include/catalog/pg_variable.h       | 132 ++++++++
 39 files changed, 2049 insertions(+), 19 deletions(-)

 

 

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index 0000000000..a844e5c217
@@ -0,0 +1,258 @@
+ * Remove variable specified by id from pg_variable
+VariableDrop(Oid varid)

 
 			/*
 
 			/*
index 0000000000..4ac6ecf730
@@ -0,0 +1,328 @@
+ * session_variable.c
+	VariableDrop(varid);

 
 				/*

 /*

 

 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 

-
 /*
 
 	return subname;
 }
+

 	/*

 

 
 
index 0000000000..e75222f90c
@@ -0,0 +1,132 @@
+extern void VariableDrop(Oid varid);

+

---

From: Julien Rouhaud <[email-masked]>
Date: Fri, 16 Sep 2022 11:59:04 +0800

Hi,

On Sun, Sep 11, 2022 at 09:29:49PM +0200, Pavel Stehule wrote:
>>
>> Originally it was not possible, because there was no xact_reset_varids list, and without this list the processing
>> ON_COMMIT_DROP started DROP VARIABLE command, and there was a request for ON_COMMIT_RESET action.
>> Now, it is possible, because in RemoveSessionVariable is conditional execution:
>> 
>> So when we process ON_COMMIT_DROP actions, we know that the reset will not be processed by ON_COMMIT_RESET action,

Thanks!  This really helps with code readability, and after looking at it I
found some issues (see below).
> - refactoring of RemoveSessionVariable  - move part of code to pg_variable.c

Thanks.  I think we could still do more to split what code belongs to
pg_variable.c and session_variable.c.  In my opinion, the various DDL code
should only invoke functions in pg_variable.c, which themselves can call
function in session_variable.c if needed, and session_variable shouldn't know
about CreateSessionVarStmt (which should probably be rename
CreateVariableStmt?) or VariableRelationId.  After an off-list bikeshedding
session with Pavel, we came up with SessionVariableCreatePostprocess() and
SessionVariableDropPostprocess() for the functions in session_variable.c called
by pg_variable.c when handling CREATE VARIABLE and DROP VARIABLE commands.

I'm attaching a new patchset with this change and some more (see below).  I'm
not sending .txt files as this is rebased on top on the recent GUC refactoring
patch.  It won't change the cfbot outcome though, as I also add new regression
tests that are for now failing (see below).  I tried to keep the changes in
extra "FIXUP" patches when possible, but the API changes in the first patch
cause conflicts in the next one, so the big session variable patch has to
contain the needed changes.

In this patchset, I also changed the following:

- global pass on the comments in session_variable
- removed now useless sessionvars_types
- added missing prototypes for static functions (for consistency), and moved
  all the static functions before the static function
- minor other nitpicking / stylistic changes

Here are the problems I found:

- IdentifyVariable()

		/*
		 * Lock relation.  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;
+	}

AFAICS it's correct, but just to be extra cautious I'd explicitly set varid to
InvalidOid before looping, so you restart in the same condition as the first
iteration (since varid is initialize when declared).  Also, the comments should
be modified, it's "Lock variable", not "Lock relation", same for the comment in
the previous chunk ("we've locked the relation  that used to have this
name...").

+Datum
+[...]

I'm wondering if we could try to improve things a bit here.  Maybe display the
variable oid instead of its name as we still have that information, the type
(using FORMAT_TYPE_ALLOW_INVALID as there's no guarantee that the type would
still exist without the dependency) and whether the variable is valid (at least
per its stored value).  We can keep NULL for the privileges, as there's no API
avoid erroring if the role has been dropped.

Since we change READ / WRITE acl for SELECT / UPDATE, we should rename the
column can_select and can_update.

+static void
+ [...]
+ [...]

This has a pretty terrible complexity.  It can degenerate badly, and there
isn't any CHECK_FOR_INTERRUPTS so you could easily lock a backend for quite
some time.

I think we should just keep appending oids, and do a list_sort(list,
list_oid_cmp) and list_deduplicate_oid(list) before processing the list, in
sync_sessionvars_all() and AtPreEOXact_SessionVariable_on_xact_actions().

Maybe while at it we could reuse sync_sessionvars_all in
AtPreEOXact_SessionVariable_on_xact_actions (with a way to ask
for the lxid check or not), rather than duplicating the whole logic twice?

+/*

The initial comment is wrong.  This function is used for both DISCARD VARIABLES
and DISCARD ALL, but only DISCARD ALL isn't allowed in a transaction (I fixed
the comment in the attached patchset).
We should allow DISCARD VARIABLES in a transaction, therefore it needs some
more thinking on which list can be freed, and in which context it should hold
its data.  AFAICS the only problematic case is ON COMMIT DROP, but an extra
check wouldn't hurt.  For instance:

rjuju=# BEGIN;
BEGIN

rjuju=# CREATE TEMP VARIABLE v AS int ON COMMIT DROP;
CREATE VARIABLE

rjuju=# DISCARD VARIABLES ;
DISCARD VARIABLES

rjuju=# COMMIT;
COMMIT

rjuju=# \dV
                                            List of variables
  Schema   | Name |  Type   | Collation | Nullable | Mutable | Default | Owner | Transactional end action
-----------+------+---------+-----------+----------+---------+---------+-------+--------------------------
 pg_temp_3 | v    | integer |           | t        | t       |         | rjuju | ON COMMIT DROP
(1 row)

Note that I still think that keeping a single List for both SVariableXActAction
helps for readability, even if it means cherry-picking which items should be
removed on DISCARD VARIABLES (which shouldn't be a very frequent operation
anyway).

Also, xact_recheck_varids is allocated in SVariableMemoryContext, so DISCARD
VARIABLE will crash if there's any pending recheck action.

There's only one regression test for DISCARD VARIABLE, which clearly wasn't
enough.  There should be one for the ON COMMIT DROP (which can be added in
normal regression test), one one with all action list populated (that need to
be in isolation tester).  Both are added in the patchset in a suggestion patch,
and for now the first test fails and the second crashes.

- set_session_variable() is documented to either succeed or not change the
  currently set value.  While it's globally true, I see 2 things that could be
  problematic:

  - free_session_variable_value() could technically fail.  However, I don't see
    how it could be happening unless there's a memory corruption, so this would
    result in either an abort, or a backend in a very bad state.  Anyway, since
    pfree() can clearly ereport(ERROR) we should probably do something about
    it.  That being said, I don't really see the point of trying to preserve a
    value that looks like random pointer, which will probably cause a segfault
    the next time it's used.  Maybe add a PG_TRY block around the call and mark
    it as invalid (and set freeval to false) if that happens?

  - the final elog(DEBUG1) can also fail.  It also seems highly unlikely, so
    maybe accept that this exception is ok?  For now I'm adding such a comment
    in a suggestion patch.

- prepare_variable_for_reading() and SetSessionVariable():

It's possible that a session variable is dropped after calling
sync_sessionvars_all(), and we would receive the sinval when acquiring the lock
on VariableRelationId but not process it until the next sync_sessionvars_all
call.  I think we should acquire the lock first and then call
sync_sessionvars_all.  I did that in the suggestion patch.

---

From: Julien Rouhaud <[email-masked]>
Date: Thu, 22 Sep 2022 14:40:59 +0800

>   Schema   | Name |  Type   | Collation | Nullable | Mutable | Default | Owner | Transactional end action

Rebased patcshet against recent conflicts, thanks to Pavel for the reminder.

While sending a new patch, I realized that I forgot mentionning this in
execMain.c:

+	/*

I haven't looked at that part yet, but the comments are a bit obscure.  IIUC
the first branch is for parallel workers only, if the main backend provided the
array, and the 2nd chunk is for the main backend.  If so, it could be made
clearer, and maybe add an assert about IsParallelWorker() (or
!IsParallelWorker()) as needed?

---

From: Julien Rouhaud <[email-masked]>
Date: Sun, 25 Sep 2022 14:56:03 +0800

Hi,

The patch has rotten again, sending an updated version.  Also, after
talking with Pavel, he can't work on this patch before a few days so
I'm adding some extra fixup patches for the things I reported in the
last few emails, so that the cfbot can hopefully turn green.

> !IsParallelWorker()) as needed?

Full list of changes:
  - rebased against multiple conflicts since last version
  - fixed the meson build
  - fixed the ON COMMIT DROP problem and the crash on RESET VARIABLES
  - fixed some copy/pasto in the expected isolation tests (visible now
that it works)
  - added the asserts and tried to clarify the comments for the
session variable handling in QueryDesc (I still haven't really read
that part)
  - did the mentioned modifications on
pg_debug_show_used_session_variables, and used CStringGetTextDatum
macro to simplify the code

Note that while waiting for the CI to finish I noticed that the commit
message for 0001 still mentions the READ/WRITE acl.  The commit
messages will probably need a bit of rewording too once everything
else is fixed, but this one could be changed already.

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 12 Oct 2022 15:26:46 +0200

Hi

> else is fixed, but this one could be changed already.

I fixed the commit message of 0001 patch. Fixed shadowed variables too.

There is a partially open issue, where I and Julien are not sure about a
solution, and we would like to ask for the  community's opinion. I'll send
this query in separate mail.

Regards

Pavel
From 710672e8207b52d21b143da7fa82ba40d6f3cd30 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 
 
index 66312b53b8..a0ebc7b8dd 100644
@@ -10232,6 +10232,21 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>

 

 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+     <varlistentry>

From 64a0febdc8d7c185b5227fb148c2674f53ef431b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

\dV and tab complete for session variables related commands
---
 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 | 71 ++++++++++++++++++++++-----
 5 files changed, 162 insertions(+), 12 deletions(-)

index ab613dd49e..5dec27c404 100644
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
index bd051e09cb..4baa699fcd 100644
 
+/* \dV */
index 584d9d5ae6..126e9429a2 100644

 /*
@@ -1668,8 +1678,8 @@ psql_completion(const char *text, int start, int end)
-		"MERGE INTO", "MOVE", "NOTIFY", "PREPARE",
+		"LISTEN", "LOAD", "LOCK", "MERGE INTO", "MOVE", "NOTIFY", "PREPARE",
 		"SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
@@ -1688,7 +1698,7 @@ psql_completion(const char *text, int start, int end)
@@ -2129,6 +2139,9 @@ psql_completion(const char *text, int start, int end)
@@ -2644,7 +2657,7 @@ psql_completion(const char *text, int start, int end)
@@ -3083,7 +3096,7 @@ psql_completion(const char *text, int start, int end)
@@ -3390,6 +3403,17 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS */
@@ -3505,7 +3529,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
@@ -3632,6 +3656,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 */
@@ -3748,7 +3778,8 @@ psql_completion(const char *text, int start, int end)
@@ -3766,6 +3797,8 @@ psql_completion(const char *text, int start, int end)
 									 "USAGE",
 									 "SET",
 									 "ALTER SYSTEM",
+									 "READ",
+									 "WRITE",
 									 "ALL");
 	}
 
@@ -3807,7 +3840,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("GRANT|REVOKE", MatchAny) ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny))
 	{
-		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
+		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|READ|WRITE|ALL"))
 			COMPLETE_WITH("ON");
 		else if (TailMatches("GRANT", MatchAny))
 			COMPLETE_WITH("TO");
@@ -3830,7 +3863,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",
@@ -3852,7 +3885,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"))
@@ -3860,7 +3894,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");
@@ -3896,6 +3931,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
@@ -4173,7 +4210,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -4461,6 +4498,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
 
@@ -4631,6 +4676,8 @@ psql_completion(const char *text, int start, int end)

From 85ea422fe261bc6a8f04de111785de7b633d6601 Mon Sep 17 00:00:00 2001

index f797b42aa7..938ab24086 100644
index 81f9ae2f02..09d3e68951 100644
index 488c571828..6e2593d956 100644
@@ -779,7 +779,9 @@ transformAssignmentIndirection(ParseState *pstate,
@@ -788,7 +790,9 @@ transformAssignmentIndirection(ParseState *pstate,

index afb922df29..f702c7ad54 100644
@@ -135,7 +135,7 @@ ERROR:  record "c" has no field "x"
@@ -157,7 +157,7 @@ ERROR:  record "c" has no field "x"
 
index 346f594ad0..bd9c048302 100644

 select a from atacc1;

index 08334761ae..d8b2814342 100644
@@ -4997,7 +4997,7 @@ LINE 1: select t2.uunique1 from
@@ -6031,7 +6031,7 @@ lateral (select * from int8_tbl t1,
 
@@ -6041,7 +6041,7 @@ LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
@@ -6076,7 +6076,7 @@ LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
@@ -6096,7 +6096,7 @@ update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1)
 
index 3c4352c0f3..68cd6436a0 100644
 
 -- should fail
 LET v1.x = 10;
-ERROR:  cannot assign to field "x" of column "v1" because there is no such column in data type t1
+ERROR:  cannot assign to field "x" of column or variable "v1" because there is no such column in data type t1
 LINE 1: LET v1.x = 10;
             ^
 DROP VARIABLE v1;
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 2b2cff7d91..60c096b857 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -256,7 +256,7 @@ SELECT * FROM trans_barbaz;	-- should have 1
 BEGIN;
 	SAVEPOINT one;
 		SELECT trans_foo;
-ERROR:  column "trans_foo" does not exist
+ERROR:  column or variable "trans_foo" does not exist
 LINE 1: SELECT trans_foo;
                ^
 	ROLLBACK TO SAVEPOINT one;
@@ -305,7 +305,7 @@ BEGIN;
 	SAVEPOINT one;
 		INSERT INTO savepoints VALUES (5);
 		SELECT trans_foo;
-ERROR:  column "trans_foo" does not exist
+ERROR:  column or variable "trans_foo" does not exist
 LINE 1: SELECT trans_foo;
                ^
 COMMIT;
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index dece7310cf..2514a2e4ac 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -907,7 +907,7 @@ ORDER BY q2,q1;

From e4da0b4b8bf2ac337c4a5d6a57bc9ed8b0b19360 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

index 44fa52cc55..609e152aaf 100644

 
index e8b7898297..9c51987bd7 100644
 
 	int			useDB;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bd9b066e4e..3eeea3cc9d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -288,6 +288,7 @@ static void dumpPolicy(Archive *fout, const PolicyInfo *polinfo);
 static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo);
 static void dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo);
 static void dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo);
+static void dumpVariable(Archive *fout, const VariableInfo * varinfo);
 static void dumpDatabase(Archive *fout);
 static void dumpDatabaseConfig(Archive *AH, PQExpBuffer outbuf,
 							   const char *dbname, Oid dboid);
@@ -4742,6 +4743,232 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 	return next_possible_free_oid;
 }
 
+/*
+ * getVariables
@@ -9396,7 +9623,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -9986,6 +10214,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION:
 			dumpSubscription(fout, (const SubscriptionInfo *) dobj);
 			break;
+		case DO_VARIABLE:
+			dumpVariable(fout, (VariableInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -14362,6 +14593,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",
@@ -17903,6 +18137,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 427f5d45f6..56ad8acac9 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,
@@ -82,7 +83,7 @@ typedef enum
 	DO_PUBLICATION,
 	DO_PUBLICATION_REL,
 	DO_PUBLICATION_TABLE_IN_SCHEMA,
-	DO_SUBSCRIPTION
+	DO_SUBSCRIPTION,
 } DumpableObjectType;
 
 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 
index a869321cdf..9b19fe6b90 100644

@@ -3276,6 +3302,30 @@ my %tests = (
 		},
 	},
 
@@ -3715,6 +3765,21 @@ my %tests = (
 		like => {},
 	},
 
index e583019426..249262523d 100644
@@ -2914,6 +2914,7 @@ Variable
 VariableAssignHook
 VariableCache
 VariableCacheData
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.37.3

From 59f3277a8e44afab643b8e1a7881e0838fe2fac0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 .../isolation/expected/session-variable.out   |  112 ++
 src/test/isolation/isolation_schedule         |    1 +
 .../isolation/specs/session-variable.spec     |   53 +
 .../regress/expected/session_variables.out    | 1309 +++++++++++++++++
 src/test/regress/parallel_schedule            |    2 +-
 src/test/regress/sql/session_variables.sql    |  975 ++++++++++++
 6 files changed, 2451 insertions(+), 1 deletion(-)
 create mode 100644 src/test/isolation/expected/session-variable.out
 create mode 100644 src/test/isolation/specs/session-variable.spec
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

index 0000000000..8eaa7fd326
@@ -0,0 +1,112 @@
+Parsed test spec with 4 sessions
+step create: CREATE VARIABLE myvar AS text;
+step create: CREATE VARIABLE myvar AS text;
+starting permutation: create3 let3 s3 o_c_d o_eox_r create4 let4 drop4 drop3 inval3 discard sc3 clean state
+step create3: CREATE VARIABLE myvar3 AS text;
+step let3: LET myvar3 = 'test';
+step s3: BEGIN;
+step o_c_d: CREATE TEMP VARIABLE myvar_o_c_d AS text ON COMMIT DROP;
+step o_eox_r: CREATE VARIABLE myvar_o_eox_r AS text ON TRANSACTION END RESET; LET myvar_o_eox_r = 'test';
+step create4: CREATE VARIABLE myvar4 AS text;
+step let4: LET myvar4 = 'test';
+step drop4: DROP VARIABLE myvar4;
+step drop3: DROP VARIABLE myvar3;
+step inval3: SELECT COUNT(*) >= 0 FROM pg_foreign_table;
+?column?
+t       
+step discard: DISCARD VARIABLES;
+step sc3: COMMIT;
+step clean: DROP VARIABLE myvar_o_eox_r;
+step state: SELECT varname FROM pg_variable;
+varname
index 5413a59a80..67837ef00f 100644
@@ -108,3 +108,4 @@ test: cluster-conflict-partition
index 0000000000..5a1035a2b2
@@ -0,0 +1,53 @@
+step create		{ CREATE VARIABLE myvar AS text; }
+session s3
+step s3			{ BEGIN; }
+step let3		{ LET myvar3 = 'test'; }
+step o_c_d		{ CREATE TEMP VARIABLE myvar_o_c_d AS text ON COMMIT DROP; }
+step o_eox_r	{ CREATE VARIABLE myvar_o_eox_r AS text ON TRANSACTION END RESET; LET myvar_o_eox_r = 'test'; }
+step create4	{ CREATE VARIABLE myvar4 AS text; }
+step let4		{ LET myvar4 = 'test'; }
+step drop4		{ DROP VARIABLE myvar4; }
+step inval3		{ SELECT COUNT(*) >= 0 FROM pg_foreign_table; }
+step discard	{ DISCARD VARIABLES; }
+step sc3		{ COMMIT; }
+step clean		{ DROP VARIABLE myvar_o_eox_r; }
+step state		{ SELECT varname FROM pg_variable; }
+session s4
+step create3	{ CREATE VARIABLE myvar3 AS text; }
+step drop3		{ DROP VARIABLE myvar3; }
+# test for DISCARD ALL when all internal queues have actions registered
+permutation create3 let3 s3 o_c_d o_eox_r create4 let4 drop4 drop3 inval3 discard sc3 clean state
index 0000000000..3c4352c0f3
@@ -0,0 +1,1309 @@
+  LET g = 1;
+  DISCARD VARIABLES;
+  LET g = 1;
+  DISCARD VARIABLES;
+SELECT schema, removed FROM pg_debug_show_used_session_variables();
+ schema | removed 
+--------+---------
+        | t
+SELECT schema, name = :varid::text AS ok, removed FROM pg_debug_show_used_session_variables() f WHERE f.varid = :varid;;
+ schema | ok | removed 
+--------+----+---------
+        | t  | t
 
 # ----------
index 0000000000..c055b42aef
@@ -0,0 +1,975 @@
+  LET g = 1;
+  DISCARD VARIABLES;
+  LET g = 1;
+  DISCARD VARIABLES;
+SELECT schema, removed FROM pg_debug_show_used_session_variables();
+SELECT schema, name = :varid::text AS ok, removed FROM pg_debug_show_used_session_variables() f WHERE f.varid = :varid;;

From 89a6e7f96e5a63132d5d6e6fb2286c79cf8271e8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

index fd5796f1b9..25e3981797 100644
index b5942e282e..ae3943a1f5 100644
@@ -794,6 +794,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 /*
index ef85d3bb68..bac97a22db 100644

index a647342948..d41ed099c3 100644

+			case PLPGSQL_STMT_LET:
 
+/* ----------
index 8d7b6b58c0..22fc6b2ca2 100644
 
 	return "unknown";

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
+typedef struct PLpgSQL_stmt_let
+{
+	PLpgSQL_stmt_type cmd_type;
+	int			lineno;
+	unsigned int stmtid;
+	PLpgSQL_expr *expr;
+} PLpgSQL_stmt_let;
+
 /*
  * PERFORM statement
  */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index fa7da444fe..e583019426 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1777,6 +1777,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
-- 
2.37.3

From 6be699c709b1fbd8ffa00079c35ce66b6dbafffe Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 
index ae3943a1f5..87900f211c 100644

diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index d77fead276..d442a0dffa 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -2952,6 +2952,9 @@ CreateCommandTag(Node *parsetree)
 				case DISCARD_SEQUENCES:
 					tag = CMDTAG_DISCARD_SEQUENCES;
 					break;
+				case DISCARD_VARIABLES:
+					tag = CMDTAG_DISCARD_VARIABLES;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6dd609e69a..e8ae45ce64 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3569,7 +3569,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;

From 37801be6f25dd7c5ebc4b5719144b0b70275701c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 9 Sep 2022 14:03:15 +0200
Subject: [PATCH 03/10] LET command

 src/backend/executor/meson.build         |   1 +
 28 files changed, 738 insertions(+), 10 deletions(-)

index f86983c660..57dbefbd77 100644
index 406b97561d..825998e64d 100644
@@ -20,16 +20,20 @@
 
 /*
@@ -1263,6 +1267,85 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit,
 	}
 }
 
+/*
  * pg_debug_show_used_session_variables - designed for testing
 
index c3fcede3fb..e53e9acbd3 100644
diff --git a/src/backend/executor/meson.build b/src/backend/executor/meson.build
index 518674cfa2..651f2ba63b 100644
--- a/src/backend/executor/meson.build
+++ b/src/backend/executor/meson.build
@@ -62,6 +62,7 @@ backend_sources += files(
   'nodeWindowAgg.c',
   'nodeWorktablescan.c',
   'spi.c',
+  'svariableReceiver.c',
   'tqueue.c',
   'tstoreReceiver.c',
 )
index 0a7b22f97e..e609b134e1 100644
@@ -3815,6 +3815,16 @@ raw_expression_tree_walker_impl(Node *node,
+				if (WALK(stmt->target))
+				if (WALK(stmt->query))

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -
index 477ae9af34..b5942e282e 100644
@@ -305,7 +305,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -453,6 +453,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
@@ -716,7 +717,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY

 

@@ -16912,6 +16959,7 @@ unreserved_keyword:
@@ -17482,6 +17530,7 @@ bare_label_keyword:
 
 			/*
 
 			/*
index 9e8c24b6a8..f797b42aa7 100644
 
 			/* okay */
 			break;
@@ -1952,6 +1953,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:
@@ -3327,6 +3329,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 
 			/*
index bd8057bc3e..488c571828 100644
@@ -88,7 +88,9 @@ transformTargetEntry(ParseState *pstate,
index fb0c687bd8..d722ba743d 100644
@@ -3708,6 +3709,39 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		}
 	}
 
+	/*
+			InitVariable(&var, resultVariable, false);
 
 	/*

 		case DestTupleQueue:
 
index d1a7d995e1..d77fead276 100644
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 /*
 
 /*
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index aae3d25112..d53148b5ca 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -40,4 +40,7 @@ extern void AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit,
 														SubTransactionId mySubid,
 														SubTransactionId parentSubid);
 
index 958eb285e0..6dd609e69a 100644
@@ -137,6 +137,7 @@ typedef struct Query
 
@@ -1699,6 +1700,21 @@ typedef struct MergeStmt
 
index 767079fd5a..a837f178c8 100644
 
 	/* 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 43e4c2e136..389bb8869e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -50,7 +50,7 @@ typedef struct PlannedStmt
 
 	NodeTag		type;

 
index 9b14b7adbe..d2362b5203 100644

 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 92e1d36702..fa7da444fe 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1392,6 +1392,7 @@ LargeObjectDesc
 LastAttnumInfo
 Latch
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
@@ -2639,6 +2640,7 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+svariableState
 SVariable
 SVariableData
 SVariableXActAction
-- 
2.37.3

From d88b1734ccead9427328a8f164f2516f99a19200 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 11 Sep 2022 20:56:40 +0200
Subject: [PATCH 02/10] session variables

 src/backend/catalog/namespace.c         |    5 +-
 src/backend/commands/meson.build        |    1 +
 src/backend/commands/session_variable.c | 1261 +++++++++++++++++++++--
 src/backend/executor/execMain.c         |   58 ++
 src/backend/parser/parse_expr.c         |  233 ++++-
 src/backend/utils/misc/guc_tables.c     |   10 +
 src/include/commands/session_variable.h |   12 +-
 34 files changed, 2083 insertions(+), 109 deletions(-)

index 0ede5e126b..790f42b6b9 100644

index 731b9805ad..989a100ea8 100644
 
+		/* A variable parameter depends on the session variable */
+		if (param->paramkind == PARAM_VARIABLE)
+			add_object_address(OCLASS_VARIABLE, param->paramvarid, 0,
+							   context->addrs);
+
 		/* A parameter must depend on the parameter's datatype */
 		add_object_address(OCLASS_TYPE, param->paramtype, 0,
 						   context->addrs);
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index b1d4cf3114..6a4d471e89 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -3219,7 +3219,7 @@ IdentifyVariable(List *names, char **attrname, bool *not_unique)
 		 * invalidation messages we processed did not change the final answer.
 		 * So we're done.
 		 *
-		 * If we got a different OID, we've locked the relation that used to
+		 * 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.
 		 */
@@ -3233,7 +3233,7 @@ IdentifyVariable(List *names, char **attrname, bool *not_unique)
 		}
 
 		/*
-		 * Lock relation.  This will also accept any pending invalidation
+		 * 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
@@ -3249,6 +3249,7 @@ IdentifyVariable(List *names, char **attrname, bool *not_unique)
 
 		retry = true;
 		old_varid = varid;
+		varid = InvalidOid;
 	}
 
 	return varid;
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 9b350d025f..440a15cf67 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -36,6 +36,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
index 3eafc05f89..406b97561d 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  src/backend/commands/sessionvariable.c
+ *	  src/backend/commands/session_variable.c
  *
  *-------------------------------------------------------------------------
  */
@@ -22,87 +22,262 @@
 #include "commands/session_variable.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "optimizer/optimizer.h"
+#include "storage/lmgr.h"
+#include "storage/proc.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/syscache.h"
 
 /*
- * The life cycle of temporary session variable can be
- * limmited by using clause ON COMMIT DROP.
+ * Values of session variables are stored in the backend local memory,
+ * in sessionvars hash table in binary format, in a dedicated memory
+ * context SVariableMemoryContext.  A session variable value can stay
+ * valid for longer than the transaction that assigns its value.  To
+ * make sure that the underlying memory is eventually freed, but not
+ * before it's guarantee that the value won't be needed anymore, we
+ * need to handle the two following points:
+ *
+ * - We need detect when a variable is dropped, whether in the current
+ * transaction in the current session or by another session, and mark
+ * the underlying entries for removal.  To protect the content against
+ * possibly rollbacked DROP VARIABLE commands, the entries (and
+ * memory) shouldn't be freed immediately but be postponed until the
+ * end of the transaction.
+ *
+ * - The session variable can be dropped explicitly (by DROP VARIABLE
+ *  command) or implicitly (using ON COMMIT DROP clause), and the
+ *  value can be implicitly removed (using the ON TRANSACTION END
+ *  clause).  In all those cases the memory should also be freed at
+ *  the transaction end.
+ *
+ * To achieve that, we maintain 3 queues of actions to be performed at
+ * certain time:
+ * - a List of SVariableXActActionItem, to handle ON COMMIT DROP
+ * variables, and delayed memory cleanup of variable dropped by the
+ * current transaction.  Those actions are transactional (for instance
+ * we don't want to cleanup the memory of a rollbacked DROP VARIABLE)
+ * so the structure is needed to keep track of the final transaction
+ * state
+ * - a List of variable Oid for the ON TRANSACTION ON RESET variables
+ * - a List of variable Oid for the concurrent DROP VARIABLE
+ * notification we receive via shared invalidations.
+ *
+ * Note that although resetting a variable doesn't technically require
+ * to remove the entry from the sessionvars hash table, we currently
+ * do it.  It's a simple way to implement the reset, and helps to
+ * reduce memory usage and prevents the hash table from bloating.
+ *
+ * There are two different ways to do the final access to session
+ * variables: buffered (indirect) or direct. Buffered access is used
+ * in regular DML statements, where we have to ensure the stability of
+ * the variable values.  The session variables have the same behaviour
+ * as external query parameters, which is consistent with using
+ * PL/pgSQL's variables in embedded queries in PL/pgSQL.
+ *
+ * This is implemented by using an aux buffer (an array) that holds a
+ * copy of values of used (in query) session variables, which is also
+ * transmitted to the parallel workers.  The values from this array
+ * are passed as constant (EEOP_CONST).
+ *
+ * Direct access is used by simple expression evaluation (PLpgSQL).
+ * In this case we don't need to ensure the stability of passed
+ * values, and maintaining the buffer with copies of values of session
+ * variables would be useless overhead.  In this case we just read the
+ * value of the session variable directly (EEOP_PARAM_VARIABLE). This
+ * strategy removes the necessity to modify related PL/pgSQL code to
+ * support session variables (the reading of session variables is
+ * fully transparent for PL/pgSQL).
  */
 typedef enum SVariableXActAction
 {
 	SVAR_ON_COMMIT_DROP,		/* used for ON COMMIT DROP */
+	SVAR_ON_COMMIT_RESET,		/* used for DROP VARIABLE */
 } SVariableXActAction;

 	/*
-	 * creating_subid is the ID of the creating subxact. If the action was
-	 * unregistered during the current transaction, deleting_subid is the ID
-	 * of the deleting subxact, otherwise InvalidSubTransactionId.
+	 * creating_subid is the ID of the sub-transaction that registered
+	 * the action. If the action was unregistered during the current
+	 * transaction, deleting_subid is the ID of the deleting
+	 * sub-transaction, otherwise InvalidSubTransactionId.
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
 } SVariableXActActionItem;
 
+/* List of SVariableXActActionItem */
 
+/*
+ * To process ON TRANSACTION END RESET variables, for which we always
+ * need to clear the saved values.
+ */
+static List *xact_reset_varids = NIL;
 
 /*
- * Do the necessary work to setup local memory management of a new
- * variable.
- *
- * Caller should already have created the necessary entry in catalog
- * and made them visible.
+ * When the session variable is dropped we need to free local memory. The
+ * session variable can be dropped by current session, but it can be
+ * dropped by other's sessions too, so we have to watch sinval message.
+ * But because we don't want to free local memory immediately, we need to
+ * hold list of possibly dropped session variables and at the end of
+ * transaction, we check session variables from this list against system
+ * catalog. This check can be postponed into next transaction if
+ * current transactions is in aborted state, as we wouldn't be able to
+ * access the system catalog.
  */
-void
-SessionVariableCreatePostprocess(Oid varid, char eoxaction)
+static List *xact_recheck_varids = NIL;
+
+typedef struct SVariableData
 {
+	Oid			varid;			/* pg_variable OID of this sequence
+								   (hash key) */
+
 	/*
-	 * For temporary variables, we need to create a new end of xact action to
-	 * ensure deletion from catalog.
+	 * The session variable is identified by oid. The oid is unique in
+	 * catalog. Unfortunately, the memory cleanup can be postponed to
+	 * the beginning
+	 * of the session next transaction, and it's possible that this next
+	 * transaction sees a different variable with the same oid.  We
+	 * therefore need an extra identifier to distinguish both cases. We
+	 * use the LSN number of session variable at creation time. The
+	 * value of session variable (in memory) is valid, when there is a
+	 * record in pg_variable with same oid and same create_lsn.
 	 */
-	if (eoxaction == VARIABLE_EOX_DROP)
-	{
-		Assert(isTempNamespace(get_session_variable_namespace(varid)));
+	XLogRecPtr	create_lsn;
 
-		register_session_variable_xact_action(varid, SVAR_ON_COMMIT_DROP);
+	bool		isnull;
+	bool		freeval;
+	Datum		value;
+
+	Oid			typid;
+	int16		typlen;
+	bool		typbyval;
+
+	bool		is_domain;
+	void	   *domain_check_extra;
+	LocalTransactionId domain_check_extra_lxid;
+
+	/*
+	 * Top level local transaction id of the last transaction that dropped the
+	 * variable if any.  We need this information to avoid freeing memory for
+	 * variable dropped by the local backend that may be eventually rollbacked.
+	 */
+	LocalTransactionId drop_lxid;
+
+	bool		is_not_null;	/* don't allow null values */
+	bool		is_immutable;	/* true when variable is immutable */
+	bool		has_defexpr;	/* true when variable has a default value */
+
+	bool		is_valid;		/* true when variable was successfully
+								 * initialized */
+
+	uint32		hashvalue;		/* used for pairing sinval message */
+
+	bool		eox_reset;		/* true, when lifecycle is limitted by
+								 * transaction */
+} SVariableData;
+
+typedef SVariableData *SVariable;
+
+static HTAB *sessionvars = NULL;	/* hash table for session variables */
+
+static MemoryContext SVariableMemoryContext = NULL;
+
+static void create_sessionvars_hashtables(void);
+static void free_session_variable_value(SVariable svar);
+static void init_session_variable(SVariable svar, Variable *var);
+static bool is_session_variable_valid(SVariable svar);
+static void pg_variable_cache_callback(Datum arg, int cacheid,
+									   uint32 hashvalue);
+static SVariable prepare_variable_for_reading(Oid varid);
+static void register_session_variable_xact_action(Oid varid,
+												  SVariableXActAction action);
+static void remove_session_variable(SVariable svar);
+static void remove_session_variable_by_id(Oid varid);
+static void set_session_variable(SVariable svar, Datum value, bool isnull,
+								 bool init_mode);
+static const char *SVariableXActActionName(SVariableXActAction action);
+static void sync_sessionvars_all(bool filter_lxid);
+static void unregister_session_variable_xact_action(Oid varid,
+													SVariableXActAction action);
+
+
+/*
+ * Returns human readable name of SVariableXActAction value.
+ */
+static const char *
+SVariableXActActionName(SVariableXActAction action)
+{
+	switch (action)
+	{
+		case SVAR_ON_COMMIT_DROP:
+			return "ON COMMIT DROP";
+		case SVAR_ON_COMMIT_RESET:
+			return "ON COMMIT RESET";
+		default:
+			elog(ERROR, "unknown SVariableXActAction action %d", action);
 	}
 }
 
 /*
- * Handle the local memory cleanup for a DROP VARIABLE command.
- *
- * Caller should take care of removing the pg_variable entry first.
+ * Free all memory allocated for the given session variable, but
+ * preserve the hash entry in sessionvars.
+ * This function shouldn't contain anything that could ereport(ERROR).
  */
-void
-SessionVariableDropPostprocess(Oid varid)
+static void
+free_session_variable_value(SVariable svar)
 {
+	/* Clean current value */
+	if (!svar->isnull)
+	{
+		if (svar->freeval)
+		{
+			PG_TRY();
+			{
+				pfree(DatumGetPointer(svar->value));
+			}
+			PG_CATCH();
+			{
+				/*
+				 * AllocSetFree can raise an "could not find block containing chunk"
+				 * error. In this case, the session persistent memory is corrupted,
+				 * and becase we don't know, what is reason, and what data are corrupted,
+				 * then it is safer to raise FATAL error.
+				 */
+				elog(FATAL, "the memory used by session variable is currupted");
+			}
+			PG_END_TRY();
+
+			svar->freeval = false;
+		}
+
+		svar->isnull = true;
+	}
+
+	svar->value = (Datum) 0;
+	svar->freeval = false;
+
 	/*
-	 * The entry was removed from catalog already, we must not do it
-	 * again at end of xact time.
+	 * We can mark this session variable as valid when it has not default
+	 * expression, and when null is allowed. When it has defexpr, then the
+	 * content will be valid after an assignment or defexp evaluation.
 	 */
-	unregister_session_variable_xact_action(varid, SVAR_ON_COMMIT_DROP);
+	svar->is_valid = !svar->has_defexpr && !svar->is_not_null;
 }
 
 /*
-/*
+ * values of session variables dropped in other backends.
@@ -111,27 +286,30 @@ register_session_variable_xact_action(Oid varid,
 
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	elog(DEBUG1, "SVariableXActAction \"%s\" is registered for session variable (oid:%u)",
+		 SVariableXActActionName(action), varid);
+
+	oldcxt = MemoryContextSwitchTo(TopTransactionContext);

 	xact_ai->varid = varid;

 
 
 /*
- * Unregister an action on a given session variable from action list. In this
- * moment, the action is just marked as deleted by setting deleting_subid. The
- * calling even might be rollbacked, in which case we should not lose this
- * action.
+ * Unregister an action on a given session variable from the action list.
+ * The action is just marked as deleted by setting deleting_subid.
+ * The calling subtransaction even might be rollbacked, in which case the
+ * action shouldn't be removed.
  */
 static void
 unregister_session_variable_xact_action(Oid varid,
@@ -139,43 +317,847 @@ unregister_session_variable_xact_action(Oid varid,
 {
 	ListCell   *l;
 
-	Assert(action == SVAR_ON_COMMIT_DROP);
+	elog(DEBUG1, "SVariableXActAction \"%s\" is unregistered for session variable (oid:%u)",
+		 SVariableXActActionName(action), varid);

 
 /*
- * Perform ON TRANSACTION END RESET or ON COMMIT DROP
- * and COMMIT/ROLLBACK of transaction session variables.
+ * Release the given session variable from sessionvars hashtab and free
+ * all underlying allocated memory.
+ */
+static void
+remove_session_variable(SVariable svar)
+{
+	free_session_variable_value(svar);
+
+	/*
+	 * In this moment, the session variable is not in catalog, so only saved
+	 * oid can be displayed.
+	 */
+	elog(DEBUG1, "session variable (oid:%u) is removing from memory",
+		 svar->varid);
+
+	if (hash_search(sessionvars,
+					(void *) &svar->varid,
+					HASH_REMOVE,
+					NULL) == NULL)
+		elog(DEBUG1, "hash table corrupted");
+}
+
+/*
+ * Release the session variable defined by varid from sessionvars
+ * hashtab and free all underlying allocated memory.
+ */
+static void
+remove_session_variable_by_id(Oid varid)
+{
+	SVariable	svar;
+	bool		found;
+
+	if (!sessionvars)
+		return;
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+								   HASH_FIND, &found);
+	if (found)
+		remove_session_variable(svar);
+}
+
+/*
+ * Callback function for session variable invalidation.
+ *
+ * It queues a list of variable Oid in xact_recheck_varids.
+ */
+static void
+pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue)
+{
+	HASH_SEQ_STATUS status;
+	SVariable	svar;
+
+	/*
+	 * There is no guarantee of sessionvars being initialized, even when
+	 * receiving an invalidation callback, as DISCARD [ ALL | VARIABLES ]
+	 * destroys the hash table entirely.
+	 */
+	if (!sessionvars)
+		return;
+
+	elog(DEBUG1, "pg_variable_cache_callback %u %u", cacheid, hashvalue);
+
+	/*
+	 * When the hashvalue is not specified, then we have to recheck all
+	 * currently used session variables. Since we can't guarantee the exact
+	 * session variable from its hashValue, we also have to iterate over
+	 * all items of the sessionvars hash table.
+	 */
+	hash_seq_init(&status, sessionvars);
+
+	while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+	{
+		if (hashvalue == 0 || svar->hashvalue == hashvalue)
+		{
+			MemoryContext oldcxt;
+
+			/* The list needs to be able to survive the transaction */
+			oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+
+			xact_recheck_varids = lappend_oid(xact_recheck_varids,
+											  svar->varid);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			elog(DEBUG1, "session variable (oid:%u) should be rechecked (forced by sinval)",
+				 svar->varid);
+		}
+
+		/*
+		 * although it there is low probability, we have to iterate over all
+		 * locally set session variables, because hashvalue is not a unique
+		 * identifier.
+		 */
+	}
+}
+
+/*
+ * Returns true when the entry in pg_variable is valid for the given session
+ * variable.
+ */
+static bool
+is_session_variable_valid(SVariable svar)
+{
+	HeapTuple	tp;
+	bool		result = false;
+
+	tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+	if (HeapTupleIsValid(tp))
+	{
+		/*
+		 * In this case, the only oid cannot be used as unique identifier,
+		 * because the oid counter can wraparound, and the oid can be used for
+		 * new other session variable. We do a second check against 64bit
+		 * unique identifier.
+		 */
+		if (svar->create_lsn == ((Form_pg_variable) GETSTRUCT(tp))->create_lsn)
+			result = true;
+
+		ReleaseSysCache(tp);
+	}
+
+	return result;
+}
+
+/*
+ * Recheck the possibly invalidated variables (in memory) against system
+ * catalog. This routine is called before any read or any write from/to session
+ * variables and when processing a committed transaction.
+ * If filter_lxid is true, this function will ignore the recheck for variables
+ * that have the same cached local transaction id as the transaction current
+ * top level local transaction id, ie. the variables dropped in the current top
+ * level transaction or any underlying subtransaction.
+ */
+static void
+sync_sessionvars_all(bool filter_lxid)
+{
+	SVariable	svar;
+	ListCell   *l;
+
+	if (!xact_recheck_varids)
+		return;
+
+	/*
+	 * If the sessionvars hashtable is NULL (which can be done by DISCARD
+	 * VARIABLES), we are sure that there aren't any active session variable
+	 * in this session.
+	 */
+	if (!sessionvars)
+	{
+		list_free(xact_recheck_varids);
+		xact_recheck_varids = NIL;
+		return;
+	}
+
+	elog(DEBUG1, "effective call of sync_sessionvars_all()");
+
+	/*
+	 * The recheck list can contain many duplicates, so clean it up before
+	 * processing to avoid extraneous work.
+	 */
+	list_sort(xact_recheck_varids, list_oid_cmp);
+	list_deduplicate_oid(xact_recheck_varids);
+
+	/*
+	 * This routine is called before any reading, so the session should be in
+	 * transaction state. This is required to access the system catalog.
+	 */
+	Assert(IsTransactionState());
+
+	foreach(l, xact_recheck_varids)
+	{
+		bool		found;
+		Oid			varid = lfirst_oid(l);
+
+		svar = (SVariable) hash_search(sessionvars, &varid,
+									   HASH_FIND, &found);
+
+		/*
+		 * Remove invalid variables, but don't touch variables that were
+		 * dropped by the current top level local transaction or any
+		 * subtransaction underneath, as there's no guarantee that the
+		 * transaction will be committed.  Such variables will be removed in
+		 * the next transaction if needed.
+		 */
+		if (found)
+		{
+			/*
+			 * If this is a variable dropped by the current transaction,
+			 * ignore it and keep the oid to recheck in the next transaction.
+			 */
+			if (filter_lxid && svar->drop_lxid == MyProc->lxid)
+				continue;
+
+			if (!is_session_variable_valid(svar))
+				remove_session_variable(svar);
+		}
+
+		/*
+		 * If caller asked to filter the list, we have to clean items as they
+		 * are processed.
+		 */
+		if (filter_lxid)
+			xact_recheck_varids = foreach_delete_current(xact_recheck_varids,
+														 l);
+	}
+
+	/*
+	 * If caller didn't ask to filter the list, all items have been processed
+	 * so we can simply destroy the list.
+	 */
+	if (!filter_lxid)
+	{
+		list_free(xact_recheck_varids);
+		xact_recheck_varids = NIL;
+	}
+}
+
+/*
+ * Create the hash table for storing session variables.
+ */
+static void
+create_sessionvars_hashtables(void)
+{
+	HASHCTL		vars_ctl;
+
+	Assert(!sessionvars);
+
+	/* set callbacks */
+	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);
+	}
+
+	Assert(SVariableMemoryContext);
+
+	memset(&vars_ctl, 0, sizeof(vars_ctl));
+	vars_ctl.keysize = sizeof(Oid);
+	vars_ctl.entrysize = sizeof(SVariableData);
+	vars_ctl.hcxt = SVariableMemoryContext;
+
+	Assert(sessionvars == NULL);
+
+	sessionvars = hash_create("Session variables", 64, &vars_ctl,
+							  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+/*
+ * Assign some content to the session variable. It's copied to
+ * SVariableMemoryContext if necessary.
+ *
+ * init_mode is true when the value of session variable should be initialized
+ * by the default expression if any. This is the only case where we allow the
+ * modification of an immutable variables with default expression.
+ *
+ * If any error happens, the existing value shouldn't be modified.
+ */
+static void
+set_session_variable(SVariable svar, Datum value, bool isnull, bool init_mode)
+{
+	Datum		newval = value;
+
+	Assert(svar && OidIsValid(svar->typid));
+
+	/* Don't allow assignment of null to NOT NULL variable */
+	if (isnull && svar->is_not_null)
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
+
+	/*
+	 * Don't allow the modification of an immutable session variable that
+	 * already has an assigned value (possibly NULL) or has a default
+	 * expression (in which case the value should always be the result of
+	 * default expression evaluation) unless the variable is being initialized.
+	 */
+	if (!init_mode &&
+		(svar->is_immutable &&
+		 (svar->is_valid || svar->has_defexpr)))
+		ereport(ERROR,
+				(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
+				 errmsg("session variable \"%s.%s\" is declared IMMUTABLE",
+						get_namespace_name(get_session_variable_namespace(svar->varid)),
+						get_session_variable_name(svar->varid))));
+
+	if (!isnull)
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+
+		newval = datumCopy(value, svar->typbyval, svar->typlen);
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else
+	{
+		/* The caller shouldn't have provided any real value. */
+		Assert(value == (Datum) 0);
+	}
+
+	/*
+	 * No error should happen after this poiht, otherwise we could leak the
+	 * newly allocated value if any.
+	 */
+
+	/*
+	 * free_session_variable_value can technically error out, but we may have
+	 * already allocated the new value in SVariableMemoryContext, so make sure
+	 * that we assign the value (and the rest of the fields) to the svar before
+	 * throwing the error.
+	 */
+	free_session_variable_value(svar);
+
+	svar->value = newval;
+
+	svar->isnull = isnull;
+	svar->freeval = newval != value;
+	svar->is_valid = true;
+
+	/*
+	 * XXX While unlikely, an error here is possible.
+	 * It wouldn't leak memory as the allocated chunk has already been
+	 * correctly assigned to the session variable, but would contradict this
+	 * function contract, which is that this function should either succeed or
+	 * leave the current value untouched.
+	 */
+	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new value",
+		 get_namespace_name(get_session_variable_namespace(svar->varid)),
+		 get_session_variable_name(svar->varid),
+		 svar->varid);
+}
+
+/*
+ * Initialize session variable svar from variable var
+ */
+static void
+init_session_variable(SVariable svar, Variable *var)
+{
+	MemoryContext oldcxt;
+
+	Assert(OidIsValid(var->oid));
+
+	svar->varid = var->oid;
+	svar->create_lsn = var->create_lsn;
+
+	svar->typid = var->typid;
+
+	get_typlenbyval(var->typid, &svar->typlen, &svar->typbyval);
+	svar->is_domain = (get_typtype(var->typid) == TYPTYPE_DOMAIN);
+
+	svar->domain_check_extra = NULL;
+	svar->domain_check_extra_lxid = InvalidLocalTransactionId;
+
+	svar->value = (Datum) 0;
+	svar->isnull = true;
+	svar->freeval = false;
+
+	svar->is_not_null = var->is_not_null;
+	svar->is_immutable = var->is_immutable;
+	svar->has_defexpr = var->has_defexpr;
+
+	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
+											ObjectIdGetDatum(var->oid));
+
+	/* the value of variable is not known yet */
+	svar->is_valid = false;
+
+	svar->eox_reset = var->eoxaction == VARIABLE_EOX_RESET ||
+		var->eoxaction == VARIABLE_EOX_DROP;
+
+	oldcxt = MemoryContextSwitchTo(TopTransactionContext);
+
+	if (svar->eox_reset)
+		xact_reset_varids = lappend_oid(xact_reset_varids, var->oid);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Search a seesion variable in the hash table given its oid. If it
+ * doesn't exist, then insert it (and calculate defexpr if it exists).
+ *
+ * Caller is responsible for doing permission checks.
+ *
+ * As side effect this function acquires AccessShareLock on
+ * related session variable until the end of the transaction.
+ */
+static SVariable
+prepare_variable_for_reading(Oid varid)
+{
+	SVariable	svar;
+	Variable	var;
+	bool		found;
+
+	var.oid = InvalidOid;
+
+	if (!sessionvars)
+		create_sessionvars_hashtables();
+
+	/* Protect used session variable against drop until transaction end */
+	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	/*
+	 * Make sure that all entries in sessionvars hash table are valid, but
+	 * keeping variables dropped by the current transaction.
+	 */
+	sync_sessionvars_all(true);
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+								   HASH_ENTER, &found);
+
+	/* Return content if it is available and valid */
+	if (!found || !svar->is_valid)
+	{
+		/* We need to load defexpr. */
+		InitVariable(&var, varid, false);
+
+		if (!found)
+		{
+			init_session_variable(svar, &var);
+
+			elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new entry in memory (emitted by READ)",
+				 get_namespace_name(get_session_variable_namespace(varid)),
+				 get_session_variable_name(varid),
+				 varid);
+		}
+
+		/*
+		 * Raise an error if this is a NOT NULL variable without default
+		 * expression.
+		 */
+		if (var.is_not_null && !var.defexpr)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+							get_namespace_name(get_session_variable_namespace(varid)),
+							get_session_variable_name(varid)),
+					 errdetail("The session variable was not initialized yet.")));
+
+		if (svar->has_defexpr)
+		{
+			Datum		value = (Datum) 0;
+			bool		isnull;
+			EState	   *estate = NULL;
+			Expr	   *defexpr;
+			ExprState  *defexprs;
+			MemoryContext oldcxt;
+
+			/* Prepare default expr */
+			estate = CreateExecutorState();
+
+			oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+			defexpr = expression_planner((Expr *) var.defexpr);
+			defexprs = ExecInitExpr(defexpr, NULL);
+			value = ExecEvalExprSwitchContext(defexprs,
+											  GetPerTupleExprContext(estate),
+											  &isnull);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			/* Store result before releasing Executor memory */
+			set_session_variable(svar, value, isnull, true);
+
+			FreeExecutorState(estate);
+		}
+		else
+			set_session_variable(svar, (Datum) 0, true, true);
+	}
+
+	/*
+	 * Although the value of domain type should be valid (it is checked when
+	 * it is assigned to session variable), we have to check related
+	 * constraints each time we access the variable. It can be more expensive
+	 * than in PL/pgSQL, as PL/pgSQL forces domain checks only when the value is assigned
+	 * to the variable or when the value is returned from function.
+	 * However, domain types have a constraint cache so it's not too much
+	 * expensive..
+	 */
+	if (svar->is_domain)
+	{
+		/*
+		 * Store domain_check extra in TopTransactionContext. When we are in
+		 * other transaction, the domain_check_extra cache is not valid
+		 * anymore.
+		 */
+		if (svar->domain_check_extra_lxid != MyProc->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->lxid;
+	}
+
+	return svar;
+}
+
+/*
+ * Store the given value in an SVariable, and cache it if not already present.
+ *
+ * Caller is responsible for doing permission checks.
+ *
+ * As side effect this function acquires AccessShareLock on
+ * related session variable until the end of the transaction.
+ */
+void
+SetSessionVariable(Oid varid, Datum value, bool isNull)
+{
+	SVariable	svar;
+	bool		found;
+
+	if (!sessionvars)
+		create_sessionvars_hashtables();
+
+	/* Protect used session variable against drop until transaction end */
+	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+	/*
+	 * Make sure that all entries in sessionvars hash table are valid, but
+	 * keeping variables dropped by the current transaction.
+	 */
+	sync_sessionvars_all(true);
+
+	svar = (SVariable) hash_search(sessionvars, &varid,
+								   HASH_ENTER, &found);
+
+	if (!found)
+	{
+		Variable	var;
+
+		/* We don't need to know defexpr here */
+		InitVariable(&var, varid, true);
+		init_session_variable(svar, &var);
+
+		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new entry in memory (emitted by WRITE)",
+			 get_namespace_name(get_session_variable_namespace(svar->varid)),
+			 get_session_variable_name(svar->varid),
+			 varid);
+	}
+
+	/*
+	 * This should either succeed or fail without changing the currently stored
+	 * value.
+	 */
+	set_session_variable(svar, value, isNull, false);
+}
+
+/*
+ * Wrapper around SetSessionVariable after checking for correct permission.
+ */
+void
+SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull)
+{
+	AclResult	aclresult;
+
+	/*
+	 * Is caller allowed to update the session variable?
+	 */
+	aclresult = pg_variable_aclcheck(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 of the session variable specified by its oid.
+ * Caller is responsible for doing permission checks.
+ */
+Datum
+CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
+{
+	SVariable	svar;
+	Datum		result;
+
+	svar = prepare_variable_for_reading(varid);
+	Assert(svar != NULL && svar->is_valid);
+
+	*typid = svar->typid;
+
+	/* force copy of non NULL value */
+	if (!svar->isnull)
+	{
+		result = datumCopy(svar->value, svar->typbyval, svar->typlen);
+		*isNull = false;
+	}
+	else
+	{
+		result = (Datum) 0;
+		*isNull = true;
+	}
+
+	return (Datum) result;
+}
+
+/*
+ * Returns a copy of ths value of the session variable specified by its oid
+ * with a check of the expected type. Like previous CopySessionVariable, the
+ * caller is responsible for doing permission checks.
+ */
+Datum
+CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
+{
+	SVariable	svar;
+	Datum		result;
+
+	svar = prepare_variable_for_reading(varid);
+	Assert(svar != NULL && 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));
+
+	if (!svar->isnull)
+	{
+		result = datumCopy(svar->value, svar->typbyval, svar->typlen);
+		*isNull = false;
+	}
+	else
+	{
+		result = (Datum) 0;
+		*isNull = true;
+	}
+
+	return (Datum) result;
+}
+
+/*
+ * Returns the value of the session variable specified by its oid  with a check
+ * of the expected type. Like CopySessionVariable, the caller is responsible
+ * for doing permission checks.
+ */
+Datum
+GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
+{
+	SVariable	svar;
+
+	svar = prepare_variable_for_reading(varid);
+	Assert(svar != NULL && 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));
+
+	*isNull = svar->isnull;
+
+	return svar->value;
+}
+
+/*
+ * Do the necessary work to setup local memory management of a new
+ * variable.
+ *
+ * Caller should already have created the necessary entry in catalog
+ * and made them visible.
+ */
+void
+SessionVariableCreatePostprocess(Oid varid, char eoxaction)
+{
+	/*
+	 * For temporary variables, we need to create a new end of xact action to
+	 * ensure deletion from catalog.
+	 */
+	if (eoxaction == VARIABLE_EOX_DROP)
+	{
+		Assert(isTempNamespace(get_session_variable_namespace(varid)));
+
+		register_session_variable_xact_action(varid, SVAR_ON_COMMIT_DROP);
+	}
+}
+
+/*
+ * Handle the local memory cleanup for a DROP VARIABLE command.
+ *
+ * Caller should take care of removing the pg_variable entry first.
+ */
+void
+SessionVariableDropPostprocess(Oid varid)
+{
+	/*
+	 * The entry was removed from catalog already, we must not do it
+	 * again at end of xact time.
+	 */
+	unregister_session_variable_xact_action(varid, SVAR_ON_COMMIT_DROP);
+
+	if (sessionvars)
+	{
+		bool found;
+		SVariable svar = (SVariable) hash_search(sessionvars, &varid,
+												 HASH_FIND, &found);
+
+		if (found)
+		{
+			/*
+			 * Save the current top level local transaction id to make sure we
+			 * don't automatically remove the local variable storage in
+			 * sync_sessionvars_all, as the DROP VARIABLE will send an
+			 * invalidation message.
+			 */
+			Assert(LocalTransactionIdIsValid(MyProc->lxid));
+			svar->drop_lxid = MyProc->lxid;
+
+			/*
+			 * For variables that are not ON TRANSACTION END RESET, we need to
+			 * register an SVAR_ON_COMMIT_RESET action to free the local
+			 * memory for this variable when the top level transaction
+			 * is committed (we don't need to wait for sinval
+			 * message).  The cleanup action for one session variable can be
+			 * duplicated in the action list without causing any problem, so we
+			 * don't need to ensure uniqueness.  We need a different action
+			 * from RESET, because RESET is executed on any transaction end,
+			 * but we want to execute this cleanup only when the current
+			 * transaction will be committed. This action can be reverted by
+			 * ABORT of DROP VARIABLE command.
+			 */
+			if (!svar->eox_reset)
+				register_session_variable_xact_action(varid,
+													  SVAR_ON_COMMIT_RESET);
+		}
+	}
+}
+
+/*
+ * Fast drop of the complete content of all session variables hash table, and
+ * cleanup of any list that wouldn't be relevant anymore.
+ * This is used by DISCARD VARIABLES (and DISCARD ALL) command.
+ */
+void
+ResetSessionVariables(void)
+{
+	ListCell   *lc;
+
+	/* Destroy hash table and reset related memory context */
+	if (sessionvars)
+	{
+		hash_destroy(sessionvars);
+		sessionvars = NULL;
+	}
+
+	/* Release memory allocated by session variables */
+	if (SVariableMemoryContext != NULL)
+		MemoryContextReset(SVariableMemoryContext);
+
+	/*
+	 * There isn't any session variable left, but we still need to retain the
+	 * ON COMMIT DROP actions if any.
+	 */
+	foreach(lc, xact_on_commit_actions)
+	{
+		SVariableXActActionItem *xact_ai =
+			(SVariableXActActionItem *) lfirst(lc);
+
+		if (xact_ai->action == SVAR_ON_COMMIT_DROP)
+			continue;
+
+		pfree(xact_ai);
+		xact_on_commit_actions = foreach_delete_current(xact_on_commit_actions,
+													   lc);
+	}
+
+	/* We should clean xact_reset_varids */
+	list_free(xact_reset_varids);
+	xact_reset_varids = NIL;
+
+	/*
+	 * xact_recheck_varids is stored in SVariableMemoryContext, so it has
+	 * already been freed, just reset the list.
+	 */
+	xact_recheck_varids = NIL;
+}
+
+/*
+ * Perform the necessary work for ON TRANSACTION END RESET and ON COMMIT DROP
+ * session variables.
+ * If the transaction is committed, also process the delayed memory cleanup of
+ * local DROP VARIABLE and process all pending rechecks.
  */
 void
 AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 {
 	ListCell   *l;
 
-	foreach(l, xact_drop_actions)
+	/*
+	 * Clean memory for all ON TRANSACTION END RESET  variables. Do it first,
+	 * as it reduces the overhead of the RECHECK action list.
+	 */
+	foreach(l, xact_reset_varids)
 	{
-		SVariableXActActionItem *xact_ai =
-		(SVariableXActActionItem *) lfirst(l);
+		remove_session_variable_by_id(lfirst_oid(l));
+	}
 
+	/* We can now clean xact_reset_varids */
+			if (xact_ai->deleting_subid != InvalidSubTransactionId)
+				continue;
 
 			/*
-			 * ON COMMIT DROP is allowed only for temp session variables. So
-			 * we should explicitly delete only when current transaction was
-			 * committed. When it's rollback, then session variable is removed
-			 * automatically.
+			 * ON COMMIT DROP is allowed only for temp session variables.
+			 * So we should explicitly delete only when the current
+			 * transaction is committed. When it's rollbacked, the session
+			 * variable is removed automatically.
 			 */
-			if (isCommit)
+			if (xact_ai->action == SVAR_ON_COMMIT_DROP)
 			{
 				ObjectAddress object;
 
@@ -191,10 +1173,30 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 				elog(DEBUG1, "session variable (oid:%u) will be deleted (forced by SVAR_ON_COMMIT_DROP action)",
 					 xact_ai->varid);
 
+				/*
+				 * If the variable was locally set, the memory will be
+				 * automatically cleaned up when we process the underlying
+				 * shared invalidation for this drop.  There can't be a recheck
+				 * action for this variable, so there's nothing to gain
+				 * explicitly removing it here.
+				 */
 				performDeletion(&object, DROP_CASCADE,
 								PERFORM_DELETION_INTERNAL |
 								PERFORM_DELETION_QUIETLY);
 			}
+			else
+			{
+				/*
+				 * When we process DROP VARIABLE statement issued by the
+				 * current transaction, we create an SVAR_ON_COMMIT_RESET xact
+				 * action. We want to process this action only when related
+				 * transaction is commited (when DROP VARIABLE statement
+				 * sucessfully processed) as we need to preserve the variable
+				 * content if the transaction that issued the DROP VARAIBLE
+				 * statement is rollbacked.
+				 */
+				remove_session_variable_by_id(xact_ai->varid);
+			}
 		}
 	}
 
@@ -202,41 +1204,160 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 	 * Any drop action left is an entry that was unregistered and not
 	 * rollbacked, so we can simply remove them.
 	 */
-	list_free_deep(xact_drop_actions);
-	xact_drop_actions = NIL;
+	list_free_deep(xact_on_commit_actions);
+	xact_on_commit_actions = NIL;
+
+	/*
+	 * We process the list of recheck last for performance reason,the previous
+	 * steps might remove entries from the hash table.
+	 * We need catalog access to process the recheck, so this can only be done
+	 * if the transaction is committed.  Otherwise, we just keep the recheck
+	 * list as-is and it will be processed at the next (committed) transaction.
+	 */
+	if (isCommit && xact_recheck_varids)
+	{
+		Assert(sessionvars);
+
+		sync_sessionvars_all(false);
+	}
 }
 
 /*
- * Post-subcommit or post-subabort cleanup of xact action list.
+ * Post-subcommit or post-subabort cleanup of xact_on_commit_actions list.
  *
  * During subabort, we can immediately remove entries created during this
  * subtransaction. During subcommit, just transfer entries marked during
  * this subtransaction as being the parent's responsibility.
  */
 void
-AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySubid,
+AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit,
+											SubTransactionId mySubid,
 											SubTransactionId parentSubid)
 {
 	ListCell   *cur_item;

+		/*
+		 * The subtransaction that created this entry was rollbacked, we can
+		 * remove it.
+		 */
 		if (!isCommit && xact_ai->creating_subid == mySubid)
 		{
-			/* cur_item must be removed */
-			xact_drop_actions = foreach_delete_current(xact_drop_actions, cur_item);
+			xact_on_commit_actions = foreach_delete_current(xact_on_commit_actions, cur_item);
 			pfree(xact_ai);
 		}
 		else
 		{
-			/* cur_item must be preserved */
+			/* Otherwise cur_item must be preserved */
 			if (xact_ai->creating_subid == mySubid)
 				xact_ai->creating_subid = parentSubid;
+
 			if (xact_ai->deleting_subid == mySubid)
-				xact_ai->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId;
+				xact_ai->deleting_subid = isCommit ? parentSubid
+												   : InvalidSubTransactionId;
 		}
 	}
 }
+
+/*
+ * pg_debug_show_used_session_variables - designed for testing
+ *
+ * This is a function designed for testing and debugging.  It returns the
+ * content of sessionvars as-is, and can therefore display entries about
+ * session variables that were dropped but for which this backend didn't
+ * process the shared invalidations yet.
+ */
+Datum
+pg_debug_show_used_session_variables(PG_FUNCTION_ARGS)
+{
+#define NUM_PG_DEBUG_SHOW_USED_SESSION_VARIABLES_ATTS 10
+
+	elog(DEBUG1, "pg_debug_show_used_session_variables start");
+
+	SetSingleFuncCall(fcinfo, 0);
+
+	if (sessionvars)
+	{
+		ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+		HASH_SEQ_STATUS status;
+		SVariable	svar;
+
+		/*
+		 * Make sure that all entries in sessionvars hash table are valid, but
+		 * keeping variables dropped by the current transaction.
+		 */
+		sync_sessionvars_all(true);
+
+		hash_seq_init(&status, sessionvars);
+
+		while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
+		{
+			Datum		values[NUM_PG_DEBUG_SHOW_USED_SESSION_VARIABLES_ATTS];
+			bool		nulls[NUM_PG_DEBUG_SHOW_USED_SESSION_VARIABLES_ATTS];
+			HeapTuple	tp;
+
+			memset(values, 0, sizeof(values));
+			memset(nulls, 0, sizeof(nulls));
+
+			values[0] = ObjectIdGetDatum(svar->varid);
+			values[3] = ObjectIdGetDatum(svar->typid);
+
+			/* check if session variable is visible in system catalog */
+			tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid));
+
+			/*
+			 * Sessionvars can hold data of variables removed from catalog,
+			 * (and not purged) and then namespacename and name cannot be read
+			 * from catalog.
+			 */
+			if (HeapTupleIsValid(tp))
+			{
+				Form_pg_variable varform = (Form_pg_variable) GETSTRUCT(tp);
+
+				/* When we see data in catalog */
+				values[1] = CStringGetTextDatum(
+												get_namespace_name(varform->varnamespace));
+
+				values[2] = CStringGetTextDatum(NameStr(varform->varname));
+
+				values[4] = CStringGetTextDatum(format_type_be(svar->typid));
+				values[5] = BoolGetDatum(false);
+				values[6] = BoolGetDatum(svar->is_valid);
+
+				values[8] = BoolGetDatum(
+										 pg_variable_aclcheck(svar->varid, GetUserId(), ACL_SELECT) == ACLCHECK_OK);
+
+				values[9] = BoolGetDatum(
+										 pg_variable_aclcheck(svar->varid, GetUserId(), ACL_UPDATE) == ACLCHECK_OK);
+
+				ReleaseSysCache(tp);
+			}
+			else
+			{
+				/*
+				 * When session variable was removed from catalog, but we
+				 * haven't processed the invlidation yet.
+				 */
+				nulls[1] = true;
+				values[2] = CStringGetTextDatum(
+												DatumGetCString(DirectFunctionCall1(oidout, svar->varid)));
+				values[4] = PointerGetDatum(
+											cstring_to_text(format_type_be(svar->typid)));
+				values[5] = BoolGetDatum(true);
+				values[6] = BoolGetDatum(svar->is_valid);
+				nulls[7] = true;
+				nulls[8] = true;
+			}
+
+			tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+		}
+	}
+
+	elog(DEBUG1, "pg_debug_show_used_session_variables end");
+
+	return (Datum) 0;
+}
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 25a94bbaaa..953b139058 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -33,6 +33,7 @@
 #include "access/nbtree.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_type.h"
+#include "commands/session_variable.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -994,6 +995,80 @@ 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;
+
+							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;
+							}
+
+							if (es_session_variables)
+							{
+								SessionVariableValue *var;
+
+								/*
+								 * 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);
+							}
+							else
+							{
+								AclResult	aclresult;
+								Oid			varid = param->paramvarid;
+								Oid			vartype = param->paramtype;
+
+								/*
+								 * When the expression is evaluated directly
+								 * without query executor start (plpgsql simple
+								 * expr evaluation), then the array es_session_variables
+								 * is null. In this case we need to use direct
+								 * access to session variables. The values are
+								 * not protected by using copy, but it is not
+								 * problem (we don't need to emulate stability
+								 * of the value).
+								 *
+								 * In this case we should to do aclcheck, because
+								 * usual aclcheck from standard_ExecutorStart
+								 * is not executed in this case. Fortunately
+								 * it is just once per transaction.
+								 */
+								aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_SELECT);
+								if (aclresult != ACLCHECK_OK)
+									aclcheck_error(aclresult, OBJECT_VARIABLE,
+												   get_session_variable_name(varid));
+
+								scratch.opcode = EEOP_PARAM_VARIABLE;
+								scratch.d.vparam.varid = varid;
+								scratch.d.vparam.vartype = vartype;
+								ExprEvalPushStep(state, &scratch);
+							}
+						}
+						break;
+
 					case PARAM_EXTERN:
 
 						/*
 
index d78862e660..c3fcede3fb 100644
@@ -200,6 +201,63 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 
+	/*
+		 * When a parallel query needs to access query parameters (including
+		 * related session variables), then related session variables are
+		 * restored (deserialized) in queryDesc already. So just push pointer
+		 * of this array to executor's estate.
+		Assert(IsParallelWorker());
+		Assert(!IsParallelWorker());
index 99512826c5..a07400e5ea 100644

 

 

index 5d0fd6e072..44142165dd 100644
 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node
 
index 7fb32a0710..618325e4f5 100644
 
 	/*

@@ -2230,6 +2232,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
@@ -2352,6 +2355,29 @@ eval_const_expressions_mutator(Node *node,
 
 				/*
@@ -4736,22 +4762,45 @@ substitute_actual_parameters_mutator(Node *node,

 

 

 

 

 

 
index 3527b78695..9e8c24b6a8 100644
 
 /* GUC parameters */

 /*
 
+/*
@@ -779,6 +809,149 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
+	/*
@@ -813,6 +986,64 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
 
index 855d48372a..76aa2b393e 100644
 
@@ -12091,6 +12101,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 	/*
 
 /*
 
 	return false;
 }
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 05ab087934..3a5db7c5bb 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -1472,6 +1472,16 @@ struct config_bool ConfigureNamesBool[] =
 		false,
 		NULL, NULL, NULL
 	},
+	{
+		{"session_variables_ambiguity_warning", PGC_USERSET, DEVELOPER_OPTIONS,
+			gettext_noop("Raise warning when reference to session variable is ambiguous."),
+			NULL,
+			GUC_NOT_IN_SAMPLE
+		},
+		&session_variables_ambiguity_warning,
+		false,
+		NULL, NULL, NULL
+	},
 	{
 		{"db_user_namespace", PGC_SIGHUP, CONN_AUTH_AUTH,
 			gettext_noop("Enables per-database user names."),
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e83512fd9a..f4ab0e9a49 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11826,4 +11826,11 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+{ oid => '8488', descr => 'debug list of used session variables',
+  proname => 'pg_debug_show_used_session_variables', prorows => '1000', proretset => 't',
+  provolatile => 's', prorettype => 'record', proargtypes => '',
+  proallargtypes => '{oid,text,text,oid,text,bool,bool,bool,bool}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o}',
+  proargnames => '{varid,schema,name,typid,typname,removed,has_value,can_select,can_update}',
+  prosrc => 'pg_debug_show_used_session_variables' },
 ]
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 51934e8d1a..aae3d25112 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
- * sessionvariable.h
- *	  prototypes for sessionvariable.c.
+ * session_variable.h
+ *	  prototypes for session_variable.c.
  *
  *
  * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
@@ -26,16 +26,18 @@
 extern void ResetSessionVariables(void);
 extern void SessionVariableCreatePostprocess(Oid varid, char eoxaction);
 extern void SessionVariableDropPostprocess(Oid varid);
+extern void SessionVariableCreatePostprocess(Oid varid, char eoxaction);
 
 extern Datum CopySessionVariable(Oid varid, bool *isNull, Oid *typid);
 extern Datum CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid);
 extern Datum GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid);

 extern void AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit);
-extern void AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySubid,
+extern void AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit,
+														SubTransactionId mySubid,
 														SubTransactionId parentSubid);
 
 #endif

 

+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
index cf3fc1a6a1..958eb285e0 100644
@@ -146,6 +146,7 @@ typedef struct Query

index 6bda383bea..767079fd5a 100644

 
+	/* 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 21e642a64c..43e4c2e136 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -92,6 +92,8 @@ typedef struct PlannedStmt

 
 
 typedef struct Param
 
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 9dffdcfd1e..ebb14e250f 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -117,4 +117,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);

 /* GUC parameters */

 

 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 41ddc2a093..92e1d36702 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2454,6 +2454,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
@@ -2638,6 +2639,8 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariable
+SVariableData
 SVariableXActAction
 SVariableXActActionItem
 Syn
-- 
2.37.3

From 6a04db57e1b3163c0eff4bdc4cb55f02fdf970f2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/10] catalog support for session variables

Implementation new system object class - session variable with new access rights SELECT, UPDATE,
 src/backend/catalog/meson.build         |   1 +
 src/backend/catalog/pg_variable.c       | 372 +++++++++++++++++++++
 src/backend/commands/session_variable.c | 242 ++++++++++++++
 src/include/catalog/meson.build         |   1 +
 src/include/catalog/pg_variable.h       | 120 +++++++
 41 files changed, 2067 insertions(+), 19 deletions(-)

index c1ffbd89b8..0ede5e126b 100644

@@ -4996,6 +5003,8 @@ CommitSubTransaction(void)
@@ -5160,6 +5169,8 @@ AbortSubTransaction(void)

 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index aa5a2ed948..b894a0a89d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -59,6 +59,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
@@ -115,6 +116,7 @@ static void ExecGrant_Namespace(InternalGrant *istmt);
 static void ExecGrant_Tablespace(InternalGrant *istmt);
 static void ExecGrant_Type(InternalGrant *istmt);
 static void ExecGrant_Parameter(InternalGrant *istmt);
+static void ExecGrant_Variable(InternalGrant *istmt);
 
@@ -266,6 +268,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
@@ -510,6 +515,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
@@ -615,6 +624,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
@@ -805,6 +817,16 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
@@ -894,6 +916,33 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
@@ -1053,6 +1102,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
@@ -1244,6 +1297,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
 
+		case OBJECT_VARIABLE:
@@ -1475,6 +1534,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
@@ -1535,6 +1597,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
@@ -3367,6 +3432,129 @@ ExecGrant_Parameter(InternalGrant *istmt)
 
+static void
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3568,6 +3756,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;
@@ -3679,6 +3870,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;
@@ -3827,6 +4021,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return pg_type_aclmask(table_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return pg_variable_aclmask(table_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized objtype: %d",
 				 (int) objtype);
@@ -4810,6 +5006,66 @@ pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how)
 	return result;
 }
 
+/*
@@ -5110,6 +5366,18 @@ pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
 		return ACLCHECK_NO_PRIV;
 }
 
+/*
@@ -5727,6 +5995,33 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid)
 
+/*
@@ -5855,6 +6150,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 
+		case OBJECT_VARIABLE:
index 7f3e64b5ae..731b9805ad 100644

+		case OCLASS_VARIABLE:
+			DropVariable(object->objectId);
+			break;
+
 			/*
 			 * These global object types are not supported here.
 			 */
@@ -2966,6 +2974,9 @@ getObjectClass(const ObjectAddress *object)

 	/* shouldn't get here */
diff --git a/src/backend/catalog/meson.build b/src/backend/catalog/meson.build
index 0874791451..27306e3592 100644
--- a/src/backend/catalog/meson.build
+++ b/src/backend/catalog/meson.build
@@ -30,6 +30,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 92aed2ff8b..b1d4cf3114 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -39,6 +39,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
@@ -764,6 +765,69 @@ RelationIsVisible(Oid relid)
 	return visible;
 }
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
@@ -2839,6 +2903,356 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
+/*
 
 /*
  * DeconstructQualifiedName
@@ -4656,3 +5070,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)
 
index 284ca55469..069fa9453b 100644
 
 /*

 	/*
 
+/*
@@ -2325,6 +2370,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
@@ -2635,6 +2681,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
@@ -3536,6 +3587,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
@@ -3888,6 +3965,16 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
@@ -4664,6 +4751,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 
+		case OCLASS_VARIABLE:
@@ -5771,6 +5862,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
@@ -6014,6 +6109,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index bc26bf1ef5..d18e859db5 100644
@@ -1613,6 +1614,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
index 0000000000..f4a06c47a3
@@ -0,0 +1,372 @@
+static ObjectAddress create_variable(const char *varName,
+ * Creates entry in pg_variable table
+create_variable(const char *varName,
+ * Creates a new variable
+CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
+	variable = create_variable(stmt->variable->relname,
+	/* We want SessionVariableCreatePostprocess to see the catalog changes. */
+	SessionVariableCreatePostprocess(variable.objectId, stmt->eoxaction);
+ * Drop variable by OID, and register the needed session variable
+ * cleanup.
+DropVariable(Oid varid)
+	/* Do the necessary cleanup if needed in local memory */
+	SessionVariableDropPostprocess(varid);
+InitVariable(Variable *var, Oid varid, bool fast_only)
 
index 441f29d684..73be29c01c 100644

 			/*
 
 			/*
index 0000000000..3eafc05f89
@@ -0,0 +1,242 @@
+ * Do the necessary work to setup local memory management of a new
+ * variable.
+ * Caller should already have created the necessary entry in catalog
+ * and made them visible.
+SessionVariableCreatePostprocess(Oid varid, char eoxaction)
+		Assert(isTempNamespace(get_session_variable_namespace(varid)));
+ * Handle the local memory cleanup for a DROP VARIABLE command.
+ * Caller should take care of removing the pg_variable entry first.
+SessionVariableDropPostprocess(Oid varid)
+	 * The entry was removed from catalog already, we must not do it
index 20135ef1b0..27cd5a9b18 100644

 		rel = relation_open(pg_depend->objid, AccessShareLock);
@@ -12707,6 +12747,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_PUBLICATION_REL:
 			case OCLASS_SUBSCRIPTION:
 			case OCLASS_TRANSFORM:
+			case OCLASS_VARIABLE:
 
 				/*
index 94d5142a4a..477ae9af34 100644
 #include "gramparse.h"
@@ -293,8 +294,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -474,6 +475,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
@@ -643,6 +645,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 /*
@@ -753,8 +757,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UESCAPE UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING

 

 /*

 
 opt_column: COLUMN

@@ -16940,6 +17072,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -17551,6 +17685,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)

index 247d0816ad..d1a7d995e1 100644
 
+				address = CreateVariable(pstate, (CreateSessionVarStmt *) parsetree);
 
index 4fac402e5b..80a46b0925 100644
 
-
 /*
 
 	return subname;
 }
+

 	/*
index 98a1a84289..84a851bfa6 100644

 /* flag bits for performDeletion/performMultipleDeletions: */
 #define PERFORM_DELETION_INTERNAL			0x0001	/* internal action */
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index 45ffa99692..d041d728fb 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -63,6 +63,7 @@ catalog_headers = [
   'pg_publication_rel.h',
   'pg_subscription.h',
   'pg_subscription_rel.h',
+  'pg_variable.h',
 ]
 
 bki_data = [
index 2a2a2e6489..976bef6a42 100644

 
 
index 68bb032d3e..e83512fd9a 100644
@@ -6277,6 +6277,9 @@
 
index 0000000000..cde36b0cea
@@ -0,0 +1,120 @@
+extern ObjectAddress CreateVariable(ParseState *pstate,
+									CreateSessionVarStmt *stmt);
+extern void DropVariable(Oid varid);
+extern void InitVariable(Variable *var, Oid varid, bool fast_only);
index 0000000000..51934e8d1a
+extern void SessionVariableCreatePostprocess(Oid varid, char eoxaction);
+extern void SessionVariableDropPostprocess(Oid varid);
index 633e7671b3..cf3fc1a6a1 100644
@@ -1908,6 +1908,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -3035,6 +3036,25 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+
index ccc927851c..9b14b7adbe 100644
@@ -454,6 +454,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)

index 9a4df3a5da..8070eb1452 100644

 

 
index d9b839c979..41ddc2a093 100644
@@ -500,6 +500,7 @@ CreateRoleStmt
@@ -883,6 +884,7 @@ Form_pg_ts_parser
@@ -2636,6 +2638,8 @@ SupportRequestRows

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 13 Oct 2022 07:41:32 +0200

Hi

> this query in separate mail.

 rebased with simplified code related to usage of pfree function

Regards

Pavel

> Pavel
From f5303e13b136235b237b595a5274d27146511c6d Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+     <varlistentry>

From 831d7aed2180cf099739b653db2d946af9c07aa7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From e48a39954bebbd0bc2e243ee2992162d2d5b9118 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

 
 # ----------

From 27aa8b243f4cd722e183f974f8ee7342e0098587 Mon Sep 17 00:00:00 2001

 

 
 
 select a from atacc1;

 

 -- should fail
 LET v1.x = 10;

From 23e55c6b40a0a415fda658b43dd0842d7e211d7f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From 5d817ccf94501bc71cdd91d734692a617240ca2c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 

From 0c9c938e1f29cd1634c9e137572f41c4dfd29559 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 177cd71b0584b82cc15b89cf5769e2ceef1b03ce Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 9 Sep 2022 14:03:15 +0200
Subject: [PATCH 03/10] LET command

index 68325f0aae..26034b2fe6 100644
 
 /*
@@ -1236,6 +1240,85 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit,
 	}
 }
 
+/*

 
 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 

 	NodeTag		type;

 

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

From ddd97235afc116a00f03e246d18e3c157e0140ea Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 11 Sep 2022 20:56:40 +0200
Subject: [PATCH 02/10] session variables

 src/backend/commands/session_variable.c | 1234 +++++++++++++++++++++--
 34 files changed, 2056 insertions(+), 109 deletions(-)

 

 		/*
 
 		retry = true;
 		old_varid = varid;
+		varid = InvalidOid;
 	}
 
 	return varid;
index 3eafc05f89..68325f0aae 100644
@@ -22,87 +22,246 @@
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*
@@ -111,27 +270,30 @@ register_session_variable_xact_action(Oid varid,

 
 	xact_ai->varid = varid;

 
 
 /*
@@ -139,43 +301,836 @@ unregister_session_variable_xact_action(Oid varid,
 {
 	ListCell   *l;

 
 
 /*

 
 			/*
 
@@ -191,10 +1146,30 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 				elog(DEBUG1, "session variable (oid:%u) will be deleted (forced by SVAR_ON_COMMIT_DROP action)",
 					 xact_ai->varid);
 
+				/*
 
@@ -202,41 +1177,160 @@ AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
 
 /*

+		/*
 
 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
 
 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

 #endif

 

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

 

 

 
 
 typedef struct Param

 
 /* GUC parameters */

 

 

From d86c48e0d9ecf7963cc573a9b5ac348b24e24129 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/10] catalog support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*

 
 				/*

 /*

 

 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 

-
 /*
 
 	return subname;
 }
+

 	/*

 
 bki_data = [

 

 
 
+

---

From: Julien Rouhaud <[email-masked]>
Date: Mon, 17 Oct 2022 11:17:43 +0800

Hi,

Thanks!

>  rebased with simplified code related to usage of pfree function

If anyone is curious the discussion happend at [1].

I looked at the patchset, this time focusing on the LET command.  Here at the
comments I have for now:

- gram.y

(and other similar places) the comment should be kept to the last statement

Also, having LetStmt as an ExplainableStmt means it's allowed in a CTE:

cte_list:
		common_table_expr						{ $$ = list_make1($1); }
		| cte_list ',' common_table_expr		{ $$ = lappend($1, $3); }
		;

common_table_expr:  name opt_name_list AS opt_materialized '(' PreparableStmt ')' opt_search_clause opt_cycle_clause

And doing so hits this assert in transformWithClause:

		if (!IsA(cte->ctequery, SelectStmt))
		{
			/* must be a data-modifying statement */
			Assert(IsA(cte->ctequery, InsertStmt) ||
				   IsA(cte->ctequery, UpdateStmt) ||
				   IsA(cte->ctequery, DeleteStmt));

			pstate->p_hasModifyingCTE = true;
		}

and I'm assuming it would also fail on this in transformLetStmt:

I guess it makes sense to be able to explain a LetStmt (or using it in a
prepared statement), so it should be properly handled in transformSelectStmt.
Also, I don't see any test for a prepared LET statement, this should also be
covered.

- transformLetStmt:

It would be nice to have a comment saying that the lock is acquired here

I'm wondering if this should be an Assert instead, as the grammar shouldn't
produce anything else no matter what how hard a user try.

This isn't covered by any regression test and it probably should.  It can be
reached with something like

LET myvar = (null::pg_class).*;

The error message could also use a bit of improvement.

I see that a_expr allows a select statement in parens, but this leads to a
sublink which already has all the required protection to gurantee a single
column, and a single row at most during execution.  This one returns for
non-scalar case:

subquery must return only one column

Maybe use something similar for it, like "expression must return only one
column"?  Similarly the error message in svariableStartupReceiver could be made
more consistent with the related errors:

+		if (++outcols > 1)

While on svariableReceiver, I see that the current code assumes that the caller
did everything right.  That's the case right now, but it should still be made
more robust in case future code (or extensions) is added.  I'm thinking:

- svariableState.rows.  Currently not really used, should check that one and
  only one row is received in svariableReceiveSlot and
  svariableShutdownReceiver (if no row is received the variable won't be reset
  which should probably always happen once you setup an svariableReceiver)
- svariableState.typid, typmod and typlen should be double checked with the
  given varid in svariableStartupReceiver.
- svariableState.varid should be initialized with InvalidOid to avoid undefined
  behavior is caller forgets to set it.

I'm also wondering if SetVariableDestReceiverParams() should have an assert
like LockHeldByMe() for the given varid, and maybe an assert that the varid is
a session variable, to avoid running a possibly expensive execution that will
fail when receiving the slot.  I think the function would be better named
SetVariableDestReceiverVarid() or something like that.

+void

Why 2 rows?  I'm assuming it's an attempt to detect queries that returns more
than 1 row, but it should be documented.  Note that as mentioned above the dest
receiver currently doesn't check it, so this definitely needs to be fixed.

- IdentifyVariable:

*attrname can be set even is no variable is identified.  I guess that's ok as
it avoids useless code, but it should probably be documented in the function
header.

Also, the API doesn't look ideal.  AFAICS the only reason this function doesn't
error out in case of ambiguous name is that transformColumnRef may check if a
given name shadows a variable when session_variables_ambiguity_warning is set.
But since IdentifyVariable returns InvalidOid if the given list of identifiers
is ambiguous, it seems that the shadow detection can fail to detect a shadowed
reference if multiple variable would shadow the name:

# CREATE TYPE ab AS (a integer, b integer);
CREATE TYPE
# CREATE VARIABLE v_ab AS ab;
CREATE VARIABLE

# CREATE TABLE v_ab (a integer, b integer);
CREATE TABLE

# SET session_variables_ambiguity_warning = 1;
SET

# sELECT v_ab.a FROM v_ab;
WARNING:  42702: session variable "v_ab.a" is shadowed
LINE 1: select v_ab.a from v_ab;
               ^
DETAIL:  Session variables can be shadowed by columns, routine's variables and routine's arguments with the same name.
 a
---
(0 rows)

# CREATE SCHEMA v_ab;
CREATE SCHEMA

# CREATE VARIABLE v_ab.a AS integer;
CREATE VARIABLE

# SELECT v_ab.a FROM v_ab;
 a
---
(0 rows)

Note that a bit later in transformColumnRef(), not_unique is checked only if
the returned varid is valid, which isn't correct as InvalidOid is currently
returned if not_unique is set.

I think that the error should be raised in IdentifyVariable rather than having
every caller check it.  I'm not sure how to perfectly handle the
session_variables_ambiguity_warning though.  Maybe make not_unique optional,
and error out if not_unique is null.  If not null, set it as necessary and
return one of the oid.  The only use would be for shadowing detection, and in
that case it won't be possible to check if a warning can be avoided as it would
be if no amgibuity is found, but that's probably ok.

Or maybe instead LookupVariable should have an extra argument to only match
variable with a composite type if caller asks to.  This would avoid scenarios
like:

CREATE VARIABLE myvar AS int;
SELECT myvar.blabla;
ERROR:  42809: type integer is not composite

Is that really ok to match a variable here rather than complaining about a
missing FROM-clause?

I'm not sure why you use this approach rather than just having something like
"ListCell *indirection_head", set it to a non-NULL value when needed, and use
that (with names) instead.  Note that it's also not correct to compare a List
to NULL, use NIL instead.

- expr_kind_allows_session_variables

Even if that's a bit annoying, I think it's better to explicitly put all values
there rather than having a default clause.

For instance, EXPR_KIND_CYCLE_MARK is currently allowing session variables,
which doesn't look ok.  It's probably just an error from when the patchset was
rebased, but this probably wouldn't happen if you get an error for an unmatched
value if you add a new expr kind (which doesn't happen that often).

[1] https://www.postgresql.org/message-id/[email-masked]

---

From: Dmitry Dolgov <[email-masked]>
Date: Sun, 30 Oct 2022 19:05:42 +0100

>  rebased with simplified code related to usage of pfree function

Thanks for the patch, great work!

I've got a couple of questions, although I haven't fully finished reviewing yet
(so more to come):

* I'm curious about ALTER VARIABLE. Current implementation allows altering only
  the name, schema or the owner -- why not e.g. immutability?

* psql tab completion implementation mentions that CREATE VARIABLE could be
  used inside CREATE SCHEMA:

    /* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
    /* Complete CREATE VARIABLE <name> with AS */
    else if (TailMatches("IMMUTABLE"))

  Is that correct? It doesn't like it works, and from what I see it requires
  some modifications in transformCreateSchemaStmt and schema_stmt.

* psql describe mentions the following:

	/*
	 * 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.
	 */

  I guess it's taken from listTables, and the extended versions says "because
  of the possibility that the user is confused about what the two pattern
  arguments mean". Are those historical reasons apply to variables as well?

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 30 Oct 2022 19:49:41 +0100

Hi

>   the name, schema or the owner -- why not e.g. immutability?

It is just in category - "not implemented yet". The name, schema or owner
doesn't change behavior. It can be possible (in next versions) to change
type, default expression, immutability (I think). But the patch is long
enough so I prefer just to support basic generic ALTER related to schema,
and other possibilities to implement in next iterations.

>   some modifications in transformCreateSchemaStmt and schema_stmt.

yes,

This is a bug. It should be fixed

>   arguments mean". Are those historical reasons apply to variables as well?

The behave is same like the tables

(2022-10-30 19:48:14) postgres=# \dt
Did not find any relations.
(2022-10-30 19:48:16) postgres=# \dV
Did not find any session variables.

Thank you for comments

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 31 Oct 2022 21:27:21 +0100

Hi

fixed

> common_table_expr:  name opt_name_list AS opt_materialized '('
> PreparableStmt ')' opt_search_clause opt_cycle_clause
>

The LET statement doesn't return data, so it should be disallowed similar
like  MERGE statement

I enhanced the regression test about PREPARE of the LET statement. I found
and fix the missing plan dependency of target variable of LET command

done

done

>  exprLocation((Node *) exprList))));
>

done - the error message is like related plpgsql error message

done

done

done

>   behavior is caller forgets to set it.

svariableState is initialized by palloc0

> like LockHeldByMe() for the given varid,

done

> and maybe an assert that the varid is

done

done

> receiver currently doesn't check it, so this definitely needs to be fixed.

done + check + tests

> it avoids useless code, but it should probably be documented in the

This is a side effect. The attrname is used only when the returned oid is
valid. I checked code, and
I extended comments on the function.

I am sending updated patch, next points I'll process tomorrow

> DETAIL:  Session variables can be shadowed by columns, routine's variables
> and routine's arguments with the same name.
>  a
> ---
> (0 rows)
>
> # CREATE SCHEMA v_ab;
> CREATE SCHEMA
>
>                 (Node *) param,
>                 targetName,
>                 targetIsArray,
>                 typid,
>                 typmod,
>                 InvalidOid,
>                 indirection,
>                 list_head(indirection),
>                 (Node *) expr,
>                 COERCION_PLPGSQL,
>                 stmt->location);

+ fixed issue reported by Dmitry Dolgov

>
> [1]
> https://www.postgresql.org/message-id/[email-masked]
>
From 0f88c135c3ae3f061e6a7167acca810778e3aa7b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;

+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 
index 5df5a0ad59..bc6e8b81ce 100644

 
 
index 3decd5f245..b0760da2ae 100644

From e2a159db4332afbf2831f770658f339831bfdc7c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 doc/src/sgml/ref/create_schema.sgml           |   7 +-
 19 files changed, 978 insertions(+), 12 deletions(-)

 
 
index 559eb898a9..605a754f05 100644
@@ -10233,6 +10233,21 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>

 

 
   <para>
 
diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 3c2dddb163..f7488fd01b 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>
 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+     <varlistentry>

From 76c68e0b27955eee828d8465d908370bbb193c50 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 .../isolation/expected/session-variable.out   |  112 ++
 src/test/isolation/isolation_schedule         |    1 +
 .../isolation/specs/session-variable.spec     |   53 +
 .../regress/expected/session_variables.out    | 1389 +++++++++++++++++
 src/test/regress/parallel_schedule            |    2 +-
 src/test/regress/sql/session_variables.sql    | 1045 +++++++++++++
 6 files changed, 2601 insertions(+), 1 deletion(-)
 create mode 100644 src/test/isolation/expected/session-variable.out
 create mode 100644 src/test/isolation/specs/session-variable.spec
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

index 0000000000..216add9e4b
@@ -0,0 +1,1389 @@
+CREATE SCHEMA svartest CREATE VARIABLE xx AS int CREATE TABLE foo(a int);
+LET svartest.xx = 100;
+DROP SCHEMA svartest CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table svartest.foo
+drop cascades to session variable svartest.xx
+LET svartest.xx = 100;
+ERROR:  session variable "svartest.xx" doesn't exist
+LINE 1: LET svartest.xx = 100;
+-- don't allow multi column query
+CREATE TYPE vartesttp AS (a1 int, b1 int, c1 int);
+CREATE VARIABLE v1 AS vartesttp;
+LET v1 = (NULL::vartesttp).*;
+ERROR:  assignment expression returned 3 columns
+LINE 1: LET v1 = (NULL::vartesttp).*;
+DROP TYPE vartesttp;
+-- the LET statement should be disallowed in CTE
+WITH x AS (LET public.svar = 100) SELECT * FROM x;
+ERROR:  LET not supported in WITH query
+LINE 1: WITH x AS (LET public.svar = 100) SELECT * FROM x;
+-- the LET statement can be prepared
+PREPARE pp(int) AS LET public.svar = $1;
+EXECUTE pp(10);
+EXECUTE pp(1000);
+-- test of correct dependency
+EXECUTE pp(-1);
+ERROR:  session variable "public.svar" doesn't exist
+CREATE VARIABLE public.svar2 AS int;
+CREATE VARIABLE public.svar1 AS int DEFAULT 100;
+PREPARE pp(int) AS LET public.svar2 = public.svar1 + $1;
+EXECUTE pp(100);
+SELECT public.svar1, public.svar2;
+ svar1 | svar2 
+   100 |   200
+LET public.svar1 = 1000;
+EXECUTE pp(100);
+SELECT public.svar1, public.svar2;
+ svar1 | svar2 
+  1000 |  1100
+DROP VARIABLE public.svar1;
+EXECUTE pp(100);
+ERROR:  missing FROM-clause entry for table "public"
+LET public.svar = generate_series(1, 1);
+LET public.svar = generate_series(1, 2);
+ERROR:  expression returned more than one row
+LET public.svar = generate_series(1, 0);
+ERROR:  expression returned no rows
index 9a139f1e24..4c319184ee 100644
 
 # ----------
index 0000000000..ef1df47bc1
@@ -0,0 +1,1045 @@
+CREATE SCHEMA svartest CREATE VARIABLE xx AS int CREATE TABLE foo(a int);
+LET svartest.xx = 100;
+DROP SCHEMA svartest CASCADE;
+LET svartest.xx = 100;
+-- don't allow multi column query
+CREATE TYPE vartesttp AS (a1 int, b1 int, c1 int);
+CREATE VARIABLE v1 AS vartesttp;
+LET v1 = (NULL::vartesttp).*;
+DROP TYPE vartesttp;
+-- the LET statement should be disallowed in CTE
+WITH x AS (LET public.svar = 100) SELECT * FROM x;
+-- the LET statement can be prepared
+PREPARE pp(int) AS LET public.svar = $1;
+EXECUTE pp(10);
+EXECUTE pp(1000);
+-- test of correct dependency
+EXECUTE pp(-1);
+CREATE VARIABLE public.svar2 AS int;
+CREATE VARIABLE public.svar1 AS int DEFAULT 100;
+PREPARE pp(int) AS LET public.svar2 = public.svar1 + $1;
+EXECUTE pp(100);
+SELECT public.svar1, public.svar2;
+LET public.svar1 = 1000;
+EXECUTE pp(100);
+SELECT public.svar1, public.svar2;
+DROP VARIABLE public.svar1;
+EXECUTE pp(100);
+LET public.svar = generate_series(1, 1);
+LET public.svar = generate_series(1, 2);
+LET public.svar = generate_series(1, 0);

From f322f951a867669e2f7aa5199bb971974ac5d227 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index a64571215b..6231b5e293 100644

 /*
@@ -2647,7 +2660,7 @@ psql_completion(const char *text, int start, int end)
@@ -3086,7 +3099,7 @@ psql_completion(const char *text, int start, int end)
@@ -3393,6 +3406,17 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS */
@@ -3508,7 +3532,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
@@ -3635,6 +3659,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 */
@@ -3751,7 +3781,8 @@ psql_completion(const char *text, int start, int end)
@@ -3769,6 +3800,8 @@ psql_completion(const char *text, int start, int end)
 									 "USAGE",
 									 "SET",
 									 "ALTER SYSTEM",
+									 "READ",
+									 "WRITE",
 									 "ALL");
 	}
 
@@ -3810,7 +3843,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("GRANT|REVOKE", MatchAny) ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny))
 	{
-		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
+		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|READ|WRITE|ALL"))
 			COMPLETE_WITH("ON");
 		else if (TailMatches("GRANT", MatchAny))
 			COMPLETE_WITH("TO");
@@ -3833,7 +3866,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",
@@ -3855,7 +3888,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"))
@@ -3863,7 +3897,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");
@@ -3899,6 +3934,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
@@ -4176,7 +4213,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -4464,6 +4501,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
 
@@ -4634,6 +4679,8 @@ psql_completion(const char *text, int start, int end)

From 26f117fe6a48b364c3e805218bf5fc2beb389a07 Mon Sep 17 00:00:00 2001

index 01d4ad7b72..cf24f077e7 100644

 

 select a from atacc1;

index 9b69a8c122..629f10a387 100644
 
index 624d0e5aae..02c4498e4b 100644
@@ -2721,7 +2721,7 @@ select * from rules_fooview;
 (1 row)
 
index 216add9e4b..4f8206358d 100644
@@ -267,7 +267,7 @@ SELECT v1;
 
 -- should fail
 LET v1.x = 10;

From fbde0746ef157d7672b75aa81846a4eea48e5a6b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 
index c116c5825c..74ff76530e 100644
@@ -2025,7 +2025,12 @@ DiscardStmt:

index 18f408881a..e48f582e6f 100644

From c52e4dc03b3271827963244dec1dea12c81dd54b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

index 77adfa52ab..c116c5825c 100644

 /*

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

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index 220e42fd79..3decd5f245 100644

From 3fa4ebe70e3d5658c123f36ae667f47c7c43d2d9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 9 Sep 2022 14:03:15 +0200
Subject: [PATCH 03/10] LET command

Set session variable value to result of query or result of default expression
---
 src/backend/commands/explain.c           |  16 ++
 src/backend/commands/session_variable.c  |  86 +++++++
 src/backend/executor/Makefile            |   1 +
 src/backend/executor/execMain.c          |   1 +
 src/backend/executor/meson.build         |   1 +
 src/backend/executor/svariableReceiver.c | 215 +++++++++++++++++
 src/backend/nodes/nodeFuncs.c            |  10 +
 src/backend/optimizer/plan/setrefs.c     |  38 +--
 src/backend/parser/analyze.c             | 286 +++++++++++++++++++++++
 src/backend/parser/gram.y                |  57 ++++-
 src/backend/parser/parse_agg.c           |   4 +
 src/backend/parser/parse_cte.c           |   8 +
 src/backend/parser/parse_expr.c          |   4 +
 src/backend/parser/parse_func.c          |   3 +
 src/backend/parser/parse_target.c        |   4 +-
 src/backend/rewrite/rewriteHandler.c     |  34 +++
 src/backend/rewrite/rowsecurity.c        |   8 +-
 src/backend/tcop/dest.c                  |   7 +
 src/backend/tcop/utility.c               |  15 ++
 src/backend/utils/cache/plancache.c      |  12 +
 src/include/commands/session_variable.h  |   3 +
 src/include/executor/svariableReceiver.h |  25 ++
 src/include/nodes/parsenodes.h           |  16 ++
 src/include/nodes/pathnodes.h            |   1 +
 src/include/nodes/plannodes.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                  |   3 +-
 src/tools/pgindent/typedefs.list         |   2 +
 30 files changed, 842 insertions(+), 25 deletions(-)
 create mode 100644 src/backend/executor/svariableReceiver.c
 create mode 100644 src/include/executor/svariableReceiver.h

index caf2c2c492..dedebc55e5 100644
 
 /*
@@ -1236,6 +1240,88 @@ AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit,
 	}
 }
 
+/*
+	SetVariableDestReceiverVarid(dest, varid);
+	 * run the plan to completion. The result should be only one
+	 * row. For an check too_many_rows we need to read two rows.
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index c3fcede3fb..e53e9acbd3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -51,6 +51,7 @@
 #include "commands/session_variable.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSubplan.h"
+#include "executor/svariableReceiver.h"
 #include "foreign/fdwapi.h"
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
diff --git a/src/backend/executor/meson.build b/src/backend/executor/meson.build
index 518674cfa2..651f2ba63b 100644
--- a/src/backend/executor/meson.build
+++ b/src/backend/executor/meson.build
@@ -62,6 +62,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..6b827b68e5
--- /dev/null
+++ b/src/backend/executor/svariableReceiver.c
@@ -0,0 +1,215 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.c
+ *	  An implementation of DestReceiver that stores the result value in
+ *	  a session variable.
+ *
+ * Portions Copyright (c) 1996-2022, 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 "executor/svariableReceiver.h"
+#include "commands/session_variable.h"
+#include "storage/lock.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+typedef struct
+{
+	DestReceiver pub;
+	Oid			varid;
+	Oid			typid;
+	int32		typmod;
+	int			typlen;
+	int			slot_offset;
+	int			rows;
+} svariableState;
+
+
+/*
+ * Prepare to receive tuples from executor.
+ */
+static void
+svariableStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+	svariableState *myState = (svariableState *) self;
+	int			natts = typeinfo->natts;
+	int			outcols = 0;
+	int			i;
+
+	/* Receiver should be initialized by SetVariableDestReceiverVarid */
+	Assert(OidIsValid(myState->varid));
+
+	for (i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+		Oid		   typid;
+		Oid		   collid;
+		int32	   typmod;
+
+		if (attr->attisdropped)
+			continue;
+
+		if (++outcols > 1)
+			continue;
+
+		get_session_variable_type_typmod_collid(myState->varid,
+												&typid,
+												&typmod,
+												&collid);
+
+		/*
+		 * double check - the type and typmod of target variable
+		 * should be same as type and typmod of assignment expression.
+		 * It should be, the expression is wrapped by cast to target
+		 * type and typmod.
+		 */
+		if (attr->atttypid != typid ||
+			(attr->atttypmod >= 0 &&
+			 attr->atttypmod != typmod))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("target session varible 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->typid = attr->atttypid;
+		myState->typmod = attr->atttypmod;
+		myState->typlen = attr->attlen;
+		myState->slot_offset = i;
+	}
+
+	if (outcols != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg_plural("assignment expression returned %d column",
+							   "assignment expression returned %d columns",
+							   outcols,
+							   outcols)));
+
+
+
+	myState->rows = 0;
+}
+
+/*
+ * Receive a tuple from the executor and store it in session variable.
+ */
+static bool
+svariableReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
+{
+	svariableState *myState = (svariableState *) self;
+	Datum		value;
+	bool		isnull;
+	bool		freeval = false;
+
+	/* Make sure the tuple is fully deconstructed */
+	slot_getallattrs(slot);
+
+	value = slot->tts_values[myState->slot_offset];
+	isnull = slot->tts_isnull[myState->slot_offset];
+
+	if (myState->typlen == -1 && !isnull && VARATT_IS_EXTERNAL(DatumGetPointer(value)))
+	{
+		value = PointerGetDatum(detoast_external_attr((struct varlena *)
+													  DatumGetPointer(value)));
+		freeval = true;
+	}
+
+	myState->rows += 1;
+
+	if (myState->rows > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_ROWS),
+				 errmsg("expression returned more than one row")));
+
+	SetSessionVariable(myState->varid, value, isnull);
+
+	if (freeval)
+		pfree(DatumGetPointer(value));
+
+	return true;
+}
+
+/*
+ * Clean up at end of an executor run
+ */
+static void
+svariableShutdownReceiver(DestReceiver *self)
+{
+	if (((svariableState *) self)->rows == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NO_DATA_FOUND),
+				 errmsg("expression returned no rows")));
+}
+
+/*
+ * Destroy receiver when done with it
+ */
+static void
+svariableDestroyReceiver(DestReceiver *self)
+{
+	pfree(self);
+}
+
+/*
+ * Initially create a DestReceiver object.
+ */
+DestReceiver *
+CreateVariableDestReceiver(void)
+{
+	svariableState *self = (svariableState *) palloc0(sizeof(svariableState));
+
+	self->pub.receiveSlot = svariableReceiveSlot;
+	self->pub.rStartup = svariableStartupReceiver;
+	self->pub.rShutdown = svariableShutdownReceiver;
+	self->pub.rDestroy = svariableDestroyReceiver;
+	self->pub.mydest = DestVariable;
+
+	/*
+	 * Private fields will be set by SetVariableDestReceiverVarid and
+	 * svariableStartupReceiver.
+	 */
+	return (DestReceiver *) self;
+}
+
+/*
+ * Set parameters for a VariableDestReceiver.
+ * Should be called right after creating the DestReceiver.
+ */
+void
+SetVariableDestReceiverVarid(DestReceiver *self, Oid varid)
+{
+	svariableState *myState = (svariableState *) self;
+	LOCKTAG locktag PG_USED_FOR_ASSERTS_ONLY;
+
+	Assert(myState->pub.mydest == DestVariable);
+	Assert(OidIsValid(varid));
+	Assert(SearchSysCacheExists1(VARIABLEOID, varid));
+
+#ifdef USE_ASSERT_CHECKING
+
+	SET_LOCKTAG_OBJECT(locktag,
+					   MyDatabaseId,
+					   VariableRelationId,
+					   varid,
+					   0);
+
+	Assert(LockHeldByMe(&locktag, AccessShareLock));
+
+#endif
+
+	myState->varid = varid;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 0a7b22f97e..e609b134e1 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3815,6 +3815,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/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 12c0364f32..566ebf4600 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -183,7 +183,7 @@ static List *set_windowagg_runcondition_references(PlannerInfo *root,
 												   Plan *plan);
 static bool pull_up_has_session_variables_walker(Node *node,
 												 PlannerInfo *root);
-
+static void record_plan_variable_dependency(PlannerInfo *root, Oid varid);
 
 /*****************************************************************************
  *
@@ -1974,18 +1974,7 @@ fix_expr_common(PlannerInfo *root, Node *node)
 		Param	   *p = (Param *) node;
 
 		if (p->paramkind == PARAM_VARIABLE)
-		{
-			PlanInvalItem *inval_item = makeNode(PlanInvalItem);
-
-			/* paramid is still session variable id */
-			inval_item->cacheId = VARIABLEOID;
-			inval_item->hashValue = GetSysCacheHashValue1(VARIABLEOID,
-														  ObjectIdGetDatum(p->paramvarid));
-
-			/* Append this variable to global, register dependency */
-			root->glob->invalItems = lappend(root->glob->invalItems,
-											 inval_item);
-		}
+			record_plan_variable_dependency(root, p->paramvarid);
 	}
 }
 
@@ -3379,6 +3368,25 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
 	}
 }
 
+/*
+ * Record dependency on session variable. The variable can be used
+ * session variable in expression list, or target of LET statement.
+ */
+static void
+record_plan_variable_dependency(PlannerInfo *root, Oid varid)
+{
+	PlanInvalItem *inval_item = makeNode(PlanInvalItem);
+
+	/* paramid is still session variable id */
+	inval_item->cacheId = VARIABLEOID;
+	inval_item->hashValue = GetSysCacheHashValue1(VARIABLEOID,
+												  ObjectIdGetDatum(varid));
+
+	/* Append this variable to global, register dependency */
+	root->glob->invalItems = lappend(root->glob->invalItems,
+									 inval_item);
+}
+
 /*
  * extract_query_dependencies
  *		Given a rewritten, but not yet planned, query or queries
@@ -3452,6 +3460,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 			query = UtilityContainsQuery(query->utilityStmt);
 			if (query == NULL)
 				return false;
+
+			/* Record the session variable used as target of LET statement. */
+			if (OidIsValid(query->resultVariable))
+				record_plan_variable_dependency(context, query->resultVariable);
 		}
 
 		/* Remember if any Query has RLS quals applied by rewriter */
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 702d875d0d..e4c10c6bd5 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,8 +25,11 @@
 #include "postgres.h"

 
+		case T_LetStmt:
 
 			/*
 
@@ -1634,6 +1647,279 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+	Assert(IsA(selectQuery, Query) && selectQuery->commandType == CMD_SELECT);
+				 errmsg_plural("assignment expression returned %d column",
+							   "assignment expression returned %d columns",
+							   list_length(exprList),
+							   list_length(exprList)),
index 5fcc8f0c83..77adfa52ab 100644

 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
@@ -1043,6 +1044,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -11918,7 +11920,8 @@ ExplainableStmt:
 			| CreateAsStmt
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
-			| ExecuteStmt					/* by default all are $$=$1 */
+			| ExecuteStmt
+			| LetStmt						/* by default all are $$=$1 */
 		;
 
 /*****************************************************************************
@@ -11948,7 +11951,8 @@ PreparableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt
-			| MergeStmt						/* by default all are $$=$1 */
+			| MergeStmt
+			| LetStmt						/* by default all are $$=$1 */
 		;
 
 /*****************************************************************************
@@ -12536,6 +12540,49 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -16913,6 +16960,7 @@ unreserved_keyword:
@@ -17483,6 +17531,7 @@ bare_label_keyword:
 
 			/*
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c
index 8fc8658608..c2a42d408f 100644
--- a/src/backend/parser/parse_cte.c
+++ b/src/backend/parser/parse_cte.c
@@ -133,6 +133,14 @@ transformWithClause(ParseState *pstate, WithClause *withClause)
 					errmsg("MERGE not supported in WITH query"),
 					parser_errposition(pstate, cte->location));
 
+		/* LET is allowed by parser, but not supported. Reject for now */
+		if (IsA(cte->ctequery, LetStmt))
+			ereport(ERROR,
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("LET not supported in WITH query"),
+					parser_errposition(pstate, cte->location));
+
+
 		for_each_cell(rest, withClause->ctes, lnext(withClause->ctes, lc))
 		{
 			CommonTableExpr *cte2 = (CommonTableExpr *) lfirst(rest);
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 132f6cce07..01d4ad7b72 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -537,6 +537,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
 		case EXPR_KIND_VARIABLE_DEFAULT:
+		case EXPR_KIND_LET_TARGET:
 
 			/* okay */
 			break;
@@ -1960,6 +1961,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:
@@ -3335,6 +3337,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 
 			/*
 
+	/*
 
 	/*
 
index 6afaa153ca..8e1d94a6e3 100644
 
 		case DestTupleQueue:
 
 	/* should never get here */
@@ -208,6 +212,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
@@ -253,6 +258,7 @@ NullCommand(CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
@@ -296,6 +302,7 @@ ReadyForQuery(CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index d1a7d995e1..d77fead276 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -242,6 +242,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index ab58a4d9df..a5bbaceade 100644
 
 /*
 
 /*
 
index 0000000000..0eaa237b44
+extern void SetVariableDestReceiverVarid(DestReceiver *self, Oid varid);
index d5b95769b1..18f408881a 100644

index 8939797e93..d543b6d149 100644
 
index 020a46dc2e..85b8956a19 100644
 
 	NodeTag		type;

 
index 0eb85fabc0..d2b9d05a8e 100644

 /* ----------------
index ae1809fa60..220e42fd79 100644

From f1b8227a145d68e1e1191ede49e459aa6fc91217 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 11 Sep 2022 20:56:40 +0200
Subject: [PATCH 02/10] session variables

 src/backend/parser/parse_expr.c         |  241 ++++-
 34 files changed, 2064 insertions(+), 109 deletions(-)

index 6643739e53..c0284e60be 100644

 
index 5bceb9eff3..2484af3d59 100644
@@ -3224,7 +3224,7 @@ IdentifyVariable(List *names, char **attrname, bool *not_unique)
@@ -3238,7 +3238,7 @@ IdentifyVariable(List *names, char **attrname, bool *not_unique)
 		}
 
 		/*
@@ -3254,6 +3254,7 @@ IdentifyVariable(List *names, char **attrname, bool *not_unique)
 
 		retry = true;
 		old_varid = varid;
+		varid = InvalidOid;
 	}
 
 	return varid;
index 3eafc05f89..caf2c2c492 100644
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*

 
 			/*

+				/*

 /*

+		/*
+	InitMaterializedSRF(fcinfo, 0);
 
 						/*

+	/*

 

 

index 493a3af0fa..7d61fdba32 100644
 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
index 5b1fadf183..132f6cce07 100644
 
 /* GUC parameters */

 /*
 
+/*
@@ -779,6 +809,157 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
+	/*
@@ -813,6 +994,64 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
index 52e2db6452..06a5bfbbfa 100644
 
index 70d723e80c..2b744238f8 100644
@@ -8074,6 +8076,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
@@ -12092,6 +12102,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*
index cc943205d3..ab58a4d9df 100644

 	/*
 
 /*
 
 	return false;
 }
index 836b49484a..9f5f37a06b 100644
@@ -1478,6 +1478,16 @@ struct config_bool ConfigureNamesBool[] =
index 3bbae2fae4..4bc2e9f2b3 100644
@@ -11831,4 +11831,11 @@

 

 #endif

 

+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
index 937f8c5348..d5b95769b1 100644

index 09342d128d..8939797e93 100644

 
index 5c2ab1b379..020a46dc2e 100644

index f71f551782..238499d114 100644

 typedef struct Param

 
 /* GUC parameters */

 

 
index d11c17d5de..ae1809fa60 100644

From 829ee211c034532e918b92df1565a2890a31c89a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/10] catalog support for session variables

 src/backend/catalog/aclchk.c            | 299 ++++++++++++++++
 src/backend/catalog/namespace.c         | 430 ++++++++++++++++++++++++
 src/backend/catalog/pg_variable.c       | 372 ++++++++++++++++++++
 src/backend/commands/session_variable.c | 242 +++++++++++++
 src/backend/parser/gram.y               | 145 +++++++-
 src/backend/parser/parse_utilcmd.c      |  12 +
 42 files changed, 2085 insertions(+), 19 deletions(-)

index 883d6c0f70..6643739e53 100644

 

 
 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
index 539df1da94..5bceb9eff3 100644
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
@@ -2839,6 +2903,361 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
+/*
+ * Note: the out attrname should be used only when the session variable
+ * is identified. When the session variable is not identified, then this
+ * output variable can hold reference to some string, but isn't sure
+ * about its semantics.
 
 /*
  * DeconstructQualifiedName
@@ -4656,3 +5075,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index 0000000000..973d8fd7d0
+	Assert(varName);
+	Assert(OidIsValid(varNamespace));
+	Assert(OidIsValid(varType));
+	Assert(OidIsValid(varOwner));
 
index 8d36b66488..875f19538b 100644

 			/*
 
 			/*
index bc90185da6..a01fa354a0 100644

 
 				/*
index 737bd2d06d..5fcc8f0c83 100644

 /*

 
@@ -1001,6 +1005,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1545,6 +1550,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;

@@ -5009,6 +5015,69 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6786,6 +6855,7 @@ object_type_any_name:
 
 /*
@@ -7662,6 +7732,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7707,6 +7785,14 @@ privilege_target:

@@ -7904,6 +7990,7 @@ defacl_privilege_target:

@@ -9686,6 +9773,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10047,6 +10153,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10326,6 +10451,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -16940,6 +17073,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -17551,6 +17686,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)
index e5fc708c8a..5b1fadf183 100644

@@ -1726,6 +1729,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("cannot use subquery in DEFAULT expression");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
@@ -3066,6 +3070,7 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "CHECK";
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			return "DEFAULT";
 		case EXPR_KIND_INDEX_EXPRESSION:
 			return "index expression";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 827989f379..005bc0400f 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2621,6 +2621,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("set-returning functions are not allowed in DEFAULT expressions");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 8140e79d8f..339977a142 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;

@@ -3839,6 +3840,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 	cxt.indexes = NIL;
 	cxt.triggers = NIL;
 	cxt.grants = NIL;
+	cxt.variables = NIL;
 
 	/*
 	 * Run through each schema element in the schema element list. Separate
@@ -3907,6 +3909,15 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 				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));
@@ -3920,6 +3931,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 	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;
 }

 
-
 /*
 
 	return subname;
 }
+

 	/*

 
 bki_data = [

 
 
index 20f5aa56ea..3bbae2fae4 100644
@@ -6282,6 +6282,9 @@
 
index 7e7ad3f7e4..937f8c5348 100644

+
index 957ee18d84..0eb85fabc0 100644

 

 
 
index 2f02cc8f42..d11c17d5de 100644

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 3 Nov 2022 16:48:43 +0100

Hi

done

I partially rewrote the IdentifyVariable routine. Now it should be robust.

> Is that really ok to match a variable here rather than complaining about a

I feel "missing FROM-clause" is a little bit better, although the message
"type integer is not composite" is correct too. But there is agreement so
implementation of session variables should minimize impacts on PostgreSQL
behaviour, and it is more comfortant with some filtering used in other
places.

> to NULL, use NIL instead.

changed, fixed

done

updated patch assigned

Regards

Pavel

> https://www.postgresql.org/message-id/[email-masked]
From 88d3be43a473c0e6f084bbaf9b9fb963ff7d224a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 .../isolation/expected/session-variable.out   |  112 ++
 src/test/isolation/isolation_schedule         |    1 +
 .../isolation/specs/session-variable.spec     |   53 +
 .../regress/expected/session_variables.out    | 1427 +++++++++++++++++
 src/test/regress/parallel_schedule            |    2 +-
 src/test/regress/sql/session_variables.sql    | 1075 +++++++++++++
 6 files changed, 2669 insertions(+), 1 deletion(-)
 create mode 100644 src/test/isolation/expected/session-variable.out
 create mode 100644 src/test/isolation/specs/session-variable.spec
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

index 0000000000..ab14fc19d6
@@ -0,0 +1,1427 @@
+ERROR:  missing FROM-clause entry for table "varx"
+LINE 1: SELECT varx.xxx;
+SET search_path TO DEFAULT;
+CREATE TYPE ab AS (a integer, b integer);
+CREATE VARIABLE v_ab AS ab;
+CREATE TABLE v_ab (a integer, b integer);
+SET session_variables_ambiguity_warning = 1;
+-- warning should be raised
+SELECT v_ab.a FROM v_ab;
+WARNING:  session variable "v_ab.a" is shadowed
+LINE 1: SELECT v_ab.a FROM v_ab;
+---
+CREATE SCHEMA v_ab;
+CREATE VARIABLE v_ab.a AS integer;
+-- warning should be raised
+SELECT v_ab.a FROM v_ab;
+WARNING:  session variable "v_ab.a" is shadowed
+LINE 1: SELECT v_ab.a FROM v_ab;
+---
+DROP VARIABLE v_ab;
+DROP TABLE v_ab;
+DROP TYPE ab;
+CREATE VARIABLE myvar AS int;
+SELECT myvar.blabla;
+ERROR:  missing FROM-clause entry for table "myvar"
+LINE 1: SELECT myvar.blabla;
+DROP VARIABLE myvar;
 
 # ----------
index 0000000000..f831623bd6
@@ -0,0 +1,1075 @@
+SET search_path TO DEFAULT;
+
+CREATE TYPE ab AS (a integer, b integer);
+
+CREATE VARIABLE v_ab AS ab;
+
+CREATE TABLE v_ab (a integer, b integer);
+
+SET session_variables_ambiguity_warning = 1;
+
+-- warning should be raised
+SELECT v_ab.a FROM v_ab;
+
+CREATE SCHEMA v_ab;
+
+CREATE VARIABLE v_ab.a AS integer;
+
+-- warning should be raised
+SELECT v_ab.a FROM v_ab;
+
+DROP VARIABLE v_ab;
+DROP TABLE v_ab;
+DROP TYPE ab;
+
+CREATE VARIABLE myvar AS int;
+
+SELECT myvar.blabla;
+
+DROP VARIABLE myvar;
-- 
2.37.3

From a0728be93bdd5c2b04802b867dc9dc7192244417 Mon Sep 17 00:00:00 2001

index 9c85ae1280..dd01c6e279 100644
@@ -440,7 +440,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)

 

 select a from atacc1;

index 090ef6c7a8..165bd7491d 100644
@@ -122,7 +122,7 @@ LINE 1: COPY x TO stdout WHERE a = 1;
index b901d7299f..5c1eea1c1b 100644
@@ -5060,7 +5060,7 @@ LINE 1: select t2.uunique1 from
@@ -6094,7 +6094,7 @@ lateral (select * from int8_tbl t1,
 
@@ -6104,7 +6104,7 @@ LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
@@ -6139,7 +6139,7 @@ LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
@@ -6159,7 +6159,7 @@ update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1)
 
index ab14fc19d6..fb6b34fc0a 100644
@@ -269,7 +269,7 @@ SELECT v1;
 
 -- should fail
 LET v1.x = 10;

From 463edbb50dc90017670e03303e3e30ed14aad72b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 src/bin/pg_dump/pg_backup_archiver.c |   9 +
 10 files changed, 359 insertions(+), 4 deletions(-)

 

 	int			useDB;
index f39c0fa36f..69c0a4fc0a 100644
@@ -3418,6 +3426,7 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te)
 		strcmp(type, "TYPE") == 0 ||
 		strcmp(type, "VIEW") == 0 ||
index da427f4d4a..85ec940fd0 100644
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 
index 8dc1f0eccb..12a1877fdd 100644

@@ -3283,6 +3309,30 @@ my %tests = (
 		},
 	},
 
@@ -3722,6 +3772,21 @@ my %tests = (
 		like => {},
 	},
 
index b3c4c2654b..dd008d364b 100644

From e5c4651238c7b6d6846234165311a9249d223fd8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 4c45e4747a..51a5ea9568 100644

 /*
@@ -2671,7 +2684,7 @@ psql_completion(const char *text, int start, int end)
@@ -3110,7 +3123,7 @@ psql_completion(const char *text, int start, int end)
@@ -3417,6 +3430,17 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS */
@@ -3532,7 +3556,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
@@ -3659,6 +3683,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 */
@@ -3775,7 +3805,8 @@ psql_completion(const char *text, int start, int end)
@@ -3793,6 +3824,8 @@ psql_completion(const char *text, int start, int end)
 									 "USAGE",
 									 "SET",
 									 "ALTER SYSTEM",
+									 "READ",
+									 "WRITE",
 									 "ALL");
 	}
 
@@ -3834,7 +3867,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("GRANT|REVOKE", MatchAny) ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny))
 	{
-		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
+		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|READ|WRITE|ALL"))
 			COMPLETE_WITH("ON");
 		else if (TailMatches("GRANT", MatchAny))
 			COMPLETE_WITH("TO");
@@ -3857,7 +3890,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",
@@ -3879,7 +3912,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"))
@@ -3887,7 +3921,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");
@@ -3923,6 +3958,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
@@ -4200,7 +4237,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -4488,6 +4525,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
 
@@ -4658,6 +4703,8 @@ psql_completion(const char *text, int start, int end)

From e8b8eed435d8006561178315151cabe47ca4cdde Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+     <varlistentry>

From 5de044b07984f924da03773dfd6e01e1824aabc9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 

From ba1ddb9a12afd6e9209fef7ad807d16783830048 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 845ac21db6ffc56292d3893eb0e836cc00d8f50e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 9 Sep 2022 14:03:15 +0200
Subject: [PATCH 03/10] LET command

 src/backend/optimizer/plan/setrefs.c     |  38 ++-
 src/backend/parser/analyze.c             | 292 +++++++++++++++++++++++
 30 files changed, 848 insertions(+), 25 deletions(-)

 /*
 
+/*

 

+/*
 
index 702d875d0d..d2a5249257 100644

 
+		case T_LetStmt:
 
 			/*
 
@@ -1634,6 +1647,285 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+	ListCell   *indirection_head = NULL;
+	 * Calculate start of possible position of an indirection in list,
+	 * and when it is inside the list, store pointer on first node
+	 * of indirection.
+	if (list_length(stmt->target) > indirection_start)
+		indirection_head = list_nth_cell(stmt->target, indirection_start);
+		if (indirection_head)
+											   stmt->target,
+											   indirection_head,

 

 
 
 			/*
 
 			/*
 
index dca415ff4e..9c85ae1280 100644
@@ -565,6 +565,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
 			/* okay */
 			break;
@@ -1992,6 +1993,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:
@@ -3367,6 +3369,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 

 	NodeTag		type;

 

 /* ----------------
index ad30bda9ed..b2c0f0d033 100644

From 1c4a876b2b2c993b15e98a70667d324519ec13e9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 11 Sep 2022 20:56:40 +0200
Subject: [PATCH 02/10] session variables

 src/backend/parser/parse_expr.c         |  273 ++++-
 32 files changed, 2089 insertions(+), 103 deletions(-)

 

 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*

 
 			/*

+				/*

 /*

+		/*
 
 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
index 5b1fadf183..dca415ff4e 100644
@@ -33,15 +33,17 @@
 #include "parser/parse_target.h"
 
 /* GUC parameters */
 
@@ -83,6 +85,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,

 /*
@@ -431,6 +436,59 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	return result;
 }
 
+/*
+ * Returns true, when expression of kind allows using of
+ * session variables.
+ */
+static bool
+expr_kind_allows_session_variables(ParseExprKind p_expr_kind)
+{
+	switch (p_expr_kind)
+	{
+		case EXPR_KIND_NONE:
+			Assert(false);		/* can't happen */
+			break;
+
+		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_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:
+
+			/* okay */
+			return true;
+
+		default:
+			return false;
+	}
+}
+
 /*
  * Transform a ColumnRef.
  *
@@ -779,6 +837,161 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 					 parser_errposition(pstate, cref->location)));
 	}
 
+	/*
+				/* This path will ending by WARNING. Unlock variable first */
+					UnlockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
@@ -813,6 +1026,64 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

 #endif

 

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

 

 

 
 
 typedef struct Param

 
 /* GUC parameters */

 
 
index 6778bbb80d..ad30bda9ed 100644

From 8501129079139d1623e68bf9d96d11a25b8f19b0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/10] catalog support for session variables

 src/backend/catalog/aclchk.c            | 302 ++++++++++++++++
 src/backend/catalog/namespace.c         | 453 ++++++++++++++++++++++++
 src/backend/catalog/pg_variable.c       | 372 +++++++++++++++++++
 src/backend/utils/cache/lsyscache.c     | 113 ++++++
 src/include/catalog/namespace.h         |   7 +
 src/include/utils/lsyscache.h           |   9 +
 42 files changed, 2137 insertions(+), 19 deletions(-)

 

 
index aa5a2ed948..11b2788529 100644
 
@@ -805,6 +817,19 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
+				relOid = LookupVariable(varvar->schemaname,
+										varvar->relname,
@@ -894,6 +919,33 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
@@ -1053,6 +1105,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
@@ -1244,6 +1300,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
 
+		case OBJECT_VARIABLE:
@@ -1475,6 +1537,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
@@ -1535,6 +1600,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
@@ -3367,6 +3435,129 @@ ExecGrant_Parameter(InternalGrant *istmt)
 
+static void
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3568,6 +3759,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;
@@ -3679,6 +3873,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;
@@ -3827,6 +4024,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return pg_type_aclmask(table_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return pg_variable_aclmask(table_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized objtype: %d",
 				 (int) objtype);
@@ -4810,6 +5009,66 @@ pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how)
 	return result;
 }
 
+/*
@@ -5110,6 +5369,18 @@ pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
 		return ACLCHECK_NO_PRIV;
 }
 
+/*
@@ -5727,6 +5998,33 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid)
 
+/*
@@ -5855,6 +6153,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
index 539df1da94..1a9c92b3a8 100644
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
@@ -2839,6 +2903,384 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
+/*
+ * When rowtype_only argument is true the session variables of not
+ * composite types are ignored. This should to reduce possible collisions.
+LookupVariable(const char *nspname,
+			   const char *varname,
+			   bool rowtype_only,
+			   bool missing_ok)
+		if (OidIsValid(namespaceId))
+				if (rowtype_only && !type_is_rowtype(get_session_variable_type(varoid)))
+					varoid = InvalidOid;
+				if (rowtype_only)
+					if (!type_is_rowtype(get_session_variable_type(varoid)))
+						varoid = InvalidOid;
+ * is ambiguous. In this case this routine returns oid of variable
+ * x in schema y, and the output parameter "not_unique" is set to
+ * true. In this case this variable is locked.
+				varid = LookupVariable(NULL, strVal(field1), false, true);
+					varoid_without_attr = LookupVariable(a, b, false, true);
+					varoid_with_attr = LookupVariable(NULL, a, true, true);
+				else if (OidIsValid(varoid_with_attr))
+					varoid_without_attr = LookupVariable(b, c, false, true);
+					varoid_with_attr = LookupVariable(a, b, true, true);
+				else if (OidIsValid(varoid_with_attr))
+				varid = LookupVariable(b, c, true, true);
 
 /*
  * DeconstructQualifiedName
@@ -4656,3 +5098,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)
 
index 284ca55469..26f557eb47 100644
 
 /*

 	/*
 
+/*
+	address.objectId = LookupVariable(nspname, varname, false, missing_ok);
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*

 
 				/*

 /*

 

 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 	/*

 	return result;
 }

 
-
 /*
index a16a63f495..f4b1f57823 100644
@@ -3679,3 +3680,115 @@ get_subscription_name(Oid subid, bool missing_ok)
 
 	return subname;
 }
+
+get_session_variable_type(Oid varid)
+	vartype = varform->vartype;
+	return vartype;

 	/*

 
 bki_data = [
index 2a2a2e6489..e6873d369a 100644
 
@@ -164,6 +166,11 @@ extern void SetTempNamespaceState(Oid tempNamespaceId,
 
+extern Oid	LookupVariable(const char *nspname, const char *varname, bool rowtype_only,
+						   bool missing_ok);

 

+

 
 
index 50f0288305..10d97ffbf3 100644
@@ -203,6 +204,14 @@ extern char *get_publication_name(Oid pubid, bool missing_ok);
 
+extern Oid	get_session_variable_type(Oid varid);

index 9683b0a88e..6778bbb80d 100644

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 4 Nov 2022 05:58:06 +0100

Hi

fix clang warning

Regards

Pavel
From bb0dccb1fd133cde34277ba3f48e7715e746a8cc Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From e6fb6d29af8599cee344d9df51309b265f7a609e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

+     <varlistentry>

From 914e20238f9c871413a7ee6bf85a9adcae0b8227 Mon Sep 17 00:00:00 2001

index e2906b304c..5df2a225f6 100644

 

 select a from atacc1;

 

 -- should fail
 LET v1.x = 10;

From fdb474c8ea50b7a1df02eee321148b26752d1356 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From aee5999a68a15574739d5cf92aa8742d0f2dfee0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

 
 # ----------

From 0429f97fc401ea5d92ae4d8899e342d0a9f114f5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 
index 98a2dc33c8..4e3f65c9ba 100644
@@ -2026,7 +2026,12 @@ DiscardStmt:

index a24ac782e4..cda76611fc 100644
@@ -3570,7 +3570,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;

From 05b114713ba4b8fda7febb71f26b6a79eb6e4b4c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

index a49bb4d1eb..98a2dc33c8 100644

@@ -904,6 +905,13 @@ parse_toplevel:
 
 /*

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

+static void

+static void

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

From 8c891a097f43ef5accb34bbdd1de14b546155aa7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 9 Sep 2022 14:03:15 +0200
Subject: [PATCH 03/10] LET command

 
 /*
 
+/*

 

+/*

 
 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -
index a799bcc72c..a49bb4d1eb 100644

 
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
@@ -1044,6 +1045,7 @@ stmt:
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -11919,7 +11921,8 @@ ExplainableStmt:
 			| CreateAsStmt
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
-			| ExecuteStmt					/* by default all are $$=$1 */
+			| ExecuteStmt
+			| LetStmt						/* by default all are $$=$1 */
 		;
 
 /*****************************************************************************
@@ -11949,7 +11952,8 @@ PreparableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt
-			| MergeStmt						/* by default all are $$=$1 */
+			| MergeStmt
+			| LetStmt						/* by default all are $$=$1 */
 		;
 
 /*****************************************************************************
@@ -12537,6 +12541,49 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -16914,6 +16961,7 @@ unreserved_keyword:
@@ -17484,6 +17532,7 @@ bare_label_keyword:
 
 			/*
 
 			/*
 
index 7918361481..e2906b304c 100644
 
 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*
 
index c86d71cf86..a24ac782e4 100644
 
@@ -1700,6 +1701,21 @@ typedef struct MergeStmt

 
 	NodeTag		type;

 

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

From a039e5ffd259757d2b0b984e2b2238f065b54766 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 11 Sep 2022 20:56:40 +0200
Subject: [PATCH 02/10] session variables

 

 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*

 
 			/*

+				/*

 /*

+		/*
 
 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 	/*

 
 				/*

 

 

 

 

 

 
index 5b1fadf183..7918361481 100644
 
 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*

 
+/*

 	/*
 
 /*
 
 	return false;
 }

 

 #endif

 

+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
index e85878e13e..c86d71cf86 100644

 

 

 
 typedef struct Param

 
 /* GUC parameters */

 

From f77140506c95255694427d52af7b9867bc40a836 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/10] catalog support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*
index 6007e10730..4c484e2135 100644

 		rel = relation_open(pg_depend->objid, AccessShareLock);
@@ -12726,6 +12766,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_PUBLICATION_REL:
 			case OCLASS_SUBSCRIPTION:
 			case OCLASS_TRANSFORM:
+			case OCLASS_VARIABLE:
 
 				/*
index 6ca23f88c4..a799bcc72c 100644

 /*

 
@@ -1002,6 +1006,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1546,6 +1551,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;

@@ -5010,6 +5016,69 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6787,6 +6856,7 @@ object_type_any_name:
 
 /*
@@ -7663,6 +7733,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7708,6 +7786,14 @@ privilege_target:

@@ -7905,6 +7991,7 @@ defacl_privilege_target:

@@ -9687,6 +9774,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10048,6 +10154,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10327,6 +10452,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -16941,6 +17074,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -17552,6 +17687,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)

 
 	/*

 	return result;
 }

 
-
 /*
 
 	return subname;
 }
+

 	/*

 
 bki_data = [

 

index 7caff62af7..e85878e13e 100644
@@ -1909,6 +1909,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -3036,6 +3037,25 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+

---

From: Dmitry Dolgov <[email-masked]>
Date: Fri, 4 Nov 2022 15:07:48 +0100

> On Fri, Nov 04, 2022 at 05:58:06AM +0100, Pavel Stehule wrote:
> Hi
>
> fix clang warning

I've stumbled upon something that looks weird to me (inspired by the
example from tests):

    =# create variable v2 as int;
    =# let v2 = 3;
    =# create view vv2 as select coalesce(v2, 0) + 1000 as result

    =# select * from vv2;
     result
     --------
        1003

    =# set force_parallel_mode to on;
    =# select * from vv2;
     result
     --------
        1000

In the second select the actual work is done from a worker backend.
Since values of session variables are stored in the backend local
memory, it's not being shared with the worker and the value is not found
in the hash map. Does this suppose to be like that?

---

From: Julien Rouhaud <[email-masked]>
Date: Fri, 4 Nov 2022 22:17:13 +0800

Hi,

> in the hash map. Does this suppose to be like that?

There's code to serialize and restore all used variables for parallel workers
(see code about PARAM_VARIABLE and queryDesc->num_session_variables /
queryDesc->plannedstmt->sessionVariables).  I haven't reviewed that part yet,
but it's supposed to be working.  Blind guess would be that it's missing
something in expression walker.

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 4 Nov 2022 15:17:18 +0100

Hi

> in the hash map. Does this suppose to be like that?

It looks like a bug, but parallel queries should be supported.

The value of the variable is passed as parameter to the worker backend. But
probably somewhere the original reference was not replaced by parameter

---

From: Dmitry Dolgov <[email-masked]>
Date: Fri, 4 Nov 2022 15:28:42 +0100

> On Fri, Nov 04, 2022 at 03:17:18PM +0100, Pavel Stehule wrote:
> On Fri, Nov 04, 2022 at 10:17:13PM +0800, Julien Rouhaud wrote:
> Hi,
>

I see, thanks. I'll take a deeper look, my initial assumption was due to
the fact that in the worker case create_sessionvars_hashtables is
getting called for every query.

---

From: Tomas Vondra <[email-masked]>
Date: Sat, 5 Nov 2022 17:04:31 +0100

Hi,

I did a quick initial review of this patch series - attached is a
version with "review" commits for some of the parts. The current patch
seems in pretty good shape, most of what I noticed are minor issues. I
plan to do a more thorough review later.

A quick overview of the issues:

0001
----

- AtPreEOXact_SessionVariable_on_xact_actions name seems unnecessarily
complicated and redundant, and mismatching nearby functions. Why not
call it AtEOXact_SessionVariable, similar to AtEOXact_LargeObject?

- some whitespace / ordering cleanup

- I'm not sure why find_composite_type_dependencies needs the extra
"else if" branch (instead of just doing "if" as before)

- NamesFromList and IdentifyVariable seem introduced unnecessarily
early, as they are only used in 0002 and 0003 parts (in the original
patch series). Not sure if the plan is to squash everything into a
single patch, or commit individual patches.

- AFAIK patches don't need to modify typedefs.list.

0002
----

- some whitespace / ordering cleanup

- moving setting hasSessionVariables right after similar fields

- SessionVariableCreatePostprocess prototype is redundant (2x)

- I'd probably rename pg_debug_show_used_session_variables to
pg_session_variables (assuming we want to keep this view)

0003
----

- I'd rename svariableState to SVariableState, to keep the naming
consistent with other similar/related typedefs.

- some whitespace / ordering cleanup

0007
----

- minor wording change

Aside from that, I tried running this under valgrind, and that produces
this report:

==250595== Conditional jump or move depends on uninitialised value(s)
==250595==    at 0x731A48: sync_sessionvars_all (session_variable.c:513)
==250595==    by 0x7321A6: prepare_variable_for_reading
(session_variable.c:727)
==250595==    by 0x7320BA: CopySessionVariable (session_variable.c:898)
==250595==    by 0x7BC3BF: standard_ExecutorStart (execMain.c:252)
==250595==    by 0x7BC042: ExecutorStart (execMain.c:146)
==250595==    by 0xA89283: PortalStart (pquery.c:520)
==250595==    by 0xA84E8D: exec_simple_query (postgres.c:1199)
==250595==    by 0xA8425B: PostgresMain (postgres.c:4551)
==250595==    by 0x998B03: BackendRun (postmaster.c:4482)
==250595==    by 0x9980EC: BackendStartup (postmaster.c:4210)
==250595==    by 0x996F0D: ServerLoop (postmaster.c:1804)
==250595==    by 0x9948CA: PostmasterMain (postmaster.c:1476)
==250595==    by 0x8526B6: main (main.c:197)
==250595==  Uninitialised value was created by a heap allocation
==250595==    at 0xCD86F0: MemoryContextAllocExtended (mcxt.c:1138)
==250595==    by 0xC9FA1F: DynaHashAlloc (dynahash.c:292)
==250595==    by 0xC9FEC1: element_alloc (dynahash.c:1715)
==250595==    by 0xCA102A: get_hash_entry (dynahash.c:1324)
==250595==    by 0xCA0879: hash_search_with_hash_value (dynahash.c:1097)
==250595==    by 0xCA0432: hash_search (dynahash.c:958)
==250595==    by 0x731614: SetSessionVariable (session_variable.c:846)
==250595==    by 0x82FEED: svariableReceiveSlot (svariableReceiver.c:138)
==250595==    by 0x7BD277: ExecutePlan (execMain.c:1726)
==250595==    by 0x7BD0C5: standard_ExecutorRun (execMain.c:422)
==250595==    by 0x7BCE63: ExecutorRun (execMain.c:366)
==250595==    by 0x7332F0: ExecuteLetStmt (session_variable.c:1310)
==250595==    by 0xA8CC15: standard_ProcessUtility (utility.c:1076)
==250595==    by 0xA8BC72: ProcessUtility (utility.c:533)
==250595==    by 0xA8B2B9: PortalRunUtility (pquery.c:1161)
==250595==    by 0xA8A454: PortalRunMulti (pquery.c:1318)
==250595==    by 0xA89A16: PortalRun (pquery.c:794)
==250595==    by 0xA84F9E: exec_simple_query (postgres.c:1238)
==250595==    by 0xA8425B: PostgresMain (postgres.c:4551)
==250595==    by 0x998B03: BackendRun (postmaster.c:4482)
==250595==

Which I think means this:

    if (filter_lxid && svar->drop_lxid == MyProc->lxid)
        continue;

accesses drop_lxid, which was not initialized in init_session_variable.

regards

-- 
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

From a9f9ac7b471934399aae14938567239b10e60ed6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/15] catalog support for session variables

index 883d6c0f703..6643739e53d 100644

index 89a0221ec9b..f89e12eaf29 100644

 
index aa5a2ed9483..11b2788529b 100644

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+/*
 
+/*
 
+/*
 
+		case OBJECT_VARIABLE:
index 7f3e64b5ae6..731b9805adb 100644

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
index 08747914516..27306e35928 100644
index 539df1da949..1a9c92b3a8c 100644
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
 
index 284ca55469e..26f557eb470 100644
 
 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index bc26bf1ef5a..d18e859db57 100644
index 00000000000..973d8fd7d02
index 48f7348f91c..642454c745c 100644
index 55219bb0974..c39a2787588 100644
 
index 26157eb4e3f..b55b3b05286 100644
index 8d36b664880..875f19538ba 100644

 			/*
index 7ae19b98bb9..9b113745356 100644
 
 			/*
index 00000000000..3eafc05f898
index 6007e10730a..4c484e2135f 100644

 
 				/*
index deb101710e4..0fbe1d1e5ac 100644

 /*

 

 
 /*

 
 opt_column: COLUMN

index 3ef9e8ee5e1..dbe630a51bc 100644
 
 			if (isAgg)
index e5fc708c8a5..5b1fadf183e 100644

index 827989f3799..005bc0400f0 100644
index 8140e79d8f1..339977a1427 100644

 	/*

 	return result;
 }
index 247d0816ad8..d1a7d995e15 100644

index 4fac402e5b6..80a46b0925a 100644
 
-
 /*
index a16a63f4957..f4b1f57823e 100644
 
 	return subname;
 }
+
index eec644ec848..b6d756a056d 100644
 
index a9dd068095b..af74fe345ed 100644
 
 	/*
index 98a1a842890..84a851bfa6d 100644

index 45ffa99692e..d041d728fb9 100644
 
 bki_data = [
index 2a2a2e6489e..e6873d369af 100644

index 2a791556362..672c5715620 100644

index 20f5aa56eab..3bbae2fae46 100644
 
index 00000000000..cde36b0ceab
index 00000000000..51934e8d1a8
index 7caff62af7f..e85878e13ec 100644

+
index 957ee18d849..0eb85fabc0d 100644
index 962ebf65de1..4d65a9e9e8e 100644

index 9e94f44c5f4..6c1be5e9d4b 100644
index 9a4df3a5dac..8070eb14528 100644

 
index 50f02883052..10d97ffbf34 100644
 
index 4463ea66bea..3d18dc88ac4 100644

index 215eb899be3..d9953321402 100644
index 9683b0a88e5..6778bbb80d1 100644
2.38.1

From 86b3943379d274a93bd177f9ce22831e2a3e3cfb Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email-masked]>
Date: Sat, 5 Nov 2022 00:03:34 +0100
Subject: [PATCH 02/15] review comments

---
 src/backend/access/transam/xact.c | 3 +++
 src/backend/catalog/aclchk.c      | 2 --
 src/backend/commands/Makefile     | 2 +-
 src/backend/commands/tablecmds.c  | 3 ++-
 src/backend/utils/adt/acl.c       | 1 +
 src/backend/utils/fmgr/fmgr.c     | 3 +++
 src/include/catalog/namespace.h   | 2 ++
 src/tools/pgindent/typedefs.list  | 3 ++-
 8 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 6643739e53d..a05f18ab23e 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2215,6 +2215,8 @@ CommitTransaction(void)
 	smgrDoPendingSyncs(true, is_parallel_worker);
 
 	/* Let ON COMMIT DROP */
+	/* review: everything else is called just AtEOXact_. Also why suffix
+	 * _on_xact_actions? Seems redundant with AtEOXact_ prefix. */
 	AtPreEOXact_SessionVariable_on_xact_actions(true);
 
 	/* close large objects before lower-level cleanup */
@@ -2795,6 +2797,7 @@ AbortTransaction(void)
 	AtEOXact_LargeObject(false);
 
 	/* 'false' means it's abort */
+	/* review: same naming issues as for commit */
 	AtPreEOXact_SessionVariable_on_xact_actions(false);
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 11b2788529b..fb7e3a223c7 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -945,7 +945,6 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 					table_close(rel, AccessShareLock);
 				}
 				break;
-
 			default:
 				/* should not happen */
 				elog(ERROR, "unrecognized GrantStmt.objtype: %d",
@@ -6024,7 +6023,6 @@ pg_variable_ownercheck(Oid db_oid, Oid roleid)
 	return has_privs_of_role(roleid, ownerId);
 }
 
-
 /*
  * Check whether specified role has CREATEROLE privilege (or is a superuser)
  *
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 642454c745c..1cfaeca51ea 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -48,9 +48,9 @@ OBJS = \
 	proclang.o \
 	publicationcmds.o \
 	schemacmds.o \
-	session_variable.o \
 	seclabel.o \
 	sequence.o \
+	session_variable.o \
 	statscmds.o \
 	subscriptioncmds.o \
 	tablecmds.o \
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4c484e2135f..c2f2eaf8e8b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6499,7 +6499,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
-		/* Don't allow change of type usedby session's variable */
+		/* Don't allow change of type used by session's variable */
 		if (pg_depend->classid == VariableRelationId)
 		{
 			Oid			varid = pg_depend->objid;
@@ -6538,6 +6538,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 
 		/* Else, ignore dependees that aren't user columns of relations */
 		/* (we assume system columns are never of interesting types) */
+		/* review: why is this in "else" branch? */
 		else if (pg_depend->classid != RelationRelationId ||
 				 pg_depend->objsubid <= 0)
 			continue;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 80a46b0925a..913e0f5fa44 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -1612,6 +1612,7 @@ makeaclitem(PG_FUNCTION_ARGS)
 	PG_RETURN_ACLITEM_P(result);
 }
 
+
 /*
  * convert_any_priv_string: recognize privilege strings for has_foo_privilege
  *
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index af74fe345ed..3fe55edcc81 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1899,6 +1899,9 @@ get_call_expr_arg_stable(Node *expr, int argnum)
 	 * Either a true Const or an external Param or variable will have a value
 	 * that doesn't change during the execution of the query.  In future we
 	 * might want to consider other cases too, e.g. now().
+	 *
+	 * review: Why does this update the comment, when the code did not change
+	 * at all? Is a variable represented as Param, or what?
 	 */
 	if (IsA(arg, Const))
 		return true;
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index e6873d369af..ca6f120da79 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -166,9 +166,11 @@ extern void SetTempNamespaceState(Oid tempNamespaceId,
 								  Oid tempToastNamespaceId);
 extern void ResetTempTableNamespace(void);
 
+/* review: NamesFromList is only used in 0003, so maybe don't add it before that? */
 extern List *NamesFromList(List *names);
 extern Oid	LookupVariable(const char *nspname, const char *varname, bool rowtype_only,
 						   bool missing_ok);
+/* review: IdentifyVariable is only used in 0002, so maybe don't add it before that? */
 extern Oid	IdentifyVariable(List *names, char **attrname, bool *not_unique);
 
 extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6778bbb80d1..ebf2e11456c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -826,6 +826,7 @@ FormData_pg_ts_parser
 FormData_pg_ts_template
 FormData_pg_type
 FormData_pg_user_mapping
+FormData_pg_variable
 Form_pg_aggregate
 Form_pg_am
 Form_pg_amop
@@ -884,7 +885,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
-FormData_pg_variable
+Form_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
-- 
2.38.1

From a19d2fccd8feff6b41ead84a38ccf6c0ca43ab41 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 11 Sep 2022 20:56:40 +0200
Subject: [PATCH 03/15] session variables

index a05f18ab23e..8b33d336142 100644
 
-	/* Let ON COMMIT DROP */
+	/* Let ON COMMIT DROP or ON TRANSACTION END */
 	/* review: everything else is called just AtEOXact_. Also why suffix
 	 * _on_xact_actions? Seems redundant with AtEOXact_ prefix. */
 	AtPreEOXact_SessionVariable_on_xact_actions(true);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 731b9805adb..989a100ea81 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1909,6 +1909,11 @@ find_expr_references_walker(Node *node,
 	{
 		Param	   *param = (Param *) node;
 
index 9b350d025ff..440a15cf672 100644
index 3eafc05f898..caf2c2c4923 100644
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*

 
 			/*

+				/*

 /*

+		/*
index 25a94bbaaaa..953b139058d 100644
 
 						/*
index 9b9bbf00a97..8f01ebfe651 100644
 
index d78862e660e..c3fcede3fba 100644
 
+	/*
index 99512826c54..a07400e5ea7 100644

 

 
 
index 95d0807bdde..c5f824c0bd3 100644
 
index 493a3af0fa3..7d61fdba32a 100644
 
 	/*

+	/*
index 1cb0abdbc1f..12c0364f327 100644

+/*
 
 /*
  * fix_param_node
 
index 7fb32a07103..618325e4f56 100644
 
 	/*

 
 				/*

 
index 6688c2a865b..702d875d0dd 100644

 

 

 

 

 
index 5b1fadf183e..7918361481b 100644
 
 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*
index 52e2db6452b..06a5bfbbfaf 100644
 
index 70d723e80ca..2b744238f84 100644

+/*
index cc943205d34..ab58a4d9df3 100644

 	/*
 
 /*
index 3fe55edcc81..5bc28de27d8 100644
@@ -1905,9 +1905,13 @@ get_call_expr_arg_stable(Node *expr, int argnum)
 
 	return false;
 }
index 836b49484a1..9f5f37a06bf 100644
index 3bbae2fae46..4bc2e9f2b37 100644
 
index 51934e8d1a8..aae3d251126 100644

 
 
 #endif
index e14f15d435b..c7137a9b128 100644

index e79e2c001f4..dbf4dc7ea0d 100644

index 01b1727fc09..493b9dda657 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
index e85878e13ec..c86d71cf867 100644

index 09342d128d5..8939797e939 100644

 
index 5c2ab1b3792..020a46dc2e0 100644

index f71f551782f..238499d114c 100644

 typedef struct Param
 
index 9dffdcfd1e6..ebb14e250f4 100644
 
index c8e5c57b430..14b0adb9487 100644
 
 /* GUC parameters */

index 4d65a9e9e8e..d07f5b05847 100644

diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ebf2e11456c..ac252cab6be 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2455,6 +2455,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
@@ -2639,6 +2640,8 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariable
+SVariableData
 SVariableXActAction
 SVariableXActActionItem
 Syn
-- 
2.38.1

From eaec2f9018e5e486f92138f6096bf32276bd9a87 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email-masked]>
Date: Sat, 5 Nov 2022 10:22:25 +0100
Subject: [PATCH 04/15] review comments

---
 src/backend/access/transam/xact.c       | 1 +
 src/backend/executor/execParallel.c     | 3 +--
 src/backend/optimizer/util/clauses.c    | 1 -
 src/backend/parser/analyze.c            | 6 ++----
 src/backend/utils/cache/plancache.c     | 3 +++
 src/backend/utils/misc/guc_tables.c     | 2 +-
 src/include/catalog/pg_proc.dat         | 2 ++
 src/include/commands/session_variable.h | 1 +
 src/include/nodes/pathnodes.h           | 5 +++--
 src/include/nodes/plannodes.h           | 2 +-
 src/include/nodes/primnodes.h           | 3 +--
 11 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 8b33d336142..45a8f237f30 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2217,6 +2217,7 @@ CommitTransaction(void)
 	/* Let ON COMMIT DROP or ON TRANSACTION END */
 	/* review: everything else is called just AtEOXact_. Also why suffix
 	 * _on_xact_actions? Seems redundant with AtEOXact_ prefix. */
+	/* review: seems like unnecessary comment change? */
 	AtPreEOXact_SessionVariable_on_xact_actions(true);
 
 	/* close large objects before lower-level cleanup */
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index a07400e5ea7..59d9d06d5dd 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -1526,8 +1526,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 }
 
 /*
- * Estimate the amount of space required to serialize a
- * session variable.
+ * Estimate the amount of space required to serialize a session variable.
  */
 static Size
 EstimateSessionVariables(EState *estate)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 618325e4f56..806380bb8cd 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4800,7 +4800,6 @@ substitute_actual_parameters_mutator(Node *node,
 			return list_nth(context->args, param->paramid - 1);
 		}
 	}
-
 	return expression_tree_mutator(node, substitute_actual_parameters_mutator,
 								   (void *) context);
 }
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 702d875d0dd..d59fca31d20 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -1400,6 +1400,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)
 	{
@@ -1407,8 +1408,6 @@ transformSelectStmt(ParseState *pstate, SelectStmt *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 */
@@ -1877,6 +1876,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)
 	{
@@ -1884,8 +1884,6 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *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 */
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index ab58a4d9df3..3e701723efb 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -1867,6 +1867,9 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
 	/*
 	 * Recurse into sublink subqueries, too.  But we already did the ones in
 	 * the rtable and cteList.
+	 *
+	 * review: why are we checking hasSessionVariables too? Doesn't seem very
+	 * clear, maybe explain in the comment. 
 	 */
 	if (parsetree->hasSubLinks ||
 		parsetree->hasSessionVariables)
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 9f5f37a06bf..348c5f39407 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -1480,7 +1480,7 @@ struct config_bool ConfigureNamesBool[] =
 	},
 	{
 		{"session_variables_ambiguity_warning", PGC_USERSET, DEVELOPER_OPTIONS,
-			gettext_noop("Raise warning when reference to session variable is ambiguous."),
+			gettext_noop("Raise warning when reference to a session variable is ambiguous."),
 			NULL,
 			GUC_NOT_IN_SAMPLE
 		},
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bc2e9f2b37..a1d37d4512e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11831,6 +11831,8 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+# review: why "debug"? maybe have a list of session vars with a flag if they've
+# been used, similar to pg_settings
 { oid => '8488', descr => 'debug list of used session variables',
   proname => 'pg_debug_show_used_session_variables', prorows => '1000', proretset => 't',
   provolatile => 's', prorettype => 'record', proargtypes => '',
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index aae3d251126..a0c99b9755b 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -26,6 +26,7 @@
 extern void ResetSessionVariables(void);
 extern void SessionVariableCreatePostprocess(Oid varid, char eoxaction);
 extern void SessionVariableDropPostprocess(Oid varid);
+/* review: seems duplicate with earlier prototype */
 extern void SessionVariableCreatePostprocess(Oid varid, char eoxaction);
 
 extern Datum CopySessionVariable(Oid varid, bool *isNull, Oid *typid);
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 8939797e939..c646d84f256 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -158,7 +158,8 @@ typedef struct PlannerGlobal
 	/* partition descriptors */
 	PartitionDirectory partition_directory pg_node_attr(read_write_ignore);
 
-	List	   *sessionVariables;	/* list of used session variables */
+	/* list of used session variables */
+	List	   *sessionVariables;
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -460,9 +461,9 @@ 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 020a46dc2e0..92e018a2139 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -92,7 +92,7 @@ typedef struct PlannedStmt

-	List	   *sessionVariables;	/* list of OIDs for PARAM_VARIABLE Params */
+	List	   *sessionVariables;	/* OIDs for PARAM_VARIABLE Params */
 
 	/* statement location in source string (copied from Query) */
 	int			stmt_location;	/* start location, or -1 if unknown */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 238499d114c..877e459df99 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -44,8 +44,7 @@ typedef struct Alias
 } Alias;
 
 /*
- * What to do at commit time for temporary relations or
- * persistent/temporary variable.
+ * What to do at commit time for temporary relations or session variables.
  */
 typedef enum OnCommitAction
 {
-- 
2.38.1

From 0d0cb7462bc63744c0432beb1e94b3844e41400e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Fri, 9 Sep 2022 14:03:15 +0200
Subject: [PATCH 05/15] LET command

 29 files changed, 847 insertions(+), 25 deletions(-)

index f86983c6601..57dbefbd777 100644
index caf2c2c4923..dedebc55e51 100644
 
 /*
 
+/*
index 11118d0ce02..71248a34f26 100644
 
index c3fcede3fba..e53e9acbd39 100644
index 518674cfa28..651f2ba63ba 100644
index 00000000000..6b827b68e57
index 0a7b22f97e7..e609b134e1d 100644
index 12c0364f327..566ebf4600e 100644

 
 
+/*
 
index d59fca31d20..13244bd94bb 100644

 
+		case T_LetStmt:
 
 			/*
 
@@ -1633,6 +1646,285 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	return qry;
 }
 
+/*
+ * transformLetStmt -
index 0fbe1d1e5ac..788f4a5d7bf 100644

 

 
index dbe630a51bc..4365692735c 100644
 
 			/*
 
 			/*
index 8fc86586085..c2a42d408fc 100644
 
index 7918361481b..e2906b304c5 100644
 
 			/* okay */
 			break;
 
 			/*
index 005bc0400f0..21fbf939f20 100644
 
 			/*
index bd8057bc3e7..488c5718283 100644
index fb0c687bd85..d722ba743d0 100644
 
+	/*
index b2a72374306..36c6b2d63ad 100644
 
 	/*
 
index 6afaa153cad..8e1d94a6e3e 100644
 
 		case DestTupleQueue:
 
index d1a7d995e15..d77fead276f 100644
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index 3e701723efb..89ea93ab127 100644
 
 /*
@@ -1878,6 +1879,17 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
 						  (void *) &acquire,
 
 /*
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index a0c99b9755b..c22288fee40 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -41,4 +41,7 @@ extern void AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit,
 														SubTransactionId mySubid,
 														SubTransactionId parentSubid);
 
index 00000000000..0eaa237b445
index c86d71cf867..a24ac782e40 100644

index 92e018a2139..d62020e6bc7 100644
 
 	NodeTag		type;

 
index 0eb85fabc0d..d2b9d05a8e6 100644
index d07f5b05847..43fd9ca8877 100644

index 6c1be5e9d4b..357989b52f0 100644
index 3c3eabae674..7f18e079150 100644
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ac252cab6be..56b21df4471 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1393,6 +1393,7 @@ LargeObjectDesc
 LastAttnumInfo
 Latch
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
@@ -2640,6 +2641,7 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+svariableState
 SVariable
 SVariableData
 SVariableXActAction
-- 
2.38.1

From 7f7f254ca3b0dd5d03052492c6aa42cae95a213c Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email-masked]>
Date: Sat, 5 Nov 2022 10:39:18 +0100
Subject: [PATCH 06/15] review commnents

---
 src/backend/optimizer/plan/setrefs.c | 2 +-
 src/backend/parser/analyze.c         | 2 +-
 src/backend/parser/parse_func.c      | 1 +
 src/tools/pgindent/typedefs.list     | 2 +-
 4 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 566ebf4600e..88855bc8e85 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -3369,7 +3369,7 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
 }
 
 /*
- * Record dependency on session variable. The variable can be used
+ * Record dependency on a session variable. The variable can be used as a
  * session variable in expression list, or target of LET statement.
  */
 static void
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 13244bd94bb..789ef637764 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -53,8 +53,8 @@
 #include "utils/backend_status.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
-#include "utils/queryjumble.h"
 #include "utils/lsyscache.h"
+#include "utils/queryjumble.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 21fbf939f20..d10ffd5993f 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2661,6 +2661,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 			errkind = true;
 			break;
 		case EXPR_KIND_LET_TARGET:
+			/* review: This should probably talk about "LET" and not "CALL" */
 			err = _("set-returning functions are not allowed in CALL arguments");
 			break;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 56b21df4471..8f17f36c3e0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2641,7 +2641,7 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
-svariableState
+SVariableState
 SVariable
 SVariableData
 SVariableXActAction
-- 
2.38.1

From 130782ee9ac47decd92468f9665c83cfc13fe565 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 07/15] support of LET command in PLpgSQL

index fd5796f1b9e..25e39817978 100644
index 788f4a5d7bf..117a8c1e258 100644

 /*
index ef85d3bb68e..bac97a22dba 100644
 
index 828150f01bc..bf5d5acf488 100644
 
index a6473429480..d41ed099c37 100644

+			case PLPGSQL_STMT_LET:
 
+/* ----------
index 8d7b6b58c05..22fc6b2ca26 100644
 
 	return "unknown";

+static void

+static void
index f7cf2b4b899..e1c9c8f7c81 100644

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
index 9043fbddbc8..e68b5d5b267 100644
index 088768867d9..3aeb7badf83 100644
 
 /*
 
+/*
+ * Let statement
+ */
+typedef struct PLpgSQL_stmt_let
+{
+	PLpgSQL_stmt_type cmd_type;
+	int			lineno;
+	unsigned int stmtid;
+	PLpgSQL_expr *expr;
+} PLpgSQL_stmt_let;
+
 /*
  * PERFORM statement
  */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8f17f36c3e0..865070b4b31 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1778,6 +1778,7 @@ PLpgSQL_stmt_forq
 PLpgSQL_stmt_fors
 PLpgSQL_stmt_getdiag
 PLpgSQL_stmt_if
+PLpgSQL_stmt_let
 PLpgSQL_stmt_loop
 PLpgSQL_stmt_open
 PLpgSQL_stmt_perform
-- 
2.38.1

From 88562dc1786798ad10bf8c8ca8a99e5648478359 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 08/15] DISCARD VARIABLES command

---

index c583539e0c3..68fe550a710 100644

index 117a8c1e258..85875cf0db5 100644

index d77fead276f..d442a0dffad 100644
index a24ac782e40..cda76611fcd 100644
 
index 357989b52f0..09bd4d467ff 100644
2.38.1

From 1f1fb39a25ed6feaa7ee56a7be24da6f7e8d28b9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 09/15] enhancing psql for session variables

index ab613dd49e0..5dec27c4046 100644
index c645d66418a..a958853c6db 100644
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
index bd051e09cbb..4baa699fcd1 100644
 
+/* \dV */
index f8ce1a07060..dcdfb204185 100644
index 4c45e4747a9..51a5ea95687 100644

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*
 
2.38.1

From 8ca9d051b9ba515d9e9fb31e56acb5c569044fe0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 10/15] possibility to dump session variables by pg_dump

index 44fa52cc55f..609e152aaf7 100644

index 6e501a54138..dc1f0e1ba80 100644
 
index e8b78982971..9c51987bd7f 100644
 
 	int			useDB;
index f39c0fa36fd..69c0a4fc0a6 100644
index da427f4d4a1..85ec940fd06 100644
 
+/*
+ * getVariables
 
index 427f5d45f65..56ad8acac9d 100644
 
 /*
 
+/*
 
 #endif							/* PG_DUMP_H */
index 5de3241eb49..1a660b8a9f0 100644
index 049a1006347..830cde54216 100644

 
index 8dc1f0eccb5..12a1877fdd0 100644

 
 
index 865070b4b31..34a90ef0723 100644
@@ -2915,6 +2915,7 @@ Variable
 VariableAssignHook
 VariableCache
 VariableCacheData
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.38.1

From 7dc6a085df2ed0f365a7336fe9d172546ec65c2b Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email-masked]>
Date: Sat, 5 Nov 2022 13:29:44 +0100
Subject: [PATCH 11/15] review comments

---
 src/bin/pg_dump/pg_dump.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 85ec940fd06..04f6e705651 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4873,7 +4873,7 @@ getVariables(Archive *fout)
 
 /*
  * dumpVariable
- *	  dump the definition of the given variables
+ *	  dump the definition of the given session variable
  */
 static void
 dumpVariable(Archive *fout, const VariableInfo *varinfo)
-- 
2.38.1

From 6b40635f4b574b08cba7b4ffd9ae6ddd551287f5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 12/15] regress tests for session variables

index 00000000000..8eaa7fd3260
index 5413a59a807..67837ef00f1 100644
index 00000000000..5a1035a2b21
index 00000000000..ab14fc19d69
index 9a139f1e248..4c319184eee 100644
 
 # ----------
index 00000000000..f831623bd6b
2.38.1

From 54060ce6aed5f4c692f2e987534641aea0bd6c89 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 11:40:29 +0200
Subject: [PATCH 13/15] this patch changes error message "column doesn't exist"
 to message "column or variable doesn't exist"

index e2906b304c5..5df2a225f64 100644
index 81f9ae2f024..09d3e689519 100644
index 488c5718283..6e2593d956b 100644
index 14b0adb9487..76d6e46001e 100644

index 9e22e56f001..e131febf3d3 100644
index afb922df29b..f702c7ad549 100644
index 2d922c2333e..3b6506d613e 100644
 
index 346f594ad02..bd9c0483029 100644

 select a from atacc1;

index 090ef6c7a8c..165bd7491d1 100644
index 8c527474daf..e53ae451dfc 100644
index b901d7299fa..5c1eea1c1b4 100644
 
index 08e42f17dc2..6f5f37d58fb 100644
index 624d0e5aae1..02c4498e4b1 100644
 
index ab14fc19d69..fb6b34fc0a8 100644
 
 -- should fail
 LET v1.x = 10;
index 2b2cff7d912..60c096b8576 100644
index dece7310cfe..2514a2e4ac2 100644
 
2.38.1

From b28d3e3f04f9232664c01e50e2c2a0aa031538d1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 14/15] documentation

index 755c9f14850..c65b2bb3365 100644

index 00f833d210e..e5294be4a28 100644
 
index 559eb898a9a..605a754f051 100644
 
index f1235a2c9f7..a8f303b4744 100644
index d6d0a3a8140..a46baa0c17f 100644
 
index d85f89bf303..cf73e44732e 100644
 
index e90a0e1f837..24e30fdd97e 100644
index f1d54f5aa35..354991b8491 100644

 
   <para>
index 00000000000..d2036351e53
index 23d9029af9c..50bfec6f288 100644
 
index 3c2dddb1631..f7488fd01b3 100644
index 00000000000..70c87968cee
index bf44c523cac..6f90672afa6 100644
 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
index 00000000000..67988b5fcd8
index dea19cd3480..e2191b7767e 100644
 
index 00000000000..c5d1e4d9625
index 47bd7dbda06..39cb595647e 100644
 
+     <varlistentry>
index 4fd4bfb3d74..0579b4599d4 100644
 
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index a3b743e8c1e..f5ec3c987b9 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -75,6 +75,7 @@
    &alterType;
    &alterUser;
    &alterUserMapping;
+   &alterVariable;
    &alterView;
    &analyze;
    &begin;
@@ -127,6 +128,7 @@
    &createType;
    &createUser;
    &createUserMapping;
+   &createVariable;
    &createView;
    &deallocate;
    &declare;
@@ -175,6 +177,7 @@
    &dropType;
    &dropUser;
    &dropUserMapping;
+   &dropVariable;
    &dropView;
    &end;
    &execute;
@@ -183,6 +186,7 @@
    &grant;
    &importForeignSchema;
    &insert;
+   &let;
    &listen;
    &load;
    &lock;
-- 
2.38.1

From ecdd35179bc707a77f488b5fe75791229b72ef8f Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email-masked]>
Date: Sat, 5 Nov 2022 13:45:57 +0100
Subject: [PATCH 15/15] review comments

---
 doc/src/sgml/advanced.sgml  | 3 +++
 doc/src/sgml/ref/grant.sgml | 1 +
 2 files changed, 4 insertions(+)

diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index c65b2bb3365..d52c1358bb0 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -749,6 +749,7 @@ SELECT current_user_id;
     session using the <command>LET</command> command. The content of a variable
     is not transactional.  This is the same as regular variables in PL
     languages.
+    review comment: maybe compare this to temporary tables (persistence) with persistent definition
    </para>
 
    <para>
@@ -758,6 +759,8 @@ SELECT current_user_id;
     column identifier is preferred every time. Warnings can be emitted when
     this situation happens by enabling configuration parameter <xref
     linkend="guc-session-variables-ambiguity-warning"/>.
+	review comment: this seems similar to a join, with multiple relations having the same column (which
+    forces the user to qualify the column as table.column)
    </para>
   </sect1>
 
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index e2191b7767e..b6c2445bf0d 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -109,6 +109,7 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
   | CURRENT_USER
   | SESSION_USER
 
+review comment: This should be before the "role_specification" definition, and the "TO" line seems to be duplicated
 GRANT { SELECT | UPDATE | ALL [ PRIVILEGES ] }
     ON VARIABLE <replaceable>variable_name</replaceable> [, ...]
        | ALL VARIABLES IN SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...] }
-- 
2.38.1

---

From: Julien Rouhaud <[email-masked]>
Date: Sun, 6 Nov 2022 01:25:09 +0800

Hi,

Thanks!

I agree with all of your comments, just a few answers below

> single patch, or commit individual patches.

The split was mostly done to make the patch easier to review, as it adds quite
a bit of infrastructure.

There have been some previous comments to have a more logical separation and
fix similar issues, but there are still probably other oddities like that
laying around.  I personally didn't focus much on it as I don't know if the
future committer will choose to squash everything or not.

> - AFAIK patches don't need to modify typedefs.list.

I think this was discussed a year or so ago, and my understanding is that the
general rule is that it's now welcome, if not recommended, to maintain
typedefs.list in each patchset.

> accesses drop_lxid, which was not initialized in init_session_variable.

Agreed.

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 13 Nov 2022 15:58:34 +0100

> getting called for every query.

It should be fixed in today's patch

The problem was in missing pushing the hasSessionVariables flag to the
upper subquery in pull_up_simple_subquery.

--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1275,6 +1275,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node
*jtnode, RangeTblEntry *rte,
    /* If subquery had any RLS conditions, now main query does too */
    parse->hasRowSecurity |= subquery->hasRowSecurity;

+   /* If subquery had session variables, now main query does too */
+   parse->hasSessionVariables |= subquery->hasSessionVariables;
+

Thank you for the check and bug report. Your example was added to regress
tests

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 13 Nov 2022 16:01:21 +0100

renamed

> "else if" branch (instead of just doing "if" as before)

yes, it was not necessary

> single patch, or commit individual patches.

moved to 0002

fixed

removed

renamed

renamed

fixed

fixed

Thank you very much for this review.

Today's patch should solve all issues reported by Tomas.

Regards

Pavel

> regards
From 5c3a3b13eb5391b91627bfc69a0024d6365946d7 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 doc/src/sgml/advanced.sgml                    |  66 ++++++
 doc/src/sgml/ref/grant.sgml                   |   6 +
 19 files changed, 979 insertions(+), 12 deletions(-)

diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..11edaab024 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -700,6 +700,72 @@ SELECT name, elevation
   </sect1>

+    is not transactional. This is the same as regular variables in PL languages.
+    The session variables can be persistent or can be temporary. In both cases,
+    the content of session variables is temporary and not shared (like an
+    content of temporary tables).
+    linkend="guc-session-variables-ambiguity-warning"/>. User can explicitly
+    qualify the source object by syntax <literal>table.column</literal> or
+    <literal>variable.column</literal>.

 

 

   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
index dea19cd348..9ac3c3db08 100644
@@ -101,6 +101,12 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
     [ WITH { ADMIN | INHERIT } { OPTION | TRUE | FALSE } ]
     [ 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>
 
+     <varlistentry>

From 5855253c1488588e2ed8f294d8bb16813189dd02 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 .../isolation/expected/session-variable.out   |  112 ++
 src/test/isolation/isolation_schedule         |    1 +
 .../isolation/specs/session-variable.spec     |   53 +
 .../regress/expected/session_variables.out    | 1447 +++++++++++++++++
 src/test/regress/parallel_schedule            |    2 +-
 src/test/regress/sql/session_variables.sql    | 1092 +++++++++++++
 6 files changed, 2706 insertions(+), 1 deletion(-)
 create mode 100644 src/test/isolation/expected/session-variable.out
 create mode 100644 src/test/isolation/specs/session-variable.spec
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

index 0000000000..1c4149f6ef
+step dbg: SELECT schema, name, removed FROM pg_session_variables();
+step dbg: SELECT schema, name, removed FROM pg_session_variables();
+step dbg: SELECT schema, name, removed FROM pg_session_variables();
+step dbg: SELECT schema, name, removed FROM pg_session_variables();
index 0000000000..5d089c8a4e
+step dbg	{ SELECT schema, name, removed FROM pg_session_variables(); }
index 0000000000..fbc577aa7e
@@ -0,0 +1,1447 @@
+SELECT schema, name, removed FROM pg_session_variables();
+SELECT schema, name, removed FROM pg_session_variables();
+SELECT schema, removed FROM pg_session_variables();
+SELECT schema, name, removed FROM pg_session_variables() f WHERE f.varid = :varid;
+SELECT schema, name = :varid::text AS ok, removed FROM pg_session_variables() f WHERE f.varid = :varid;;
+SELECT schema, name, removed FROM pg_session_variables() WHERE f.varid = :varid;
+LINE 1: ..., name, removed FROM pg_session_variables() WHERE f.varid = ...
+SELECT schema, name, removed FROM pg_session_variables() f WHERE f.varid = :varid;
+-- the result of view should be same in parallel mode too
+CREATE VARIABLE v1 AS int;
+LET v1 = 10;
+CREATE VIEW v1view AS SELECT COALESCE(v1, 0) AS result;
+SELECT * FROM v1view;
+SET force_parallel_mode TO on;
+SELECT * FROM v1view;
+SET force_parallel_mode TO off;
+DROP VIEW v1view;
 
 # ----------
index 0000000000..2bc0befd15
@@ -0,0 +1,1092 @@
+SELECT schema, name, removed FROM pg_session_variables();
+SELECT schema, name, removed FROM pg_session_variables();
+SELECT schema, removed FROM pg_session_variables();
+SELECT schema, name, removed FROM pg_session_variables() f WHERE f.varid = :varid;
+SELECT schema, name = :varid::text AS ok, removed FROM pg_session_variables() f WHERE f.varid = :varid;;
+SELECT schema, name, removed FROM pg_session_variables() WHERE f.varid = :varid;
+SELECT schema, name, removed FROM pg_session_variables() f WHERE f.varid = :varid;
+-- the result of view should be same in parallel mode too
+CREATE VARIABLE v1 AS int;
+LET v1 = 10;
+
+CREATE VIEW v1view AS SELECT COALESCE(v1, 0) AS result;
+
+SELECT * FROM v1view;
+
+SET force_parallel_mode TO on;
+
+SELECT * FROM v1view;
+
+SET force_parallel_mode TO off;
+
+DROP VIEW v1view;
+DROP VARIABLE v1;
-- 
2.38.1

From e46375fdd71e3bef9946d53d5494ae082f183edf Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;
index da427f4d4a..7028ebf4c8 100644
+static void dumpVariable(Archive *fout, const VariableInfo *varinfo);
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 
 
index 88cc710561..0bc3408e5d 100644

From fa441da4b423f76ce7326e24254dcf642f9a5db4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

index 2eae519b1d..39406c0fdb 100644
@@ -5067,6 +5067,102 @@ error_return:
 	return false;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 7b73886ce1..cce7e177e2 100644

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From 47dcec596ff1359ab33a18982324cf7f9b3b5150 Mon Sep 17 00:00:00 2001

 

index 600e603bdf..2a8d198343 100644

 select a from atacc1;

index 9358371072..4bc14780f3 100644
@@ -5088,7 +5088,7 @@ LINE 1: select t2.uunique1 from
@@ -6122,7 +6122,7 @@ lateral (select * from int8_tbl t1,
 
@@ -6132,7 +6132,7 @@ LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
@@ -6167,7 +6167,7 @@ LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
@@ -6187,7 +6187,7 @@ update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1)
 
index fbc577aa7e..c098b65047 100644
 
 -- should fail
 LET v1.x = 10;

From 3976b8b0a579e98f9a7669fc501881007c29c32a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 
index 77fe5e2a82..ae6590d211 100644

From 3a07e4b00670509776084b59240cba57e920c903 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

index 89a75b323b..77fe5e2a82 100644

 /*
index ef85d3bb68..505f5d3a60 100644

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

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index 2153621bdc..88cc710561 100644

From 6667681d0aac7ec2d7f57e8b66b19968a6fa7d6f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command

 src/backend/executor/svariableReceiver.c | 214 +++++++++++++++++
 29 files changed, 846 insertions(+), 25 deletions(-)

index 0f7a06453c..5a307f57b4 100644
 
 /*
@@ -1236,6 +1240,88 @@ AtEOSubXact_SessionVariable(bool isCommit,
 	}
 }
 
+/*
  * pg_session_variables - designed for testing
 
index 0000000000..2cfb4befa7
+} SVariableState;
+	SVariableState *myState = (SVariableState *) self;
+		 * double check - the type and typmod of target variable should be
+		 * same as type and typmod of assignment expression. It should be, the
+		 * expression is wrapped by cast to target type and typmod.
+	SVariableState *myState = (SVariableState *) self;
+	if (((SVariableState *) self)->rows == 0)
+	SVariableState *self = (SVariableState *) palloc0(sizeof(SVariableState));
+	SVariableState *myState = (SVariableState *) self;
index 12c0364f32..88855bc8e8 100644

 
 
+/*
 
index d59fca31d2..789ef63776 100644
 
@@ -50,6 +53,7 @@
 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -
index e5114f609e..89a75b323b 100644

 
@@ -11920,7 +11922,8 @@ ExplainableStmt:
 
 /*****************************************************************************
@@ -11950,7 +11953,8 @@ PreparableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt
-			| MergeStmt						/* by default all are $$=$1 */
+			| MergeStmt
+			| LetStmt						/* by default all are $$=$1 */
 		;

@@ -16915,6 +16962,7 @@ unreserved_keyword:
@@ -17485,6 +17533,7 @@ bare_label_keyword:
 
 			/*
 
 			/*

 			/* okay */
 			break;
 
 			/*
 			 * 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 005bc0400f..50c6328c17 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2660,6 +2660,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_LET_TARGET:
+			err = _("set-returning functions are not allowed in LET assignment expression");
+			break;
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;
 
index b824bef98c..bd3c76d7eb 100644
 
 /*
@@ -1877,6 +1878,17 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
 						  (void *) &acquire,
 
 /*
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index a4820aa21d..2667656ac2 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -40,4 +40,7 @@ extern void AtEOSubXact_SessionVariable(bool isCommit,
 										SubTransactionId mySubid,
 										SubTransactionId parentSubid);

 
index 92e018a213..d62020e6bc 100644
 
 	NodeTag		type;

 
index d07f5b0584..d9d7236099 100644

 /* ----------------
index 4e869b01d9..2153621bdc 100644

From c2eb91a23864e644333aa0dd3c972720858d8cdb Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 20:48:25 +0100
Subject: [PATCH 02/10] session variables

The most difficult part is cleaning (reset) of session variable. The
content of session variable should be cleaned when related session
variable is removed from system catalog. But queue of sinval messages
can be truncated, or current transaction state can disallow an access
to system catalog, so cleaning can be postponed.
---
 src/backend/access/transam/xact.c         |    2 +-
 src/backend/catalog/dependency.c          |    5 +
 src/backend/catalog/namespace.c           |  295 +++++
 src/backend/commands/meson.build          |    1 +
 src/backend/commands/session_variable.c   | 1229 +++++++++++++++++++--
 src/backend/executor/execExpr.c           |   75 ++
 src/backend/executor/execExprInterp.c     |   11 +
 src/backend/executor/execMain.c           |   58 +
 src/backend/executor/execParallel.c       |  147 ++-
 src/backend/jit/llvm/llvmjit_expr.c       |    6 +
 src/backend/optimizer/plan/planner.c      |    8 +
 src/backend/optimizer/plan/setrefs.c      |  118 +-
 src/backend/optimizer/prep/prepjointree.c |    3 +
 src/backend/optimizer/util/clauses.c      |   74 +-
 src/backend/parser/analyze.c              |    7 +
 src/backend/parser/parse_expr.c           |  273 ++++-
 src/backend/tcop/pquery.c                 |    3 +
 src/backend/utils/adt/ruleutils.c         |   46 +
 src/backend/utils/cache/plancache.c       |   29 +-
 src/backend/utils/fmgr/fmgr.c             |   10 +-
 src/backend/utils/misc/guc_tables.c       |   10 +
 src/include/catalog/namespace.h           |    2 +
 src/include/catalog/pg_proc.dat           |    7 +
 src/include/commands/session_variable.h   |   13 +-
 src/include/executor/execExpr.h           |   10 +
 src/include/executor/execdesc.h           |    4 +
 src/include/nodes/execnodes.h             |   19 +
 src/include/nodes/parsenodes.h            |    1 +
 src/include/nodes/pathnodes.h             |    5 +
 src/include/nodes/plannodes.h             |    2 +
 src/include/nodes/primnodes.h             |   11 +-
 src/include/optimizer/planmain.h          |    2 +
 src/include/parser/parse_expr.h           |    1 +
 src/include/parser/parse_node.h           |    1 +
 src/tools/pgindent/typedefs.list          |    3 +
 35 files changed, 2391 insertions(+), 100 deletions(-)

index 2303e1e053..1b6859f221 100644
 
-	/* Let ON COMMIT DROP */
+	/* Let ON COMMIT DROP or ON TRANSACTION END */
 	AtPreEOXact_SessionVariable(true);

index 42772c8761..1a9c92b3a8 100644
@@ -2987,6 +2987,301 @@ LookupVariable(const char *nspname,
 	return varoid;
 }
 
+/*
  *		Given a possibly-qualified name expressed as a list of String nodes,
index 4570583082..0f7a06453c 100644
@@ -22,87 +22,245 @@
 
 /*

 	/*

+/*
 
 /*
+	Oid			varid;			/* pg_variable OID of the variable (hash key) */

 /*
 
 /*
@@ -111,27 +269,30 @@ register_session_variable_xact_action(Oid varid,

 
 	xact_ai->varid = varid;

 
 
 /*
@@ -139,43 +300,837 @@ unregister_session_variable_xact_action(Oid varid,
 {
 	ListCell   *l;

 
 
 /*
+	svar->drop_lxid = InvalidLocalTransactionId;
 AtPreEOXact_SessionVariable(bool isCommit)
 {
 	ListCell   *l;

 
 			/*
 
@@ -191,10 +1146,30 @@ AtPreEOXact_SessionVariable(bool isCommit)
 				elog(DEBUG1, "session variable (oid:%u) will be deleted (forced by SVAR_ON_COMMIT_DROP action)",
 					 xact_ai->varid);
 
+				/*
 
@@ -202,12 +1177,26 @@ AtPreEOXact_SessionVariable(bool isCommit)
 
 /*
@@ -220,24 +1209,128 @@ AtEOSubXact_SessionVariable(bool isCommit,
 {
 	ListCell   *cur_item;

+		/*
+ * pg_session_variables - designed for testing
+pg_session_variables(PG_FUNCTION_ARGS)
+#define NUM_PG_SESSION_VARIABLES_ATTS 10
+	elog(DEBUG1, "pg_session_variables start");
+			Datum		values[NUM_PG_SESSION_VARIABLES_ATTS];
+			bool		nulls[NUM_PG_SESSION_VARIABLES_ATTS];
+	elog(DEBUG1, "pg_session_variables end");
 
 						/*

+	/*
index 99512826c5..59d9d06d5d 100644

 

 
 
@@ -1495,3 +1524,117 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)

 	/*

+	/*

+/*
 
 /*
  * fix_param_node
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index b4b9099eb6..f6ad7afb4c 100644
@@ -1275,6 +1275,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 
 	 * subquery won't be pulled up if it hasAggs, hasWindowFuncs, or
 	 * hasTargetSRFs, so no work needed on those flags
index 5e791333cb..f1f7ed1413 100644
 
 	/*

@@ -2264,6 +2266,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
@@ -2386,6 +2389,29 @@ eval_const_expressions_mutator(Node *node,
 
 				/*
@@ -4770,21 +4796,43 @@ substitute_actual_parameters_mutator(Node *node,

 
index 6688c2a865..d59fca31d2 100644

 

@@ -1397,6 +1400,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
 	foreach(l, stmt->lockingClause)
 	{
@@ -1622,6 +1626,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

 
@@ -1871,6 +1876,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 
 	foreach(l, lockingClause)
 	{
@@ -2409,6 +2415,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)

 
 
 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*

 
+/*
index cc943205d3..b824bef98c 100644
 
@@ -1865,9 +1866,12 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
 
 	/*
 	 * Recurse into sublink subqueries, too.  But we already did the ones in
-	 * the rtable and cteList.
+	 * the rtable and cteList. We need to force recursive call for session
+	 * variables too, to find and lock variables used in query (see
+	 * ScanQueryWalker).
 	 */
-	if (parsetree->hasSubLinks)
+	if (parsetree->hasSubLinks ||
+		parsetree->hasSessionVariables)
 	{
 		query_tree_walker(parsetree, ScanQueryWalker,
 						  (void *) &acquire,
@@ -1876,7 +1880,8 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
 }
 
 /*
- * 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)
@@ -1891,6 +1896,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
@@ -2022,7 +2041,9 @@ PlanCacheRelCallback(Datum arg, Oid relid)
 
 /*
index a9dd068095..6cfbb85f98 100644
 
 	return false;
 }
index 836b49484a..348c5f3940 100644
index 847c9fa6a0..e6873d369a 100644
@@ -166,8 +166,10 @@ extern void SetTempNamespaceState(Oid tempNamespaceId,

index 3bbae2fae4..bf12abafeb 100644
 
+{ oid => '8488', descr => 'list of used session variables',
+  proname => 'pg_session_variables', prorows => '1000', proretset => 't',
+  provolatile => 's', prorettype => 'record', proargtypes => '',
+  proallargtypes => '{oid,text,text,oid,text,bool,bool,bool,bool}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o}',
+  proargnames => '{varid,schema,name,typid,typname,removed,has_value,can_select,can_update}',
+  prosrc => 'pg_session_variables' },
 ]
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index add1ae50a6..a4820aa21d 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
- * sessionvariable.h
- *	  prototypes for sessionvariable.c.
+ * session_variable.h
+ *	  prototypes for session_variable.c.
  *
  *
  * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
@@ -26,6 +26,15 @@
 extern void SessionVariableCreatePostprocess(Oid varid, char eoxaction);
 extern void SessionVariableDropPostprocess(Oid varid);
 
 extern void AtPreEOXact_SessionVariable(bool isCommit);
 extern void AtEOSubXact_SessionVariable(bool isCommit,

 

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

 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 09342d128d..c646d84f25 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -157,6 +157,9 @@ typedef struct PlannerGlobal

@@ -458,6 +461,8 @@ struct PlannerInfo
 
 	/*
index 5c2ab1b379..92e018a213 100644

index f71f551782..877e459df9 100644
@@ -43,7 +43,9 @@ typedef struct Alias
 
@@ -296,13 +298,17 @@ typedef struct Const
 
 typedef struct Param
@@ -313,6 +319,7 @@ typedef struct Param

 
 /* GUC parameters */

 
 
index 94cfa56093..4e869b01d9 100644

From 3a49dbcfa43b75b17f9ee876f49eba3671f1094e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/10] catalog support for session variables

Implementation new system object class - session variable with new access rights SELECT, UPDATE,
with routines for creating session variable, initialization of session variable from system
catalog, and lookups routines for identification of session variables.
---
 src/backend/access/transam/xact.c       |  11 +
 src/backend/catalog/Makefile            |   4 +-
 src/backend/catalog/aclchk.c            | 301 +++++++++++++++++++
 src/backend/catalog/dependency.c        |  13 +-
 src/backend/catalog/meson.build         |   1 +
 src/backend/catalog/namespace.c         | 158 ++++++++++
 src/backend/catalog/objectaddress.c     | 126 +++++++-
 src/backend/catalog/pg_shdepend.c       |   2 +
 src/backend/catalog/pg_variable.c       | 372 ++++++++++++++++++++++++
 src/backend/commands/Makefile           |   1 +
 src/backend/commands/alter.c            |   9 +
 src/backend/commands/dropcmds.c         |   4 +
 src/backend/commands/event_trigger.c    |   6 +
 src/backend/commands/seclabel.c         |   1 +
 src/backend/commands/session_variable.c | 243 ++++++++++++++++
 src/backend/commands/tablecmds.c        |  43 +++
 src/backend/parser/gram.y               | 145 ++++++++-
 src/backend/parser/parse_agg.c          |   2 +
 src/backend/parser/parse_expr.c         |   5 +
 src/backend/parser/parse_func.c         |   1 +
 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/backend/utils/cache/syscache.c      |  23 ++
 src/include/catalog/dependency.h        |   5 +-
 src/include/catalog/meson.build         |   1 +
 src/include/catalog/namespace.h         |   5 +
 src/include/catalog/pg_default_acl.h    |   1 +
 src/include/catalog/pg_proc.dat         |   3 +
 src/include/catalog/pg_variable.h       | 120 ++++++++
 src/include/commands/session_variable.h |  34 +++
 src/include/nodes/parsenodes.h          |  20 ++
 src/include/parser/kwlist.h             |   2 +
 src/include/parser/parse_node.h         |   1 +
 src/include/tcop/cmdtaglist.h           |   3 +
 src/include/utils/acl.h                 |   6 +-
 src/include/utils/lsyscache.h           |   9 +
 src/include/utils/syscache.h            |   6 +-
 src/test/regress/expected/oidjoins.out  |   4 +
 src/tools/pgindent/typedefs.list        |   5 +
 41 files changed, 1831 insertions(+), 13 deletions(-)
 create mode 100644 src/backend/catalog/pg_variable.c
 create mode 100644 src/backend/commands/session_variable.c
 create mode 100644 src/include/catalog/pg_variable.h
 create mode 100644 src/include/commands/session_variable.h

index 8086b857b9..2303e1e053 100644
 
+	/* Let ON COMMIT DROP */
+	AtPreEOXact_SessionVariable(true);
+
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
+	AtPreEOXact_SessionVariable(false);
@@ -5004,6 +5011,8 @@ CommitSubTransaction(void)
+	AtEOSubXact_SessionVariable(true, s->subTransactionId,
@@ -5168,6 +5177,8 @@ AbortSubTransaction(void)
+		AtEOSubXact_SessionVariable(false, s->subTransactionId,

 
index 42360d37ca..accc232bcf 100644
 
@@ -894,6 +919,32 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
@@ -1053,6 +1104,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
@@ -1244,6 +1299,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
 
+		case OBJECT_VARIABLE:
@@ -1475,6 +1536,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
@@ -1535,6 +1599,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
@@ -3367,6 +3434,129 @@ ExecGrant_Parameter(InternalGrant *istmt)
 
+static void
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3568,6 +3758,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;
@@ -3679,6 +3872,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;
@@ -3827,6 +4023,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return pg_type_aclmask(table_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return pg_variable_aclmask(table_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) objtype);
@@ -4810,6 +5008,66 @@ pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how)
 	return result;
 }
 
+/*
@@ -5110,6 +5368,18 @@ pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
 		return ACLCHECK_NO_PRIV;
 }
 
+/*
@@ -5727,6 +5997,33 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid)
 
+/*
@@ -5855,6 +6152,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
index 539df1da94..42772c8761 100644
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
@@ -2839,6 +2903,89 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
+/*
 
 /*
  * DeconstructQualifiedName
@@ -4656,3 +4803,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)
 
index c7de7232b8..10ec73402f 100644
 
 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index 48f7348f91..1cfaeca51e 100644
@@ -50,6 +50,7 @@ OBJS = \
index 5dabba4dd0..569cbe0f8f 100644
 			elog(ERROR, "unsupported object class: %u", classId);

 
 			/*
 
 			/*
index 0000000000..4570583082
@@ -0,0 +1,243 @@
+AtPreEOXact_SessionVariable(bool isCommit)
+AtEOSubXact_SessionVariable(bool isCommit,
index fc4bd0de91..b03c7a000d 100644
@@ -6496,6 +6499,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
 		if (pg_depend->classid != RelationRelationId ||
@@ -12726,6 +12768,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 				/*
index 2a910ded15..e5114f609e 100644

 /*

 

@@ -5011,6 +5017,69 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6788,6 +6857,7 @@ object_type_any_name:
 
 /*
@@ -7664,6 +7734,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7709,6 +7787,14 @@ privilege_target:

@@ -7906,6 +7992,7 @@ defacl_privilege_target:

@@ -9688,6 +9775,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10049,6 +10155,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10328,6 +10453,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -16942,6 +17075,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -17553,6 +17688,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)

 
 	/*

 	return result;
 }

index 0bc79cba2b..19ca476d35 100644
 			elog(ERROR, "unrecognized object type abbreviation: %c", objtypec);
 
 	return subname;
 }
+

 
 
 bki_data = [
index 2a2a2e6489..847c9fa6a0 100644
 
@@ -164,6 +166,9 @@ extern void SetTempNamespaceState(Oid tempNamespaceId,

 
 
index 0000000000..add1ae50a6
+extern void AtPreEOXact_SessionVariable(bool isCommit);
+extern void AtEOSubXact_SessionVariable(bool isCommit,

+

 

 
 
index 245aea1dd1..94cfa56093 100644
@@ -825,6 +826,7 @@ FormData_pg_ts_parser
@@ -883,6 +885,7 @@ Form_pg_ts_parser
@@ -2636,6 +2639,8 @@ SupportRequestRows

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 13 Nov 2022 18:59:30 +0100

Hi

fresh rebase

Regards

Pavel
From 12f097ba132b0d5a925ef20ae1fe3253c6cbe821 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+     <varlistentry>

From 13b908790a78b1f986154bcbb3b9baa34a3b68b3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */

 /* PREPARE xx AS */
 
 /*

From 721e8c332d14fb0389088355886fabb67c39b1d4 Mon Sep 17 00:00:00 2001

 

 
 
 select a from atacc1;

 

 -- should fail
 LET v1.x = 10;

From 44dbf823d4533fb097a36a17972cdddc06a65cd3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 8a1cdade31f00e9d3224209156adf1ae918ca424 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

 
 # ----------

From afab8d94b455a38b575a244fa8226a1dea464452 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 

From c65559920f0f87e42546bdb5ee00224fd32a046e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 5d5e07a718168ba44f7f3306efe0a489a84e254f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command

index efe43eb63e..fff156557d 100644
 
 /*
 
+/*
+	aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);
 
index f1ae5f120e..0090901a90 100644

 
 
+/*
 
index d59fca31d2..a8ab85382e 100644

+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -
+	aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);

 

 
 
 			/*
 
 			/*

 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 
 	NodeTag		type;

 

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

From 1bcde3dd19d986113f503d15dbda0a196d0471be Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:45:23 +0100
Subject: [PATCH 02/10] session variables

 src/backend/commands/session_variable.c   | 1231 +++++++++++++++++++--
 src/backend/executor/execExpr.c           |   76 ++
 35 files changed, 2394 insertions(+), 100 deletions(-)

 
 
index 0d5c2d1f04..eed7c8f25c 100644
@@ -2988,6 +2988,301 @@ LookupVariable(const char *nspname,
 	return varoid;
 }
 
+/*
index 4570583082..efe43eb63e 100644
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*
+	aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);

 
 			/*

+				/*

 /*
@@ -220,24 +1209,130 @@ AtEOSubXact_SessionVariable(bool isCommit,
 {
 	ListCell   *cur_item;

+		/*
+										 object_aclcheck(VariableRelationId, svar->varid,
+														 GetUserId(), ACL_SELECT) == ACLCHECK_OK);
+										 object_aclcheck(VariableRelationId, svar->varid,
+														 GetUserId(), ACL_UPDATE) == ACLCHECK_OK);
index 0ecb2f8610..cc3d93f373 100644
@@ -34,6 +34,7 @@
@@ -995,6 +996,81 @@ ExecInitExprRec(Expr *node, ExprState *state,
+								aclresult = object_aclcheck(VariableRelationId, varid,
+															GetUserId(), ACL_SELECT);
 
 						/*
 
index d78862e660..f1ae5f120e 100644
 
+	/*
+			aclresult = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_SELECT);

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

index 317c10c2b9..0e52edab53 100644
 
 	/*

 
 				/*

 

 

 

 

 

 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*
 
index c5a49d0be3..912b7ece35 100644
@@ -8069,6 +8071,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}

+/*

 	/*
 
 /*
 
 	/*
 
 /*
index 3c210297aa..49b22d20e3 100644
 
 	return false;
 }

index ead33cbcaf..128d08cf58 100644

 

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

 
index a544b313d3..00a9e33d11 100644

 
 	/*

 
 
 typedef struct Param

 
 /* GUC parameters */

 

From 458f8a8922cdc6e8c06bfb4454673825e00abbb3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:39:37 +0100
Subject: [PATCH 01/10] catalog support for session variables

 src/backend/catalog/aclchk.c            | 202 +++++++++++++
 src/backend/catalog/objectaddress.c     | 122 +++++++-
 src/include/utils/acl.h                 |   1 +
 41 files changed, 1724 insertions(+), 12 deletions(-)

 

 
index 3c9f8e60ad..42a5c09278 100644
@@ -60,6 +60,7 @@
@@ -116,6 +117,7 @@ static void ExecGrant_Namespace(InternalGrant *istmt);
 
@@ -285,6 +287,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
@@ -529,6 +534,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
@@ -634,6 +643,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
@@ -824,6 +836,19 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
@@ -913,6 +938,32 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
@@ -1072,6 +1123,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
@@ -1263,6 +1318,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
 
+		case OBJECT_VARIABLE:
@@ -1494,6 +1555,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
@@ -1554,6 +1618,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
@@ -3386,6 +3453,129 @@ ExecGrant_Parameter(InternalGrant *istmt)
 
+static void
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3587,6 +3777,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;
@@ -3698,6 +3891,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;
@@ -3846,6 +4042,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);
@@ -4965,6 +5163,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 			defaclobjtype = DEFACLOBJ_NAMESPACE;
 			break;
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
index bac0deb6da..0d5c2d1f04 100644
@@ -765,6 +766,69 @@ RelationIsVisible(Oid relid)
 	return visible;
 }
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
@@ -2840,6 +2904,89 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
+/*
 
 /*
  * DeconstructQualifiedName
@@ -4657,3 +4804,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)
 
index 9dad77c28a..b058ff818d 100644
 
 /*

 	/*
 
+/*
@@ -2502,6 +2548,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			if (!object_ownercheck(address.classId, address.objectId, roleid))
@@ -3472,6 +3519,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
@@ -3824,6 +3897,16 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
@@ -4600,6 +4683,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 
+		case OCLASS_VARIABLE:
@@ -5707,6 +5794,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
@@ -5950,6 +6041,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index 0000000000..8a538f81be
+	aclresult = object_aclcheck(TypeRelationId, typid, GetUserId(), ACL_USAGE);
index b2089d785b..b71dc2fff2 100644
 
index 389fc6a102..416a8447e0 100644
index a3bdc5db07..aefdb49b18 100644

 			/*
 
 			/*
index f006807852..ab0c250b36 100644

 				/*

 /*

 

 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

index 487eb2041b..98496e3930 100644

 	/*

 	return result;
 }

index 8bdb9461b7..576b5cb651 100644
@@ -812,6 +812,10 @@ acldefault(ObjectType objtype, Oid ownerId)
@@ -909,6 +913,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
 
 	return subname;
 }
+

 
 
 bki_data = [

 
 
index 9dbe9ec801..ead33cbcaf 100644

 
+

index 35b3d8dd88..af4a7ad909 100644

---

From: Sergey Shinderuk <[email-masked]>
Date: Mon, 14 Nov 2022 10:00:38 +0300

> fresh rebase

Hello,

Sorry, I haven't been following this thread, but I'd like to report a 
memory management bug. I couldn't apply the latest patches, so I tested 
with v20221104-1-* patches applied atop of commit b0284bfb1db.

postgres=# create variable s text default 'abc';

create function f() returns text as $$
begin
         return g(s);
end;
$$ language plpgsql;

create function g(t text) returns text as $$
begin
         let s = 'BOOM!';
         return t;
end;
$$ language plpgsql;

select f();
CREATE VARIABLE
CREATE FUNCTION
CREATE FUNCTION
server closed the connection unexpectedly
	This probably means the server terminated abnormally
	before or while processing the request.

LOG:  server process (PID 55307) was terminated by signal 11: 
Segmentation fault
DETAIL:  Failed process was running: select f();

I believe it's a use-after-free error, triggered by assigning a new 
value to s in g(), thus making t a dangling pointer.

After reconnecting I get a scary error:

postgres=# select f();
ERROR:  compressed pglz data is corrupt

Best regards,

-- 
Sergey Shinderuk		https://postgrespro.com/

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 15 Nov 2022 06:00:44 +0100

> ERROR:  compressed pglz data is corrupt

I am able to reproduce it, and I have a quick fix, but I need to
investigate i this fix will be correct

It's a good example so I have to always return a copy of value.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 15 Nov 2022 21:22:12 +0100

Hi

should be fixed now

Thank you for check

Regards

Pavel

> ERROR:  compressed pglz data is corrupt
From 018b218db57807bfdf8871cc264129d2ffd85b86 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 
 
index bd50ea8e48..fbb8c163f8 100644
@@ -10237,6 +10237,21 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>

index dda667e68e..7a56da5bf7 100644
@@ -5942,6 +5942,18 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE;

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+     <varlistentry>

From 9cd072ee8d570d5b3a733c31a775e8e626bed6cb Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

Checks of basic functionality, check of usage session variables in subtransactions
and from PLpgSQL
---
 .../isolation/expected/session-variable.out   |  112 ++
 src/test/isolation/isolation_schedule         |    1 +
 .../isolation/specs/session-variable.spec     |   53 +
 .../regress/expected/session_variables.out    | 1471 +++++++++++++++++
 src/test/regress/parallel_schedule            |    2 +-
 src/test/regress/sql/session_variables.sql    | 1117 +++++++++++++
 6 files changed, 2755 insertions(+), 1 deletion(-)
 create mode 100644 src/test/isolation/expected/session-variable.out
 create mode 100644 src/test/isolation/specs/session-variable.spec
 create mode 100644 src/test/regress/expected/session_variables.out
 create mode 100644 src/test/regress/sql/session_variables.sql

index 0000000000..1be6c1c2a9
@@ -0,0 +1,1471 @@
+-- the value should not be corrupted
+CREATE VARIABLE v1 text DEFAULT 'abc';
+CREATE FUNCTION ffunc()
+RETURNS text AS $$
+  RETURN gfunc(v1);
+END
+CREATE FUNCTION gfunc(t text)
+RETURNS text AS $$
+  LET v1 = 'BOOM!';
+  RETURN t;
+select ffunc();
+ ffunc 
+ abc
+DROP FUNCTION ffunc();
+DROP FUNCTION gfunc(text);
 
 # ----------
index 0000000000..05ef7d0e72
@@ -0,0 +1,1117 @@
+-- the value should not be corrupted
+CREATE VARIABLE v1 text DEFAULT 'abc';
+
+CREATE FUNCTION ffunc()
+RETURNS text AS $$
+BEGIN
+  RETURN gfunc(v1);
+END
+$$ LANGUAGE plpgsql;
+
+CREATE FUNCTION gfunc(t text)
+RETURNS text AS $$
+BEGIN
+  LET v1 = 'BOOM!';
+  RETURN t;
+END;
+$$ LANGUAGE plpgsql;
+
+select ffunc();
+
+DROP FUNCTION ffunc();
+DROP FUNCTION gfunc(text);
+
+DROP VARIABLE v1;
-- 
2.38.1

From a10488717baa76e8862529bf3f6904ffcf862f31 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

index 7672ed9e9d..68882ab256 100644
@@ -981,6 +981,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index b4e0ec2687..2ae60c838f 100644
@@ -287,6 +287,7 @@ slashUsage(unsigned short int pager)
index a0e26bc295..9bb3f6c740 100644

 /*
@@ -1689,7 +1699,7 @@ psql_completion(const char *text, int start, int end)
@@ -2130,6 +2140,9 @@ psql_completion(const char *text, int start, int end)
@@ -2672,7 +2685,7 @@ psql_completion(const char *text, int start, int end)
@@ -3111,7 +3124,7 @@ psql_completion(const char *text, int start, int end)
@@ -3418,6 +3431,17 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS */
@@ -3533,7 +3557,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
@@ -3660,6 +3684,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 */
@@ -3776,7 +3806,8 @@ psql_completion(const char *text, int start, int end)
@@ -3794,6 +3825,8 @@ psql_completion(const char *text, int start, int end)
 									 "USAGE",
 									 "SET",
 									 "ALTER SYSTEM",
+									 "READ",
+									 "WRITE",
 									 "ALL");
 	}
 
@@ -3835,7 +3868,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("GRANT|REVOKE", MatchAny) ||
 			 TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny))
 	{
-		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
+		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|READ|WRITE|ALL"))
 			COMPLETE_WITH("ON");
 		else if (TailMatches("GRANT", MatchAny))
 			COMPLETE_WITH("TO");
@@ -3858,7 +3891,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",
@@ -3880,7 +3913,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"))
@@ -3888,7 +3922,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");
@@ -3924,6 +3959,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
@@ -4201,7 +4238,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -4489,6 +4526,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
 
@@ -4659,6 +4704,8 @@ psql_completion(const char *text, int start, int end)

From 63318bd838ec59831a28ad2f566fabbc6bad3591 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 
 
index be9ae20aa2..740cf390ec 100644

From 23b7ae160fb7a025add2ef55aaa48ac64316290e Mon Sep 17 00:00:00 2001

 src/test/regress/expected/psql.out            |  2 +-
 17 files changed, 63 insertions(+), 53 deletions(-)

 

 
 select a from atacc1;

 
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 5bdae290dc..0e34b73326 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -120,7 +120,7 @@ SELECT $1, $2 \bind 'foo' 'bar' \g
 -- errors
 -- parse error
 SELECT foo \bind \g
 LINE 1: SELECT foo 
 -- tcop error
 
index 1be6c1c2a9..e520e81cc6 100644
 
 -- should fail
 LET v1.x = 10;

From 4393e07bc894e14d548f4be9d8bb91776519f678 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 

From 2be492cd2aca632fa7f646a9bd2aca2f0d9eed82 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 91ecd8347f4191e64eb9b624e424d26a268244a0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command

index 9ea3c89902..e2bd72f193 100644
 
 /*
@@ -1238,6 +1242,88 @@ AtEOSubXact_SessionVariable(bool isCommit,
 	}
 }
 
+/*

 

+/*

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*

 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 
 	NodeTag		type;

 

 /* ----------------
index 1b081cd56e..b6dda41550 100644

From d87e9a8d790fe6889fe646f7999bb002816432d1 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:45:23 +0100
Subject: [PATCH 02/10] session variables

 src/backend/commands/session_variable.c   | 1233 +++++++++++++++++++--
 src/backend/executor/execExprInterp.c     |   17 +
 35 files changed, 2402 insertions(+), 100 deletions(-)

 

+/*
index 4570583082..9ea3c89902 100644
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*
@@ -139,43 +300,839 @@ unregister_session_variable_xact_action(Oid varid,
 {
 	ListCell   *l;

 
 
 /*
+	return CopySessionVariableWithTypeCheck(varid, isNull, expected_typid);

 
 			/*
 
@@ -191,10 +1148,30 @@ AtPreEOXact_SessionVariable(bool isCommit)
 				elog(DEBUG1, "session variable (oid:%u) will be deleted (forced by SVAR_ON_COMMIT_DROP action)",
 					 xact_ai->varid);
 
+				/*
 
@@ -202,12 +1179,26 @@ AtPreEOXact_SessionVariable(bool isCommit)
 
 /*
@@ -220,24 +1211,130 @@ AtEOSubXact_SessionVariable(bool isCommit,
 {
 	ListCell   *cur_item;

+		/*
 
 						/*
index 9b9bbf00a9..80e9d88d02 100644
@@ -1082,6 +1084,21 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_PARAM_VARIABLE)
+		{
+			/*
+			 * direct access to session variable (without buffering).
+			 * Because returned value can be used (without an assignement)
+			 * after the referenced session variables is updated, we have
+			 * to return copy of stored value every time. This is not an
+			 * issue for local (plpgsql) variables.
+			 */
+			*op->resvalue = CopySessionVariableWithTypeCheck(op->d.vparam.varid,
+															 op->resnull,
+															 op->d.vparam.vartype);
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_CASE_TESTVAL)
 		{
 			/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d78862e660..f1ae5f120e 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_publication.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
+#include "commands/session_variable.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSubplan.h"
 #include "foreign/fdwapi.h"
@@ -200,6 +201,63 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	Assert(queryDesc->sourceText != NULL);
 	estate->es_sourceText = queryDesc->sourceText;
 
+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 
 	/*

 
 				/*

 

 

 

 

 

 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*

 
+/*

 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }

 

 

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

 

 
 	/*

 
 
 typedef struct Param

 
 /* GUC parameters */

 
 
index b1dc762c24..1b081cd56e 100644

From d92a76e759285c6cdd198633d2ba1520e9d7378c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:39:37 +0100
Subject: [PATCH 01/10] catalog support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 
 			/*
 
 			/*

 				/*

 /*

 

 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 bki_data = [

 

 
 
+

 

index f8302f1ed1..b1dc762c24 100644

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 18 Nov 2022 08:58:31 +0100

Hi

fresh rebase

fix small bug in tab-complete - READ|WRITE rights are not used, and support
is removed now

Regards

Pavel
From 31636aa73a652ae0fba172b5c2c89a95a1b8ab81 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+     <varlistentry>

From 263d84fd23c34f1ef53180ade6b5b2b630545b01 Mon Sep 17 00:00:00 2001

 

 
 
 select a from atacc1;

index 80638d84a0..50590bd3eb 100644

 
 -- should fail
 LET v1.x = 10;

From 9eabeaa0f98cbfb3c8756347f659d33589ff5f59 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

\dV and tab complete for session variables related commands
---
 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 | 64 +++++++++++++++++++++----
 5 files changed, 157 insertions(+), 10 deletions(-)

+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 13014f074f..c3b74c6162 100644

 /*
@@ -1225,6 +1232,8 @@ static const pgsql_thing_t words_after_create[] = {
@@ -1263,6 +1272,7 @@ static const pgsql_thing_t words_after_create[] = {
@@ -1674,8 +1684,8 @@ psql_completion(const char *text, int start, int end)
@@ -1695,7 +1705,7 @@ psql_completion(const char *text, int start, int end)
@@ -2136,6 +2146,9 @@ psql_completion(const char *text, int start, int end)
@@ -2678,7 +2691,7 @@ psql_completion(const char *text, int start, int end)
@@ -3117,7 +3130,7 @@ psql_completion(const char *text, int start, int end)
@@ -3424,6 +3437,17 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE [ OR REPLACE ] VIEW <name> with AS */
@@ -3539,7 +3563,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
@@ -3666,6 +3690,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 */
@@ -3854,7 +3884,7 @@ psql_completion(const char *text, int start, int end)
@@ -3876,7 +3906,8 @@ psql_completion(const char *text, int start, int end)
@@ -3884,7 +3915,8 @@ psql_completion(const char *text, int start, int end)
@@ -3920,6 +3952,8 @@ psql_completion(const char *text, int start, int end)
@@ -4202,7 +4236,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -4490,6 +4524,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
 
@@ -4660,6 +4702,8 @@ psql_completion(const char *text, int start, int end)

From b2ac70b77968538bc8bbd7d4aa5e8ff3a7ad9085 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 184ac1c41fca8e029f9ff154fc3ad0f2b1fae417 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

 
 # ----------

From 2e6b5a2625d563f59d9d05150bf71d815f028f14 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 

From e02bcf080d12ef2eb134a442651f70fbe44e4b44 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 15b5fcb752b8e8f77afaee70eeebd67f7341cbec Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command

 
 /*
 
+/*

 

+/*

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*

 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 
 	NodeTag		type;

 

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

From 8b2cac3a9dd5a234427129bde25f49f2ddf6b028 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:45:23 +0100
Subject: [PATCH 02/10] session variables

 

 
+/*
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*

 
 			/*

+				/*

 /*

+		/*
 
 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node
 
index f4cdb879c2..d3dc6582d2 100644
@@ -1276,6 +1276,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 
index 33790a4f46..0184754916 100644
@@ -811,16 +812,17 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 
 	/*

@@ -2275,6 +2277,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
@@ -2397,6 +2400,29 @@ eval_const_expressions_mutator(Node *node,
 
 				/*
@@ -4781,21 +4807,43 @@ substitute_actual_parameters_mutator(Node *node,

 

 

 

 

 

 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*
 
index b4c0407588..84561d276a 100644
@@ -8044,6 +8046,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
@@ -12062,6 +12072,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }

 

 

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

 

 
 	/*

 
 
 typedef struct Param

 
 /* GUC parameters */

 

From f99ed3d5bc6ba56edc30658c32699b599028f805 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:39:37 +0100
Subject: [PATCH 01/10] catalog support for session variables

 src/backend/commands/dropcmds.c         |   5 +
 41 files changed, 1725 insertions(+), 12 deletions(-)

 

 

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
 
index fe97fbf79d..b665cc079b 100644
 
 /*
 
@@ -894,6 +913,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,
@@ -1164,6 +1184,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 */
 		}
 
@@ -2038,16 +2061,20 @@ get_object_address_defacl(List *object, bool missing_ok)
 
 	/*
@@ -2131,6 +2158,24 @@ textarray_to_strvaluelist(ArrayType *arr)
 	return list;
 }
 
+/*
@@ -2324,6 +2369,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
@@ -2501,6 +2547,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
@@ -3478,6 +3525,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
@@ -3830,6 +3903,16 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
@@ -4606,6 +4689,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 
+		case OCLASS_VARIABLE:
@@ -5713,6 +5800,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
@@ -5956,6 +6047,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
 
index db906f530e..37c996379d 100644
@@ -481,6 +481,11 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 
 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:

 			/*
 
 			/*

 				/*

 /*

 

 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 bki_data = [

 

 
 
+

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 21 Nov 2022 21:33:56 +0100

Hi

fresh rebase

Regards

Pavel
From aa4c5d7586bcee2d1894eebb18867237958eada3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9ed2b020b7..dfab64b39d 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>
@@ -9624,4 +9629,157 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </table>
  </sect1>
 
index 24b1624bad..26fd840c16 100644
 
index 93fb149d9a..eafcfe7e42 100644
@@ -1534,6 +1534,22 @@
    </glossdef>
   </glossentry>

 

   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
index 5ae523f4b3..86f951e566 100644
     [ WITH { ADMIN | INHERIT | SET } { OPTION | TRUE | FALSE } ]

 
+     <varlistentry>
index 2db66bbf37..1aef1ccbff 100644
@@ -137,6 +137,13 @@ REVOKE [ { ADMIN | INHERIT | SET } OPTION FOR ]

From 32425c38e231bb6f7927b961ba96151d08c0a6f0 Mon Sep 17 00:00:00 2001

index 4ad8044791..dd944338b3 100644
@@ -433,7 +433,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
index 250695aa7b..a6e97dee29 100644

 

 select a from atacc1;

 

 -- should fail
 LET v1.x = 10;

From 1ee942255583ad5a8aeb0102054ff05738940b8f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 
 
index 60fbaeb8f1..ebdada571b 100644

From f4416dde39a1f997adfed08a25e3c3ae3a6b8e05 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

From 720de913696355b6b0252723cf2c69f672271dec Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

 
 # ----------

From c18ea7d119a706a35ab595b13264c8f395768389 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 
index 7ff97640a1..4f70048741 100644

From 83d99eb97a9e25c9efff127e56b27d2fcfd0011b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

index 1ebf014428..7ff97640a1 100644
@@ -793,6 +793,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -902,6 +903,13 @@ parse_toplevel:
 
 /*

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

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index d04f3170b1..60fbaeb8f1 100644

From 9fb81bea8b68fef3a3b24a4503f1327e11ee54a6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command

 
 /*
 
+/*
 
index af8620ceb7..eb719ab200 100644
@@ -3791,6 +3791,16 @@ raw_expression_tree_walker_impl(Node *node,

 
 
+/*

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -
index 9b8baea285..1ebf014428 100644
@@ -304,7 +304,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -452,6 +452,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
@@ -715,7 +716,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY

@@ -1042,6 +1043,7 @@ stmt:

 
@@ -16958,6 +17005,7 @@ unreserved_keyword:
@@ -17528,6 +17576,7 @@ bare_label_keyword:
 
 			/*
 
 			/*
 
index 069c5809bc..4ad8044791 100644
@@ -558,6 +558,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
 			/* okay */
 			break;
@@ -1985,6 +1986,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:
@@ -3307,6 +3309,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_LET_TARGET:
+			return "LET";
 
 			/*
 
 			/*
index 8e0d6fd01f..250695aa7b 100644
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 
 	NodeTag		type;

 

 /* ----------------
index c48f195119..d04f3170b1 100644

From 60e5786e81971bc7f5bfa52967480c421832b208 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:45:23 +0100
Subject: [PATCH 02/10] session variables

 src/include/executor/execExpr.h           |   11 +
 35 files changed, 2403 insertions(+), 100 deletions(-)

 

+/*
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*

 
 			/*

+				/*

 /*

+		/*
index 81429b9f05..e9b23be595 100644
 
 						/*
index 1dab2787b7..d3f044962c 100644
@@ -1081,6 +1083,21 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}

+	/*

 

 
 
index f114337f8e..6e3c4d0481 100644

 	/*

+	/*

+/*
 
 /*
  * fix_param_node

index bffc8112aa..8acd8ddf82 100644
@@ -804,16 +805,17 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 
 	/*

@@ -2267,6 +2269,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
@@ -2389,6 +2392,29 @@ eval_const_expressions_mutator(Node *node,
 
 				/*
@@ -4756,21 +4782,43 @@ substitute_actual_parameters_mutator(Node *node,

 

 

 

 

 
 
index 1bb8c5a850..069c5809bc 100644
 
 /* GUC parameters */
 
@@ -81,6 +83,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,

 /*
@@ -424,6 +429,59 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	return result;
 }
 
+/*
@@ -772,6 +830,161 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
+	/*
@@ -806,6 +1019,64 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
 
index f3ea36a231..d5c138ff6a 100644
 
@@ -12064,6 +12074,42 @@ generate_collation_name(Oid collid)
 	return result;
 }
 
+/*

 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
index 349dd6a537..f309fc3610 100644
@@ -1498,6 +1498,16 @@ struct config_bool ConfigureNamesBool[] =

index 33a4a62ad3..5e4d561aff 100644
@@ -11857,4 +11857,11 @@

index 0557302b92..1b411fc6d4 100644
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -381,6 +382,13 @@ typedef struct ExprEvalStep
 			Oid			paramtype;	/* OID of parameter's datatype */
 		}			param;
 
@@ -734,6 +742,9 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
+extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalRowNull(ExprState *state, ExprEvalStep *op,

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

 

 
 	/*

index 74f228d959..1697cca7e4 100644

 typedef struct Param

 
 /* GUC parameters */

 
 
index a4e1cffeb1..c48f195119 100644

From 48fe38ca963ab1ad87bd04ab9faccbbfe78ad72b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:39:37 +0100
Subject: [PATCH 01/10] catalog support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
index 10b6fe19a2..8dc2fcaa53 100644

 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:

 			/*
 
 			/*
index 845208d662..1affc63641 100644

 				/*
index 9384214942..9b8baea285 100644
@@ -292,8 +293,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -473,6 +474,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
@@ -642,6 +644,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 /*
@@ -752,8 +756,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UESCAPE UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING

 
@@ -1000,6 +1004,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1544,6 +1549,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;

 
 /*

 
 opt_column: COLUMN

@@ -16985,6 +17118,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -17596,6 +17731,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)
index 150a8099c2..1bb8c5a850 100644
 
@@ -498,6 +499,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
@@ -1719,6 +1722,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;
@@ -3006,6 +3010,7 @@ ParseExprKindName(ParseExprKind exprKind)

 	/*

 	return result;
 }

index d4d68f9724..6352a76cd2 100644
@@ -813,6 +813,10 @@ acldefault(ObjectType objtype, Oid ownerId)
@@ -910,6 +914,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
index 94ca8e1230..ee48e5a04c 100644
@@ -3683,3 +3684,115 @@ get_subscription_name(Oid subid, bool missing_ok)
 
 	return subname;
 }
+

 
 
 bki_data = [

 
 
index f15aa2dbb1..33a4a62ad3 100644
@@ -6308,6 +6308,9 @@

 
+

index afbfdccf53..9a0cb30892 100644

 
 
index 2f5802195d..a4e1cffeb1 100644
@@ -2634,6 +2637,8 @@ SupportRequestRows

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 24 Nov 2022 13:16:23 +0100

Hi

rebase and small refactoring in parse_relation - reduce redundant code

Regards

Pavel
From 83869c41152785b728894d67a56bb56cb7adf1c0 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+     <varlistentry>

From ac4e4e6c44d788a4d015cf8d3b4ef144a1c8a376 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 24 Nov 2022 12:35:57 +0100
Subject: [PATCH 09/10] this patch changes error message "column doesn't exist"
 to message "column or variable doesn't exist"

 src/backend/parser/parse_relation.c           | 24 +++++++++++---
 src/test/regress/expected/join.out            | 12 +++----
 17 files changed, 76 insertions(+), 57 deletions(-)

index 4665f0b2b7..ed0e2820fa 100644
@@ -3648,6 +3649,19 @@ errorMissingRTE(ParseState *pstate, RangeVar *relation)
 				 parser_errposition(pstate, relation->location)));
 }
 
+/*
+ * set message "column does not exist" or "column or variable does not exist"
+ * in dependency if expression context allows session variables.
+ */
+static int
+column_or_variable_does_not_exists(ParseState *pstate, const char *colname)
+{
+	if (expr_kind_allows_session_variables(pstate->p_expr_kind))
+		return errmsg("column or variable \"%s\" does not exist", colname);
+	else
+		return errmsg("column \"%s\" does not exist", colname);
+}
+
 /*
  * Generate a suitable error about a missing column.
  *
@@ -3682,7 +3696,7 @@ errorMissingColumn(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 relname ?
 					 errmsg("column %s.%s does not exist", relname, colname) :
-					 errmsg("column \"%s\" does not exist", colname),
+					 column_or_variable_does_not_exists(pstate, colname),
 					 errdetail("There are columns named \"%s\", but they are in tables that cannot be referenced from this part of the query.",
 							   colname),
 					 !relname ? errhint("Try using a table-qualified name.") : 0,
@@ -3692,7 +3706,7 @@ errorMissingColumn(ParseState *pstate,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 relname ?
 				 errmsg("column %s.%s does not exist", relname, colname) :
-				 errmsg("column \"%s\" does not exist", colname),
+				 column_or_variable_does_not_exists(pstate, colname),
 				 errdetail("There is a column named \"%s\" in table \"%s\", but it cannot be referenced from this part of the query.",
 						   colname, state->rexact1->eref->aliasname),
 				 rte_visible_if_lateral(pstate, state->rexact1) ?
@@ -3710,14 +3724,14 @@ errorMissingColumn(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 relname ?
 					 errmsg("column %s.%s does not exist", relname, colname) :
-					 errmsg("column \"%s\" does not exist", colname),
+					 column_or_variable_does_not_exists(pstate, colname),
 					 parser_errposition(pstate, location)));
 		/* Handle case where we have a single alternative spelling to offer */
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 relname ?
 				 errmsg("column %s.%s does not exist", relname, colname) :
-				 errmsg("column \"%s\" does not exist", colname),
+				 column_or_variable_does_not_exists(pstate, colname),
 				 errhint("Perhaps you meant to reference the column \"%s.%s\".",
 						 state->rfirst->eref->aliasname,
 						 strVal(list_nth(state->rfirst->eref->colnames,
@@ -3731,7 +3745,7 @@ errorMissingColumn(ParseState *pstate,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 relname ?
 				 errmsg("column %s.%s does not exist", relname, colname) :
-				 errmsg("column \"%s\" does not exist", colname),
+				 column_or_variable_does_not_exists(pstate, colname),
 				 errhint("Perhaps you meant to reference the column \"%s.%s\" or the column \"%s.%s\".",
 						 state->rfirst->eref->aliasname,
 						 strVal(list_nth(state->rfirst->eref->colnames,
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 250695aa7b..a6e97dee29 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -779,7 +779,9 @@ transformAssignmentIndirection(ParseState *pstate,
 			if (!typrelid)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("cannot assign to field \"%s\" of column \"%s\" because its type %s is not a composite type",
+						 errmsg(expr_kind_allows_session_variables(pstate->p_expr_kind) ?
+								 "cannot assign to field \"%s\" of column or variable \"%s\" because its type %s is not a composite type" :
+								 "cannot assign to field \"%s\" of column \"%s\" because its type %s is not a composite type",
 								strVal(n), targetName,
 								format_type_be(targetTypeId)),
 						 parser_errposition(pstate, location)));
@@ -788,7 +790,9 @@ transformAssignmentIndirection(ParseState *pstate,
 			if (attnum == InvalidAttrNumber)
 				ereport(ERROR,
 						(errcode(ERRCODE_UNDEFINED_COLUMN),
-						 errmsg("cannot assign to field \"%s\" of column \"%s\" because there is no such column in data type %s",
+						 errmsg(expr_kind_allows_session_variables(pstate->p_expr_kind) ?
+								 "cannot assign to field \"%s\" of column or variable \"%s\" because there is no such column in data type %s" :
+								 "cannot assign to field \"%s\" of column \"%s\" because there is no such column in data type %s",
 								strVal(n), targetName,
 								format_type_be(targetTypeId)),
 						 parser_errposition(pstate, location)));
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index 14b0adb948..76d6e46001 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,5 +22,6 @@ extern PGDLLIMPORT bool session_variables_ambiguity_warning;
 extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKind);

 

 select a from atacc1;

index 21ca7d04bc..8e3aa8fe57 100644
@@ -5088,13 +5088,13 @@ LINE 1: select t2.uunique1 from
 select ctid from
   tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, need qualification
-ERROR:  column "ctid" does not exist
+ERROR:  column or variable "ctid" does not exist
 LINE 1: select ctid from
                ^
 DETAIL:  There are columns named "ctid", but they are in tables that cannot be referenced from this part of the query.
@@ -6129,7 +6129,7 @@ lateral (select * from int8_tbl t1,
 
 DETAIL:  There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
@@ -6141,7 +6141,7 @@ LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
 DETAIL:  There is an entry for table "a", but it cannot be referenced from this part of the query.
 HINT:  To reference that table, you must mark this subquery with LATERAL.
 DETAIL:  There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
@@ -6178,7 +6178,7 @@ LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
 DETAIL:  There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query.
@@ -6198,7 +6198,7 @@ update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1)
 DETAIL:  There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query.
index 37c1c86473..b0f7663c29 100644
 DETAIL:  There are columns named "f1", but they are in tables that cannot be referenced from this part of the query.
@@ -2724,7 +2724,7 @@ select * from rules_fooview;
 (1 row)

 -- should fail
 LET v1.x = 10;
index e2613d6777..0a6613c430 100644
 
 DETAIL:  There is a column named "q2" in table "*SELECT* 2", but it cannot be referenced from this part of the query.
-- 
2.38.1

From 3c027b8ded16b0c854f1fadf8f2c377fdac3939e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

From 785236ce7a8bb57ef478696ce18c93badda96d40 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 85236585a1eef7d11d5227ee6bd0f2166d552c2a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

 
 # ----------

From 0cda61c2a80b2adb14de09fc44a9103aea5b4ef3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From d9276eb30a248fbaf935e2558286b467fa0fd89a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 

index 6175f0ca2f..d6f8e06170 100644

From 5238022c8936cebc5b74bc3408358d428f87f1ac Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:45:23 +0100
Subject: [PATCH 02/10] session variables

 

 
+/*
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*

 
 			/*

+				/*

 /*

+		/*
 
 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 
 	/*

 
 				/*

 

 

 

 

 

 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*

 
+/*

 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }

index 77ddc3e779..ecbd56ab2a 100644

 

 
index 0f10d4432b..bb67a00f75 100644
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
index d74c9e05a8..5980f9977a 100644

 

 	/*

 
 
 typedef struct Param

 
 /* GUC parameters */

 

From a55a0c82f21e7ee2cc2af0a09a5477990a6a2479 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command

 
 /*
 
+/*

 

+/*

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*

 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*
 
index 5980f9977a..6175f0ca2f 100644

 
 	NodeTag		type;

 

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

From ab67a5832ab4580d4b2015d648a10dc3358221dd Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:39:37 +0100
Subject: [PATCH 01/10] catalog support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:

 			/*
 
 			/*

 				/*

 /*

 

 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 bki_data = [

 
 
index f9301b2627..77ddc3e779 100644
 
index f4ed9bbff9..d74c9e05a8 100644

+

index 406071037e..6699268a95 100644

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 2 Dec 2022 21:08:38 +0100

Hi

fresh rebase

Regards

Pavel
From 7a6a5ea1b7af9d976e5f5792aa5230e18b0be40b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 
 
index 39d1c89e33..fbe05d4c31 100644
@@ -10221,6 +10221,21 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>
 
index 7c01a541fe..6c52f77776 100644
 
index 2b2a1a8215..2e2b72b69e 100644
 
index 0da295daff..3914c2dc47 100644
 
     { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | VACUUM | ANALYZE }

   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
index c3c585be7e..740cba6cab 100644

 
+     <varlistentry>
index e28d192fd3..f24b1fb4b8 100644

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

 src/test/regress/expected/rules.out           |  2 +-
 17 files changed, 75 insertions(+), 56 deletions(-)

+/*

 

 select a from atacc1;

index b8d43e4c14..458bd5a0d0 100644
@@ -5108,13 +5108,13 @@ LINE 1: select t2.uunique1 from
@@ -6149,7 +6149,7 @@ lateral (select * from int8_tbl t1,
 
@@ -6161,7 +6161,7 @@ LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
@@ -6198,7 +6198,7 @@ LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
@@ -6218,7 +6218,7 @@ update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1)
index 1047399ef8..aeba1e34f9 100644
index 8d85ffc760..16c1bf796a 100644
 
 -- should fail
 LET v1.x = 10;

From 923f9d466453c6bbdf1b0ac0441779ab065bb065 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

index c11dc9a420..405aa631e9 100644
@@ -109,3 +109,4 @@ test: truncate-conflict
 test: matview-write-skew
 
 # ----------

From 0ed8127022786e7df096793506df7e86359a89c2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 89e7317c23..02660eb4e4 100644

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */
@@ -3855,7 +3885,7 @@ psql_completion(const char *text, int start, int end)
@@ -3877,7 +3907,8 @@ psql_completion(const char *text, int start, int end)
@@ -3885,7 +3916,8 @@ psql_completion(const char *text, int start, int end)
@@ -3921,6 +3953,8 @@ psql_completion(const char *text, int start, int end)
@@ -4203,7 +4237,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -4491,6 +4525,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
 
@@ -4661,6 +4703,8 @@ psql_completion(const char *text, int start, int end)

From 489e030ea4331e57f822e2b8fe3a7e33bf66c117 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 
 
index 9311417f18..3e5d912dee 100644
@@ -506,6 +506,12 @@ do { \
 
index bc6b6594af..5ae509b1d0 100644
@@ -132,12 +132,14 @@ typedef struct _restoreOptions
 
 	int			useDB;
index 0081873a72..0d340a7406 100644
@@ -2951,6 +2951,14 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
@@ -3435,6 +3443,7 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te)
index 44e8cd4704..17045cfcc5 100644
@@ -290,6 +290,7 @@ static void dumpPolicy(Archive *fout, const PolicyInfo *polinfo);
@@ -4773,6 +4774,232 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
@@ -9427,7 +9654,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -10017,6 +10245,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION:
@@ -14393,6 +14624,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -17934,6 +18168,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 
index 709db0986d..722f5f992a 100644
@@ -573,6 +573,16 @@ my %tests = (
 
@@ -1328,6 +1338,22 @@ my %tests = (
 
@@ -3284,6 +3310,30 @@ my %tests = (
 		},
 	},
 
@@ -3723,6 +3773,21 @@ my %tests = (
 		like => {},
 	},
 
index 9e0126be8d..98d01b5746 100644
@@ -2912,6 +2912,7 @@ Variable
 VariableAssignHook
 VariableCache
 VariableCacheData
+VariableInfo
 VariableSetKind
 VariableSetStmt
 VariableShowStmt
-- 
2.38.1

From 09cdaf0143ec348dbed74c5e8db7e3d14eac51a8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 
index 22b2fdf72d..bb1531cca9 100644

index eefb3f5791..d28c2f5ebd 100644
@@ -3572,7 +3572,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;

From d1ffed22667ab1831641d3de605cfdd0b0d0cdf6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

index 5e3d53cbf3..22b2fdf72d 100644

 /*

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

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index 4e01985386..9e0126be8d 100644

From fedeaef0aea0122863e5e77ccf1fecf91c9555a3 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:45:23 +0100
Subject: [PATCH 02/10] session variables

 

 
+/*
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*

 
 			/*

+				/*

 /*

+		/*
 
 						/*
 
index 12ff4f3de5..ddaece018c 100644
 
+	/*
index aca0c6f323..ac15eafcb0 100644

 
@@ -598,6 +606,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
@@ -607,6 +616,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
@@ -662,6 +672,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 
@@ -756,6 +771,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 
@@ -1403,6 +1423,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 
@@ -1428,6 +1449,14 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 	area_space = shm_toc_lookup(toc, PARALLEL_KEY_DSA, false);
 	area = dsa_attach_in_place(area_space, seg);
 
@@ -1496,3 +1525,117 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 
index 799602f5ea..295b940a4d 100644
 
 	/*
@@ -531,6 +532,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
@@ -686,6 +688,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	 */
 	pull_up_subqueries(root);
 
+	/*
index e67f0e3509..470b99e006 100644

 /*****************************************************************************
@@ -1243,6 +1245,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 	return plan;
 }
 
+/*
@@ -1838,8 +1884,9 @@ copyVar(Var *var)
@@ -1929,15 +1976,39 @@ fix_expr_common(PlannerInfo *root, Node *node)
 
 /*
  * fix_param_node
@@ -1956,6 +2027,41 @@ fix_param_node(PlannerInfo *root, Param *p)
 
@@ -2017,7 +2123,10 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
@@ -2039,7 +2148,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
index 2ea3ca734e..118b6e494a 100644

 	/*

 
 				/*

 

 

 

 

 

 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*
 
index a20a1b069b..aa6b66c78a 100644

+/*

 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
index 1bf14eec66..213d9ebf1a 100644

 

 

diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 71248a9466..a69d62145e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -597,6 +597,18 @@ typedef struct AsyncRequest
 								 * tuples) */
 } AsyncRequest;
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+	Oid			varid;
+	Oid			typid;
+	bool		isnull;
+	Datum		value;
+} SessionVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -649,6 +661,13 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
 
+	/* Variables info: */
 
index 14bc2fcd07..5290db1231 100644
@@ -148,6 +148,7 @@ typedef struct Query

diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index dd4eb8679d..b1190dbf5e 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -160,6 +160,9 @@ typedef struct PlannerGlobal

@@ -461,6 +464,8 @@ struct PlannerInfo
 
 	/*
 	 * Information about aggregates. Filled by preprocess_aggrefs().
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 2e202892a7..c01a35d46d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -95,6 +95,8 @@ typedef struct PlannedStmt

 
 
 typedef struct Param

 
 /* GUC parameters */

 
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8b08f64468..3a94570222 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2452,6 +2452,7 @@ SerializedTransactionState
 Session
 SessionBackupState
 SessionEndType
+SessionVariableValue
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
@@ -2636,6 +2637,8 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariable
+SVariableData
 SVariableXActAction
 SVariableXActActionItem
 Syn
-- 
2.38.1

From 60e61f50dcc5e3021cd9e357c2ebfffec34c4c5e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command

 
 /*
 
+/*
 
index ddaece018c..a012b627c5 100644
index 470b99e006..7419dbae20 100644
 
 /*****************************************************************************
  *
@@ -1981,18 +1981,7 @@ fix_expr_common(PlannerInfo *root, Node *node)
 		Param	   *p = (Param *) node;

@@ -3386,6 +3375,25 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
 	}
 }
 
+/*
@@ -3459,6 +3467,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -
index 260b7fbce3..5e3d53cbf3 100644

 
@@ -11925,7 +11927,8 @@ ExplainableStmt:
 
 /*****************************************************************************
@@ -11955,7 +11958,8 @@ PreparableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt
-			| MergeStmt						/* by default all are $$=$1 */
+			| MergeStmt
+			| LetStmt						/* by default all are $$=$1 */
 		;
 
 /*****************************************************************************
@@ -12543,6 +12547,49 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -16965,6 +17012,7 @@ unreserved_keyword:
@@ -17535,6 +17583,7 @@ bare_label_keyword:
 
 			/*
 
 			/*

 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
index f49cfb6cc6..c6ff1ff9f7 100644
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*
 
index 5290db1231..eefb3f5791 100644
@@ -139,6 +139,7 @@ typedef struct Query
 
@@ -1702,6 +1703,21 @@ typedef struct MergeStmt
 
index c01a35d46d..b78a06aa4e 100644
 
 	NodeTag		type;

 

 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3a94570222..4e01985386 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1392,6 +1392,7 @@ LargeObjectDesc
 LastAttnumInfo
 Latch
 LerpFunc
+LetStmt
 LexDescr
 LexemeEntry
 LexemeHashKey
@@ -2637,6 +2638,7 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariableState
 SVariable
 SVariableData
 SVariableXActAction
-- 
2.38.1

From 822e12759ae820ae77e905b4d83cc7699a7c43a9 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:39:37 +0100
Subject: [PATCH 01/10] catalog support for session variables

 

 
 
index bd967eaa78..31962eccf7 100644

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3595,6 +3785,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;
@@ -3706,6 +3899,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;
@@ -3854,6 +4050,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);
@@ -4993,6 +5191,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 			defaclobjtype = DEFACLOBJ_NAMESPACE;
 			break;
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:

 			/*
 
 			/*
index 10c1955884..af18b66510 100644
@@ -6436,6 +6437,8 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -6497,6 +6500,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
@@ -12732,6 +12774,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 				/*
index b1ae5f834c..260b7fbce3 100644

 /*

 

 
 /*
@@ -7669,6 +7739,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7714,6 +7792,14 @@ privilege_target:

@@ -7911,6 +7997,7 @@ defacl_privilege_target:

@@ -9693,6 +9780,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10054,6 +10160,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10333,6 +10458,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -16992,6 +17125,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -17603,6 +17738,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)

index 36791d8817..b61c36253c 100644

@@ -3840,6 +3841,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 	cxt.indexes = NIL;
 	cxt.triggers = NIL;
 	cxt.grants = NIL;
+	cxt.variables = NIL;
 
 	/*
 	 * Run through each schema element in the schema element list. Separate
@@ -3908,6 +3910,15 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 				cxt.grants = lappend(cxt.grants, element);
 				break;
 
@@ -3921,6 +3932,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 
 	return result;
 }

index ed1b6a41cf..e7c378faf8 100644
@@ -819,6 +819,10 @@ acldefault(ObjectType objtype, Oid ownerId)
@@ -916,6 +920,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
 
 	return subname;
 }
+

 
 
 bki_data = [

 

index 6112cd85c8..14bc2fcd07 100644
@@ -1911,6 +1911,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -3038,6 +3039,25 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+

index e566ff0c73..e10e6081dc 100644
@@ -170,6 +170,7 @@ typedef struct ArrayType Acl;

 
 
index 58daeca831..8b08f64468 100644
@@ -824,6 +825,7 @@ FormData_pg_ts_parser
@@ -882,6 +884,7 @@ Form_pg_ts_parser
@@ -2633,6 +2636,8 @@ SupportRequestRows

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 6 Dec 2022 12:16:54 +0100

Hi

fresh rebase

Regards

Pavel
From 68f32eb30f91bffbc288fee32ced6186dbd2eb30 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 
 
index ff6fcd902a..cbac8df8a7 100644

 

 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+     <varlistentry>

From 52c95628f50d5c981e27b0feed649d66a8841aad Mon Sep 17 00:00:00 2001

 
+/*

 

 select a from atacc1;

 
index b4cb6ffb5b..cf961f3d59 100644
index 532ea36990..080118165d 100644
 
 -- should fail
 LET v1.x = 10;

From 4f4701ea5723dfb42e6440a83386af3685f17c53 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

index de6a3a71f8..6d6ada383c 100644
@@ -980,6 +980,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

From 8693bbe06fb8d28c9b7e78fd3cca8ae45d9f24f5 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

 
 # ----------

From 4fdc9556a711a8c7bb7dbed8c637d58be50d03ab Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

index aba780ef4b..a0274bffcb 100644
 
 	int			useDB;
index 7f7a0f1ce7..7498d5a524 100644
index ad6693c358..857776af20 100644
@@ -4777,6 +4778,232 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
@@ -9431,7 +9658,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -10021,6 +10249,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION:
 			dumpSubscription(fout, (const SubscriptionInfo *) dobj);
 			break;
+		case DO_VARIABLE:
+			dumpVariable(fout, (VariableInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -14397,6 +14628,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",
@@ -17938,6 +18172,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 436ac5bb98..c7099a817c 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,
@@ -82,7 +83,7 @@ typedef enum
 	DO_PUBLICATION,
 	DO_PUBLICATION_REL,
 	DO_PUBLICATION_TABLE_IN_SCHEMA,
-	DO_SUBSCRIPTION
+	DO_SUBSCRIPTION,
 } DumpableObjectType;
 
 /*
 
+/*
 
 #endif							/* PG_DUMP_H */
index 31cee46f3e..c0763576ef 100644
@@ -76,6 +76,7 @@ enum dbObjectTypePriorities
 	PRIO_LARGE_OBJECT,
 	PRIO_SEQUENCE_SET,

 
index 6656222363..b25eb3e4a2 100644
@@ -624,6 +624,16 @@ my %tests = (
 
@@ -1379,6 +1389,22 @@ my %tests = (
 
@@ -3335,6 +3361,30 @@ my %tests = (
 		},
 	},
 
@@ -3774,6 +3824,21 @@ my %tests = (
 		like => {},
 	},

From c1cafacb85669f39f80bb2295998a72f9166877a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 

index 11560e6f00..9d4ab3efad 100644

From ea5d1cdc30baa5caeacfeabb2381fe2ed951eeee Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 67561947ad6f6425c3deb3d490b7ba8f3bd4097a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command

 
 /*
 
+/*
 
index a193dd1b6c..111d4ae8f2 100644

 
 
+/*

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*

 			/* okay */
 			break;
 
 			/*
 
 			/*
index 1e3efbbd36..9fe2f9d64d 100644
@@ -3716,6 +3717,39 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
 		}
 	}
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*
 
index 015dcc1d52..11560e6f00 100644

index 3042b66fba..f120f878da 100644
 
 	NodeTag		type;

 

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

From b4a9460fa408bccb38414b44377759a8af3cf6a6 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:45:23 +0100
Subject: [PATCH 02/10] session variables

 

 
+/*
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*

 
 			/*

+				/*

 /*

+		/*
 
 						/*

+	/*

 

 

 
 	/*

+	/*
index 399c1812d4..a193dd1b6c 100644

+/*
 
 /*
  * fix_param_node

 
 	/*

 
 				/*

 

 

 

 

 

 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*
 
index 641df1cabe..9af2d87d36 100644

+/*

 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }

 

 

 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
 
+	/* Variables info: */
 
index fe6c1a2c16..015dcc1d52 100644

index 12624e6adb..c14051be2d 100644

 
 	/*
index c36a15bd09..3042b66fba 100644

 
 
 typedef struct Param

 
 /* GUC parameters */

 

From 45ba9297275e76819c444c6ef9dc558c0f3215a8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:39:37 +0100
Subject: [PATCH 01/10] catalog support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:

 			/*
 
 			/*

 				/*

 /*

 

 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 bki_data = [

 

index f17846e30e..fe6c1a2c16 100644

+

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 14 Dec 2022 05:54:48 +0100

Hi

fresh rebase

regards

Pavel
From 6f216a4cef157ac78eaeff0e87d8c4e9759b843a Mon Sep 17 00:00:00 2001

index b4878a92ea..c84f9c9413 100644
@@ -3614,6 +3615,19 @@ errorMissingRTE(ParseState *pstate, RangeVar *relation)
 
+/*
@@ -3648,7 +3662,7 @@ errorMissingColumn(ParseState *pstate,
@@ -3658,7 +3672,7 @@ errorMissingColumn(ParseState *pstate,
@@ -3676,14 +3690,14 @@ errorMissingColumn(ParseState *pstate,
@@ -3697,7 +3711,7 @@ errorMissingColumn(ParseState *pstate,
index 84ef5b845f..7a6f76e48b 100644

 

 select a from atacc1;

 
index 8fc62cebd2..8142ca18e3 100644
index fb9f936d43..396323c349 100644
 
 -- should fail
 LET v1.x = 10;

From 94b88807e0b74ccf3bf400ba46aee736b1ef456f Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

 
 # ----------

From ce2b26ab7dfd06d92e96e6f65f6224f8d1d7401c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 
 
index f45cc6cb73..b206fd6dec 100644
@@ -511,6 +511,12 @@ do { \

 	int			useDB;
index 44d957c038..c9941f7183 100644
@@ -4794,6 +4795,232 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 
+/*
+ * getVariables
@@ -9448,7 +9675,8 @@ getAdditionalACLs(Archive *fout)
 
@@ -10038,6 +10266,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION:
@@ -14414,6 +14645,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
@@ -17955,6 +18189,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 
 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 
index 1c7fc728c2..5def9ebfef 100644

 
 
index 4352b356f6..9c16e0ae09 100644

From e2150388f87b4cb2e9f7f63e9096508f53480762 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

index df166365e8..05b56e7103 100644
@@ -5070,6 +5070,102 @@ error_return:
 	return false;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
 
 /*
  * \dFp
 
+/* \dV */
index 2a3921937c..9554ed7ed6 100644

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */
@@ -3857,7 +3887,7 @@ psql_completion(const char *text, int start, int end)
@@ -3879,7 +3909,8 @@ psql_completion(const char *text, int start, int end)
@@ -3887,7 +3918,8 @@ psql_completion(const char *text, int start, int end)
@@ -3923,6 +3955,8 @@ psql_completion(const char *text, int start, int end)
@@ -4207,7 +4241,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -4499,6 +4533,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
 
@@ -4669,6 +4711,8 @@ psql_completion(const char *text, int start, int end)

From c8448ec06636de082df0c3d9273a58cdfdb7376c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9316b811ac..df5402fef6 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>
@@ -9634,4 +9639,157 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </table>
  </sect1>
 
index 8e4145979d..93d063a780 100644
@@ -10255,6 +10255,21 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>

 
index a33461fbc2..a4cc0f8907 100644
 
     { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }

   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
index c8ca2b1d64..f1ec67acbc 100644

 
+     <varlistentry>
index 8df492281a..626c0231d0 100644

From 4a21caff2e37f5e5c8cfb870167bf54eee202674 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 
index 7729efa45c..c22b14a1bb 100644
@@ -2044,7 +2044,12 @@ DiscardStmt:

index 69716b8cc4..77bba4baef 100644
@@ -3593,7 +3593,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;

From 9a5cb640e7f7b8822292c28bd76fd9181af246ad Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

index 00c0f7aa6a..7729efa45c 100644

 /*

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

+static void

+static void

 stmt_perform	: K_PERFORM
 
+stmt_let		: K_LET
+					{
+						PLpgSQL_stmt_let *new;
+						RawParseMode pmode;
+
 
 /*
 
+/*
+ * Let statement
+ */
index b8e57fa40f..4352b356f6 100644

From b962f90b3afb454c6ba5081a762ddf3d094a7729 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command

 
 /*
 
+/*
 
index b409fc18be..5fd767a147 100644
index 94b6dfdc8a..c582af2f38 100644
@@ -192,7 +192,7 @@ static List *set_windowagg_runcondition_references(PlannerInfo *root,
 
 /*****************************************************************************
  *
@@ -2024,18 +2024,7 @@ fix_expr_common(PlannerInfo *root, Node *node)
 		Param	   *p = (Param *) node;

@@ -3429,6 +3418,25 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
 	}
 }
 
+/*
@@ -3502,6 +3510,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 
index 52240033ce..d9dcb7ec53 100644

+		case T_LetStmt:
 
 			/*
 
@@ -1639,6 +1652,285 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	return qry;
 }
 
+/*
+ * transformLetStmt -
index 90e13ffe68..00c0f7aa6a 100644

 
@@ -11938,7 +11940,8 @@ ExplainableStmt:
 
 /*****************************************************************************
@@ -11968,7 +11971,8 @@ PreparableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt
-			| MergeStmt						/* by default all are $$=$1 */
+			| MergeStmt
+			| LetStmt						/* by default all are $$=$1 */
 		;
 
 /*****************************************************************************
@@ -12556,6 +12560,49 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -16978,6 +17025,7 @@ unreserved_keyword:
@@ -17548,6 +17596,7 @@ bare_label_keyword:
 
 			/*
 
 			/*

 			/* okay */
 			break;
 
 			/*
 
 			/*
index 56d64c8851..84ef5b845f 100644
index 7cf0ceacc3..cf0221f743 100644
@@ -3726,6 +3727,39 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
 		}
 	}
 
+	/*
index f03b36a6e4..57273e2652 100644
@@ -220,10 +220,10 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	}
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*
 
index 4a6630cd38..69716b8cc4 100644
 
@@ -1727,6 +1728,21 @@ typedef struct MergeStmt
 
index b2ac18f05c..8af89a69b0 100644
 
 	NodeTag		type;

 
index 3a292e1f35..b816079a6c 100644

 /* ----------------
index 3269bf0bc5..b8e57fa40f 100644

From ac40ac6da78def6195b15b4de70037ea2f617d2a Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:45:23 +0100
Subject: [PATCH 02/10] session variables

index 396e210de5..04686a5c71 100644

 
 
+/*
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*

 
 			/*

+				/*

 /*

+		/*
 
 						/*
 
index 2c2b3a8874..b409fc18be 100644
@@ -201,6 +202,63 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 
+	/*
index a5b8e43ec5..14c5a9e17e 100644

 
@@ -599,6 +607,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
@@ -608,6 +617,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
@@ -663,6 +673,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 
@@ -757,6 +772,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 
@@ -1404,6 +1424,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 
@@ -1429,6 +1450,14 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 	area_space = shm_toc_lookup(toc, PARALLEL_KEY_DSA, false);
 	area = dsa_attach_in_place(area_space, seg);
 
@@ -1497,3 +1526,117 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 
index 5dd4f92720..2c7e43544e 100644
@@ -319,6 +319,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
 	/*
@@ -535,6 +536,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 
@@ -690,6 +692,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	 */
 	pull_up_subqueries(root);
 
+	/*
index 596f1fbc8e..94b6dfdc8a 100644
@@ -190,6 +190,8 @@ static List *set_returning_clause_references(PlannerInfo *root,

 /*****************************************************************************
@@ -1286,6 +1288,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 	return plan;
 }
 
+/*
@@ -1881,8 +1927,9 @@ copyVar(Var *var)
@@ -1972,15 +2019,39 @@ fix_expr_common(PlannerInfo *root, Node *node)
 
 /*
  * fix_param_node
@@ -1999,6 +2070,41 @@ fix_param_node(PlannerInfo *root, Param *p)
 
@@ -2060,7 +2166,10 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
@@ -2082,7 +2191,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
index c2239d18b4..e8657e0c93 100644
@@ -1270,6 +1270,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,

 	/*

 
 				/*

 
index 2e593aed2b..52240033ce 100644
@@ -526,6 +526,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)

 	/* this must be done after collations, for reliable comparison of exprs */
@@ -948,6 +950,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)

 
@@ -1402,6 +1405,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
 	foreach(l, stmt->lockingClause)
 	{
@@ -1628,6 +1632,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

 
@@ -1878,6 +1883,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 
 	foreach(l, lockingClause)
 	{
@@ -2418,6 +2424,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)

 
 
 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*

 
+/*

 	/*
 
 /*
 
 	/*
 
 /*
index 7b28a266ce..fb1ef7a0e5 100644
@@ -2013,9 +2013,13 @@ get_call_expr_arg_stable(Node *expr, int argnum)
 
 	return false;
 }

index cb05043b39..77161bb618 100644
@@ -11865,4 +11865,11 @@

 

 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9a64a830a2..5baaea59e9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -594,6 +594,18 @@ typedef struct AsyncRequest
 								 * tuples) */
 } AsyncRequest;
 
+/* ----------------
+ * SessionVariableValue
+ * ----------------
+ */
+typedef struct SessionVariableValue
+{
+	Oid			varid;
+	Oid			typid;
+	bool		isnull;
+	Datum		value;
+} SessionVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -647,6 +659,13 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
 
+	/* Variables info: */
 
index 21e7e16154..4a6630cd38 100644
@@ -147,6 +147,7 @@ typedef struct Query

diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 654dba61aa..2f6ff6c94b 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -163,6 +163,9 @@ typedef struct PlannerGlobal

@@ -464,6 +467,8 @@ struct PlannerInfo
 
 	/*
 	 * Information about aggregates. Filled by preprocess_aggrefs().
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index bddfe86191..b2ac18f05c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -98,6 +98,8 @@ typedef struct PlannedStmt

 
 
 typedef struct Param

 
 /* GUC parameters */

diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 44f4e5c1ed..3a292e1f35 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -217,6 +217,7 @@ struct ParseState
 	bool		p_hasTargetSRFs;
 	bool		p_hasSubLinks;
 	bool		p_hasModifyingCTE;
+	bool		p_hasSessionVariables;

index 6161b16601..3269bf0bc5 100644

From d2b9ef5071616fbd04ae2cf3f53ebb25cb57e42e Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:39:37 +0100
Subject: [PATCH 01/10] catalog support for session variables

index b7c7fd9f00..396e210de5 100644

@@ -5012,6 +5019,8 @@ CommitSubTransaction(void)
@@ -5176,6 +5185,8 @@ AbortSubTransaction(void)

 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index b5019059e8..2d67af8907 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -60,6 +60,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
@@ -112,6 +113,7 @@ static void ExecGrant_Language_check(InternalGrant *istmt, HeapTuple tuple);
 static void ExecGrant_Largeobject(InternalGrant *istmt);
 static void ExecGrant_Type_check(InternalGrant *istmt, HeapTuple tuple);
 static void ExecGrant_Parameter(InternalGrant *istmt);
+static void ExecGrant_Variable(InternalGrant *istmt);
 
@@ -281,6 +283,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
@@ -525,6 +530,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
@@ -630,6 +639,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
@@ -820,6 +832,19 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
@@ -909,6 +934,32 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
@@ -1068,6 +1119,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
@@ -1259,6 +1314,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
 
+		case OBJECT_VARIABLE:
@@ -1490,6 +1551,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
@@ -1550,6 +1614,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
@@ -2584,6 +2651,129 @@ ExecGrant_Parameter(InternalGrant *istmt)
 
+static void
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -2789,6 +2979,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;
@@ -2900,6 +3093,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;
@@ -3048,6 +3244,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);
@@ -4178,6 +4376,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 			defaclobjtype = DEFACLOBJ_NAMESPACE;
 			break;
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:

 			/*
 
 			/*
index 56dc995713..f581580de4 100644
@@ -6395,6 +6396,8 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -6456,6 +6459,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
@@ -12691,6 +12733,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 				/*
index 63b4baaed9..90e13ffe68 100644

 /*

 

@@ -5029,6 +5035,69 @@ create_extension_opt_item:
 				}
 		;
 
@@ -6806,6 +6875,7 @@ object_type_any_name:
 
 /*
@@ -7682,6 +7752,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
@@ -7727,6 +7805,14 @@ privilege_target:

@@ -7924,6 +8010,7 @@ defacl_privilege_target:

@@ -9706,6 +9793,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
 opt_column: COLUMN
@@ -10067,6 +10173,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
 
@@ -10346,6 +10471,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -17005,6 +17138,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -17616,6 +17751,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
 
 			if (isAgg)

index f743cd548c..11edf13ab8 100644

@@ -3834,6 +3835,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 	cxt.indexes = NIL;
 	cxt.triggers = NIL;
 	cxt.grants = NIL;
+	cxt.variables = NIL;
 
 	/*
 	 * Run through each schema element in the schema element list. Separate
@@ -3902,6 +3904,15 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 				cxt.grants = lappend(cxt.grants, element);
 				break;
 
@@ -3915,6 +3926,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 
 	return result;
 }

index bba953cd6e..f75ab56f44 100644
@@ -816,6 +816,10 @@ acldefault(ObjectType objtype, Oid ownerId)
@@ -913,6 +917,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
 
 	return subname;
 }
+

 
 
 bki_data = [

 
 
index 719599649a..cb05043b39 100644
 
index 34bc640ff2..21e7e16154 100644
@@ -1936,6 +1936,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -3059,6 +3060,25 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+
index 3fd56ceccd..44f4e5c1ed 100644

index 69eb437376..beb95683e4 100644
@@ -169,6 +169,7 @@ typedef struct ArrayType Acl;

 
 
index 60c71d05fe..6161b16601 100644
@@ -2635,6 +2638,8 @@ SupportRequestRows

---

From: Erik Rijkers <[email-masked]>
Date: Wed, 14 Dec 2022 06:20:22 +0100

> fresh rebase

typo alert:

v20221214-0003-LET-command.patch contains

errmsg("target session varible is of type %s"

('varible' should be 'variable')

Erik

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 15 Dec 2022 06:06:51 +0100

Hi

fix regress tests after
https://github.com/postgres/postgres/commit/2af33369e7940770cb81c0a9b7d3ec874ee8cb22

Regards

Pavel
From 760ddf6f68c31c8cba8a3a166cc1ee78c3029e98 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

 
 # ----------

From f1c7e0fb823edf3157a15cbf48b52f9004d635f8 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

From fa50a8c14bb6814142d41ee22abf03bd74365e04 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 
index 1c7fc728c2..f240f2348a 100644

 

From 81e9d3328c28c05743762dcf310fa3edcedb8271 Mon Sep 17 00:00:00 2001

 
+/*

 

 select a from atacc1;

 
 
 -- should fail
 LET v1.x = 10;

From 9a6370edee2295041637c0b9f4c48cb82971bc45 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+     <varlistentry>

From 26a68e2a1d55f11e4af2390e701c29a5938a8018 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 

From f25b8adb6f0013e5653b2883370c66dcc8289330 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 56759f9e5a7d67e54ab0e697a01d2739d199afb2 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:45:23 +0100
Subject: [PATCH 02/10] session variables

 

 
+/*
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*

 
 			/*

+				/*

 /*

+		/*
 
 						/*
index be48886511..5e34550211 100644

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 
 	/*

 
 				/*

 

 

 

 

 

 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*

 
+/*

 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }

 

 

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

 

 
 	/*

 
 
 typedef struct Param

 
 /* GUC parameters */

 

From 36ee7a2d6906f3e311b2ef5fbf581f25b4504057 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command

 
 /*
 
+/*

 

+/*

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*

 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 
 	NodeTag		type;

 

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

From 391bbda1cb8cc5cad585cf375b1df8bbdd48eafa Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:39:37 +0100
Subject: [PATCH 01/10] catalog support for session variables

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:

 			/*
 
 			/*

 				/*

 /*

 

 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+
index 8b35968353..24997877ba 100644

 
 
 bki_data = [

 

 
 
+

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 15 Dec 2022 07:12:59 +0100

Hi

should be fixed now

Thank you for check

> Erik
From 5087e29a7866745b225884cc2d5d5efcd09a4d60 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

 
 # ----------

From ca661d0e42453eeaf59a2a011d7c8c2c035a8de4 Mon Sep 17 00:00:00 2001

 
+/*

 

 select a from atacc1;

 
 
 -- should fail
 LET v1.x = 10;

From 029452f7022433d67de33918856c9856d9c420de Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 

 

From 666a33642a5ff2bd3e5885da0f34f8064f932840 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

From 81b82546bc6bba1dd3621733609a72d7a76751c4 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 

 

 
 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+     <varlistentry>

From 02445385da8d9386a38752b0ac80cd85e0d9e52c Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 

From 8fd06497e4154dca9c0977838782d692655fd435 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From ad3cf18aebe60e2e9b7fad9a461aded2bdfcc063 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command

 
 /*
 
+/*
 
index 0000000000..bd004cb494
+					 errmsg("target session variable is of type %s"

 
 
+/*

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*

 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*

 		case DestTupleQueue:

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

 /*
 
 /*

 
 
 	NodeTag		type;

 

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

 

 
+/*
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*

 
 
 /*

 
 			/*

+				/*

 /*

+		/*
 
 						/*

+	/*

 

 

 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node

 
 	/*

 
 				/*

 

 

 

 

 

 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*

 
+/*

 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }

 

 

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

 

 
 	/*

 
 
 typedef struct Param

 
 /* GUC parameters */

 

 

 

 
+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+		case OBJECT_VARIABLE:

+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*

 /*

 
 	/*
 
+/*
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
 
+		case OCLASS_VARIABLE:
 
 				if (objname)
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:

 			/*
 
 			/*

 				/*

 /*

 

 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 	/*

 	return result;
 }

 
 	return subname;
 }
+

 
 
 bki_data = [

 

 
 
+

---

From: Dmitry Dolgov <[email-masked]>
Date: Thu, 22 Dec 2022 17:15:54 +0100

Hi,

I'm continuing review the patch slowly, and have one more issue plus one
philosophical question.

The issue have something to do with variables invalidation. Enabling
debug_discard_caches = 1 (about which I've learned from this thread) and
running this subset of the test suite:

	CREATE SCHEMA svartest;
	SET search_path = svartest;
	CREATE VARIABLE var3 AS int;

	CREATE OR REPLACE FUNCTION inc(int)
	RETURNS int AS $$
	BEGIN
	  LET svartest.var3 = COALESCE(svartest.var3 + $1, $1);
	  RETURN var3;
	END;
	$$ LANGUAGE plpgsql;

	SELECT inc(1);
	SELECT inc(1);
	SELECT inc(1);

crashes in my setup like this:

	#2  0x0000000000b432d4 in ExceptionalCondition (conditionName=0xce9b99 "n >= 0 && n < list->length", fileName=0xce9c18 "list.c", lineNumber=770) at assert.c:66
	#3  0x00000000007d3acd in list_delete_nth_cell (list=0x18ab248, n=-3388) at list.c:770
	#4  0x00000000007d3b88 in list_delete_cell (list=0x18ab248, cell=0x18dc028) at list.c:842
	#5  0x00000000006bcb52 in sync_sessionvars_all (filter_lxid=true) at session_variable.c:524
	#6  0x00000000006bd4cb in SetSessionVariable (varid=16386, value=2, isNull=false) at session_variable.c:844
	#7  0x00000000006bd617 in SetSessionVariableWithSecurityCheck (varid=16386, value=2, isNull=false) at session_variable.c:885
	#8  0x00007f763b890698 in exec_stmt_let (estate=0x7ffcc6fd5190, stmt=0x18aa920) at pl_exec.c:5030
	#9  0x00007f763b88a746 in exec_stmts (estate=0x7ffcc6fd5190, stmts=0x18aaaa0) at pl_exec.c:2116
	#10 0x00007f763b88a247 in exec_stmt_block (estate=0x7ffcc6fd5190, block=0x18aabf8) at pl_exec.c:1935
	#11 0x00007f763b889a49 in exec_toplevel_block (estate=0x7ffcc6fd5190, block=0x18aabf8) at pl_exec.c:1626
	#12 0x00007f763b8879df in plpgsql_exec_function (func=0x18781b0, fcinfo=0x18be110, simple_eval_estate=0x0, simple_eval_resowner=0x0, procedure_resowner=0x0, atomic=true) at pl_exec.c:615
	#13 0x00007f763b8a2320 in plpgsql_call_handler (fcinfo=0x18be110) at pl_handler.c:277
	#14 0x0000000000721716 in ExecInterpExpr (state=0x18bde28, econtext=0x18bd3d0, isnull=0x7ffcc6fd56d7) at execExprInterp.c:730
	#15 0x0000000000723642 in ExecInterpExprStillValid (state=0x18bde28, econtext=0x18bd3d0, isNull=0x7ffcc6fd56d7) at execExprInterp.c:1855
	#16 0x000000000077a78b in ExecEvalExprSwitchContext (state=0x18bde28, econtext=0x18bd3d0, isNull=0x7ffcc6fd56d7) at ../../../src/include/executor/executor.h:344
	#17 0x000000000077a7f4 in ExecProject (projInfo=0x18bde20) at ../../../src/include/executor/executor.h:378
	#18 0x000000000077a9dc in ExecResult (pstate=0x18bd2c0) at nodeResult.c:136
	#19 0x0000000000738ea0 in ExecProcNodeFirst (node=0x18bd2c0) at execProcnode.c:464
	#20 0x000000000072c6e3 in ExecProcNode (node=0x18bd2c0) at ../../../src/include/executor/executor.h:262
	#21 0x000000000072f426 in ExecutePlan (estate=0x18bd098, planstate=0x18bd2c0, use_parallel_mode=false, operation=CMD_SELECT, sendTuples=true, numberTuples=0, direction=ForwardScanDirection, dest=0x18b3eb8, execute_once=true) at execMain.c:1691
	#22 0x000000000072cf76 in standard_ExecutorRun (queryDesc=0x189c688, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:423
	#23 0x000000000072cdb3 in ExecutorRun (queryDesc=0x189c688, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:367
	#24 0x000000000099afdc in PortalRunSelect (portal=0x1866648, forward=true, count=0, dest=0x18b3eb8) at pquery.c:927
	#25 0x000000000099ac99 in PortalRun (portal=0x1866648, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x18b3eb8, altdest=0x18b3eb8, qc=0x7ffcc6fd5a70) at pquery.c:771
	#26 0x000000000099487d in exec_simple_query (query_string=0x17edcc8 "SELECT inc(1);") at postgres.c:1238

It seems that sync_sessionvars_all tries to remove a cell that either doesn't
belong to the xact_recheck_varids or weird in some other way:

	+>>> p l - xact_recheck_varids->elements
	$81 = -3388

The second thing I wanted to ask about is a more strategical question. Does
anyone have clear understanding where this patch is going? The thread is quite
large, and it's hard to catch up with all the details -- it would be great if
someone could summarize the current state of things, are there any outstanding
design issues or not addressed concerns?

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 22 Dec 2022 20:45:57 +0100

čt 22. 12. 2022 v 17:15 odesílatel Dmitry Dolgov <[email-masked]>
napsal:

>         #2  0x0000000000b432d4 in ExceptionalCondition
> (conditionName=0xce9b99 "n >= 0 && n < list->length", fileName=0xce9c18
> "list.c", lineNumber=770) at assert.c:66
>         #6  0x00000000006bd4cb in SetSessionVariable (varid=16386,
> value=2, isNull=false) at session_variable.c:844
>         #7  0x00000000006bd617 in SetSessionVariableWithSecurityCheck
> (varid=16386, value=2, isNull=false) at session_variable.c:885
>         #11 0x00007f763b889a49 in exec_toplevel_block
> (estate=0x7ffcc6fd5190, block=0x18aabf8) at pl_exec.c:1626
>         #12 0x00007f763b8879df in plpgsql_exec_function (func=0x18781b0,
> fcinfo=0x18be110, simple_eval_estate=0x0, simple_eval_resowner=0x0,
> procedure_resowner=0x0, atomic=true) at pl_exec.c:615
>         #14 0x0000000000721716 in ExecInterpExpr (state=0x18bde28,
> econtext=0x18bd3d0, isnull=0x7ffcc6fd56d7) at execExprInterp.c:730
>         #15 0x0000000000723642 in ExecInterpExprStillValid
> (state=0x18bde28, econtext=0x18bd3d0, isNull=0x7ffcc6fd56d7) at
> execExprInterp.c:1855
>         #16 0x000000000077a78b in ExecEvalExprSwitchContext
> (state=0x18bde28, econtext=0x18bd3d0, isNull=0x7ffcc6fd56d7) at
> ../../../src/include/executor/executor.h:344
>         #17 0x000000000077a7f4 in ExecProject (projInfo=0x18bde20) at
> ../../../src/include/executor/executor.h:378
>         #20 0x000000000072c6e3 in ExecProcNode (node=0x18bd2c0) at
> ../../../src/include/executor/executor.h:262
>         #21 0x000000000072f426 in ExecutePlan (estate=0x18bd098,
> planstate=0x18bd2c0, use_parallel_mode=false, operation=CMD_SELECT,
> sendTuples=true, numberTuples=0, direction=ForwardScanDirection,
> dest=0x18b3eb8, execute_once=true) at execMain.c:1691
>         #22 0x000000000072cf76 in standard_ExecutorRun
> (queryDesc=0x189c688, direction=ForwardScanDirection, count=0,
> execute_once=true) at execMain.c:423
>         #23 0x000000000072cdb3 in ExecutorRun (queryDesc=0x189c688,
> direction=ForwardScanDirection, count=0, execute_once=true) at
> execMain.c:367
>         #24 0x000000000099afdc in PortalRunSelect (portal=0x1866648,
> forward=true, count=0, dest=0x18b3eb8) at pquery.c:927
>         #25 0x000000000099ac99 in PortalRun (portal=0x1866648,
> count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x18b3eb8,
> altdest=0x18b3eb8, qc=0x7ffcc6fd5a70) at pquery.c:771
>         #26 0x000000000099487d in exec_simple_query
> (query_string=0x17edcc8 "SELECT inc(1);") at postgres.c:1238
>

I am able to repeat this issue. I'll look at it.

> design issues or not addressed concerns?

I hope I fixed the issues founded by Julian and Tomas. Now there is not
implemented transaction support related to values, and I plan to implement
this feature in the next stage.
It is waiting for review.

>
> From the first look it seems some major topics the discussion is evolving
> are about:
>
> * Validity of the use case. Seems to be quite convincingly addressed in
> [1] and
> [2].
>
> * Complicated logic around invalidation, concurrent create/drop etc. (I
> guess
> the issue above is falling into the same category).
>
> * Concerns that session variables could repeat some problems of temporary
> tables.
>

Why do you think so? The variable has no mvcc support - it is just stored
value with local visibility without mvcc support. There can be little bit
similar issues like with global temporary tables.

>
> Is there anything else?
>
> [1]:
> https://www.postgresql.org/message-id/CAFj8pRBT-bRQJBqkzon7tHcoFZ1byng06peZfZa0G72z46YFxg%40mail.gmail.com
> [2]:
> https://www.postgresql.org/message-id/flat/CAFj8pRBHSAHdainq5tRhN2Nns62h9-SZi0pvNq9DTe0VM6M1%3Dg%40mail.gmail.com#4faccb978d60e9b0b5d920e1a8a05bbf
>

---

From: Dmitry Dolgov <[email-masked]>
Date: Thu, 22 Dec 2022 22:23:52 +0100

> On Thu, Dec 22, 2022 at 08:45:57PM +0100, Pavel Stehule wrote:

Yeah, sorry for not being precise, I mean global temporary tables. This
is not my analysis, I've simply picked up it was mentioned a couple of
times here. The points above are not meant to serve as an objection
against the patch, but rather to figure out if there are any gaps left
to address and come up with some sort of plan with "committed" as a
final destination.

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 23 Dec 2022 08:38:43 +0100

Hi

> > > * Concerns that session variables could repeat some problems of

I am sending an updated patch, fixing the mentioned issue. Big thanks for
testing, and checking.

> to address and come up with some sort of plan with "committed" as a

There are some similarities, but there are a lot of differences too.
Handling of metadata is partially similar, but session variable is almost
the value cached in session memory. It has no statistics, it is not stored
in a file. Because there is different storage, I don't think there is some
intersection on implementation level.

Regards

Pavel
From f84db5457d6519921ac8a201ae5b36dc33905c12 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:23:54 +0200
Subject: [PATCH 10/10] documentation

 
 
index 9eedab652d..47ad1f3a91 100644
@@ -10278,6 +10278,21 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>

 

 
   <para>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+     <varlistentry>

From 9bb12bf33b73d4e0729257249322df196b204bbe Mon Sep 17 00:00:00 2001

 
+/*

 

 select a from atacc1;

index 3ddea3b683..b01d3fabbb 100644
@@ -5137,13 +5137,13 @@ LINE 1: select t2.uunique1 from
@@ -6178,7 +6178,7 @@ lateral (select * from int8_tbl t1,
 
@@ -6190,7 +6190,7 @@ LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
@@ -6227,7 +6227,7 @@ LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
@@ -6247,7 +6247,7 @@ update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1)
 
 -- should fail
 LET v1.x = 10;

From 469df05bf2bcb98653919f049dbd86da16bd434b Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:54:43 +0200
Subject: [PATCH 08/10] regress tests for session variables

 
 # ----------

From 4ab9647481a5499d2b03cc5a6d2001b162e09e75 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 22:40:13 +0200
Subject: [PATCH 06/10] enhancing psql for session variables

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

 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches("DO"))
 
+	/* DROP VARIABLE */
 
 /* PREPARE xx AS */
 
 /*

From aa8d2f801a279089f6c3f49327f627b5af9baeea Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:49:11 +0200
Subject: [PATCH 07/10] possibility to dump session variables by pg_dump

 

 
 	int			useDB;
 
+/*
+ * getVariables

 /*
 
+/*
 
 #endif							/* PG_DUMP_H */

 
index 7c3067a3f4..2cd53121a9 100644

 

From 3b3df7f6c3eeb53c99463dfd390405be05db4f63 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Tue, 5 Apr 2022 00:13:58 +0200
Subject: [PATCH 05/10] DISCARD VARIABLES command

---

 

From 8ac514c3d8b24940349a2b5392ce35daf0a81684 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Mon, 4 Apr 2022 20:32:45 +0200
Subject: [PATCH 04/10] support of LET command in PLpgSQL

 
 /*

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

+static void

+static void

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

From 6090fc781c40ac982b7bdca71a10063ae8d56046 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command

index 02e63bce32..5c38b86553 100644
 
 /*
@@ -1260,6 +1264,88 @@ AtEOSubXact_SessionVariable(bool isCommit,
 	}
 }
 
+/*
 
index 952483560f..63503ed82e 100644
@@ -64,6 +64,7 @@ backend_sources += files(

 
 
+/*

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -

 

 
 
 			/*
 
 			/*
index bf2d9d8287..3c40b8e4de 100644

 			/* okay */
 			break;
 
 			/*
 
 			/*
 
+	/*
 
 	/*
 
index e879304ae1..8f74aa2f6a 100644
 
 		case DestTupleQueue:
 
 	/* should never get here */
@@ -187,6 +191,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
@@ -232,6 +237,7 @@ NullCommand(CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
@@ -275,6 +281,7 @@ ReadyForQuery(CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index d1a7d995e1..d77fead276 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -242,6 +242,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 
 		case T_CallStmt:
 		case T_DoStmt:
+		case T_LetStmt:
 			{
 				/*
 
+		case T_LetStmt:
 
+		case T_LetStmt:
 
+		case T_LetStmt:
+			tag = CMDTAG_LET;
+			break;
+
 
 		case T_PLAssignStmt:
+		case T_LetStmt:
 			lev = LOGSTMT_ALL;
 			break;

 /*
 
 /*

 
 
 	NodeTag		type;

 

index 83d1af88b4..39ec23d9c9 100644
@@ -95,7 +95,8 @@ typedef enum
 
 /* ----------------

From 092c240c78e82fec56e40fe3bf979b7429b1b281 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:45:23 +0100
Subject: [PATCH 02/10] session variables

 src/backend/commands/session_variable.c   | 1255 +++++++++++++++++++--
 35 files changed, 2425 insertions(+), 100 deletions(-)

 
 	/* close large objects before lower-level cleanup */
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 6a908c07ee..a7158ce28a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1915,6 +1915,11 @@ find_expr_references_walker(Node *node,
 	{
 		Param	   *param = (Param *) node;

+/*
index 867911c0d3..ad47c7a86a 100644
@@ -38,6 +38,7 @@ backend_sources += files(
index 4570583082..02e63bce32 100644
 
 /*

 	/*

+/*
 
 /*

 /*
 
 /*

 
 	xact_ai->varid = varid;

 
 
 /*
@@ -139,43 +300,861 @@ unregister_session_variable_xact_action(Oid varid,
 {
 	ListCell   *l;

 
 
 /*
+	List	   *xact_recheck_varids_snapshot;
+	 * When we check the variables, the system cache can be invalidated,
+	 * and xac_recheck_varids can be enhanced. We want to iterate
+	 * over stable list, and we don't to throw invalidation messages.
+	 * The possible solution is 1. move xact_recheck_varids to aux
+	 * variable, and reset xact_recheck_varids, 2. process fields in
+	 * list in aux variable, 3. merge the content of aux variable back
+	 * to xact_recheck_varids.
+	xact_recheck_varids_snapshot = xact_recheck_varids;
+	foreach(l, xact_recheck_varids_snapshot)
+			xact_recheck_varids_snapshot =
+							foreach_delete_current(xact_recheck_varids_snapshot,
+	 * If caller ask to filter the list, some items are not processed
+	 * and we should to merge these items to xact_recheck_varids.
+		xact_recheck_varids = list_concat(xact_recheck_varids,
+										  xact_recheck_varids_snapshot);
+	list_free(xact_recheck_varids_snapshot);

 
 			/*
 
@@ -191,10 +1170,30 @@ AtPreEOXact_SessionVariable(bool isCommit)
 				elog(DEBUG1, "session variable (oid:%u) will be deleted (forced by SVAR_ON_COMMIT_DROP action)",
 					 xact_ai->varid);
 
+				/*
 
@@ -202,12 +1201,26 @@ AtPreEOXact_SessionVariable(bool isCommit)
 
 /*
@@ -220,24 +1233,130 @@ AtEOSubXact_SessionVariable(bool isCommit,
 {
 	ListCell   *cur_item;

+		/*
 
 						/*
index 957406ae69..e4463d9710 100644

+	/*

 

 

index e21e72eb87..1de18c664d 100644
 
 	/*

+	/*

+/*
 
 /*
  * fix_param_node
 
index 4cec12ab19..19c2c2096c 100644
@@ -1279,6 +1279,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,

 	/*

 
 				/*

 

 

 

 

 

 /* GUC parameters */

 /*
 
+/*
 
+	/*
 
+/*

 
+/*

 	/*
 
 /*
 
 	/*
 
 /*
 
 	return false;
 }
index 436afe1d21..19b3b304c9 100644
@@ -1513,6 +1513,16 @@ struct config_bool ConfigureNamesBool[] =

index 96966bdb3c..6f90af1f70 100644
@@ -11879,4 +11879,11 @@

 

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

 

 
 	/*

 
 
 typedef struct Param

 
 /* GUC parameters */

 

From 666d1e0837fcfadb806b4c7bcf631d9ebc03a469 Mon Sep 17 00:00:00 2001
From: "[email-masked]" <[email-masked]>
Date: Sun, 13 Nov 2022 17:39:37 +0100
Subject: [PATCH 01/10] catalog support for session variables

 

 
 
index d2597bf512..db583cdd5e 100644

+		case OBJECT_VARIABLE:
 
+static void
 
 static AclMode
 
+		case OBJECT_VARIABLE:
index 30394dccf5..6a908c07ee 100644
 
 /*
@@ -1514,6 +1518,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			DropObjectById(object);
 			break;
 
+		case OCLASS_VARIABLE:

 	/* shouldn't get here */
index 771ddad34d..0bb12d7a64 100644
@@ -32,6 +32,7 @@ backend_sources += files(
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
 
index 109bdfb33f..9d28652190 100644
 
 /*

 
 	/*
 
+/*
@@ -2495,6 +2541,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
@@ -4576,6 +4659,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 
+		case OCLASS_VARIABLE:
@@ -5683,6 +5770,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
@@ -5926,6 +6017,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;

 		case OBJECT_COLUMN:
 		case OBJECT_DATABASE:

 			/*
 
 			/*
index 4bea7b3c90..2d67d2182f 100644

 				/*

 /*

 

 
 /*

 
 opt_column: COLUMN

 
 			if (isAgg)

 
 	/*

 	return result;
 }

index d70b64a8cf..e87fc9eb8e 100644
@@ -844,6 +844,10 @@ acldefault(ObjectType objtype, Oid ownerId)
@@ -941,6 +945,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
 
 	return subname;
 }
+
index 5f17047047..42098b3ea0 100644

 
 /* flag bits for performDeletion/performMultipleDeletions: */
 #define PERFORM_DELETION_INTERNAL			0x0001	/* internal action */
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index 04a9a0978a..8f5cd43051 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -65,6 +65,7 @@ catalog_headers = [
   'pg_publication_rel.h',
   'pg_subscription.h',
   'pg_subscription_rel.h',
+  'pg_variable.h',
 ]
 
 bki_data = [

 
 
index 98d90d9338..96966bdb3c 100644
@@ -6315,6 +6315,9 @@

 
+