Subject: proposal: schema variables

From: Pavel Stehule <[email-masked]>
Date: Thu, 26 Oct 2017 09:21:24 +0200

Hi,

I propose a  new database object - a variable. The variable is persistent
object, that holds unshared session based not transactional in memory value
of any type. Like variables in any other languages. The persistence is
required for possibility to do static checks, but can be limited to session
- the variables can be temporal.

My proposal is related to session variables from Sybase, MSSQL or MySQL
(based on prefix usage @ or @@), or package variables from Oracle (access
is controlled by scope), or schema variables from DB2. Any design is coming
from different sources, traditions and has some advantages or
disadvantages. The base of my proposal is usage schema variables as session
variables for stored procedures. It should to help to people who try to
port complex projects to PostgreSQL from other databases.

The Sybase  (T-SQL) design is good for interactive work, but it is weak for
usage in stored procedures - the static check is not possible. Is not
possible to set some access rights on variables.

The ADA design (used on Oracle) based on scope is great, but our
environment is not nested. And we should to support other PL than PLpgSQL
more strongly.

There is not too much other possibilities - the variable that should be
accessed from different PL, different procedures (in time) should to live
somewhere over PL, and there is the schema only.

The variable can be created by CREATE statement:

CREATE VARIABLE public.myvar AS integer;
CREATE VARIABLE myschema.myvar AS mytype;

CREATE [TEMP] VARIABLE [IF NOT EXISTS] name AS type
  [ DEFAULT expression ] [[NOT] NULL]
  [ ON TRANSACTION END { RESET | DROP } ]
  [ { VOLATILE | STABLE } ];

It is dropped by command DROP VARIABLE  [ IF EXISTS] varname.

The access rights is controlled by usual access rights - by commands
GRANT/REVOKE. The possible rights are: READ, WRITE

The variables can be modified by SQL command SET (this is taken from
standard, and it natural)

SET varname = expression;

Unfortunately we use the SET command for different purpose. But I am
thinking so we can solve it with few tricks. The first is moving our GUC to
pg_catalog schema. We can control the strictness of SET command. In one
variant, we can detect custom GUC and allow it, in another we can disallow
a custom GUC and allow only schema variables. A new command LET can be
alternative.

The variables should be used in queries implicitly (without JOIN)

SELECT varname;

The SEARCH_PATH is used, when varname is located. The variables can be used
everywhere where query parameters are allowed.

I hope so this proposal is good enough and simple.

Comments, notes?

regards

Pavel

---

From: Nico Williams <[email-masked]>
Date: Thu, 26 Oct 2017 17:07:33 -0500

> Comments, notes?

I like it.

I would further like to move all of postgresql.conf into the database,
as much as possible, as well as pg_ident.conf and pg_hba.conf.

Variables like current_user have a sort of nesting context
functionality: calling a SECURITY DEFINER function "pushes" a new value
onto current_user, then when the function returns the new value of
current_user is "popped" and the previous value restored.

It might be nice to be able to generalize this.

Questions that then arise:

 - can one see up the stack?
 - are there permissions issues with seeing up the stack?

I recently posted proposing a feature such that SECURITY DEFINER
functions could observe the _caller_'s current_user.

Nico
-- 

-- 
Sent via pgsql-hackers mailing list ([email-masked])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 27 Oct 2017 07:08:43 +0200

Hi

> current_user is "popped" and the previous value restored.

My proposal doesn't expecting with nesting, because there is only one scope
- schema / session - but I don't think so it is necessary

current_user is a function - it is based on parser magic in Postgres. The
origin from Oracle uses the feature of ADA language. When function has no
parameters then parenthesis are optional. So current_user, current_time are
functions current_user(), current_time().

>  - are there permissions issues with seeing up the stack?

these variables are pined to schema - so there is not any relation to
stack. It is like global variables.

Theoretically we can introduce "functional" variables, where the value is
based on immediate evaluation of expression. It can be very similar to
current current_user.

> functions could observe the _caller_'s current_user.

your use case is good example - this proposed feature doesn't depend on
stack, depends on security context (security context stack) what is super
set of call stack

Regards

Pavel

---

From: Tatsuo Ishii <[email-masked]>
Date: Fri, 27 Oct 2017 14:30:11 +0900 (JST)

> I hope so this proposal is good enough and simple.

Just q quick follow up. Looks like a greate feature!

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

---

From: "Tsunakawa, Takayuki" <[email-masked]>
Date: Fri, 27 Oct 2017 05:47:29 +0000

From: [email-masked]
> [mailto:[email-masked]] On Behalf Of Pavel Stehule
> from different sources, traditions and has some advantages or disadvantages.
> The base of my proposal is usage schema variables as session variables for
> stored procedures. It should to help to people who try to port complex
> projects to PostgreSQL from other databases.

Very interesting.  I hope I could join the review and testing.

How do you think this would contribute to easing the port of Oracle PL/SQL procedures?  Would the combination of orafce and this feature promote auto-translation of PL/SQL procedures?  I'm curious what will be the major road blocks after adding the schema variable.

Regards
Takayuki Tsunakawa

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 27 Oct 2017 08:16:56 +0200

2017-10-27 7:47 GMT+02:00 Tsunakawa, Takayuki <
[email-masked]>:

> disadvantages.

you are welcome. I wrote a prototype last year based on envelope functions.
But the integration must be much more close to SQL to be some clear benefit
of this feature. So there is lot of work. I hope so I have a prototype
after this winter. It is my plan for winter.

>
> How do you think this would contribute to easing the port of Oracle PL/SQL
> procedures?  Would the combination of orafce and this feature promote
> auto-translation of PL/SQL procedures?  I'm curious what will be the major
> road blocks after adding the schema variable.
>

It depends on creativity of PL/SQL developers. Usual .. 80% application is
possible to migrate with current GUC - some work does ora2pg. But GUC is
little bit slower (not too important) and is not simple possibility to
secure it.

So work with variables will be similar like GUC, but significantly more
natural (not necessary to build wrap functions). It should be much better
when value is of some composite type. The migrations will need some
inteligence still, but less work and code will be more readable and cleaner.

I talked already about "schema pined" functions (schema private/public
objects) - but I didn't think about it more deeply. There can be special
access right to schema variables, the pined schema can be preferred before
search_path. With this feature the schema will have very similar behave
like Oracle Modules. Using different words - we can implement scope access
rights based on schemas. But it is far horizon. What is important -
proposal doesn't block any future enhancing in this case, and is consistent
with current state. In future you can work with schema private functions,
tables, variables, sequences. So variables are nothing special.

Regards

Pavel

Regards

---

From: Gilles Darold <[email-masked]>
Date: Fri, 27 Oct 2017 15:38:40 +0200

Le 26/10/2017 à 09:21, Pavel Stehule a écrit :
> Hi,
>
> I propose a  new database object - a variable. The variable is
> persistent object, that holds unshared session based not transactional
> in memory value of any type. Like variables in any other languages.
> The persistence is required for possibility to do static checks, but
> can be limited to session - the variables can be temporal.
>
> My proposal is related to session variables from Sybase, MSSQL or
> MySQL (based on prefix usage @ or @@), or package variables from
> Oracle (access is controlled by scope), or schema variables from DB2.
> Any design is coming from different sources, traditions and has some
> advantages or disadvantages. The base of my proposal is usage schema
> variables as session variables for stored procedures. It should to
> help to people who try to port complex projects to PostgreSQL from
> other databases.
>
> The Sybase  (T-SQL) design is good for interactive work, but it is
> weak for usage in stored procedures - the static check is not
> possible. Is not possible to set some access rights on variables.
>
> environment is not nested. And we should to support other PL than
> PLpgSQL more strongly.
>
> There is not too much other possibilities - the variable that should
> be accessed from different PL, different procedures (in time) should
> to live somewhere over PL, and there is the schema only.
>
> thinking so we can solve it with few tricks. The first is moving our
> GUC to pg_catalog schema. We can control the strictness of SET
> command. In one variant, we can detect custom GUC and allow it, in
> another we can disallow a custom GUC and allow only schema variables.
> A new command LET can be alternative.
>
> The SEARCH_PATH is used, when varname is located. The variables can be
> used everywhere where query parameters are allowed.
>

Great feature that will help for migration. How will you handle CONSTANT
declaration? With Oracle it is possible to declare a constant as follow:

  varname     CONSTANT INTEGER    := 500;

for a variable that can't be changed. Do you plan to add a CONSTANT or
READONLY keyword or do you want use GRANT on the object to deal with
this case?

Regards

-- 
Gilles Darold
Consultant PostgreSQL
http://dalibo.com - http://dalibo.org

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 27 Oct 2017 16:09:36 +0200

> READONLY keyword or do you want use GRANT on the object to deal with

Plpgsql  declaration supports CONSTANT

I forgot it. Thank you

Pavel

---

From: Chris Travers <[email-masked]>
Date: Sat, 28 Oct 2017 16:24:43 +0200

On Thu, Oct 26, 2017 at 9:21 AM, Pavel Stehule <[email-masked]>
wrote:

> The Sybase  (T-SQL) design is good for interactive work, but it is weak
> for usage in stored procedures - the static check is not possible. Is not

I have a question on this.  Since one can issue set commands on arbitrary
settings (and later ALTER database/role/system on settings you have created
in the current session) I am wondering how much overlap there is between a
sort of extended GUC with custom settings and variables.

Maybe it would be simpler to treat variables and GUC settings to be similar
and see what can be done to extend GUC in this way?

I mean if instead we allowed restricting SET to known settings then we
could have a CREATE SETTING command which would behave like this and then
use SET the same way across both.

In essence I am wondering if this really needs to be as separate from GUC
as you are proposing.

If done this way then:

1.  You could issue grant or revoke on GUC settings, allowing some users
but not others to set things like work_mem for their queries
2.  You could specify allowed types in custom settings.
3.  In a subsequent stage you might be able to SELECT .... INTO
setting_name FROM ....;  allowing access to setting writes based on queries.

-- 
Best Regards,
Chris Travers
Database Administrator

Tel: +49 162 9037 210 | Skype: einhverfr | www.adjust.com
Saarbrücker Straße 37a, 10405 Berlin

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 28 Oct 2017 16:56:49 +0200

Hi

2017-10-28 16:24 GMT+02:00 Chris Travers <[email-masked]>:

> Maybe it would be simpler to treat variables and GUC settings to be
> similar and see what can be done to extend GUC in this way?
>
The creating database objects and necessary infrastructure is the most
simple task of this project. I'll be more happy if there are zero
intersection because variables and GUC are designed for different purposes.
But due SET keyword the intersection there is.

When I thinking about it, I have only one, but important reason, why I
prefer design new type of database object -the GUC are stack based with
different default granularity - global, database, user, session, function.
This can be unwanted behave for variables - it can be source of hard to
detected bugs. I afraid so this behave can be too messy for usage as
variables.

@1 I have not clean opinion about it - not sure if rights are good enough -
probably some user limits can be more practical - but can be hard to choose
result when some user limits and GUC will be against
@2 With variables typed custom GUC are not necessary
@3 Why you need it? It is possible with set_config function now.

Regards

Pavel

>
>
>> regards
>>
>> Pavel
>>
>>
>>
>
>
> --
> Best Regards,
> Chris Travers
> Database Administrator
>
> Tel: +49 162 9037 210 <+49%20162%209037210> | Skype: einhverfr |
> www.adjust.com
> <https://maps.google.com/?q=Saarbr%C3%BCcker+Stra%C3%9Fe+37a,+10405+Berlin&entry=gmail&source=g>
>
>

---

From: Chris Travers <[email-masked]>
Date: Sun, 29 Oct 2017 09:51:55 +0100

On Sat, Oct 28, 2017 at 4:56 PM, Pavel Stehule <[email-masked]>
wrote:

> @1 I have not clean opinion about it - not sure if rights are good enough
> - probably some user limits can be more practical - but can be hard to
> choose result when some user limits and GUC will be against
>

I was mostly thinking that users can probably set things like work_mem and
possibly this might be a problem.

> @2 With variables typed custom GUC are not necessary

I don't know about that.  For example with the geoip2lookup extension it is
nice that you could set the preferred language for translation on a per
user basis or the mmdb path on a per-db basis.

> @3 Why you need it? It is possible with set_config function now.

Yeah you could do it safely with set_config and a CTE, but suppose I have:

with a (Id, value) as (values (1::Int, 'foo'), (2, 'bar'), (3, 'baz'))
SELECT set_config('custom_val', value) from a where id = 2;

What is the result out of this?  I would *expect* that this would probably
run set_config 3 times and filter the output.

-- 
Best Regards,
Chris Travers
Database Administrator

---

From: Hannu Krosing <[email-masked]>
Date: Sun, 29 Oct 2017 11:47:58 +0100

but you can always do

with a (id, value) as (
  values (1, 'foo'), (2, 'bar'), (3, 'baz')
)
select set_config('custom.value',(select value from a where id = 2),true);

if you are worried about the evaluation order

On 29 October 2017 at 09:51, Chris Travers <[email-masked]> wrote:

> I don't know about that.  For example with the geoip2lookup extension it
> is nice that you could set the preferred language for translation on a per

---

From: srielau <[email-masked]>
Date: Mon, 30 Oct 2017 14:42:40 -0700 (MST)

Pavel,

I wouldn't put in the DROP option.
Or at least not in that form of syntax.

By convention CREATE persists DDL and makes object definitions visible
across sessions.
DECLARE defines session private objects which cannot collide with other
sessions.

If you want variables with a short lifetime that get dropped at the end of
the transaction that by definition would imply a session private object. So
it ought to be DECLARE'd.

As far as I can see PG has been following this practice so far.

Cheers
Serge Rielau
Salesforce.com

--
Sent from: http://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 31 Oct 2017 21:33:35 +0100

Hi

> As far as I can see PG has been following this practice so far.

I am thinking so there is little bit overlap between DECLARE and CREATE
TEMP VARIABLE command. With DECLARE command, you are usually has not any
control when variable will be destroyed. For CREATE TEMP xxxx is DROP IF
EXISTS, but it should not be used.

It should be very similar to our current temporary tables, that are created
in session related temp schema.

I can imagine, so DECLARE command will be introduced as short cut for
CREATE TEMP VARIABLE, but in this moment I would not to open this topic. I
afraid of bikeshedding and I hope so CREATE TEMP VAR is anough.

Regards

Pavel

---

From: Serge Rielau <[email-masked]>
Date: Tue, 31 Oct 2017 14:08:18 -0700

Pavel, I can imagine, so DECLARE command will be introduced as short cut 
for CREATE TEMP VARIABLE, but in this moment I would not to open this 
topic. I afraid of bikeshedding and I hope so CREATE TEMP VAR is anough. 
Language is important because language stays. You choice of syntax will 
outlive your code and possibly yourself.
My 2 cents Serge

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 31 Oct 2017 22:10:43 +0100

2017-10-31 22:08 GMT+01:00 Serge Rielau <[email-masked]>:

> Language is important because language stays.
> You choice of syntax will outlive your code and possibly yourself.
>

sure. But in this moment I don't see difference between DECLARE VARIABLE
and CREATE TEMP VARIABLE different than "TEMP" keyword.

Regards

Pavel

> My 2 cents
> Serge

---

From: srielau <[email-masked]>
Date: Tue, 31 Oct 2017 14:28:37 -0700 (MST)

Pavel,

There is no
DECLARE TEMP CURSOR
or
DECLARE TEMP variable in PLpgSQL
and
CREATE TEMP TABLE has a different meaning from what I understand you
envision for variables.

But maybe I'm mistaken. Your original post did not describe the entire
syntax:

Especially the TEMP is not spelled out and how its presence affects or
doesn't ON TRANSACTION END.
So may be if you elaborate I understand where you are coming from.

---

From: Gilles Darold <[email-masked]>
Date: Tue, 31 Oct 2017 23:36:53 +0100

> So may be if you elaborate I understand where you are coming from.

I think that the TEMP keyword can be removed. If I understand well the
default scope for variable is the session, every transaction in a
session will see the same value. For the transaction level, probably the
reason of the TEMP keyword, I think the [ ON TRANSACTION END { RESET |
DROP } ] will allow to restrict the scope to this transaction level
without needing the TEMP keyword. When a variable is created in a
transaction, it is temporary if "ON TRANSACTION END DROP" is used
otherwise it will persist after the transaction end. I guess that this
is the same as using TEMP keyword?

---

From: Gilles Darold <[email-masked]>
Date: Wed, 1 Nov 2017 00:02:26 +0100

> is the same as using TEMP keyword?

I forgot to say that in the last case the DECLARE statement can be used
so I don't see the reason of this kind of "temporary" variables.

Maybe the variable object like used in DB2 and defined in document :
https://www.ibm.com/support/knowledgecenter/en/SSEPEK_11.0.0/sqlref/src/tpc/db2z_sql_createvariable.html
could be enough to cover our needs.

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 1 Nov 2017 05:15:58 +0100

> DECLARE TEMP variable in PLpgSQL

sure .. DECLARE TEMP has no sense, I talked about similarity DECLARE and
CREATE TEMP

> So may be if you elaborate I understand where you are coming from.

TEMP has same functionality (and implementation) like our temp tables - so
at session end the temp variables are destroyed, but it can be assigned to
transaction.

---

From: Serge Rielau <[email-masked]>
Date: Tue, 31 Oct 2017 22:07:59 -0700

" Although the syntax of CREATE TEMPORARY TABLE resembles that of the SQL standard, the effect is not the same. In the standard, temporary tables are defined just once and automatically exist (starting with empty contents) in every session that needs them. PostgreSQL instead requires each session to issue its own CREATE TEMPORARY TABLE command for each temporary table to be used. This allows different sessions to use the same temporary table name for different purposes, whereas the standard's approach constrains all instances of a given temporary table name to have the same table structure.”
Yeah, that’s a DECLAREd table in my book. No wonder we didn’t link up.
Cheers Serge

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 1 Nov 2017 06:56:03 +0100

2017-11-01 6:07 GMT+01:00 Serge Rielau <[email-masked]>:

> "Although the syntax of CREATE TEMPORARY TABLE resembles that of the SQL
> standard, the effect is not the same. In the standard, temporary tables are
> defined just once and automatically exist (starting with empty contents) in
> every session that needs them. PostgreSQL instead requires each session
> to issue its own CREATE TEMPORARY TABLE command for each temporary table
> to be used. This allows different sessions to use the same temporary table
> name for different purposes, whereas the standard's approach constrains all
> instances of a given temporary table name to have the same table structure.”

This is known discussion about local / global temp tables in PostgresSQL.
And ToDo point: implementation of global temp tables in Postgres.

This temporary behave is marginal part of proposal - so I can to remove it
from proposal - and later open discussion about CREATE TEMPORARY VARIABLE
versus DECLARE VARIABLE

Regards

Pavel

Serge

---

From: Mark Dilger <[email-masked]>
Date: Wed, 1 Nov 2017 11:03:28 -0700

> Comments, notes?

How would variables behave on transaction rollback?

CREATE TEMP VARIABLE myvar;
SET myvar := 1;
BEGIN;
SET myvar := 2;
COMMIT;
BEGIN;
SET myvar := 3;
ROLLBACK;
SELECT myvar;

How would variables behave when modified in a procedure
that aborts rather than returning cleanly?

mark

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 1 Nov 2017 20:19:48 +0100

> that aborts rather than returning cleanly?
The result is 3

When you create variable like you did, then there are not any relation
between variable content and transactions. Almost every where session -
package - schema variables are untransactional. It can be changed, but with
negative impact on performance - so I propose relative simply solution -
reset to default on rollback, when variables was changed in transaction -
but it is not default behave.

Variables are variables like you know from PlpgSQL. But the holder is not
the plpgsql function. The holder is a schema in this case. The variable
(meta) is permanent. The content of variable is session based
untransactional.

Regards

Pavel

---

From: Gilles Darold <[email-masked]>
Date: Wed, 1 Nov 2017 23:13:47 +0100

Le 01/11/2017 à 05:15, Pavel Stehule a écrit :
>
>
> 2017-10-31 22:28 GMT+01:00 srielau <[email-masked]
> <mailto:[email-masked]>>:
>
>     Pavel,
>
>     There is no
>     DECLARE TEMP CURSOR
>     or
> sure .. DECLARE TEMP has no sense, I talked about similarity DECLARE
> and CREATE TEMP
>
>
> TEMP has same functionality (and implementation) like our temp tables
> - so at session end the temp variables are destroyed, but it can be
> assigned to transaction.

Oh ok, I understand thanks for the precision.

---

From: Robert Haas <[email-masked]>
Date: Thu, 2 Nov 2017 18:05:54 +0530

> SET varname = expression;

Overloading SET to handle both variables and GUCs seems likely to
create problems, possibly including security problems.  For example,
maybe a security-definer function could leave behind variables to
trick the calling code into failing to set GUCs that it intended to
set.  Or maybe creating a variable at the wrong time will just break
things randomly.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

---

From: Craig Ringer <[email-masked]>
Date: Thu, 2 Nov 2017 23:07:55 +0800

On 26 October 2017 at 15:21, Pavel Stehule <[email-masked]> wrote:
> Hi,
>
> I propose a  new database object - a variable.

Didn't we have a pretty long discussion about this already in

Yeah.

https://www.postgresql.org/message-id/flat/CAMsr%2BYF0G8_FehQyFS8gSfnEer9OPsMOvpfniDJOVGQzJzHzsw%40ma[email-masked]

It'd be nice if you summarised any outcomes from that and addressed
it, rather than taking this as a new topic.

-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services

---

From: Nico Williams <[email-masked]>
Date: Thu, 2 Nov 2017 10:35:07 -0500

> set.  Or maybe creating a variable at the wrong time will just break

That's already true of GUCs, since there are no access controls on
set_config()/current_setting().

Presumably "schema variables" would really just be GUC-like and not at
all like lexically scoped variables.  And also subject to access
controls, thus an overall improvement on set_config()/current_setting().

With access controls, GUCs could become schema variables, and settings
from postgresql.conf could move into the database itself (which I think
would be nice).

Nico
--

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 2 Nov 2017 16:40:36 +0100

> from postgresql.conf could move into the database itself (which I think

I am sorry, but I don't plan it. the behave of GUC is too different than
behave of variables. But I am planning so system GUC can be "moved" to
pg_catalog to be possibility to specify any object exactly.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 2 Nov 2017 16:42:20 +0100

2017-11-02 16:07 GMT+01:00 Craig Ringer <[email-masked]>:

> https://www.postgresql.org/message-id/flat/CAMsr%2BYF0G8_
> FehQyFS8gSfnEer9OPsMOvpfniDJOVGQzJzHzsw%40mail.gmail.com#CAMsr+YF0G8_
> [email-masked]
>

I am sorry. This thread follow mentioned and I started with small
recapitulation.

Regards

Pavel

---

From: Tom Lane <[email-masked]>
Date: Thu, 02 Nov 2017 11:48:44 -0400

> from postgresql.conf could move into the database itself (which I think

People re-propose some variant of that every so often, but it never works,
because it ignores the fact that some of the GUCs' values are needed
before you can access system catalogs at all, or in places where relying
on system catalog access would be a bad idea.

Sure, we could have two completely different configuration mechanisms
so that some of the variables could be "inside the database", but that
doesn't seem like a net improvement to me.  The point of the Grand Unified
Configuration mechanism was to be unified, after all.

I'm on board with having a totally different mechanism for session
variables.  The fact that people have been abusing GUC to store
user-defined variables doesn't make it a good way to do that.

			regards, tom lane

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 2 Nov 2017 16:49:20 +0100

> set.  Or maybe creating a variable at the wrong time will just break

The syntax CREATE OR REPLACE FUNCTION xxx $$ ... $$ SET GUC=, ... is always
related only to GUC. So there should not be any security risk.

It is another reason why GUC and variables should be separated.

I know so there is risk of possibility of collision. There are two
possibilities

a) use different keyword - but it is out of SQL/PSM and out of another
databases.

b) detect possible collision and raise error when assignment is ambiguous.
I am thinking about similar solution used in plpgsql, where is a
possibility of collision between SQL identifier and plpgsql variable.

Regards

Pavel

---

From: Robert Haas <[email-masked]>
Date: Thu, 2 Nov 2017 22:51:54 +0530

> set_config()/current_setting().

No, it isn't.  Right now, SET always refers to a GUC, never a
variable, so there's no possibility of getting confused about whether
it's intending to change a GUC or an eponymous variable.  Once you
make SET able to change either one of two different kinds of objects,
then that possibility does exist.

---

From: Nico Williams <[email-masked]>
Date: Thu, 2 Nov 2017 13:52:39 -0500

> on system catalog access would be a bad idea.

ISTM that it should be possible to break the chicken-egg issue by having
the config variables stored in such a way that knowing only the pgdata
directory path should suffice to find them.  That's effectively the case
already in that postgresql.conf is found... there.

One could do probably this as a PoC entirely as a SQL-coded VIEW that
reads and writes (via the adminpack module's pg_catalog.pg_file_write())
postgresql.conf (without preserving comments, or with some rules
regarding comments so that they are effectively attached to params).

Nico
--

---

From: Chris Travers <[email-masked]>
Date: Fri, 3 Nov 2017 13:58:02 +0100

Some thoughts on this.

> on system catalog access would be a bad idea.

I think the basic point one should get here is that no matter the
unification, you still have some things in the db and some things out.

I would rather look at how the GUC could be improved on a functional/use
case level before we look at the question of a technical solution.

 One major use case today would be restricting how high various users can
set something like work_mem or the like.  As it stands, there isn't really
a way to control this with any granularity.  So some of the proposals
regarding granting access to a session variable would be very handy in
granting access to a GUC variable.

> Configuration mechanism was to be unified, after all.

+1

> user-defined variables doesn't make it a good way to do that.

What about having a more clunky syntax as:

SET VARIABLE foo='bar';

Perhaps one can have a short form of:

SET VAR foo = 'bar';

vs

SET foo = 'bar'; -- GUC

-- 
Best Regards,
Chris Travers
Database Administrator

---

From: Pavel Golub <[email-masked]>
Date: Mon, 13 Nov 2017 14:15:00 +0200

Hello, Pavel.

You wrote:

PS> Hi,

PS> I propose a  new database object - a variable. The variable is
PS> persistent object, that holds unshared session based not
PS> transactional in memory value of any type. Like variables in any
PS> other languages. The persistence is required for possibility to do
PS> static checks, but can be limited to session - the variables can be temporal.

Great idea.

PS> My proposal is related to session variables from Sybase, MSSQL or
PS> MySQL (based on prefix usage @ or @@), or package variables from
PS> Oracle (access is controlled by scope), or schema variables from
PS> DB2. Any design is coming from different sources, traditions and
PS> has some advantages or disadvantages. The base of my proposal is
PS> usage schema variables as session variables for stored procedures.
PS> It should to help to people who try to port complex projects to PostgreSQL from other databases.

PS> The Sybase  (T-SQL) design is good for interactive work, but it
PS> is weak for usage in stored procedures - the static check is not
PS> possible. Is not possible to set some access rights on variables.

PS> The ADA design (used on Oracle) based on scope is great, but our
PS> environment is not nested. And we should to support other PL than PLpgSQL more strongly.

PS> There is not too much other possibilities - the variable that
PS> should be accessed from different PL, different procedures (in
PS> time) should to live somewhere over PL, and there is the schema only.

PS> The variable can be created by CREATE statement:

PS> CREATE VARIABLE public.myvar AS integer;
PS> CREATE VARIABLE myschema.myvar AS mytype;

PS> CREATE [TEMP] VARIABLE [IF NOT EXISTS] name AS type 
PS>   [ DEFAULT expression ] [[NOT] NULL]
PS>   [ ON TRANSACTION END { RESET | DROP } ]
PS>   [ { VOLATILE | STABLE } ];

PS> It is dropped by command DROP VARIABLE  [ IF EXISTS] varname.

PS> The access rights is controlled by usual access rights - by
PS> commands GRANT/REVOKE. The possible rights are: READ, WRITE

PS> The variables can be modified by SQL command SET (this is taken from standard, and it natural)

PS> SET varname = expression;

I propose LET keyword for this to distinguish GUC from variables, e.g.

LET varname = expression;

PS> Unfortunately we use the SET command for different purpose. But I
PS> am thinking so we can solve it with few tricks. The first is
PS> moving our GUC to pg_catalog schema. We can control the strictness
PS> of SET command. In one variant, we can detect custom GUC and allow
PS> it, in another we can disallow a custom GUC and allow only schema
PS> variables. A new command LET can be alternative.

PS> The variables should be used in queries implicitly (without JOIN)

PS> SELECT varname;

PS> The SEARCH_PATH is used, when varname is located. The variables
PS> can be used everywhere where query parameters are allowed. 

PS> I hope so this proposal is good enough and simple.

PS> Comments, notes?

PS> regards

PS> Pavel

-- 
With best wishes,
 Pavel                          mailto:[email-masked]

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 13 Nov 2017 13:30:58 +0100

Hi

> LET varname = expression;

 It is one possible variant. I plan to implement more variants and then
choose one.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 2 Feb 2018 23:06:44 +0100

Hi

I wrote proof concept of schema variables. The patch is not nice, but the
functionality is almost complete (for scalars only) and can be good enough
for playing with this concept.

I recap a goals (the order is random):

1. feature like PL/SQL package variables (with similar content life cycle)
2. available from any PL used by PostgreSQL, data can be shared between
different PL
3. possibility to store short life data in fast secured storage
4. possibility to pass parameters and results to/from anonymous blocks
5. session variables with possibility to process static code check
6. multiple API available from different environments - SQL commands, SQL
functions, internal functions
7. data are stored in binary form

Example:

CREATE VARIABLE public.foo AS integer;

LET foo = 10 + 20;

DO $$
declare x int = random() * 1000;
BEGIN
  RAISE NOTICE '%', foo;
  LET foo = x + 100;
END;
$$;

SELECT public.foo + 10;
SELECT * FROM data WHERE col = foo;

All implemented features are described by regress tests

Interesting note - it is running without any modification of plpgsql code.

Regards

Pavel
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 71e20f2740..fbf78e602d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1813,7 +1813,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>m</literal> = materialized view,
        <literal>c</literal> = composite type,
        <literal>f</literal> = foreign table,
-       <literal>p</literal> = partitioned table
+       <literal>p</literal> = partitioned table,
+       <literal>V</literal> = schema variable
       </entry>
      </row>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 487c7ff750..5031cd4d70 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -15742,6 +15742,82 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
 
  </sect1>
 
+ <sect1 id="functions-schemavar">
+  <title>Functions for access to schema variables</title>
+
+  <indexterm zone="functions-schemavar">
+   <primary>Functions for access to schema variables</primary>
+   <secondary>functions</secondary>
+  </indexterm>
+
+  <indexterm>
+   <primary>get_schema_variable</primary>
+  </indexterm>
+
+  <indexterm>
+   <primary>set_schema_variable</primary>
+  </indexterm>
+
+  <para>
+   These functions allow reading and writing schema variables values.
+  </para>
+
+  <table id="functions-schemavar-tab">
+   <title>Functions for access to chema variables</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Function</entry>
+      <entry>Argument Type</entry>
+      <entry>Return Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><literal><function>get_schema_variable(<parameter>variable</parameter>, <parameter>expected type</parameter>)</function></literal></entry>
+      <entry><type>regclass</type>, <type>anyelement</type></entry>
+      <entry><type>anyelement</type></entry>
+      <entry>
+       Returns value of schema variables coverted to expected type.
+      </entry>
+     </row>
+
+     <row>
+      <entry><literal><function>set_schema_variable(<parameter>variable</parameter>, <parameter>value</parameter>)</function></literal></entry>
+      <entry><type>regclass</type>, <type>anyelement</type></entry>
+      <entry><type>void</type></entry>
+      <entry>
+       Set a value of schema variable. Value is converted to type of schema variable.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The usage is very simple:
+<programlisting>
+CREATE TEMP VARIABLE foo AS numeric;
+SELECT set_schema_variable('foo', 345.445);
+ set_schema_variable 
+---------------------
+ 
+(1 row)
+
+SELECT get_schema_variable('foo', null::numeric);
+
+ get_schema_variable 
+---------------------
+             345.445
+(1 row)
+</programlisting>
+  </para>
+
+ </sect1>
+
  <sect1 id="functions-info">
   <title>System Information Functions</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 22e6893211..1d34f72bdd 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -99,6 +99,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createType         SYSTEM "create_type.sgml">
 <!ENTITY createUser         SYSTEM "create_user.sgml">
 <!ENTITY createUserMapping  SYSTEM "create_user_mapping.sgml">
+<!ENTITY createVariable     SYSTEM "create_variable.sgml">
 <!ENTITY createView         SYSTEM "create_view.sgml">
 <!ENTITY deallocate         SYSTEM "deallocate.sgml">
 <!ENTITY declare            SYSTEM "declare.sgml">
@@ -147,6 +148,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropType           SYSTEM "drop_type.sgml">
 <!ENTITY dropUser           SYSTEM "drop_user.sgml">
 <!ENTITY dropUserMapping    SYSTEM "drop_user_mapping.sgml">
+<!ENTITY dropVariable       SYSTEM "drop_variable.sgml">
 <!ENTITY dropView           SYSTEM "drop_view.sgml">
 <!ENTITY end                SYSTEM "end.sgml">
 <!ENTITY execute            SYSTEM "execute.sgml">
@@ -155,6 +157,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/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
new file mode 100644
index 0000000000..037fa087f5
--- /dev/null
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -0,0 +1,144 @@
+<!--
+doc/src/sgml/ref/create_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-createvariable">
+ <indexterm zone="sql-createvariable">
+  <primary>CREATE VARIABLE</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE VARIABLE</refname>
+  <refpurpose>define a new schema secure typed variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE VARIABLE</command> creates a new schema variable.
+   These variables are memory only non transactional, but typed and
+   secure. The access is controlled by rights defined by command
+   <command>GRANT</command> and command <command>REVOKE</command>.
+  </para>
+
+  <para>
+   The schema variable is initialized to NULL value. The content of
+   variable is lost when session is destroyed.
+  </para>
+
+  <para>
+   The schema variable can be any scalar only.
+   type.
+  </para>
+
+  <para>
+   After a variable is created, you use the special functions
+   <function>get_schema_variables</function>, <function>set_schema_variables</function>.
+   type.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF NOT EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if a relation with the same name already exists.
+      A notice is issued in this case. Note that there is no guarantee that
+      the existing relation is anything like the variable that would have
+      been created - it might not even be a variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the variable to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">data_type</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the data type ofvariable to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   Use <command>DROP VARIABLE</command> to remove a variable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create an integer variable <literal>var1</literal>:
+<programlisting>
+CREATE VARIABLE var1 AS integer;
+</programlisting>
+  </para>
+
+  <para>
+   Set a value of this variable:
+<programlisting>
+CREATE VARIABLE
+postgres=# select set_schema_variable('var1', 10);
+ set_schema_variable 
+---------------------
+ 
+(1 row)
+
+postgres=# select get_schema_variable('var1', null::numeric);
+ get_schema_variable 
+---------------------
+                  10
+(1 row)
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE VARIABLE</command> is PostgreSQL feature
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-dropvariable"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml
index 6b909b7232..b348c02e0b 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,15 @@ DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>VARIABLES</literal></term>
+    <listitem>
+     <para>
+      Releases content of all schema variables in current session.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ALL</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml
new file mode 100644
index 0000000000..f6c2e46476
--- /dev/null
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -0,0 +1,89 @@
+<!--
+doc/src/sgml/ref/drop_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-dropvariable">
+ <indexterm zone="sql-dropvariable">
+  <primary>DROP VARIABLE</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP VARIABLE</refname>
+  <refpurpose>remove a schema variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP VARIABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [, ...] [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP VARIABLE</command> removes schema variable.
+   A variable can only be dropped by its owner or a superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the variable does not exist. A notice is issued
+      in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of a schema variable.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To remove the schema variable <literal>var1</literal>:
+
+<programlisting>
+DROP VARIABLE var1;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>DROP VARIABLE</command> is proprietary PostgreSQL command.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createvariable"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index ff64c7a3ba..7dde54ce0f 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -79,6 +79,12 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
     ON TYPE <replaceable>type_name</replaceable> [, ...]
     TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
 
+GRANT { { SELECT | UPDATE }
+    [, ...] | ALL [ PRIVILEGES ] }
+    ON { VARIABLE <replaceable class="parameter">variable_name</replaceable> [, ...]
+         | ALL VARIABLES IN SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...] }
+    TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
+
 <phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase>
 
     [ GROUP ] <replaceable class="parameter">role_name</replaceable>
diff --git a/doc/src/sgml/ref/let.sgml b/doc/src/sgml/ref/let.sgml
new file mode 100644
index 0000000000..b040b5e1fe
--- /dev/null
+++ b/doc/src/sgml/ref/let.sgml
@@ -0,0 +1,88 @@
+<!--
+doc/src/sgml/ref/let.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-let">
+ <indexterm zone="sql-let">
+  <primary>LET</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>LET</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>LET</refname>
+  <refpurpose>change a schema variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+LET <replaceable class="parameter">schema_variable</replaceable> = <replaceable class="parameter">sql_expression</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   The <command>LET</command> command sets specified schema variable.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>schema_variable</literal></term>
+    <listitem>
+     <para>
+      Specifies that the name of schema variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>sql expression</literal></term>
+    <listitem>
+     <para>
+      Any SQL expression.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+
+  <para>
+   Example:
+<programlisting>
+CREATE VARIABLE myvar AS integer;
+LET myvar = 10;
+LET myvar = (SELECT sum(val) FROM tab);
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <literal>LET</literal> extends syntax defined in the SQL
+   standard. The standard knows <literal>SET</literal> command,
+   that is used for different purpouse in PostgreSQL.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createvariable"/></member>
+   <member><xref linkend="sql-dropvariable"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 7018202f14..73778f01f9 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -108,6 +108,14 @@ REVOKE [ GRANT OPTION FOR ]
 REVOKE [ ADMIN OPTION FOR ]
     <replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable class="parameter">role_name</replaceable> [, ...]
     [ CASCADE | RESTRICT ]
+
+REVOKE [ GRANT OPTION FOR ]
+    { { SELECT | UPDATE }
+    [, ...] | ALL [ PRIVILEGES ] }
+    ON { VARIABLE <replaceable class="parameter">variable_name</replaceable> [, ...]
+         | ALL VARIABLES IN SCHEMA <replaceable>schema_name</replaceable> [, ...] }
+    FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
+    [ CASCADE | RESTRICT ]
 </synopsis>
  </refsynopsisdiv>
 
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index d27fb414f7..b3f9fff511 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -127,6 +127,7 @@
    &createType;
    &createUser;
    &createUserMapping;
+   &createVariable;
    &createView;
    &deallocate;
    &declare;
@@ -175,6 +176,7 @@
    &dropType;
    &dropUser;
    &dropUserMapping;
+   &dropVariable;
    &dropView;
    &end;
    &execute;
@@ -183,6 +185,7 @@
    &grant;
    &importForeignSchema;
    &insert;
+   &let;
    &listen;
    &load;
    &lock;
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 1156627b9e..268534ea87 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -284,6 +284,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
 		case OBJECT_TYPE:
 			whole_mask = ACL_ALL_RIGHTS_TYPE;
 			break;
+		case OBJECT_VARIABLE:
+			whole_mask = ACL_ALL_RIGHTS_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d", objtype);
 			/* not reached, but keep compiler quiet */
@@ -506,6 +509,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
 			all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER;
 			errormsg = gettext_noop("invalid privilege type %s for foreign server");
 			break;
+		case OBJECT_VARIABLE:
+			all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			errormsg = gettext_noop("invalid privilege type %s for schema variable");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) stmt->objtype);
@@ -576,6 +583,7 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 	{
 		case OBJECT_TABLE:
 		case OBJECT_SEQUENCE:
+		case OBJECT_VARIABLE:
 			ExecGrant_Relation(istmt);
 			break;
 		case OBJECT_DATABASE:
@@ -645,6 +653,7 @@ objectNamesToOids(ObjectType objtype, List *objnames)
 	{
 		case OBJECT_TABLE:
 		case OBJECT_SEQUENCE:
+		case OBJECT_VARIABLE:
 			foreach(cell, objnames)
 			{
 				RangeVar   *relvar = (RangeVar *) lfirst(cell);
@@ -1021,6 +1030,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
 			all_privileges = ACL_ALL_RIGHTS_SCHEMA;
 			errormsg = gettext_noop("invalid privilege type %s for schema");
 			break;
+		case OBJECT_VARIABLE:
+			all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			errormsg = gettext_noop("invalid privilege type %s for schema variable");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) action->objtype);
@@ -1218,6 +1231,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);
@@ -1444,6 +1463,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",
@@ -3459,6 +3481,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("permission denied for type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("permission denied for schema variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("permission denied for view %s");
 						break;
@@ -3569,6 +3594,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("must be owner of type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("must be owner of schema variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("must be owner of view %s");
 						break;
@@ -3683,6 +3711,7 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
 				pg_attribute_aclmask(table_oid, attnum, roleid, mask, how);
 		case OBJECT_TABLE:
 		case OBJECT_SEQUENCE:
+		case OBJECT_VARIABLE:
 			return pg_class_aclmask(table_oid, roleid, mask, how);
 		case OBJECT_DATABASE:
 			return pg_database_aclmask(table_oid, roleid, mask, how);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 774c07b03a..569bae00e2 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -291,6 +291,7 @@ heap_create(const char *relname,
 	switch (relkind)
 	{
 		case RELKIND_VIEW:
+		case RELKIND_VARIABLE:
 		case RELKIND_COMPOSITE_TYPE:
 		case RELKIND_FOREIGN_TABLE:
 		case RELKIND_PARTITIONED_TABLE:
@@ -1067,7 +1068,9 @@ heap_create_with_catalog(const char *relname,
 	if (existing_relid != InvalidOid)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_TABLE),
-				 errmsg("relation \"%s\" already exists", relname)));
+				 errmsg("%s \"%s\" already exists",
+						relkind == RELKIND_VARIABLE ? "variable" : "relation",
+						relname)));
 
 	/*
 	 * Since we are going to create a rowtype as well, also check for
@@ -1150,6 +1153,10 @@ heap_create_with_catalog(const char *relname,
 				relacl = get_user_default_acl(OBJECT_SEQUENCE, ownerid,
 											  relnamespace);
 				break;
+			case RELKIND_VARIABLE:
+				relacl = get_user_default_acl(OBJECT_VARIABLE, ownerid,
+											  relnamespace);
+				break;
 			default:
 				relacl = NULL;
 				break;
@@ -1181,7 +1188,8 @@ heap_create_with_catalog(const char *relname,
 	 * Decide whether to create an array type over the relation's rowtype. We
 	 * do not create any array types for system catalogs (ie, those made
 	 * during initdb). We do not create them where the use of a relation as
-	 * such is an implementation detail: toast tables, sequences and indexes.
+	 * such is an implementation detail: toast tables, sequences, indexes and
+	 * variables.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
 							  relkind == RELKIND_VIEW ||
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 570e65affb..62479743c7 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -520,6 +520,9 @@ static const struct object_type_map
 	{
 		"sequence", OBJECT_SEQUENCE
 	},
+	{
+		"variable", OBJECT_VARIABLE
+	},
 	{
 		"toast table", -1
 	},							/* unmapped */
@@ -824,6 +827,7 @@ get_object_address(ObjectType objtype, Node *object,
 			case OBJECT_VIEW:
 			case OBJECT_MATVIEW:
 			case OBJECT_FOREIGN_TABLE:
+			case OBJECT_VARIABLE:
 				address =
 					get_relation_by_qualified_name(objtype, castNode(List, object),
 												   &relation, lockmode,
@@ -1260,6 +1264,14 @@ get_relation_by_qualified_name(ObjectType objtype, List *object,
 						 errmsg("\"%s\" is not a foreign table",
 								RelationGetRelationName(relation))));
 			break;
+		case OBJECT_VARIABLE:
+			if (relation->rd_rel->relkind != RELKIND_VARIABLE)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("\"%s\" is not a schema variable",
+								RelationGetRelationName(relation))));
+			break;
+
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			break;
@@ -1847,6 +1859,8 @@ get_object_address_defacl(List *object, bool missing_ok)
 		case DEFACLOBJ_NAMESPACE:
 			objtype_str = "schemas";
 			break;
+		case DEFACLOBJ_VARIABLE:
+			objtype_str = "variables";
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -2109,6 +2123,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 	{
 		case OBJECT_TABLE:
 		case OBJECT_SEQUENCE:
+		case OBJECT_VARIABLE:
 		case OBJECT_VIEW:
 		case OBJECT_MATVIEW:
 		case OBJECT_INDEX:
@@ -2233,6 +2248,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 		case OBJECT_INDEX:
 		case OBJECT_SEQUENCE:
 		case OBJECT_TABLE:
+		case OBJECT_VARIABLE:
 		case OBJECT_VIEW:
 		case OBJECT_MATVIEW:
 		case OBJECT_FOREIGN_TABLE:
@@ -3299,6 +3315,11 @@ getObjectDescription(const ObjectAddress *object)
 										 _("default privileges on new schemas belonging to role %s"),
 										 GetUserNameFromId(defacl->defaclrole, false));
 						break;
+					case DEFACLOBJ_VARIABLE:
+						appendStringInfo(&buffer,
+										 _("default privileges on new schema variables belonging to role %s"),
+										 GetUserNameFromId(defacl->defaclrole, false));
+						break;
 					default:
 						/* shouldn't get here */
 						appendStringInfo(&buffer,
@@ -3502,6 +3523,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
 			appendStringInfo(buffer, _("sequence %s"),
 							 relname);
 			break;
+		case RELKIND_VARIABLE:
+			appendStringInfo(buffer, _("variable %s"),
+							 relname);
+			break;
 		case RELKIND_TOASTVALUE:
 			appendStringInfo(buffer, _("toast table %s"),
 							 relname);
@@ -4830,6 +4855,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 						appendStringInfoString(&buffer,
 											   " on schemas");
 						break;
+					case DEFACLOBJ_VARIABLE:
+						appendStringInfoString(&buffer,
+											   " on schema variables");
+						break;
 				}
 
 				if (objname)
@@ -5122,6 +5151,8 @@ get_relkind_objtype(char relkind)
 			return OBJECT_INDEX;
 		case RELKIND_SEQUENCE:
 			return OBJECT_SEQUENCE;
+		case RELKIND_VARIABLE:
+			return OBJECT_VARIABLE;
 		case RELKIND_VIEW:
 			return OBJECT_VIEW;
 		case RELKIND_MATVIEW:
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 4a6c99e090..5747272c9a 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -18,7 +18,7 @@ OBJS = amcmds.o aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	policy.o portalcmds.o prepare.o proclang.o publicationcmds.o \
-	schemacmds.o seclabel.o sequence.o statscmds.o subscriptioncmds.o \
+	schemacmds.o schemavar.o seclabel.o sequence.o statscmds.o subscriptioncmds.o \
 	tablecmds.o tablespace.o trigger.o tsearchcmds.o typecmds.o user.o \
 	vacuum.o vacuumlazy.o variable.o view.o
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 04a24c6082..20d1483a4e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1485,6 +1485,9 @@ BeginCopy(ParseState *pstate,
 
 		Assert(query->utilityStmt == NULL);
 
+		/* Don't expect LET stmt here, is not possible to do write it */
+		Assert(query->commandType != CMD_LET);
+
 		/*
 		 * Similarly the grammar doesn't enforce the presence of a RETURNING
 		 * clause, but this is required here.
diff --git a/src/backend/commands/discard.c b/src/backend/commands/discard.c
index 353ec990af..33db47e634 100644
--- a/src/backend/commands/discard.c
+++ b/src/backend/commands/discard.c
@@ -18,6 +18,7 @@
 #include "commands/async.h"
 #include "commands/discard.h"
 #include "commands/prepare.h"
+#include "commands/schemavar.h"
 #include "commands/sequence.h"
 #include "utils/guc.h"
 #include "utils/portal.h"
@@ -25,7 +26,7 @@
 static void DiscardAll(bool isTopLevel);
 
 /*
- * DISCARD { ALL | SEQUENCES | TEMP | PLANS }
+ * DISCARD { ALL | SEQUENCES | TEMP | PLANS | VARIABLES}
  */
 void
 DiscardCommand(DiscardStmt *stmt, bool isTopLevel)
@@ -48,6 +49,10 @@ DiscardCommand(DiscardStmt *stmt, bool isTopLevel)
 			ResetTempTableNamespace();
 			break;
 
+		case DISCARD_VARIABLES:
+			ResetSchemaVariablesCache();
+			break;
+
 		default:
 			elog(ERROR, "unrecognized DISCARD target: %d", stmt->target);
 	}
@@ -75,4 +80,5 @@ DiscardAll(bool isTopLevel)
 	ResetPlanCache();
 	ResetTempTableNamespace();
 	ResetSequenceCaches();
+	ResetSchemaVariablesCache();
 }
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 549c7ea51d..c8e2b822e1 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -126,6 +126,7 @@ static event_trigger_support_data event_trigger_support[] = {
 	{"TEXT SEARCH TEMPLATE", true},
 	{"TYPE", true},
 	{"USER MAPPING", true},
+	{"VARIABLE", true},
 	{"VIEW", true},
 	{NULL, false}
 };
@@ -1124,6 +1125,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_TYPE:
 		case OBJECT_USER_MAPPING:
+		case OBJECT_VARIABLE:
 		case OBJECT_VIEW:
 			return true;
 
@@ -2222,6 +2224,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:
@@ -2304,6 +2308,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 			return "TABLESPACES";
 		case OBJECT_TYPE:
 			return "TYPES";
+		case OBJECT_VARIABLE:
+			return "VARIABLES";
 		/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 41cd47e8bc..11c8257fca 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -893,6 +893,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 				case CMD_DELETE:
 					pname = operation = "Delete";
 					break;
+				case CMD_LET:
+					pname = operation = "Let";
+					break;
 				default:
 					pname = "???";
 					break;
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index b945b1556a..a69471e926 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -151,6 +151,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString,
 		case CMD_INSERT:
 		case CMD_UPDATE:
 		case CMD_DELETE:
+		case CMD_LET:
 			/* OK */
 			break;
 		default:
diff --git a/src/backend/commands/schemavar.c b/src/backend/commands/schemavar.c
new file mode 100644
index 0000000000..cb803fab0c
--- /dev/null
+++ b/src/backend/commands/schemavar.c
@@ -0,0 +1,663 @@
+/*-------------------------------------------------------------------------
+ *
+ * schemavar.c
+ *	  PostgreSQL session variable support code.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/schemavar.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "miscadmin.h"
+
+#include "access/htup_details.h"
+#include "access/xact.h"
+#include "catalog/objectaddress.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
+#include "commands/schemavar.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_type.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/hsearch.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+
+/*
+ * This schema variable cache mixes the cache and storages behave. That is not
+ * correct and it is problematic, when variable is removed. The own storage
+ * based on storage manager can be implemented, RelFileNode can be defined and
+ * mechanism based on PendingRelDelete struct can be used. This is a argument
+ * for implementation schema variables based on pg_class.
+ * Alternative solution can be detection of schema changes and recheck at and
+ * of transaction.
+ */
+typedef struct SchemaVarData
+{
+	Oid			varid;			/* pg_class OID of this sequence (hash key) */
+	Oid			typid;			/* OID of the data type */
+	int32		typmod;
+	int16		typlen;
+	bool		typbyval;
+	bool		isnull;
+	bool		freeval;
+	Datum		value;
+} SchemaVarData;
+
+typedef SchemaVarData *SchemaVar;
+
+static HTAB *schemavarhashtab = NULL;		/* hash table for session variables */
+static MemoryContext SchemaVarMemoryContext = NULL;
+
+static Datum datumCast(Datum value,
+						  Oid target_typid, int target_typmod,
+						  Oid source_typid, int source_typmod);
+
+static bool	first_time = true;
+static bool	cache_is_valid = true;
+
+static void InvalidateSchemaVarCacheCallback(Datum arg, int cacheid, uint32 hashvalue);
+
+/* just mark cache to recheck */
+static void
+InvalidateSchemaVarCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+	/*
+	 * because this cache holds values of schema variables, then
+	 * the content cannot be removed in this momemt. We should to
+	 * wait on transaction end.
+	 */
+	cache_is_valid = false;
+}
+
+/*
+ * Wait on commit or rollback and clean values that miss entry in system
+ * catalog. It is temporary solution (although it is working). Storage manager
+ * based solution will be better, but it is not necessary for this PoC.
+ *
+ * removes uncommitted or dropped schema variables, so event can be ignored.
+ */
+static void
+recheck_schema_variables(XactEvent event, void *arg)
+{
+	HASH_SEQ_STATUS status;
+	SchemaVar		var;
+
+	if (cache_is_valid || schemavarhashtab == NULL || !IsTransactionState())
+		return;
+
+	hash_seq_init(&status, schemavarhashtab);
+
+	while ((var = (SchemaVar) hash_seq_search(&status)) != NULL)
+	{
+		HeapTuple	tp = InvalidOid;
+
+		tp = SearchSysCache1(RELOID, ObjectIdGetDatum(var->varid));
+		if (!HeapTupleIsValid(tp))
+		{
+			elog(DEBUG1, "variable %d is removed from cache", var->varid);
+
+			if (var->freeval)
+			{
+				pfree(DatumGetPointer(var->value));
+				var->freeval = false;
+			}
+
+			if (hash_search(schemavarhashtab,
+								(void *) &var->varid,
+								HASH_REMOVE,
+								NULL) == NULL)
+				elog(ERROR, "hash table corrupted");
+		}
+		else
+			ReleaseSysCache(tp);
+	}
+	cache_is_valid = true;
+}
+
+/*
+ * DefineSessionVariable
+ *				Creates a new variable related relation
+ */
+ObjectAddress
+DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt *var)
+{
+	CreateStmt *stmt = makeNode(CreateStmt);
+	Oid			typoid;
+	Oid			varoid;
+	ObjectAddress address;
+
+	/*
+	 * If if_not_exists was given and a relation with the same name already
+	 * exists, bail out. (Note: we needn't check this when not if_not_exists,
+	 * because DefineRelation will complain anyway.)
+	 */
+	if (var->if_not_exists)
+	{
+		RangeVarGetAndCheckCreationNamespace(var->variable, NoLock, &varoid);
+		if (OidIsValid(varoid))
+		{
+			ereport(NOTICE,
+					(errcode(ERRCODE_DUPLICATE_TABLE),
+					 errmsg("variable \"%s\" already exists, skipping",
+							var->variable->relname)));
+			return InvalidObjectAddress;
+		}
+	}
+
+	typoid = LookupTypeNameOid(pstate, var->typeName, false);
+
+	/*
+	 * Don't allow composite types and arrays. The left expression of
+	 * LET statement is simple in this moment (don't allow record field
+	 * or array field specification). Without this support we should
+	 * not to support non scalars ever.
+	 */
+	if (type_is_rowtype(typoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Composite types are not allowed as variable type.")));
+
+	if (get_base_element_type(typoid) != InvalidOid)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Schema variables cannot be a array.")));
+
+	if (get_typtype(typoid) == TYPTYPE_PSEUDO)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("variable cannot be %s",
+						format_type_be(varoid))));
+
+	stmt->tableElts = NIL;
+	stmt->relation = var->variable;
+	stmt->inhRelations = NIL;
+	stmt->constraints = NIL;
+	stmt->options = NIL;
+	stmt->oncommit = ONCOMMIT_NOOP;
+	stmt->tablespacename = NULL;
+	stmt->if_not_exists = var->if_not_exists;
+
+	/*
+	 * Use reloftype attribute. This attribute should be composite type for
+	 * tables, but there are no reason to apply this rule for variables. Can
+	 * be changed later with composite type support. In this moment I don't
+	 * play with it, because I would not allow queries like:
+	 * SELECT schemavar FROM schemavar, because there is semantic colission
+	 * with SELECT schemavar. Users expects composite value (one attribute)
+	 * from first query, but scalar from second query. This schisma can be
+	 * solved by disallowing SELECT . FROM schemavar for scalar variables.
+	 *
+	 * On second hand - without additional fields, just with reloftype is
+	 * not possible to store typmod. So all variables can be typmod less.
+	 * Is not possible to store default expressions. So final design should
+	 * be based on aux composite types for scalar variables.
+	 *
+	 * Theoretically, there can be used a reltype and reloftype together.
+	 * reloftype will be scalar, and reltype will be composite one field
+	 * row type. When reloftype = reltype, then schema variable is based
+	 * on composite type, else schema variable is of scalar type.
+	 */
+	stmt->ofTypename = var->typeName;
+
+	address = DefineRelation(stmt, RELKIND_VARIABLE, InvalidOid, NULL, NULL);
+	Assert(address.objectId != InvalidOid);
+
+	return address;
+}
+
+/*
+ * Implementation of schemavar cache. It is question if it should be in this place, or
+ * it should be storage related or cache related place? But for this moment (PoC) it
+ * can be here. Cache is implemented as hash table with own memory context.
+ */
+
+/*
+ * Create the hash table for storing schema variables
+ */
+static void
+create_schemavar_hashtable(void)
+{
+	HASHCTL		ctl;
+
+	/* set callbacks */
+	if (first_time)
+	{
+
+		CacheRegisterSyscacheCallback(RELOID,
+									  InvalidateSchemaVarCacheCallback,
+									  (Datum) 0);
+		RegisterXactCallback(recheck_schema_variables, NULL);
+
+		first_time = false;
+	}
+
+	/* needs own long life memory context */
+	if (SchemaVarMemoryContext == NULL)
+	{
+		SchemaVarMemoryContext = AllocSetContextCreate(TopMemoryContext,
+															"schema variables",
+															ALLOCSET_START_SMALL_SIZES);
+	}
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(SchemaVarData);
+	ctl.hcxt = SchemaVarMemoryContext;
+
+	schemavarhashtab = hash_create("Schema variables", 64, &ctl,
+										HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	cache_is_valid = true;
+}
+
+/*
+ * Fast drop complete content of schema variables
+ */
+void
+ResetSchemaVariablesCache(void)
+{
+	if (schemavarhashtab)
+	{
+		hash_destroy(schemavarhashtab);
+		schemavarhashtab = NULL;
+	}
+
+	if (SchemaVarMemoryContext != NULL)
+	{
+		MemoryContextReset(SchemaVarMemoryContext);
+	}
+}
+
+/*
+ * Copy datum value to schema variables cache place
+ */
+static void
+SetValue(SchemaVar var,
+				Datum value, bool isNull,
+						Oid	typid, int32 typmod)
+{
+	/* release previously stored value */
+	if (var->freeval)
+	{
+		pfree(DatumGetPointer(var->value));
+		var->freeval = false;
+	}
+
+	if (!isNull)
+	{
+		MemoryContext oldcxt;
+
+		/*
+		 * cast the value if conversion is necessary.
+		 * Expecting: current context is short context.
+		 *
+		 * QUESTION: how much should be this cast tolerant/strict?
+		 */
+		if (var->typid != typid || var->typmod != typmod)
+		{
+			value = datumCast(value,
+								var->typid, var->typmod,
+								typid, typmod);
+		}
+
+		var->isnull = false;
+
+		oldcxt = MemoryContextSwitchTo(SchemaVarMemoryContext);
+
+		var->value = datumCopy(value, var->typbyval, var->typlen);
+		if (var->value != value)
+			var->freeval = true;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else
+	{
+		var->value = (Datum) 0;
+		var->isnull = true;
+	}
+}
+
+/*
+ * Access functions to schema variables.
+ */
+void
+SetSchemaVariable(Oid varid, Datum value, bool isNull,
+						Oid	typid, int32 typmod,
+						int16 typlen, bool typbyval)
+{
+	SchemaVar	var;
+	bool		found;
+
+	if (schemavarhashtab == NULL)
+	{
+		/* don't init hashtable for NULL values */
+		if (isNull)
+			return;
+
+		create_schemavar_hashtable();
+	}
+
+	var = (SchemaVar) hash_search(schemavarhashtab, &varid, HASH_ENTER, &found);
+	if (!found)
+	{
+		HeapTuple	tp;
+		Form_pg_class	vartup;
+
+		var->value = (Datum) 0;
+		var->isnull = true;
+		var->freeval = false;
+
+		/* now, type info for schema variable is collected */
+		tp = SearchSysCache1(RELOID, ObjectIdGetDatum(varid));
+		if (!HeapTupleIsValid(tp))
+			elog(ERROR, "cache lookup faild for variable %u", varid);
+
+		vartup = (Form_pg_class) GETSTRUCT(tp);
+		var->typid = vartup->reloftype;
+
+		/* typmod is not saved */
+		var->typmod = -1;
+
+		ReleaseSysCache(tp);
+
+		get_typlenbyval(var->typid, &var->typlen, &var->typbyval);
+	}
+
+	SetValue(var, value, isNull, typid, typmod);
+}
+
+/*
+ * Returns variable name
+ */
+char *
+get_schemavar_name(Oid varid)
+{
+	HeapTuple		relTup;
+	Form_pg_class	relForm;
+	char	   *nspname;
+	char	   *relname;
+
+	relTup = SearchSysCache1(RELOID,
+							  ObjectIdGetDatum(varid));
+	if (!HeapTupleIsValid(relTup))
+			elog(ERROR, "cache lookup failed for schema variable %u", varid);
+	relForm = (Form_pg_class) GETSTRUCT(relTup);
+
+	/* Qualify the name if not visible in search path */
+	if (RelationIsVisible(varid))
+		nspname = NULL;
+	else
+		nspname = get_namespace_name(relForm->relnamespace);
+
+	relname = quote_qualified_identifier(nspname, NameStr(relForm->relname));
+
+	ReleaseSysCache(relTup);
+
+	return relname;
+}
+
+/*
+ * Securized versions SetSchemaVariable
+ */
+void
+SetSchemaVariableSecure(Oid varid, Datum value, bool isNull,
+						Oid	typid, int32 typmod,
+						int16 typlen, bool typbyval)
+{
+	AclResult		aclresult;
+
+	/* Check permissions */
+	aclresult = pg_class_aclcheck(varid, GetUserId(), ACL_UPDATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, get_schemavar_name(varid));
+
+	SetSchemaVariable(varid, value, isNull, typid, typmod, typlen, typbyval);
+}
+
+/*
+ * Cast datum
+ */
+static Datum
+datumCast(Datum value,
+		  Oid target_typid, int target_typmod,
+		  Oid source_typid, int source_typmod)
+{
+	CoercionPathType cpathtype;
+	Oid		cfuncid;
+	Datum	result = (Datum) 0;
+	bool	is_binary_cast = false;
+
+	if (target_typid != source_typid)
+	{
+		cpathtype = find_coercion_pathway(target_typid, source_typid,
+											COERCION_EXPLICIT,
+											&cfuncid);
+
+		if (cpathtype == COERCION_PATH_NONE)
+			elog(ERROR, "could not find cast from %s to %s",
+						format_type_be(source_typid),
+						format_type_be(target_typid));
+
+		if (cpathtype == COERCION_PATH_RELABELTYPE)
+		{
+			result = value;
+			is_binary_cast = true;
+		}
+		else if (cpathtype == COERCION_PATH_COERCEVIAIO)
+		{
+			Oid		outfunc;
+			Oid		infunc;
+			Oid		ioparam;
+			bool	isVarlena;
+			char	*str;
+
+			getTypeOutputInfo(source_typid, &outfunc, &isVarlena);
+			str = OidOutputFunctionCall(outfunc, value);
+
+			getTypeInputInfo(target_typid, &infunc, &ioparam);
+			result = OidInputFunctionCall(infunc, str, ioparam, -1);
+		}
+		else if (cpathtype == COERCION_PATH_FUNC)
+		{
+			result = OidFunctionCall3(cfuncid,
+							value,
+							Int32GetDatum(target_typmod),
+							BoolGetDatum(false));
+		}
+	}
+	else
+	{
+		result = value;
+		is_binary_cast = true;
+	}
+
+	if (target_typmod < 1 || (target_typmod == source_typmod && is_binary_cast))
+		return result;
+
+	cpathtype = find_typmod_coercion_function(target_typid, &cfuncid);
+	if (cpathtype == COERCION_PATH_FUNC)
+	{
+		result = OidFunctionCall3(cfuncid,
+											result,
+											Int32GetDatum(target_typmod),
+											BoolGetDatum(false));
+	}
+
+	return result;
+}
+
+Datum
+GetSchemaVariable(Oid varid, bool *isNull,
+						Oid	typid, int32 typmod,
+						int16 typlen, bool typbyval)
+{
+	Assert(varid != InvalidOid);
+
+	if (schemavarhashtab != NULL)
+	{
+		SchemaVar		var;
+		bool			found;
+
+		var = (SchemaVar) hash_search(schemavarhashtab,
+										  &varid, HASH_FIND, &found);
+
+		if (found && !var->isnull)
+		{
+			Datum result;
+
+			result = datumCast(var->value, typid, typmod,
+									var->typid, var->typmod);
+			*isNull = false;
+
+			if (result != var->value)
+				return result;
+			else
+				return datumCopy(result, typbyval, typlen);
+		}
+	}
+
+	/*
+	 * This implementation is simple, because default expressions
+	 * are not supported. With support of default expression, there
+	 * should be insert schema variable into cache. Not supported yet,
+	 * so do just simply work.
+	 */
+	*isNull = true;
+	return (Datum) 0;
+}
+
+/*
+ * Securized version of GetSchemaVariable
+ */
+Datum
+GetSchemaVariableSecure(Oid varid, bool *isNull,
+						Oid	typid, int32 typmod,
+						int16 typlen, bool typbyval)
+{
+	AclResult aclresult;
+
+	/* Check permissions */
+	aclresult = pg_class_aclcheck(varid, GetUserId(), ACL_SELECT);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, get_schemavar_name(varid));
+
+	return GetSchemaVariable(varid, isNull, typid, typmod, typlen, typbyval);
+}
+
+/*
+ * V1 function API
+ *
+ * void set_schema_variable(var regclass, value anyelement);
+ * anyelement get_schema_variable(var regclass, expected_type anyelement)
+ *
+ */
+Datum
+set_schema_variable(PG_FUNCTION_ARGS)
+{
+	Oid		varid;
+	Datum	value;
+	bool	isNull;
+	Oid		typid;
+	int16	typlen;
+	bool	typbyval;
+
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("null value not allowed for variable identity")));
+
+	varid = PG_GETARG_OID(0);
+
+	if (!PG_ARGISNULL(1))
+	{
+		value = PG_GETARG_DATUM(1);
+		isNull = false;
+	}
+	else
+	{
+		value = (Datum) 0;
+		isNull = true;
+	}
+
+	typid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (typid == InvalidOid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("could not determine input data type")));
+
+	get_typlenbyval(typid, &typlen, &typbyval);
+	SetSchemaVariableSecure(varid, value, isNull, typid, -1, typlen, typbyval);
+
+	PG_RETURN_VOID();
+}
+
+Datum
+get_schema_variable(PG_FUNCTION_ARGS)
+{
+	Oid		varid;
+	Oid		typid;
+	int16	typlen;
+	bool	typbyval;
+	bool	isNull;
+	Datum	result;
+
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("null value not allowed for variable identity")));
+
+	varid = PG_GETARG_OID(0);
+
+	typid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (typid == InvalidOid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("could not determine input data type")));
+
+	get_typlenbyval(typid, &typlen, &typbyval);
+	result = GetSchemaVariableSecure(varid, &isNull, typid, -1, typlen, typbyval);
+
+	if (isNull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/*
+ * Results:
+ *=
+ 
+1. The schema variables are similar to temporary tables - but the data are not saved
+   in 8KB blocks, so new storage for Pg storage manager should be created.
+
+2. We should to work with typmod, so pg_attribute entry should be created anytime.
+
+3. A risk of collisions of variable and table name will be reduced, when variables
+   and tables cannot to have same name.
+
+4. If schema variables are pg_class based, then some current syntax has sense
+
+    INSERT INTO schema.variable SELECT xxx
+    maybe (but it is not consistent with PostgreSQL SQL, but consistent with PLpgSQL):
+    SELECT * INTO schema.variable FROM xxx
+
+5. LET cmd can be implemented as CMD (like INSERT, UPDATE, DELETE) or Utility (like
+   CreateTableAsSelect). Prefer first option, because there can be prepared, can be
+   used together with EXPLAIN, etc.
+
+   Expected form:
+     LET foo = (SELECT id FROM boo WHERE some = 'hello');
+
+   so possibility to run EXPLAIN LET .. has enough benefit
+*/
\ No newline at end of file
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c6eb3ebacf..53ea890517 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/schemavar.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -723,6 +724,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				Param	   *param = (Param *) node;
 				ParamListInfo params;
+				AclResult	aclresult;
 
 				switch (param->paramkind)
 				{
@@ -730,6 +732,23 @@ ExecInitExprRec(Expr *node, ExprState *state,
 						scratch.opcode = EEOP_PARAM_EXEC;
 						scratch.d.param.paramid = param->paramid;
 						scratch.d.param.paramtype = param->paramtype;
+						ExprEvalPushStep(state, &scratch);
+						break;
+					case PARAM_SCHEMA_VARIABLE:
+						/* Check permission to read schema variable */
+						aclresult = pg_class_aclcheck(param->paramid, GetUserId(), ACL_SELECT);
+						if (aclresult != ACLCHECK_OK)
+							aclcheck_error(aclresult, OBJECT_VARIABLE, get_schemavar_name(param->paramid));
+
+						scratch.opcode = EEOP_PARAM_SCHEMA_VARIABLE;
+						scratch.d.param.paramid = param->paramid;
+						scratch.d.param.paramtype = param->paramtype;
+						scratch.d.param.paramtypmod = param->paramtypmod;
+
+						get_typlenbyval(param->paramtype,
+												&scratch.d.param.paramtyplen,
+												&scratch.d.param.paramtypbyval);
+
 						ExprEvalPushStep(state, &scratch);
 						break;
 					case PARAM_EXTERN:
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f646fd9c51..7a3b283039 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -59,6 +59,7 @@
 #include "access/tuptoaster.h"
 #include "catalog/pg_type.h"
 #include "commands/sequence.h"
+#include "commands/schemavar.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -350,6 +351,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_PARAM_EXEC,
 		&&CASE_EEOP_PARAM_EXTERN,
 		&&CASE_EEOP_PARAM_CALLBACK,
+		&&CASE_EEOP_PARAM_SCHEMA_VARIABLE,
 		&&CASE_EEOP_CASE_TESTVAL,
 		&&CASE_EEOP_MAKE_READONLY,
 		&&CASE_EEOP_IOCOERCE,
@@ -1031,6 +1033,23 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_PARAM_SCHEMA_VARIABLE)
+		{
+			Datum	d;
+			bool	isnull;
+
+			d = GetSchemaVariable(op->d.param.paramid, &isnull,
+													  op->d.param.paramtype,
+													  -1,
+													  op->d.param.paramtyplen,
+													  op->d.param.paramtypbyval);
+
+			*op->resvalue = d;
+			*op->resnull = isnull;
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_PARAM_CALLBACK)
 		{
 			/* allow an extension module to supply a PARAM_EXTERN value */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 410921cc40..a1ae732ae5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -210,6 +210,7 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	switch (queryDesc->operation)
 	{
 		case CMD_SELECT:
+		case CMD_LET:
 
 			/*
 			 * SELECT FOR [KEY] UPDATE/SHARE and modifying CTEs need to mark
@@ -1119,6 +1120,36 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
 					 errmsg("cannot change TOAST relation \"%s\"",
 							RelationGetRelationName(resultRel))));
 			break;
+		case RELKIND_VARIABLE:
+
+			/* Only LET statement is allowed */
+			if (operation != CMD_LET)
+			{
+				switch (operation)
+				{
+					case CMD_INSERT:
+						ereport(ERROR,
+							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+							 errmsg("cannot insert into variable \"%s\"",
+									RelationGetRelationName(resultRel))));
+						break;
+					case CMD_UPDATE:
+						ereport(ERROR,
+							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+							 errmsg("cannot update variable \"%s\"",
+									RelationGetRelationName(resultRel))));
+						break;
+					case CMD_DELETE:
+						ereport(ERROR,
+							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+							 errmsg("cannot delete from variable \"%s\"",
+									RelationGetRelationName(resultRel))));
+					default:
+						elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+						break;
+				}
+			}
+			break;
 		case RELKIND_VIEW:
 
 			/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2a8ecbd830..f8e478aa42 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -39,6 +39,7 @@
 
 #include "access/htup_details.h"
 #include "access/xact.h"
+#include "commands/schemavar.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -68,6 +69,7 @@ static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
 static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
 						int whichplan);
 
+
 /*
  * Verify that the tuples to be produced by INSERT or UPDATE match the
  * target relation's rowtype
@@ -1568,6 +1570,81 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 }

+
+
+
+/* ----------------------------------------------------------------
+ *		ExecLet
+ *
+ *		For LET, we have to update target variable,
+ *      Returns NULL, there are not RETURNING clause.
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+ExecLet(ModifyTableState *mtstate,
+		   TupleTableSlot *slot,
+		   EState *estate,
+		   bool canSetTag)
+{
+	HeapTuple	tuple;
+	ResultRelInfo *resultRelInfo;
+	Relation	resultRelationDesc;
+	TupleDesc	tupdesc;
+	bool		isnull = true;
+	Datum		value;
+	Form_pg_attribute attr = NULL;
+	Oid			varid;
+
+	if (slot != NULL && !slot->tts_isempty)
+	{
+		tuple = slot->tts_tuple;
+		tupdesc = slot->tts_tupleDescriptor;
+
+		Assert(tupdesc != NULL);
+
+		/* should be checked before */
+		if (tupdesc->natts != 1)
+			elog(ERROR, "unexpected number of attributes");
+
+		attr = TupleDescAttr(tupdesc, 0);
+
+		if (!slot->tts_isnull[0])
+		{
+			isnull = false;
+			value = slot->tts_values[0];
+		}
+	}
+
+	/*
+	 * Now, es_result_relation_info is empty, but can be initialized
+	 * to structure of used schema variable.
+	 */
+	resultRelInfo = estate->es_result_relation_info;
+	resultRelationDesc = resultRelInfo->ri_RelationDesc;
+	varid = resultRelationDesc->rd_id;
+
+	if (!isnull)
+	{
+		/* expecting so variable and expression are equal */
+		SetSchemaVariable(varid, value, isnull,
+								attr->atttypid, -1,
+								attr->attlen, attr->attbyval);
+	}
+	else
+	{
+		SetSchemaVariable(varid, (Datum) 0, true,
+								  InvalidOid, -1, -1, false);
+	}
+
+	if (canSetTag)
+	{
+		Assert(estate->es_processed == 0);
+		(estate->es_processed)++;
+	}
+
+	return NULL;
+}
+
 /*
  * Process BEFORE EACH STATEMENT triggers
  */
@@ -1598,6 +1675,9 @@ fireBSTriggers(ModifyTableState *node)
 		case CMD_DELETE:
 			ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
 			break;
+		case CMD_LET:
+			/* there are no trigger */
+			break;
 		default:
 			elog(ERROR, "unknown operation");
 			break;
@@ -1652,6 +1732,9 @@ fireASTriggers(ModifyTableState *node)
 			ExecASDeleteTriggers(node->ps.state, resultRelInfo,
 								 node->mt_transition_capture);
 			break;
+		case CMD_LET:
+			/* variables has not triggers */
+			break;
 		default:
 			elog(ERROR, "unknown operation");
 			break;
@@ -2056,6 +2139,9 @@ ExecModifyTable(PlanState *pstate)
 								  &node->mt_epqstate, estate,
 								  NULL, true, node->canSetTag);
 				break;
+			case CMD_LET:
+				slot = ExecLet(node, slot, estate, node->canSetTag);
+				break;
 			default:
 				elog(ERROR, "unknown operation");
 				break;
@@ -2562,6 +2648,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 				break;
 			case CMD_UPDATE:
 			case CMD_DELETE:
+			case CMD_LET:
 				junk_filter_needed = true;
 				break;
 			default:
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 9fc4431b80..310bc3f2c7 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2404,6 +2404,9 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount)
 			else
 				res = SPI_OK_UPDATE;
 			break;
+		case CMD_LET:
+			res = SPI_OK_UTILITY;
+			break;
 		default:
 			return SPI_ERROR_OPUNKNOWN;
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index fd3001c493..6d57e01179 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3055,6 +3055,17 @@ _copySelectStmt(const SelectStmt *from)
 	return newnode;
 }
 
+static LetStmt *
+_copyLetStmt(const LetStmt *from)
+{
+	LetStmt *newnode = makeNode(LetStmt);
+
+	COPY_NODE_FIELD(variable);
+	COPY_NODE_FIELD(selectStmt);
+
+	return newnode;
+}
+
 static SetOperationStmt *
 _copySetOperationStmt(const SetOperationStmt *from)
 {
@@ -5090,6 +5101,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 7d2aa1a2d3..928cf63092 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1067,6 +1067,15 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	return true;
 }
 
+static bool
+_equalLetStmt(const LetStmt *a, const LetStmt *b)
+{
+	COMPARE_NODE_FIELD(variable);
+	COMPARE_NODE_FIELD(selectStmt);
+
+	return true;
+}
+
 static bool
 _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 {
@@ -3227,6 +3236,9 @@ equal(const void *a, const void *b)
 		case T_SelectStmt:
 			retval = _equalSelectStmt(a, b);
 			break;
+		case T_LetStmt:
+			retval = _equalLetStmt(a, b);
+			break;
 		case T_SetOperationStmt:
 			retval = _equalSetOperationStmt(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6c76c41ebe..8d24818c9f 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3444,6 +3444,16 @@ raw_expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_LetStmt:
+			{
+				LetStmt *stmt = (LetStmt *) node;
+
+				if (walker(stmt->variable, context))
+					return true;
+				if (walker(stmt->selectStmt, context))
+					return true;
+			}
+			break;
 		case T_A_Expr:
 			{
 				A_Expr	   *expr = (A_Expr *) node;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 89f27ce0eb..f4d8756487 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1251,12 +1251,15 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 	 * PARAM_EXEC Params listed in safe_param_ids, meaning they could be
 	 * either generated within the worker or can be computed in master and
 	 * then their value can be passed to the worker.
+	 * PARAM_SCHEMA_VARIABLE params are newer changed by workers, so they can be
+	 * safe.
 	 */
 	else if (IsA(node, Param))
 	{
 		Param	   *param = (Param *) node;
 
-		if (param->paramkind == PARAM_EXTERN)
+		if (param->paramkind == PARAM_EXTERN ||
+			param->paramkind == PARAM_SCHEMA_VARIABLE)
 			return false;
 
 		if (param->paramkind != PARAM_EXEC ||
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index e7b2bc7e73..f22eab422e 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -42,6 +42,7 @@
 #include "parser/parse_target.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
 #include "utils/rel.h"

@@ -54,6 +55,7 @@ static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
 static List *transformInsertRow(ParseState *pstate, List *exprlist,
 				   List *stmtcols, List *icolumns, List *attrnos,
 				   bool strip_indirection);
+static Query *transformLetStmt(ParseState *pstate, LetStmt *stmt);
 static OnConflictExpr *transformOnConflictClause(ParseState *pstate,
 						  OnConflictClause *onConflictClause);
 static int	count_rowexpr_columns(ParseState *pstate, Node *expr);
@@ -263,6 +265,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
 		case T_InsertStmt:
 		case T_UpdateStmt:
 		case T_DeleteStmt:
+		case T_LetStmt:
 			(void) test_raw_expression_coverage(parseTree, NULL);
 			break;
 		default:
@@ -300,6 +303,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
 			}
 			break;
 
+		case T_LetStmt:
+			result = transformLetStmt(pstate, (LetStmt *) parseTree);
+			break;
+
 			/*
 			 * Special cases
 			 */
@@ -358,6 +365,7 @@ analyze_requires_snapshot(RawStmt *parseTree)
 		case T_DeleteStmt:
 		case T_UpdateStmt:
 		case T_SelectStmt:
+		case T_LetStmt:
 			result = true;
 			break;
 
@@ -1532,6 +1540,207 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+ *	  transform an Let Statement
+ */
+static Query *
+transformLetStmt(ParseState *pstate, LetStmt *stmt)
+{
+	Query	   *qry = makeNode(Query);
+	List	   *exprList = NIL;
+	List	   *exprListCoer = NIL;
+	List	   *sub_rtable;
+	List	   *sub_namespace;
+	RangeTblEntry *rte;
+	RangeTblRef *rtr;
+	ListCell   *lc;
+	AclMode		targetPerms;
+	ParseState *sub_pstate;
+	Query	   *selectQuery;
+	int			i = 0;
+
+	Relation	rd;
+	Oid			vartypid = InvalidOid;
+
+	/* There can't be any outer WITH to worry about */
+	Assert(pstate->p_ctenamespace == NIL);
+
+	qry->commandType = CMD_LET;
+	pstate->p_is_let = true;
+
+	/*
+	 * If a non-nil rangetable/namespace was passed in, and we are doing
+	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
+	 * SELECT.  This can only happen if we are inside a CREATE RULE, and in
+	 * that case we want the rule's OLD and NEW rtable entries to appear as
+	 * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
+	 * The SELECT's joinlist is not affected however.  We must do this before
+	 * adding the target table to the INSERT's rtable.
+	 */
+	sub_rtable = pstate->p_rtable;
+	pstate->p_rtable = NIL;
+	sub_namespace = pstate->p_namespace;
+	pstate->p_namespace = NIL;
+
+	targetPerms = ACL_UPDATE;
+	qry->resultRelation = setTargetTable(pstate, stmt->variable,
+										 false, false, targetPerms);
+
+	rd = pstate->p_target_relation;
+	vartypid = rd->rd_rel->reloftype;
+
+	/*
+	 * We make the sub-pstate a child of the outer pstate so that it can
+	 * see any Param definitions supplied from above.  Since the outer
+	 * pstate's rtable and namespace are presently empty, there are no
+	 * side-effects of exposing names the sub-SELECT shouldn't be able to
+	 * see.
+	 */
+	sub_pstate = make_parsestate(pstate);
+
+	/*
+	 * Process the source SELECT.
+	 *
+	 * It is important that this be handled just like a standalone SELECT;
+	 * otherwise the behavior of SELECT within INSERT might be different
+	 * from a stand-alone SELECT. (Indeed, Postgres up through 6.5 had
+	 * bugs of just that nature...)
+	 *
+	 * The sole exception is that we prevent resolving unknown-type
+	 * outputs as TEXT.  This does not change the semantics since if the
+	 * column type matters semantically, it would have been resolved to
+	 * something else anyway.  Doing this lets us resolve such outputs as
+	 * the target column's type, which we handle below.
+	 */
+	sub_pstate->p_rtable = sub_rtable;
+	sub_pstate->p_joinexprs = NIL;	/* sub_rtable has no joins */
+	sub_pstate->p_namespace = sub_namespace;
+	sub_pstate->p_resolve_unknowns = false;
+
+	selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
+
+	free_parsestate(sub_pstate);
+
+	/* The grammar should have produced a SELECT */
+	if (!IsA(selectQuery, Query) ||
+		selectQuery->commandType != CMD_SELECT)
+		elog(ERROR, "unexpected non-SELECT command in LET ... SELECT");
+
+	/*
+	 * Make the source be a subquery in the LET's rangetable, and add
+	 * it to the LET's joinlist.
+	 */
+	rte = addRangeTableEntryForSubquery(pstate,
+										selectQuery,
+										makeAlias("*SELECT*", NIL),
+										false,
+										false);
+	rtr = makeNode(RangeTblRef);
+	/* assume new rte is at end */
+	rtr->rtindex = list_length(pstate->p_rtable);
+	Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
+	pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
+
+	/*----------
+	 * Generate an expression list for the LET that selects all the
+	 * non-resjunk columns from the subquery.  (LET's tlist must be
+	 * separate from the subquery's tlist because we may add datatype
+	 * coercions, etc.)
+	 *----------
+	 */
+	exprList = NIL;
+	foreach(lc, selectQuery->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Expr	   *expr;
+
+		if (tle->resjunk)
+			continue;
+		if (tle->expr &&
+			(IsA(tle->expr, Const) ||IsA(tle->expr, Param)) &&
+			exprType((Node *) tle->expr) == UNKNOWNOID)
+			expr = tle->expr;
+		else
+		{
+			Var		   *var = makeVarFromTargetEntry(rtr->rtindex, tle);
+
+			var->location = exprLocation((Node *) tle->expr);
+			expr = (Expr *) var;
+		}
+		exprList = lappend(exprList, expr);
+	}
+
+	/*
+	 * Because supports only scalar variables, we can only simple
+	 * transformations and checks here.
+	 */
+	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)
+	{
+		Node	   *orig_expr = (Node*) lfirst(lc);
+		Oid			exprtypid = exprType((Node *) orig_expr);
+		Expr	   *expr;
+
+		expr = (Expr *)
+			coerce_to_target_type(pstate,
+								  orig_expr, exprtypid,
+								  vartypid, -1,
+								  COERCION_ASSIGNMENT,
+								  COERCE_IMPLICIT_CAST,
+								  -1);
+
+		if (expr == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("variable \"%s\" is of type %s"
+							  " but expression is of type %s",
+							RelationGetRelationName(rd),
+							format_type_be(vartypid),
+							format_type_be(exprtypid)),
+				 errhint("You will need to rewrite or cast the expression."),
+				 parser_errposition(pstate, exprLocation((Node *) orig_expr))));
+
+		exprListCoer = lappend(exprListCoer, expr);
+	}
+
+	/*
+	 * Generate query's target list using the computed list of expressions.
+	 * Also, mark all the target columns as needing insert permissions.
+	 */
+	rte = pstate->p_target_rangetblentry;
+	qry->targetList = NIL;
+	foreach(lc, exprList)
+	{
+		Expr	   *expr = (Expr *) lfirst(lc);
+		TargetEntry *tle;
+
+		tle = makeTargetEntry(expr,
+							  i + 1,
+							  FigureColname((Node *)expr),
+							  false);
+		qry->targetList = lappend(qry->targetList, tle);
+	}
+
+	/* done building the range table and jointree */
+	qry->rtable = pstate->p_rtable;
+	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+	qry->hasSubLinks = pstate->p_hasSubLinks;
+
+	assign_query_collations(pstate, qry);
+
+	return qry;
+}
+
 /*
  * transformSetOperationStmt -
  *	  transforms a set-operations tree
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5329432f25..d2a264d1e1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -257,8 +257,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
 		CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt
 		CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
-		CreateSchemaStmt CreateSeqStmt CreateStmt CreateStatsStmt CreateTableSpaceStmt
-		CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
+		CreateSchemaStmt CreateSchemaVarStmt CreateSeqStmt CreateStmt CreateStatsStmt
+		CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
 		CreateAssertStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
 		CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
 		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
@@ -268,7 +268,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropTransformStmt
 		DropUserMappingStmt ExplainStmt FetchStmt
 		GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
-		ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
+		LetStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
 		CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
 		RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
 		RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@@ -646,7 +646,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
-	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+	LEADING LEAKPROOF LEAST LEFT LET LEVEL LIKE LIMIT LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
 	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -682,8 +682,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
 	UNTIL UPDATE USER USING
 
-	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIABLE VARIABLES
+	VARIADIC VARYING VERBOSE VERSION_P VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -873,6 +873,7 @@ stmt :
 			| CreatePLangStmt
 			| CreateSchemaStmt
 			| CreateSeqStmt
+			| CreateSchemaVarStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
 			| CreateStatsStmt
@@ -914,6 +915,7 @@ stmt :
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
+			| LetStmt
 			| LockStmt
 			| NotifyStmt
 			| PrepareStmt
@@ -1374,6 +1376,7 @@ schema_stmt:
 			CreateStmt
 			| IndexStmt
 			| CreateSeqStmt
+			| CreateSchemaVarStmt
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
@@ -1802,7 +1805,12 @@ DiscardStmt:
 					n->target = DISCARD_SEQUENCES;
 					$$ = (Node *) n;
 				}
-
+			| DISCARD VARIABLES
+				{
+					DiscardStmt *n = makeNode(DiscardStmt);
+					n->target = DISCARD_VARIABLES;
+					$$ = (Node *) n;
+				}
 		;

@@ -4267,6 +4275,34 @@ NumericOnly_list:	NumericOnly						{ $$ = list_make1($1); }
 				| NumericOnly_list ',' NumericOnly	{ $$ = lappend($1, $3); }
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY :
+ *				CREATE VARIABLE seqname [AS] type
+ *
+ *****************************************************************************/
+
+CreateSchemaVarStmt:
+			CREATE OptTemp VARIABLE qualified_name opt_as Typename
+				{
+					CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+					$4->relpersistence = $2;
+					n->variable = $4;
+					n->typeName = $6;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+			| CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename
+				{
+					CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+					$7->relpersistence = $2;
+					n->variable = $7;
+					n->typeName = $9;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
+		;
+
 /*****************************************************************************
  *
  *		QUERIES :
@@ -6315,6 +6351,7 @@ drop_type_any_name:
 			| TEXT_P SEARCH DICTIONARY				{ $$ = OBJECT_TSDICTIONARY; }
 			| TEXT_P SEARCH TEMPLATE				{ $$ = OBJECT_TSTEMPLATE; }
 			| TEXT_P SEARCH CONFIGURATION			{ $$ = OBJECT_TSCONFIGURATION; }
+			| VARIABLE								{ $$ = OBJECT_VARIABLE; }
 		;
 
 /* object types taking name_list */
@@ -6584,6 +6621,7 @@ comment_type_any_name:
 			| TEXT_P SEARCH DICTIONARY			{ $$ = OBJECT_TSDICTIONARY; }
 			| TEXT_P SEARCH PARSER				{ $$ = OBJECT_TSPARSER; }
 			| TEXT_P SEARCH TEMPLATE			{ $$ = OBJECT_TSTEMPLATE; }
+			| VARIABLE							{ $$ = OBJECT_VARIABLE; }
 		;
 
 /* object types taking name */
@@ -6722,6 +6760,7 @@ security_label_type_any_name:
 			| TABLE								{ $$ = OBJECT_TABLE; }
 			| VIEW								{ $$ = OBJECT_VIEW; }
 			| MATERIALIZED VIEW					{ $$ = OBJECT_MATVIEW; }
+			| VARIABLE							{ $$ = OBJECT_VARIABLE; }
 		;
 
 /* object types taking name */
@@ -7047,6 +7086,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;
+				}
 			| FOREIGN DATA_P WRAPPER name_list
 				{
 					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -7159,6 +7206,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;
+				}
 			| ALL FUNCTIONS IN_P SCHEMA name_list
 				{
 					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -7341,6 +7396,7 @@ defacl_privilege_target:
 			| FUNCTIONS		{ $$ = OBJECT_FUNCTION; }
 			| ROUTINES		{ $$ = OBJECT_FUNCTION; }
 			| SEQUENCES		{ $$ = OBJECT_SEQUENCE; }
+			| VARIABLES		{ $$ = OBJECT_VARIABLE; }
 			| TYPES_P		{ $$ = OBJECT_TYPE; }
 			| SCHEMAS		{ $$ = OBJECT_SCHEMA; }
 		;
@@ -10645,7 +10701,8 @@ ExplainableStmt:
 			| CreateAsStmt
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
-			| ExecuteStmt					/* by default all are $$=$1 */
+			| ExecuteStmt
+			| LetStmt					/* by default all are $$=$1 */
 		;
 
 explain_option_list:
@@ -10702,7 +10759,8 @@ PreparableStmt:
 			SelectStmt
 			| InsertStmt
 			| UpdateStmt
-			| DeleteStmt					/* by default all are $$=$1 */
+			| DeleteStmt
+			| LetStmt					/* by default all are $$=$1 */
 		;
 
 /*****************************************************************************
@@ -11101,6 +11159,30 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY:
+ *				LET STATEMENTS
+ *
+ *****************************************************************************/
+LetStmt:	LET qualified_name '=' a_expr
+				{
+					LetStmt *n = makeNode(LetStmt);
+					SelectStmt *select = makeNode(SelectStmt);
+					ResTarget	*res = makeNode(ResTarget);
+
+					res->name = NULL;
+					res->indirection = NIL;
+					res->val = (Node *) $4;
+					res->location = @4;
+					select->targetList = list_make1(res);
+					n->variable = $2;
+					n->selectStmt = (Node *) select;
+
+					$$ = (Node *) n;
+				}
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -15056,6 +15138,7 @@ unreserved_keyword:
 			| LARGE_P
 			| LAST_P
 			| LEAKPROOF
+			| LET
 			| LEVEL
 			| LISTEN
 			| LOAD
@@ -15202,6 +15285,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index b2f5e46e3b..cbf757d059 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -509,6 +509,7 @@ static Node *
 transformColumnRef(ParseState *pstate, ColumnRef *cref)
 {
 	Node	   *node = NULL;
+	Node	   *variable = NULL;
 	char	   *nspname = NULL;
 	char	   *relname = NULL;
 	char	   *colname = NULL;
@@ -749,6 +750,70 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 			break;
 	}
 
+	/*
+	 * Try to identify column ref as variable. Possible variants are
+	 *
+	 * A .. variable name
+	 * A.B .. qualified variable name
+	 */
+	switch (list_length(cref->fields))
+	{
+		case 1:
+			{
+				Node	   *field1 = (Node *) linitial(cref->fields);
+
+				if (IsA(field1, String))
+				{
+					char *varname  = strVal(field1);
+
+					/* Try to identify as an unqualified column */
+					variable = toSchemaVariable(pstate,
+													NULL, varname,
+													cref->location);
+				}
+				break;
+			}
+		case 2:
+			{
+				Node	   *field1 = (Node *) linitial(cref->fields);
+				Node	   *field2 = (Node *) lsecond(cref->fields);
+
+				if (IsA(field1, String) && IsA(field2, String))
+				{
+					char *nspname = strVal(field1);
+					char *varname = strVal(field2);
+
+					/* Try to identify as an unqualified column */
+					variable = toSchemaVariable(pstate,
+													nspname, varname,
+													cref->location);
+				}
+				break;
+			}
+		default:
+
+			/*
+			 * There can be another variants, more when composite variables
+			 * will be supported. Currently only scalars are supported, so
+			 * there are not necessary to solve other questions.
+			 *
+			 * do nothing
+			 */
+			break;
+	}
+
+	if (variable != NULL)
+	{
+		if (node != NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_AMBIGUOUS_COLUMN),
+					 errmsg("column reference \"%s\" is ambiguous",
+							NameListToString(cref->fields)),
+					 parser_errposition(pstate, cref->location)));
+
+		node = variable;
+	}
+
 	/*
 	 * Now give the PostParseColumnRefHook, if any, a chance.  We pass the
 	 * translation-so-far so that it can throw an error if it wishes in the
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 2625da5327..f7d9a0c939 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1150,6 +1150,7 @@ parserOpenTable(ParseState *pstate, const RangeVar *relation, int lockmode)
 
 	setup_parser_errposition_callback(&pcbstate, pstate, relation->location);
 	rel = heap_openrv_extended(relation, lockmode, true);
+
 	if (rel == NULL)
 	{
 		if (relation->schemaname)
@@ -1180,6 +1181,24 @@ parserOpenTable(ParseState *pstate, const RangeVar *relation, int lockmode)
 								relation->relname)));
 		}
 	}
+
+	/*
+	 * RELKIND_VARIABLE can be used only in LET command.
+	 * Probably this check can be done elsewhere, but here I
+	 * have a used relation and parse state together first time.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_VARIABLE && !pstate->p_is_let)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an schema variable",
+						RelationGetRelationName(rel))));
+
+	if (pstate->p_is_let && rel->rd_rel->relkind != RELKIND_VARIABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not an schema variable",
+						RelationGetRelationName(rel))));
+
 	cancel_parser_errposition_callback(&pcbstate);
 	return rel;
 }
@@ -3360,3 +3379,42 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Try to replace ColumnRef by Param related to variable
+ */
+Node *
+toSchemaVariable(ParseState *pstate, char *nspname, char *varname, int location)
+{
+	Oid			varid;
+	Param	   *param = NULL;
+
+	varid = RangeVarGetRelid(makeRangeVar(nspname, varname, -1), NoLock, true);
+	if (OidIsValid(varid))
+	{
+		HeapTuple	tp;
+		Form_pg_class	vartup;
+
+			/* now, type info for schema variable is collected */
+		tp = SearchSysCache1(RELOID, ObjectIdGetDatum(varid));
+		if (HeapTupleIsValid(tp))
+		{
+			vartup = (Form_pg_class) GETSTRUCT(tp);
+
+			if (vartup->relkind == RELKIND_VARIABLE)
+			{
+				param = makeNode(Param);
+				param->paramkind = PARAM_SCHEMA_VARIABLE;
+				param->paramid = varid;
+				param->paramtype = vartup->reloftype;
+				param->paramtypmod = -1;
+				param->paramcollid = get_typcollation(param->paramtype);
+				param->location = location;
+			}
+
+			ReleaseSysCache(tp);
+		}
+	}
+
+	return (Node *) param;
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1d35815fcf..49ea3b6e8b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -106,6 +106,7 @@ typedef struct
 	List	   *views;			/* CREATE VIEW items */
 	List	   *indexes;		/* CREATE INDEX items */
 	List	   *triggers;		/* CREATE TRIGGER items */
+	List	   *variables;		/* CREATE VARIABLE items */
 	List	   *grants;			/* GRANT items */
 } CreateSchemaStmtContext;
 
@@ -3178,6 +3179,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 	cxt.views = NIL;
 	cxt.indexes = NIL;
 	cxt.triggers = NIL;
+	cxt.variables = NIL;
 	cxt.grants = NIL;
 
 	/*
@@ -3243,6 +3245,14 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 				}
 				break;
 
+			case T_CreateSchemaVarStmt:
+				{
+					CreateSchemaVarStmt *elp = (CreateSchemaVarStmt *) element;
+
+					setSchemaName(cxt.schemaname, &elp->variable->schemaname);
+					cxt.variables = lappend(cxt.variables, element);
+				}
+
 			case T_GrantStmt:
 				cxt.grants = lappend(cxt.grants, element);
 				break;
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 32e3798972..9b0d84a5a5 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3337,7 +3337,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									rt_entry_relation,
 									parsetree->resultRelation, NULL);
 		}
-		else if (event == CMD_DELETE)
+		else if (event == CMD_DELETE || event == CMD_LET)
 		{
 			/* Nothing to do here */
 		}
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 66cc5c35c6..34ddb79a3d 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -193,6 +193,10 @@ ProcessQuery(PlannedStmt *plan,
 						 "DELETE " UINT64_FORMAT,
 						 queryDesc->estate->es_processed);
 				break;
+			case CMD_LET:
+				snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
+						 "LET ");
+				break;
 			default:
 				strcpy(completionTag, "???");
 				break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3abe7d6155..27a21c48da 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -47,6 +47,7 @@
 #include "commands/proclang.h"
 #include "commands/publicationcmds.h"
 #include "commands/schemacmds.h"
+#include "commands/schemavar.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
 #include "commands/subscriptioncmds.h"
@@ -112,6 +113,7 @@ CommandIsReadOnly(PlannedStmt *pstmt)
 		case CMD_DELETE:
 			return false;
 		case CMD_UTILITY:
+		case CMD_LET:
 			/* For now, treat all utility commands as read/write */
 			return false;
 		default:
@@ -177,6 +179,7 @@ check_xact_readonly(Node *parsetree)
 		case T_CreateSchemaStmt:
 		case T_CreateSeqStmt:
 		case T_CreateStmt:
+		case T_CreateSchemaVarStmt:
 		case T_CreateTableAsStmt:
 		case T_RefreshMatViewStmt:
 		case T_CreateTableSpaceStmt:
@@ -1474,6 +1477,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				address = AlterSequence(pstate, (AlterSeqStmt *) parsetree);
 				break;
 
+			case T_CreateSchemaVarStmt:
+				address = DefineSchemaVariable(pstate, (CreateSchemaVarStmt *) parsetree);
+				break;
+
 			case T_CreateTableAsStmt:
 				address = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 											queryString, params, queryEnv,
@@ -2095,6 +2102,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "SELECT";
 			break;
 
+		case T_LetStmt:
+			tag = "LET";
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -2259,6 +2270,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_INDEX:
 					tag = "DROP INDEX";
 					break;
+				case OBJECT_VARIABLE:
+					tag = "DROP VARIABLE";
+					break;
 				case OBJECT_TYPE:
 					tag = "DROP TYPE";
 					break;
@@ -2513,6 +2527,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "ALTER SEQUENCE";
 			break;
 
+		case T_CreateSchemaVarStmt:
+			tag = "CREATE VARIABLE";
+			break;
+
 		case T_DoStmt:
 			tag = "DO";
 			break;
@@ -2630,6 +2648,9 @@ CreateCommandTag(Node *parsetree)
 				case DISCARD_SEQUENCES:
 					tag = "DISCARD SEQUENCES";
 					break;
+				case DISCARD_VARIABLES:
+					tag = "DISCARD VARIABLES";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2834,6 +2855,9 @@ CreateCommandTag(Node *parsetree)
 					case CMD_DELETE:
 						tag = "DELETE";
 						break;
+					case CMD_LET:
+						tag = "LET";
+						break;
 					case CMD_UTILITY:
 						tag = CreateCommandTag(stmt->utilityStmt);
 						break;
@@ -2952,6 +2976,10 @@ GetCommandLogLevel(Node *parsetree)
 				lev = LOGSTMT_ALL;
 			break;
 
+		case T_LetStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			lev = LOGSTMT_ALL;
@@ -3405,6 +3433,7 @@ GetCommandLogLevel(Node *parsetree)
 				switch (stmt->commandType)
 				{
 					case CMD_SELECT:
+					case CMD_LET:
 						lev = LOGSTMT_ALL;
 						break;
 
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 0cfc297b65..fcd695836a 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -808,6 +808,10 @@ acldefault(ObjectType objtype, Oid ownerId)
 			world_default = ACL_USAGE;
 			owner_default = ACL_ALL_RIGHTS_TYPE;
 			break;
+		case OBJECT_VARIABLE:
+			world_default = ACL_NO_RIGHTS;
+			owner_default = ACL_ALL_RIGHTS_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			world_default = ACL_NO_RIGHTS;	/* keep compiler quiet */
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c5f5a1ca3f..ba592be4ae 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -41,6 +41,7 @@
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "commands/tablespace.h"
+#include "commands/schemavar.h"
 #include "common/keywords.h"
 #include "executor/spi.h"
 #include "funcapi.h"
@@ -379,6 +380,7 @@ static void get_update_query_targetlist_def(Query *query, List *targetList,
 								deparse_context *context,
 								RangeTblEntry *rte);
 static void get_delete_query_def(Query *query, deparse_context *context);
+static void get_let_query_def(Query *query, deparse_context *context);
 static void get_utility_query_def(Query *query, deparse_context *context);
 static void get_basic_select_query(Query *query, deparse_context *context,
 					   TupleDesc resultDesc);
@@ -4926,6 +4928,10 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
 			get_delete_query_def(query, &context);
 			break;
 
+		case CMD_LET:
+			get_let_query_def(query, &context);
+			break;
+
 		case CMD_NOTHING:
 			appendStringInfoString(buf, "NOTHING");
 			break;
@@ -6134,6 +6140,58 @@ get_insert_query_def(Query *query, deparse_context *context)
 	}
 }
 
+/* ----------
+ * get_let_query_def			- Parse back an LET parsetree
+ * ----------
+ */
+static void
+get_let_query_def(Query *query, deparse_context *context)
+{
+	StringInfo	buf = context->buf;
+	RangeTblEntry *select_rte = NULL;
+	RangeTblEntry *rte;
+	ListCell   *l;
+
+	/*
+	 * If it's an INSERT ... SELECT or multi-row VALUES, there will be a
+	 * single RTE for the SELECT or VALUES.  Plain VALUES has neither.
+	 */
+	foreach(l, query->rtable)
+	{
+		rte = (RangeTblEntry *) lfirst(l);
+
+		if (rte->rtekind == RTE_SUBQUERY)
+		{
+			if (select_rte)
+				elog(ERROR, "too many subquery RTEs in INSERT");
+			select_rte = rte;
+		}
+	}
+
+	/*
+	 * Start the query with INSERT INTO relname
+	 */
+	rte = rt_fetch(query->resultRelation, query->rtable);
+	Assert(rte->rtekind == RTE_RELATION);
+
+	if (PRETTY_INDENT(context))
+	{
+		context->indentLevel += PRETTYINDENT_STD;
+		appendStringInfoChar(buf, ' ');
+	}
+	appendStringInfo(buf, "LET %s ",
+					 generate_relation_name(rte->relid, NIL));
+
+	appendStringInfo(buf, " = ");
+
+	if (select_rte)
+	{
+		/* Add the SELECT */
+		get_query_def(select_rte->subquery, buf, NIL, NULL,
+					  context->prettyFlags, context->wrapColumn,
+					  context->indentLevel);
+	}
+}
 
 /* ----------
  * get_update_query_def			- Parse back an UPDATE parsetree
@@ -7208,6 +7266,13 @@ get_parameter(Param *param, deparse_context *context)
 	deparse_namespace *dpns;
 	ListCell   *ancestor_cell;
 
+	if (param->paramkind == PARAM_SCHEMA_VARIABLE)
+	{
+		appendStringInfo(context->buf, "%s", get_schemavar_name(param->paramid));
+
+		return;
+	}
+
 	/*
 	 * If it's a PARAM_EXEC parameter, try to locate the expression from which
 	 * the parameter was computed.  Note that failing to find a referent isn't
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 3560318749..ad0030c0dd 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -794,6 +794,7 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 			case 'i':
 			case 's':
 			case 'E':
+			case 'V':
 				success = listTables(&cmd[1], pattern, show_verbose, show_system);
 				break;
 			case 'r':
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 466a78004b..c272de2baa 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1683,6 +1683,42 @@ describeOneTableDetails(const char *schemaname,
 		retval = true;
 		goto error_return;		/* not an error, just return early */
 	}
+	else if (tableinfo.relkind == RELKIND_VARIABLE)
+	{
+		PGresult   *res = NULL;
+		printQueryOpt myopt = pset.popt;
+
+		printfPQExpBuffer(&buf,
+							  "SELECT pg_catalog.format_type(reloftype, NULL) AS \"%s\"\n"
+							  "FROM pg_catalog.pg_class\n"
+							  "WHERE oid = '%s';",
+							  gettext_noop("Type"),
+							  oid);
+
+		res = PSQLexec(buf.data);
+		if (!res)
+			goto error_return;
+
+		/* Did we get anything? */
+		if (PQntuples(res) == 0)
+		{
+			if (!pset.quiet)
+				psql_error("Did not find any variable with OID %s.\n", oid);
+			goto error_return;
+		}
+
+		printfPQExpBuffer(&title, _("Schema variable \"%s.%s\""),
+						  schemaname, relationname);
+
+		myopt.title = title.data;
+
+		printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+		PQclear(res);
+
+		retval = true;
+		goto error_return;		/* not an error, just return early */
+	}
 
 	/*
 	 * Get column info
@@ -3365,6 +3401,7 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
  * m - materialized views
  * s - sequences
  * E - foreign table (Note: different from 'f', the relkind value)
+ * V - schema variable
  * (any order of the above is fine)
  */
 bool
@@ -3376,6 +3413,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 	bool		showMatViews = strchr(tabtypes, 'm') != NULL;
 	bool		showSeq = strchr(tabtypes, 's') != NULL;
 	bool		showForeign = strchr(tabtypes, 'E') != NULL;
+	bool		showVariables = strchr(tabtypes, 'V') != NULL;
 
 	PQExpBufferData buf;
 	PGresult   *res;
@@ -3383,8 +3421,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 	static const bool translate_columns[] = {false, false, true, false, false, false, false};
 
 	/* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
-	if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
-		showTables = showViews = showMatViews = showSeq = showForeign = true;
+	if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign || showVariables))
+		showTables = showViews = showMatViews = showSeq = showForeign = showVariables = true;
 
 	initPQExpBuffer(&buf);
 
@@ -3405,6 +3443,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
 					  " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
 					  " WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'"
+					  " WHEN " CppAsString2(RELKIND_VARIABLE) " THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
 					  gettext_noop("Schema"),
@@ -3418,6 +3457,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("foreign table"),
 					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("index"),	/* partitioned index */
+					  gettext_noop("schema variable"),
 					  gettext_noop("Type"),
 					  gettext_noop("Owner"));
 
@@ -3471,6 +3511,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBufferStr(&buf, "'s',"); /* was RELKIND_SPECIAL */
 	if (showForeign)
 		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_FOREIGN_TABLE) ",");
+	if (showVariables)
+		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_VARIABLE) ",");
 
 	appendPQExpBufferStr(&buf, "''");	/* dummy */
 	appendPQExpBufferStr(&buf, ")\n");
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index a4cc5efae0..c5f107d814 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -60,7 +60,7 @@ extern bool listTSTemplates(const char *pattern, bool verbose);
 /* \l */
 extern bool listAllDbs(const char *pattern, bool verbose);
 
-/* \dt, \di, \ds, \dS, etc. */
+/* \dt, \di, \ds, \dS, \dvar etc. */
 extern bool listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSystem);
 
 /* \dD */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 702e742af4..2da50f7290 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -167,7 +167,7 @@ slashUsage(unsigned short int pager)
 	 * Use "psql --help=commands | wc" to count correctly.  It's okay to count
 	 * the USE_READLINE line even in builds without that.
 	 */
-	output = PageOutput(125, pager ? &(pset.popt.topt) : NULL);
+	output = PageOutput(126, pager ? &(pset.popt.topt) : NULL);
 
 	fprintf(output, _("General\n"));
 	fprintf(output, _("  \\copyright             show PostgreSQL usage and distribution terms\n"));
@@ -257,6 +257,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dT[S+] [PATTERN]      list data types\n"));
 	fprintf(output, _("  \\du[S+] [PATTERN]      list roles\n"));
 	fprintf(output, _("  \\dv[S+] [PATTERN]      list views\n"));
+	fprintf(output, _("  \\dV[S+] [PATTERN]      list schema variables\n"));
 	fprintf(output, _("  \\dx[+]  [PATTERN]      list extensions\n"));
 	fprintf(output, _("  \\dy     [PATTERN]      list event triggers\n"));
 	fprintf(output, _("  \\l[+]   [PATTERN]      list databases\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8bc4a194a5..ba5f6b0832 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -679,6 +679,20 @@ static const SchemaQuery Query_for_list_of_statistics = {
 	NULL
 };
 
+static const SchemaQuery Query_for_list_of_variables = {
+	/* catname */
+	"pg_catalog.pg_class c",
+	/* selcondition */
+	"c.relkind IN ('V')",
+	/* viscondition */
+	"pg_catalog.pg_table_is_visible(c.oid)",
+	/* namespace */
+	"c.relnamespace",
+	/* result */
+	"pg_catalog.quote_ident(c.relname)",
+	/* qualresult */
+	NULL
+};
 
 /*
  * Queries to get lists of names of various kinds of things, possibly
@@ -1108,6 +1122,7 @@ static const pgsql_thing_t words_after_create[] = {
 																 * TABLE ... */
 	{"USER", Query_for_list_of_roles " UNION SELECT 'MAPPING FOR'"},
 	{"USER MAPPING FOR", NULL, NULL},
+	{"VARIABLE", NULL, &Query_for_list_of_variables},
 	{"VIEW", NULL, &Query_for_list_of_views},
 	{NULL}						/* end of list */
 };
@@ -1460,7 +1475,7 @@ psql_completion(const char *text, int start, int end)
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CALL", "CHECKPOINT", "CLOSE", "CLUSTER",
 		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
 		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
-		"FETCH", "GRANT", "IMPORT", "INSERT", "LISTEN", "LOAD", "LOCK",
+		"FETCH", "GRANT", "IMPORT", "INSERT", "LET", "LISTEN", "LOAD", "LOCK",
 		"MOVE", "NOTIFY", "PREPARE",
 		"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
 		"RESET", "REVOKE", "ROLLBACK",
@@ -1479,7 +1494,7 @@ psql_completion(const char *text, int start, int end)
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dm", "\\dn", "\\do", "\\dO", "\\dp",
 		"\\drds", "\\dRs", "\\dRp", "\\ds", "\\dS",
-		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
+		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy", "\\dvar",
 		"\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding",
 		"\\endif", "\\errverbose", "\\ev",
 		"\\f",
@@ -2684,6 +2699,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
+/* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	/* Complete CREATE VARIABLE <name> with AS */
+	else if (TailMatches3("CREATE", "VARIABLE", MatchAny))
+		COMPLETE_WITH_CONST("AS");
+	/* Complete CREATE VARIABLE <name> with AS types*/
+	else if (TailMatches4("CREATE", "VARIABLE", MatchAny, "AS"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VIEW <name> with AS */
 	else if (TailMatches3("CREATE", "VIEW", MatchAny))
@@ -2839,6 +2862,12 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* DROP VARIABLE */
+	else if (Matches2("DROP", "VARIABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
+	else if (Matches3("DROP", "VARIABLE", MatchAny))
+		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
+
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -2849,14 +2878,14 @@ psql_completion(const char *text, int start, int end)
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
 	else if (Matches1("EXPLAIN"))
-		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
-							"ANALYZE", "VERBOSE");
+		COMPLETE_WITH_LIST8("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+							"ANALYZE", "VERBOSE", "LET");
 	else if (Matches2("EXPLAIN", "ANALYZE"))
-		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
-							"VERBOSE");
+		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+							"VERBOSE", "LET");
 	else if (Matches2("EXPLAIN", "VERBOSE") ||
 			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
-		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
+		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "LET");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
@@ -2965,6 +2994,7 @@ psql_completion(const char *text, int start, int end)
 									   " UNION SELECT 'ALL ROUTINES IN SCHEMA'"
 									   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
 									   " UNION SELECT 'ALL TABLES IN SCHEMA'"
+									   " UNION SELECT 'ALL VARIABLES IN SCHEMA'"
 									   " UNION SELECT 'DATABASE'"
 									   " UNION SELECT 'DOMAIN'"
 									   " UNION SELECT 'FOREIGN DATA WRAPPER'"
@@ -2978,14 +3008,16 @@ psql_completion(const char *text, int start, int end)
 									   " UNION SELECT 'SEQUENCE'"
 									   " UNION SELECT 'TABLE'"
 									   " UNION SELECT 'TABLESPACE'"
-									   " UNION SELECT 'TYPE'");
+									   " UNION SELECT 'TYPE'"
+									   " UNION SELECT 'VARIABLE'");
 	}
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
-		COMPLETE_WITH_LIST5("FUNCTIONS IN SCHEMA",
+		COMPLETE_WITH_LIST6("FUNCTIONS IN SCHEMA",
 							"PROCEDURES IN SCHEMA",
 							"ROUTINES IN SCHEMA",
 							"SEQUENCES IN SCHEMA",
-							"TABLES IN SCHEMA");
+							"TABLES IN SCHEMA",
+							"VARIABLES IN SCHEMA");
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
@@ -3015,6 +3047,8 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
 		else if (TailMatches1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		else if (TailMatches1("VARIABLE"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
 		else if (TailMatches1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		else if (TailMatches1("TYPE"))
@@ -3171,7 +3205,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 	else if (Matches3("PREPARE", MatchAny, "AS"))
-		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
+		COMPLETE_WITH_LIST5("SELECT", "UPDATE", "INSERT", "DELETE FROM", "LET");
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -3390,6 +3424,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
+/* LET --- can be inside EXPLAIN, PREPARE etc */
+	/* If prev. word is LET suggest a list of variables */
+	else if (TailMatches1("LET"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
+	/* Complete LET <variable> with "=" */
+	else if (TailMatches2("LET", MatchAny))
+		COMPLETE_WITH_CONST("=");
+
 /* USER MAPPING */
 	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 26b1866c69..c5146fc138 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -167,6 +167,7 @@ DESCR("");
 #define		  RELKIND_FOREIGN_TABLE   'f'	/* foreign table */
 #define		  RELKIND_PARTITIONED_TABLE 'p' /* partitioned table */
 #define		  RELKIND_PARTITIONED_INDEX 'I' /* partitioned index */
+#define		  RELKIND_VARIABLE		  'V'	/* schema variable */
 
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index 11b306037d..13232d7a43 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -71,5 +71,6 @@ typedef FormData_pg_default_acl *Form_pg_default_acl;
 #define DEFACLOBJ_FUNCTION		'f' /* function */
 #define DEFACLOBJ_TYPE			'T' /* type */
 #define DEFACLOBJ_NAMESPACE		'n' /* namespace */
+#define DEFACLOBJ_VARIABLE		'V' /* variable */
 
 #endif							/* PG_DEFAULT_ACL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f01648c961..600d3d5849 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5533,6 +5533,12 @@ DESCR("list of files in the WAL directory");
 DATA(insert OID = 5028 ( satisfies_hash_partition PGNSP PGUID 12 1 0 2276 0 f f f f f f i s 4 0 16 "26 23 23 2276" _null_ "{i,i,i,v}" _null_ _null_ _null_ satisfies_hash_partition _null_ _null_ _null_ ));
 DESCR("hash partition CHECK constraint");
 
+/* schema variables function interface */
+DATA(insert OID = 6122 ( get_schema_variable PGNSP PGUID 12 1 0 0 0 f f f f f f v r 2 0 2283 "2205 2283" _null_ _null_ _null_ _null_ _null_ get_schema_variable _null_ _null_ _null_ ));
+DESCR("returns value of schema variable");
+DATA(insert OID = 6123 ( set_schema_variable PGNSP PGUID 12 1 0 0 0 f f f f f f v r 2 0 2278 "2205 2283" _null_ _null_ _null_ _null_ _null_ set_schema_variable _null_ _null_ _null_ ));
+DESCR("returns value of schema variable");
+
 /*
  * Symbolic values for provolatile column: these indicate whether the result
  * of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/commands/schemavar.h b/src/include/commands/schemavar.h
new file mode 100644
index 0000000000..6f65b1f1d3
--- /dev/null
+++ b/src/include/commands/schemavar.h
@@ -0,0 +1,31 @@
+/*-------------------------------------------------------------------------
+ *
+ * schemavar.h
+ *	  prototypes for schemavar.c.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/schemavar.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SCHEMAVAR_H
+#define SCHEMAVAR_H
+
+#include "catalog/objectaddress.h"
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+
+extern ObjectAddress DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt *var);
+
+extern void ResetSchemaVariablesCache(void);
+
+extern char *get_schemavar_name(Oid varid);
+
+extern void SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid	typid, int32 typmod, int16 typlen, bool typbyval);
+extern Datum GetSchemaVariable(Oid varid, bool *isNull, Oid	typid, int32 typmod, int16 typlen, bool typbyval);
+extern void SetSchemaVariableSecure(Oid varid, Datum value, bool isNull, Oid	typid, int32 typmod, int16 typlen, bool typbyval);
+extern Datum GetSchemaVariableSecure(Oid varid, bool *isNull, Oid	typid, int32 typmod, int16 typlen, bool typbyval);
+
+#endif
\ No newline at end of file
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 117fc892f4..a282f1e4e0 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -138,6 +138,7 @@ typedef enum ExprEvalOp
 	EEOP_PARAM_EXEC,
 	EEOP_PARAM_EXTERN,
 	EEOP_PARAM_CALLBACK,
+	EEOP_PARAM_SCHEMA_VARIABLE,
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -342,11 +343,14 @@ typedef struct ExprEvalStep
 			TupleDesc	argdesc;
 		}			nulltest_row;
 
-		/* for EEOP_PARAM_EXEC/EXTERN */
+		/* for EEOP_PARAM_EXEC/EXTERN/VARIABLE */
 		struct
 		{
-			int			paramid;	/* numeric ID for parameter */
-			Oid			paramtype;	/* OID of parameter's datatype */
+			int			paramid;		/* numeric ID for parameter */
+			Oid			paramtype;		/* OID of parameter's datatype */
+			int32		paramtypmod;	/* typmod of param (not used yet) */
+			int16		paramtyplen;	/* expected length */
+			bool		paramtypbyval;	/* is passed by value */
 		}			param;
 
 		/* for EEOP_PARAM_CALLBACK */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 74b094a9c3..2f4986099d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -344,6 +344,7 @@ typedef enum NodeTag
 	T_CreateTableAsStmt,
 	T_CreateSeqStmt,
 	T_AlterSeqStmt,
+	T_CreateSchemaVarStmt,
 	T_VariableSetStmt,
 	T_VariableShowStmt,
 	T_DiscardStmt,
@@ -415,6 +416,7 @@ typedef enum NodeTag
 	T_CreateStatsStmt,
 	T_AlterCollationStmt,
 	T_CallStmt,
+	T_LetStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -657,6 +659,7 @@ typedef enum CmdType
 	CMD_UPDATE,					/* update stmt */
 	CMD_INSERT,					/* insert stmt */
 	CMD_DELETE,
+	CMD_LET,
 	CMD_UTILITY,				/* cmds like create, destroy, copy, vacuum,
 								 * etc. */
 	CMD_NOTHING					/* dummy command for instead nothing rules
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 76a73b2a37..7ed5c61f5f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1486,6 +1486,14 @@ typedef struct UpdateStmt
 	WithClause *withClause;		/* WITH clause */
 } UpdateStmt;
 
+typedef struct LetStmt
+{
+	NodeTag		type;
+	RangeVar   *variable;		/* relation to insert into */
+	Node	   *selectStmt;		/* the source SELECT/VALUES, or NULL */
+} LetStmt;
+
+
 /* ----------------------
  *		Select Statement
  *
@@ -1663,6 +1671,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -2475,6 +2484,18 @@ typedef struct AlterSeqStmt
 	bool		missing_ok;		/* skip error if a role is missing? */
 } AlterSeqStmt;
 
+/* ----------------------
+ *		Create VARIABLE Statement
+ * ----------------------
+ */
+typedef struct CreateSchemaVarStmt
+{
+	NodeTag		type;
+	RangeVar   *variable;		/* the variable to create */
+	TypeName   *typeName;		/* the variable type */
+	bool		if_not_exists;	/* just do nothing if it already exists? */
+} CreateSchemaVarStmt;
+
 /* ----------------------
  *		Create {Aggregate|Operator|Type} Statement
  * ----------------------
@@ -3204,7 +3225,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;
 
 typedef struct DiscardStmt
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4b0d75af..b366471940 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -229,13 +229,17 @@ typedef struct Const
  *				of the `paramid' field contain the SubLink's subLinkId, and
  *				the low-order 16 bits contain the column number.  (This type
  *				of Param is also converted to PARAM_EXEC during planning.)
+ *
+ *		PARAM_SCHEMA_VARIABLE:  The parameter is a access to schema variable
+ *				paramid holds varid.
  */
 typedef enum ParamKind
 {
 	PARAM_EXTERN,
 	PARAM_EXEC,
 	PARAM_SUBLINK,
-	PARAM_MULTIEXPR
+	PARAM_MULTIEXPR,
+	PARAM_SCHEMA_VARIABLE
 } ParamKind;
 
 typedef struct Param
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 26af944e03..3971d7478b 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -229,6 +229,7 @@ PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD)
 PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD)
 PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD)
 PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("let", LET, UNRESERVED_KEYWORD)
 PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
 PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
@@ -430,6 +431,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD)
 PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD)
 PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD)
+PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD)
+PG_KEYWORD("variables", VARIABLES, UNRESERVED_KEYWORD)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 4e96fa7907..18ba221180 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -134,6 +134,8 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * to process them like UPDATE.  (Note this can change intra-statement, for
  * cases like INSERT ON CONFLICT UPDATE.)
  *
+ * p_is_let: true to process assignment expressions like LET.
+ *
  * p_windowdefs: list of WindowDefs representing WINDOW and OVER clauses.
  * We collect these while transforming expressions and then transform them
  * afterwards (so that any resjunk tlist items needed for the sort/group
@@ -183,6 +185,7 @@ struct ParseState
 	Relation	p_target_relation;	/* INSERT/UPDATE/DELETE target rel */
 	RangeTblEntry *p_target_rangetblentry;	/* target rel's RTE */
 	bool		p_is_insert;	/* process assignment like INSERT not UPDATE */
+	bool		p_is_let;		/* process assignment LET stmt */
 	List	   *p_windowdefs;	/* raw representations of window clauses */
 	ParseExprKind p_expr_kind;	/* what kind of expression we're parsing */
 	int			p_next_resno;	/* next targetlist resno to assign */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index b9792acdae..760aaed9a8 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -129,4 +129,6 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+extern Node *toSchemaVariable(ParseState *pstate, char *nspname, char *varname, int location);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index f4d4be8d0d..d0737a9e4b 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -166,6 +166,7 @@ typedef ArrayType Acl;
 #define ACL_ALL_RIGHTS_SCHEMA		(ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE	(ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE			(ACL_USAGE)
+#define ACL_ALL_RIGHTS_VARIABLE		(ACL_SELECT|ACL_UPDATE)
 
 /* operation codes for pg_*_aclmask */
 typedef enum
diff --git a/src/test/regress/expected/schema_variables.out b/src/test/regress/expected/schema_variables.out
new file mode 100644
index 0000000000..ad700c15d8
--- /dev/null
+++ b/src/test/regress/expected/schema_variables.out
@@ -0,0 +1,236 @@
+CREATE VARIABLE var1 AS integer;
+CREATE TEMP VARIABLE var2 AS text;
+-- should to fail
+CREATE VARIABLE var2 AS pg_class;
+ERROR:  Composite types are not allowed as variable type.
+DROP VARIABLE var1, var2;
+-- functional interface, attention typmod is not stored
+CREATE VARIABLE var1 AS numeric(10,1);
+SELECT set_schema_variable('var1', 333);
+ set_schema_variable 
+---------------------
+ 
+(1 row)
+
+SELECT get_schema_variable('var1', null::numeric);
+ get_schema_variable 
+---------------------
+                 333
+(1 row)
+
+SELECT set_schema_variable('var1', 333::integer);
+ set_schema_variable 
+---------------------
+ 
+(1 row)
+
+SELECT get_schema_variable('var1', null::numeric);
+ get_schema_variable 
+---------------------
+                 333
+(1 row)
+
+SELECT set_schema_variable('var1', '333.55'::text);
+ set_schema_variable 
+---------------------
+ 
+(1 row)
+
+SELECT get_schema_variable('var1', null::numeric);
+ get_schema_variable 
+---------------------
+              333.55
+(1 row)
+
+SELECT get_schema_variable('var1', null::int);
+ get_schema_variable 
+---------------------
+                 334
+(1 row)
+
+SELECT get_schema_variable('var1', null::text);
+ get_schema_variable 
+---------------------
+ 333.55
+(1 row)
+
+-- access rights test
+CREATE ROLE var_test_role;
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT set_schema_variable('var1', '1000'::text);
+ERROR:  permission denied for schema variable var1
+SELECT get_schema_variable('var1', null::numeric);
+ERROR:  permission denied for schema variable var1
+SET ROLE TO DEFAULT;
+GRANT SELECT ON VARIABLE var1 TO var_test_role;
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT set_schema_variable('var1', '1000'::text);
+ERROR:  permission denied for schema variable var1
+-- should to work
+SELECT get_schema_variable('var1', null::numeric);
+ get_schema_variable 
+---------------------
+              333.55
+(1 row)
+
+SET ROLE TO DEFAULT;
+GRANT UPDATE ON VARIABLE var1 TO var_test_role;
+SET ROLE TO var_test_role;
+-- should to work
+SELECT set_schema_variable('var1', '1000'::text);
+ set_schema_variable 
+---------------------
+ 
+(1 row)
+
+SELECT get_schema_variable('var1', null::numeric);
+ get_schema_variable 
+---------------------
+                1000
+(1 row)
+
+SET ROLE TO DEFAULT;
+REVOKE ALL ON VARIABLE var1 FROM var_test_role;
+CREATE VARIABLE var AS integer;
+SELECT set_schema_variable('public.var', 1234);
+ set_schema_variable 
+---------------------
+ 
+(1 row)
+
+SELECT public.var;
+ var  
+------
+ 1234
+(1 row)
+
+DO $$
+BEGIN
+  RAISE NOTICE 'public.var is = %', public.var;
+END;
+$$;
+NOTICE:  public.var is = 1234
+CREATE OR REPLACE FUNCTION secure_var()
+RETURNS int AS $$
+  SELECT public.var;
+$$ LANGUAGE sql SECURITY DEFINER;
+SELECT secure_var();
+ secure_var 
+------------
+       1234
+(1 row)
+
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT public.var;
+ERROR:  permission denied for schema variable var
+-- should to work;
+SELECT secure_var();
+ secure_var 
+------------
+       1234
+(1 row)
+
+SET ROLE TO DEFAULT;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Function Scan on pg_catalog.generate_series g
+   Output: v
+   Function Call: generate_series(1, 100)
+   Filter: (g.v = var)
+(4 rows)
+
+CREATE VIEW schema_var_view AS SELECT var;
+SELECT * FROM schema_var_view;
+ var  
+------
+ 1234
+(1 row)
+
+\c -
+-- should to work still, but var will be empty
+SELECT * FROM schema_var_view;
+ var 
+-----
+    
+(1 row)
+
+LET var1 = pi();
+SELECT var1;
+       var1       
+------------------
+ 3.14159265358979
+(1 row)
+
+-- we can look on execution plan
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+                      QUERY PLAN                      
+------------------------------------------------------
+ Let on public.var1
+   ->  Result
+         Output: '3.14159265358979'::double precision
+(3 rows)
+
+-- LET can be prepared
+PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2;
+EXECUTE var_pp(100, 1.23456);
+SELECT var1;
+   var1    
+-----------
+ 101.23456
+(1 row)
+
+CREATE VARIABLE var3 AS int;
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+  LET public.var3 = COALESCE(public.var3 + $1, $1);
+  RETURN var3;
+END;
+$$ LANGUAGE plpgsql;
+SELECT inc(1);
+ inc 
+-----
+   1
+(1 row)
+
+SELECT inc(1);
+ inc 
+-----
+   2
+(1 row)
+
+SELECT inc(1);
+ inc 
+-----
+   3
+(1 row)
+
+SELECT inc(1) FROM generate_series(1,10);
+ inc 
+-----
+   4
+   5
+   6
+   7
+   8
+   9
+  10
+  11
+  12
+  13
+(10 rows)
+
+SET ROLE TO var_test_role;
+-- should to fail
+LET var3 = 0;
+ERROR:  permission denied for schema variable var3
+SET ROLE TO DEFAULT;
+DROP VIEW schema_var_view;
+DROP ROLE var_test_role;
+DROP VARIABLE var CASCADE;
+DROP VARIABLE var1 CASCADE;
+DROP VARIABLE var3 CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ad9434fb87..33fe7ee476 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml schema_variables
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 27cd49845e..22c4cac7ce 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -187,3 +187,4 @@ test: hash_part
 test: indexing
 test: event_trigger
 test: stats
+test: schema_variables
diff --git a/src/test/regress/sql/schema_variables.sql b/src/test/regress/sql/schema_variables.sql
new file mode 100644
index 0000000000..9ee9e174f9
--- /dev/null
+++ b/src/test/regress/sql/schema_variables.sql
@@ -0,0 +1,139 @@
+CREATE VARIABLE var1 AS integer;
+CREATE TEMP VARIABLE var2 AS text;
+
+-- should to fail
+CREATE VARIABLE var2 AS pg_class;
+
+DROP VARIABLE var1, var2;
+
+-- functional interface, attention typmod is not stored
+CREATE VARIABLE var1 AS numeric(10,1);
+SELECT set_schema_variable('var1', 333);
+SELECT get_schema_variable('var1', null::numeric);
+
+SELECT set_schema_variable('var1', 333::integer);
+SELECT get_schema_variable('var1', null::numeric);
+
+SELECT set_schema_variable('var1', '333.55'::text);
+SELECT get_schema_variable('var1', null::numeric);
+SELECT get_schema_variable('var1', null::int);
+SELECT get_schema_variable('var1', null::text);
+
+-- access rights test
+
+CREATE ROLE var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+SELECT set_schema_variable('var1', '1000'::text);
+SELECT get_schema_variable('var1', null::numeric);
+
+SET ROLE TO DEFAULT;
+
+GRANT SELECT ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT set_schema_variable('var1', '1000'::text);
+-- should to work
+SELECT get_schema_variable('var1', null::numeric);
+
+SET ROLE TO DEFAULT;
+
+GRANT UPDATE ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should to work
+SELECT set_schema_variable('var1', '1000'::text);
+SELECT get_schema_variable('var1', null::numeric);
+
+SET ROLE TO DEFAULT;
+
+REVOKE ALL ON VARIABLE var1 FROM var_test_role;
+
+CREATE VARIABLE var AS integer;
+
+SELECT set_schema_variable('public.var', 1234);
+
+SELECT public.var;
+
+DO $$
+BEGIN
+  RAISE NOTICE 'public.var is = %', public.var;
+END;
+$$;
+
+CREATE OR REPLACE FUNCTION secure_var()
+RETURNS int AS $$
+  SELECT public.var;
+$$ LANGUAGE sql SECURITY DEFINER;
+
+SELECT secure_var();
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+SELECT public.var;
+
+-- should to work;
+SELECT secure_var();
+
+SET ROLE TO DEFAULT;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var;
+
+CREATE VIEW schema_var_view AS SELECT var;
+
+SELECT * FROM schema_var_view;
+
+\c -
+
+-- should to work still, but var will be empty
+SELECT * FROM schema_var_view;
+
+LET var1 = pi();
+
+SELECT var1;
+
+-- we can look on execution plan
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+
+-- LET can be prepared
+PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2;
+
+EXECUTE var_pp(100, 1.23456);
+
+SELECT var1;
+
+CREATE VARIABLE var3 AS int;
+
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+  LET public.var3 = COALESCE(public.var3 + $1, $1);
+  RETURN var3;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT inc(1);
+SELECT inc(1);
+SELECT inc(1);
+
+SELECT inc(1) FROM generate_series(1,10);
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+LET var3 = 0;
+
+SET ROLE TO DEFAULT;
+
+DROP VIEW schema_var_view;
+
+DROP ROLE var_test_role;
+
+DROP VARIABLE var CASCADE;
+DROP VARIABLE var1 CASCADE;
+DROP VARIABLE var3 CASCADE;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a42ff9794a..5f0bc22f6d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -421,6 +421,7 @@ CreateReplicationSlotCmd
 CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
+CreateSchemaVarStmt
 CreateSeqStmt
 CreateStatsStmt
 CreateStmt

---

From: "David G. Johnston" <[email-masked]>
Date: Fri, 2 Feb 2018 17:48:05 -0700

​I've done a non-compilation documentation review, the diff from the poc
patch and the diff from master are attached.

Comments are inter-twined in the patch in xml comment format; though I
reiterate (some of?) them below.

> 3. possibility to store short life data in fast secured storage

​The generic use of the word secure here bothers me.  I'm taking it to be
"protected by grant/revoke"-based privileges; plus session-locality.

> 5. session variables with possibility to process static code check

What does "process static code check" means here?​

> functions, internal functions

I made the public aspect of this explicit in the CREATE VARIABLE doc
(though as noted below it probably belongs in section II)
​

> 7. data are stored in binary form

Thoughts during my review:

There is, for me, a cognitive dissonance between "schema variable" and
"variable value" - I'm partial to the later.  Since we use "setting" for
GUCs the term variable here hopefully wouldn't cause ambiguity...

I've noticed that we don't seem to have or enforce any policy on how to
communicate "SQL standards compatibility" to the user...

We are missing the ability to alter ownership (or at least its
undocumented), and if that brings into existing ALTER VARIABLE we should
probably add ALTER TYPE TO new_type USING (cast) for completeness.

Its left for the reader to presume that because these are schema
"relations" that namespace resolution via search_path works the same as any
other relation.

I think I've answered my own question regarding DISCARD in that "variables"
discards values while if TEMP is in effect all temp variables are dropped.

Examples abound though it doesn't feel like too much: but saying "The usage
is very simple:" before giving the example in the function section seems to
be outside of our general style.  A better preamble than "An example:"
would be nice but the example is so simple I could not think of anything
worth writing.

Its worth considering how both:

https://www.postgresql.org/docs/10/static/ddl.html
and
https://www.postgresql.org/docs/10/static/queries.html

could be updated to incorporate the broad picture of schema variables, with
examples, and leave the reference (SQL and functions) sections mainly
relegated to syntax and reminders.

A moderate number of lines changed are for typos and minor grammar edits.

David J.

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 3 Feb 2018 07:58:33 +0100

Hi

> "protected by grant/revoke"-based privileges; plus session-locality.

I have not a problem with any other formulation.

> What does "process static code check" means here?​

It mean the possibility to check validity of code without code execution.
You can use plpgsql_check for example.

> GUCs the term variable here hopefully wouldn't cause ambiguity...

The "schema" is important in this case. 1) it is a analogy to "package
variable", 2) not necessary, but probably often it will be used together
with PLpgSQL. There are variables too. "Session variables" doesn't well
specify the implementation. The session variables can be GUC, psql client
variables or some custom implementation in Postgres or package variables in
Oracle.

> probably add ALTER TYPE TO new_type USING (cast) for completeness.

good note. I didn't test it. I am not sure, what variants of ALTER should
be supported. Type of variables is interface. Probably we can allow to add
new field, but change type or remove field can break other object. So it
can be prohibited like we doesn't support ALTER on views. ALTERing is
another and pretty complex topic, and I don't think it is necessary to
solve it now. This feature can be valuable without ALTER support, and
nothing block later ALTER VARIABLE implementation.

This design allows lot of interesting features (that can be implemented
step by step)

1. support for default expression
2. support for constraints and maybe triggers
3. reset on transaction end
4. initialization of session start - via default expression or triggers it
can be way how to start code on session start.

> I think I've answered my own question regarding DISCARD in that
> "variables" discards values while if TEMP is in effect all temp variables
> are dropped.
>

DISCARD should to remove TEMP variables and should to remove content of all
variables.

>
> Examples abound though it doesn't feel like too much: but saying "The
> usage is very simple:" before giving the example in the function section
> seems to be outside of our general style.  A better preamble than "An
> example:" would be nice but the example is so simple I could not think of
> anything worth writing.
>

This doc is just design frame. I invite any enhancing because this feature
can be difficult for some people, because mix persistent object with
temporal/session content - and term "variable" can be used in relation
algebra in different semantic. It is natural for people with stored
procedures experience - mainly with Oracle, but for any other can be little
bit difficult. I believe so there should be more practical examples -
related to RLS for example.

> could be updated to incorporate the broad picture of schema variables,
> with examples, and leave the reference (SQL and functions) sections mainly

Thank you very much

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 7 Feb 2018 07:34:44 +0100

Hi

updated patch with your changes in documentation and pg_dump (initial)
support

Main issue of this patch is storage. We can reuse local buffers used for
temp tables. But it does allocation by 8KB and it creates temp files for
every object. That is too big overhead. Storing just in session memory is
too simple - then there should be lot of new code used, when variable will
be dropped.

I have ideas how to allow work with mix of scalar and composite types - so
it will be next step of this prototype.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 8 Mar 2018 19:00:36 +0100

Hi

> it will be next step of this prototype.

new update - rebased, + some initial support for composite values on right
side and custom types, arrays are supported too.

omega=# CREATE VARIABLE xx AS (a int, b numeric);
CREATE VARIABLE
omega=# LET xx = (10, 20)::xx;
LET
omega=# SELECT xx;
+---------+
|   xx    |
+---------+
| (10,20) |
+---------+
(1 row)

omega=# SELECT xx.a + xx.b;
+----------+
| ?column? |
+----------+
|       30 |
+----------+
(1 row)

omega=# \d xx
schema variable "public.xx"
+--------+---------+
| Column |  Type   |
+--------+---------+
| a      | integer |
| b      | numeric |
+--------+---------+

Regards

Pavel

---

From: Pavel Luzanov <[email-masked]>
Date: Mon, 12 Mar 2018 09:49:29 +0300

Hi,

I plan to make usability and feature test review in several days.

Is there any chances that it will work on replicas?
Such possibility is very helpful in generating reports.
Now, LET command produces an error:

ERROR:  cannot execute LET in a read-only transaction

But if we say that variables are non-transactional ?

-----
Pavel Luzanov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

On 08.03.2018 21:00, Pavel Stehule wrote:
> Hi
>
> 2018-02-07 7:34 GMT+01:00 Pavel Stehule <[email-masked] 
> <mailto:[email-masked]>>:
>
>     Hi
>
>     updated patch with your changes in documentation and pg_dump
>     (initial) support
>
>     Main issue of this patch is storage. We can reuse local buffers
>     used for temp tables. But it does allocation by 8KB and it creates
>     temp files for every object. That is too big overhead. Storing
>     just in session memory is too simple - then there should be lot of
>     new code used, when variable will be dropped.
>
>     I have ideas how to allow work with mix of scalar and composite
>     types - so it will be next step of this prototype.
>
>     Regards
>
>     Pavel
>
>
> new update - rebased, + some initial support for composite values on 
> right side and custom types, arrays are supported too.
>

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 12 Mar 2018 07:54:26 +0100

> But if we say that variables are non-transactional ?

sure, it should to work. Now, I am try to solve a issues on concept level -
the LET code is based on DML code base, so probably there is check for rw
transactions. But it is useless for LET command.

Regards

Pavel

>> I have ideas how to allow work with mix of scalar and composite types -
>> so it will be next step of this prototype.
>>
>> Regards
>>
>> Pavel
>>
>

---

From: Pavel Luzanov <[email-masked]>
Date: Mon, 12 Mar 2018 18:38:39 +0300

On 12.03.2018 09:54, Pavel Stehule wrote:
>
> 2018-03-12 7:49 GMT+01:00 Pavel Luzanov <[email-masked] 
> <mailto:[email-masked]>>:
>
>
> ...
> sure, it should to work. Now, I am try to solve a issues on concept 
> level - the LET code is based on DML code base, so probably there is 
> check for rw transactions. But it is useless for LET command.

Very, very good!

As I understand, the work on this patch now in progress and it not in 
commitfest.
Please explain what features of schema variables I can review now.

 From first post of this thread the syntax of the CREATE VARIABLE command:

But in psql I see only:
\h create variable
Command:     CREATE VARIABLE
Description: define a new permissioned typed schema variable
Syntax:
CREATE VARIABLE [ IF NOT EXISTS ] name [ AS ] data_type ]

I can include DEFAULT clause in CREATE VARIABLE command, but the value 
not used:
postgres=# create variable i int default 0;
CREATE VARIABLE
postgres=# select i;
  i
---

(1 row)

postgres=# \d+ i
  schema variable "public.i"
  Column |  Type   | Storage
--------+---------+---------
  i      | integer | plain

BTW, I found an error in handling of table aliases:

postgres=# create variable x text;
CREATE VARIABLE
postgres=# select * from pg_class AS x where x.relname = 'x';
ERROR:  type text is not composite

It thinks that x.relname is an attribute of x variable instead of an 
alias for pg_class table.

-----
Pavel Luzanov

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 12 Mar 2018 17:13:40 +0100

2018-03-12 16:38 GMT+01:00 Pavel Luzanov <[email-masked]>:

> sure, it should to work. Now, I am try to solve a issues on concept level
> - the LET code is based on DML code base, so probably there is check for rw

Now, it is too early for review - it is in development. Some features are
not implemented yet - DEFAULTs, ON TRANSACTION END .., others has not sense
(what I know now VOLATILE, STABLE). Schema variables are passed as
parameters to query, so the behave is like any other params - it is STABLE
only.

> I can include DEFAULT clause in CREATE VARIABLE command, but the value not
> used:
defaults are not implemented yet

> It thinks that x.relname is an attribute of x variable instead of an alias
> for pg_class table.
>
>
It is not well handled collision. This should be detected and prohibited.
In this case, because x is scalar, then x.xx has not sense, and then it
should not be handled like variable. So the current design is not too
practical - it generates more collisions than it is necessary and still,
there are some errors.

Now, there is one important question - storage - Postgres stores all
objects to files - only memory storage is not designed yet. This is part,
where I need a help.

Regards

Pavel

---

From: Pavel Luzanov <[email-masked]>
Date: Tue, 13 Mar 2018 02:54:30 -0700 (MST)

Pavel Stehule wrote
> objects to files - only memory storage is not designed yet. This is part,

O, I do not feel confident in such questions.
May be some ideas you can get from extension with similar functionality:
https://github.com/postgrespro/pg_variables

-----
Pavel Luzanov

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 13 Mar 2018 19:44:01 +0100

> https://github.com/postgrespro/pg_variables

Unfortunately not - it doesn't implement this functionality

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 20 Mar 2018 18:38:47 +0100

Hi

I am sending new update. The code is less ugly, and the current
functionality is +/- final for first stage. It should be good enough for
playing and testing this concept.

What is supported:

1. scalar, composite and array variables
2. composite can be defined on place or some composite type can be used
3. variable, or any field of variable, can have defined default value
4. variable is database object - the access rights are required
5. the values are stored in binary form with defined typmod

An usage is very simple:

postgres=# create variable foo as numeric default 0;
CREATE VARIABLE
postgres=# select foo;
┌─────┐
│ foo │
╞═════╡
│   0 │
└─────┘
(1 row)

postgres=# let foo = pi();
LET
postgres=# select foo;
┌──────────────────┐
│       foo        │
╞══════════════════╡
│ 3.14159265358979 │
└──────────────────┘
(1 row)

postgres=# create variable boo as (x numeric default 0, y numeric default
0);
CREATE VARIABLE
postgres=# let boo.x = 100;
LET
postgres=# select boo;
┌─────────┐
│   boo   │
╞═════════╡
│ (100,0) │
└─────────┘
(1 row)

postgres=# select boo.x;
┌─────┐
│  x  │
╞═════╡
│ 100 │
└─────┘
(1 row)

Please try it.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 21 Mar 2018 06:24:16 +0100

> postgres=# let foo = pi();

small fix - support for SQL functions

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 23 Mar 2018 06:37:58 +0100

> small fix - support for SQL functions

the patch is in commit fest list https://commitfest.postgresql.org/18/1608/

Regards

Pavel

---

From: Arthur Zakirov <[email-masked]>
Date: Tue, 17 Apr 2018 17:14:12 +0300

Hello Pavel,

> I hope so this proposal is good enough and simple.

As I understood variables are stored in pg_class table. Did you consider
storing variables in a special catalog table? It can be named as
pg_variable.

pg_variable table requires more code of course, but it would fix the issues:
- pg_class has a lot attributes which are not related with variables,
  and I think variables don't need many of them
- in a future variables might want to have some additional attributes
  which are not needed for relations, you can easily add them to
  pg_variable

What do you think?

-- 
Arthur Zakirov
Russian Postgres Company

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 17 Apr 2018 18:28:19 +0200

Hi

>   which are not needed for relations, you can easily add them to

I though about it, and I am inclined to prefer pg_class instead separate
tables.

It true, so there are lot of "unused" attributes for this purpose, but
there is lot of shared attributes, and lot of shared code. Semantically, I
see variables in family of sequences, tables, indexes, views. Now, it
shares code, and I hope in next steps more code can be shared -
constraints, triggers.

There are two objective arguments for using pg_class:

1. unique name in schema - it reduces risk of collisions
2. sharing lot of code

So in this case I don't see well benefits of separate table.

Regards

Pavel

---

From: Arthur Zakirov <[email-masked]>
Date: Wed, 18 Apr 2018 14:37:11 +0300

> So in this case I don't see well benefits of separate table.

Understood. I haven't strong opinion here though. But I thought that
pg_class approach may limit extensibility of variables.

BTW:
- there is unitialized variable 'j' in pg_dump.c:15422
- in tab-complete.c:1268 initialization needs extra NULL before
  &Query_for_list_of_variables

Also I think makeRangeVarForTargetOfSchemaVariable() has non friendly
argument names 'field1', 'field2', 'field2'.

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 18 Apr 2018 13:54:44 +0200

Hi

I am sending rebased patch

> pg_class approach may limit extensibility of variables.

I didn't touch limit (I don't know if there will be some issue - still is
far to upstream). This is technology, but workable, demo. I though so some
users had a problem to imagine what is persistent variable in my view.

But almost all code and tests can be used for final version - only storage
implementation is nothing more than workaround.

>   &Query_for_list_of_variables

I found it too today when I did rebase. But thank you for report.

> argument names 'field1', 'field2', 'field2'.

yes, I hadn't better names :(

In this routine I am doing diagnostic what semantic has sense for current
values. the field1, field2 can be schema.variable or variable.field. So
when I used semantic names: schema, varname, fieldname, then it was more
messy (for me).

Regards

Pavel

---

From: Robert Haas <[email-masked]>
Date: Fri, 20 Apr 2018 11:32:07 -0400

On Tue, Apr 17, 2018 at 12:28 PM, Pavel Stehule <[email-masked]> wrote:
> It true, so there are lot of "unused" attributes for this purpose, but there
> is lot of shared attributes, and lot of shared code. Semantically, I see
> variables in family of sequences, tables, indexes, views. Now, it shares
> code, and I hope in next steps more code can be shared - constraints,
> triggers.

I dunno, it seems awfully different to me.  There's only one "column",
right?  What code is really shared here?  Are constraints and triggers
even desirable feature for variables?  What would be the use case?

I think stuffing this into pg_class is pretty strange.

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 20 Apr 2018 19:45:06 +0200

2018-04-20 17:32 GMT+02:00 Robert Haas <[email-masked]>:

> there

The schema variable can hold composite value. The patch allows to use any
composite type or adhoc composite values

DECLARE x AS compositetype;
DECLARE x AS (a int, b int, c int);

Constraints are clear, no.

Triggers are strange maybe, but why not - it can be used like enhanced
constraints, can be used for some value calculations, ..

> I think stuffing this into pg_class is pretty strange.

It will be if variable is just scalar value without any possibilities. But
then there is only low benefit

The access rights implementation is shared with other from pg_class too.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 29 Apr 2018 14:34:41 +0200

Hi

I am sending rebased patch to master

Regards

Pavel

---

From: Fabrízio Mello <[email-masked]>
Date: Mon, 30 Apr 2018 17:28:31 +0000

The following review has been posted through the commitfest application:
make installcheck-world:  tested, passed
Implements feature:       not tested
Spec compliant:           not tested
Documentation:            not tested

1) There are some errors applying the patch against the current master:

fabrizio@macanudo:/d/postgresql (master) 
$ git apply /tmp/schema-variables-poc-180429-01-diff
/tmp/schema-variables-poc-180429-01-diff:2305: trailing whitespace.
 
/tmp/schema-variables-poc-180429-01-diff:2317: indent with spaces.
    We can support UPDATE and SELECT commands on variables.
/tmp/schema-variables-poc-180429-01-diff:2319: indent with spaces.
    possible syntaxes:
/tmp/schema-variables-poc-180429-01-diff:2321: indent with spaces.
        -- there can be a analogy with record functions
/tmp/schema-variables-poc-180429-01-diff:2322: indent with spaces.
        SELECT varname;
warning: squelched 14 whitespace errors
warning: 19 lines add whitespace errors.

2) There are some warnings when during build process

schemavar.c:383:18: warning: expression which evaluates to zero treated as a null pointer constant of type 'HeapTuple' (aka 'struct HeapTupleData *')
      [-Wnon-literal-null-conversion]
                HeapTuple       tp = InvalidOid;
                                     ^~~~~~~~~~
../../../src/include/postgres_ext.h:36:21: note: expanded from macro 'InvalidOid'
#define InvalidOid              ((Oid) 0)
                                ^~~~~~~~~
1 warning generated.
tab-complete.c:1268:21: warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
  {"VARIABLE", NULL, &Query_for_list_of_variables},
                     ^
tab-complete.c:1268:21: note: (near initialization for ‘words_after_create[48].vquery’)
llvmjit_expr.c: In function ‘llvm_compile_expr’:
llvmjit_expr.c:253:3: warning: enumeration value ‘EEOP_PARAM_SCHEMA_VARIABLE’ not handled in switch [-Wswitch]
   switch (opcode)
   ^

---

From: Peter Eisentraut <[email-masked]>
Date: Mon, 30 Apr 2018 21:56:45 -0400

On 4/20/18 13:45, Pavel Stehule wrote:
> The schema variable can hold composite value. The patch allows to use
> any composite type or adhoc composite values
> 

I'm not sure that this anonymous composite type thing is such a good
idea.  Such a variable will then be incompatible with anything else,
because it's of a different type.

In any case, I find that a weak argument for storing this in pg_class.
You could just as well create these pg_class entries implicitly and link
them from "pg_variable", same as composite types have a main entry in
pg_type and additional stuff in pg_class.

> It will be if variable is just scalar value without any possibilities.
> But then there is only low benefit
> 

In DB2, the privileges for variables are named READ and WRITE.  That
would make more sense to me than reusing the privilege names for tables.

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 1 May 2018 05:11:27 +0200

Hi

2018-05-01 3:56 GMT+02:00 Peter Eisentraut <[email-masked]
>:

> because it's of a different type.

Using anonymous composite type variable is just shortcut for situations
when mentioned feature is not a problem. These variables are global, so
there can be only one variable of some specific composite type, and
incompatibility with others is not a issue.

This feature can be interesting for short live temp variables - these
variables can be used for parametrization of anonymous block.

But this feature is not significant, and can be removed from patch.

> would make more sense to me than reusing the privilege names for tables.
good idea

Regards

Pavel

---

From: Gilles Darold <[email-masked]>
Date: Wed, 27 Jun 2018 12:21:16 +0200

Hi,

I'm reviewing the patch as it was flagged in the current commit fest. 
Here are my feedback:

  - The patch need to be rebased due to changes in file 
src/sgml/catalogs.sgml

  - Some compilation warning must be fixed:

    analyze.c: In function ‘transformLetStmt’:
    analyze.c:1568:17: warning: variable ‘rte’ set but not used
    [-Wunused-but-set-variable]
       RangeTblEntry *rte;
                      ^~~
    tab-complete.c:1268:21: warning: initialization from incompatible
    pointer type [-Wincompatible-pointer-types]
       {"VARIABLE", NULL, &Query_for_list_of_variables},

    In the last warning a NULL is missing, should be written:
    {"VARIABLE", NULL, NULL, &Query_for_list_of_variables},

  - How about Peter's suggestion?:
     "In DB2, the privileges for variables are named READ and WRITE. 
That would make more sense to me than reusing the privilege names for 
tables.
     The patch use SELECT and UPDATE which make sense too for SELECT but 
less for UPDATE.

  - The implementation of "ALTER VARIABLE varname SET SCHEMA 
schema_name;" is missing

  - ALTER VARIABLE var1 OWNER TO gilles; ok but not documented and 
missing in regression test

  - ALTER VARIABLE var1 RENAME TO var2; ok but not documented and 
missing in regression test

More generally I think that some comments must be rewritten, especially 
those talking about a PoC. In documentation there is HTML comments that 
can be removed.

Comment at end of file src/backend/commands/schemavar.c generate some 
"indent with spaces" errors with git apply but perhaps the comment can 
be entirely removed or undocumented details moved to the right place.

Otherwise all regression tests passed without issue and especially your 
new regression tests about schema variables.

I have a patch rebased, let me known if you want me to post the new diff.

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 27 Jun 2018 13:22:10 +0200

Hi

2018-06-27 12:21 GMT+02:00 Gilles Darold <[email-masked]>:

> Hi,
>
> I'm reviewing the patch as it was flagged in the current commit fest. Here
> are my feedback:
>
> In the last warning a NULL is missing, should be written: {"VARIABLE",
> NULL, NULL, &Query_for_list_of_variables},
>
>
>     "In DB2, the privileges for variables are named READ and WRITE. That
> less for UPDATE.
>
>  - The implementation of "ALTER VARIABLE varname SET SCHEMA schema_name;"
> is missing
>
>  - ALTER VARIABLE var1 OWNER TO gilles; ok but not documented and missing
> in regression test
>
>  - ALTER VARIABLE var1 RENAME TO var2; ok but not documented and missing
> in regression test
>
> those talking about a PoC. In documentation there is HTML comments that can
> be removed.
>
> "indent with spaces" errors with git apply but perhaps the comment can be
> entirely removed or undocumented details moved to the right place.
>

I plan significant refactoring of this patch for next commitfest. There was
anotherstrong Peter's and Robert comments

1. The schema variables should to have own system table
2. The composite schema variables should to use explicitly defined
composite type
3. The memory management is not nice - transactional drop table with
content is implemented ugly.

I hope, so I can start on these issues next month.

Thank you for review - I'll recheck ALTER commands.

Regards

Pavel

>
> --
> Gilles Darold
> Consultant PostgreSQLhttp://dalibo.com - http://dalibo.org
>
>

---

From: Gilles Darold <[email-masked]>
Date: Wed, 27 Jun 2018 19:15:54 +0200

Le 27/06/2018 à 13:22, Pavel Stehule a écrit :
> Hi
>
> 2018-06-27 12:21 GMT+02:00 Gilles Darold <[email-masked] 
> <mailto:[email-masked]>>:
>
>     Hi,
>
>     I'm reviewing the patch as it was flagged in the current commit
>     fest. Here are my feedback:
>
>         tab-complete.c:1268:21: warning: initialization from
>         incompatible pointer type [-Wincompatible-pointer-types]
>         "In DB2, the privileges for variables are named READ and
>     WRITE. That would make more sense to me than reusing the privilege
>     names for tables.
>
>         The patch use SELECT and UPDATE which make sense too for
>     SELECT but less for UPDATE.
>
>     More generally I think that some comments must be rewritten,
>     especially those talking about a PoC. In documentation there is
>     HTML comments that can be removed.
>
>     Comment at end of file src/backend/commands/schemavar.c generate
>     some "indent with spaces" errors with git apply but perhaps the
>     comment can be entirely removed or undocumented details moved to
>     the right place.
>
>     Otherwise all regression tests passed without issue and especially
>     your new regression tests about schema variables.
>
> I plan significant refactoring of this patch for next commitfest. 
> There was anotherstrong Peter's and Robert comments
>
>     Otherwise all regression tests passed without issue and especially
>     your new regression tests about schema variables.
>
> I plan significant refactoring of this patch for next commitfest. 
> There was anotherstrong Peter's and Robert c
> Regards

Ok Pavel, I've changed the status to "Waiting for authors" so that no 
one will make an other review until you send a new patch.

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 27 Jun 2018 19:17:05 +0200

2018-06-27 19:15 GMT+02:00 Gilles Darold <[email-masked]>:

> I plan significant refactoring of this patch for next commitfest. There
> was anotherstrong Peter's and Robert comments
>
> I plan significant refactoring of this patch for next commitfest. There
> was anotherstrong Peter's and Robert c
> Regards
>
>
> Ok Pavel, I've changed the status to "Waiting for authors" so that no one
> will make an other review until you send a new patch.
>
sure

Thank you

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 8 Aug 2018 22:29:29 +0200

> will make an other review until you send a new patch.
I am sending a new update of this feature. The functionality is +/- same
like previous patch, but a implementation is based on own catalog table.

I removed functions for manipulation with schema variables. These functions
can be added later simply. Now If we hold these functions, then we should
to solve often collision inside pg_proc.

Changes:

* own catalog - pg_variable
* the rights are renamed - READ|WRITE
* the code is cleaner

Regards

Pavel

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3bb48d4ccf..0a7a932ef5 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -359,6 +359,11 @@
       <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
       <entry>mappings of users to foreign servers</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+      <entry>schema variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -11255,7 +11260,6 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
 
  </sect1>
 
-
  <sect1 id="view-pg-views">
   <title><structname>pg_views</structname></title>
 
@@ -11311,4 +11315,104 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
 
  </sect1>
 
+ <sect1 id="catalog-pg-variable">
+  <title><structname>pg_variable</structname></title>
+
+  <indexterm zone="catalog-pg-variable">
+   <primary>pg_variable</primary>
+  </indexterm>
+
+  <para>
+   The table <structname>pg_variable</structname> holds metadata
+   of schema variables.
+  </para>
+
+  <table>
+   <title><structname>pg_views</structname> Columns</title>
+
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>varname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>Name of the schema variable</entry>
+     </row>
+
+     <row>
+      <entry><structfield>varnamespace</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the namespace that contains this variable
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>vartype</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the data type of this variable.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>vartypmod</structfield></entry>
+      <entry><type>int4</type></entry>
+      <entry></entry>
+      <entry>
+       <structfield>vartypmod</structfield> records type-specific data
+       supplied at table creation time (for example, the maximum
+       length of a <type>varchar</type> column).  It is passed to
+       type-specific input functions and length coercion functions.
+       The value will generally be -1 for types that do not need <structfield>vartypmod</structfield>.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>varowner</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.oid</literal></entry>
+      <entry>Owner of the variable</entry>
+     </row>
+
+     <row>
+      <entry><structfield>vardefexpr</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>The internal representation of the variable default value</entry>
+     </row>
+
+     <row>
+      <entry><structfield>varacl</structfield></entry>
+      <entry><type>aclitem[]</type></entry>
+      <entry></entry>
+      <entry>
+       Access privileges; see
+       <xref linkend="sql-grant"/> and
+       <xref linkend="sql-revoke"/>
+       for details
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
 </chapter>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index c81c87ef41..f5aaf60233 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -99,6 +99,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 +149,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 +157,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/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
new file mode 100644
index 0000000000..c8070051f5
--- /dev/null
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -0,0 +1,133 @@
+<!--
+doc/src/sgml/ref/create_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-createvariable">
+ <indexterm zone="sql-createvariable">
+  <primary>CREATE VARIABLE</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE VARIABLE</refname>
+  <refpurpose>define a new permissioned typed schema variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ]
+</synopsis>
+ </refsynopsisdiv>
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE VARIABLE</command> creates a new schema variable.
+   These variables are scalar typed, non-transactional, and, like relations,
+   exist within a schema with access controlled via
+   <command>GRANT</command> and <command>REVOKE</command>.
+  </para>
+
+  <para>
+   The value of a schema variable is session-local. Retrieving
+   a variable's value will return NULL unless its value has been set
+   to something else in the current session.
+  </para>
+
+  <para>
+   Retrieval is done via the <function>get_schema_variable</function>dunxrion or the SQL
+   command <command>SELECT</command>.  Setting of values is done via the
+   <function>set_schema_variable</function> function or the SQL command
+   <command>LET</command>.
+   Notably, while schema variables are in many ways a kind of table you cannot use
+   <command>UPDATE</command> on them.
+  </para>
+
+  <para>
+   For purposes of name uniqueness relation-like objects (e.g., tables, indexes)
+   within the same schema are considered.  i.e., you cannot give a table and a
+   schema variable the same name.  This is a consequence of them being treated
+   like relations for purposes of <command>SELECT</command>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF NOT EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the name already exists. A notice is issued in this case.
+      Note that type of the variable is not considered, nor could it be since the namespace
+      searched contains non-variable objects.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the variable to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">data_type</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the data type of the variable to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   Use <command>DROP VARIABLE</command> to remove a variable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create an integer variable <literal>var1</literal>:
+<programlisting>
+CREATE VARIABLE var1 AS integer;
+SELECT var1;
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE VARIABLE</command> is a PostgreSQL feature.
+   <!-- The choice of wording here seems to be left to personal preference... -->
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-dropvariable"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml
index 6b909b7232..d83ad811fd 100644
--- a/doc/src/sgml/ref/discard.sgml
+++ b/doc/src/sgml/ref/discard.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }
+DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP | VARIABLES }
 </synopsis>
  </refsynopsisdiv>
 
@@ -75,6 +75,17 @@ DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>VARIABLES</literal></term>
+    <listitem>
+     <para>
+      Resets the value of all schema variables. When variables
+      will be used later, then will be initialized again to
+      NULL or default value.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ALL</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml
new file mode 100644
index 0000000000..06130fd510
--- /dev/null
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -0,0 +1,92 @@
+<!--
+doc/src/sgml/ref/drop_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-dropvariable">
+ <indexterm zone="sql-dropvariable">
+  <primary>DROP VARIABLE</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP VARIABLE</refname>
+  <refpurpose>remove a schema variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP VARIABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [, ...] [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP VARIABLE</command> removes a schema variable.
+   A variable can only be dropped by its owner or a superuser.
+   <!-- this would suggest that we need an alter variable owner to command -->
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the variable does not exist. A notice is issued
+      in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of a schema variable.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To remove the schema variable <literal>var1</literal>:
+
+<programlisting>
+DROP VARIABLE var1;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>DROP VARIABLE</command> is proprietary PostgreSQL command.
+   <!-- create variable is a "PostgreSQL feature",
+        this is a "proprietary PostgreSQL command" ... -->
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createvariable"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index ff64c7a3ba..a83920a7a1 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -79,6 +79,10 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
     ON TYPE <replaceable>type_name</replaceable> [, ...]
     TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
 
+GRANT { READ | WRITE | ALL [ PRIVILEGES ] }
+    ON VARIABLE <replaceable>variable_name</replaceable> [, ...]
+    TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
+
 <phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase>
 
     [ GROUP ] <replaceable class="parameter">role_name</replaceable>
@@ -167,6 +171,7 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
    foreign servers,
    large objects,
    schemas,
+   schema variable
    or tablespaces.
    For other types of objects, the default privileges
    granted to <literal>PUBLIC</literal> are as follows:
@@ -385,6 +390,24 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>READ</literal></term>
+     <listitem>
+      <para>
+       Allows to read a schema variable.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>WRITE</literal></term>
+     <listitem>
+      <para>
+       Allows to set a schema variable.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><literal>ALL PRIVILEGES</literal></term>
      <listitem>
@@ -550,6 +573,8 @@ rolename=xxxx -- privileges granted to a role
             C -- CREATE
             c -- CONNECT
             T -- TEMPORARY
+            S -- READ
+            w -- WRITE
       arwdDxt -- ALL PRIVILEGES (for tables, varies for other objects)
             * -- grant option for preceding privilege
 
diff --git a/doc/src/sgml/ref/let.sgml b/doc/src/sgml/ref/let.sgml
new file mode 100644
index 0000000000..e8bf3f6dd4
--- /dev/null
+++ b/doc/src/sgml/ref/let.sgml
@@ -0,0 +1,90 @@
+<!--
+doc/src/sgml/ref/let.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-let">
+ <indexterm zone="sql-let">
+  <primary>LET</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>LET</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>LET</refname>
+  <refpurpose>change a schema variable's value</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+LET <replaceable class="parameter">schema_variable</replaceable> = <replaceable class="parameter">sql_expression</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   The <command>LET</command> command updates the specified schema variable' value.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>schema_variable</literal></term>
+    <listitem>
+     <para>
+      The name of schema variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>sql expression</literal></term>
+    <listitem>
+     <para>
+      An SQL expression, the result is cast to the schema variable's type.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+
+  <para>
+   Example:
+<programlisting>
+CREATE VARIABLE myvar AS integer;
+LET myvar = 10;
+LET myvar = (SELECT sum(val) FROM tab);
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <!-- this feels like it needs to be more specific,
+        but I don't know enough to make it so -->
+   <literal>LET</literal> extends syntax defined in the SQL
+   standard. The standard knows <literal>SET</literal> command,
+   that is used for different purpouse in PostgreSQL.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createvariable"/></member>
+   <member><xref linkend="sql-dropvariable"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 5317f8ccba..8435e05957 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -108,6 +108,12 @@ REVOKE [ GRANT OPTION FOR ]
 REVOKE [ ADMIN OPTION FOR ]
     <replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable class="parameter">role_name</replaceable> [, ...]
     [ CASCADE | RESTRICT ]
+
+REVOKE [ GRANT OPTION FOR ]
+    { { READ | WRITE } [, ...] | ALL [ PRIVILEGES ] }
+    ON VARIABLE <replaceable>variable_name</replaceable> [, ...]
+    FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
+    [ CASCADE | RESTRICT ]
 </synopsis>
  </refsynopsisdiv>
 
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index db4f4167e3..afcc69432d 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -127,6 +127,7 @@
    &createType;
    &createUser;
    &createUserMapping;
+   &createVariable;
    &createView;
    &deallocate;
    &declare;
@@ -175,6 +176,7 @@
    &dropType;
    &dropUser;
    &dropUserMapping;
+   &dropVariable;
    &dropView;
    &end;
    &execute;
@@ -183,6 +185,7 @@
    &grant;
    &importForeignSchema;
    &insert;
+   &let;
    &listen;
    &load;
    &lock;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 0865240f11..1f7c4d1223 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -19,7 +19,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_publication.o pg_range.o \
 	   pg_db_role_setting.o pg_shdepend.o pg_subscription.o pg_type.o \
-	   storage.o toasting.o
+	   pg_variable.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -46,7 +46,7 @@ CATALOG_HEADERS := \
 	pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
 	pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
-	pg_subscription_rel.h
+	pg_subscription_rel.h pg_variable.h
 
 GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 578e4c6592..86917e15a8 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -57,6 +57,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_transform.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
@@ -112,6 +113,7 @@ static void ExecGrant_Largeobject(InternalGrant *grantStmt);
 static void ExecGrant_Namespace(InternalGrant *grantStmt);
 static void ExecGrant_Tablespace(InternalGrant *grantStmt);
 static void ExecGrant_Type(InternalGrant *grantStmt);
+static void ExecGrant_Variable(InternalGrant *grantStmt);
 
 static void SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames);
 static void SetDefaultACL(InternalDefaultACL *iacls);
@@ -284,6 +286,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
 		case OBJECT_TYPE:
 			whole_mask = ACL_ALL_RIGHTS_TYPE;
 			break;
+		case OBJECT_VARIABLE:
+			whole_mask = ACL_ALL_RIGHTS_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d", objtype);
 			/* not reached, but keep compiler quiet */
@@ -507,6 +512,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
 			all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER;
 			errormsg = gettext_noop("invalid privilege type %s for foreign server");
 			break;
+		case OBJECT_VARIABLE:
+			all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			errormsg = gettext_noop("invalid privilege type %s for schema variable");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) stmt->objtype);
@@ -609,6 +618,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 		case OBJECT_TABLESPACE:
 			ExecGrant_Tablespace(istmt);
 			break;
+		case OBJECT_VARIABLE:
+			ExecGrant_Variable(istmt);
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
@@ -768,6 +780,16 @@ objectNamesToOids(ObjectType objtype, List *objnames)
 				objects = lappend_oid(objects, srvid);
 			}
 			break;
+		case OBJECT_VARIABLE:
+			foreach(cell, objnames)
+			{
+				RangeVar   *varvar = (RangeVar *) lfirst(cell);
+				Oid			relOid;
+
+				relOid = lookup_variable(varvar->schemaname, varvar->relname, false);
+				objects = lappend_oid(objects, relOid);
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) objtype);
@@ -855,6 +877,31 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 					heap_close(rel, AccessShareLock);
 				}
 				break;
+			case OBJECT_VARIABLE:
+				{
+					ScanKeyData key;
+					Relation	rel;
+					HeapScanDesc scan;
+					HeapTuple	tuple;
+
+					ScanKeyInit(&key,
+								Anum_pg_variable_varnamespace,
+								BTEqualStrategyNumber, F_OIDEQ,
+								ObjectIdGetDatum(namespaceId));
+
+					rel = heap_open(VariableRelationId, AccessShareLock);
+					scan = heap_beginscan_catalog(rel, 1, &key);
+
+					while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+					{
+						objects = lappend_oid(objects, HeapTupleGetOid(tuple));
+					}
+
+					heap_endscan(scan);
+					heap_close(rel, AccessShareLock);
+				}
+				break;
+
 			default:
 				/* should not happen */
 				elog(ERROR, "unrecognized GrantStmt.objtype: %d",
@@ -1018,6 +1065,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
 			all_privileges = ACL_ALL_RIGHTS_SCHEMA;
 			errormsg = gettext_noop("invalid privilege type %s for schema");
 			break;
+		case OBJECT_VARIABLE:
+			all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			errormsg = gettext_noop("invalid privilege type %s for schema variable");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) action->objtype);
@@ -1215,6 +1266,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
 				this_privileges = ACL_ALL_RIGHTS_SCHEMA;
 			break;
 
+		case OBJECT_VARIABLE:
+			objtype = DEFACLOBJ_VARIABLE;
+			if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
+				this_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			break;
+
 		default:
 			elog(ERROR, "unrecognized objtype: %d",
 				 (int) iacls->objtype);
@@ -1441,6 +1498,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case DEFACLOBJ_NAMESPACE:
 				iacls.objtype = OBJECT_SCHEMA;
 				break;
+			case DEFACLOBJ_VARIABLE:
+				iacls.objtype = OBJECT_VARIABLE;
+				break;
 			default:
 				/* Shouldn't get here */
 				elog(ERROR, "unexpected default ACL type: %d",
@@ -3266,6 +3326,129 @@ ExecGrant_Type(InternalGrant *istmt)
 	heap_close(relation, RowExclusiveLock);
 }
 
+static void
+ExecGrant_Variable(InternalGrant *istmt)
+{
+	Relation	relation;
+	ListCell   *cell;
+
+	if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
+		istmt->privileges = ACL_ALL_RIGHTS_VARIABLE;
+
+	relation = heap_open(VariableRelationId, RowExclusiveLock);
+
+	foreach(cell, istmt->objects)
+	{
+		Oid			varId = lfirst_oid(cell);
+		Form_pg_variable pg_variable_tuple;
+		Datum		aclDatum;
+		bool		isNull;
+		AclMode		avail_goptions;
+		AclMode		this_privileges;
+		Acl		   *old_acl;
+		Acl		   *new_acl;
+		Oid			grantorId;
+		Oid			ownerId;
+		HeapTuple	tuple;
+		HeapTuple	newtuple;
+		Datum		values[Natts_pg_variable];
+		bool		nulls[Natts_pg_variable];
+		bool		replaces[Natts_pg_variable];
+		int			noldmembers;
+		int			nnewmembers;
+		Oid		   *oldmembers;
+		Oid		   *newmembers;
+
+		tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varId));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for schema variables %u", varId);
+
+		pg_variable_tuple = (Form_pg_variable) GETSTRUCT(tuple);
+
+		/*
+		 * Get owner ID and working copy of existing ACL. If there's no ACL,
+		 * substitute the proper default.
+		 */
+		ownerId = pg_variable_tuple->varowner;
+		aclDatum = SysCacheGetAttr(VARIABLEOID, tuple, Anum_pg_variable_varacl,
+								   &isNull);
+		if (isNull)
+		{
+			old_acl = acldefault(OBJECT_VARIABLE, ownerId);
+			/* There are no old member roles according to the catalogs */
+			noldmembers = 0;
+			oldmembers = NULL;
+		}
+		else
+		{
+			old_acl = DatumGetAclPCopy(aclDatum);
+			/* Get the roles mentioned in the existing ACL */
+			noldmembers = aclmembers(old_acl, &oldmembers);
+		}
+
+		/* Determine ID to do the grant as, and available grant options */
+		select_best_grantor(GetUserId(), istmt->privileges,
+							old_acl, ownerId,
+							&grantorId, &avail_goptions);
+
+		/*
+		 * Restrict the privileges to what we can actually grant, and emit the
+		 * standards-mandated warning and error messages.
+		 */
+		this_privileges =
+			restrict_and_check_grant(istmt->is_grant, avail_goptions,
+									 istmt->all_privs, istmt->privileges,
+									 varId, grantorId, OBJECT_VARIABLE,
+									 NameStr(pg_variable_tuple->varname),
+									 0, NULL);
+
+		/*
+		 * Generate new ACL.
+		 */
+		new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
+									   istmt->grant_option, istmt->behavior,
+									   istmt->grantees, this_privileges,
+									   grantorId, ownerId);
+
+		/*
+		 * We need the members of both old and new ACLs so we can correct the
+		 * shared dependency information.
+		 */
+		nnewmembers = aclmembers(new_acl, &newmembers);
+
+		/* finished building new ACL value, now insert it */
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, false, sizeof(nulls));
+		MemSet(replaces, false, sizeof(replaces));
+
+		replaces[Anum_pg_variable_varacl - 1] = true;
+		values[Anum_pg_variable_varacl - 1] = PointerGetDatum(new_acl);
+
+		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
+									 nulls, replaces);
+
+		CatalogTupleUpdate(relation, &newtuple->t_self, newtuple);
+
+		/* Update initial privileges for extensions */
+		recordExtensionInitPriv(varId, VariableRelationId, 0, new_acl);
+
+		/* Update the shared dependency ACL info */
+		updateAclDependencies(VariableRelationId, varId, 0,
+							  ownerId,
+							  noldmembers, oldmembers,
+							  nnewmembers, newmembers);
+
+		ReleaseSysCache(tuple);
+
+		pfree(new_acl);
+
+		/* prevent error when processing duplicate objects */
+		CommandCounterIncrement();
+	}
+
+	heap_close(relation, RowExclusiveLock);
+}
+
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3298,6 +3481,10 @@ string_to_privilege(const char *privname)
 		return ACL_CONNECT;
 	if (strcmp(privname, "rule") == 0)
 		return 0;				/* ignore old RULE privileges */
+	if (strcmp(privname, "read") == 0)
+		return ACL_READ;
+	if (strcmp(privname, "write") == 0)
+		return ACL_WRITE;
 	ereport(ERROR,
 			(errcode(ERRCODE_SYNTAX_ERROR),
 			 errmsg("unrecognized privilege type \"%s\"", privname)));
@@ -3333,6 +3520,10 @@ privilege_to_string(AclMode privilege)
 			return "TEMP";
 		case ACL_CONNECT:
 			return "CONNECT";
+		case ACL_READ:
+			return "READ";
+		case ACL_WRITE:
+			return "WRITE";
 		default:
 			elog(ERROR, "unrecognized privilege: %d", (int) privilege);
 	}
@@ -3456,6 +3647,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("permission denied for type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("permission denied for schema variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("permission denied for view %s");
 						break;
@@ -3566,6 +3760,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("must be owner of type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("must be owner of schema variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("must be owner of view %s");
 						break;
@@ -3710,6 +3907,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return pg_type_aclmask(table_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return pg_variable_aclmask(table_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized objtype: %d",
 				 (int) objtype);
@@ -4499,6 +4698,67 @@ pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how)
 	return result;
 }
 
+/*
+ * Exported routine for examining a user's privileges for a variable.
+ */
+AclMode
+pg_variable_aclmask(Oid var_oid, Oid roleid, AclMode mask, AclMaskHow how)
+{
+	AclMode		result;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+	Acl		   *acl;
+	Oid			ownerId;
+
+	Form_pg_variable varForm;
+
+	/* Bypass permission checks for superusers */
+	if (superuser_arg(roleid))
+		return mask;
+
+	/*
+	 * Must get the type's tuple from pg_type
+	 */
+	tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(var_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("variable with OID %u does not exist",
+						var_oid)));
+	varForm = (Form_pg_variable) GETSTRUCT(tuple);
+
+	/*
+	 * Now get the type's owner and ACL from the tuple
+	 */
+	ownerId = varForm->varowner;
+
+	aclDatum = SysCacheGetAttr(VARIABLEOID, tuple,
+							   Anum_pg_variable_varacl, &isNull);
+	if (isNull)
+	{
+		/* No ACL, so build default ACL */
+		acl = acldefault(OBJECT_VARIABLE, ownerId);
+		aclDatum = (Datum) 0;
+	}
+	else
+	{
+		/* detoast rel's ACL if necessary */
+		acl = DatumGetAclP(aclDatum);
+	}
+
+	result = aclmask(acl, roleid, ownerId, mask, how);
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+
 /*
  * Exported routine for checking a user's access privileges to a column
  *
@@ -4744,6 +5004,18 @@ pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
 		return ACLCHECK_NO_PRIV;
 }
 
+/*
+ * Exported routine for checking a user's access privileges to a variable
+ */
+AclResult
+pg_variable_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
+{
+	if (pg_variable_aclmask(type_oid, roleid, mode, ACLMASK_ANY) != 0)
+		return ACLCHECK_OK;
+	else
+		return ACLCHECK_NO_PRIV;
+}
+
 /*
  * Ownership check for a relation (specified by OID).
  */
@@ -5361,6 +5633,33 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid)
 	return has_privs_of_role(roleid, ownerId);
 }
 
+/*
+ * Ownership check for a schema variables (specified by OID).
+ */
+bool
+pg_variable_ownercheck(Oid db_oid, Oid roleid)
+{
+	HeapTuple	tuple;
+	Oid			ownerId;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return true;
+
+	tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(db_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_DATABASE),
+				 errmsg("variable with OID %u does not exist", db_oid)));
+
+	ownerId = ((Form_pg_variable) GETSTRUCT(tuple))->varowner;
+
+	ReleaseSysCache(tuple);
+
+	return has_privs_of_role(roleid, ownerId);
+}
+
+
 /*
  * Check whether specified role has CREATEROLE privilege (or is a superuser)
  *
@@ -5486,6 +5785,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
 			defaclobjtype = DEFACLOBJ_NAMESPACE;
 			break;
 
+		case OBJECT_VARIABLE:
+			defaclobjtype = DEFACLOBJ_VARIABLE;
+			break;
+
 		default:
 			return NULL;
 	}
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 4f1d365357..782ddb1655 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -59,6 +59,7 @@
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
@@ -67,6 +68,7 @@
 #include "commands/proclang.h"
 #include "commands/publicationcmds.h"
 #include "commands/schemacmds.h"
+#include "commands/schemavariable.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
 #include "commands/trigger.h"
@@ -1280,6 +1282,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			DropTransformById(object->objectId);
 			break;
 
+		case OCLASS_VARIABLE:
+			RemoveVariableById(object->objectId);
+			break;
+
 			/*
 			 * These global object types are not supported here.
 			 */
@@ -2537,6 +2543,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case TransformRelationId:
 			return OCLASS_TRANSFORM;
+
+		case VariableRelationId:
+			return OCLASS_VARIABLE;
 	}
 
 	/* shouldn't get here */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 0f67a122ed..81aaf454a8 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -39,6 +39,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
@@ -755,6 +756,71 @@ RelationIsVisible(Oid relid)
 	return visible;
 }
 
+/*
+ * VariableIsVisible
+ *		Determine whether a variable (identified by OID) is visible in the
+ *		current search path.  Visible means "would be found by searching
+ *		for the unqualified variable name".
+ */
+bool
+VariableIsVisible(Oid varid)
+{
+	HeapTuple	vartup;
+	Form_pg_variable varform;
+	Oid			varnamespace;
+	bool		visible;
+
+	vartup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+	if (!HeapTupleIsValid(vartup))
+		elog(ERROR, "cache lookup failed for schema variable %u", varid);
+	varform = (Form_pg_variable) GETSTRUCT(vartup);
+
+	recomputeNamespacePath();
+
+	/*
+	 * Quick check: if it ain't in the path at all, it ain't visible. Items in
+	 * the system namespace are surely in the path and so we needn't even do
+	 * list_member_oid() for them.
+	 */
+	varnamespace = varform->varnamespace;
+	if (varnamespace != PG_CATALOG_NAMESPACE &&
+		!list_member_oid(activeSearchPath, varnamespace))
+		visible = false;
+	else
+	{
+		/*
+		 * If it is in the path, it might still not be visible; it could be
+		 * hidden by another relation of the same name earlier in the path. So
+		 * we must do a slow check for conflicting relations.
+		 */
+		char	   *varname = NameStr(varform->varname);
+		ListCell   *l;
+
+		visible = false;
+		foreach(l, activeSearchPath)
+		{
+			Oid			namespaceId = lfirst_oid(l);
+
+			if (namespaceId == varnamespace)
+			{
+				/* Found it first in path */
+				visible = true;
+				break;
+			}
+			if (OidIsValid(get_varname_varid(varname, namespaceId)))
+			{
+				/* Found something else first in path */
+				break;
+			}
+		}
+	}
+
+	ReleaseSysCache(vartup);
+
+	return visible;
+}
+
+
 
 /*
  * TypenameGetTypid
@@ -2776,6 +2842,202 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
+/*
+ * When we know a variable name, then we can find variable simply
+ */
+Oid
+lookup_variable(const char *nspname, const char *varname, bool missing_ok)
+{
+	Oid			namespaceId;
+	Oid			varoid		= InvalidOid;
+	ListCell   *l;
+
+	if (nspname)
+	{
+		namespaceId = LookupExplicitNamespace(nspname, missing_ok);
+		if (!OidIsValid(namespaceId))
+			return InvalidOid;
+
+		varoid = GetSysCacheOid2(VARIABLENAMENSP,
+								  PointerGetDatum(varname),
+								  ObjectIdGetDatum(namespaceId));
+	}
+	else
+	{
+		/* search for it in search path */
+		recomputeNamespacePath();
+
+		foreach(l, activeSearchPath)
+		{
+			namespaceId = lfirst_oid(l);
+
+			varoid = GetSysCacheOid2(VARIABLENAMENSP,
+									  PointerGetDatum(varname),
+									  ObjectIdGetDatum(namespaceId));
+
+			if (OidIsValid(varoid))
+				break;
+		}
+	}
+
+	if (!OidIsValid(varoid) && !missing_ok)
+	{
+		if (nspname)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("variable \"%s\".\"%s\" does not exist",
+								nspname, varname)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("variable \"%s\" does not exist",
+								varname)));
+	}
+
+	return varoid;
+}
+
+List *
+NamesFromList(List *names)
+{
+	ListCell *l;
+	List	*result = NIL;
+
+	foreach(l, names)
+	{
+		Node *n = lfirst(l);
+
+		if (IsA(n, String))
+		{
+			result = lappend(result, n);
+		}
+		else
+			break;
+	}
+
+	return result;
+}
+
+/*
+ * identify_variable
+ *
+ * Returns oid of not ambigonuous variable specified by qualified path
+ * or InvalidOid. When the path is ambigonuous, then not_uniq flag is
+ * is true.
+ */
+Oid
+identify_variable(List *names, char **attrname, bool *not_uniq)
+{
+	char   *a = NULL;
+	char   *b = NULL;
+	char   *c = NULL;
+	char   *d = NULL;
+	Oid		varoid_without_attr;
+	Oid		varoid_with_attr;
+
+	*not_uniq = false;
+
+	switch (list_length(names))
+	{
+		case 1:
+			a = strVal(linitial(names));
+			return lookup_variable(NULL, a, true);
+
+		case 2:
+			a = strVal(linitial(names));
+			b = strVal(lsecond(names));
+
+			/*
+			 * a.b can mean "schema"."variable" or "variable"."field",
+			 * Check both variants, and returns InvalidOid with not_uniq
+			 * flag, when both interpretations are possible.
+			 */
+			varoid_without_attr = lookup_variable(a, b, true);
+			varoid_with_attr = lookup_variable(NULL, a, true);
+
+			if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+			{
+				*not_uniq = true;
+				return InvalidOid;
+			}
+			else if (OidIsValid(varoid_without_attr))
+			{
+				*attrname = NULL;
+				return varoid_without_attr;
+			}
+			else
+			{
+				*attrname = b;
+				return varoid_with_attr;
+			}
+			break;
+
+		case 3:
+			a = strVal(linitial(names));
+			b = strVal(lsecond(names));
+			c = strVal(lthird(names));
+
+			/*
+			 * a.b.c can mean "catalog"."schema"."variable" or "schema"."variable"."field",
+			 * Check both variants, and returns InvalidOid with not_uniq
+			 * flag, when both interpretations are possible.
+			 */
+			varoid_without_attr = lookup_variable(b, c, true);
+			varoid_with_attr = lookup_variable(a, b, true);
+
+			if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+			{
+				*not_uniq = true;
+				return InvalidOid;
+			}
+			else if (OidIsValid(varoid_without_attr))
+			{
+				*attrname = NULL;
+
+				/*
+				 * We in this case a "a" is used as catalog name, check it.
+				 */
+				if (strcmp(a, get_database_name(MyDatabaseId)) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("cross-database references are not implemented: %s",
+									NameListToString(names))));
+
+				return varoid_without_attr;
+			}
+			else
+			{
+				*attrname = c;
+				return varoid_with_attr;
+			}
+			break;
+
+		case 4:
+			a = strVal(linitial(names));
+			b = strVal(lsecond(names));
+			c = strVal(lthird(names));
+			d = strVal(lfourth(names));
+
+			/*
+			 * We in this case a "a" is used as catalog name, check it.
+			 */
+			if (strcmp(a, get_database_name(MyDatabaseId)) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cross-database references are not implemented: %s",
+								NameListToString(names))));
+
+			*attrname = d;
+			return lookup_variable(b, c, true);
+
+		default:
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("improper qualified name (too many dotted names): %s",
+							NameListToString(names))));
+			break;
+	}
+}
 
 /*
  * DeconstructQualifiedName
@@ -4416,3 +4678,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(isOtherTempNamespace(oid));
 }
+
+Datum
+pg_variable_is_visible(PG_FUNCTION_ARGS)
+{
+	Oid			oid = PG_GETARG_OID(0);
+
+	if (!SearchSysCacheExists1(VARIABLEOID, ObjectIdGetDatum(oid)))
+		PG_RETURN_NULL();
+
+	PG_RETURN_BOOL(VariableIsVisible(oid));
+}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 7db942dcba..cc3d415e61 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -58,6 +58,7 @@
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
@@ -489,6 +490,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,		/* no ACL (same as relation) */
 		OBJECT_STATISTIC_EXT,
 		true
+	},
+	{
+		VariableRelationId,
+		VariableObjectIndexId,
+		VARIABLEOID,
+		VARIABLENAMENSP,
+		Anum_pg_variable_varname,
+		Anum_pg_variable_varnamespace,
+		Anum_pg_variable_varowner,
+		Anum_pg_variable_varacl,
+		OBJECT_VARIABLE,
+		true
 	}
 };
 
@@ -714,6 +727,10 @@ static const struct object_type_map
 	/* OBJECT_STATISTIC_EXT */
 	{
 		"statistics object", OBJECT_STATISTIC_EXT
+	},
+	/* OCLASS_VARIABLE */
+	{
+		"schema variable", OBJECT_VARIABLE
 	}
 };
 
@@ -739,6 +756,7 @@ static ObjectAddress get_object_address_attrdef(ObjectType objtype,
 						   bool missing_ok);
 static ObjectAddress get_object_address_type(ObjectType objtype,
 						TypeName *typename, bool missing_ok);
+static ObjectAddress get_object_address_variable(List *object, bool missing_ok);
 static ObjectAddress get_object_address_opcf(ObjectType objtype, List *object,
 						bool missing_ok);
 static ObjectAddress get_object_address_opf_member(ObjectType objtype,
@@ -996,6 +1014,10 @@ get_object_address(ObjectType objtype, Node *object,
 															 missing_ok);
 				address.objectSubId = 0;
 				break;
+			case OBJECT_VARIABLE:
+				address = get_object_address_variable(castNode(List, object), missing_ok);
+				break;
+
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				/* placate compiler, in case it thinks elog might return */
@@ -1848,16 +1870,20 @@ get_object_address_defacl(List *object, bool missing_ok)
 		case DEFACLOBJ_NAMESPACE:
 			objtype_str = "schemas";
 			break;
+		case DEFACLOBJ_VARIABLE:
+			objtype_str = "variables";
+			break;
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("unrecognized default ACL object type \"%c\"", objtype),
-					 errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
+					 errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
 							 DEFACLOBJ_RELATION,
 							 DEFACLOBJ_SEQUENCE,
 							 DEFACLOBJ_FUNCTION,
 							 DEFACLOBJ_TYPE,
-							 DEFACLOBJ_NAMESPACE)));
+							 DEFACLOBJ_NAMESPACE,
+							 DEFACLOBJ_VARIABLE)));
 	}
 
 	/*
@@ -1942,6 +1968,24 @@ textarray_to_strvaluelist(ArrayType *arr)
 	return list;
 }
 
+/*
+ * Find the ObjectAddress for a type or domain
+ */
+static ObjectAddress
+get_object_address_variable(List *object, bool missing_ok)
+{
+	ObjectAddress address;
+	char	   *nspname = NULL;
+	char	   *varname = NULL;
+
+	ObjectAddressSet(address, VariableRelationId, InvalidOid);
+
+	DeconstructQualifiedName(object, &nspname, &varname);
+	address.objectId = lookup_variable(nspname, varname, missing_ok);
+
+	return address;
+}
+
 /*
  * SQL-callable version of get_object_address
  */
@@ -2131,6 +2175,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 		case OBJECT_TABCONSTRAINT:
 		case OBJECT_OPCLASS:
 		case OBJECT_OPFAMILY:
+		case OBJECT_VARIABLE:
 			objnode = (Node *) name;
 			break;
 		case OBJECT_ACCESS_METHOD:
@@ -2415,6 +2460,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			if (!pg_statistics_object_ownercheck(address.objectId, roleid))
 				aclcheck_error_type(ACLCHECK_NOT_OWNER, address.objectId);
 			break;
+		case OBJECT_VARIABLE:
+			if (!pg_variable_ownercheck(address.objectId, roleid))
+				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
+							   NameListToString(castNode(List, object)));
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) objtype);
@@ -3157,6 +3207,32 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
+				Form_pg_variable	varform;
+
+				tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for schema variable %u",
+						 object->objectId);
+
+				varform = (Form_pg_variable) GETSTRUCT(tup);
+
+				if (VariableIsVisible(object->objectId))
+					nspname = NULL;
+				else
+					nspname = get_namespace_name(varform->varnamespace);
+
+				appendStringInfo(&buffer, _("schema variable %s"),
+								 quote_qualified_identifier(nspname,
+															NameStr(varform->varname)));
+
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		case OCLASS_TSPARSER:
 			{
 				HeapTuple	tup;
@@ -3422,6 +3498,16 @@ getObjectDescription(const ObjectAddress *object)
 										 _("default privileges on new schemas belonging to role %s"),
 										 rolename);
 						break;
+					case DEFACLOBJ_VARIABLE:
+						if (nspname)
+							appendStringInfo(&buffer,
+											 _("default privileges on new variables belonging to role %s in schema %s"),
+											 rolename, nspname);
+						else
+							appendStringInfo(&buffer,
+											 _("default privileges on new variables belonging to role %s"),
+											 rolename);
+						break;
 					default:
 						/* shouldn't get here */
 						if (nspname)
@@ -4070,6 +4156,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "transform");
 			break;
 
+		case OCLASS_VARIABLE:
+			appendStringInfoString(&buffer, "schema variable");
+			break;
+
 			/*
 			 * There's intentionally no default: case here; we want the
 			 * compiler to warn if a new OCLASS hasn't been handled above.
@@ -4962,6 +5052,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 						appendStringInfoString(&buffer,
 											   " on schemas");
 						break;
+					case DEFACLOBJ_VARIABLE:
+						appendStringInfoString(&buffer,
+											   " on variables");
+						break;
 				}
 
 				if (objname)
@@ -5121,6 +5215,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
+				Form_pg_variable	varform;
+
+				tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for schema variable %u",
+						 object->objectId);
+
+				varform = (Form_pg_variable) GETSTRUCT(tup);
+
+				schema = get_namespace_name_or_temp(varform->varnamespace);
+				varname = NameStr(varform->varname);
+
+				appendStringInfo(&buffer, "%s",
+								quote_qualified_identifier(schema, varname));
+
+				if (objname)
+					*objname = list_make2(schema, varname);
+
+				ReleaseSysCache(tup);
+				break;
+			}
+
 			/*
 			 * There's intentionally no default: case here; we want the
 			 * compiler to warn if a new OCLASS hasn't been handled above.
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
new file mode 100644
index 0000000000..ff71f8bf6a
--- /dev/null
+++ b/src/backend/catalog/pg_variable.c
@@ -0,0 +1,305 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.c
+ *		schema variables
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		src/backend/catalog/pg_variable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/xact.h"
+
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
+
+#include "nodes/makefuncs.h"
+
+#include "storage/lmgr.h"
+
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/pg_lsn.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * Returns name of schema variable. When variable is not on path,
+ * then the name is qualified.
+ */
+char *
+schema_variable_get_name(Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	char   *varname;
+	char   *nspname;
+	char   *result;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	varname = NameStr(varform->varname);
+
+	if (VariableIsVisible(varid))
+		nspname = NULL;
+	else
+		nspname = get_namespace_name(varform->varnamespace);
+
+	result = quote_qualified_identifier(nspname, varname);
+
+	ReleaseSysCache(tup);
+
+	return result;
+}
+
+/*
+ * Returns varname field of pg_variable
+ */
+char *
+get_schema_variable_name(Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	char   *varname;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	varname = NameStr(varform->varname);
+
+	ReleaseSysCache(tup);
+
+	return varname;
+}
+
+/*
+ * Returns type, typmod of schema variable
+ */
+void
+get_schema_variable_type_typmod(Oid varid, Oid *typid, int32 *typmod)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	*typid = varform->vartype;
+	*typmod = varform->vartypmod;
+
+	ReleaseSysCache(tup);
+
+	return;
+}
+
+/*
+ * Fetch all fields of schema variable from the syscache.
+ */
+Variable *
+GetVariable(Oid varid, bool missing_ok)
+{
+	HeapTuple	tup;
+	Variable *var;
+	Form_pg_variable varform;
+	Datum		aclDatum;
+	Datum		defexprDatum;
+	bool		isnull;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+	{
+		if (missing_ok)
+			return NULL;
+
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+	}
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	var = (Variable *) palloc(sizeof(Variable));
+	var->oid = varid;
+	var->name = pstrdup(NameStr(varform->varname));
+	var->namespace = varform->varnamespace;
+	var->typid = varform->vartype;
+	var->typmod = varform->vartypmod;
+	var->owner = varform->varowner;
+
+	/* Get defexpr */
+	defexprDatum = SysCacheGetAttr(VARIABLEOID,
+							tup,
+							Anum_pg_variable_vardefexpr,
+							&isnull);
+
+	if (!isnull)
+		var->defexpr = stringToNode(TextDatumGetCString(defexprDatum));
+	else
+		var->defexpr = NULL;
+
+	/* Get varacl */
+	aclDatum = SysCacheGetAttr(VARIABLEOID,
+							tup,
+							Anum_pg_variable_varacl,
+							&isnull);
+	if (!isnull)
+		var->acl = DatumGetAclPCopy(aclDatum);
+	else
+		var->acl = NULL;
+
+	ReleaseSysCache(tup);
+
+	return var;
+}
+
+ObjectAddress
+VariableCreate(const char *varName,
+			   Oid varNamespace,
+			   Oid varType,
+			   int32 varTypmod,
+			   Oid varOwner,
+			   Node *varDefexpr,
+			   bool if_not_exists)
+{
+	Acl		   *varacl;
+	NameData	varname;
+	bool		nulls[Natts_pg_variable];
+	Datum		values[Natts_pg_variable];
+	Relation	rel;
+	HeapTuple	tup,
+				oldtup;
+	TupleDesc	tupdesc;
+	ObjectAddress	myself,
+					referenced;
+	Oid			retval;
+	int			i;
+
+	for (i = 0; i < Natts_pg_variable; i++)
+	{
+		nulls[i] = false;
+		values[i] = (Datum) 0;
+	}
+
+	namestrcpy(&varname, varName);
+	values[Anum_pg_variable_varname - 1] = NameGetDatum(&varname);
+	values[Anum_pg_variable_varnamespace - 1] = ObjectIdGetDatum(varNamespace);
+	values[Anum_pg_variable_vartype - 1] = ObjectIdGetDatum(varType);
+	values[Anum_pg_variable_vartypmod - 1] = Int32GetDatum(varTypmod);
+	values[Anum_pg_variable_varowner - 1] = ObjectIdGetDatum(varOwner);
+	/* proacl will be determined later */
+
+	if (varDefexpr)
+		values[Anum_pg_variable_vardefexpr - 1] = CStringGetTextDatum(nodeToString(varDefexpr));
+	else
+		nulls[Anum_pg_variable_vardefexpr - 1] = true;
+
+	rel = heap_open(VariableRelationId, RowExclusiveLock);
+	tupdesc = RelationGetDescr(rel);
+
+	oldtup = SearchSysCache2(VARIABLENAMENSP,
+							 PointerGetDatum(varName),
+							 ObjectIdGetDatum(varNamespace));
+
+	if (HeapTupleIsValid(oldtup))
+	{
+		if (if_not_exists)
+			ereport(NOTICE,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("schema variable \"%s\" already exists, skipping",
+							varName)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("schema variable \"%s\" already exists",
+							varName)));
+
+		heap_freetuple(oldtup);
+		heap_close(rel, RowExclusiveLock);
+
+		return InvalidObjectAddress;
+	}
+
+	varacl = get_user_default_acl(OBJECT_VARIABLE, varOwner,
+								  varNamespace);
+
+	if (varacl != NULL)
+		values[Anum_pg_variable_varacl - 1] = PointerGetDatum(varacl);
+	else
+		nulls[Anum_pg_variable_varacl - 1] = true;
+
+	tup = heap_form_tuple(tupdesc, values, nulls);
+	CatalogTupleInsert(rel, tup);
+
+	retval = HeapTupleGetOid(tup);
+
+	myself.classId = VariableRelationId;
+	myself.objectId = retval;
+	myself.objectSubId = 0;
+
+	/* dependency on namespace */
+	referenced.classId = NamespaceRelationId;
+	referenced.objectId = varNamespace;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/* dependency on used type */
+	referenced.classId = TypeRelationId;
+	referenced.objectId = varType;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/* dependency on any roles mentioned in ACL */
+	if (varacl != NULL)
+	{
+		int			nnewmembers;
+		Oid		   *newmembers;
+
+		nnewmembers = aclmembers(varacl, &newmembers);
+		updateAclDependencies(VariableRelationId, retval, 0,
+							  varOwner,
+							  0, NULL,
+							  nnewmembers, newmembers);
+	}
+
+	/* dependency on extension */
+	recordDependencyOnCurrentExtension(&myself, false);
+
+	heap_freetuple(tup);
+
+	/* Post creation hook for new function */
+	InvokeObjectPostCreateHook(VariableRelationId, retval, 0);
+
+	heap_close(rel, RowExclusiveLock);
+
+	return myself;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 4a6c99e090..2cb5b1172d 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -18,7 +18,7 @@ OBJS = amcmds.o aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	policy.o portalcmds.o prepare.o proclang.o publicationcmds.o \
-	schemacmds.o seclabel.o sequence.o statscmds.o subscriptioncmds.o \
+	schemacmds.o seclabel.o sequence.o schemavariable.o statscmds.o subscriptioncmds.o \
 	tablecmds.o tablespace.o trigger.o tsearchcmds.o typecmds.o user.o \
 	vacuum.o vacuumlazy.o variable.o view.o
 
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index eff325cc7d..a9d5e5e0ad 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -387,6 +387,7 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_PUBLICATION:
 		case OBJECT_SUBSCRIPTION:
+		case OBJECT_VARIABLE:
 			{
 				ObjectAddress address;
 				Relation	catalog;
@@ -504,6 +505,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_VARIABLE:
 			{
 				Relation	catalog;
 				Relation	relation;
@@ -594,6 +596,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 		case OCLASS_TSDICT:
 		case OCLASS_TSTEMPLATE:
 		case OCLASS_TSCONFIG:
+		case OCLASS_VARIABLE:
 			{
 				Relation	catalog;
 
@@ -852,6 +855,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 		case OBJECT_TABLESPACE:
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSCONFIGURATION:
+		case OBJECT_VARIABLE:
 			{
 				Relation	catalog;
 				Relation	relation;
diff --git a/src/backend/commands/discard.c b/src/backend/commands/discard.c
index 01a999c2ac..fec2495e93 100644
--- a/src/backend/commands/discard.c
+++ b/src/backend/commands/discard.c
@@ -19,6 +19,7 @@
 #include "commands/discard.h"
 #include "commands/prepare.h"
 #include "commands/sequence.h"
+#include "commands/schemavariable.h"
 #include "utils/guc.h"
 #include "utils/portal.h"

+		case DISCARD_VARIABLES:
+			ResetSchemaVariableCache();
+			break;
+
 		default:
 			elog(ERROR, "unrecognized DISCARD target: %d", stmt->target);
 	}
@@ -75,4 +80,5 @@ DiscardAll(bool isTopLevel)
 	ResetPlanCache();
 	ResetTempTableNamespace();
 	ResetSequenceCaches();
+	ResetSchemaVariableCache();
 }
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index eecc85d14e..426df246b3 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -126,6 +126,7 @@ static event_trigger_support_data event_trigger_support[] = {
 	{"TEXT SEARCH TEMPLATE", true},
 	{"TYPE", true},
 	{"USER MAPPING", true},
+	{"VARIABLE", true},
 	{"VIEW", true},
 	{NULL, false}
 };
@@ -297,7 +298,8 @@ check_ddl_tag(const char *tag)
 		pg_strcasecmp(tag, "REVOKE") == 0 ||
 		pg_strcasecmp(tag, "DROP OWNED") == 0 ||
 		pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0 ||
-		pg_strcasecmp(tag, "SECURITY LABEL") == 0)
+		pg_strcasecmp(tag, "SECURITY LABEL") == 0 ||
+		pg_strcasecmp(tag, "CREATE VARIABLE") == 0)
 		return EVENT_TRIGGER_COMMAND_TAG_OK;
 
 	/*
@@ -1146,6 +1148,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_TYPE:
 		case OBJECT_USER_MAPPING:
+		case OBJECT_VARIABLE:
 		case OBJECT_VIEW:
 			return true;
 
@@ -1209,6 +1212,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_PUBLICATION_REL:
 		case OCLASS_SUBSCRIPTION:
 		case OCLASS_TRANSFORM:
+		case OCLASS_VARIABLE:
 			return true;
 
 			/*
@@ -2244,6 +2248,8 @@ stringify_grant_objtype(ObjectType objtype)
 			return "TABLESPACE";
 		case OBJECT_TYPE:
 			return "TYPE";
+		case OBJECT_VARIABLE:
+			return "VARIABLE";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
@@ -2326,6 +2332,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 			return "TABLESPACES";
 		case OBJECT_TYPE:
 			return "TYPES";
+		case OBJECT_VARIABLE:
+			return "VARIABLES";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index b945b1556a..eb8c08baf3 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -151,6 +151,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString,
 		case CMD_INSERT:
 		case CMD_UPDATE:
 		case CMD_DELETE:
+		case CMD_PLAN_UTILITY:
 			/* OK */
 			break;
 		default:
diff --git a/src/backend/commands/schemavariable.c b/src/backend/commands/schemavariable.c
new file mode 100644
index 0000000000..208d0d20c4
--- /dev/null
+++ b/src/backend/commands/schemavariable.c
@@ -0,0 +1,470 @@
+#include "postgres.h"
+#include "miscadmin.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/xact.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_variable.h"
+#include "commands/schemavariable.h"
+#include "executor/executor.h"
+#include "executor/svariableReceiver.h"
+#include "nodes/execnodes.h"
+#include "optimizer/planner.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_type.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/inval.h"
+#include "utils/memutils.h"
+#include "utils/lsyscache.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+/*
+ * The content of variables is not transactional. Due this fact the
+ * implementation of DROP can be simple, because although DROP VARIABLE
+ * can be reverted, the content of variable can be lost. In this example,
+ * DROP VARIABLE is same like reset variable.
+ */
+
+typedef struct SchemaVariableData
+{
+	Oid			varid;			/* pg_variable OID of this sequence (hash key) */
+	Oid			typid;			/* OID of the data type */
+	int32		typmod;
+	int16		typlen;
+	bool		typbyval;
+	bool		isnull;
+	bool		freeval;
+	Datum		value;
+	bool		is_rowtype;		/* true when variable is composite */
+	bool		is_valid;		/* true when variable was successfuly initialized */
+} SchemaVariableData;
+
+typedef SchemaVariableData *SchemaVariable;
+
+static HTAB *schemavarhashtab = NULL;		/* hash table for session variables */
+static MemoryContext SchemaVariableMemoryContext = NULL;
+
+static bool first_time = true;
+static void create_schemavar_hashtable(void);
+static bool clean_cache_req = false;
+
+static void clean_cache(void);
+static void force_clean_cache(XactEvent event, void *arg);
+
+
+/*
+ * Save info about ncessity to clean hash table, because some
+ * schema variable was dropped. Don't do here more, recheck
+ * needs to be in transaction state.
+ */
+static void
+InvalidateSchemaVarCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+	if (cacheid != VARIABLEOID)
+		return;
+
+	clean_cache_req = true;
+}
+
+static void
+force_clean_cache(XactEvent event, void *arg)
+{
+	/*
+	 * should continue only in transaction time, when
+	 * syscache is available.
+	 */
+	if (clean_cache_req && IsTransactionState())
+	{
+		clean_cache();
+		clean_cache_req = false;
+	}
+}
+
+static void
+clean_cache(void)
+{
+	HASH_SEQ_STATUS status;
+	SchemaVariable		var;
+
+	if (!schemavarhashtab)
+		return;
+
+	hash_seq_init(&status, schemavarhashtab);
+
+	/*
+	 * Every valid variable have to have entry in system
+	 * catalog. Removed if there is nothing.
+	 */
+	while ((var = (SchemaVariable) hash_seq_search(&status)) != NULL)
+	{
+		HeapTuple	tp = InvalidOid;
+
+		tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(var->varid));
+		if (!HeapTupleIsValid(tp))
+		{
+			elog(DEBUG1, "variable %d is removed from cache", var->varid);
+
+			if (var->freeval)
+			{
+				pfree(DatumGetPointer(var->value));
+				var->freeval = false;
+			}
+
+			if (hash_search(schemavarhashtab,
+								(void *) &var->varid,
+								HASH_REMOVE,
+								NULL) == NULL)
+				elog(DEBUG1, "hash table corrupted");
+		}
+		else
+			ReleaseSysCache(tp);
+	}
+}
+
+char *
+VariableGetName(Variable *var)
+{
+	char   *nspname;
+
+	if (VariableIsVisible(var->oid))
+		nspname = NULL;
+	else
+		nspname = get_namespace_name(var->namespace);
+
+	return quote_qualified_identifier(nspname, var->name);
+}
+
+/*
+ * Create the hash table for storing schema variables
+ */
+static void
+create_schemavar_hashtable(void)
+{
+	HASHCTL		ctl;
+
+	/* set callbacks */
+	if (first_time)
+	{
+		CacheRegisterSyscacheCallback(VARIABLEOID,
+									  InvalidateSchemaVarCacheCallback,
+									  (Datum) 0);
+
+		RegisterXactCallback(force_clean_cache, NULL);
+
+		first_time = false;
+	}
+
+	/* needs own long life memory context */
+	if (SchemaVariableMemoryContext == NULL)
+	{
+		SchemaVariableMemoryContext = AllocSetContextCreate(TopMemoryContext,
+															"schema variables",
+															ALLOCSET_START_SMALL_SIZES);
+	}
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(SchemaVariableData);
+	ctl.hcxt = SchemaVariableMemoryContext;
+
+	schemavarhashtab = hash_create("Schema variables", 64, &ctl,
+										HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+/*
+ * Fast drop complete content of schema variables
+ */
+void
+ResetSchemaVariableCache(void)
+{
+	if (schemavarhashtab)
+	{
+		hash_destroy(schemavarhashtab);
+		schemavarhashtab = NULL;
+	}
+
+	if (SchemaVariableMemoryContext != NULL)
+	{
+		MemoryContextReset(SchemaVariableMemoryContext);
+	}
+}
+
+/*
+ * Drop variable by OID
+ */
+void
+RemoveVariableById(Oid varid)
+{
+	Relation	rel;
+	HeapTuple	tup;
+
+	rel = heap_open(VariableRelationId, RowExclusiveLock);
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+
+	CatalogTupleDelete(rel, &tup->t_self);
+
+	ReleaseSysCache(tup);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Creates new variable - entry in pg_catalog.pg_variable table
+ */
+ObjectAddress
+DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt *stmt)
+{
+	Oid			namespaceid;
+	AclResult	aclresult;
+	Oid			typid;
+	int32		typmod;
+	Oid			varowner = GetUserId();
+
+	Node	   *cooked_default = NULL;
+
+	namespaceid =
+		RangeVarGetAndCheckCreationNamespace(stmt->variable, NoLock, NULL);
+
+	typenameTypeIdAndMod(pstate, stmt->typeName, &typid, &typmod);
+
+	aclresult = pg_type_aclcheck(typid, GetUserId(), ACL_USAGE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error_type(aclresult, typid);
+
+	if (stmt->defexpr)
+	{
+		cooked_default = transformExpr(pstate, stmt->defexpr,
+										EXPR_KIND_VARIABLE_DEFAULT);
+
+		cooked_default = coerce_to_specific_type(pstate,
+												 cooked_default, typid, "DEFAULT");
+	}
+
+	return VariableCreate(stmt->variable->relname,
+						  namespaceid,
+						  typid,
+						  typmod,
+						  varowner,
+						  cooked_default,
+						  stmt->if_not_exists);
+}
+
+/*
+ * Try to search value in hash table. If doesn't
+ * exists insert it (and calculate defexpr if exists.
+ */
+static SchemaVariable
+PrepareSchemaVariableForReading(Oid varid)
+{
+	SchemaVariable	svar;
+	Variable	*var;
+	bool			found;
+
+	if (schemavarhashtab == NULL)
+		create_schemavar_hashtable();
+
+	svar = (SchemaVariable) hash_search(schemavarhashtab, &varid,
+											  HASH_ENTER, &found);
+	if (!found)
+	{
+		var = GetVariable(varid, false);
+		get_typlenbyval(var->typid, &svar->typlen, &svar->typbyval);
+
+		svar->varid = varid;
+		svar->typid = var->typid;
+		svar->typmod = var->typmod;
+		svar->isnull = true;
+		svar->freeval = false;
+		svar->value = (Datum) 0;
+		svar->is_rowtype = type_is_rowtype(var->typid);
+
+		/* when we don't need calculate defexpr, value is valid already */
+		svar->is_valid = var->defexpr ? false : true;
+	}
+	else if (!svar->is_valid)
+	{
+		/* we need var to recalculate defexpr */
+		var = GetVariable(varid, false);
+	}
+	else
+		/* we don't need to go to sys cache */
+		var = NULL;
+
+	/*
+	 * Initialize variable when it is necessary. It is fresh
+	 * or last initialization was not successfull.
+	 */
+	if (var != NULL && var->defexpr && !svar->is_valid)
+	{
+		MemoryContext oldcontext = NULL;
+
+		Datum	   value = (Datum) 0;
+		bool	   null;
+		EState	   *estate = NULL;
+		Expr	   *defexpr;
+		ExprState  *defexprs;
+
+		/* Prepare default expr */
+		estate = CreateExecutorState();
+		oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		defexpr = expression_planner((Expr *) var->defexpr);
+		defexprs = ExecInitExpr(defexpr, NULL);
+		value = ExecEvalExprSwitchContext(defexprs, GetPerTupleExprContext(estate), &null);
+
+		MemoryContextSwitchTo(SchemaVariableMemoryContext);
+
+		if (!null)
+		{
+			svar->value = datumCopy(value, svar->typbyval, svar->typlen);
+			svar->freeval = svar->value != value;
+			svar->isnull = false;
+			svar->is_valid = true;
+		}
+		else
+		{
+			svar->isnull = true;
+			svar->is_valid = true;
+		}
+
+		MemoryContextSwitchTo(oldcontext);
+
+		FreeExecutorState(estate);
+	}
+
+	if (!svar->is_valid)
+		elog(ERROR, "the content of variable is not valid");
+
+	return svar;
+}
+
+/*
+ * Returns content of variable. We expext secured access now.
+ * Secure check should be done before.
+ */
+Datum
+GetSchemaVariable(Oid varid, bool *isNull, Oid expected_typid)
+{
+	SchemaVariable	svar;
+
+	svar = PrepareSchemaVariableForReading(varid);
+	*isNull = svar->isnull;
+
+	if (expected_typid != svar->typid)
+		elog(ERROR, "type of variable \"%s\" is different than expected",
+				   schema_variable_get_name(varid));
+
+	return (Datum) svar->value;
+}
+
+/*
+ * Write value to variable. We expect secured access in this moment.
+ * In this time, we recheck syschache about used type.
+ */
+void
+SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid, int32 typmod)
+{
+	MemoryContext oldcontext = NULL;
+
+	SchemaVariable svar;
+	Oid			var_typid;
+	int32		var_typmod;
+	bool		found;
+
+	if (schemavarhashtab == NULL)
+		create_schemavar_hashtable();
+
+	svar = (SchemaVariable) hash_search(schemavarhashtab, &varid,
+											  HASH_ENTER, &found);
+
+	get_schema_variable_type_typmod(varid, &var_typid, &var_typmod);
+
+	/* check types first */
+	if (var_typid != typid)
+		elog(ERROR, "type of expression is different than schema variable type");
+
+	if (found)
+	{
+		/* release current content first */
+		if (svar->freeval)
+		{
+			pfree(DatumGetPointer(svar->value));
+			svar->value = (Datum) 0;
+			svar->isnull = true;
+			svar->freeval = false;
+		}
+	}
+
+	get_typlenbyval(typid, &svar->typlen, &svar->typbyval);
+
+	svar->varid = varid;
+	svar->typid = typid;
+	svar->typmod = typmod;
+
+	svar->isnull = true;
+	svar->freeval = false;
+	svar->value = (Datum) 0;
+
+	svar->is_rowtype = type_is_rowtype(typid);
+	svar->is_valid = false;
+
+	oldcontext = MemoryContextSwitchTo(SchemaVariableMemoryContext);
+
+	if (!isNull)
+	{
+		svar->value = datumCopy(value, svar->typbyval, svar->typlen);
+		svar->freeval = svar->value != value;
+		svar->isnull = false;
+		svar->is_valid = true;
+	}
+	else
+	{
+		svar->isnull = true;
+		svar->is_valid = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+}
+
+void
+doLetStmt(PlannedStmt *pstmt,
+		  ParamListInfo params,
+		  QueryEnvironment *queryEnv,
+		  const char *queryString)
+{
+	QueryDesc *queryDesc;
+	DestReceiver *dest;
+
+	PushCopiedSnapshot(GetActiveSnapshot());
+	UpdateActiveSnapshotCommandId();
+
+	/* Create dest receiver for LET */
+	dest = CreateDestReceiver(DestVariable);
+
+	SetVariableDestReceiverParams(dest, pstmt->resultVariable);
+
+	/* Create a QueryDesc requesting no output */
+	queryDesc = CreateQueryDesc(pstmt, queryString,
+											GetActiveSnapshot(),
+											InvalidSnapshot,
+											dest, params, queryEnv, 0);
+
+	ExecutorStart(queryDesc, 0);
+	ExecutorRun(queryDesc, ForwardScanDirection, 2L, true);
+	ExecutorFinish(queryDesc);
+	ExecutorEnd(queryDesc);
+
+	FreeQueryDesc(queryDesc);
+
+	PopActiveSnapshot();
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index eb2d33dd86..22cd7871cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9630,6 +9630,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_PUBLICATION_REL:
 			case OCLASS_SUBSCRIPTION:
 			case OCLASS_TRANSFORM:
+			case OCLASS_VARIABLE:
 
 				/*
 				 * We don't expect any of these sorts of objects to depend on
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index cc09895fa5..ee8ff7da9e 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -29,6 +29,6 @@ OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \
        nodeCtescan.o nodeNamedtuplestorescan.o nodeWorktablescan.o \
        nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
        nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o \
-       nodeTableFuncscan.o
+       nodeTableFuncscan.o svariableReceiver.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index e284fd71d7..58d4955dd8 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -33,6 +33,7 @@
 #include "access/nbtree.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_type.h"
+#include "commands/schemavariable.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -727,6 +728,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				Param	   *param = (Param *) node;
 				ParamListInfo params;
+				AclResult	aclresult;
 
 				switch (param->paramkind)
 				{
@@ -736,6 +738,19 @@ ExecInitExprRec(Expr *node, ExprState *state,
 						scratch.d.param.paramtype = param->paramtype;
 						ExprEvalPushStep(state, &scratch);
 						break;
+					case PARAM_SCHEMA_VARIABLE:
+						/* Check permission to read schema variable */
+						aclresult = pg_variable_aclcheck(param->paramid, GetUserId(), ACL_READ);
+						if (aclresult != ACLCHECK_OK)
+							aclcheck_error(aclresult, OBJECT_VARIABLE,
+											schema_variable_get_name(param->paramid));
+
+						scratch.opcode = EEOP_PARAM_VARIABLE;
+						scratch.d.param.paramid = param->paramid;
+						scratch.d.param.paramtype = param->paramtype;
+						ExprEvalPushStep(state, &scratch);
+						break;
+
 					case PARAM_EXTERN:
 
 						/*
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 9d6e25aae5..25966ceeeb 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -59,6 +59,7 @@
 #include "access/tuptoaster.h"
 #include "catalog/pg_type.h"
 #include "commands/sequence.h"
+#include "commands/schemavariable.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -351,6 +352,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_PARAM_EXEC,
 		&&CASE_EEOP_PARAM_EXTERN,
 		&&CASE_EEOP_PARAM_CALLBACK,
+		&&CASE_EEOP_PARAM_VARIABLE,
 		&&CASE_EEOP_CASE_TESTVAL,
 		&&CASE_EEOP_MAKE_READONLY,
 		&&CASE_EEOP_IOCOERCE,
@@ -1007,6 +1009,20 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_PARAM_VARIABLE)
+		{
+			Datum	d;
+			bool	isnull;
+
+			d = GetSchemaVariable(op->d.param.paramid, &isnull,
+													  op->d.param.paramtype);
+
+			*op->resvalue = d;
+			*op->resnull = isnull;
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_CASE_TESTVAL)
 		{
 			/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 01e1a46180..3721aec7a1 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -43,9 +43,11 @@
 #include "access/xact.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_publication.h"
+#include "catalog/pg_variable.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
+#include "executor/svariableReceiver.h"
 #include "foreign/fdwapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
@@ -204,12 +206,18 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	 */
 	estate->es_queryEnv = queryDesc->queryEnv;
 
+	/*
+	 * Result can be stored in schema variable.
+	 */
+	estate->es_result_variable = queryDesc->plannedstmt->resultVariable;
+
 	/*
 	 * If non-read-only query, set the command ID to mark output tuples with
 	 */
 	switch (queryDesc->operation)
 	{
 		case CMD_SELECT:
+		case CMD_PLAN_UTILITY:
 
 			/*
 			 * SELECT FOR [KEY] UPDATE/SHARE and modifying CTEs need to mark
@@ -345,6 +353,7 @@ standard_ExecutorRun(QueryDesc *queryDesc,
 	estate->es_lastoid = InvalidOid;
 
 	sendTuples = (operation == CMD_SELECT ||
+				  OidIsValid(estate->es_result_variable) ||
 				  queryDesc->plannedstmt->hasReturning);
 
 	if (sendTuples)
@@ -924,6 +933,17 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 		estate->es_num_root_result_relations = 0;
 	}
 
+	if (OidIsValid(estate->es_result_variable))
+	{
+		AclResult	aclresult;
+		Oid			varid = estate->es_result_variable;
+
+		/* Ensure this variable is writeable */
+		aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error(aclresult, OBJECT_VARIABLE, schema_variable_get_name(varid));
+	}
+
 	/*
 	 * Similarly, we have to lock relations selected FOR [KEY] UPDATE/SHARE
 	 * before we initialize the plan tree, else we'd be risking lock upgrades.
diff --git a/src/backend/executor/svariableReceiver.c b/src/backend/executor/svariableReceiver.c
new file mode 100644
index 0000000000..0eac4b5d0c
--- /dev/null
+++ b/src/backend/executor/svariableReceiver.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.c
+ *	  An implementation of DestReceiver that stores the result value in
+ *	  a schema variable.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/svariableReceiver.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/tuptoaster.h"
+#include "executor/svariableReceiver.h"
+#include "commands/schemavariable.h"
+
+typedef struct
+{
+	DestReceiver pub;
+	Oid		varid;
+	Oid		typid;
+	int32	typmod;
+	int		typlen;
+	int		slot_offset;
+	int		rows;
+} svariableState;
+
+
+/*
+ * Prepare to receive tuples from executor.
+ */
+static void
+svariableStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+	svariableState *myState = (svariableState *) self;
+	int			natts = typeinfo->natts;
+	int			outcols = 0;
+	int			i;
+
+	for (i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+
+		if (attr->attisdropped)
+			continue;
+
+		if (++outcols > 1)
+			elog(ERROR, "svariable DestReceiver can take only one attribute");
+
+		myState->typid = attr->atttypid;
+		myState->typmod = attr->atttypmod;
+		myState->typlen = attr->attlen;
+		myState->slot_offset = i;
+	}
+
+	myState->rows = 0;
+}
+
+/*
+ * Receive a tuple from the executor and store it in schema variable.
+ */
+static bool
+svariableReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
+{
+	svariableState *myState = (svariableState *) self;
+	Datum		value;
+	bool		isnull;
+	bool		freeval = false;
+
+	/* Make sure the tuple is fully deconstructed */
+	slot_getallattrs(slot);
+
+	value = slot->tts_values[myState->slot_offset];
+	isnull = slot->tts_isnull[myState->slot_offset];
+
+	if (myState->typlen == -1 && !isnull && VARATT_IS_EXTERNAL(DatumGetPointer(value)))
+	{
+		value = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
+													DatumGetPointer(value)));
+		freeval = true;
+	}
+
+	SetSchemaVariable(myState->varid, value, isnull, myState->typid, myState->typmod);
+
+	if (freeval)
+		pfree(DatumGetPointer(value));
+
+	return true;
+}
+
+/*
+ * Clean up at end of an executor run
+ */
+static void
+svariableShutdownReceiver(DestReceiver *self)
+{
+	/* Do nothing */
+}
+
+/*
+ * Destroy receiver when done with it
+ */
+static void
+svariableDestroyReceiver(DestReceiver *self)
+{
+	pfree(self);
+}
+
+/*
+ * Initially create a DestReceiver object.
+ */
+DestReceiver *
+CreateVariableDestReceiver(void)
+{
+	svariableState *self = (svariableState *) palloc0(sizeof(svariableState));
+
+	self->pub.receiveSlot = svariableReceiveSlot;
+	self->pub.rStartup = svariableStartupReceiver;
+	self->pub.rShutdown = svariableShutdownReceiver;
+	self->pub.rDestroy = svariableDestroyReceiver;
+	self->pub.mydest = DestVariable;
+
+	/* private fields will be set by SetVariableDestReceiverParams */
+
+	return (DestReceiver *) self;
+}
+
+/*
+ * Set parameters for a VariableDestReceiver
+ */
+void
+SetVariableDestReceiverParams(DestReceiver *self, Oid varid)
+{
+	svariableState *myState = (svariableState *) self;
+
+	Assert(myState->pub.mydest == DestVariable);
+	Assert(OidIsValid(varid));
+
+	myState->varid = varid;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7c8220cf65..fcaa2db51a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -93,6 +93,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(nonleafResultRelations);
 	COPY_NODE_FIELD(rootResultRelations);
+	COPY_SCALAR_FIELD(resultVariable);
 	COPY_NODE_FIELD(subplans);
 	COPY_BITMAPSET_FIELD(rewindPlanIDs);
 	COPY_NODE_FIELD(rowMarks);
@@ -3000,6 +3001,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(resultVariable);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasWindowFuncs);
 	COPY_SCALAR_FIELD(hasTargetSRFs);
@@ -3118,6 +3120,18 @@ _copySelectStmt(const SelectStmt *from)
 	return newnode;
 }
 
+static LetStmt *
+_copyLetStmt(const LetStmt *from)
+{
+	LetStmt *newnode = makeNode(LetStmt);
+
+	COPY_NODE_FIELD(target);
+	COPY_NODE_FIELD(selectStmt);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 static SetOperationStmt *
 _copySetOperationStmt(const SetOperationStmt *from)
 {
@@ -5166,6 +5180,9 @@ copyObjectImpl(const void *from)
 		case T_SelectStmt:
 			retval = _copySelectStmt(from);
 			break;
+		case T_LetStmt:
+			retval = _copyLetStmt(from);
+			break;
 		case T_SetOperationStmt:
 			retval = _copySetOperationStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 378f2facb8..3ec472e19b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -949,6 +949,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(canSetTag);
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
+	COMPARE_SCALAR_FIELD(resultVariable);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasWindowFuncs);
 	COMPARE_SCALAR_FIELD(hasTargetSRFs);
@@ -1057,6 +1058,16 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	return true;
 }
 
+static bool
+_equalLetStmt(const LetStmt *a, const LetStmt *b)
+{
+	COMPARE_NODE_FIELD(target);
+	COMPARE_NODE_FIELD(selectStmt);
+
+	return true;
+}
+
+
 static bool
 _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 {
@@ -3225,6 +3236,9 @@ equal(const void *a, const void *b)
 		case T_SelectStmt:
 			retval = _equalSelectStmt(a, b);
 			break;
+		case T_LetStmt:
+			retval = _equalLetStmt(a, b);
+			break;
 		case T_SetOperationStmt:
 			retval = _equalSetOperationStmt(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6269f474d2..46404ff9ac 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -278,6 +278,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(nonleafResultRelations);
 	WRITE_NODE_FIELD(rootResultRelations);
+	WRITE_OID_FIELD(resultVariable);
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(rowMarks);
@@ -2793,6 +2794,16 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(rarg);
 }
 
+static void
+_outLetStmt(StringInfo str, const LetStmt *node)
+{
+	WRITE_NODE_TYPE("LET");
+
+	WRITE_NODE_FIELD(target);
+	WRITE_NODE_FIELD(selectStmt);
+	WRITE_LOCATION_FIELD(location);
+}
+
 static void
 _outFuncCall(StringInfo str, const FuncCall *node)
 {
@@ -2971,6 +2982,7 @@ _outQuery(StringInfo str, const Query *node)
 		appendStringInfoString(str, " :utilityStmt <>");
 
 	WRITE_INT_FIELD(resultRelation);
+	WRITE_INT_FIELD(resultVariable);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasWindowFuncs);
 	WRITE_BOOL_FIELD(hasTargetSRFs);
@@ -4191,6 +4203,9 @@ outNode(StringInfo str, const void *obj)
 			case T_SelectStmt:
 				_outSelectStmt(str, obj);
 				break;
+			case T_LetStmt:
+				_outLetStmt(str, obj);
+				break;
 			case T_ColumnDef:
 				_outColumnDef(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3254524223..4454327549 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -242,6 +242,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
+	READ_INT_FIELD(resultVariable);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasWindowFuncs);
 	READ_BOOL_FIELD(hasTargetSRFs);
@@ -1485,6 +1486,7 @@ _readPlannedStmt(void)
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(nonleafResultRelations);
 	READ_NODE_FIELD(rootResultRelations);
+	READ_OID_FIELD(resultVariable);
 	READ_NODE_FIELD(subplans);
 	READ_BITMAPSET_FIELD(rewindPlanIDs);
 	READ_NODE_FIELD(rowMarks);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index fd06da98b9..01f97f2d86 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -335,7 +335,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	 */
 	if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
 		IsUnderPostmaster &&
-		parse->commandType == CMD_SELECT &&
+		(parse->commandType == CMD_SELECT ||
+		 parse->commandType == CMD_PLAN_UTILITY) &&
 		!parse->hasModifyingCTE &&
 		max_parallel_workers_per_gather > 0 &&
 		!IsParallelWorker() &&
@@ -352,6 +353,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 		glob->parallelModeOK = false;
 	}
 
+
+
 	/*
 	 * glob->parallelModeNeeded is normally set to false here and changed to
 	 * true during plan creation if a Gather or Gather Merge plan is actually
@@ -521,6 +524,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->resultRelations = glob->resultRelations;
 	result->nonleafResultRelations = glob->nonleafResultRelations;
 	result->rootResultRelations = glob->rootResultRelations;
+	result->resultVariable = parse->resultVariable;
 	result->subplans = glob->subplans;
 	result->rewindPlanIDs = glob->rewindPlanIDs;
 	result->rowMarks = glob->finalrowmarks;
@@ -2167,7 +2171,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 		 * If this is an INSERT/UPDATE/DELETE, and we're not being called from
 		 * inheritance_planner, add the ModifyTable node.
 		 */
-		if (parse->commandType != CMD_SELECT && !inheritance_update)
+		if (parse->commandType != CMD_SELECT && parse->commandType != CMD_PLAN_UTILITY && !inheritance_update)
 		{
 			List	   *withCheckOptionLists;
 			List	   *returningLists;
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 8603feef2b..2923e3fcc7 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -71,6 +71,7 @@ preprocess_targetlist(PlannerInfo *root)
 {
 	Query	   *parse = root->parse;
 	int			result_relation = parse->resultRelation;
+	int			result_variable = parse->resultVariable;
 	List	   *range_table = parse->rtable;
 	CmdType		command_type = parse->commandType;
 	RangeTblEntry *target_rte = NULL;
@@ -96,6 +97,10 @@ preprocess_targetlist(PlannerInfo *root)
 
 		target_relation = heap_open(target_rte->relid, NoLock);
 	}
+	else if (result_variable)
+	{
+		Assert(command_type == CMD_PLAN_UTILITY);
+	}
 	else
 		Assert(command_type == CMD_SELECT);
 
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a04ad6e99e..da570bb23b 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1254,7 +1254,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 	{
 		Param	   *param = (Param *) node;

 		if (param->paramkind != PARAM_EXEC ||
@@ -4799,7 +4800,7 @@ substitute_actual_parameters_mutator(Node *node,
 {
 	if (node == NULL)
 		return NULL;
-	if (IsA(node, Param))
+	if (IsA(node, Param) && ((Param *) node)->paramkind != PARAM_SCHEMA_VARIABLE)
 	{
 		Param	   *param = (Param *) node;
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8369e3ad62..fc0cf34c7d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1272,7 +1272,7 @@ get_relation_constraints(PlannerInfo *root,
 	 * descriptor, instead of constraint exclusion which is driven by the
 	 * individual partition's partition constraint.
 	 */
-	if (enable_partition_pruning && root->parse->commandType != CMD_SELECT)
+	if (enable_partition_pruning && root->parse->commandType != CMD_SELECT && root->parse->commandType != CMD_PLAN_UTILITY)
 	{
 		List	   *pcqual = RelationGetPartitionQual(relation);
 
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c601b6d40d..441b298693 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,7 +25,10 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
+#include "commands/schemavariable.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -44,6 +47,8 @@
 #include "parser/parse_target.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"

@@ -78,6 +83,8 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
 						   CreateTableAsStmt *stmt);
 static Query *transformCallStmt(ParseState *pstate,
 				  CallStmt *stmt);
+static Query *transformLetStmt(ParseState *pstate,
+				  LetStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
@@ -267,6 +274,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
 		case T_InsertStmt:
 		case T_UpdateStmt:
 		case T_DeleteStmt:
+		case T_LetStmt:
 			(void) test_raw_expression_coverage(parseTree, NULL);
 			break;
 		default:
@@ -327,6 +335,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
 									   (CallStmt *) parseTree);
 			break;
 
+		case T_LetStmt:
+			result = transformLetStmt(pstate,
+									  (LetStmt *) parseTree);
+			break;
+
 		default:
 
 			/*
@@ -367,6 +380,7 @@ analyze_requires_snapshot(RawStmt *parseTree)
 		case T_DeleteStmt:
 		case T_UpdateStmt:
 		case T_SelectStmt:
+		case T_LetStmt:
 			result = true;
 			break;
 
@@ -1567,6 +1581,203 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+ *	  transform an Let Statement
+ */
+static Query *
+transformLetStmt(ParseState *pstate, LetStmt *stmt)
+{
+	Query	   *qry = makeNode(Query);
+	List	   *exprList = NIL;
+	List	   *exprListCoer = NIL;
+	List	   *indirection = NIL;
+	ListCell   *lc;
+	Query	   *selectQuery;
+	int			i = 0;
+
+	Oid			varid;
+
+	ParseExprKind sv_expr_kind;
+	char	   *attrname = NULL;
+	bool		not_unique;
+	bool		is_rowtype;
+	Oid			typid;
+	int32		typmod;
+
+	AclResult	aclresult;
+	List	   *names = NULL;
+	int			indirection_start;
+
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = EXPR_KIND_LET;
+
+	/* There can't be any outer WITH to worry about */
+	Assert(pstate->p_ctenamespace == NIL);
+
+	/* Exec this command as utility */
+	qry->commandType = CMD_PLAN_UTILITY;
+	qry->utilityStmt = (Node *) stmt;
+
+	names = NamesFromList(stmt->target);
+
+	varid = identify_variable(names, &attrname, &not_unique);
+	if (not_unique)
+		ereport(ERROR,
+				(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+				 errmsg("target \"%s\" of LET command is ambiguous",
+						NameListToString(names)),
+				 parser_errposition(pstate, stmt->location)));
+
+	if (!OidIsValid(varid))
+		ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("schema variable \"%s\" doesn't exists",
+								NameListToString(names)),
+						 parser_errposition(pstate, stmt->location)));
+
+	qry->resultVariable = varid;
+
+	get_schema_variable_type_typmod(varid, &typid, &typmod);
+
+	is_rowtype = type_is_rowtype(typid);
+
+	if (attrname && !is_rowtype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("target variable \"%s\" is not row type",
+						schema_variable_get_name(varid)),
+				parser_errposition(pstate, stmt->location)));
+
+	aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_VARIABLE, NameListToString(names));
+
+	selectQuery = transformStmt(pstate, stmt->selectStmt);
+
+	/* The grammar should have produced a SELECT */
+	if (!IsA(selectQuery, Query) ||
+		selectQuery->commandType != CMD_SELECT)
+		elog(ERROR, "unexpected non-SELECT command in LET ... SELECT");
+
+	/*----------
+	 * Generate an expression list for the LET that selects all the
+	 * non-resjunk columns from the subquery.
+	 *----------
+	 */
+	exprList = NIL;
+	foreach(lc, selectQuery->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+		if (tle->resjunk)
+			continue;
+
+		exprList = lappend(exprList, tle->expr);
+	}
+
+	/*
+	 * Because doesn't support pattern matching, don't allow multicolumn result
+	 */
+	if (list_length(exprList) != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("expression is not scalar value"),
+				 parser_errposition(pstate,
+									exprLocation((Node *) exprList))));
+
+	indirection_start = list_length(names) - (attrname ? 1 : 0);
+	indirection = list_copy_tail(stmt->target, indirection_start);
+
+	exprListCoer = NIL;
+	foreach(lc, exprList)
+	{
+		Node	   *orig_expr = (Node*) lfirst(lc);
+		Oid			exprtypid = exprType((Node *) orig_expr);
+		Param *param = makeNode(Param);
+		Expr	   *expr = NULL;
+
+		param->paramkind = PARAM_SCHEMA_VARIABLE;
+		param->paramid = varid;
+		param->paramtype = typid;
+		param->paramtypmod = typmod;
+
+		if (indirection != NULL)
+		{
+			bool		targetIsArray;
+			char	    *targetName;
+
+			targetName = attrname != NULL ? attrname : get_schema_variable_name(varid);
+			targetIsArray = OidIsValid(get_element_type(typid));
+
+			expr = (Expr *)
+					transformAssignmentIndirection(pstate,
+												(Node *) param,
+												targetName,
+												targetIsArray,
+												typid,
+												typmod,
+												InvalidOid,
+												list_head(indirection),
+												(Node *) orig_expr,
+												stmt->location);
+		}
+		else
+			expr = (Expr *)
+					coerce_to_target_type(pstate,
+											(Node *) orig_expr,
+											exprtypid,
+											typid, typmod,
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											stmt->location);
+
+		if (expr == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("variable \"%s\" is of type %s,"
+							  " but expression is of type %s",
+						schema_variable_get_name(varid),
+							format_type_be(typid),
+						  format_type_be(exprtypid)),
+				 errhint("You will need to rewrite or cast the expression."),
+				 parser_errposition(pstate, exprLocation((Node *) orig_expr))));
+
+		exprListCoer = lappend(exprListCoer, expr);
+	}
+
+	/*
+	 * Generate query's target list using the computed list of expressions.
+	 * Also, mark all the target columns as needing insert permissions.
+	 */
+	qry->targetList = NIL;
+	foreach(lc, exprListCoer)
+	{
+		Expr	   *expr = (Expr *) lfirst(lc);
+		TargetEntry *tle;
+
+		tle = makeTargetEntry(expr,
+							  i + 1,
+							  FigureColname((Node *)expr),
+							  false);
+		qry->targetList = lappend(qry->targetList, tle);
+	}
+
+	/* done building the range table and jointree */
+	qry->rtable = pstate->p_rtable;
+	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+	qry->hasSubLinks = pstate->p_hasSubLinks;
+
+	assign_query_collations(pstate, qry);
+
+	pstate->p_expr_kind = sv_expr_kind;
+
+	return qry;
+}
+
+
 /*
  * transformSetOperationStmt -
  *	  transforms a set-operations tree
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 87f5e95827..25036669c1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -257,8 +257,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
 		CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt
 		CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
-		CreateSchemaStmt CreateSeqStmt CreateStmt CreateStatsStmt CreateTableSpaceStmt
-		CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
+		CreateSchemaStmt CreateSchemaVarStmt CreateSeqStmt CreateStmt CreateStatsStmt
+		CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
 		CreateAssertStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
 		CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
 		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
@@ -268,7 +268,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropTransformStmt
 		DropUserMappingStmt ExplainStmt FetchStmt
 		GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
-		ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
+		LetStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
 		CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
 		RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
 		RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@@ -400,6 +400,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				TriggerTransitions TriggerReferencing
 				publication_name_list
 				vacuum_relation_list opt_vacuum_relation_list
+				let_target
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -584,6 +585,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		hash_partbound partbound_datum_list range_datum_list
 %type <defelt>		hash_partbound_elem
+%type <node>		optSchemaVarDefExpr
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -649,7 +651,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY

 	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -687,8 +689,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
 	UNTIL UPDATE USER USING

 
@@ -878,6 +880,7 @@ stmt :
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSchemaVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -917,6 +920,7 @@ stmt :
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -1808,7 +1812,12 @@ DiscardStmt:
 					n->target = DISCARD_SEQUENCES;
 					$$ = (Node *) n;
 				}
-
+			| DISCARD VARIABLES
+				{
+					DiscardStmt *n = makeNode(DiscardStmt);
+					n->target = DISCARD_VARIABLES;
+					$$ = (Node *) n;
+				}
 		;

@@ -4479,6 +4488,42 @@ create_extension_opt_item:
 				}
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY :
+ *				CREATE VARIABLE varname [AS] type
+ *
+ *****************************************************************************/
+
+CreateSchemaVarStmt:
+			CREATE OptTemp VARIABLE qualified_name opt_as Typename optSchemaVarDefExpr
+				{
+					CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+					$4->relpersistence = $2;
+					n->variable = $4;
+					n->typeName = $6;
+					n->defexpr = $7;
+					n->if_not_exists = false;
+					$$ = (Node *) n;
+				}
+			| CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename optSchemaVarDefExpr
+				{
+					CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+					$7->relpersistence = $2;
+					n->variable = $7;
+					n->typeName = $9;
+					n->defexpr = $10;
+					n->if_not_exists = true;
+					$$ = (Node *) n;
+				}
+		;
+
+optSchemaVarDefExpr: DEFAULT b_expr							{ $$ = $2; }
+			| /* EMPTY */									{ $$ = NULL; }
+		;
+
+
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
@@ -6335,6 +6380,7 @@ drop_type_any_name:
 			| TEXT_P SEARCH DICTIONARY				{ $$ = OBJECT_TSDICTIONARY; }
 			| TEXT_P SEARCH TEMPLATE				{ $$ = OBJECT_TSTEMPLATE; }
 			| TEXT_P SEARCH CONFIGURATION			{ $$ = OBJECT_TSCONFIGURATION; }
+			| VARIABLE								{ $$ = OBJECT_VARIABLE; }
 		;
 
@@ -6604,6 +6650,7 @@ comment_type_any_name:
 
@@ -6742,6 +6789,7 @@ security_label_type_any_name:
 
 /* object types taking name */
@@ -7163,6 +7211,14 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
+			| VARIABLE qualified_name_list
+				{
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_VARIABLE;
+					n->objs = $2;
+					$$ = n;
+				}
 			| ALL TABLES IN_P SCHEMA name_list
 				{
 					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -7203,6 +7259,14 @@ privilege_target:
 					n->objs = $5;
 					$$ = n;
 				}
+			| ALL VARIABLES IN_P SCHEMA name_list
+				{
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+					n->targtype = ACL_TARGET_ALL_IN_SCHEMA;
+					n->objtype = OBJECT_VARIABLE;
+					n->objs = $5;
+					$$ = n;
+				}
 		;

@@ -7363,6 +7427,7 @@ defacl_privilege_target:

@@ -8959,6 +9024,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ALTER VARIABLE any_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_VARIABLE;
+					n->object = (Node *) $3;
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER VARIABLE IF_P EXISTS any_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_VARIABLE;
+					n->object = (Node *) $5;
+					n->newname = $8;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+
 		;
 
 opt_column: COLUMN									{ $$ = COLUMN; }
@@ -9277,6 +9361,25 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ALTER VARIABLE any_name SET SCHEMA name
+				{
+					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+					n->objectType = OBJECT_VARIABLE;
+					n->object = (Node *) $3;
+					n->newschema = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER VARIABLE IF_P EXISTS any_name SET SCHEMA name
+				{
+					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+					n->objectType = OBJECT_VARIABLE;
+					n->object = (Node *) $5;
+					n->newschema = $8;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+
 		;
 
 /*****************************************************************************
@@ -9512,6 +9615,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
 					n->newowner = $6;
 					$$ = (Node *)n;
 				}
+			| ALTER VARIABLE any_name OWNER TO RoleSpec
+				{
+					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+					n->objectType = OBJECT_VARIABLE;
+					n->object = (Node *) $3;
+					n->newowner = $6;
+					$$ = (Node *)n;
+				}
 		;

@@ -10693,6 +10804,7 @@ ExplainableStmt:
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 explain_option_list:
@@ -10750,6 +10862,7 @@ PreparableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -11148,6 +11261,44 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY:
+ *				LET STATEMENTS
+ *
+ *****************************************************************************/
+LetStmt:	LET let_target '=' a_expr
+				{
+					LetStmt *n = makeNode(LetStmt);
+					SelectStmt *select = makeNode(SelectStmt);
+					ResTarget	*res = makeNode(ResTarget);
+
+					n->target = $2;
+
+					/* Create target list for implicit query */
+					res->name = NULL;
+					res->indirection = NIL;
+					res->val = (Node *) $4;
+					res->location = @4;
+
+					select->targetList = list_make1(res);
+					n->selectStmt = (Node *) select;
+
+					n->location = @2;
+
+					$$ = (Node *) n;
+				}
+		;
+
+let_target:
+			ColId opt_indirection
+				{
+					$$ = list_make1(makeString($1));
+					if ($2)
+						  $$ = list_concat($$,
+										   check_indirection($2, yyscanner));
+				}
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -15127,6 +15278,7 @@ unreserved_keyword:
 			| LARGE_P
 			| LAST_P
 			| LEAKPROOF
+			| LET
 			| LEVEL
 			| LISTEN
 			| LOAD
@@ -15275,6 +15427,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 61727e1d71..6823612fba 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -349,6 +349,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			Assert(false);		/* can't happen */
 			break;
 		case EXPR_KIND_OTHER:
+		case EXPR_KIND_LET:
 
 			/*
 			 * Accept aggregate/grouping here; caller must throw error if
@@ -465,6 +466,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 
 			if (isAgg)
 				err = _("aggregate functions are not allowed in DEFAULT expressions");
@@ -879,6 +881,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("window functions are not allowed in DEFAULT expressions");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
@@ -902,6 +905,8 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_CALL_ARGUMENT:
 			err = _("window functions are not allowed in CALL arguments");
 			break;
+		case EXPR_KIND_LET:
+			err = _("window functions are not allowed in LET statement");
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 385e54a9b6..bcdda0fb4a 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -37,6 +38,7 @@
 #include "utils/date.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"

@@ -116,6 +118,9 @@ static Node *transformXmlSerialize(ParseState *pstate, XmlSerialize *xs);
 static Node *transformBooleanTest(ParseState *pstate, BooleanTest *b);
 static Node *transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr);
 static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
+static Node *makeParamSchemaVariable(ParseState *pstate,
+						  Oid varid, Oid typid, int32 typmod,
+						  char *attrname, int location);
 static Node *transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte,
 					 int location);
 static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
@@ -512,6 +517,10 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	char	   *nspname = NULL;
 	char	   *relname = NULL;
 	char	   *colname = NULL;
+	Oid			varid = InvalidOid;
+	char	   *attrname = NULL;
+	bool		not_unique;
+
 	RangeTblEntry *rte;
 	int			levels_up;
 	enum
@@ -749,6 +758,15 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 			break;
 	}
 
+	varid = identify_variable(cref->fields, &attrname, &not_unique);
+
+	if (not_unique)
+		ereport(ERROR,
+				(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+				 errmsg("schema variable reference \"%s\" is ambiguous",
+						NameListToString(cref->fields)),
+				 parser_errposition(pstate, cref->location)));
+
 	/*
 	 * Now give the PostParseColumnRefHook, if any, a chance.  We pass the
 	 * translation-so-far so that it can throw an error if it wishes in the
@@ -773,6 +791,71 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 					 parser_errposition(pstate, cref->location)));
 	}
 
+	if (OidIsValid(varid))
+	{
+		Oid		typid;
+		int32	typmod;
+
+		get_schema_variable_type_typmod(varid, &typid, &typmod);
+
+		if (node != NULL)
+		{
+			/*
+			 * some collision can be solved simply here to reduce errors
+			 * based on simply existence of some variables. Often error
+			 * can be using alias same like variable name. In this case,
+			 * when we found column reference, and we found reference to
+			 * possible composite variable, but the variable is not composite,
+			 * then we can ignore the variable as simply improper, and we
+			 * use column reference only.
+			 */
+			if (attrname)
+			{
+				if (type_is_rowtype(typid))
+				{
+					TupleDesc		tupdesc;
+					bool			found = false;
+					int			i;
+
+					/* slow part, I hope it will not be to often */
+					tupdesc = lookup_rowtype_tupdesc(typid, typmod);
+					for (i = 0; i < tupdesc->natts; i++)
+					{
+						if (namestrcmp(&(TupleDescAttr(tupdesc, i)->attname), attrname) == 0 &&
+								!TupleDescAttr(tupdesc, i)->attisdropped)
+						{
+							found = true;
+							break;
+						}
+					}
+
+					FreeTupleDesc(tupdesc);
+
+					/* there are not composite variable with this field */
+					if (!found)
+						varid = InvalidOid;
+				}
+				else
+					/* there are not composite variable with this name */
+					varid = InvalidOid;
+			}
+
+			/* Raise error if varid is still valid. It should be really amigonuous */
+			if (OidIsValid(varid))
+				ereport(ERROR,
+						(errcode(ERRCODE_AMBIGUOUS_COLUMN),
+						 errmsg("column reference \"%s\" is ambiguous",
+								NameListToString(cref->fields)),
+						 errdetail("The qualified identifier can be column reference or schema variable reference"),
+						 parser_errposition(pstate, cref->location)));
+		}
+
+		if (OidIsValid(varid))
+			node = makeParamSchemaVariable(pstate,
+											varid, typid, typmod,
+											attrname, cref->location);
+	}
+
 	/*
 	 * Throw error if no translation found.
 	 */
@@ -807,6 +890,59 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
+ * Generate param variable for reference to schema variable
+ */
+static Node *
+makeParamSchemaVariable(ParseState *pstate, Oid varid, Oid typid, int32 typmod, char *attrname, int location)
+{
+	Param	   *param;
+
+	param = makeNode(Param);
+
+	param->paramkind = PARAM_SCHEMA_VARIABLE;
+	param->paramid = varid;
+	param->paramtype = typid;
+	param->paramtypmod = typmod;
+
+	if (attrname != NULL)
+	{
+		TupleDesc		tupdesc;
+		int		i;
+
+		tupdesc = lookup_rowtype_tupdesc(typid, typmod);
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+			if (strcmp(attrname, NameStr(att->attname)) == 0 &&
+				!att->attisdropped)
+			{
+				/* Success, so generate a FieldSelect expression */
+				FieldSelect *fselect = makeNode(FieldSelect);
+
+				fselect->arg = (Expr *) param;
+				fselect->fieldnum = i + 1;
+				fselect->resulttype = att->atttypid;
+				fselect->resulttypmod = att->atttypmod;
+				/* save attribute's collation for parse_collate.c */
+				fselect->resultcollid = att->attcollation;
+
+				ReleaseTupleDesc(tupdesc);
+				return (Node *) fselect;
+			}
+		}
+
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("could not identify column \"%s\" in variable", attrname),
+				 parser_errposition(pstate, location)));
+	}
+
+	return (Node *) param;
+}
+
 static Node *
 transformParamRef(ParseState *pstate, ParamRef *pref)
 {
@@ -1818,6 +1954,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
 		case EXPR_KIND_VALUES_SINGLE:
+		case EXPR_KIND_LET:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -1826,6 +1963,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("cannot use subquery in DEFAULT expression");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
@@ -3460,6 +3598,7 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "CHECK";
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			return "DEFAULT";
 		case EXPR_KIND_INDEX_EXPRESSION:
 			return "index expression";
@@ -3475,6 +3614,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "PARTITION BY";
 		case EXPR_KIND_CALL_ARGUMENT:
 			return "CALL";
+		case EXPR_KIND_LET:
+			return "LET";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 44257154b8..b2c9900e00 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2347,6 +2347,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("set-returning functions are not allowed in DEFAULT expressions");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
@@ -2370,6 +2371,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_CALL_ARGUMENT:
 			err = _("set-returning functions are not allowed in CALL arguments");
 			break;
+		case EXPR_KIND_LET:
+			err = _("set-returning functions are not allowed in CALL arguments");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4932e58022..c60fe011f7 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -35,16 +35,6 @@
 
 static void markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
 					 Var *var, int levelsup);
-static Node *transformAssignmentIndirection(ParseState *pstate,
-							   Node *basenode,
-							   const char *targetName,
-							   bool targetIsArray,
-							   Oid targetTypeId,
-							   int32 targetTypMod,
-							   Oid targetCollation,
-							   ListCell *indirection,
-							   Node *rhs,
-							   int location);
 static Node *transformAssignmentSubscripts(ParseState *pstate,
 							  Node *basenode,
 							  const char *targetName,
@@ -672,7 +662,7 @@ updateTargetListEntry(ParseState *pstate,
  * might want to decorate indirection cells with their own location info,
  * in which case the location argument could probably be dropped.)
  */
-static Node *
+Node *
 transformAssignmentIndirection(ParseState *pstate,
 							   Node *basenode,
 							   const char *targetName,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 3123ee274d..10737d422d 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3350,7 +3350,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 	 * get executed.  Also, utilities aren't rewritten at all (do we still
 	 * need that check?)
 	 */
-	if (event != CMD_SELECT && event != CMD_UTILITY)
+	if (event != CMD_SELECT && event != CMD_UTILITY && event != CMD_PLAN_UTILITY)
 	{
 		int			result_relation;
 		RangeTblEntry *rt_entry;
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index 61ef396d8a..6a068af799 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -212,7 +212,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	}
 
 	/*
-	 * For SELECT, UPDATE and DELETE, add security quals to enforce the USING
+	 * For SELECT, LET, UPDATE and DELETE, add security quals to enforce the USING
 	 * policies.  These security quals control access to existing table rows.
 	 * Restrictive policies are combined together using AND, and permissive
 	 * policies are combined together using OR.
@@ -222,6 +222,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 							  &restrictive_policies);
 
 	if (commandType == CMD_SELECT ||
+		commandType == CMD_PLAN_UTILITY ||
 		commandType == CMD_UPDATE ||
 		commandType == CMD_DELETE)
 		add_security_quals(rt_index,
@@ -423,6 +424,7 @@ get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id,
 			switch (cmd)
 			{
 				case CMD_SELECT:
+				case CMD_PLAN_UTILITY:
 					if (policy->polcmd == ACL_SELECT_CHR)
 						cmd_matches = true;
 					break;
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index c95a4d519d..47fb0f38b1 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -37,6 +37,7 @@
 #include "executor/functions.h"
 #include "executor/tqueue.h"
 #include "executor/tstoreReceiver.h"
+#include "executor/svariableReceiver.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "utils/portal.h"
@@ -143,6 +144,9 @@ CreateDestReceiver(CommandDest dest)
 
 		case DestTupleQueue:
 			return CreateTupleQueueDestReceiver(NULL);
+
+		case DestVariable:
+			return CreateVariableDestReceiver();
 	}
 
 	/* should never get here */
@@ -178,6 +182,7 @@ EndCommand(const char *commandTag, CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
@@ -222,6 +227,7 @@ NullCommand(CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
@@ -268,6 +274,7 @@ ReadyForQuery(CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b5804f64ad..35199fd0dc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -47,6 +47,7 @@
 #include "commands/proclang.h"
 #include "commands/publicationcmds.h"
 #include "commands/schemacmds.h"
+#include "commands/schemavariable.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
 #include "commands/subscriptioncmds.h"
@@ -344,7 +345,7 @@ ProcessUtility(PlannedStmt *pstmt,
 			   char *completionTag)
 {
 	Assert(IsA(pstmt, PlannedStmt));
-	Assert(pstmt->commandType == CMD_UTILITY);
+	Assert(pstmt->commandType == CMD_UTILITY || pstmt->commandType == CMD_PLAN_UTILITY);
 	Assert(queryString != NULL);	/* required as of 8.4 */
 
 	/*
@@ -915,6 +916,14 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_LetStmt:
+			{
+				doLetStmt(pstmt, params, queryEnv, queryString);
+				if (completionTag)
+					strcpy(completionTag, "LET");
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -1221,6 +1230,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				}
 				break;
 
+			case T_CreateSchemaVarStmt:
+				address = DefineSchemaVariable(pstate, (CreateSchemaVarStmt *) parsetree);
+				break;
+
 				/*
 				 * ************* object creation / destruction **************
 				 */
@@ -2055,6 +2068,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_STATISTIC_EXT:
 			tag = "ALTER STATISTICS";
 			break;
+		case OBJECT_VARIABLE:
+			tag = "ALTER VARIABLE";
+			break;
 		default:
 			tag = "???";
 			break;
@@ -2104,6 +2120,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "SELECT";
 			break;
 
+		case T_LetStmt:
+			tag = "LET";
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -2358,6 +2378,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_STATISTIC_EXT:
 					tag = "DROP STATISTICS";
 					break;
+				case OBJECT_VARIABLE:
+					tag = "DROP VARIABLE";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2639,6 +2662,9 @@ CreateCommandTag(Node *parsetree)
 				case DISCARD_SEQUENCES:
 					tag = "DISCARD SEQUENCES";
 					break;
+				case DISCARD_VARIABLES:
+					tag = "DISCARD VARIABLES";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2844,6 +2870,7 @@ CreateCommandTag(Node *parsetree)
 						tag = "DELETE";
 						break;
 					case CMD_UTILITY:
+					case CMD_PLAN_UTILITY:
 						tag = CreateCommandTag(stmt->utilityStmt);
 						break;
 					default:
@@ -2915,6 +2942,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_CreateSchemaVarStmt:
+			tag = "CREATE VARIABLE";
+			break;
+
 		default:
 			elog(WARNING, "unrecognized node type: %d",
 				 (int) nodeTag(parsetree));
@@ -2961,6 +2992,10 @@ GetCommandLogLevel(Node *parsetree)
 				lev = LOGSTMT_ALL;
 			break;
 
+		case T_LetStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			lev = LOGSTMT_ALL;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index a45e093de7..952c0d9628 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -315,6 +315,12 @@ aclparse(const char *s, AclItem *aip)
 			case ACL_CONNECT_CHR:
 				read = ACL_CONNECT;
 				break;
+			case ACL_READ_CHR:
+				read = ACL_READ;
+				break;
+			case ACL_WRITE_CHR:
+				read = ACL_WRITE;
+				break;
 			case 'R':			/* ignore old RULE privileges */
 				read = 0;
 				break;
@@ -808,6 +814,10 @@ acldefault(ObjectType objtype, Oid ownerId)
 			world_default = ACL_USAGE;
 			owner_default = ACL_ALL_RIGHTS_TYPE;
 			break;
+		case OBJECT_VARIABLE:
+			world_default = ACL_NO_RIGHTS;
+			owner_default = ACL_ALL_RIGHTS_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			world_default = ACL_NO_RIGHTS;	/* keep compiler quiet */
@@ -903,6 +913,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
 		case 'T':
 			objtype = OBJECT_TYPE;
 			break;
+		case 'V':
+			objtype = OBJECT_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype abbreviation: %c", objtypec);
 	}
@@ -1627,6 +1640,10 @@ convert_priv_string(text *priv_type_text)
 		return ACL_CONNECT;
 	if (pg_strcasecmp(priv_type, "RULE") == 0)
 		return 0;				/* ignore old RULE privileges */
+	if (pg_strcasecmp(priv_type, "READ") == 0)
+		return ACL_READ;
+	if (pg_strcasecmp(priv_type, "WRITE") == 0)
+		return ACL_WRITE;
 
 	ereport(ERROR,
 			(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1721,6 +1738,10 @@ convert_aclright_to_string(int aclright)
 			return "TEMPORARY";
 		case ACL_CONNECT:
 			return "CONNECT";
+		case ACL_READ:
+			return "READ";
+		case ACL_WRITE:
+			return "WRITE";
 		default:
 			elog(ERROR, "unrecognized aclright: %d", aclright);
 			return NULL;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 03e9a28a63..488cb26d3f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7362,6 +7362,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
+	/* translate paramid to original schema variable name */
+	if (param->paramkind == PARAM_SCHEMA_VARIABLE)
+	{
+		appendStringInfo(context->buf, "%s",
+							schema_variable_get_name(param->paramid));
+		return;
+	}
+
 	/*
 	 * Not PARAM_EXEC, or couldn't find referent: just print $N.
 	 */
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index bba595ad1d..858a6dd4be 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1691,6 +1691,18 @@ get_relname_relid(const char *relname, Oid relnamespace)
 						   ObjectIdGetDatum(relnamespace));
 }
 
+/*
+ * get_varname_varid
+ *		Given name and namespace of variable, look up the OID.
+ */
+Oid
+get_varname_varid(const char *varname, Oid varnamespace)
+{
+	return GetSysCacheOid2(VARIABLENAMENSP,
+						   PointerGetDatum(varname),
+						   ObjectIdGetDatum(varnamespace));
+}
+
 #ifdef NOT_USED
 /*
  * get_relnatts
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 2b381782a3..35dc32f649 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -73,6 +73,7 @@
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "utils/rel.h"
 #include "utils/catcache.h"
 #include "utils/syscache.h"
@@ -968,6 +969,28 @@ static const struct cachedesc cacheinfo[] = {
 			0
 		},
 		2
+	},
+	{VariableRelationId,			/* VARIABLENAMENSP */
+		VariableNameNspIndexId,
+		2,
+		{
+			Anum_pg_variable_varname,
+			Anum_pg_variable_varnamespace,
+			0,
+			0
+		},
+		8
+	},
+	{VariableRelationId,			/* VARIABLEOID */
+		VariableObjectIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		8
 	}
 };
 
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 0d147cb08d..6d97931d85 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -296,6 +296,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading subscriptions\n");
 	getSubscriptions(fout);
 
+	if (g_verbose)
+		write_msg(NULL, "reading variables\n");
+	getVariables(fout);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 83c976eaf7..c9bc91ca68 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3471,6 +3471,7 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
 		strcmp(type, "TEXT SEARCH DICTIONARY") == 0 ||
 		strcmp(type, "TEXT SEARCH CONFIGURATION") == 0 ||
 		strcmp(type, "STATISTICS") == 0 ||
+		strcmp(type, "VARIABLE") == 0 ||
 	/* non-schema-specified objects */
 		strcmp(type, "DATABASE") == 0 ||
 		strcmp(type, "PROCEDURAL LANGUAGE") == 0 ||
@@ -3670,7 +3671,8 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 			strcmp(te->desc, "SERVER") == 0 ||
 			strcmp(te->desc, "STATISTICS") == 0 ||
 			strcmp(te->desc, "PUBLICATION") == 0 ||
-			strcmp(te->desc, "SUBSCRIPTION") == 0)
+			strcmp(te->desc, "SUBSCRIPTION") == 0 ||
+			strcmp(te->desc, "VARIABLE") == 0)
 		{
 			PQExpBuffer temp = createPQExpBuffer();
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 9baf7b2fde..f825a00c9d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -260,6 +260,7 @@ static void dumpPolicy(Archive *fout, PolicyInfo *polinfo);
 static void dumpPublication(Archive *fout, PublicationInfo *pubinfo);
 static void dumpPublicationTable(Archive *fout, PublicationRelInfo *pubrinfo);
 static void dumpSubscription(Archive *fout, SubscriptionInfo *subinfo);
+static void dumpVariable(Archive *fout, VariableInfo *varinfo);
 static void dumpDatabase(Archive *AH);
 static void dumpDatabaseConfig(Archive *AH, PQExpBuffer outbuf,
 				   const char *dbname, Oid dboid);
@@ -4221,6 +4222,208 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
+/*
+ * getVariables
+ *	  get information about variables
+ */
+void
+getVariables(Archive *fout)
+{
+	DumpOptions *dopt = fout->dopt;
+	PQExpBuffer query;
+	PQExpBuffer acl_subquery = createPQExpBuffer();
+	PQExpBuffer racl_subquery = createPQExpBuffer();
+	PQExpBuffer init_acl_subquery = createPQExpBuffer();
+	PQExpBuffer init_racl_subquery = createPQExpBuffer();
+	PGresult   *res;
+	VariableInfo *varinfo;
+	int			i_tableoid;
+	int			i_oid;
+	int			i_varname;
+	int			i_varnamespace;
+	int			i_vartype;
+	int			i_vartypname;
+	int			i_vardefexpr;
+	int			i_rolname;
+	int			i_varacl;
+	int			i_rvaracl;
+	int			i_initvaracl;
+	int			i_initrvaracl;
+	int			i,
+				ntups;
+
+	if (fout->remoteVersion <= 110000)
+		return;
+
+	acl_subquery = createPQExpBuffer();
+	racl_subquery = createPQExpBuffer();
+	init_acl_subquery = createPQExpBuffer();
+	init_racl_subquery = createPQExpBuffer();
+
+	buildACLQueries(acl_subquery, racl_subquery, init_acl_subquery,
+					init_racl_subquery, "v.varacl", "v.varowner", "'V'",
+					dopt->binary_upgrade);
+
+	query = createPQExpBuffer();
+
+	resetPQExpBuffer(query);
+
+	/* Get the variables in current database. */
+	appendPQExpBuffer(query,
+						  "SELECT v.tableoid, v.oid, v.varname, "
+						  "v.varnamespace,"
+						  "(%s varowner) AS rolname, "
+						  "%s as varacl, "
+						  "%s as rvaracl, "
+						  "%s as initvaracl, "
+						  "%s as initrvaracl, "
+						  "v.vartype, "
+						  "pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname, "
+						  "pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr "
+						  "FROM pg_variable v "
+						  "LEFT JOIN pg_init_privs pip "
+						  "ON (v.oid = pip.objoid "
+						  "AND pip.classoid = 'pg_variable'::regclass "
+						  "AND pip.objsubid = 0)",
+						  username_subquery,
+						  acl_subquery->data,
+						  racl_subquery->data,
+						  init_acl_subquery->data,
+						  init_racl_subquery->data);
+
+	destroyPQExpBuffer(acl_subquery);
+	destroyPQExpBuffer(racl_subquery);
+	destroyPQExpBuffer(init_acl_subquery);
+	destroyPQExpBuffer(init_racl_subquery);
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	i_tableoid = PQfnumber(res, "tableoid");
+	i_oid = PQfnumber(res, "oid");
+	i_varname = PQfnumber(res, "varname");
+	i_varnamespace = PQfnumber(res, "varnamespace");
+	i_rolname = PQfnumber(res, "rolname");
+	i_vartype = PQfnumber(res, "vartype");
+	i_vartypname = PQfnumber(res, "vartypname");
+	i_vardefexpr = PQfnumber(res, "vardefexpr");
+	i_varacl = PQfnumber(res, "varacl");
+	i_rvaracl = PQfnumber(res, "rvaracl");
+	i_initvaracl = PQfnumber(res, "initvaracl");
+	i_initrvaracl = PQfnumber(res, "initrvaracl");
+
+	varinfo = pg_malloc(ntups * sizeof(VariableInfo));
+
+	for (i = 0; i < ntups; i++)
+	{
+		TypeInfo	   *vtype;
+
+		varinfo[i].dobj.objType = DO_VARIABLE;
+		varinfo[i].dobj.catId.tableoid =
+			atooid(PQgetvalue(res, i, i_tableoid));
+		varinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+		AssignDumpId(&varinfo[i].dobj);
+		varinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_varname));
+		varinfo[i].dobj.namespace =
+			findNamespace(fout,
+						  atooid(PQgetvalue(res, i, i_varnamespace)));
+
+		varinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+		varinfo[i].vartype = atooid(PQgetvalue(res, i, i_vartype));
+		varinfo[i].vartypname = pg_strdup(PQgetvalue(res, i, i_vartypname));
+
+		varinfo[i].varacl = pg_strdup(PQgetvalue(res, i, i_varacl));
+		varinfo[i].rvaracl = pg_strdup(PQgetvalue(res, i, i_rvaracl));
+		varinfo[i].initvaracl = pg_strdup(PQgetvalue(res, i, i_initvaracl));
+		varinfo[i].initrvaracl = pg_strdup(PQgetvalue(res, i, i_initrvaracl));
+
+		/* Decide whether we want to dump it */
+		selectDumpableObject(&(varinfo[i].dobj), fout);
+
+		/* Do not try to dump ACL if no ACL exists. */
+		if (PQgetisnull(res, i, i_varacl) && PQgetisnull(res, i, i_rvaracl) &&
+			PQgetisnull(res, i, i_initvaracl) &&
+			PQgetisnull(res, i, i_initrvaracl))
+			varinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL;
+
+		if (PQgetisnull(res, i, i_vardefexpr))
+			varinfo[i].vardefexpr = NULL;
+		else
+			varinfo[i].vardefexpr = pg_strdup(PQgetvalue(res, i, i_vardefexpr));
+
+		if (strlen(varinfo[i].rolname) == 0)
+			write_msg(NULL, "WARNING: owner of variable \"%s\" appears to be invalid\n",
+					  varinfo[i].dobj.name);
+
+		/* Decide whether we want to dump it */
+		selectDumpableObject(&(varinfo[i].dobj), fout);
+
+		vtype = findTypeByOid(varinfo[i].vartype);
+		addObjectDependency(&varinfo[i].dobj, vtype->dobj.dumpId);
+	}
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpVariable
+ *	  dump the definition of the given variables
+ */
+static void
+dumpVariable(Archive *fout, VariableInfo *varinfo)
+{
+	DumpOptions *dopt = fout->dopt;
+
+	PQExpBuffer delq;
+	PQExpBuffer query;
+	const char	   *varname;
+	const char	   *vartypname;
+	const char	   *vardefexpr;
+
+	/* Skip if not to be dumped */
+	if (!varinfo->dobj.dump || dopt->dataOnly)
+		return;
+
+	delq = createPQExpBuffer();
+	query = createPQExpBuffer();
+
+	varname = fmtQualifiedDumpable(varinfo);
+	vartypname = varinfo->vartypname;
+	vardefexpr = varinfo->vardefexpr;
+
+	appendPQExpBuffer(delq, "DROP VARIABLE %s;\n",
+					  varname);
+
+	appendPQExpBuffer(query, "CREATE VARIABLE %s AS %s",
+					  varname, vartypname);
+
+	if (vardefexpr)
+		appendPQExpBuffer(query, " DEFAULT %s",
+						  vardefexpr);
+
+	appendPQExpBuffer(query, ";\n");
+
+	ArchiveEntry(fout, varinfo->dobj.catId, varinfo->dobj.dumpId,
+				 varinfo->dobj.name,
+				 NULL,
+				 NULL,
+				 varinfo->rolname, false,
+				 "VARIABLE", SECTION_PRE_DATA,
+				 query->data, delq->data, NULL,
+				 NULL, 0,
+				 NULL, NULL);
+
+	if (varinfo->dobj.dump & DUMP_COMPONENT_COMMENT)
+		dumpComment(fout, "VARIABLE", varname,
+					NULL, varinfo->rolname,
+					varinfo->dobj.catId, 0, varinfo->dobj.dumpId);
+
+	destroyPQExpBuffer(delq);
+	destroyPQExpBuffer(query);
+}
+
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
@@ -9849,6 +10052,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION:
 			dumpSubscription(fout, (SubscriptionInfo *) dobj);
 			break;
+		case DO_VARIABLE:
+			dumpVariable(fout, (VariableInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -17935,6 +18141,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_OPFAMILY:
 			case DO_COLLATION:
 			case DO_CONVERSION:
+			case DO_VARIABLE:
 			case DO_TABLE:
 			case DO_ATTRDEF:
 			case DO_PROCLANG:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 1448005f30..0d49bb7ed7 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -84,7 +84,8 @@ typedef enum
 	DO_POLICY,
 	DO_PUBLICATION,
 	DO_PUBLICATION_REL,
-	DO_SUBSCRIPTION
+	DO_SUBSCRIPTION,
+	DO_VARIABLE
 } DumpableObjectType;
 
 /* component types of an object which can be selected for dumping */
@@ -625,6 +626,22 @@ typedef struct _SubscriptionInfo
 	char	   *subpublications;
 } SubscriptionInfo;
 
+/*
+ * The VariableInfo struct is used to represent schema variables
+ */
+typedef struct _VariableInfo
+{
+	DumpableObject dobj;
+	Oid			vartype;
+	char	   *vartypname;
+	char	   *rolname;		/* name of owner, or empty string */
+	char	   *vardefexpr;
+	char	   *varacl;
+	char	   *rvaracl;
+	char	   *initvaracl;
+	char	   *initrvaracl;
+} VariableInfo;
+
 /*
  * We build an array of these with an entry for each object that is an
  * extension member according to pg_depend.
@@ -725,5 +742,6 @@ extern void getPublications(Archive *fout);
 extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
 					 int numTables);
 extern void getSubscriptions(Archive *fout);
+extern void getVariables(Archive *fout);
 
 #endif							/* PG_DUMP_H */
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index ec751a7c23..2a67766ed4 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2601,6 +2601,38 @@ my %tests = (
 		},
 	},
 
+	'CREATE VARIABLE test_variable' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE VARIABLE dump_test.variable AS integer DEFAULT 0;',
+		regexp => qr/^
+			\QCREATE VARIABLE dump_test.variable AS integer DEFAULT 0;\E/xm,
+		like => {
+			binary_upgrade          => 1,
+			clean                   => 1,
+			clean_if_exists         => 1,
+			createdb                => 1,
+			defaults                => 1,
+			exclude_test_table      => 1,
+			exclude_test_table_data => 1,
+			no_blobs                => 1,
+			no_privs                => 1,
+			no_owner                => 1,
+			only_dump_test_schema   => 1,
+			pg_dumpall_dbprivs      => 1,
+			schema_only             => 1,
+			section_pre_data        => 1,
+			test_schema_plus_blobs  => 1,
+			with_oids               => 1, },
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_test_table     => 1,
+			pg_dumpall_globals       => 1,
+			pg_dumpall_globals_clean => 1,
+			role                     => 1,
+			section_post_data        => 1, }, },
+
 	'CREATE VIEW test_view' => {
 		create_order => 61,
 		create_sql   => 'CREATE VIEW dump_test.test_view
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 5b4d54a442..73a752fd7e 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -853,6 +853,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 						break;
 				}
 				break;
+			case 'V':			/* Variables */
+				success = listVariables(pattern, show_verbose);
+				break;
 			case 'x':			/* Extensions */
 				if (show_verbose)
 					success = listExtensionContents(pattern);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 80d8338b96..d645bba7af 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4178,6 +4178,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
 	return true;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
+listVariables(const char *pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+	static const bool translate_columns[] = {false, false, false, false, false, false, false};
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT n.nspname as \"%s\",\n"
+					  "  v.varname as \"%s\",\n"
+					  "  pg_catalog.format_type(v.vartype, v.vartypmod) as \"%s\",\n"
+					  "  pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n"
+					  "  pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\"",
+					  gettext_noop("Schema"),
+					  gettext_noop("Name"),
+					  gettext_noop("Type"),
+					  gettext_noop("Owner"),
+					  gettext_noop("Default"));
+
+	appendPQExpBufferStr(&buf,
+						 "\nFROM pg_catalog.pg_variable v"
+						 "\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = v.varnamespace");
+
+	appendPQExpBufferStr(&buf, "\nWHERE true\n");
+	if (!pattern)
+		appendPQExpBufferStr(&buf, "      AND n.nspname <> 'pg_catalog'\n"
+							 "      AND n.nspname <> 'information_schema'\n");
+
+	processSQLNamePattern(pset.db, &buf, pattern, true, false,
+						  "n.nspname", "v.varname", NULL,
+						  "pg_catalog.pg_variable_is_visible(v.oid)");
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	/*
+	 * Most functions in this file are content to print an empty table when
+	 * there are no matching objects.  We intentionally deviate from that
+	 * here, but only in !quiet mode, for historical reasons.
+	 */
+	if (PQntuples(res) == 0 && !pset.quiet)
+	{
+		if (pattern)
+			psql_error("Did not find any schema variable named \"%s\".\n",
+					   pattern);
+		else
+			psql_error("Did not find any schema variables.\n");
+	}
+	else
+	{
+		myopt.nullPrint = NULL;
+		myopt.title = _("List of variables");
+		myopt.translate_header = true;
+		myopt.translate_columns = translate_columns;
+		myopt.n_translate_columns = lengthof(translate_columns);
+
+		printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+	}
+
+	PQclear(res);
+	return true;
+}
 
 /*
  * \dFp
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index a4cc5efae0..ecc4e3a531 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -63,6 +63,9 @@ extern bool listAllDbs(const char *pattern, bool verbose);
 /* \dt, \di, \ds, \dS, etc. */
 extern bool listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSystem);
 
+/* \dV */
+extern bool listVariables(const char *pattern, bool varbose);
+
 /* \dD */
 extern bool listDomains(const char *pattern, bool verbose, bool showSystem);
 
index 316030d358..adcc36cb6e 100644
 
+	fprintf(output, _("  \\dV     [PATTERN]      list variables\n"));
index bb696f8ee9..ebec00fe1f 100644
@@ -805,6 +805,22 @@ static const SchemaQuery Query_for_list_of_statistics = {
 	NULL
 };
 
+static const SchemaQuery Query_for_list_of_variables = {
+	/* min_server_version */
+	0,
+	/* catname */
+	"pg_catalog.pg_variable v",
+	/* selcondition */
+	NULL,
+	/* viscondition */
+	"pg_catalog.pg_variable_is_visible(v.oid)",
+	/* namespace */
+	"v.varnamespace",
+	/* result */
+	"pg_catalog.quote_ident(v.varname)",
+	/* qualresult */
+	NULL
+};
 
 /*
  * Queries to get lists of names of various kinds of things, possibly
@@ -1249,6 +1265,7 @@ static const pgsql_thing_t words_after_create[] = {
 																	 * TABLE ... */
 	{"USER", Query_for_list_of_roles " UNION SELECT 'MAPPING FOR'"},
 	{"USER MAPPING FOR", NULL, NULL, NULL},
+	{"VARIABLE", NULL, NULL, &Query_for_list_of_variables},
 	{"VIEW", NULL, NULL, &Query_for_list_of_views},
 	{NULL}						/* end of list */
 };
@@ -1604,7 +1621,7 @@ psql_completion(const char *text, int start, int end)
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CALL", "CHECKPOINT", "CLOSE", "CLUSTER",
 		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
 		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
-		"FETCH", "GRANT", "IMPORT", "INSERT", "LISTEN", "LOAD", "LOCK",
+		"FETCH", "GRANT", "IMPORT", "INSERT", "LET", "LISTEN", "LOAD", "LOCK",
 		"MOVE", "NOTIFY", "PREPARE",
 		"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
 		"RESET", "REVOKE", "ROLLBACK",
@@ -1621,9 +1638,9 @@ psql_completion(const char *text, int start, int end)
 		"\\d", "\\da", "\\dA", "\\db", "\\dc", "\\dC", "\\dd", "\\ddp", "\\dD",
 		"\\des", "\\det", "\\deu", "\\dew", "\\dE", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
-		"\\dm", "\\dn", "\\do", "\\dO", "\\dp",
+		"\\dm", "\\dn", "\\do", "\\dO", "\\dp"
 		"\\drds", "\\dRs", "\\dRp", "\\ds", "\\dS",
-		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
+		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy", "\\dV",
 		"\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding",
 		"\\endif", "\\errverbose", "\\ev",
 		"\\f",
@@ -2837,6 +2854,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
@@ -2890,7 +2915,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 	else if (Matches1("DISCARD"))
-		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
+		COMPLETE_WITH_LIST5("ALL", "PLANS", "SEQUENCES", "TEMP", "VARIABLES");
 
 /* DO */
 	else if (Matches1("DO"))
@@ -2992,6 +3017,12 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* DROP VARIABLE */
@@ -3002,14 +3033,14 @@ psql_completion(const char *text, int start, int end)
 
 /* FETCH && MOVE */
@@ -3118,6 +3149,7 @@ psql_completion(const char *text, int start, int end)
@@ -3131,14 +3163,16 @@ psql_completion(const char *text, int start, int end)
 
@@ -3172,6 +3206,8 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		else if (TailMatches1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		else if (TailMatches1("VARIABLE"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
 		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -3324,7 +3360,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -3547,6 +3583,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
+/* LET --- can be inside EXPLAIN, PREPARE etc */
+	/* If prev. word is LET suggest a list of variables */
+	else if (TailMatches1("LET"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
+	/* Complete LET <variable> with "=" */
+	else if (TailMatches2("LET", MatchAny))
+		COMPLETE_WITH_CONST("=");
+
 /* USER MAPPING */
 	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 46c271a46c..3e38a05e55 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -180,7 +180,8 @@ typedef enum ObjectClass
 	OCLASS_PUBLICATION,			/* pg_publication */
 	OCLASS_PUBLICATION_REL,		/* pg_publication_rel */
 	OCLASS_SUBSCRIPTION,		/* pg_subscription */
-	OCLASS_TRANSFORM			/* pg_transform */
+	OCLASS_TRANSFORM,			/* pg_transform */
+	OCLASS_VARIABLE				/* pg_variable */
 } ObjectClass;
 
 #define LAST_OCLASS		OCLASS_TRANSFORM
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 24915824ca..dae80c20a8 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -360,4 +360,10 @@ DECLARE_UNIQUE_INDEX(pg_subscription_subname_index, 6115, on pg_subscription usi
 DECLARE_UNIQUE_INDEX(pg_subscription_rel_srrelid_srsubid_index, 6117, on pg_subscription_rel using btree(srrelid oid_ops, srsubid oid_ops));
 #define SubscriptionRelSrrelidSrsubidIndexId 6117
 
+DECLARE_UNIQUE_INDEX(pg_variable_oid_index, 4288, on pg_variable using btree(oid oid_ops));
+#define VariableObjectIndexId 4288
+
+DECLARE_UNIQUE_INDEX(pg_variable_varname_nsp_index, 4289, on pg_variable using btree(varname name_ops, varnamespace oid_ops));
+#define VariableNameNspIndexId  4289
+
 #endif							/* INDEXING_H */
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 7991de5e21..75068d7e92 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -75,10 +75,13 @@ extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *newRelation,
 extern void RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid);
 extern Oid	RelnameGetRelid(const char *relname);
 extern bool RelationIsVisible(Oid relid);
+extern bool VariableIsVisible(Oid relid);
 
 extern Oid	TypenameGetTypid(const char *typname);
 extern bool TypeIsVisible(Oid typid);
 
+extern bool VariableIsVisible(Oid varid);
+
 extern FuncCandidateList FuncnameGetCandidates(List *names,
 					  int nargs, List *argnames,
 					  bool expand_variadic,
@@ -145,6 +148,10 @@ extern void SetTempNamespaceState(Oid tempNamespaceId,
 					  Oid tempToastNamespaceId);
 extern void ResetTempTableNamespace(void);
 
+extern List *NamesFromList(List *names);
+extern Oid lookup_variable(const char *nspname, const char *varname, bool missing_ok);
+extern Oid identify_variable(List *names, char **attrname, bool *not_uniq);
+
 extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context);
 extern OverrideSearchPath *CopyOverrideSearchPath(OverrideSearchPath *path);
 extern bool OverrideSearchPathMatchesCurrent(OverrideSearchPath *path);
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index d0410f5586..56deef1a45 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -57,6 +57,7 @@ typedef FormData_pg_default_acl *Form_pg_default_acl;
 #define DEFACLOBJ_FUNCTION		'f' /* function */
 #define DEFACLOBJ_TYPE			'T' /* type */
 #define DEFACLOBJ_NAMESPACE		'n' /* namespace */
+#define DEFACLOBJ_VARIABLE		'V' /* variable */
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a14651010f..61cbe65805 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5961,6 +5961,9 @@
   proname => 'pg_collation_is_visible', procost => '10', provolatile => 's',
   prorettype => 'bool', proargtypes => 'oid',
   prosrc => 'pg_collation_is_visible' },
+{ oid => '4187', descr => 'is schema variable visible in search path?',
+  proname => 'pg_variable_is_visible', procost => '10', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'oid', prosrc => 'pg_variable_is_visible' },
 
 { oid => '2854', descr => 'get OID of current session\'s temp schema, if any',
   proname => 'pg_my_temp_schema', provolatile => 's', proparallel => 'r',
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
new file mode 100644
index 0000000000..34f4c34202
--- /dev/null
+++ b/src/include/catalog/pg_variable.h
@@ -0,0 +1,85 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.h
+ *	  definition of schema variables system catalog (pg_variables)
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_variable.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_VARIABLE_H
+#define PG_VARIABLE_H
+
+#include "catalog/genbki.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_variable_d.h"
+#include "utils/acl.h"
+
+/* ----------------
+ *		pg_variable definition.  cpp turns this into
+ *		typedef struct FormData_pg_variable
+ * ----------------
+ */
+CATALOG(pg_variable,4287,VariableRelationId)
+{
+	NameData	varname;		/* variable name */
+	Oid			varnamespace;	/* OID of namespace containing variable class */
+	Oid			vartype;		/* OID of entry in pg_type for variable's type */
+	int32		vartypmod;			/* typmode for variable's type */
+	Oid			varowner;		/* class owner */
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+
+	/* list of expression trees for variable default (NULL if none) */
+	pg_node_tree vardefexpr BKI_DEFAULT(_null_);
+
+	aclitem		varacl[1] BKI_DEFAULT(_null_);		/* access permissions */
+
+#endif
+} FormData_pg_variable;
+
+/* ----------------
+ *		Form_pg_variable corresponds to a pointer to a tuple with
+ *		the format of pg_variable relation.
+ * ----------------
+ */
+typedef FormData_pg_variable *Form_pg_variable;
+
+typedef struct Variable
+{
+	Oid		oid;
+	char   *name;
+	Oid		namespace;
+	Oid		typid;
+	int32	typmod;
+	Oid		owner;
+	Node   *defexpr;
+	Acl	   *acl;
+} Variable;
+
+/* returns fields from pg_variable table */
+extern char *get_schema_variable_name(Oid varid);
+extern void get_schema_variable_type_typmod(Oid varid, Oid *typid, int32 *typmod);
+
+/* returns name of variable based on current search path */
+extern char *schema_variable_get_name(Oid varid);
+
+extern Variable *GetVariable(Oid varid, bool missing_ok);
+extern ObjectAddress VariableCreate(const char *varName,
+									   Oid varNamespace,
+									   Oid varType,
+									   int32 varTypmod,
+									   Oid varOwner,
+									   Node *varDefexpr,
+									   bool if_not_exists);
+
+
+#endif							/* PG_VARIABLE_H */
diff --git a/src/include/commands/schemavariable.h b/src/include/commands/schemavariable.h
new file mode 100644
index 0000000000..dd3239b236
--- /dev/null
+++ b/src/include/commands/schemavariable.h
@@ -0,0 +1,37 @@
+/*-------------------------------------------------------------------------
+ *
+ * schemavariable.h
+ *	  prototypes for schemavariable.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/schemavariable.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SCHEMAVARIABLE_H
+#define SCHEMAVARIABLE_H
+
+#include "catalog/objectaddress.h"
+#include "catalog/pg_variable.h"
+#include "nodes/params.h"
+#include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
+#include "utils/queryenvironment.h"
+
+extern char *VariableGetName(Variable *var);
+
+extern void ResetSchemaVariableCache(void);
+
+extern void RemoveVariableById(Oid varid);
+extern ObjectAddress DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt *stmt);
+
+extern Datum GetSchemaVariable(Oid varid, bool *isNull, Oid expected_typid);
+extern void SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid, int32 typmod);
+
+extern void doLetStmt(PlannedStmt *pstmt, ParamListInfo params, QueryEnvironment *queryEnv, const char *queryString);
+
+#endif
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index f7b1f77616..cca30f275b 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -138,6 +138,7 @@ typedef enum ExprEvalOp
 	EEOP_PARAM_EXEC,
 	EEOP_PARAM_EXTERN,
 	EEOP_PARAM_CALLBACK,
+	EEOP_PARAM_VARIABLE,
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -344,11 +345,11 @@ typedef struct ExprEvalStep
 			TupleDesc	argdesc;
 		}			nulltest_row;

 		/* for EEOP_PARAM_CALLBACK */
diff --git a/src/include/executor/svariableReceiver.h b/src/include/executor/svariableReceiver.h
new file mode 100644
index 0000000000..8c8117701f
--- /dev/null
+++ b/src/include/executor/svariableReceiver.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.h
+ *	  prototypes for svariableReceiver.c
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/svariableReceiver.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SVARIABLE_RECEIVER_H
+#define SVARIABLE_RECEIVER_H
+
+#include "tcop/dest.h"
+
+
+extern DestReceiver *CreateVariableDestReceiver(void);
+
+extern void SetVariableDestReceiverParams(DestReceiver *self, Oid varid);
+
+#endif							/* SVARIABLE_RECEIVER_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 018f50bbb7..08b4b2c2f2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -564,6 +564,8 @@ typedef struct EState
 	/* The per-query shared memory area to use for parallel execution. */
 	struct dsa_area *es_query_dsa;
 
+	int			es_result_variable;	/* Oid of target variable */
+
 	/*
 	 * JIT information. es_jit_flags indicates whether JIT should be performed
 	 * and with which options.  es_jit is created on-demand when JITing is
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 697d3d7a5f..dd7fd8ed42 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -348,6 +348,7 @@ typedef enum NodeTag
 	T_CreateTableAsStmt,
 	T_CreateSeqStmt,
 	T_AlterSeqStmt,
+	T_CreateSchemaVarStmt,
 	T_VariableSetStmt,
 	T_VariableShowStmt,
 	T_DiscardStmt,
@@ -419,6 +420,7 @@ typedef enum NodeTag
 	T_CreateStatsStmt,
 	T_AlterCollationStmt,
 	T_CallStmt,
+	T_LetStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -663,6 +665,7 @@ typedef enum CmdType
 	CMD_DELETE,
 	CMD_UTILITY,				/* cmds like create, destroy, copy, vacuum,
 								 * etc. */
+	CMD_PLAN_UTILITY,			/* only let stmt now, requires planning */
 	CMD_NOTHING					/* dummy command for instead nothing rules
 								 * with qual */
 } CmdType;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07ab1a3dde..2d4a3cb1b6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -84,7 +84,9 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
 #define ACL_CREATE		(1<<9)	/* for namespaces and databases */
 #define ACL_CREATE_TEMP (1<<10) /* for databases */
 #define ACL_CONNECT		(1<<11) /* for databases */
-#define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
+#define ACL_READ		(1<<12) /* for variables */
+#define ACL_WRITE		(1<<13) /* for variables */
+#define N_ACL_RIGHTS	14		/* 1 plus the last 1<<x */
 #define ACL_NO_RIGHTS	0
 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
 #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
@@ -121,6 +123,7 @@ typedef struct Query
 
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
+	int			resultVariable;	/* Oid of target variable or 0 */
 
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
@@ -1505,6 +1508,18 @@ typedef struct UpdateStmt
 	WithClause *withClause;		/* WITH clause */
 } UpdateStmt;
 
+/* ----------------------
+ *		Let Statement
+ * ----------------------
+ */
+typedef struct LetStmt
+{
+	NodeTag		type;
+	List	   *target;			/* target variable */
+	Node	   *selectStmt;		/* source expression */
+	int			location;
+} LetStmt;
+
 /* ----------------------
  *		Select Statement
  *
@@ -1682,6 +1697,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -2497,6 +2513,19 @@ typedef struct AlterSeqStmt
 	bool		missing_ok;		/* skip error if a role is missing? */
 } AlterSeqStmt;
 
+/* ----------------------
+ *		{Create|Alter} VARIABLE Statement
+ * ----------------------
+ */
+typedef struct CreateSchemaVarStmt
+{
+	NodeTag		type;
+	RangeVar   *variable;		/* the variable to create */
+	TypeName   *typeName;		/* the type of variable */
+	Node	   *defexpr;		/* default expression */
+	bool		if_not_exists;	/* do nothing if it already exists */
+} CreateSchemaVarStmt;
+
 /* ----------------------
  *		Create {Aggregate|Operator|Type} Statement
  * ----------------------
@@ -3238,7 +3267,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;
 
 typedef struct DiscardStmt
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 7c2abbd03a..2588f1455f 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -43,7 +43,7 @@ typedef struct PlannedStmt
 {
 	NodeTag		type;
 
-	CmdType		commandType;	/* select|insert|update|delete|utility */
+	CmdType		commandType;	/* select|let|insert|update|delete|utility */
 
 	uint64		queryId;		/* query identifier (copied from Query) */
 
@@ -81,6 +81,9 @@ typedef struct PlannedStmt
 	 */
 	List	   *rootResultRelations;
 
+	/* Oid of target variable for LET command */
+	Oid			resultVariable;
+
 	List	   *subplans;		/* Plan trees for SubPlan expressions; note
 								 * that some could be NULL */

 typedef struct Param
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 23db40147b..d3ed3f4d0f 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -231,6 +231,7 @@ PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD)
 PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD)
 PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD)
 PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("let", LET, UNRESERVED_KEYWORD)
 PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
 PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
@@ -434,6 +435,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD)
 PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD)
 PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD)
+PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD)
+PG_KEYWORD("variables", VARIABLES, UNRESERVED_KEYWORD)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 0230543810..f7c2e67f33 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -69,7 +69,9 @@ typedef enum ParseExprKind
 	EXPR_KIND_TRIGGER_WHEN,		/* WHEN condition in CREATE TRIGGER */
 	EXPR_KIND_POLICY,			/* USING or WITH CHECK expr in policy */
 	EXPR_KIND_PARTITION_EXPRESSION, /* PARTITION BY expression */
-	EXPR_KIND_CALL_ARGUMENT		/* procedure argument in CALL */
+	EXPR_KIND_CALL_ARGUMENT,		/* procedure argument in CALL */
+	EXPR_KIND_VARIABLE_DEFAULT,	/* default value for schema variable */
+	EXPR_KIND_LET				/* LET assignment (should be same like UPDATE) */
 } ParseExprKind;

diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h
index ec6e0c102f..1ee199ed8f 100644
--- a/src/include/parser/parse_target.h
+++ b/src/include/parser/parse_target.h
@@ -32,6 +32,16 @@ extern Expr *transformAssignedExpr(ParseState *pstate, Expr *expr,
 					  int attrno,
 					  List *indirection,
 					  int location);
+extern Node *transformAssignmentIndirection(ParseState *pstate,
+							   Node *basenode,
+							   const char *targetName,
+							   bool targetIsArray,
+							   Oid targetTypeId,
+							   int32 targetTypMod,
+							   Oid targetCollation,
+							   ListCell *indirection,
+							   Node *rhs,
+							   int location);
 extern void updateTargetListEntry(ParseState *pstate, TargetEntry *tle,
 					  char *colname, int attrno,
 					  List *indirection,
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 82f0f2e741..c49b653555 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -96,7 +96,8 @@ typedef enum
 	DestCopyOut,				/* results sent to COPY TO code */
 	DestSQLFunction,			/* results sent to SQL-language func mgr */
 	DestTransientRel,			/* results sent to transient relation */
-	DestTupleQueue				/* results sent to tuple queue */
+	DestTupleQueue,				/* results sent to tuple queue */
+	DestVariable				/* results sents to schema variable */
 } CommandDest;
 
 /* ----------------
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index f4d4be8d0d..c624d8dd0b 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -147,9 +147,11 @@ typedef ArrayType Acl;
 #define ACL_CREATE_CHR			'C'
 #define ACL_CREATE_TEMP_CHR		'T'
 #define ACL_CONNECT_CHR			'c'
+#define ACL_READ_CHR			'S' /* 'R' is occupated by old RULE priv */
+#define ACL_WRITE_CHR			'W'
 
 /* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTc"
+#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTcSW"
 
 /*
  * Bitmasks defining "all rights" for each supported object type
@@ -166,6 +168,7 @@ typedef ArrayType Acl;
 #define ACL_ALL_RIGHTS_SCHEMA		(ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE	(ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE			(ACL_USAGE)
+#define ACL_ALL_RIGHTS_VARIABLE		(ACL_READ|ACL_WRITE)
 
 /* operation codes for pg_*_aclmask */
 typedef enum
@@ -253,6 +256,8 @@ extern AclMode pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
 						  AclMode mask, AclMaskHow how);
 extern AclMode pg_type_aclmask(Oid type_oid, Oid roleid,
 				AclMode mask, AclMaskHow how);
+extern AclMode pg_variable_aclmask(Oid var_oid, Oid roleid,
+				AclMode mask, AclMaskHow how);
 
 extern AclResult pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum,
 					  Oid roleid, AclMode mode);
@@ -269,6 +274,7 @@ extern AclResult pg_tablespace_aclcheck(Oid spc_oid, Oid roleid, AclMode mode);
 extern AclResult pg_foreign_data_wrapper_aclcheck(Oid fdw_oid, Oid roleid, AclMode mode);
 extern AclResult pg_foreign_server_aclcheck(Oid srv_oid, Oid roleid, AclMode mode);
 extern AclResult pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode);
+extern AclResult pg_variable_aclcheck(Oid type_oid, Oid roleid, AclMode mode);
 
 extern void aclcheck_error(AclResult aclerr, ObjectType objtype,
 			   const char *objectname);
@@ -305,6 +311,7 @@ extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
 extern bool pg_publication_ownercheck(Oid pub_oid, Oid roleid);
 extern bool pg_subscription_ownercheck(Oid sub_oid, Oid roleid);
 extern bool pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid);
+extern bool pg_variable_ownercheck(Oid stat_oid, Oid roleid);
 extern bool has_createrole_privilege(Oid roleid);
 extern bool has_bypassrls_privilege(Oid roleid);
 
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index e55ea4035b..cb3f4aaca9 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -122,6 +122,7 @@ extern bool get_func_leakproof(Oid funcid);
 extern float4 get_func_cost(Oid funcid);
 extern float4 get_func_rows(Oid funcid);
 extern Oid	get_relname_relid(const char *relname, Oid relnamespace);
+extern Oid get_varname_varid(const char *varname, Oid varnamespace);
 extern char *get_rel_name(Oid relid);
 extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 4f333586ee..453699be3c 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -107,9 +107,11 @@ enum SysCacheIdentifier
 	TYPENAMENSP,
 	TYPEOID,
 	USERMAPPINGOID,
-	USERMAPPINGUSERSERVER
+	USERMAPPINGUSERSERVER,
+	VARIABLENAMENSP,
+	VARIABLEOID
 
-#define SysCacheSize (USERMAPPINGUSERSERVER + 1)
+#define SysCacheSize (VARIABLEOID + 1)
 };
 
 extern void InitCatalogCache(void);
diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out
index 2d3522b500..48286f8e1a 100644
--- a/src/test/regress/expected/misc_sanity.out
+++ b/src/test/regress/expected/misc_sanity.out
@@ -105,5 +105,7 @@ ORDER BY 1, 2;
  pg_index                | indpred       | pg_node_tree
  pg_largeobject          | data          | bytea
  pg_largeobject_metadata | lomacl        | aclitem[]
-(11 rows)
+ pg_variable             | varacl        | aclitem[]
+ pg_variable             | vardefexpr    | pg_node_tree
+(13 rows)
 
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 0aa5357917..848b041a4b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -163,6 +163,7 @@ pg_ts_parser|t
 pg_ts_template|t
 pg_type|t
 pg_user_mapping|t
+pg_variable|t
 point_tbl|t
 polygon_tbl|t
 quad_box_tbl|t
diff --git a/src/test/regress/expected/schema_variables.out b/src/test/regress/expected/schema_variables.out
new file mode 100644
index 0000000000..f2017b8da9
--- /dev/null
+++ b/src/test/regress/expected/schema_variables.out
@@ -0,0 +1,306 @@
+CREATE VARIABLE var1 AS integer;
+CREATE TEMP VARIABLE var2 AS text;
+DROP VARIABLE var1, var2;
+-- functional interface
+CREATE VARIABLE var1 AS numeric;
+CREATE ROLE var_test_role;
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT var1;
+ERROR:  permission denied for schema variable var1
+SET ROLE TO DEFAULT;
+GRANT READ ON VARIABLE var1 TO var_test_role;
+SET ROLE TO var_test_role;
+-- should to fail
+LET var1 = 10;
+ERROR:  permission denied for schema variable var1
+-- should to work
+SELECT var1;
+ var1 
+------
+     
+(1 row)
+
+SET ROLE TO DEFAULT;
+GRANT WRITE ON VARIABLE var1 TO var_test_role;
+SET ROLE TO var_test_role;
+-- should to work
+LET var1 = 333;
+SET ROLE TO DEFAULT;
+REVOKE ALL ON VARIABLE var1 FROM var_test_role;
+CREATE OR REPLACE FUNCTION secure_var()
+RETURNS int AS $$
+  SELECT public.var1::int;
+$$ LANGUAGE sql SECURITY DEFINER;
+SELECT secure_var();
+ secure_var 
+------------
+        333
+(1 row)
+
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT public.var1;
+ERROR:  permission denied for schema variable var1
+-- should to work;
+SELECT secure_var();
+ secure_var 
+------------
+        333
+(1 row)
+
+SET ROLE TO DEFAULT;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var1;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Function Scan on pg_catalog.generate_series g
+   Output: v
+   Function Call: generate_series(1, 100)
+   Filter: ((g.v)::numeric = var1)
+(4 rows)
+
+CREATE VIEW schema_var_view AS SELECT var1;
+SELECT * FROM schema_var_view;
+ var1 
+------
+  333
+(1 row)
+
+\c -
+-- should to work still, but var will be empty
+SELECT * FROM schema_var_view;
+ var1 
+------
+     
+(1 row)
+
+LET var1 = pi();
+SELECT var1;
+       var1       
+------------------
+ 3.14159265358979
+(1 row)
+
+-- we can look on execution plan
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+         QUERY PLAN         
+----------------------------
+ Result
+   Output: 3.14159265358979
+(2 rows)
+
+-- LET can be prepared
+PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2;
+EXECUTE var_pp(100, 1.23456);
+SELECT var1;
+   var1    
+-----------
+ 101.23456
+(1 row)
+
+CREATE VARIABLE var3 AS int;
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+  LET public.var3 = COALESCE(public.var3 + $1, $1);
+  RETURN var3;
+END;
+$$ LANGUAGE plpgsql;
+SELECT inc(1);
+ inc 
+-----
+   1
+(1 row)
+
+SELECT inc(1);
+ inc 
+-----
+   2
+(1 row)
+
+SELECT inc(1);
+ inc 
+-----
+   3
+(1 row)
+
+SELECT inc(1) FROM generate_series(1,10);
+ inc 
+-----
+   4
+   5
+   6
+   7
+   8
+   9
+  10
+  11
+  12
+  13
+(10 rows)
+
+SET ROLE TO var_test_role;
+-- should to fail
+LET var3 = 0;
+ERROR:  permission denied for schema variable var3
+SET ROLE TO DEFAULT;
+DROP VIEW schema_var_view;
+DROP VARIABLE var1 CASCADE;
+DROP VARIABLE var3 CASCADE;
+-- composite variables
+CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2));
+CREATE VARIABLE v1 AS sv_xyz;
+CREATE VARIABLE v2 AS sv_xyz;
+\d v1
+\d v2
+LET v1 = (1,2,3.14);
+LET v2 = (10,20,3.14*10);
+-- should to work too - there are prepared casts
+LET v1 = (1,2,3.14);
+SELECT v1;
+     v1     
+------------
+ (1,2,3.14)
+(1 row)
+
+SELECT v2;
+      v2       
+---------------
+ (10,20,31.40)
+(1 row)
+
+SELECT (v1).*;
+ x | y |  z   
+---+---+------
+ 1 | 2 | 3.14
+(1 row)
+
+SELECT (v2).*;
+ x  | y  |   z   
+----+----+-------
+ 10 | 20 | 31.40
+(1 row)
+
+SELECT v1.x + v1.z;
+ ?column? 
+----------
+     4.14
+(1 row)
+
+SELECT v2.x + v2.z;
+ ?column? 
+----------
+    41.40
+(1 row)
+
+-- access to composite fields should be safe too
+-- should to fail
+SET ROLE TO var_test_role;
+SELECT v2.x;
+ERROR:  permission denied for schema variable v2
+SET ROLE TO DEFAULT;
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+DROP ROLE var_test_role;
+-- scalar variables should not be in conflict with qualified column
+CREATE VARIABLE varx AS text;
+SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class';
+ relname  
+----------
+ pg_class
+(1 row)
+
+-- should to fail
+SELECT varx.xxx;
+ERROR:  type text is not composite
+-- variables can be updated under RO transaction
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET varx = 'hello';
+COMMIT;
+SELECT varx;
+ varx  
+-------
+ hello
+(1 row)
+
+DROP VARIABLE varx;
+CREATE TYPE t1 AS (a int, b numeric, c text);
+CREATE VARIABLE v1 AS t1;
+LET v1 = (1, pi(), 'hello');
+SELECT v1;
+             v1             
+----------------------------
+ (1,3.14159265358979,hello)
+(1 row)
+
+LET v1.b = 10.2222;
+SELECT v1;
+        v1         
+-------------------
+ (1,10.2222,hello)
+(1 row)
+
+-- should to fail
+LET v1.x = 10;
+ERROR:  cannot assign to field "x" of column "x" because there is no such column in data type t1
+LINE 1: LET v1.x = 10;
+            ^
+DROP VARIABLE v1;
+DROP TYPE t1;
+-- arrays are supported
+CREATE VARIABLE va1 AS numeric[];
+LET va1 = ARRAY[1.1,2.1];
+LET va1[1] = 10.1;
+SELECT va1;
+    va1     
+------------
+ {10.1,2.1}
+(1 row)
+
+CREATE TYPE ta2 AS (a numeric, b numeric[]);
+CREATE VARIABLE va2 AS ta2;
+LET va2 = (10.1, ARRAY[0.0, 0.0]);
+LET va2.a = 10.2;
+SELECT va2;
+        va2         
+--------------------
+ (10.2,"{0.0,0.0}")
+(1 row)
+
+LET va2.b[1] = 10.3;
+SELECT va2;
+         va2         
+---------------------
+ (10.2,"{10.3,0.0}")
+(1 row)
+
+DROP VARIABLE va1;
+DROP VARIABLE va2;
+DROP TYPE ta2;
+-- default values
+CREATE VARIABLE v1 AS numeric DEFAULT pi();
+LET v1 = v1 * 2;
+SELECT v1;
+        v1        
+------------------
+ 6.28318530717958
+(1 row)
+
+CREATE TYPE t2 AS (a numeric, b text);
+CREATE VARIABLE v2 AS t2 DEFAULT (NULL, 'Hello');
+LET public.v2.a = pi();
+SELECT v2;
+            v2            
+--------------------------
+ (3.14159265358979,Hello)
+(1 row)
+
+-- shoudl fail due dependency
+DROP TYPE t2;
+ERROR:  cannot drop type t2 because other objects depend on it
+DETAIL:  schema variable v2 depends on type t2
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- should be ok
+DROP VARIABLE v1;
+DROP VARIABLE v2;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 16f979c8d9..9bf379b87b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml schema_variables
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/schema_variables.out b/src/test/regress/schema_variables.out
new file mode 100644
index 0000000000..b485cc0995
--- /dev/null
+++ b/src/test/regress/schema_variables.out
@@ -0,0 +1,313 @@
+CREATE VARIABLE var1 AS integer;
+CREATE TEMP VARIABLE var2 AS text;
+DROP VARIABLE var1, var2;
+-- functional interface
+CREATE VARIABLE var1 AS numeric;
+CREATE ROLE var_test_role;
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT var1;
+ERROR:  permission denied for schema variable var1
+SET ROLE TO DEFAULT;
+GRANT SELECT ON VARIABLE var1 TO var_test_role;
+ERROR:  invalid privilege type SELECT for schema variable
+SET ROLE TO var_test_role;
+-- should to fail
+LET var1 = 10;
+ERROR:  permission denied for schema variable var1
+-- should to work
+SELECT var1;
+ERROR:  permission denied for schema variable var1
+SET ROLE TO DEFAULT;
+GRANT UPDATE ON VARIABLE var1 TO var_test_role;
+ERROR:  invalid privilege type UPDATE for schema variable
+SET ROLE TO var_test_role;
+-- should to work
+LET var1 = 333;
+ERROR:  permission denied for schema variable var1
+SET ROLE TO DEFAULT;
+REVOKE ALL ON VARIABLE var1 FROM var_test_role;
+CREATE OR REPLACE FUNCTION secure_var()
+RETURNS int AS $$
+  SELECT public.var1;
+$$ LANGUAGE sql SECURITY DEFINER;
+ERROR:  return type mismatch in function declared to return integer
+DETAIL:  Actual return type is numeric.
+CONTEXT:  SQL function "secure_var"
+SELECT secure_var();
+ERROR:  function secure_var() does not exist
+LINE 1: SELECT secure_var();
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT public.var1;
+ERROR:  permission denied for schema variable var1
+-- should to work;
+SELECT secure_var();
+ERROR:  function secure_var() does not exist
+LINE 1: SELECT secure_var();
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+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 = $46404)
+(4 rows)
+
+CREATE VIEW schema_var_view AS SELECT var1;
+SELECT * FROM schema_var_view;
+ERROR:  the content of variable is not valid
+\c -
+-- should to work still, but var will be empty
+SELECT * FROM schema_var_view;
+ERROR:  the content of variable is not valid
+LET var1 = pi();
+SELECT var1;
+       var1       
+------------------
+ 3.14159265358979
+(1 row)
+
+-- we can look on execution plan
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+         QUERY PLAN         
+----------------------------
+ Result
+   Output: 3.14159265358979
+(2 rows)
+
+-- LET can be prepared
+PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2;
+EXECUTE var_pp(100, 1.23456);
+SELECT var1;
+   var1    
+-----------
+ 101.23456
+(1 row)
+
+CREATE VARIABLE var3 AS int;
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+  LET public.var3 = COALESCE(public.var3 + $1, $1);
+  RETURN var3;
+END;
+$$ LANGUAGE plpgsql;
+NOTICE:  InvalidateSchemaVarCacheCallback
+SELECT inc(1);
+ERROR:  the content of variable is not valid
+CONTEXT:  SQL statement "LET public.var3 = COALESCE(public.var3 + $1, $1)"
+PL/pgSQL function inc(integer) line 3 at SQL statement
+SELECT inc(1);
+ERROR:  the content of variable is not valid
+CONTEXT:  SQL statement "LET public.var3 = COALESCE(public.var3 + $1, $1)"
+PL/pgSQL function inc(integer) line 3 at SQL statement
+SELECT inc(1);
+ERROR:  the content of variable is not valid
+CONTEXT:  SQL statement "LET public.var3 = COALESCE(public.var3 + $1, $1)"
+PL/pgSQL function inc(integer) line 3 at SQL statement
+SELECT inc(1) FROM generate_series(1,10);
+ERROR:  the content of variable is not valid
+CONTEXT:  SQL statement "LET public.var3 = COALESCE(public.var3 + $1, $1)"
+PL/pgSQL function inc(integer) line 3 at SQL statement
+SET ROLE TO var_test_role;
+-- should to fail
+LET var3 = 0;
+ERROR:  permission denied for schema variable var3
+SET ROLE TO DEFAULT;
+DROP VIEW schema_var_view;
+DROP VARIABLE var1 CASCADE;
+NOTICE:  InvalidateSchemaVarCacheCallback
+DROP VARIABLE var3 CASCADE;
+NOTICE:  InvalidateSchemaVarCacheCallback
+NOTICE:  InvalidateSchemaVarCacheCallback
+-- composite variables
+CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2));
+NOTICE:  InvalidateSchemaVarCacheCallback
+CREATE VARIABLE v1 AS sv_xyz;
+CREATE VARIABLE v2 AS (x int, y int, z numeric(10,2));
+NOTICE:  InvalidateSchemaVarCacheCallback
+ERROR:  syntax error at or near "("
+LINE 1: CREATE VARIABLE v2 AS (x int, y int, z numeric(10,2));
+                              ^
+\d v1
+\d v2
+LET v1 = (1,2,3.14);
+LET v2 = (10,20,3.14*10);
+ERROR:  schema variable "v2" doesn't exists
+LINE 1: LET v2 = (10,20,3.14*10);
+            ^
+-- should to work too - there are prepared casts
+LET v1 = (1,2,3.14);
+SELECT v1;
+     v1     
+------------
+ (1,2,3.14)
+(1 row)
+
+SELECT v2;
+ERROR:  column "v2" does not exist
+LINE 1: SELECT v2;
+               ^
+SELECT (v1).*;
+ x | y |  z   
+---+---+------
+ 1 | 2 | 3.14
+(1 row)
+
+SELECT (v2).*;
+ERROR:  column "v2" does not exist
+LINE 1: SELECT (v2).*;
+                ^
+SELECT v1.x + v1.z;
+ ?column? 
+----------
+     4.14
+(1 row)
+
+SELECT v2.x + v2.z;
+ERROR:  missing FROM-clause entry for table "v2"
+LINE 1: SELECT v2.x + v2.z;
+               ^
+-- access to composite fields should be safe too
+-- should to fail
+SET ROLE TO var_test_role;
+SELECT v2.x;
+ERROR:  missing FROM-clause entry for table "v2"
+LINE 1: SELECT v2.x;
+               ^
+SET ROLE TO DEFAULT;
+DROP VARIABLE v1;
+NOTICE:  InvalidateSchemaVarCacheCallback
+DROP VARIABLE v2;
+NOTICE:  InvalidateSchemaVarCacheCallback
+ERROR:  variable "v2" does not exist
+DROP VARIABLE v3;
+ERROR:  variable "v3" does not exist
+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';
+NOTICE:  InvalidateSchemaVarCacheCallback
+ relname  
+----------
+ pg_class
+(1 row)
+
+-- should to fail
+SELECT varx.xxx;
+ERROR:  type text is not composite
+-- variables can be updated under RO transaction
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET varx = 'hello';
+WARNING:  unrecognized commandType: 6
+COMMIT;
+SELECT varx;
+ varx  
+-------
+ hello
+(1 row)
+
+DROP VARIABLE varx;
+NOTICE:  InvalidateSchemaVarCacheCallback
+CREATE VARIABLE v1 AS (a int, b numeric, c text);
+NOTICE:  InvalidateSchemaVarCacheCallback
+ERROR:  syntax error at or near "("
+LINE 1: CREATE VARIABLE v1 AS (a int, b numeric, c text);
+                              ^
+LET v1 = (1, pi(), 'hello');
+ERROR:  schema variable "v1" doesn't exists
+LINE 1: LET v1 = (1, pi(), 'hello');
+            ^
+SELECT v1;
+ERROR:  column "v1" does not exist
+LINE 1: SELECT v1;
+               ^
+LET v1.b = 10.2222;
+ERROR:  schema variable "v1.b" doesn't exists
+LINE 1: LET v1.b = 10.2222;
+            ^
+SELECT v1;
+ERROR:  column "v1" does not exist
+LINE 1: SELECT v1;
+               ^
+-- should to fail
+LET v1.x = 10;
+ERROR:  schema variable "v1.x" doesn't exists
+LINE 1: LET v1.x = 10;
+            ^
+DROP VARIABLE v1;
+ERROR:  variable "v1" does not exist
+-- arrays are supported
+CREATE VARIABLE va1 AS numeric[];
+LET va1 = ARRAY[1.1,2.1];
+NOTICE:  InvalidateSchemaVarCacheCallback
+LET va1[1] = 10.1;
+SELECT va1;
+    va1     
+------------
+ {10.1,2.1}
+(1 row)
+
+CREATE VARIABLE va2 AS (a numeric, b numeric[]);
+ERROR:  syntax error at or near "("
+LINE 1: CREATE VARIABLE va2 AS (a numeric, b numeric[]);
+                               ^
+LET va2 = (10.1, ARRAY[0.0, 0.0]);
+ERROR:  schema variable "va2" doesn't exists
+LINE 1: LET va2 = (10.1, ARRAY[0.0, 0.0]);
+            ^
+LET va2.a = 10.2;
+ERROR:  schema variable "va2.a" doesn't exists
+LINE 1: LET va2.a = 10.2;
+            ^
+SELECT va2;
+ERROR:  column "va2" does not exist
+LINE 1: SELECT va2;
+               ^
+LET va2.b[1] = 10.3;
+ERROR:  schema variable "va2.b" doesn't exists
+LINE 1: LET va2.b[1] = 10.3;
+            ^
+SELECT va2;
+ERROR:  column "va2" does not exist
+LINE 1: SELECT va2;
+               ^
+DROP VARIABLE va1;
+NOTICE:  InvalidateSchemaVarCacheCallback
+DROP VARIABLE va2;
+NOTICE:  InvalidateSchemaVarCacheCallback
+ERROR:  variable "va2" does not exist
+-- default values
+CREATE VARIABLE v1 AS numeric DEFAULT pi();
+LET v1 = v1 * 2;
+NOTICE:  InvalidateSchemaVarCacheCallback
+SELECT v1;
+        v1        
+------------------
+ 6.28318530717958
+(1 row)
+
+CREATE VARIABLE v2 AS (a numeric, b text DEFAULT 'hello');
+ERROR:  syntax error at or near "("
+LINE 1: CREATE VARIABLE v2 AS (a numeric, b text DEFAULT 'hello');
+                              ^
+LET public.v2.a = pi();
+ERROR:  schema variable "public.v2.a" doesn't exists
+LINE 1: LET public.v2.a = pi();
+            ^
+SELECT v2;
+ERROR:  column "v2" does not exist
+LINE 1: SELECT v2;
+               ^
+DROP VARIABLE v1;
+NOTICE:  InvalidateSchemaVarCacheCallback
+DROP VARIABLE v2;
+NOTICE:  InvalidateSchemaVarCacheCallback
+ERROR:  variable "v2" does not exist
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 42632be675..42bf4ecb3f 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -191,3 +191,4 @@ test: partition_aggregate
 test: event_trigger
 test: fast_default
 test: stats
+test: schema_variables
diff --git a/src/test/regress/sql/schema_variables.sql b/src/test/regress/sql/schema_variables.sql
new file mode 100644
index 0000000000..619b6ee4c0
--- /dev/null
+++ b/src/test/regress/sql/schema_variables.sql
@@ -0,0 +1,213 @@
+CREATE VARIABLE var1 AS integer;
+CREATE TEMP VARIABLE var2 AS text;
+
+DROP VARIABLE var1, var2;
+
+-- functional interface
+CREATE VARIABLE var1 AS numeric;
+
+CREATE ROLE var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+SELECT var1;
+
+SET ROLE TO DEFAULT;
+
+GRANT READ ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+-- should to fail
+LET var1 = 10;
+-- should to work
+SELECT var1;
+
+SET ROLE TO DEFAULT;
+
+GRANT WRITE ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should to work
+LET var1 = 333;
+
+SET ROLE TO DEFAULT;
+
+REVOKE ALL ON VARIABLE var1 FROM var_test_role;
+
+CREATE OR REPLACE FUNCTION secure_var()
+RETURNS int AS $$
+  SELECT public.var1::int;
+$$ LANGUAGE sql SECURITY DEFINER;
+
+SELECT secure_var();
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+SELECT public.var1;
+
+-- should to work;
+SELECT secure_var();
+
+SET ROLE TO DEFAULT;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var1;
+
+CREATE VIEW schema_var_view AS SELECT var1;
+
+SELECT * FROM schema_var_view;
+
+\c -
+
+-- should to work still, but var will be empty
+SELECT * FROM schema_var_view;
+
+LET var1 = pi();
+
+SELECT var1;
+
+-- we can look on execution plan
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+
+-- LET can be prepared
+PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2;
+
+EXECUTE var_pp(100, 1.23456);
+
+SELECT var1;
+
+CREATE VARIABLE var3 AS int;
+
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+  LET public.var3 = COALESCE(public.var3 + $1, $1);
+  RETURN var3;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT inc(1);
+SELECT inc(1);
+SELECT inc(1);
+
+SELECT inc(1) FROM generate_series(1,10);
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+LET var3 = 0;
+
+SET ROLE TO DEFAULT;
+
+DROP VIEW schema_var_view;
+
+DROP VARIABLE var1 CASCADE;
+DROP VARIABLE var3 CASCADE;
+
+-- composite variables
+
+CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2));
+
+CREATE VARIABLE v1 AS sv_xyz;
+CREATE VARIABLE v2 AS sv_xyz;
+
+\d v1
+\d v2
+
+LET v1 = (1,2,3.14);
+LET v2 = (10,20,3.14*10);
+
+-- should to work too - there are prepared casts
+LET v1 = (1,2,3.14);
+
+SELECT v1;
+SELECT v2;
+SELECT (v1).*;
+SELECT (v2).*;
+
+SELECT v1.x + v1.z;
+SELECT v2.x + v2.z;
+
+-- access to composite fields should be safe too
+-- should to fail
+SET ROLE TO var_test_role;
+
+SELECT v2.x;
+
+SET ROLE TO DEFAULT;
+
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+DROP ROLE var_test_role;
+
+-- scalar variables should not be in conflict with qualified column
+CREATE VARIABLE varx AS text;
+SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class';
+
+-- should to fail
+SELECT varx.xxx;
+
+
+-- variables can be updated under RO transaction
+
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET varx = 'hello';
+COMMIT;
+
+SELECT varx;
+
+DROP VARIABLE varx;
+
+CREATE TYPE t1 AS (a int, b numeric, c text);
+
+CREATE VARIABLE v1 AS t1;
+LET v1 = (1, pi(), 'hello');
+SELECT v1;
+LET v1.b = 10.2222;
+SELECT v1;
+
+-- should to fail
+LET v1.x = 10;
+
+DROP VARIABLE v1;
+DROP TYPE t1;
+
+-- arrays are supported
+CREATE VARIABLE va1 AS numeric[];
+LET va1 = ARRAY[1.1,2.1];
+LET va1[1] = 10.1;
+SELECT va1;
+
+CREATE TYPE ta2 AS (a numeric, b numeric[]);
+CREATE VARIABLE va2 AS ta2;
+LET va2 = (10.1, ARRAY[0.0, 0.0]);
+LET va2.a = 10.2;
+SELECT va2;
+LET va2.b[1] = 10.3;
+SELECT va2;
+
+DROP VARIABLE va1;
+DROP VARIABLE va2;
+DROP TYPE ta2;
+
+-- default values
+CREATE VARIABLE v1 AS numeric DEFAULT pi();
+LET v1 = v1 * 2;
+SELECT v1;
+
+CREATE TYPE t2 AS (a numeric, b text);
+CREATE VARIABLE v2 AS t2 DEFAULT (NULL, 'Hello');
+LET public.v2.a = pi();
+SELECT v2;
+
+-- shoudl fail due dependency
+DROP TYPE t2;
+
+-- should be ok
+DROP VARIABLE v1;
+DROP VARIABLE v2;

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 8 Aug 2018 22:35:28 +0200

removed forgotten file

Regards

Pavel
 
  </sect1>
 
-

  </sect1>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+    <varlistentry>

 

 

+		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;

 

 	/*

 			/*
 
 				/*

 
 						/*

+	/*
 
 			/*

 	if (sendTuples)

+static LetStmt *
 
+static bool
 
+static void

+
+
 	/*

 

 

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -

 /*

 

 

 

 

 
 explain_option_list:

 
 			/*
 
 			if (isAgg)
 
 			/*

 
 
+	if (OidIsValid(varid))
+	{
+		Oid		typid;
+		int32	typmod;
+
 
+/*
 
 			/*
 
 			/*

 	/*

 		case DestTupleQueue:

 	/*
 
+		case T_LetStmt:
+			{

+		case T_LetStmt:
+			tag = "LET";
+			break;
+

+		case T_LetStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 
 	ereport(ERROR,

+/*
+ * get_varname_varid

+	if (g_verbose)

+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

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

 
 
 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches1("DO"))
 
+	/* DROP VARIABLE */
 
 /* FETCH && MOVE */

 /* PREPARE xx AS */
 
 /*

 

 

 

 

 	/*

 

 

 

 
 typedef struct Param

 /* ----------------

 /*

 

 

 # ----------

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 11 Aug 2018 07:39:49 +0200

Hi

I am sending updated patch. It should to solve almost all Giles's and
Peter's objections.

I am not happy so executor access values of variables directly. It is most
simple implementation - and I hope so it is good enough, but now the access
to variables is too volatile. But it is works good enough for usability
testing.

I am thinking about some cache of used variables in ExprContext, so the
variable in one ExprContext will look like stable - more like PLpgSQL
variables.

Regards

Pavel
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3bb48d4ccf..b863823160 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -359,6 +359,11 @@
       <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
       <entry>mappings of users to foreign servers</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+      <entry>schema variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -11311,4 +11316,104 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
 
  </sect1>
 
+ <sect1 id="catalog-pg-variable">
+  <title><structname>pg_variable</structname></title>
+
+  <indexterm zone="catalog-pg-variable">
+   <primary>pg_variable</primary>
+  </indexterm>
+
+  <para>
+   The table <structname>pg_variable</structname> holds metadata
+   of schema variables.
+  </para>
+
+  <table>
+   <title><structname>pg_views</structname> Columns</title>
+
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>varname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>Name of the schema variable</entry>
+     </row>
+
+     <row>
+      <entry><structfield>varnamespace</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the namespace that contains this variable
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>vartype</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the data type of this variable.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>vartypmod</structfield></entry>
+      <entry><type>int4</type></entry>
+      <entry></entry>
+      <entry>
+       <structfield>vartypmod</structfield> records type-specific data
+       supplied at table creation time (for example, the maximum
+       length of a <type>varchar</type> column).  It is passed to
+       type-specific input functions and length coercion functions.
+       The value will generally be -1 for types that do not need <structfield>vartypmod</structfield>.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>varowner</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.oid</literal></entry>
+      <entry>Owner of the variable</entry>
+     </row>
+
+     <row>
+      <entry><structfield>vardefexpr</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>The internal representation of the variable default value</entry>
+     </row>
+
+     <row>
+      <entry><structfield>varacl</structfield></entry>
+      <entry><type>aclitem[]</type></entry>
+      <entry></entry>
+      <entry>
+       Access privileges; see
+       <xref linkend="sql-grant"/> and
+       <xref linkend="sql-revoke"/>
+       for details
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
 </chapter>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index c81c87ef41..0631c9ed56 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -47,6 +47,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY alterType          SYSTEM "alter_type.sgml">
 <!ENTITY alterUser          SYSTEM "alter_user.sgml">
 <!ENTITY alterUserMapping   SYSTEM "alter_user_mapping.sgml">
+<!ENTITY alterVariable      SYSTEM "alter_variable.sgml">
 <!ENTITY alterView          SYSTEM "alter_view.sgml">
 <!ENTITY analyze            SYSTEM "analyze.sgml">
 <!ENTITY begin              SYSTEM "begin.sgml">
@@ -99,6 +100,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createType         SYSTEM "create_type.sgml">
 <!ENTITY createUser         SYSTEM "create_user.sgml">
 <!ENTITY createUserMapping  SYSTEM "create_user_mapping.sgml">
+<!ENTITY createVariable     SYSTEM "create_variable.sgml">
 <!ENTITY createView         SYSTEM "create_view.sgml">
 <!ENTITY deallocate         SYSTEM "deallocate.sgml">
 <!ENTITY declare            SYSTEM "declare.sgml">
@@ -148,6 +150,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropUser           SYSTEM "drop_user.sgml">
 <!ENTITY dropUserMapping    SYSTEM "drop_user_mapping.sgml">
 <!ENTITY dropView           SYSTEM "drop_view.sgml">
+<!ENTITY dropVariable       SYSTEM "drop_variable.sgml">
 <!ENTITY end                SYSTEM "end.sgml">
 <!ENTITY execute            SYSTEM "execute.sgml">
 <!ENTITY explain            SYSTEM "explain.sgml">
@@ -155,6 +158,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY grant              SYSTEM "grant.sgml">
 <!ENTITY importForeignSchema SYSTEM "import_foreign_schema.sgml">
 <!ENTITY insert             SYSTEM "insert.sgml">
+<!ENTITY let                SYSTEM "let.sgml">
 <!ENTITY listen             SYSTEM "listen.sgml">
 <!ENTITY load               SYSTEM "load.sgml">
 <!ENTITY lock               SYSTEM "lock.sgml">
diff --git a/doc/src/sgml/ref/alter_variable.sgml b/doc/src/sgml/ref/alter_variable.sgml
new file mode 100644
index 0000000000..6376ac716b
--- /dev/null
+++ b/doc/src/sgml/ref/alter_variable.sgml
@@ -0,0 +1,170 @@
+<!--
+doc/src/sgml/ref/alter_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-altervariable">
+ <indexterm zone="sql-altervariable">
+  <primary>ALTER VARIABLE</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER VARIABLE</refname>
+  <refpurpose>
+   change the definition of a variable
+  </refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER VARIABLE <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable class="parameter">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
+ALTER VARIABLE <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
+ALTER VARIABLE <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER VARIABLE</command> changes the definition of an existing variable.
+   There are several subforms:
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>OWNER</literal></term>
+    <listitem>
+     <para>
+      This form changes the owner of the variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RENAME</literal></term>
+    <listitem>
+     <para>
+      This form changes the name of the variable.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>SET SCHEMA</literal></term>
+    <listitem>
+     <para>
+      This form moves the variable into another schema.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+  </para>
+
+  <para>
+   You must own the variable to use <command>ALTER VARIABLE</command>.
+   To change the schema of a variable, you must also have
+   <literal>CREATE</literal> privilege on the new schema.
+   To alter the owner, you must also be a direct or indirect member of the new
+   owning role, and that role must have <literal>CREATE</literal> privilege on
+   the variable's schema.  (These restrictions enforce that altering the owner
+   doesn't do anything you couldn't do by dropping and recreating the variable.
+   However, a superuser can alter ownership of any type anyway.)
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+   <para>
+    <variablelist>
+     <varlistentry>
+      <term><replaceable class="parameter">name</replaceable></term>
+      <listitem>
+       <para>
+        The name (possibly schema-qualified) of an existing variable to
+        alter.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">new_name</replaceable></term>
+      <listitem>
+       <para>
+        The new name for the variable.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">new_owner</replaceable></term>
+      <listitem>
+       <para>
+        The user name of the new owner of the variable.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="parameter">new_schema</replaceable></term>
+      <listitem>
+       <para>
+        The new schema for the variable.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+  </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To rename a variable:
+<programlisting>
+ALTER VARIABLE foo RENAME TO boo;
+</programlisting>
+  </para>
+
+  <para>
+   To change the owner of the variable <literal>boo</literal>
+   to <literal>joe</literal>:
+<programlisting>
+ALTER VARIABLE boo OWNER TO joe;
+</programlisting>
+  </para>
+
+  <para>
+   To change the schema of the variable <literal>boo</literal>
+   to <literal>private</literal>:
+<programlisting>
+ALTER VARIABLE boo SET SCHEMA private;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   This comman is a PostgreSQL extension.
+  </para>
+ </refsect1>
+
+ <refsect1 id="sql-altervariable-see-also">
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createvariable"/></member>
+   <member><xref linkend="sql-dropvariable"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
new file mode 100644
index 0000000000..6099538813
--- /dev/null
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -0,0 +1,134 @@
+<!--
+doc/src/sgml/ref/create_variable.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-createvariable">
+ <indexterm zone="sql-createvariable">
+  <primary>CREATE VARIABLE</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE VARIABLE</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE VARIABLE</refname>
+  <refpurpose>define a new permissioned typed schema variable</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ]
+</synopsis>
+ </refsynopsisdiv>
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE VARIABLE</command> creates a new schema variable.
+   These variables are scalar typed, non-transactional, and, like relations,
+   exist within a schema with access controlled via
+   <command>GRANT</command> and <command>REVOKE</command>.
+  </para>
+
+  <para>
+   The value of a schema variable is session-local. Retrieving
+   a variable's value will return NULL unless its value has been set
+   to something else in the current session.
+  </para>
+
+  <para>
+   Retrieval is done via the <function>get_schema_variable</function>dunxrion or the SQL
+   command <command>SELECT</command>.  Setting of values is done via the
+   <function>set_schema_variable</function> function or the SQL command
+   <command>LET</command>.
+   Notably, while schema variables are in many ways a kind of table you cannot use
+   <command>UPDATE</command> on them.
+  </para>
+
+  <para>
+   For purposes of name uniqueness relation-like objects (e.g., tables, indexes)
+   within the same schema are considered.  i.e., you cannot give a table and a
+   schema variable the same name.  This is a consequence of them being treated
+   like relations for purposes of <command>SELECT</command>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF NOT EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the name already exists. A notice is issued in this case.
+      Note that type of the variable is not considered, nor could it be since the namespace
+      searched contains non-variable objects.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the variable to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">data_type</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the data type of the variable to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   Use <command>DROP VARIABLE</command> to remove a variable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create an integer variable <literal>var1</literal>:
+<programlisting>
+CREATE VARIABLE var1 AS integer;
+SELECT var1;
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE VARIABLE</command> is a PostgreSQL feature.
+   <!-- The choice of wording here seems to be left to personal preference... -->
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-altervariable"/></member>
+   <member><xref linkend="sql-dropvariable"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml
index 6b909b7232..d83ad811fd 100644
--- a/doc/src/sgml/ref/discard.sgml
+++ b/doc/src/sgml/ref/discard.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>
index 0000000000..c1c1a2bd67
@@ -0,0 +1,93 @@
+   <member><xref linkend="sql-altervariable"/></member>

 
+    <varlistentry>

index db4f4167e3..5fb82df51e 100644
@@ -75,6 +75,7 @@
    &alterType;
    &alterUser;
    &alterUserMapping;
+   &alterVariable;
    &alterView;
    &analyze;
    &begin;
@@ -127,6 +128,7 @@
@@ -175,6 +177,7 @@
@@ -183,6 +186,7 @@

 

 
+		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 cef6632840..30e6c1290b 100644
@@ -9656,6 +9656,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 				/*

 
 						/*
 
index b797d064b7..a49deb810c 100644
 
+	/*
 
 			/*

 	if (sendTuples)

+static LetStmt *
 
+static bool
 
+static void

+
+
 	/*

 

 

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -

 /*

 

 

 

 

 
 explain_option_list:

 
 			/*
 
 			if (isAgg)
 
 			/*

 
 
+	if (OidIsValid(varid))
+	{
+		Oid		typid;
+		int32	typmod;
+
 
+/*
 
 			/*
 
 			/*

 	/*

 		case DestTupleQueue:

 	/*
 
+		case T_LetStmt:
+			{

+		case T_LetStmt:
+			tag = "LET";
+			break;
+

+		case T_LetStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 
 	ereport(ERROR,

+/*
+ * get_varname_varid

+	if (g_verbose)

+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

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

index bb696f8ee9..a7583810e8 100644

 /*
  * Queries to get lists of names of various kinds of things, possibly
@@ -1249,6 +1265,7 @@ static const pgsql_thing_t words_after_create[] = {
 																	 * TABLE ... */
 	{"USER", Query_for_list_of_roles " UNION SELECT 'MAPPING FOR'"},
 	{"USER MAPPING FOR", NULL, NULL, NULL},
+	{"VARIABLE", NULL, NULL, &Query_for_list_of_variables},
 	{"VIEW", NULL, NULL, &Query_for_list_of_views},
 	{NULL}						/* end of list */
 };
@@ -1604,7 +1621,7 @@ psql_completion(const char *text, int start, int end)
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CALL", "CHECKPOINT", "CLOSE", "CLUSTER",
 		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
 		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
-		"FETCH", "GRANT", "IMPORT", "INSERT", "LISTEN", "LOAD", "LOCK",
+		"FETCH", "GRANT", "IMPORT", "INSERT", "LET", "LISTEN", "LOAD", "LOCK",
 		"MOVE", "NOTIFY", "PREPARE",
 		"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
 		"RESET", "REVOKE", "ROLLBACK",
@@ -1621,9 +1638,9 @@ psql_completion(const char *text, int start, int end)
 		"\\d", "\\da", "\\dA", "\\db", "\\dc", "\\dC", "\\dd", "\\ddp", "\\dD",
 		"\\des", "\\det", "\\deu", "\\dew", "\\dE", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
-		"\\dm", "\\dn", "\\do", "\\dO", "\\dp",
+		"\\dm", "\\dn", "\\do", "\\dO", "\\dp"
 		"\\drds", "\\dRs", "\\dRp", "\\ds", "\\dS",
-		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
+		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy", "\\dV",
 		"\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding",
 		"\\endif", "\\errverbose", "\\ev",
 		"\\f",
@@ -1988,6 +2005,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	else if (Matches4("ALTER", "SYSTEM", "SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
+	/* ALTER VARIABLE <name> */
+	else if (Matches3("ALTER", "VARIABLE", MatchAny))
+		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 	/* ALTER VIEW <name> */
 	else if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -2837,6 +2857,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
@@ -2890,7 +2918,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches1("DO"))
@@ -2992,6 +3020,12 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* DROP VARIABLE */
@@ -3002,14 +3036,14 @@ psql_completion(const char *text, int start, int end)
 
 /* FETCH && MOVE */
@@ -3118,6 +3152,7 @@ psql_completion(const char *text, int start, int end)
@@ -3131,14 +3166,16 @@ psql_completion(const char *text, int start, int end)
 
@@ -3172,6 +3209,8 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		else if (TailMatches1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		else if (TailMatches1("VARIABLE"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
 		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -3324,7 +3363,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -3547,6 +3586,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");

 

 

 

 

 	/*

 

 

 

 
 typedef struct Param

 /* ----------------

 /*

 

 
 
index 0000000000..8cddf66573
@@ -0,0 +1,344 @@
+-- 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;
+ALTER VARIABLE var_schema2.var1 OWNER 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
+ALTER VARIABLE var_schema2.var1 SET SCHEMA public;
+DROP VARIABLE public.var1;
+SET ROLE TO DEFAULt;
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 42632be675..42bf4ecb3f 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -191,3 +191,4 @@ test: partition_aggregate
 test: event_trigger
 test: fast_default
 test: stats
+test: schema_variables
diff --git a/src/test/regress/sql/schema_variables.sql b/src/test/regress/sql/schema_variables.sql
new file mode 100644
index 0000000000..91b2bbb28b
--- /dev/null
+++ b/src/test/regress/sql/schema_variables.sql
@@ -0,0 +1,247 @@
+CREATE VARIABLE var1 AS integer;
+CREATE TEMP VARIABLE var2 AS text;
+
+DROP VARIABLE var1, var2;
+
+-- functional interface
+CREATE VARIABLE var1 AS numeric;
+
+CREATE ROLE var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+SELECT var1;
+
+SET ROLE TO DEFAULT;
+
+GRANT READ ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+-- should to fail
+LET var1 = 10;
+-- should to work
+SELECT var1;
+
+SET ROLE TO DEFAULT;
+
+GRANT WRITE ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should to work
+LET var1 = 333;
+
+SET ROLE TO DEFAULT;
+
+REVOKE ALL ON VARIABLE var1 FROM var_test_role;
+
+CREATE OR REPLACE FUNCTION secure_var()
+RETURNS int AS $$
+  SELECT public.var1::int;
+$$ LANGUAGE sql SECURITY DEFINER;
+
+SELECT secure_var();
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+SELECT public.var1;
+
+-- should to work;
+SELECT secure_var();
+
+SET ROLE TO DEFAULT;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var1;
+
+CREATE VIEW schema_var_view AS SELECT var1;
+
+SELECT * FROM schema_var_view;
+
+\c -
+
+-- should to work still, but var will be empty
+SELECT * FROM schema_var_view;
+
+LET var1 = pi();
+
+SELECT var1;
+
+-- we can look on execution plan
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+
+-- LET can be prepared
+PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2;
+
+EXECUTE var_pp(100, 1.23456);
+
+SELECT var1;
+
+CREATE VARIABLE var3 AS int;
+
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+  LET public.var3 = COALESCE(public.var3 + $1, $1);
+  RETURN var3;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT inc(1);
+SELECT inc(1);
+SELECT inc(1);
+
+SELECT inc(1) FROM generate_series(1,10);
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+LET var3 = 0;
+
+SET ROLE TO DEFAULT;
+
+DROP VIEW schema_var_view;
+
+DROP VARIABLE var1 CASCADE;
+DROP VARIABLE var3 CASCADE;
+
+-- composite variables
+
+CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2));
+
+CREATE VARIABLE v1 AS sv_xyz;
+CREATE VARIABLE v2 AS sv_xyz;
+
+\d v1
+\d v2
+
+LET v1 = (1,2,3.14);
+LET v2 = (10,20,3.14*10);
+
+-- should to work too - there are prepared casts
+LET v1 = (1,2,3.14);
+
+SELECT v1;
+SELECT v2;
+SELECT (v1).*;
+SELECT (v2).*;
+
+SELECT v1.x + v1.z;
+SELECT v2.x + v2.z;
+
+-- access to composite fields should be safe too
+-- should to fail
+SET ROLE TO var_test_role;
+
+SELECT v2.x;
+
+SET ROLE TO DEFAULT;
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+DROP ROLE var_test_role;
+
+-- scalar variables should not be in conflict with qualified column
+CREATE VARIABLE varx AS text;
+SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class';
+
+-- should to fail
+SELECT varx.xxx;
+
+-- variables can be updated under RO transaction
+
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET varx = 'hello';
+COMMIT;
+
+SELECT varx;
+
+DROP VARIABLE varx;
+
+CREATE TYPE t1 AS (a int, b numeric, c text);
+
+CREATE VARIABLE v1 AS t1;
+LET v1 = (1, pi(), 'hello');
+SELECT v1;
+LET v1.b = 10.2222;
+SELECT v1;
+
+-- should to fail
+LET v1.x = 10;
+
+DROP VARIABLE v1;
+DROP TYPE t1;
+
+-- arrays are supported
+CREATE VARIABLE va1 AS numeric[];
+LET va1 = ARRAY[1.1,2.1];
+LET va1[1] = 10.1;
+SELECT va1;
+
+CREATE TYPE ta2 AS (a numeric, b numeric[]);
+CREATE VARIABLE va2 AS ta2;
+LET va2 = (10.1, ARRAY[0.0, 0.0]);
+LET va2.a = 10.2;
+SELECT va2;
+LET va2.b[1] = 10.3;
+SELECT va2;
+
+DROP VARIABLE va1;
+DROP VARIABLE va2;
+DROP TYPE ta2;
+
+-- default values
+CREATE VARIABLE v1 AS numeric DEFAULT pi();
+LET v1 = v1 * 2;
+SELECT v1;
+
+CREATE TYPE t2 AS (a numeric, b text);
+CREATE VARIABLE v2 AS t2 DEFAULT (NULL, 'Hello');
+LET public.v2.a = pi();
+SELECT v2;
+
+-- shoudl fail due dependency
+DROP TYPE t2;
+
+-- should be ok
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
+-- tests of alters
+CREATE SCHEMA var_schema1;
+CREATE SCHEMA var_schema2;
+
+CREATE VARIABLE var_schema1.var1 AS integer;
+LET var_schema1.var1 = 1000;
+SELECT var_schema1.var1;
+ALTER VARIABLE var_schema1.var1 SET SCHEMA var_schema2;
+SELECT var_schema2.var1;
+
+CREATE ROLE var_test_role;
+
+ALTER VARIABLE var_schema2.var1 OWNER TO var_test_role;
+SET ROLE TO var_test_role;
+
+-- should fail, no access to schema var_schema2.var
+SELECT var_schema2.var1;
+DROP VARIABLE var_schema2.var1;
+
+SET ROLE TO DEFAULT;
+
+ALTER VARIABLE var_schema2.var1 SET SCHEMA public;
+
+SET ROLE TO var_test_role;
+SELECT public.var1;
+
+ALTER VARIABLE public.var1 RENAME TO var1_renamed;
+
+SELECT public.var1_renamed;
+
+DROP VARIABLE public.var1_renamed;
+
+SET ROLE TO DEFAULt;
+
+DROP ROLE var_test_role;

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 11 Aug 2018 20:46:34 +0200

> variable in one ExprContext will look like stable - more like PLpgSQL

I wrote EState based schema variable values cache, so now the variables in
queries are stable (like PARAM_EXTERN) and can be used for optimization.

Regards

Pavel

  </sect1>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+    <varlistentry>

 

 

+		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..5b65d762d4
@@ -0,0 +1,490 @@
+GetSchemaVariableCopy(Oid varid, bool *isNull, Oid expected_typid)
+	if (!svar->isnull)
+		return datumCopy(svar->value, svar->typbyval, svar->typlen);
 
 				/*
 
index e284fd71d7..442c539991 100644
 
 				switch (param->paramkind)
 				{
@@ -736,6 +738,26 @@ ExecInitExprRec(Expr *node, ExprState *state,
 						scratch.d.param.paramtype = param->paramtype;
 						ExprEvalPushStep(state, &scratch);
 						break;
+					case PARAM_SCHEMA_VARIABLE:
+						/* Check permission to read schema variable */
+						aclresult = pg_variable_aclcheck(param->paramid, GetUserId(), ACL_READ);
+						if (aclresult != ACLCHECK_OK)
+							aclcheck_error(aclresult, OBJECT_VARIABLE,
+											schema_variable_get_name(param->paramid));
+
+						/*
+						 * Using varoid as paramid is not practical. Better to recount
+						 * used schema variables from zero, and later to use paramid like
+						 * offset.
+						 */
+						scratch.opcode = EEOP_PARAM_VARIABLE;
+						scratch.d.vparam.paramid = state->nvariables++;
+						scratch.d.vparam.varoid = param->paramid;
+						scratch.d.vparam.paramtype = param->paramtype;
+
+						ExprEvalPushStep(state, &scratch);
+						break;
+
 					case PARAM_EXTERN:
 
 						/*
index 9d6e25aae5..6c25689a2a 100644
@@ -1007,6 +1009,71 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_PARAM_VARIABLE)
+		{
+			EState *estate = econtext->ecxt_estate;
+			MemoryContext	old_cxt;
+
+			if (estate && !estate->es_shared)
+			{
+				int		paramid = op->d.vparam.paramid;
+
+				if (estate->es_nvariables == 0)
+				{
+					/*
+					 * A query's schema variable's cache should be initialized. This cache
+					 * is related to EState. When estate is used per query, then this caching
+					 * is not surprise. A unexpected behave can be with some PLpgSQL expressions
+					 * where EState is reused, so in this case, don't use this cache.
+					 */
+					old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+					estate->es_nvariables = state->nvariables;
+					estate->es_varnulls = palloc(sizeof(bool) * state->nvariables);
+					estate->es_vartypes = palloc0(sizeof(Oid) * state->nvariables);
+					estate->es_varvalues = palloc(sizeof(Datum) * state->nvariables);
+
+					MemoryContextSwitchTo(old_cxt);
+				}
+
+				Assert(estate->es_nvariables == state->nvariables);
+				Assert(estate->es_nvariables > paramid);
+
+				if (!OidIsValid(estate->es_vartypes[paramid]))
+				{
+					old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+					estate->es_varvalues[paramid] =
+												GetSchemaVariableCopy(op->d.vparam.varoid,
+																  &estate->es_varnulls[paramid],
+																  op->d.vparam.paramtype);
+					estate->es_vartypes[paramid] = op->d.vparam.paramtype;
+
+					MemoryContextSwitchTo(old_cxt);
+				}
+
+				Assert(OidIsValid(estate->es_vartypes[paramid]));
+
+				*op->resvalue = estate->es_varvalues[paramid];
+				*op->resnull = estate->es_varnulls[paramid];
+			}
+			else
+			{
+				Datum		d;
+				bool		isnull;
+
+				/* read content of variable every time */
+				d = GetSchemaVariable(op->d.vparam.varoid,
+									  &isnull,
+									  op->d.vparam.paramtype);
+
+				*op->resvalue = d;
+				*op->resnull = isnull;
+			}
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_CASE_TESTVAL)
 		{
 			/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b797d064b7..a49deb810c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -43,9 +43,11 @@
 #include "access/xact.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_publication.h"
+#include "catalog/pg_variable.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
+#include "executor/svariableReceiver.h"
 #include "foreign/fdwapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
@@ -204,12 +206,18 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	 */
 	estate->es_queryEnv = queryDesc->queryEnv;
 
+	/*
 
 			/*

 	if (sendTuples)
 
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 5b3eaec80b..eca7805517 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -102,6 +102,7 @@ CreateExecutorState(void)
 	 * Initialize all fields of the Executor State structure
+	estate->es_shared = false;
 	estate->es_direction = ForwardScanDirection;
 	estate->es_snapshot = InvalidSnapshot;	/* caller must initialize this */
 	estate->es_crosscheck_snapshot = InvalidSnapshot;	/* no crosscheck */
 
+static LetStmt *
 
+static bool
 
+static void

+
+
 	/*

 

 

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -

 /*

 

 

 

 

 
 explain_option_list:

 
 			/*
 
 			if (isAgg)
 
 			/*

 
 
+	if (OidIsValid(varid))
+	{
+		Oid		typid;
+		int32	typmod;
+
 
+/*
 
 			/*
 
 			/*

 	/*

 		case DestTupleQueue:

 	/*
 
+		case T_LetStmt:
+			{

+		case T_LetStmt:
+			tag = "LET";
+			break;
+

+		case T_LetStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 
 	ereport(ERROR,

+/*
+ * get_varname_varid

+	if (g_verbose)

+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

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

 
 
 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches1("DO"))
 
+	/* DROP VARIABLE */
 
 /* FETCH && MOVE */

 /* PREPARE xx AS */
 
 /*

 

 

 
index 0000000000..4ea1dc1209
@@ -0,0 +1,39 @@
+extern Datum GetSchemaVariableCopy(Oid varid, bool *isNull, Oid expected_typid);
index f7b1f77616..9c03580541 100644
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -344,13 +345,22 @@ typedef struct ExprEvalStep
 			TupleDesc	argdesc;
 		}			nulltest_row;

+		/* for EEOP_PARAM_VARIABLE */
+		struct
+		{
+			int			paramid;		/* numeric ID for parameter */
+			Oid			varoid;			/* OID of assigned variable */
+			Oid			paramtype;		/* OID of parameter's datatype */
+		}			vparam;
+
+
 		/* for EEOP_PARAM_CALLBACK */
 		struct
 		{
diff --git a/src/include/executor/svariableReceiver.h b/src/include/executor/svariableReceiver.h
new file mode 100644
index 0000000000..8c8117701f
--- /dev/null
+++ b/src/include/executor/svariableReceiver.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.h
+ *	  prototypes for svariableReceiver.c
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/svariableReceiver.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SVARIABLE_RECEIVER_H
+#define SVARIABLE_RECEIVER_H
+
+#include "tcop/dest.h"
+
+
+extern DestReceiver *CreateVariableDestReceiver(void);
+
+extern void SetVariableDestReceiverParams(DestReceiver *self, Oid varid);
+
+#endif							/* SVARIABLE_RECEIVER_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 018f50bbb7..33cc8be55a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -100,6 +100,8 @@ typedef struct ExprState
 	int			steps_len;		/* number of steps currently */
 	int			steps_alloc;	/* allocated length of steps array */
 
+	int			nvariables;		/* number of used variables */
+
 	struct PlanState *parent;	/* parent PlanState node, if any */
 	ParamListInfo ext_params;	/* for compiling PARAM_EXTERN nodes */
 
@@ -472,6 +474,7 @@ typedef struct ResultRelInfo
 typedef struct EState
 {
 	NodeTag		type;
+	bool		es_shared;		/* plpgsql uses share estate */
 
 	/* Basic state for all query types: */
 	ScanDirection es_direction; /* current scan direction */
@@ -564,6 +567,14 @@ typedef struct EState
 	/* The per-query shared memory area to use for parallel execution. */
 	struct dsa_area *es_query_dsa;
 
+	/* query schema variable cache */
+	int			es_nvariables;
+	bool	   *es_varnulls;
+	Oid		   *es_vartypes;
+	Datum	   *es_varvalues;
 
 	/*

 

 

 

 
 typedef struct Param

 /* ----------------

 /*

 

 
 extern void InitCatalogCache(void);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 380d1de8f4..ac71dd7d7a 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8049,6 +8049,7 @@ plpgsql_create_econtext(PLpgSQL_execstate *estate)
 		{
 			oldcontext = MemoryContextSwitchTo(TopTransactionContext);
 			shared_simple_eval_estate = CreateExecutorState();
+			shared_simple_eval_estate->es_shared = true;
 			MemoryContextSwitchTo(oldcontext);
 		}
 		estate->simple_eval_estate = shared_simple_eval_estate;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 7d3647a12d..7f183d4f1b 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -332,6 +332,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
 
 	/* Create a private EState for simple-expression execution */
 	simple_eval_estate = CreateExecutorState();
+	simple_eval_estate->es_shared = true;
 
 	/* And run the function */
 	PG_TRY();
 
index 0000000000..84fe30a2c0
@@ -0,0 +1,351 @@
+ var1_renamed 
+--------------
 
 # ----------

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 12 Aug 2018 07:35:33 +0200

Hi

2018-08-11 20:46 GMT+02:00 Pavel Stehule <[email-masked]>:

>> I am not happy so executor access values of variables directly. It is
>> most simple implementation - and I hope so it is good enough, but now the
>> access to variables is too volatile. But it is works good enough for
>> usability testing.
>>

new update - after cleaning

Regards

Pavel

  </sect1>

  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+    <varlistentry>

 

 

+		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..3d39f5fd41
@@ -0,0 +1,467 @@
+GetSchemaVariable(Oid varid, bool *isNull, Oid expected_typid, bool copy)
+	Assert(svar != NULL);
+	value = svar->value;
+	isnull = svar->isnull;
+	*isNull = isnull;
+	if (!isnull && copy)
+		return datumCopy(value, svar->typbyval, svar->typlen);
+	return value;
 
 				/*
 
index e284fd71d7..bb9bf53e1c 100644
 
@@ -736,6 +738,28 @@ ExecInitExprRec(Expr *node, ExprState *state,
+					case PARAM_VARIABLE:
 
 						/*
index 9d6e25aae5..4462dcc952 100644
@@ -1007,6 +1009,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_PARAM_VARIABLE)
+		{
+			/* iut of line implementation; too large */
+			ExecEvalParamVariable(state, op, econtext);
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_CASE_TESTVAL)
 		{
 			/*
@@ -2323,6 +2332,79 @@ ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 			 errmsg("no value found for parameter %d", paramId)));
 }
 
+/*
+ * Evaluate a PARAM_VARIABLE parameter
+ */
+void
+ExecEvalParamVariable(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	EState *estate = econtext->ecxt_estate;
+
+	/*
+	 * We should to ensure stable behave of schema variables in queries. It is
+	 * important, because optimizer uses these values as stable, like extern
+	 * parameters, what is nice, because queries are optimized well. So, don't
+	 * try to access variables directly, use this query variable cache.
+	 * This cache cannot be used when EState is shared - PLpgSQL did it for
+	 * simple expressions.
+	 */
+	if (estate && !estate->es_shared)
+	{
+		int		paramid = op->d.vparam.paramid;
+
+		if (estate->es_nvariables == 0)
+		{
+			MemoryContext	old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* initialize estate schema variable cache */
+
+			estate->es_nvariables = state->nvariables;
+			estate->es_varnulls = palloc(sizeof(bool) * state->nvariables);
+			estate->es_vartypes = palloc0(sizeof(Oid) * state->nvariables);
+			estate->es_varvalues = palloc(sizeof(Datum) * state->nvariables);
+
+			MemoryContextSwitchTo(old_cxt);
+		}
+
+		Assert(estate->es_nvariables == state->nvariables);
+		Assert(estate->es_nvariables > paramid);
+
+		if (!OidIsValid(estate->es_vartypes[paramid]))
+		{
+			MemoryContext	old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* copy variable to estate schema variable cache */
+			estate->es_varvalues[paramid] =
+										GetSchemaVariable(op->d.vparam.varoid,
+														  &estate->es_varnulls[paramid],
+														  op->d.vparam.paramtype,
+														  true);
+			estate->es_vartypes[paramid] = op->d.vparam.paramtype;
+
+			MemoryContextSwitchTo(old_cxt);
+		}
+
+		Assert(OidIsValid(estate->es_vartypes[paramid]));
+
+		*op->resvalue = estate->es_varvalues[paramid];
+		*op->resnull = estate->es_varnulls[paramid];
+	}
+	else
+	{
+		Datum		d;
+		bool		isnull;
+
+		/* read content of variable directly */
+		d = GetSchemaVariable(op->d.vparam.varoid,
+							  &isnull,
+							  op->d.vparam.paramtype,
+							  false);
+
+		*op->resvalue = d;
+		*op->resnull = isnull;
+	}
+}
+
 /*
  * Evaluate a SQLValueFunction expression.
  */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b797d064b7..a49deb810c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -43,9 +43,11 @@
 #include "access/xact.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_publication.h"
+#include "catalog/pg_variable.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
+#include "executor/svariableReceiver.h"
 #include "foreign/fdwapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
@@ -204,12 +206,18 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	 */
 	estate->es_queryEnv = queryDesc->queryEnv;
 
+	/*
 
 			/*

 	if (sendTuples)

+static LetStmt *
 
+static bool
 
+static void

+
+
 	/*

index a04ad6e99e..8f023225c6 100644
 
-		if (param->paramkind == PARAM_EXTERN)
+		if (param->paramkind == PARAM_EXTERN ||
+			param->paramkind == PARAM_VARIABLE)
 			return false;
 
 		if (param->paramkind != PARAM_EXEC ||
@@ -4799,7 +4800,7 @@ substitute_actual_parameters_mutator(Node *node,
 {
 	if (node == NULL)
 		return NULL;
-	if (IsA(node, Param))
+	if (IsA(node, Param) && ((Param *) node)->paramkind != PARAM_VARIABLE)
 	{
 		Param	   *param = (Param *) node;

diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c601b6d40d..53ce2435d6 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,7 +25,10 @@
 #include "postgres.h"

 
+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -
+		param->paramkind = PARAM_VARIABLE;

 /*

 

 

 

 

 
 explain_option_list:

 
 			/*
 
 			if (isAgg)
 
 			/*
index 385e54a9b6..6ea194b563 100644

 
 
+	if (OidIsValid(varid))
+	{
+		Oid		typid;
+		int32	typmod;
+
 
+/*
+	param->paramkind = PARAM_VARIABLE;
 
 			/*
 
 			/*

 	/*

 		case DestTupleQueue:

 	/*
 
+		case T_LetStmt:
+			{

+		case T_LetStmt:
+			tag = "LET";
+			break;
+

+		case T_LetStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 
 	ereport(ERROR,
 			(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1721,6 +1738,10 @@ convert_aclright_to_string(int aclright)
 			return "TEMPORARY";
 		case ACL_CONNECT:
 			return "CONNECT";
+		case ACL_READ:
+			return "READ";
+		case ACL_WRITE:
+			return "WRITE";
 		default:
 			elog(ERROR, "unrecognized aclright: %d", aclright);
 			return NULL;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 03e9a28a63..cc8f3326ac 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -38,6 +38,7 @@
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/defrem.h"
 #include "commands/tablespace.h"
 #include "common/keywords.h"
@@ -7362,6 +7363,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}
 
+	if (param->paramkind == PARAM_VARIABLE)
 
+/*
+ * get_varname_varid

+	if (g_verbose)

+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 6227a8fd26..969a021771 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1477,6 +1477,10 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "POST-DATA BOUNDARY  (ID %d)",
 					 obj->dumpId);
 			return;
+		case DO_VARIABLE:
+			snprintf(buf, bufsize,
+					 "VARIABLE %s  (ID %d OID %u)",
+					 obj->name, obj->dumpId, obj->catId.oid);
 	}
 	/* shouldn't get here */
 	snprintf(buf, bufsize,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index ec751a7c23..2a67766ed4 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2601,6 +2601,38 @@ my %tests = (
 		},
 	},

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

 
 
 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches1("DO"))
 
+	/* DROP VARIABLE */
 
 /* FETCH && MOVE */

 /* PREPARE xx AS */
 
 /*

 

 

 
index 0000000000..2823d35b7c
@@ -0,0 +1,35 @@
+extern Datum GetSchemaVariable(Oid varid, bool *isNull, Oid expected_typid, bool copy);
index f7b1f77616..4fdceb6cee 100644

 
+		/* for EEOP_PARAM_VARIABLE */
+		struct
+		{
+			int			paramid;		/* numeric ID for parameter */
+			Oid			varoid;			/* OID of assigned variable */
+			Oid			paramtype;		/* OID of parameter's datatype */
+		}			vparam;
+
+
 		/* for EEOP_PARAM_CALLBACK */
 		struct
 		{
@@ -700,6 +710,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
 extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
 extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
 					ExprContext *econtext);
+extern void ExecEvalParamVariable(ExprState *state, ExprEvalStep *op,
+					ExprContext *econtext);
 extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/svariableReceiver.h b/src/include/executor/svariableReceiver.h
new file mode 100644
index 0000000000..8c8117701f
--- /dev/null
+++ b/src/include/executor/svariableReceiver.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.h
+ *	  prototypes for svariableReceiver.c
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/svariableReceiver.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SVARIABLE_RECEIVER_H
+#define SVARIABLE_RECEIVER_H
+
+#include "tcop/dest.h"
+
+
+extern DestReceiver *CreateVariableDestReceiver(void);
+
+extern void SetVariableDestReceiverParams(DestReceiver *self, Oid varid);
+
+#endif							/* SVARIABLE_RECEIVER_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 018f50bbb7..33cc8be55a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -100,6 +100,8 @@ typedef struct ExprState
 	int			steps_len;		/* number of steps currently */
 	int			steps_alloc;	/* allocated length of steps array */

 

 	/*

 

 

 

diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4b0d75af..97f838a54e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -229,13 +229,17 @@ typedef struct Const
  *				of the `paramid' field contain the SubLink's subLinkId, and
  *				the low-order 16 bits contain the column number.  (This type
  *				of Param is also converted to PARAM_EXEC during planning.)
+ *
+ *		PARAM_VARIABLE:  The parameter is a access to schema variable
+ *				paramid holds varid.
  */
 typedef enum ParamKind
 {
 	PARAM_EXTERN,
 	PARAM_EXEC,
 	PARAM_SUBLINK,
-	PARAM_MULTIEXPR
+	PARAM_MULTIEXPR,
+	PARAM_VARIABLE
 } ParamKind;
 
 typedef struct Param

 /* ----------------

 /*

 

 

 
 
 # ----------

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 14 Aug 2018 16:38:56 +0200

Hi

I wrote missing collation support

Regards

Pavel
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3bb48d4ccf..ffeea7df3f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -359,6 +359,11 @@
       <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
       <entry>mappings of users to foreign servers</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+      <entry>schema variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -11311,4 +11316,114 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
 
  </sect1>
 
+      <entry><structfield>varcollation</structfield></entry>
+      <entry><literal><link linkend="catalog-pg-collation"><structname>pg_collation</structname></link>.oid</literal></entry>
+       The defined collation of the variable, or zero if the variable is
+       not of a collatable data type.
index 0000000000..1bf127eccd
+CREATE VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
+    <term><literal>COLLATE <replaceable>collation</replaceable></literal></term>
+      The <literal>COLLATE</literal> clause assigns a collation to
+      the variable (which must be of a collatable data type).
+      If not specified, the variable data type's default collation is used.
 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+    <varlistentry>

 

 

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

 	/* shouldn't get here */
index 3971346e73..90d16e263f 100644
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
  * DeconstructQualifiedName
@@ -4484,3 +4746,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..ea6b0960a3
@@ -0,0 +1,309 @@
+get_schema_variable_type_typmod_collid(Oid varid, Oid *typid, int32 *typmod, Oid *collid)
+	*collid = varform->varcollation;
+	var->collation = varform->varcollation;
+			   Oid varCollation,
+	values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation);

 

 	/*

 			/*
index 0000000000..6bdd200e40
@@ -0,0 +1,492 @@
+#include "parser/parse_collate.h"
+	Oid			collation;
+	Oid			typcollation;
+	typcollation = get_typcollation(typid);
+	if (stmt->collClause)
+		collation = LookupCollation(pstate,
+									stmt->collClause->collname,
+									stmt->collClause->location);
+		collation = typcollation;;
+	/* Complain if COLLATE is applied to an uncollatable type */
+	if (OidIsValid(collation) && !OidIsValid(typcollation))
+				 errmsg("collations are not supported by type %s",
+						format_type_be(typid)),
+				 parser_errposition(pstate, stmt->collClause->location)));
+		assign_expr_collations(pstate, cooked_default);
+						  collation,
+	Oid			var_collid;
+	get_schema_variable_type_typmod_collid(varid,
+										   &var_typid,
+										   &var_typmod,
+										   &var_collid);
index f6210226e9..59dc30f14d 100644
@@ -9650,6 +9650,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 				/*

 
 						/*

+/*
index c583e020a0..797c1f43b3 100644
 
+	/*
 
 			/*

 	if (sendTuples)

+static LetStmt *
 
+static bool
 
+static void
 
index 96bf0601a8..4573a88f35 100644
 
+
+
 	/*
@@ -2173,7 +2177,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,

 

 
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c601b6d40d..8a724fe3bf 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,7 +25,10 @@
 #include "postgres.h"

 
+		case T_LetStmt:
 
 			/*
 
@@ -1567,6 +1581,204 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+	Oid			collid;
+	get_schema_variable_type_typmod_collid(varid, &typid, &typmod, &collid);
index 87f5e95827..4310c28538 100644

 /*

 

@@ -4479,6 +4488,44 @@ create_extension_opt_item:
 				}
 		;
 
+			CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause optSchemaVarDefExpr
+					n->collClause = $7;
+					n->defexpr = $8;
+			| CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause optSchemaVarDefExpr
+					n->collClause = $10;
+					n->defexpr = $11;
@@ -6335,6 +6382,7 @@ drop_type_any_name:
 
@@ -6604,6 +6652,7 @@ comment_type_any_name:
 
@@ -6742,6 +6791,7 @@ security_label_type_any_name:
 
@@ -7163,6 +7213,14 @@ privilege_target:
@@ -7203,6 +7261,14 @@ privilege_target:

@@ -7363,6 +7429,7 @@ defacl_privilege_target:

@@ -8959,6 +9026,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
 
@@ -9277,6 +9363,25 @@ AlterObjectSchemaStmt:
 
@@ -9512,6 +9617,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -10693,6 +10806,7 @@ ExplainableStmt:
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 explain_option_list:
@@ -10750,6 +10864,7 @@ PreparableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -11148,6 +11263,44 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -15127,6 +15280,7 @@ unreserved_keyword:
@@ -15275,6 +15429,8 @@ unreserved_keyword:
 
 			/*
 
 			if (isAgg)
 
 			/*
index 385e54a9b6..cc614b3902 100644

+						  Oid varid, Oid typid, int32 typmod, Oid collid,
 
@@ -773,6 +791,72 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
+	if (OidIsValid(varid))
+	{
+		Oid		typid;
+		int32	typmod;
+		Oid		collid;
+
+		get_schema_variable_type_typmod_collid(varid, &typid, &typmod, &collid);
+
+		if (node != NULL)
+		{
+			/*
+											varid, typid, typmod, collid,
@@ -807,6 +891,60 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
+makeParamSchemaVariable(ParseState *pstate, Oid varid, Oid typid, int32 typmod, Oid collid, char *attrname, int location)
+	param->paramcollid = collid;
@@ -1818,6 +1956,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
@@ -1826,6 +1965,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
@@ -3460,6 +3600,7 @@ ParseExprKindName(ParseExprKind exprKind)
@@ -3475,6 +3616,8 @@ ParseExprKindName(ParseExprKind exprKind)
 
 			/*
 
 			/*

 	/*

 		case DestTupleQueue:

 	/*
 
+		case T_LetStmt:
+			{

+		case T_LetStmt:
+			tag = "LET";
+			break;
+

+		case T_LetStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 
 	ereport(ERROR,

+/*
+ * get_varname_varid

+	if (g_verbose)

+/*
+ * getVariables

+/*
 
 #endif							/* PG_DUMP_H */

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

 
 
 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches1("DO"))
 
+	/* DROP VARIABLE */
 
 /* FETCH && MOVE */

 /* PREPARE xx AS */
 
 /*

 
index 0e202372d5..8812075b2e 100644

@@ -146,6 +149,10 @@ extern void SetTempNamespaceState(Oid tempNamespaceId,

 
 
index 0000000000..2a2b1f3e08
@@ -0,0 +1,91 @@
+	Oid			varcollation;	/* variable collation */
+	Oid		collation;
+extern void get_schema_variable_type_typmod_collid(Oid varid,
+												   Oid *typid,
+												   int32 *typmod,
+												   Oid *collid);
+									   Oid varCollation,

 
index 41fa2052a2..1d6eaf9ce7 100644

 

 	/*
index 07ab1a3dde..72e26b9f35 100644

 
 
@@ -2497,6 +2513,20 @@ typedef struct AlterSeqStmt
 	bool		missing_ok;		/* skip error if a role is missing? */
 } AlterSeqStmt;
 
+	CollateClause *collClause;
@@ -3238,7 +3268,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;

 

 
 
 typedef struct Param

 /* ----------------

 /*

 

 

 
index 0000000000..f8c72ccd85
@@ -0,0 +1,366 @@
+CREATE VARIABLE xx AS text DEFAULT 'hello';
+SELECT xx, upper(xx);
+  xx   | upper 
+-------+-------
+ hello | HELLO
+LET xx = 'Hi';
+SELECT xx;
+ xx 
+----
+ Hi
+DROP VARIABLE xx;
 
 # ----------
index 0000000000..6bd801e771
@@ -0,0 +1,257 @@
+CREATE VARIABLE xx AS text DEFAULT 'hello';
+
+SELECT xx, upper(xx);
+
+LET xx = 'Hi';
+
+SELECT xx;
+
+DROP VARIABLE xx;

---

From: Fabien COELHO <[email-masked]>
Date: Tue, 21 Aug 2018 19:55:57 +0200 (CEST)

Hello Pavel,

AFAICR, I had an objection on such new objects when you first proposed 
something similar in October 2016.

Namely, if session variables are not transactional, they cannot be used to 
implement security related auditing features which were advertised as the 
motivating use case: an the audit check may fail on a commit because of a 
differed constraint, but the variable would keep its "okay" value unduly, 
which would create a latent security issue, the audit check having failed 
but the variable saying the opposite.

So my point was that they should be transactional by default, although I 
would be ok with an option for having a voluntary non transactional 
version.

Is this issue addressed somehow with this version?

-- 
Fabien.

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 21 Aug 2018 20:48:25 +0200

Hi Fabien

Dne út 21. 8. 2018 19:56 uživatel Fabien COELHO <[email-masked]>
napsal:

> Is this issue addressed somehow with this ?

1. I respect your opinion, but I dont agree with it. Oracle, db2 has
similar or very similar feature non transactional, and I didnt find any
requests to change it.

2. the prototype implementation was based on relclass items, and some
transactional behave was possible. Peter E. had objections to this design
and proposed own catalog table. I did it. Now, the transactional behave is
harder to implement, although it is not impossible. This patch is not small
now, so I didnt implement it. I have a strong opinion so default behave
have to be non transactional.

Transactional variables significantly increases complexity of this patch,
now is simple, because we can reset variable on drop variable command.
Maybe I miss some simply implementation, but I spent on it more than few
days. Still, any cooperation are welcome.

Regards

Pavel

---

From: Fabien COELHO <[email-masked]>
Date: Wed, 22 Aug 2018 09:00:45 +0200 (CEST)

Hello Pavel,

> similar or very similar feature non transactional, and I didnt find any

The argument of authority that "X does it like that" is not a valid answer 
to my technical objection about security implications of this feature.

> now, so I didnt implement it.

"It is harder to implement" does not look like a valid answer either.

> I have a strong opinion so default behave have to be non transactional.

The fact that you have a "strong opinion" does not really answer my 
objection. Moreover, I said that I would be ok with a non transactional 
option, provided that a default transactional is available.

> days. Still, any cooperation are welcome.

"It is simpler to implement this way" is not an answer either, especially 
as you said that it could have been on point 2.

As I do not see any clear answer to my objection about security 
implications, I understand that it is not addressed by this patch.

At the bare minimum, if this feature ever made it as is, I think that a 
clear caveat must be included in the documentation about not using it for 
any security-related purpose.

Also, I'm not really sure how useful such a non-transactional object can 
be for other purposes: the user should take into account that the 
transaction may fail and the value of the session variable be inconsistent 
as a result. Sometimes it may not matter, but if it matters there is no 
easy way around the fact.

-- 
Fabien.

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 23 Aug 2018 07:35:22 +0200

> easy way around the fact.

I agree, so it should be well documented to be clear, what is possible,
what not, and to be correct expectations.

This feature has two (three) purposes

1. global variables for PL language
2. holding some session based informations, that can be used in security
definer functions.
3. Because it is not transactional, then it allows write operation on read
only hot stand by instances.

It is not transactional safe, but it is secure in sense a possibility to
set a access rights. I understand, so some patterns are not possible, but
when you need hold some keys per session, then this simply solution can be
good enough. The variables are clean after session end.

I think it is possible for some more complex patterns, but then developer
should be smarter, and should to enocode state result to content of
variable. There is strong benefit - read write access to variables is very
cheap and fast.

I invite any patch to doc (or everywhere) with explanation and about
possible risks.

Regards

Pavel

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 23 Aug 2018 10:17:28 +0200 (CEST)

Hello Pavel,

> 2. holding some session based informations, that can be used in security

Hmmm, I see our disagreement. My point is that this feature is *NOT* fit 
for security-related uses because if the transaction fails the variable 
would keep the value it had if the transaction had not failed...

> set a access rights.

This is a misleading play on words. It is secure wrt to access right, but 
unsecure wrt security purposes which is the only point for having such a 
feature in the first place.

> I understand, so some patterns are not possible, but when you need hold 
> some keys per session, then this simply solution can be good enough.

Security vs "good enough in some cases" looks bad to me.

> I think it is possible for some more complex patterns,

I'm not sure of any pattern which would be correct wrt security if it 
depends on the success of a transaction.

> but then developer should be smarter, and should to enocode state result 
> to content of variable.

I do not see how the developer can be smarter if they need a transactional 
for security but they do not have it.

> There is strong benefit - read write access to variables is very cheap 
> and fast.

I'd say that PostgreSQL is about "ACID & security" first, not "cheap & 
fast" first.

> I invite any patch to doc (or everywhere) with explanation and about

Hmmm... You are the one proposing the feature...

Here is something, thanks for adjusting it to the syntax you are proposing 
and inserting it where appropriate. Possibly in the corresponding CREATE 
doc?

"""
<caution>
<par>
Beware that session variables are not transactional.
This is a concern in a security context where the variable must be set to
some trusted value depending on the success of the transaction:
if the transaction fails, the variable keeps its trusted value unduly.
</par>

<par>
For instance, the following pattern does NOT work:

<programlisting>
CREATE USER auditer;
SET ROLE auditer;
CREATE SESSION VARIABLE is_audited BOOLEAN DEFAULT FALSE ...;
-- ensure that only "auditer" can write "is_audited":
REVOKE ... ON SESSION VARIABLE is_audited FROM ...;

-- create an audit function
CREATE FUNCTION audit_session(...) SECURITY DEFINER AS $$
   -- record the session and checks in some place...
   -- then tell it was done:
   LET is_audited = TRUE;
$$;

-- the intention is that other security definier functions can check that
-- the session is audited by checking on "is_audited", eg:
CREATE FUNCTION only_for_audited(...) SECURITY DEFINER AS $$
   IF NOT is_audited THEN RAISE "security error";
   -- do protected stuff here.
$$;
</programlisting>

The above pattern can be attacked with the following approach:
<programlisting>
BEGIN;
SELECT audit_session(...);
-- success, "is_audited" is set...
ROLLBACK;
-- the audit login has been reverted, but "is_audited" retains its value.

-- any subsequent operation believes wrongly that the session is audited,
-- but its logging has really been removed by the ROLLBACK.

-- ok but should not:
SELECT only_for_audited(...);
</programlisting>
</par>
</caution>
"""

For the record, I'm "-1" on this feature as proposed, for what it's worth, 
because of the misleading security implications. This feature would just 
help people have their security wrong.

-- 
Fabien.

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 23 Aug 2018 10:44:10 +0200

> Security vs "good enough in some cases" looks bad to me.

We don't find a agreement, because you are concentrated on transation, me
on session. And we have different expectations.

> There is strong benefit - read write access to variables is very cheap and
>> fast.
>>
>
It is good example of not supported pattern. It is not designed for this.
I'll merge this doc.

Note: I am not sure, if I have all relations to described issue, but if I
understand well, then solution can be reset on transaction end, maybe reset
on rollback. This is solvable, I'll look how it is complex.

> help people have their security wrong.

I respect your opinion - and I hope so integration of your proposed doc is
good warning for users that would to use not transactional variable like
transactional source.

Regards

Pavel

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 23 Aug 2018 11:46:32 +0200 (CEST)

> We don't find a agreement, because you are concentrated on transation, 
> me on session. And we have different expectations.

I do not understand your point, as usual. I raise a factual issue about 
security, and you do not answer how this can be solved with your proposal, 
but appeal to argument of authority and declare your "strong opinion".

I do not see any intrinsic opposition between having session objects and 
transactions. Nothing prevents a session object to be transactional beyond 
your willingness that it should not be.

Now, I do expect all PostgreSQL features to be security-wise, whatever 
their scope.

I do not think that security should be traded for "cheap & fast", esp as 
the sole use case for a feature is a security pattern that cannot be 
implemented securely with it. This appears to me as a huge contradiction, 
hence my opposition against this feature as proposed.

The good news is that I'm a nobody: if a committer is happy with your 
patch, it will get committed, you do not need my approval.

-- 
Fabien.

---

From: Pavel Luzanov <[email-masked]>
Date: Thu, 23 Aug 2018 15:39:07 +0300

On 23.08.2018 12:46, Fabien COELHO wrote:
> I do not understand your point, as usual. I raise a factual issue 
> about security, and you do not answer how this can be solved with your 
> proposal, but appeal to argument of authority and declare your "strong 
> opinion".
>
> I do not see any intrinsic opposition between having session objects 
> and transactions. Nothing prevents a session object to be 
> transactional beyond your willingness that it should not be.
>
> I do not think that security should be traded for "cheap & fast", esp 
> as the sole use case for a feature is a security pattern that cannot 
> be implemented securely with it. This appears to me as a huge 
> contradiction, hence my opposition against this feature as proposed.

I can't to agree with your position.

Consider this example.
I want to record some inappropriate user actions to audit table and 
rollback transaction.
But aborting transaction will also abort record to audit table.
So, do not use tables, becouse they have security implications.

This is very similar to your approach.

Schema variables is a very needed and important feature, but for others 
purposes.

-----
Pavel Luzanov

---

From: Fabien COELHO <[email-masked]>
Date: Wed, 29 Aug 2018 20:10:02 +0200 (CEST)

Hello Pavel L.

> Consider this example. I want to record some inappropriate user actions 
> to audit table and rollback transaction. But aborting transaction will 
> also abort record to audit table. So, do not use tables, becouse they 
> have security implications.

Indeed, you cannot record a transaction failure from a transaction.

> This is very similar to your approach.

I understand that your point is that some use case could require a non 
transactional session variable. I'm not sure of how the use case would go 
on though, because once the "attacker" disconnects, the session variable 
disappears, so it does not record that there was a problem.

Anyway, I'm not against having session variables per se. I'm argumenting 
that there is a good case to have them transactional by default, and 
possibly an option to have them non transactional if this is really needed 
by some use case to provide.

The only use case put forward by Pavel S. is the security audit one 
where a session variable stores that audit checks have been performed, 
which AFAICS cannot be implemented securely with the proposed non 
transactional session variables.

-- 
Fabien.

---

From: Dean Rasheed <[email-masked]>
Date: Tue, 4 Sep 2018 08:21:07 +0100

AFAICS this patch does nothing to consider parallel safety -- that is,
as things stand, a variable is allowed in a query that may be
parallelised, but its value is not copied to workers, leading to
incorrect results. For example:

create table foo(a int);
insert into foo select * from generate_series(1,1000000);
create variable zero int;
let zero = 0;

explain (costs off) select count(*) from foo where a%10 = zero;

                  QUERY PLAN
-----------------------------------------------
 Finalize Aggregate
   ->  Gather
         Workers Planned: 2
         ->  Partial Aggregate
               ->  Parallel Seq Scan on foo
                     Filter: ((a % 10) = zero)
(6 rows)

select count(*) from foo where a%10 = zero;

 count
-------
 38037    -- Different random result each time, should be 100,000
(1 row)

Thoughts?

Regards,
Dean

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 4 Sep 2018 15:00:51 +0200

Hi

> explain (costs off) select count(*) from foo where a%10 = zero;

The query use copy of values of variables now - but unfortunately, these
values are not passed to workers.  Should be fixed.

Thank you for test case.

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 6 Sep 2018 10:30:04 +0200

Hi

here is updated patch - I wrote some transactional support

I am not sure how these new features are understandable and if these
features does it better or not.

There are possibility to reset to default value when

a) any transaction is finished - the scope of value is limited by
transaction

CREATE VARIABLE foo int ON TRANSACTION END RESET;

b) when transaction finished by rollback

CREATE VARIABLE foo int ON ROLLBACK RESET

Now, when I am thinking about it, the @b is simple, but not too practical -
when some fails, then we lost a value (any transaction inside session can
fails). The @a has sense - the behave is global value (what is not possible
in Postgres now), but this value is destroyed by any unhandled exceptions,
and it cleaned on transaction end. The @b is just for information and for
discussion, but I'll remove it - because it is obscure.

The open question is syntax. PostgreSQL has already ON COMMIT xxx . It is
little bit unclean, because it has semantic "on transaction end", but if I
didn't implement @b, then ON COMMIT syntax can be used.

Regards

Pavel
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0179deea2e..8bb478f5fd 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -359,6 +359,11 @@
       <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
       <entry>mappings of users to foreign servers</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+      <entry>schema variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -11303,4 +11308,123 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
 
  </sect1>
 
+      <entry><structfield>varoncommitreset</structfield></entry>
+      <entry><type>bool</type></entry>
+       When it is true, then content of variable is thrown on transaction end.
 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+    <varlistentry>

diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index db4f4167e3..5fb82df51e 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -75,6 +75,7 @@
    &alterType;
    &alterUser;
    &alterUserMapping;
+   &alterVariable;
    &alterView;
    &analyze;
    &begin;
@@ -127,6 +128,7 @@
    &createType;
    &createUser;
    &createUserMapping;
+   &createVariable;
    &createView;
    &deallocate;
    &declare;
@@ -175,6 +177,7 @@
    &dropType;
    &dropUser;
    &dropUserMapping;
+   &dropVariable;
    &dropView;
    &end;
    &execute;
@@ -183,6 +186,7 @@
    &grant;
    &importForeignSchema;
    &insert;
+   &let;
    &listen;
    &load;
    &lock;
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index cd8270d5fb..f3aa9d6c8f 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/schemavariable.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
@@ -1996,6 +1997,7 @@ CommitTransaction(void)
 	 * cursors, to avoid dangling-reference problems)
 	 */
 	PreCommit_on_commit_actions();
+	SchemaVariablePreCommit_on_commit_actions();
 
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
@@ -2121,6 +2123,7 @@ CommitTransaction(void)
 	AtEOXact_GUC(true, 1);
 	AtEOXact_SPI(true);
 	AtEOXact_on_commit_actions(true);
+	AtEOXact_SchemaVariables_on_commit_actions(true);
 	AtEOXact_Namespace(true, is_parallel_worker);
 	AtEOXact_SMgr();
 	AtEOXact_Files(true);
@@ -2601,6 +2604,7 @@ AbortTransaction(void)
 		AtEOXact_GUC(false, 1);
 		AtEOXact_SPI(false);
 		AtEOXact_on_commit_actions(false);
+		AtEOXact_SchemaVariables_on_commit_actions(false);
 		AtEOXact_Namespace(false, is_parallel_worker);
 		AtEOXact_SMgr();
 		AtEOXact_Files(false);
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 0865240f11..1f7c4d1223 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -19,7 +19,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_publication.o pg_range.o \
 	   pg_db_role_setting.o pg_shdepend.o pg_subscription.o pg_type.o \
-	   storage.o toasting.o
+	   pg_variable.o storage.o toasting.o

 

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

 	/* shouldn't get here */
index 5d13e6a3d7..453ec1c5a1 100644
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
 
+/*
 
 /*
  * DeconstructQualifiedName
@@ -4490,3 +4752,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..79543bbd4d
@@ -0,0 +1,358 @@
+#include "nodes/primnodes.h"
+static VariableEOXActionCodes
+to_eoxaction_code(VariableEOXAction action)
+	switch (action)
+		case VARIABLE_EOX_NOOP:
+			return VARIABLE_EOX_CODE_NOOP;
+		case VARIABLE_EOX_DROP:
+			return VARIABLE_EOX_CODE_DROP;
+		case VARIABLE_EOX_RESET:
+			return VARIABLE_EOX_CODE_RESET;
+		case VARIABLE_EOX_ROLLBACK_RESET:
+			return VARIABLE_EOX_CODE_ROLLBACK_RESET;
+			elog(ERROR, "unexpected action");
+static VariableEOXAction
+to_eoxaction(VariableEOXActionCodes code)
+	switch (code)
+		case VARIABLE_EOX_CODE_NOOP:
+			return VARIABLE_EOX_NOOP;
+		case VARIABLE_EOX_CODE_DROP:
+			return VARIABLE_EOX_DROP;
+		case VARIABLE_EOX_CODE_RESET:
+			return VARIABLE_EOX_RESET;
+		case VARIABLE_EOX_CODE_ROLLBACK_RESET:
+			return VARIABLE_EOX_ROLLBACK_RESET;
+			elog(ERROR, "unexpected code");
+	var->eoxaction = to_eoxaction(varform->vareoxaction);
+			   VariableEOXAction eoxaction,
+	values[Anum_pg_variable_vareoxaction - 1] = CharGetDatum((char) to_eoxaction_code(eoxaction));
+	/* register on commit action if it is necessary */
+	register_variable_on_commit_action(myself.objectId, eoxaction);

 

 	/*

 			/*
@@ -2244,6 +2248,8 @@ stringify_grant_objtype(ObjectType objtype)
 			return "TABLESPACE";
 		case OBJECT_TYPE:
 			return "TYPE";
+		case OBJECT_VARIABLE:
+			return "VARIABLE";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
@@ -2326,6 +2332,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 			return "TABLESPACES";
 		case OBJECT_TYPE:
 			return "TYPES";
+		case OBJECT_VARIABLE:
+			return "VARIABLES";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index b945b1556a..eb8c08baf3 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -151,6 +151,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString,
 		case CMD_INSERT:
 		case CMD_UPDATE:
 		case CMD_DELETE:
+		case CMD_PLAN_UTILITY:
 			/* OK */
 			break;
 		default:
diff --git a/src/backend/commands/schemavariable.c b/src/backend/commands/schemavariable.c
new file mode 100644
index 0000000000..3ef576825d
--- /dev/null
+++ b/src/backend/commands/schemavariable.c
@@ -0,0 +1,691 @@
+#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_class.h"
+#include "catalog/pg_variable.h"
+#include "commands/schemavariable.h"
+#include "executor/executor.h"
+#include "executor/svariableReceiver.h"
+#include "nodes/execnodes.h"
+#include "optimizer/planner.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_type.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/inval.h"
+#include "utils/memutils.h"
+#include "utils/lsyscache.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+/*
+ * ON COMMIT action list
+ */
+typedef struct OnCommitItem
+{
+	Oid			varid;			/* relid of relation */
+	VariableEOXAction eoxaction;	/* what to do at end of xact */
+	bool		deleted;		/* true, when varid should be deleted */
+	bool		conditional_reset; /* when is true, then variable is reseted on rollback */
+} OnCommitItem;
+
+static List *on_commits = NIL;
+
+/*
+ * The content of variables is not transactional. Due this fact the
+ * implementation of DROP can be simple, because although DROP VARIABLE
+ * can be reverted, the content of variable can be lost. In this example,
+ * DROP VARIABLE is same like reset variable.
+ */
+
+typedef struct SchemaVariableData
+{
+	Oid			varid;			/* pg_variable OID of this sequence (hash key) */
+	Oid			typid;			/* OID of the data type */
+	int32		typmod;
+	int16		typlen;
+	bool		typbyval;
+	bool		isnull;
+	bool		freeval;
+	Datum		value;
+	bool		is_rowtype;		/* true when variable is composite */
+	bool		is_valid;		/* true when variable was successfuly initialized */
+} SchemaVariableData;
+
+typedef SchemaVariableData *SchemaVariable;
+
+static HTAB *schemavarhashtab = NULL;		/* hash table for session variables */
+static MemoryContext SchemaVariableMemoryContext = NULL;
+
+static bool first_time = true;
+static void create_schemavar_hashtable(void);
+static bool clean_cache_req = false;
+
+static void clean_cache(void);
+static void force_clean_cache(XactEvent event, void *arg);
+static void remove_variable_on_commit_actions(Oid varid);
+
+
+/*
+ * Save info about ncessity to clean hash table, because some
+ * schema variable was dropped. Don't do here more, recheck
+ * needs to be in transaction state.
+ */
+static void
+InvalidateSchemaVarCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+	if (cacheid != VARIABLEOID)
+		return;
+
+	clean_cache_req = true;
+}
+
+static void
+force_clean_cache(XactEvent event, void *arg)
+{
+	/*
+	 * should continue only in transaction time, when
+	 * syscache is available.
+	 */
+	if (clean_cache_req && IsTransactionState())
+	{
+		clean_cache();
+		clean_cache_req = false;
+	}
+}
+
+static void
+clean_cache(void)
+{
+	HASH_SEQ_STATUS status;
+	SchemaVariable		var;
+
+	if (!schemavarhashtab)
+		return;
+
+	hash_seq_init(&status, schemavarhashtab);
+
+	/*
+	 * Every valid variable have to have entry in system
+	 * catalog. Removed if there is nothing.
+	 */
+	while ((var = (SchemaVariable) hash_seq_search(&status)) != NULL)
+	{
+		HeapTuple	tp = InvalidOid;
+
+		tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(var->varid));
+		if (!HeapTupleIsValid(tp))
+		{
+			elog(DEBUG1, "variable %d is removed from cache", var->varid);
+
+			if (var->freeval)
+			{
+				pfree(DatumGetPointer(var->value));
+				var->freeval = false;
+			}
+
+			if (hash_search(schemavarhashtab,
+								(void *) &var->varid,
+								HASH_REMOVE,
+								NULL) == NULL)
+				elog(DEBUG1, "hash table corrupted");
+		}
+		else
+			ReleaseSysCache(tp);
+	}
+}
+
+/*
+ * Clean variable defined by varid
+ */
+static void
+clean_cache_varid(Oid varid)
+{
+	SchemaVariable	svar;
+	bool			found;
+
+	if (!schemavarhashtab)
+		return;
+
+	if (schemavarhashtab == NULL)
+		create_schemavar_hashtable();
+
+	svar = (SchemaVariable) hash_search(schemavarhashtab, &varid,
+											  HASH_FIND, &found);
+	if (found)
+	{
+		/* clean content, if it is necessary */
+		if (svar->freeval)
+			pfree(DatumGetPointer(svar->value));
+
+		if (hash_search(schemavarhashtab,
+							(void *) &svar->varid,
+							HASH_REMOVE,
+							NULL) == NULL)
+			elog(DEBUG1, "hash table corrupted");
+
+		remove_variable_on_commit_actions(varid);
+	}
+}
+
+/*
+ * Create the hash table for storing schema variables
+ */
+static void
+create_schemavar_hashtable(void)
+{
+	HASHCTL		ctl;
+
+	/* set callbacks */
+	if (first_time)
+	{
+		CacheRegisterSyscacheCallback(VARIABLEOID,
+									  InvalidateSchemaVarCacheCallback,
+									  (Datum) 0);
+
+		RegisterXactCallback(force_clean_cache, NULL);
+
+		first_time = false;
+	}
+
+	/* needs own long life memory context */
+	if (SchemaVariableMemoryContext == NULL)
+	{
+		SchemaVariableMemoryContext = AllocSetContextCreate(TopMemoryContext,
+															"schema variables",
+															ALLOCSET_START_SMALL_SIZES);
+	}
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(SchemaVariableData);
+	ctl.hcxt = SchemaVariableMemoryContext;
+
+	schemavarhashtab = hash_create("Schema variables", 64, &ctl,
+										HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+/*
+ * Fast drop complete content of schema variables
+ */
+void
+ResetSchemaVariableCache(void)
+{
+	if (schemavarhashtab)
+	{
+		hash_destroy(schemavarhashtab);
+		schemavarhashtab = NULL;
+	}
+
+	if (SchemaVariableMemoryContext != NULL)
+	{
+		MemoryContextReset(SchemaVariableMemoryContext);
+	}
+}
+
+/*
+ * Drop variable by OID
+ */
+void
+RemoveVariableById(Oid varid)
+{
+	Relation	rel;
+	HeapTuple	tup;
+
+	rel = heap_open(VariableRelationId, RowExclusiveLock);
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+
+	CatalogTupleDelete(rel, &tup->t_self);
+
+	ReleaseSysCache(tup);
+
+	heap_close(rel, RowExclusiveLock);
+
+	/* remove variable from on_commits list */
+	remove_variable_on_commit_actions(varid);
+}
+
+/*
+ * Creates new variable - entry in pg_catalog.pg_variable table
+ */
+ObjectAddress
+DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt *stmt)
+{
+	Oid			namespaceid;
+	AclResult	aclresult;
+	Oid			typid;
+	int32		typmod;
+	Oid			varowner = GetUserId();
+	Oid			collation;
+	Oid			typcollation;
+	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")));
+
+	namespaceid =
+		RangeVarGetAndCheckCreationNamespace(stmt->variable, NoLock, NULL);
+
+	typenameTypeIdAndMod(pstate, stmt->typeName, &typid, &typmod);
+	typcollation = get_typcollation(typid);
+
+	aclresult = pg_type_aclcheck(typid, GetUserId(), ACL_USAGE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error_type(aclresult, typid);
+
+	if (stmt->collClause)
+		collation = LookupCollation(pstate,
+									stmt->collClause->collname,
+									stmt->collClause->location);
+	else
+		collation = typcollation;;
+
+	/* Complain if COLLATE is applied to an uncollatable type */
+	if (OidIsValid(collation) && !OidIsValid(typcollation))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("collations are not supported by type %s",
+						format_type_be(typid)),
+				 parser_errposition(pstate, stmt->collClause->location)));
+
+	if (stmt->defexpr)
+	{
+		cooked_default = transformExpr(pstate, stmt->defexpr,
+										EXPR_KIND_VARIABLE_DEFAULT);
+
+		cooked_default = coerce_to_specific_type(pstate,
+												 cooked_default, typid, "DEFAULT");
+		assign_expr_collations(pstate, cooked_default);
+	}
+
+	return VariableCreate(stmt->variable->relname,
+						  namespaceid,
+						  typid,
+						  typmod,
+						  varowner,
+						  collation,
+						  cooked_default,
+						  stmt->eoxaction,
+						  stmt->if_not_exists);
+
+	return variable;
+}
+
+/*
+ * Try to search value in hash table. If doesn't
+ * exists insert it (and calculate defexpr if exists.
+ */
+static SchemaVariable
+PrepareSchemaVariableForReading(Oid varid)
+{
+	SchemaVariable	svar;
+	Variable	*var;
+	bool			found;
+
+	if (schemavarhashtab == NULL)
+		create_schemavar_hashtable();
+
+	svar = (SchemaVariable) hash_search(schemavarhashtab, &varid,
+											  HASH_ENTER, &found);
+	if (!found)
+	{
+		var = GetVariable(varid, false);
+		get_typlenbyval(var->typid, &svar->typlen, &svar->typbyval);
+
+		svar->varid = varid;
+		svar->typid = var->typid;
+		svar->typmod = var->typmod;
+		svar->isnull = true;
+		svar->freeval = false;
+		svar->value = (Datum) 0;
+		svar->is_rowtype = type_is_rowtype(var->typid);
+
+		/* when we don't need calculate defexpr, value is valid already */
+		svar->is_valid = var->defexpr ? false : true;
+
+		if (var->eoxaction != VARIABLE_EOX_NOOP)
+			register_variable_on_commit_action(varid, var->eoxaction);
+	}
+	else if (!svar->is_valid)
+	{
+		/* we need var to recalculate defexpr */
+		var = GetVariable(varid, false);
+	}
+	else
+		/* we don't need to go to sys cache */
+		var = NULL;
+
+	/*
+	 * Initialize variable when it is necessary. It is fresh
+	 * or last initialization was not successfull.
+	 */
+	if (var != NULL && var->defexpr && !svar->is_valid)
+	{
+		MemoryContext oldcontext = NULL;
+
+		Datum	   value = (Datum) 0;
+		bool	   null;
+		EState	   *estate = NULL;
+		Expr	   *defexpr;
+		ExprState  *defexprs;
+
+		/* Prepare default expr */
+		estate = CreateExecutorState();
+		oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		defexpr = expression_planner((Expr *) var->defexpr);
+		defexprs = ExecInitExpr(defexpr, NULL);
+		value = ExecEvalExprSwitchContext(defexprs, GetPerTupleExprContext(estate), &null);
+
+		MemoryContextSwitchTo(SchemaVariableMemoryContext);
+
+		if (!null)
+		{
+			svar->value = datumCopy(value, svar->typbyval, svar->typlen);
+			svar->freeval = svar->value != value;
+			svar->isnull = false;
+			svar->is_valid = true;
+		}
+		else
+		{
+			svar->isnull = true;
+			svar->is_valid = true;
+		}
+
+		MemoryContextSwitchTo(oldcontext);
+
+		FreeExecutorState(estate);
+	}
+
+	if (!svar->is_valid)
+		elog(ERROR, "the content of variable is not valid");
+
+	return svar;
+}
+
+/*
+ * Returns content of variable. We expext secured access now.
+ * Secure check should be done before.
+ */
+Datum
+GetSchemaVariable(Oid varid, bool *isNull, Oid expected_typid, bool copy)
+{
+	SchemaVariable	svar;
+	Datum			value;
+	bool			isnull;
+
+	svar = PrepareSchemaVariableForReading(varid);
+	Assert(svar != NULL);
+
+	if (expected_typid != svar->typid)
+		elog(ERROR, "type of variable \"%s\" is different than expected",
+				   schema_variable_get_name(varid));
+
+	value = svar->value;
+	isnull = svar->isnull;
+
+	*isNull = isnull;
+
+	if (!isnull && copy)
+		return datumCopy(value, svar->typbyval, svar->typlen);
+
+	return value;
+}
+
+/*
+ * Write value to variable. We expect secured access in this moment.
+ * In this time, we recheck syschache about used type.
+ */
+void
+SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid, int32 typmod)
+{
+	MemoryContext oldcontext = NULL;
+
+	SchemaVariable svar;
+	Oid			var_typid;
+	int32		var_typmod;
+	Oid			var_collid;
+	bool		found;
+
+	if (schemavarhashtab == NULL)
+		create_schemavar_hashtable();
+
+	svar = (SchemaVariable) hash_search(schemavarhashtab, &varid,
+											  HASH_ENTER, &found);
+
+	get_schema_variable_type_typmod_collid(varid,
+										   &var_typid,
+										   &var_typmod,
+										   &var_collid);
+
+	/* check types first */
+	if (var_typid != typid)
+		elog(ERROR, "type of expression is different than schema variable type");
+
+	if (found)
+	{
+		/* release current content first */
+		if (svar->freeval)
+		{
+			pfree(DatumGetPointer(svar->value));
+			svar->value = (Datum) 0;
+			svar->isnull = true;
+			svar->freeval = false;
+		}
+	}
+	else
+	{
+		Variable *var = GetVariable(varid, false);
+
+		register_variable_on_commit_action(varid, var->eoxaction);
+	}
+
+	get_typlenbyval(typid, &svar->typlen, &svar->typbyval);
+
+	svar->varid = varid;
+	svar->typid = typid;
+	svar->typmod = typmod;
+
+	svar->isnull = true;
+	svar->freeval = false;
+	svar->value = (Datum) 0;
+
+	svar->is_rowtype = type_is_rowtype(typid);
+	svar->is_valid = false;
+
+	oldcontext = MemoryContextSwitchTo(SchemaVariableMemoryContext);
+
+	if (!isNull)
+	{
+		svar->value = datumCopy(value, svar->typbyval, svar->typlen);
+		svar->freeval = svar->value != value;
+		svar->isnull = false;
+		svar->is_valid = true;
+	}
+	else
+	{
+		svar->isnull = true;
+		svar->is_valid = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+}
+
+void
+doLetStmt(PlannedStmt *pstmt,
+		  ParamListInfo params,
+		  QueryEnvironment *queryEnv,
+		  const char *queryString)
+{
+	QueryDesc *queryDesc;
+	DestReceiver *dest;
+
+	PushCopiedSnapshot(GetActiveSnapshot());
+	UpdateActiveSnapshotCommandId();
+
+	/* Create dest receiver for LET */
+	dest = CreateDestReceiver(DestVariable);
+
+	SetVariableDestReceiverParams(dest, pstmt->resultVariable);
+
+	/* Create a QueryDesc requesting no output */
+	queryDesc = CreateQueryDesc(pstmt, queryString,
+											GetActiveSnapshot(),
+											InvalidSnapshot,
+											dest, params, queryEnv, 0);
+
+	ExecutorStart(queryDesc, 0);
+	ExecutorRun(queryDesc, ForwardScanDirection, 2L, true);
+	ExecutorFinish(queryDesc);
+	ExecutorEnd(queryDesc);
+
+	FreeQueryDesc(queryDesc);
+
+	PopActiveSnapshot();
+}
+
+/*
+ * Register a newly-created relation's ON COMMIT action.
+ */
+void
+register_variable_on_commit_action(Oid varid, VariableEOXAction action)
+{
+	OnCommitItem *oc;
+	MemoryContext oldcxt;
+
+	/*
+	 * We needn't bother registering the relation unless there is an ON COMMIT
+	 * action we need to take.
+	 */
+	if (action == VARIABLE_EOX_NOOP)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+	oc = (OnCommitItem *) palloc(sizeof(OnCommitItem));
+	oc->varid = varid;
+	oc->eoxaction = action;
+	oc->deleted = false;
+
+	on_commits = lcons(oc, on_commits);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Remove variable from on_commits action
+ */
+static void
+remove_variable_on_commit_actions(Oid varid)
+{
+	ListCell	   *l;
+
+	foreach(l, on_commits)
+	{
+		OnCommitItem *oc = (OnCommitItem *) lfirst(l);
+
+		if (oc->varid == varid)
+		{
+			oc->deleted = true;
+		}
+	}
+}
+
+/*
+ * Perform VARIABLE ON COMMIT action
+ */
+void
+SchemaVariablePreCommit_on_commit_actions(void)
+{
+	ListCell	   *l;
+
+	foreach(l, on_commits)
+	{
+		OnCommitItem *oc = (OnCommitItem *) lfirst(l);
+
+		switch (oc->eoxaction)
+		{
+			case VARIABLE_EOX_NOOP:
+			case VARIABLE_EOX_ROLLBACK_RESET:
+				/* Do nothing */
+				break;
+			case VARIABLE_EOX_RESET:
+				clean_cache_varid(oc->varid);
+				break;
+			case VARIABLE_EOX_DROP:
+				{
+					ObjectAddress object;
+
+					object.classId = VariableRelationId;
+					object.objectId = oc->varid;
+					object.objectSubId = 0;
+
+					/*
+					 * Since this is an automatic drop, rather than one
+					 * directly initiated by the user, we pass the
+					 * PERFORM_DELETION_INTERNAL flag.
+					 */
+					performDeletion(&object,
+									DROP_CASCADE, PERFORM_DELETION_INTERNAL);
+					break;
+				}		
+		}
+	}
+}
+
+/*
+ * Post-commit or post-abort cleanup for ON COMMIT management.
+ */
+void
+AtEOXact_SchemaVariables_on_commit_actions(bool isCommit)
+{
+	ListCell   *cur_item;
+	ListCell   *prev_item;
+
+	prev_item = NULL;
+	cur_item = list_head(on_commits);
+
+	while (cur_item != NULL)
+	{
+		OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
+
+		if (oc->eoxaction == VARIABLE_EOX_ROLLBACK_RESET && !isCommit)
+			clean_cache_varid(oc->varid);
+
+		if (oc->deleted)
+		{
+			/* cur_item must be removed */
+			on_commits = list_delete_cell(on_commits, cur_item, prev_item);
+			pfree(oc);
+			if (prev_item)
+				cur_item = lnext(prev_item);
+			else
+				cur_item = list_head(on_commits);
+		}
+		else
+		{
+			prev_item = cur_item;
+			cur_item = lnext(prev_item);
+		}
+	}
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f9e83c2456..d9dd23e5d6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9655,6 +9655,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_PUBLICATION_REL:
 			case OCLASS_SUBSCRIPTION:
 			case OCLASS_TRANSFORM:
+			case OCLASS_VARIABLE:
 
 				/*

 
 						/*

+/*
 
+	/*
 
 			/*

 	if (sendTuples)

+static LetStmt *
 
+static bool
index b5af904c18..a32e01ae4d 100644
@@ -2794,6 +2795,16 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(rarg);
 }
 
+static void
@@ -2972,6 +2983,7 @@ _outQuery(StringInfo str, const Query *node)
 
@@ -4192,6 +4204,9 @@ outNode(StringInfo str, const void *obj)
 
+
+
 	/*

diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index ee6f4cdf4d..f232c6cfd1 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1268,7 +1268,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 	{
 		Param	   *param = (Param *) node;

 		if (param->paramkind != PARAM_EXEC ||
@@ -4813,7 +4814,7 @@ substitute_actual_parameters_mutator(Node *node,
 {
 	if (node == NULL)
 		return NULL;
-	if (IsA(node, Param))
+	if (IsA(node, Param) && ((Param *) node)->paramkind != PARAM_VARIABLE)
 	{
 		Param	   *param = (Param *) node;

 

+		case T_LetStmt:
 
 			/*

+/*
+ * transformLetStmt -
index 4bd2223f26..0f9d67506a 100644
@@ -211,6 +211,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	JoinType			jtype;
 	DropBehavior		dbehavior;
 	OnCommitAction		oncommit;
+	VariableEOXAction	oneoxaction;
 	List				*list;
 	Node				*node;
 	Value				*value;
@@ -257,8 +258,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -268,7 +269,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -400,6 +401,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -422,6 +424,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	 OptTemp
 %type <ival>	 OptNoLog
 %type <oncommit> OnCommitOption
+%type <oneoxaction> OnEOXActionOption
 
 %type <ival>	for_locking_strength
 %type <node>	for_locking_item
@@ -584,6 +587,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		hash_partbound partbound_datum_list range_datum_list
 %type <defelt>		hash_partbound_elem
+%type <node>		optSchemaVarDefExpr
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -649,7 +653,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY

 	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -687,8 +691,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
 	UNTIL UPDATE USER USING

 
@@ -878,6 +882,7 @@ stmt :
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSchemaVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -917,6 +922,7 @@ stmt :
 			| ImportForeignSchemaStmt
 			| IndexStmt
 			| InsertStmt
+			| LetStmt
 			| ListenStmt
 			| RefreshMatViewStmt
 			| LoadStmt
@@ -1808,7 +1814,12 @@ DiscardStmt:
 					n->target = DISCARD_SEQUENCES;
 					$$ = (Node *) n;
 				}
-
+			| DISCARD VARIABLES
+				{
+					DiscardStmt *n = makeNode(DiscardStmt);
+					n->target = DISCARD_VARIABLES;
+					$$ = (Node *) n;
+				}
 		;

@@ -4479,6 +4490,49 @@ create_extension_opt_item:
 				}
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY :
+ *				CREATE VARIABLE varname [AS] type
+ *
+ *****************************************************************************/
+
+CreateSchemaVarStmt:
+			CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause optSchemaVarDefExpr OnEOXActionOption
+				{
+					CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+					$4->relpersistence = $2;
+					n->variable = $4;
+					n->typeName = $6;
+					n->collClause = $7;
+					n->defexpr = $8;
+					n->eoxaction = $9;
+					n->if_not_exists = false;
+					$$ = (Node *) n;
+				}
+			| CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause optSchemaVarDefExpr OnEOXActionOption
+				{
+					CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+					$7->relpersistence = $2;
+					n->variable = $7;
+					n->typeName = $9;
+					n->collClause = $10;
+					n->defexpr = $11;
+					n->eoxaction = $12;
+					n->if_not_exists = true;
+					$$ = (Node *) n;
+				}
+		;
+
+optSchemaVarDefExpr: DEFAULT b_expr							{ $$ = $2; }
+			| /* EMPTY */									{ $$ = NULL; }
+		;
+
+OnEOXActionOption:  ON TRANSACTION END_P DROP		{ $$ = VARIABLE_EOX_DROP; }
+			| ON TRANSACTION END_P RESET			{ $$ = VARIABLE_EOX_RESET; }
+			| ON ROLLBACK RESET					{ $$ = VARIABLE_EOX_ROLLBACK_RESET; }
+			| /*EMPTY*/							{ $$ = VARIABLE_EOX_NOOP; }
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
@@ -6340,6 +6394,7 @@ drop_type_any_name:
 			| TEXT_P SEARCH DICTIONARY				{ $$ = OBJECT_TSDICTIONARY; }
 			| TEXT_P SEARCH TEMPLATE				{ $$ = OBJECT_TSTEMPLATE; }
 			| TEXT_P SEARCH CONFIGURATION			{ $$ = OBJECT_TSCONFIGURATION; }
+			| VARIABLE								{ $$ = OBJECT_VARIABLE; }
 		;
 
@@ -6609,6 +6664,7 @@ comment_type_any_name:
 
@@ -6747,6 +6803,7 @@ security_label_type_any_name:
 
@@ -7168,6 +7225,14 @@ privilege_target:
@@ -7208,6 +7273,14 @@ privilege_target:

@@ -7368,6 +7441,7 @@ defacl_privilege_target:

@@ -8964,6 +9038,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
 
@@ -9282,6 +9375,25 @@ AlterObjectSchemaStmt:
 
@@ -9517,6 +9629,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -10698,6 +10818,7 @@ ExplainableStmt:
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 explain_option_list:
@@ -10755,6 +10876,7 @@ PreparableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -11153,6 +11275,44 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
@@ -15132,6 +15292,7 @@ unreserved_keyword:
@@ -15280,6 +15441,8 @@ unreserved_keyword:
 
 			/*
 
 			if (isAgg)
 
 			/*

 
 
+	if (OidIsValid(varid))
+	{
+		Oid		typid;
+		int32	typmod;
+		Oid		collid;
+
 
+/*
 
 			/*
 
 			/*
 
index d830569641..c27aecedb5 100644
@@ -3359,7 +3359,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 
 	/*

 		case DestTupleQueue:

 	/*
 
+		case T_LetStmt:
+			{

+		case T_LetStmt:
+			tag = "LET";
+			break;
+

+		case T_LetStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 
 	ereport(ERROR,
index 5cce3f1242..7cdb40952b 100644
@@ -7395,6 +7396,14 @@ get_parameter(Param *param, deparse_context *context)
 		return;
 	}

+/*
+ * get_varname_varid
 
index 9b5869add8..c4e4d10c6a 100644
 
+	if (g_verbose)
index 36e3383b85..58d15af7b1 100644
@@ -3465,6 +3465,7 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
@@ -3664,7 +3665,8 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f0ea83e6a9..dd5f38e327 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -258,6 +258,7 @@ static void dumpPolicy(Archive *fout, PolicyInfo *polinfo);
 static void dumpPublication(Archive *fout, PublicationInfo *pubinfo);
 static void dumpPublicationTable(Archive *fout, PublicationRelInfo *pubrinfo);
 static void dumpSubscription(Archive *fout, SubscriptionInfo *subinfo);
+static void dumpVariable(Archive *fout, VariableInfo *varinfo);
 static void dumpDatabase(Archive *AH);
 static void dumpDatabaseConfig(Archive *AH, PQExpBuffer outbuf,
 				   const char *dbname, Oid dboid);
@@ -4224,6 +4225,222 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
+/*
+ * getVariables
+	int			i_vareoxaction;
+						  "v.vareoxaction, "
+						  "v.varnamespace, "
+	i_vareoxaction = PQfnumber(res, "vareoxaction");
+		varinfo[i].vareoxaction = pg_strdup(PQgetvalue(res, i, i_vareoxaction));
+	const char	   *vareoxaction;
+	vareoxaction = varinfo->vareoxaction;
+	if (strcmp(vareoxaction, "d") == 0)
+		appendPQExpBuffer(query, " ON TRANSACTION END DROP");
+	else if (strcmp(vareoxaction, "r") == 0)
+		appendPQExpBuffer(query, " ON TRANSACTION END RESET");
+	if (strcmp(vareoxaction, "R") == 0)
+		appendPQExpBuffer(query, " ON ROLLBACK RESET");
@@ -9791,6 +10008,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
@@ -17877,6 +18097,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
index 1448005f30..5471e667fc 100644
 
 /* component types of an object which can be selected for dumping */
@@ -625,6 +626,23 @@ typedef struct _SubscriptionInfo
 	char	   *subpublications;
 } SubscriptionInfo;
 
+/*
+	char	   *vareoxaction;
@@ -725,5 +743,6 @@ extern void getPublications(Archive *fout);
 
 #endif							/* PG_DUMP_H */
 
index 4ca0db1d0c..7a650af19b 100644
@@ -4198,6 +4198,85 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
 	return true;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
+					  "  pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\",\n"
+					  "  CASE v.vareoxaction\n"
+					  "    WHEN 'd' THEN 'ON TRANSACTION END DROP'\n"
+					  "    WHEN 'r' THEN 'ON TRANSACTION END RESET'\n"
+					  "    WHEN 'R' THEN 'ON ROLLBACK RESET' END as \"%s\"",
+					  gettext_noop("Default"),
+					  gettext_noop("Transaction end action"));
 
 /*
  * \dFp
 
+/* \dV */

 
 
 /*

 /* DISCARD */
 
 /* DO */
 	else if (Matches1("DO"))
 
+	/* DROP VARIABLE */
 
 /* FETCH && MOVE */

 /* PREPARE xx AS */
 
 /*

index 254fbef1f7..67ed04f351 100644

 
 
index aee49fdb6d..f84ea21c68 100644

 
index 0000000000..1e0ae6da53
@@ -0,0 +1,102 @@
+	char		vareoxaction;	/* action on transaction end */
+typedef enum VariableEOXActionCodes
+	VARIABLE_EOX_CODE_NOOP = 'n',			/* NOOP */
+	VARIABLE_EOX_CODE_DROP = 'd',			/* ON TRANSACTION END DROP */
+	VARIABLE_EOX_CODE_RESET = 'r',			/* ON TRANSACTION END RESET */
+	VARIABLE_EOX_CODE_ROLLBACK_RESET = 'R'	/* ON ROLLBACK RESET */
+} VariableEOXActionCodes;
+	VariableEOXAction eoxaction;
+									   VariableEOXAction eoxaction,
index 0000000000..b7e8221967
+extern void register_variable_on_commit_action(Oid varid, VariableEOXAction action);
+extern void SchemaVariablePreCommit_on_commit_actions(void);
+extern void AtEOXact_SchemaVariables_on_commit_actions(bool isCommit);

 
index c830f141b1..efdb51cbfe 100644

@@ -473,6 +475,7 @@ typedef struct ResultRelInfo
 typedef struct EState
 {
 	NodeTag		type;
+	bool		es_shared;		/* plpgsql uses share estate */
 
@@ -565,6 +568,14 @@ typedef struct EState

 	/*
index 07ab1a3dde..28a818337d 100644

 
 
@@ -2497,6 +2513,21 @@ typedef struct AlterSeqStmt
 	bool		missing_ok;		/* skip error if a role is missing? */
 } AlterSeqStmt;
 
+	VariableEOXAction eoxaction;	/* on commit action */
@@ -3238,7 +3269,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;

 

 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4b0d75af..d3bdebac98 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -43,15 +43,26 @@ 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) */
 	ONCOMMIT_PRESERVE_ROWS,		/* ON COMMIT PRESERVE ROWS (do nothing) */
 	ONCOMMIT_DELETE_ROWS,		/* ON COMMIT DELETE ROWS */
-	ONCOMMIT_DROP				/* ON COMMIT DROP */
+	ONCOMMIT_DROP,				/* ON COMMIT DROP */
 } OnCommitAction;
 
+typedef enum VariableEOXAction
+{
+	VARIABLE_EOX_NOOP,			/* Do nothing */
+	VARIABLE_EOX_DROP,			/* ON TRANSACTION END DROP */
+	VARIABLE_EOX_RESET,			/* ON TRANSACTION END RESET */
+	VARIABLE_EOX_ROLLBACK_RESET	/* ON ROLLBACK RESET */
+} VariableEOXAction;
+
 /*
  * RangeVar - range variable, used in FROM clauses
  *
@@ -229,13 +240,17 @@ typedef struct Const
  *				of the `paramid' field contain the SubLink's subLinkId, and
  *				the low-order 16 bits contain the column number.  (This type
  *				of Param is also converted to PARAM_EXEC during planning.)
+ *
+ *		PARAM_VARIABLE:  The parameter is a access to schema variable
+ *				paramid holds varid.
  */
 typedef enum ParamKind
 {
 	PARAM_EXTERN,
 	PARAM_EXEC,
 	PARAM_SUBLINK,
-	PARAM_MULTIEXPR
+	PARAM_MULTIEXPR,
+	PARAM_VARIABLE
 } ParamKind;
 
 typedef struct Param

 /* ----------------

 /*

 

 

 
index 0000000000..d38b0a686f
@@ -0,0 +1,428 @@
+-- using special behave that depends on transactions
+CREATE VARIABLE t1 AS int DEFAULT -1 ON TRANSACTION END RESET;
+  SELECT t1;
+ t1 
+ -1
+  LET t1 = 100;
+  SELECT t1;
+ t1  
+ 100
+SELECT t1;
+ t1 
+ -1
+DROP VARIABLE t1;
+CREATE VARIABLE t1 AS int DEFAULT -1 ON ROLLBACK RESET;
+  SELECT t1;
+ t1 
+ -1
+  LET t1 = 100;
+  SELECT t1;
+ t1  
+ 100
+SELECT t1;
+ t1  
+ 100
+  LET t1 = 1000;
+  SELECT t1;
+  t1  
+ROLLBACK;
+SELECT t1;
+ t1 
+ -1
+DROP VARIABLE t1;
 
 # ----------
index 0000000000..4350ef893c
@@ -0,0 +1,290 @@
+-- using special behave that depends on transactions
+
+CREATE VARIABLE t1 AS int DEFAULT -1 ON TRANSACTION END RESET;
+
+BEGIN;
+  SELECT t1;
+  LET t1 = 100;
+  SELECT t1;
+COMMIT;
+
+SELECT t1;
+
+DROP VARIABLE t1;
+
+CREATE VARIABLE t1 AS int DEFAULT -1 ON ROLLBACK RESET;
+
+BEGIN;
+  SELECT t1;
+  LET t1 = 100;
+  SELECT t1;
+COMMIT;
+
+SELECT t1;
+
+BEGIN;
+  LET t1 = 1000;
+  SELECT t1;
+ROLLBACK;
+
+SELECT t1;
+
+DROP VARIABLE t1;

---

From: Fabien COELHO <[email-masked]>
Date: Fri, 7 Sep 2018 14:34:04 +0200 (CEST)

Hello Pavel,

> CREATE VARIABLE foo int ON TRANSACTION END RESET;

With this option I understand that it is a "within a transactionnal" 
variable, i.e. when the transaction ends, whether commit or rollback, the 
variable is reset to a default variable. It is not really a "session" 
variable anymore, each transaction has its own value.

  -- begin session
  -- foo has default value, eg NULL
  BEGIN;
     LET foo = 1;
  COMMIT/ROLLBACK;
  -- foo has default value again, NULL

> CREATE VARIABLE foo int ON ROLLBACK RESET

That is a little bit safer and you are back to a SESSION-scope variable, 
which is reset to the default value if the (any) transaction fails?

   -- begin session
   -- foo has default value, eg NULL
   BEGIN;
     LET foo = 1;
   COMMIT;
   -- foo has value 1
   BEGIN;
     -- foo has value 1...
   ROLLBACK;
   -- foo has value NULL

c) A more logical (from a transactional point of view - but not necessary 
simple to implement, I do not know) feature/variant would be to reset the 
value to the one it had at the beginning of the transaction, which is not 
necessarily the default.

   -- begin session
   -- foo has default value, eg NULL
   BEGIN;
     LET foo = 1;
   COMMIT;
   -- foo has value 1
   BEGIN;
     LET foo = 2; (*)
     -- foo has value 2
   ROLLBACK;
   -- foo has value 1 back, change (*) has been reverted

> fails).

Indeed.

> The @a has sense - the behave is global value (what is not possible

Indeed.

> didn't implement @b, then ON COMMIT syntax can be used.

I was more arguing on the third (c) option, i.e. on rollback the value is 
reverted to its value at the beginning of the rollbacked transaction.

At the minimum, ISTM that option (b) is enough to implement the audit 
pattern, but it would mean that any session which has a rollback, for any 
reason (deadlock, serialization...), would have to be reinitialized, which 
would be a drawback.

The to options could be non-transactional session variables "ON ROLLBACK 
DO NOT RESET/DO NOTHING", and somehow transactional session variables "ON 
ROLLBACK RESET TO DEFAULT" (b) or "ON ROLLBACK RESET TO INITIAL" (c).

-- 
Fabien.

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 7 Sep 2018 16:28:00 +0200

> variable anymore, each transaction has its own value.

yes, the correct name should be "schema variable with transaction scope". I
think it can be useful like short life global variable. These variables can
works like transaction caches.

> ROLLBACK RESET TO DEFAULT" (b) or "ON ROLLBACK RESET TO INITIAL" (c).

@b is hardly understandable for not trained people, because any rollback in
session does reset. But people expecting @c, or some near @c.

I understand so you talked about @c. Now I think so it is possible to
implement, but it is not trivial. The transactional behave have to
calculate not only with transactions, but with SAVEPOINTS and ROLLBACK TO
savepoints. On second hand, the implementation will be relatively compact.

I'll hold it in my memory, but there are harder issues (support for
parallelism).

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 14 Sep 2018 23:31:16 +0200

> explain (costs off) select count(*) from foo where a%10 = zero;

This issue should be fixed in attached patch (and more others).

The code is more cleaner now, there are more tests, and documentation is
mostly complete. I am sorry - my English is not good.
New features:

o ON COMMIT DROP and ON TRANSACTION END RESET -- remove temp variable on
commit, reset variable on transaction end (commit, rollback)
o LET var = DEFAULT -- reset specified variable

Regards

Pavel

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0179deea2e..13043987ff 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -359,6 +359,11 @@
       <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
       <entry>mappings of users to foreign servers</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-variable"><structname>pg_variable</structname></link></entry>
+      <entry>schema variables</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -11303,4 +11308,124 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
 
  </sect1>
 
+      <entry><structfield>vareoxaction</structfield></entry>
+      <entry><type>char</type></entry>
+       <literal>n</literal> = no action, <literal>d</literal> = drop variable,
+       <literal>r</literal> = reset variable
index 0000000000..bcb6824711
+CREATE { TEMPORARY | TEMP } VARIABLE [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> [ AS ] <replaceable class="parameter">data_type</replaceable> ] [ COLLATE <replaceable class="parameter">collation</replaceable> ]
+    [ DEFAULT <replaceable class="parameter">default_expr</replaceable> ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
+    <term><literal>DEFAULT <replaceable>default_expr</replaceable></literal></term>
+      The <literal>DEFAULT</literal> clause assigns a default data for
+      schema variable.
+    <term><literal>ON COMMIT DROP</literal>, <literal>ON TRANSACTION END RESET</literal></term>
+      The <literal>ON COMMIT DROP</literal> clause specify the bahaviour of
+      temporary schema variable at commit of transaction. It is allowed only
+      for temporal variables, and enforce drop variable at commit time. The
+      <literal>ON TRANSACTION END RESET</literal> enforce reset to default
+      value at transaction end (<literal>COMMIT</literal>, <literal>ROLLBACK</literal>).
 
  <refsynopsisdiv>
 <synopsis>

+   <varlistentry>

 
+    <varlistentry>
 
index 0000000000..299cfdf413
@@ -0,0 +1,104 @@
+LET <replaceable class="parameter">schema_variable</replaceable> = DEFAULT
+    <term><literal>DEFAULT</literal></term>
+      Ensure reset schema variable to default value if it is defined.
+      When there are not assigned default value, then value of schema
+      variable will be null.
+LET myvar = DEFAULT;
 
index 875be180fe..adc91a4238 100644
@@ -1978,6 +1979,9 @@ CommitTransaction(void)
 
+	/* Let ON COMMIT DROP or ON TRANSACTION END */
+	AtPreEOXact_SchemaVariable_on_commit_actions(true);
+
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
@@ -2102,6 +2106,7 @@ CommitTransaction(void)
 	AtEOXact_GUC(true, 1);
 	AtEOXact_SPI(true);
 	AtEOXact_on_commit_actions(true);
+	AtEOXact_SchemaVariable_on_commit_actions(true);
 	AtEOXact_Namespace(true, is_parallel_worker);
 	AtEOXact_SMgr();
 	AtEOXact_Files(true);
@@ -2519,6 +2524,9 @@ AbortTransaction(void)
 	AfterTriggerEndXact(false); /* 'false' means it's abort */
 	AtAbort_Portals();
 	AtEOXact_LargeObject(false);
+
+	/* 'false' means it's abort */
+	AtPreEOXact_SchemaVariable_on_commit_actions(false);
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
@@ -2582,6 +2590,7 @@ AbortTransaction(void)
 		AtEOXact_GUC(false, 1);
 		AtEOXact_SPI(false);
 		AtEOXact_on_commit_actions(false);
+		AtEOXact_SchemaVariable_on_commit_actions(false);
 		AtEOXact_Namespace(false, is_parallel_worker);
 		AtEOXact_SMgr();
 		AtEOXact_Files(false);
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 0865240f11..1f7c4d1223 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -19,7 +19,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_publication.o pg_range.o \
 	   pg_db_role_setting.o pg_shdepend.o pg_subscription.o pg_type.o \
-	   storage.o toasting.o
+	   pg_variable.o storage.o toasting.o

 

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

 	/* shouldn't get here */
index 5d13e6a3d7..e47caa356d 100644
@@ -755,6 +756,69 @@ RelationIsVisible(Oid relid)
 	return visible;
 }
 
+/*
+ * VariableIsVisible
 
 /*
  * TypenameGetTypid
@@ -2776,6 +2840,202 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
+/*
 
 /*
  * DeconstructQualifiedName
@@ -4490,3 +4750,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..5372f30f31
@@ -0,0 +1,352 @@

 

 	/*

 			/*
index 0000000000..3d21dddbf8
@@ -0,0 +1,753 @@
+	TransactionId creating_xid;
+	 * If this entry was created during the current transaction,
+	 * creating_subid is the ID of the creating subxact; if created in a prior
+	 * transaction, creating_subid is zero.  If deleted during the current
+	 * transaction, deleting_subid is the ID of the deleting subxact; if no
+	 * deletion request is pending, deleting_subid is zero.
+	SubTransactionId creating_subid;
+	SubTransactionId deleting_subid;
+	variable = VariableCreate(stmt->variable->relname,
+	 * We must bump the command counter to make the newly-created variable
+	 * tuple visible for any other operations.
+ * Returns copy of value specified schema variable
+CopySchemaVariable(Oid varid, bool *isNull, Oid *typid)
+	*typid = svar->typid;
+ * Reset to default specified schema variable
+doLetStmtReset(PlannedStmt *pstmt)
+	clean_cache_varid(pstmt->resultVariable);
+ * Assign result of evaluated expression to schema variable
+doLetStmtEval(PlannedStmt *pstmt,
+	oc->creating_subid = GetCurrentSubTransactionId();
+	oc->deleting_subid = InvalidSubTransactionId;
+			oc->deleting_subid = GetCurrentSubTransactionId();
+ * Perform ON TRANSACTION END RESET or ON COMMIT DROP
+AtPreEOXact_SchemaVariable_on_commit_actions(bool isCommit)
+		/* Ignore entry if already dropped in this xact */
+		if (oc->deleting_subid != InvalidSubTransactionId)
+					 * ON COMMIT DROP is allowed only for temp schema variables.
+					 * So we should explicit delete only when current transaction
+					 * was committed. When is rollback, then schema variable is
+					 * removed automatically.
+					if (isCommit)
+ * All we do here is remove no-longer-needed OnCommitItem entries.
+ * During commit, remove entries that were deleted during this transaction;
+ * during abort, remove those created during this transaction.
+AtEOXact_SchemaVariable_on_commit_actions(bool isCommit)
+		if (isCommit ? oc->deleting_subid != InvalidSubTransactionId :
+			oc->creating_subid != InvalidSubTransactionId)
+			oc->creating_subid = InvalidSubTransactionId;
+			oc->deleting_subid = InvalidSubTransactionId;
index e96512e051..0bd9b240b4 100644
 
 				/*
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index e284fd71d7..6ad95028cf 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -33,6 +33,7 @@
 #include "access/nbtree.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_type.h"
+#include "commands/schemavariable.h"
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
@@ -736,6 +737,56 @@ ExecInitExprRec(Expr *node, ExprState *state,
 						scratch.d.param.paramtype = param->paramtype;
 						ExprEvalPushStep(state, &scratch);
 						break;
+
+					case PARAM_VARIABLE:
+						{
+							int			es_num_schema_variables = 0;
+							SchemaVariableValue *es_schema_variables = NULL;
+
+							if (state->parent && state->parent->state)
+							{
+								es_schema_variables = state->parent->state->es_schema_variables;
+								es_num_schema_variables = state->parent->state->es_num_schema_variables;
+							}
+
+							/*
+							 * We should to use schema variable buffer, when it is
+							 * available.
+							 */
+							if (es_schema_variables)
+							{
+								SchemaVariableValue *var;
+
+								/* check params, unexpected */
+								if (param->paramid >= es_num_schema_variables)
+									elog(ERROR, "paramid of PARAM_VARIABLE param is out of range");
+
+								var = &es_schema_variables[param->paramid];
+
+								/* unexpected */
+								if (var->typid != param->paramtype)
+									elog(ERROR, "type of buffered value is different than PARAM_VARIABLE type");
+
+								/* In this case, the parameter is like a constant */
+								scratch.opcode = EEOP_CONST;
+								scratch.d.constval.value = var->value;
+								scratch.d.constval.isnull = var->isnull;
+								ExprEvalPushStep(state, &scratch);
+							}
+							else
+							{
+								/*
+								 * When we have not a full PlanState (plpgsql simple
+								 * expr evaluation, then we should to use direct access.
+								 */
+								scratch.opcode = EEOP_PARAM_VARIABLE;
+								scratch.d.vparam.varid = param->paramvarid;
+								scratch.d.vparam.vartype = param->paramtype;
+								ExprEvalPushStep(state, &scratch);
+							}
+						}
+						break;
+
 					case PARAM_EXTERN:
 
 						/*
index 9d6e25aae5..5dc22ee1c7 100644
@@ -1007,6 +1009,16 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_PARAM_VARIABLE)
+		{
+			/* direct access to schema variable (without buffering) */
+			*op->resvalue = GetSchemaVariable(op->d.vparam.varid,
+													  op->resnull,
+													  op->d.vparam.vartype,
+													  true);
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_CASE_TESTVAL)
 		{
 			/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index c583e020a0..70d1fcdaa7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -43,9 +43,12 @@
 #include "access/xact.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_publication.h"
+#include "catalog/pg_variable.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
+#include "commands/schemavariable.h"
 #include "executor/execdebug.h"
+#include "executor/svariableReceiver.h"
 #include "foreign/fdwapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
@@ -199,17 +202,65 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 
 	estate->es_sourceText = queryDesc->sourceText;
 
+	/*
+	 * Prepare schema variables, if are not prepared in queryDesc
+	 */
+	if (queryDesc->num_schema_variables > 0)
+	{
+		/* When buffer of used schema variables loaded from shared memory */
+		estate->es_schema_variables = queryDesc->schema_variables;
+		estate->es_num_schema_variables = queryDesc->num_schema_variables;
+	}
+	else if (queryDesc->plannedstmt->schemaVariables)
+	{
+		ListCell *lc;
+		int		nSchemaVariables;
+		int		i = 0;
+
+		nSchemaVariables = list_length(queryDesc->plannedstmt->schemaVariables);
+
+		/* Create buffer for used schema variables */
+		estate->es_schema_variables = (SchemaVariableValue *)
+						palloc(nSchemaVariables * sizeof(SchemaVariableValue));
+
+		foreach(lc, queryDesc->plannedstmt->schemaVariables)
+		{
+			AclResult	aclresult;
+			Oid		varid = lfirst_oid(lc);
+
+			aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_READ);
+			if (aclresult != ACLCHECK_OK)
+						aclcheck_error(aclresult, OBJECT_VARIABLE,
+													schema_variable_get_name(varid));
+
+			estate->es_schema_variables[i].varid = varid;
+			estate->es_schema_variables[i].value = CopySchemaVariable(varid,
+												&estate->es_schema_variables[i].isnull,
+												&estate->es_schema_variables[i].typid);
+
+			i++;
+		}
+
+		estate->es_num_schema_variables = nSchemaVariables;
+	}
+
 	/*
 	 * Fill in the query environment, if any, from queryDesc.
 	 */
 	estate->es_queryEnv = queryDesc->queryEnv;
 
+	/*
 
 			/*
 			 * SELECT FOR [KEY] UPDATE/SHARE and modifying CTEs need to mark
@@ -345,6 +396,7 @@ standard_ExecutorRun(QueryDesc *queryDesc,
 	estate->es_lastoid = InvalidOid;

 	if (sendTuples)
@@ -924,6 +976,17 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 		estate->es_num_root_result_relations = 0;
 	}
 
+	if (OidIsValid(estate->es_result_variable))
+	{
+		AclResult	aclresult;
+		Oid			varid = estate->es_result_variable;
+
+		/* Ensure this variable is writeable */
+		aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error(aclresult, OBJECT_VARIABLE, schema_variable_get_name(varid));
+	}
+
 	/*
 	 * Similarly, we have to lock relations selected FOR [KEY] UPDATE/SHARE
 	 * before we initialize the plan tree, else we'd be risking lock upgrades.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index ee0f07a81e..be36fbb011 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 usage info, and
- * the actual plan to be passed down to the worker.
+ * any ParamListInfo associated with the query, buffer usage info, used
+ * schema variables buffer, and the actual plan to be passed down to the
+ * worker.
  *
  * IDENTIFICATION
  *	  src/backend/executor/execParallel.c
@@ -62,6 +63,7 @@
 #define PARALLEL_KEY_INSTRUMENTATION	UINT64CONST(0xE000000000000006)
 #define PARALLEL_KEY_DSA				UINT64CONST(0xE000000000000007)
 #define PARALLEL_KEY_QUERY_TEXT		UINT64CONST(0xE000000000000008)
+#define PARALLEL_KEY_SCHEMA_VARIABLES	UINT64CONST(0xE000000000000009)
 
 #define PARALLEL_TUPLE_QUEUE_SIZE		65536
 
@@ -136,6 +138,12 @@ static bool ExecParallelRetrieveInstrumentation(PlanState *planstate,
 /* Helper function that runs in the parallel worker. */
 static DestReceiver *ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc);
 
+/* Helper functions that can pass values of used schema variables */
+static Size EstimateSchemaVariables(EState *estate);
+static void SerializeSchemaVariables(EState *estate, char **start_address);
+static SchemaVariableValue *RestoreSchemaVariables(char **start_address,
+								int *num_schema_variables);
+
 /*
  * Create a serialized representation of the plan to be sent to each worker.
  */
@@ -571,12 +579,14 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	char	   *pstmt_data;
 	char	   *pstmt_space;
 	char	   *paramlistinfo_space;
+	char	   *schema_variables_space;
 	BufferUsage *bufusage_space;
 	SharedExecutorInstrumentation *instrumentation = NULL;
 	int			pstmt_len;
 	int			paramlistinfo_len;
 	int			instrumentation_len = 0;
 	int			instrument_offset = 0;
+	int			schema_variables_len = 0;
 	Size		dsa_minsize = dsa_minimum_size();
 	char	   *query_string;
 	int			query_len;
@@ -622,6 +632,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	shm_toc_estimate_chunk(&pcxt->estimator, paramlistinfo_len);
 	shm_toc_estimate_keys(&pcxt->estimator, 1);
 
+	/* Estimate space for serialized schema variables. */
+	schema_variables_len = EstimateSchemaVariables(estate);
+	shm_toc_estimate_chunk(&pcxt->estimator, schema_variables_len);
+	shm_toc_estimate_keys(&pcxt->estimator, 1);
+
 	/*
 	 * Estimate space for BufferUsage.
 	 *
@@ -699,6 +714,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 schema variables. */
+	schema_variables_space = shm_toc_allocate(pcxt->toc, schema_variables_len);
+	shm_toc_insert(pcxt->toc, PARALLEL_KEY_SCHEMA_VARIABLES, schema_variables_space);
+	SerializeSchemaVariables(estate, &schema_variables_space);
+
 	/* Allocate space for each worker's BufferUsage; no need to initialize. */
 	bufusage_space = shm_toc_allocate(pcxt->toc,
 									  mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -1099,6 +1119,7 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver,
 {
 	char	   *pstmtspace;
 	char	   *paramspace;
+	char	   *schemavariablespace;
 	PlannedStmt *pstmt;
 	ParamListInfo paramLI;
 	char	   *queryString;
@@ -1262,6 +1283,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 	SharedExecutorInstrumentation *instrumentation;
 	int			instrument_options = 0;
 	void	   *area_space;
+	char	   *schemavariable_space;
 	dsa_area   *area;
 	ParallelWorkerContext pwcxt;
 
@@ -1285,6 +1307,14 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 	area_space = shm_toc_lookup(toc, PARALLEL_KEY_DSA, false);
 	area = dsa_attach_in_place(area_space, seg);
 
+	/* Reconstruct schema variables. */
+	schemavariable_space = shm_toc_lookup(toc,
+										  PARALLEL_KEY_SCHEMA_VARIABLES,
+										  false);
+	queryDesc->schema_variables =
+					RestoreSchemaVariables(&schemavariable_space,
+											&queryDesc->num_schema_variables);
+
 	/* Start up the executor */
 	queryDesc->plannedstmt->jitFlags = fpes->jit_flags;
 	ExecutorStart(queryDesc, fpes->eflags);
@@ -1344,3 +1374,118 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
 	FreeQueryDesc(queryDesc);
 	receiver->rDestroy(receiver);
 }
+
+/*
+ * Estimate the amount of space required to serialize a used
+ * schema variables.
+ */
+static Size
+EstimateSchemaVariables(EState *estate)
+{
+	int			i;
+	Size		sz = sizeof(int);
+
+	if (estate->es_schema_variables == NULL)
+		return sz;
+
+	for (i = 0; i < estate->es_num_schema_variables; i++)
+	{
+		SchemaVariableValue	   *svarval;
+		Oid			typeOid;
+		int16		typLen;
+		bool		typByVal;
+
+		svarval = &estate->es_schema_variables[i];
+
+		typeOid = svarval->typid;
+
+		sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+		/* space for datum/isnull */
+		Assert(OidIsValid(typeOid));
+		get_typlenbyval(typeOid, &typLen, &typByVal);
+
+		sz = add_size(sz,
+					  datumEstimateSpace(svarval->value, svarval->isnull, typByVal, typLen));
+	}
+
+	return sz;
+}
+
+/*
+ * Serialize a schema variables buffer into caller-provided storage.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn.  The details for each parameter
+ * consist of a 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().  The caller is responsible for ensuring that there is
+ * enough storage to store the number of bytes that will be written; use
+ * EstimateSchemaVariables to find out how many will be needed.
+ * *start_address is updated to point to the byte immediately following those
+ * written.
+ *
+ * RestoreSchemaVariables can be used to recreate a schema variable buffer
+ * based on the serialized representation;
+ */
+static void
+SerializeSchemaVariables(EState *estate, char **start_address)
+{
+	int			nparams;
+	int			i;
+
+	/* Write number of parameters. */
+	nparams = estate->es_num_schema_variables;
+	memcpy(*start_address, &nparams, sizeof(int));
+	*start_address += sizeof(int);
+
+	/* Write each parameter in turn. */
+	for (i = 0; i < nparams; i++)
+	{
+		SchemaVariableValue	   *svarval;
+		Oid			typeOid;
+		int16		typLen;
+		bool		typByVal;
+
+		svarval = &estate->es_schema_variables[i];
+		typeOid = svarval->typid;
+
+		/* Write type OID. */
+		memcpy(*start_address, &typeOid, sizeof(Oid));
+		*start_address += sizeof(Oid);
+
+		Assert(OidIsValid(typeOid));
+		get_typlenbyval(typeOid, &typLen, &typByVal);
+
+		datumSerialize(svarval->value, svarval->isnull, typByVal, typLen,
+					   start_address);
+	}
+}
+
+static SchemaVariableValue *
+RestoreSchemaVariables(char **start_address, int *num_schema_variables)
+{
+	SchemaVariableValue *schema_variables;
+	int			i;
+	int			nparams;
+
+	memcpy(&nparams, *start_address, sizeof(int));
+	*start_address += sizeof(int);
+
+	*num_schema_variables = nparams;
+	schema_variables = (SchemaVariableValue *)
+					palloc(nparams * sizeof(SchemaVariableValue));
+
+	for (i = 0; i < nparams; i++)
+	{
+		SchemaVariableValue *svarval = &schema_variables[i];
+
+		/* Read type OID. */
+		memcpy(&svarval->typid, *start_address, sizeof(Oid));
+		*start_address += sizeof(Oid);
+
+		/* Read datum/isnull. */
+		svarval->value = datumRestore(start_address, &svarval->isnull);
+	}
+
+	return schema_variables;
+}
diff --git a/src/backend/executor/svariableReceiver.c b/src/backend/executor/svariableReceiver.c
new file mode 100644
index 0000000000..0eac4b5d0c
--- /dev/null
+++ b/src/backend/executor/svariableReceiver.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.c
+ *	  An implementation of DestReceiver that stores the result value in
+ *	  a schema variable.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/svariableReceiver.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/tuptoaster.h"
+#include "executor/svariableReceiver.h"
+#include "commands/schemavariable.h"
+
+typedef struct
+{
+	DestReceiver pub;
+	Oid		varid;
+	Oid		typid;
+	int32	typmod;
+	int		typlen;
+	int		slot_offset;
+	int		rows;
+} svariableState;
+
+
+/*
+ * Prepare to receive tuples from executor.
+ */
+static void
+svariableStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+	svariableState *myState = (svariableState *) self;
+	int			natts = typeinfo->natts;
+	int			outcols = 0;
+	int			i;
+
+	for (i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+
+		if (attr->attisdropped)
+			continue;
+
+		if (++outcols > 1)
+			elog(ERROR, "svariable DestReceiver can take only one attribute");
+
+		myState->typid = attr->atttypid;
+		myState->typmod = attr->atttypmod;
+		myState->typlen = attr->attlen;
+		myState->slot_offset = i;
+	}
+
+	myState->rows = 0;
+}
+
+/*
+ * Receive a tuple from the executor and store it in schema variable.
+ */
+static bool
+svariableReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
+{
+	svariableState *myState = (svariableState *) self;
+	Datum		value;
+	bool		isnull;
+	bool		freeval = false;
+
+	/* Make sure the tuple is fully deconstructed */
+	slot_getallattrs(slot);
+
+	value = slot->tts_values[myState->slot_offset];
+	isnull = slot->tts_isnull[myState->slot_offset];
+
+	if (myState->typlen == -1 && !isnull && VARATT_IS_EXTERNAL(DatumGetPointer(value)))
+	{
+		value = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *)
+													DatumGetPointer(value)));
+		freeval = true;
+	}
+
+	SetSchemaVariable(myState->varid, value, isnull, myState->typid, myState->typmod);
+
+	if (freeval)
+		pfree(DatumGetPointer(value));
+
+	return true;
+}
+
+/*
+ * Clean up at end of an executor run
+ */
+static void
+svariableShutdownReceiver(DestReceiver *self)
+{
+	/* Do nothing */
+}
+
+/*
+ * Destroy receiver when done with it
+ */
+static void
+svariableDestroyReceiver(DestReceiver *self)
+{
+	pfree(self);
+}
+
+/*
+ * Initially create a DestReceiver object.
+ */
+DestReceiver *
+CreateVariableDestReceiver(void)
+{
+	svariableState *self = (svariableState *) palloc0(sizeof(svariableState));
+
+	self->pub.receiveSlot = svariableReceiveSlot;
+	self->pub.rStartup = svariableStartupReceiver;
+	self->pub.rShutdown = svariableShutdownReceiver;
+	self->pub.rDestroy = svariableDestroyReceiver;
+	self->pub.mydest = DestVariable;
+
+	/* private fields will be set by SetVariableDestReceiverParams */
+
+	return (DestReceiver *) self;
+}
+
+/*
+ * Set parameters for a VariableDestReceiver
+ */
+void
+SetVariableDestReceiverParams(DestReceiver *self, Oid varid)
+{
+	svariableState *myState = (svariableState *) self;
+
+	Assert(myState->pub.mydest == DestVariable);
+	Assert(OidIsValid(varid));
+
+	myState->varid = varid;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7c8220cf65..523db538aa 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -93,6 +93,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(nonleafResultRelations);
 	COPY_NODE_FIELD(rootResultRelations);
+	COPY_SCALAR_FIELD(resultVariable);
 	COPY_NODE_FIELD(subplans);
 	COPY_BITMAPSET_FIELD(rewindPlanIDs);
 	COPY_NODE_FIELD(rowMarks);
@@ -100,6 +101,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_NODE_FIELD(invalItems);
 	COPY_NODE_FIELD(paramExecTypes);
 	COPY_NODE_FIELD(utilityStmt);
+	COPY_NODE_FIELD(schemaVariables);
 	COPY_LOCATION_FIELD(stmt_location);
 	COPY_LOCATION_FIELD(stmt_len);
 
@@ -1415,6 +1417,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;
@@ -3000,6 +3003,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);
@@ -3009,6 +3013,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(hasModifyingCTE);
 	COPY_SCALAR_FIELD(hasForUpdate);
 	COPY_SCALAR_FIELD(hasRowSecurity);
+	COPY_SCALAR_FIELD(hasSchemaVariable);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
 	COPY_NODE_FIELD(jointree);
@@ -3118,6 +3123,18 @@ _copySelectStmt(const SelectStmt *from)
 	return newnode;
 }
 
+static LetStmt *
+_copyLetStmt(const LetStmt *from)
+{
+	LetStmt *newnode = makeNode(LetStmt);
+
+	COPY_NODE_FIELD(target);
+	COPY_NODE_FIELD(selectStmt);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 static SetOperationStmt *
 _copySetOperationStmt(const SetOperationStmt *from)
 {
@@ -5166,6 +5183,9 @@ copyObjectImpl(const void *from)
 		case T_SelectStmt:
 			retval = _copySelectStmt(from);
 			break;
+		case T_LetStmt:
+			retval = _copyLetStmt(from);
+			break;
 		case T_SetOperationStmt:
 			retval = _copySetOperationStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 378f2facb8..2ea5e18b1a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -202,6 +202,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;
@@ -949,6 +950,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);
@@ -958,6 +960,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(hasModifyingCTE);
 	COMPARE_SCALAR_FIELD(hasForUpdate);
 	COMPARE_SCALAR_FIELD(hasRowSecurity);
+	COMPARE_SCALAR_FIELD(hasSchemaVariable);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
 	COMPARE_NODE_FIELD(jointree);
@@ -1057,6 +1060,16 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 	return true;
 }
 
+static bool
+_equalLetStmt(const LetStmt *a, const LetStmt *b)
+{
+	COMPARE_NODE_FIELD(target);
+	COMPARE_NODE_FIELD(selectStmt);
+
+	return true;
+}
+
+
 static bool
 _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
 {
@@ -3225,6 +3238,9 @@ equal(const void *a, const void *b)
 		case T_SelectStmt:
 			retval = _equalSelectStmt(a, b);
 			break;
+		case T_LetStmt:
+			retval = _equalLetStmt(a, b);
+			break;
 		case T_SetOperationStmt:
 			retval = _equalSetOperationStmt(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b5af904c18..9eb3e3f3bb 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -278,6 +278,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(nonleafResultRelations);
 	WRITE_NODE_FIELD(rootResultRelations);
+	WRITE_OID_FIELD(resultVariable);
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(rowMarks);
@@ -285,6 +286,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_NODE_FIELD(invalItems);
 	WRITE_NODE_FIELD(paramExecTypes);
 	WRITE_NODE_FIELD(utilityStmt);
+	WRITE_NODE_FIELD(schemaVariables);
 	WRITE_LOCATION_FIELD(stmt_location);
 	WRITE_LOCATION_FIELD(stmt_len);
 }
@@ -1194,6 +1196,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);
 }
 
@@ -2260,6 +2263,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(relationOids);
 	WRITE_NODE_FIELD(invalItems);
 	WRITE_NODE_FIELD(paramExecTypes);
+	WRITE_NODE_FIELD(schemaVariables);
 	WRITE_UINT_FIELD(lastPHId);
 	WRITE_UINT_FIELD(lastRowMarkId);
 	WRITE_INT_FIELD(lastPlanNodeId);
@@ -2316,6 +2320,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
 	WRITE_BOOL_FIELD(hasHavingQual);
 	WRITE_BOOL_FIELD(hasPseudoConstantQuals);
 	WRITE_BOOL_FIELD(hasRecursion);
+	WRITE_BOOL_FIELD(hasSchemaVariable);
 	WRITE_INT_FIELD(wt_param_id);
 	WRITE_BITMAPSET_FIELD(curOuterRels);
 	WRITE_NODE_FIELD(curOuterParams);
@@ -2794,6 +2799,16 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
 	WRITE_NODE_FIELD(rarg);
 }
 
+static void
@@ -2972,6 +2987,7 @@ _outQuery(StringInfo str, const Query *node)
 
 	WRITE_INT_FIELD(resultRelation);
+	WRITE_INT_FIELD(resultVariable);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasWindowFuncs);
 	WRITE_BOOL_FIELD(hasTargetSRFs);
@@ -2981,6 +2997,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(hasModifyingCTE);
 	WRITE_BOOL_FIELD(hasForUpdate);
 	WRITE_BOOL_FIELD(hasRowSecurity);
+	WRITE_BOOL_FIELD(hasSchemaVariable);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
 	WRITE_NODE_FIELD(jointree);
@@ -4192,6 +4209,9 @@ outNode(StringInfo str, const void *obj)
 			case T_SelectStmt:
 				_outSelectStmt(str, obj);
 				break;
+			case T_LetStmt:
+				_outLetStmt(str, obj);
+				break;
 			case T_ColumnDef:
 				_outColumnDef(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3254524223..3ea7e47387 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -242,6 +242,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
+	READ_INT_FIELD(resultVariable);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasWindowFuncs);
 	READ_BOOL_FIELD(hasTargetSRFs);
@@ -251,6 +252,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(hasModifyingCTE);
 	READ_BOOL_FIELD(hasForUpdate);
 	READ_BOOL_FIELD(hasRowSecurity);
+	READ_BOOL_FIELD(hasSchemaVariable);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
 	READ_NODE_FIELD(jointree);
@@ -571,6 +573,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();
@@ -1485,6 +1488,7 @@ _readPlannedStmt(void)
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(nonleafResultRelations);
 	READ_NODE_FIELD(rootResultRelations);
+	READ_OID_FIELD(resultVariable);
 	READ_NODE_FIELD(subplans);
 	READ_BITMAPSET_FIELD(rewindPlanIDs);
 	READ_NODE_FIELD(rowMarks);
@@ -1492,6 +1496,7 @@ _readPlannedStmt(void)
 	READ_NODE_FIELD(invalItems);
 	READ_NODE_FIELD(paramExecTypes);
 	READ_NODE_FIELD(utilityStmt);
+	READ_NODE_FIELD(schemaVariables);
 	READ_LOCATION_FIELD(stmt_location);
 	READ_LOCATION_FIELD(stmt_len);
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index e589471fee..c0eb222296 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -301,6 +301,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPlanNodeId = 0;
 	glob->transientPlan = false;
 	glob->dependsOnRole = false;
+	glob->schemaVariables = NIL;
 
 	/*
 	 * Assess whether it's feasible to use parallel mode for this query. We
@@ -334,7 +335,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	 */
 	if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
 		IsUnderPostmaster &&
-		parse->commandType == CMD_SELECT &&
+		(parse->commandType == CMD_SELECT ||
+		 parse->commandType == CMD_PLAN_UTILITY) &&
 		!parse->hasModifyingCTE &&
 		max_parallel_workers_per_gather > 0 &&
 		!IsParallelWorker() &&
@@ -520,6 +522,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->resultRelations = glob->resultRelations;
 	result->nonleafResultRelations = glob->nonleafResultRelations;
 	result->rootResultRelations = glob->rootResultRelations;
+	result->resultVariable = parse->resultVariable;
 	result->subplans = glob->subplans;
 	result->rewindPlanIDs = glob->rewindPlanIDs;
 	result->rowMarks = glob->finalrowmarks;
@@ -528,6 +531,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->paramExecTypes = glob->paramExecTypes;
 	/* utilityStmt should be null, but we might as well copy it */
 	result->utilityStmt = parse->utilityStmt;
+	result->schemaVariables = glob->schemaVariables;
 	result->stmt_location = parse->stmt_location;
 	result->stmt_len = parse->stmt_len;
 
@@ -659,6 +663,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	 */
 	pull_up_subqueries(root);
 
+	/*
+	 * Check if some subquery uses schema variable. Flag hasSchemaVariable
+	 * should be true if query or some subquery uses any schema variable.
+	 */
+	pull_up_has_schema_variable(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
@@ -2172,7 +2182,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 		 * If this is an INSERT/UPDATE/DELETE, and we're not being called from
 		 * inheritance_planner, add the ModifyTable node.
 		 */
-		if (parse->commandType != CMD_SELECT && !inheritance_update)
+		if (parse->commandType != CMD_SELECT && parse->commandType != CMD_PLAN_UTILITY && !inheritance_update)
 		{
 			List	   *withCheckOptionLists;
 			List	   *returningLists;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index f66f39d8c6..8db0ae0501 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -140,6 +140,7 @@ static List *set_returning_clause_references(PlannerInfo *root,
 								int rtoffset);
 static bool extract_query_dependencies_walker(Node *node,
 								  PlannerInfo *context);
+static bool pull_up_has_schema_variable_walker(Node *node, PlannerInfo *root);
 
 /*****************************************************************************
  *
@@ -1004,6 +1005,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 	return plan;
 }
 
+/*
+ * Search usage of schema variables in subqueries
+ */
+void
+pull_up_has_schema_variable(PlannerInfo *root)
+{
+	Query *query = root->parse;
+
+	if (query->hasSchemaVariable)
+	{
+		root->hasSchemaVariable = true;
+	}
+	else
+	{
+		(void) query_tree_walker(query,
+								 pull_up_has_schema_variable_walker,
+								 (void *) root, 0);
+	}
+}
+
+static bool
+pull_up_has_schema_variable_walker(Node *node, PlannerInfo *root)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Query))
+	{
+		Query *query = (Query *) node;
+
+		if (query->hasSchemaVariable)
+		{
+			root->hasSchemaVariable = true;
+			return false;
+		}
+
+		/* Recurse into subselects */
+		return query_tree_walker((Query *) node,
+								 pull_up_has_schema_variable_walker,
+								 (void *) root, 0);
+	}
+	return expression_tree_walker(node, pull_up_has_schema_variable_walker,
+								  (void *) root);
+}
+
 /*
  * set_indexonlyscan_references
  *		Do set_plan_references processing on an IndexOnlyScan
@@ -1439,10 +1484,14 @@ fix_expr_common(PlannerInfo *root, Node *node)
 /*
  * fix_param_node
  *		Do set_plan_references processing on a Param
+ *		Collect schema variables list and replace variable oid by
+ *		index to collected list.
  *
  * If it's a PARAM_MULTIEXPR, replace it with the appropriate Param from
  * root->multiexpr_params; otherwise no change is needed.
  * Just for paranoia's sake, we make a copy of the node in either case.
+ *
+ * If it's a PARAM_VARIABLE, then we should to calculate paramid.
  */
 static Node *
 fix_param_node(PlannerInfo *root, Param *p)
@@ -1461,6 +1510,52 @@ fix_param_node(PlannerInfo *root, Param *p)
 			elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", p->paramid);
 		return copyObject(list_nth(params, colno - 1));
 	}
+
+	if (p->paramkind == PARAM_VARIABLE)
+	{
+		ListCell   *lc;
+		int			n = 0;
+		bool		found = false;
+
+		/* We will modify object */
+		p = (Param *) copyObject(p);
+
+		/*
+		 * Now, we can actualize list of schema variables, and we can
+		 * complete paramid parameter.
+		 */
+		foreach(lc, root->glob->schemaVariables)
+		{
+			if (lfirst_oid(lc) == p->paramvarid)
+			{
+				p->paramid = n;
+				found = true;
+				break;
+			}
+			n += 1;
+		}
+
+		if (!found)
+		{
+			PlanInvalItem *inval_item = makeNode(PlanInvalItem);
+
+			/* paramid is still schema variable id */
+			inval_item->cacheId = VARIABLEOID;
+			inval_item->hashValue = GetSysCacheHashValue1(VARIABLEOID,
+												ObjectIdGetDatum(p->paramvarid));
+
+			/* Append this variable to global, register dependency */
+			root->glob->invalItems = lappend(root->glob->invalItems,
+																inval_item);
+			root->glob->schemaVariables = lappend_oid(root->glob->schemaVariables,
+																  p->paramvarid);
+
+			p->paramid = n;
+		}
+
+		return (Node *) p;
+	}
+
 	return (Node *) copyObject(p);
 }
 
@@ -1472,7 +1567,9 @@ fix_param_node(PlannerInfo *root, Param *p)
  * replacing PARAM_MULTIEXPR Params, expanding PlaceHolderVars,
  * replacing Aggref nodes that should be replaced by initplan output Params,
  * 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 replacing PARAM_VARIABLE paramid, that is oid of schema variable
+ * to offset to array of by query used schema variables.
  */
 static Node *
 fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
@@ -1485,7 +1582,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
 	if (rtoffset != 0 ||
 		root->multiexpr_params != NIL ||
 		root->glob->lastPHId != 0 ||
-		root->minmax_aggs != NIL)
+		root->minmax_aggs != NIL ||
+		root->hasSchemaVariable)
 	{
 		return fix_scan_expr_mutator(node, &context);
 	}
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 8603feef2b..2923e3fcc7 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -71,6 +71,7 @@ preprocess_targetlist(PlannerInfo *root)
 {
 	Query	   *parse = root->parse;
 	int			result_relation = parse->resultRelation;
+	int			result_variable = parse->resultVariable;
 	List	   *range_table = parse->rtable;
 	CmdType		command_type = parse->commandType;
 	RangeTblEntry *target_rte = NULL;
@@ -96,6 +97,10 @@ preprocess_targetlist(PlannerInfo *root)

 

 
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c601b6d40d..86e21d519b 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,7 +25,10 @@
 #include "postgres.h"

 
+		case T_LetStmt:
 
 			/*
 
@@ -454,6 +468,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	if (pstate->p_hasAggs)
 		parseCheckAggregates(pstate, qry);
 
+	qry->hasSchemaVariable = pstate->p_hasSchemaVariable;
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
@@ -880,6 +896,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
+	qry->hasSchemaVariable = pstate->p_hasSchemaVariable;
 
 	assign_query_collations(pstate, qry);
 
@@ -1327,6 +1344,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 							   (LockingClause *) lfirst(l), false);
 	}
 
+	qry->hasSchemaVariable = pstate->p_hasSchemaVariable;
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
@@ -1561,12 +1580,229 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
+	qry->hasSchemaVariable = pstate->p_hasSchemaVariable;
 
 	assign_query_collations(pstate, qry);
 
 	return qry;
 }
 
+/*
+ * transformLetStmt -
+	/* Simple behave, when LET xx = DEFAULT was used */
+	if (stmt->selectStmt == NULL)
+					 errmsg("only complete variable can be set to default"),
+		qry->commandType = CMD_UTILITY;
+		param->paramvarid = varid;
+			pstate->p_hasSchemaVariable = true;
+	qry->hasSchemaVariable = pstate->p_hasSchemaVariable;
@@ -1799,6 +2035,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 							   (LockingClause *) lfirst(l), false);
 	}
 
+	qry->hasSchemaVariable = pstate->p_hasSchemaVariable;
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
@@ -2278,6 +2516,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
+	qry->hasSchemaVariable = pstate->p_hasSchemaVariable;
 
 	assign_query_collations(pstate, qry);
 
index 4bd2223f26..9f5229914b 100644

 
 /*

 

@@ -4479,6 +4490,48 @@ create_extension_opt_item:
 				}
 		;
 
+					n->collClause = (CollateClause *) $7;
+					n->collClause = (CollateClause *) $10;
+OnEOXActionOption:  ON COMMIT DROP		{ $$ = VARIABLE_EOX_DROP; }
@@ -6340,6 +6393,7 @@ drop_type_any_name:
 
@@ -6609,6 +6663,7 @@ comment_type_any_name:
 
@@ -6747,6 +6802,7 @@ security_label_type_any_name:
 
@@ -7168,6 +7224,14 @@ privilege_target:
@@ -7208,6 +7272,14 @@ privilege_target:

@@ -7368,6 +7440,7 @@ defacl_privilege_target:

@@ -8964,6 +9037,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
 
@@ -9282,6 +9374,25 @@ AlterObjectSchemaStmt:
 
@@ -9517,6 +9628,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec

@@ -10698,6 +10817,7 @@ ExplainableStmt:
 			| CreateMatViewStmt
 			| RefreshMatViewStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 explain_option_list:
@@ -10755,6 +10875,7 @@ PreparableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt					/* by default all are $$=$1 */
+			| LetStmt
 		;
 
 /*****************************************************************************
@@ -11153,6 +11274,50 @@ opt_hold: /* EMPTY */						{ $$ = 0; }
 			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
+					if (!IsA((Node *) $4, SetToDefault))
+						n->selectStmt = NULL;
@@ -15132,6 +15297,7 @@ unreserved_keyword:
@@ -15280,6 +15446,8 @@ unreserved_keyword:
 
 			/*
 
 			if (isAgg)
 
 			/*
index 385e54a9b6..54565834fd 100644

@@ -512,6 +517,9 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
@@ -749,6 +757,15 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 			break;
 	}
 
@@ -773,6 +790,72 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
+	if (OidIsValid(varid))
+	{
+		Oid		typid;
+		int32	typmod;
+		Oid		collid;
+
@@ -807,6 +890,74 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return node;
 }
 
+/*
+ * Generate param variable for reference to schema variable
+ */
+static Node *
+makeParamSchemaVariable(ParseState *pstate,
+						  Oid varid, Oid typid, int32 typmod, Oid collid,
+						  char *attrname, int location)
+{
+	Param	   *param;
+
+	param = makeNode(Param);
+
+	param->paramkind = PARAM_VARIABLE;
+	param->paramvarid = varid;
+	param->paramtype = typid;
+	param->paramtypmod = typmod;
+	param->paramcollid = collid;
+
+	/*
+	 * There are two access to schema variables - direct, used by simple
+	 * plpgsql expressions, where there are not necessary to emulate stability.
+	 * Buffered access is used elsewhere. We should to ensure stable values,
+	 * and because schema variables are global, then we should to work with
+	 * copied values instead direct access to variables. For direct access
+	 * the varid is best for access. For buffered access we need to assign
+	 * index to buffer - later, when we will know what variables are used.
+	 * Now, we just remember, so we use schema variables.
+	 */
+	pstate->p_hasSchemaVariable = 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)
 {
@@ -1818,6 +1969,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
 		case EXPR_KIND_VALUES_SINGLE:
+		case EXPR_KIND_LET:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -1826,6 +1978,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("cannot use subquery in DEFAULT expression");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
@@ -3460,6 +3613,7 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "CHECK";
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			return "DEFAULT";
 		case EXPR_KIND_INDEX_EXPRESSION:
 			return "index expression";
@@ -3475,6 +3629,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "PARTITION BY";
 		case EXPR_KIND_CALL_ARGUMENT:
 			return "CALL";
+		case EXPR_KIND_LET:
+			return "LET";
 
 			/*
 
 			/*

 	/*

 		case DestTupleQueue:
 
 	/* should never get here */
@@ -178,6 +182,7 @@ EndCommand(const char *commandTag, CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
@@ -222,6 +227,7 @@ NullCommand(CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
@@ -268,6 +274,7 @@ ReadyForQuery(CommandDest dest)
 		case DestSQLFunction:
 		case DestTransientRel:
 		case DestTupleQueue:
+		case DestVariable:
 			break;
 	}
 }
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7a9ada2c71..e711cd3366 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -869,6 +869,7 @@ pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams)
 			stmt->utilityStmt = query->utilityStmt;
 			stmt->stmt_location = query->stmt_location;
 			stmt->stmt_len = query->stmt_len;
+			stmt->resultVariable = query->resultVariable;
 		}
 		else
 		{
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 66cc5c35c6..cd07fcb6ee 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -86,6 +86,9 @@ CreateQueryDesc(PlannedStmt *plannedstmt,
 	qd->queryEnv = queryEnv;
 	qd->instrument_options = instrument_options;	/* instrumentation wanted? */
 
+	qd->num_schema_variables = 0;
+	qd->schema_variables = NULL;
+
 	/* null these fields until set by ExecutorStart */
 	qd->tupDesc = NULL;
 	qd->estate = NULL;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b5804f64ad..da09893103 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -47,6 +47,7 @@
 #include "commands/proclang.h"
 #include "commands/publicationcmds.h"
 #include "commands/schemacmds.h"
+#include "commands/schemavariable.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
 #include "commands/subscriptioncmds.h"
@@ -344,7 +345,7 @@ ProcessUtility(PlannedStmt *pstmt,
 			   char *completionTag)
 {
 	Assert(IsA(pstmt, PlannedStmt));
-	Assert(pstmt->commandType == CMD_UTILITY);
+	Assert(pstmt->commandType == CMD_UTILITY || pstmt->commandType == CMD_PLAN_UTILITY);
 	Assert(queryString != NULL);	/* required as of 8.4 */
 
 	/*
@@ -915,6 +916,21 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				break;
 			}
 
+		case T_LetStmt:
+			{
+				if (pstmt->commandType == CMD_UTILITY)
+					doLetStmtReset(pstmt);
+				else
+				{
+					Assert(pstmt->commandType == CMD_PLAN_UTILITY);
+					doLetStmtEval(pstmt, params, queryEnv, queryString);
+				}
+
+				if (completionTag)
+					strcpy(completionTag, "LET");
+			}
+			break;
+
 		default:
 			/* All other statement types have event trigger support */
 			ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -1221,6 +1237,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				}
 				break;
 
+			case T_CreateSchemaVarStmt:
+				address = DefineSchemaVariable(pstate, (CreateSchemaVarStmt *) parsetree);
+				break;
+
 				/*
 				 * ************* object creation / destruction **************
 				 */
@@ -2055,6 +2075,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_STATISTIC_EXT:
 			tag = "ALTER STATISTICS";
 			break;
+		case OBJECT_VARIABLE:
+			tag = "ALTER VARIABLE";
+			break;
 		default:
 			tag = "???";
 			break;
@@ -2104,6 +2127,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "SELECT";
 			break;
 
+		case T_LetStmt:
+			tag = "LET";
+			break;
+
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -2358,6 +2385,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_STATISTIC_EXT:
 					tag = "DROP STATISTICS";
 					break;
+				case OBJECT_VARIABLE:
+					tag = "DROP VARIABLE";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2639,6 +2669,9 @@ CreateCommandTag(Node *parsetree)
 				case DISCARD_SEQUENCES:
 					tag = "DISCARD SEQUENCES";
 					break;
+				case DISCARD_VARIABLES:
+					tag = "DISCARD VARIABLES";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2844,6 +2877,7 @@ CreateCommandTag(Node *parsetree)
 						tag = "DELETE";
 						break;
 					case CMD_UTILITY:
+					case CMD_PLAN_UTILITY:
 						tag = CreateCommandTag(stmt->utilityStmt);
 						break;
 					default:
@@ -2915,6 +2949,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
@@ -2961,6 +2999,10 @@ GetCommandLogLevel(Node *parsetree)
 				lev = LOGSTMT_ALL;
 			break;
 
+		case T_LetStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 
 	ereport(ERROR,
index 4c2408d655..ec10cb4468 100644
 
+	/* translate paramvarid to schema variable name */
+							schema_variable_get_name(param->paramvarid));
 
+/*
+ * get_varname_varid

+	if (g_verbose)
 
index f0ea83e6a9..c365d9fcf8 100644
@@ -4224,6 +4225,220 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
+/*
+ * getVariables
+		appendPQExpBuffer(query, " ON COMMIT DROP");
@@ -9791,6 +10006,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
@@ -17877,6 +18095,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,

+/*
 
 #endif							/* PG_DUMP_H */
 
index 4ca0db1d0c..fd4e1285d9 100644
@@ -4198,6 +4198,84 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
 	return true;
 }
 
+/*
+ * \dV
+ *
+ * listVariables()
+ */
+bool
+					  "    WHEN 'd' THEN 'ON COMMIT DROP'\n"
+					  "    WHEN 'r' THEN 'ON TRANSACTION END RESET' END as \"%s\"\n",
 
 /*
  * \dFp
 
+/* \dV */
 
index 586aebddd3..5e0175b384 100644
 
index 7549b40192..6ce9a3d6ec 100644
@@ -610,6 +610,22 @@ static const SchemaQuery Query_for_list_of_statistics = {
 	.result = "pg_catalog.quote_ident(s.stxname)",
 };

 /*
@@ -1054,6 +1070,7 @@ static const pgsql_thing_t words_after_create[] = {
@@ -1409,7 +1426,7 @@ psql_completion(const char *text, int start, int end)
@@ -1426,9 +1443,9 @@ psql_completion(const char *text, int start, int end)
@@ -1793,6 +1810,9 @@ psql_completion(const char *text, int start, int end)
@@ -2643,6 +2663,14 @@ psql_completion(const char *text, int start, int end)
 
@@ -2696,7 +2724,7 @@ psql_completion(const char *text, int start, int end)
 
 /* DISCARD */
 
 /* DO */
 	else if (Matches1("DO"))
@@ -2798,6 +2826,12 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* DROP VARIABLE */
@@ -2808,14 +2842,14 @@ psql_completion(const char *text, int start, int end)
 
 /* FETCH && MOVE */
@@ -2924,6 +2958,7 @@ psql_completion(const char *text, int start, int end)
@@ -2937,14 +2972,16 @@ psql_completion(const char *text, int start, int end)
 
@@ -2978,6 +3015,8 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		else if (TailMatches1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		else if (TailMatches1("VARIABLE"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
 		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -3130,7 +3169,7 @@ psql_completion(const char *text, int start, int end)
 
 /* PREPARE xx AS */
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -3353,6 +3392,14 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");

 

 

index 860571440a..7f3a0884d2 100644
 
index 0000000000..62355edf4c
@@ -0,0 +1,101 @@
+	VARIABLE_EOX_CODE_DROP = 'd',			/* ON COMMIT DROP */
+	VARIABLE_EOX_CODE_RESET = 'r',			/* ON COMMIT RESET */
index 0000000000..d1577fcec3
@@ -0,0 +1,42 @@
+extern Datum CopySchemaVariable(Oid varid, bool *isNull, Oid *typid);
+extern void doLetStmtReset(PlannedStmt *pstmt);
+extern void doLetStmtEval(PlannedStmt *pstmt, ParamListInfo params, QueryEnvironment *queryEnv, const char *queryString);
+extern void AtPreEOXact_SchemaVariable_on_commit_actions(bool isCommit);
+extern void AtEOXact_SchemaVariable_on_commit_actions(bool isCommit);
index f7b1f77616..1e92e8e1be 100644
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -351,6 +352,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
 		{
@@ -700,6 +708,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
 extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
 extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
 					ExprContext *econtext);
+extern void ExecEvalParamVariable(ExprState *state, ExprEvalStep *op,
+					ExprContext *econtext);
 extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h
index 10e9ded246..1ba9b9f4c6 100644
--- a/src/include/executor/execdesc.h
+++ b/src/include/executor/execdesc.h
@@ -48,6 +48,10 @@ typedef struct QueryDesc
 	EState	   *estate;			/* executor's query-wide state */
 	PlanState  *planstate;		/* tree of per-plan-node state */
 
+	/* reference to schema variables buffer */
+	int			num_schema_variables;
+	SchemaVariableValue *schema_variables;
+
 	/* This field is set by ExecutorRun */
 	bool		already_executed;	/* true if previously executed */
 
index c830f141b1..0d9cdd551c 100644
@@ -464,6 +464,18 @@ typedef struct ResultRelInfo
 	bool		ri_PartitionReadyForRouting;
 } ResultRelInfo;
 
+/* ----------------
+ * SchemaVariableValue
+ * ----------------
+ */
+typedef struct SchemaVariableValue
+{
+	Oid		varid;
+	Oid		typid;
+	bool	isnull;
+	Datum	value;
+} SchemaVariableValue;
+
 /* ----------------
  *	  EState information
  *
@@ -518,6 +530,13 @@ typedef struct EState
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
 
+	/* Variables info: */
+	/* number of used schema variables */
+	int			es_num_schema_variables;
+
+	/* array of copied values of schema variables */
+	SchemaVariableValue *es_schema_variables;
+
 	QueryEnvironment *es_queryEnv;	/* query environment */
 
 	/* Other working state: */
@@ -565,6 +584,8 @@ typedef struct EState
 	/* The per-query shared memory area to use for parallel execution. */
 	struct dsa_area *es_query_dsa;

 	/*
index 07ab1a3dde..539f248851 100644

 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
@@ -131,6 +134,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		hasSchemaVariable; /* uses schema variables */
 
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
@@ -1505,6 +1509,18 @@ typedef struct UpdateStmt
 	WithClause *withClause;		/* WITH clause */
 } UpdateStmt;
 
@@ -1682,6 +1698,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -2497,6 +2514,21 @@ typedef struct AlterSeqStmt
 	bool		missing_ok;		/* skip error if a role is missing? */
 } AlterSeqStmt;
 
@@ -3238,7 +3270,8 @@ typedef enum DiscardMode
 	DISCARD_ALL,
 	DISCARD_PLANS,
 	DISCARD_SEQUENCES,
-	DISCARD_TEMP
+	DISCARD_TEMP,
+	DISCARD_VARIABLES
 } DiscardMode;
 
index 7c2abbd03a..cbf5b8e0a0 100644

 

@@ -96,6 +99,8 @@ typedef struct PlannedStmt
 
 	Node	   *utilityStmt;	/* non-null if this is utility stmt */
 
+	List	   *schemaVariables; /* list of OIDs for PARAM_VARIABLE Params */
+
 	/* statement location in source string (copied from Query) */
 	int			stmt_location;	/* start location, or -1 if unknown */
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4b0d75af..2a5f24c612 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -43,15 +43,25 @@ typedef struct Alias
 	List	   *colnames;		/* optional list of column aliases */
 } Alias;

@@ -229,13 +239,17 @@ typedef struct Const
 
 typedef struct Param
@@ -246,6 +260,7 @@ typedef struct Param
 	Oid			paramtype;		/* pg_type OID of parameter's datatype */
 	int32		paramtypmod;	/* typmod value, if known */
 	Oid			paramcollid;	/* OID of collation, or InvalidOid if none */
+	Oid			paramvarid;		/* OID of schema variable if it is used */
 	int			location;		/* token location, or -1 if unknown */
 } Param;
 
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index adb4265047..8d8adb8cee 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -145,6 +145,8 @@ typedef struct PlannerGlobal
 	bool		parallelModeNeeded; /* parallel mode actually required? */
 
 	char		maxParallelHazard;	/* worst PROPARALLEL hazard level */
+
+	List	   *schemaVariables;	/* list of used schema variables */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -329,6 +331,7 @@ typedef struct PlannerInfo
 	bool		hasPseudoConstantQuals; /* true if any RestrictInfo has
 										 * pseudoconstant = true */
 	bool		hasRecursion;	/* true if planning a recursive WITH item */
+	bool		hasSchemaVariable;		/* true if schema variables was used */
 
 	/* These fields are used only when hasRecursion is true: */
 	int			wt_param_id;	/* PARAM_EXEC ID for the work table */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index c8ab0280d2..37b67d136f 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -120,5 +120,6 @@ extern void extract_query_dependencies(Node *query,
 						   List **relationOids,
 						   List **invalItems,
 						   bool *hasRowSecurity);
+extern void pull_up_has_schema_variable(PlannerInfo *root);
 
 #endif							/* PLANMAIN_H */
index 0230543810..143597ce80 100644

@@ -202,6 +204,7 @@ struct ParseState
 	bool		p_hasTargetSRFs;
 	bool		p_hasSubLinks;
 	bool		p_hasModifyingCTE;
+	bool		p_hasSchemaVariable;
 
 	Node	   *p_last_srf;		/* most recent set-returning func/op found */

 /* ----------------

 /*

 

 
 
index 48e0508a96..6cd3a77a8f 100644
@@ -164,6 +164,7 @@ pg_ts_parser|t
index 0000000000..c659c83dcf
@@ -0,0 +1,491 @@
+-- ON TRANSACTION END RESET tests
+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
+LET v1 = DEFAULT;
+LET v2 = DEFAULT;
+SELECT v1, v2;
+ v1 |  v2  
+----+------
+  0 | none
+-- ON COMMIT DROP tests
+-- should be 0 always
+SELECT count(*) FROM pg_variable;
+ count 
+CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+SELECT count(*) FROM pg_variable;
+ count 
+  CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+SELECT count(*) FROM pg_variable;
+ count 
+  CREATE TEMP VARIABLE g AS int ON COMMIT DROP;
+SELECT count(*) FROM pg_variable;
+ count 
+-- 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;
+--------------------------------------------------
+ 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
+drop table svar_test;
+drop variable zero;
 
 # ----------
index 0000000000..4be31e1bb0
@@ -0,0 +1,328 @@
+-- ON TRANSACTION END RESET tests
+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;
+
+drop table svar_test;
+drop variable zero;
+

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 15 Sep 2018 18:06:59 +0200

> o LET var = DEFAULT -- reset specified variable
fix some forgotten warnings and dependency issue
few more tests

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 17 Sep 2018 21:46:01 +0200

> fix some forgotten warnings and dependency issue
new update:

o support NOT NULL check
o implementation limited transaction variables - these variables doesn't
respects subtransactions(this is much more complex), drop variable drops
content although the drop can be reverted (maybe this limit will be
removed).

CREATE TRANSACTION VARIABLE fx AS int;

LET fx = 10;
BEGIN
  LET fx = 20;
ROLLBACK;

SELECT fx;

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 19 Sep 2018 10:30:31 +0200

Hi

new update:

I fixed pg_restore, and I cleaned a code related to transaction processing

There should be a full functionality now.

Regards

Pavel

---

From: Arthur Zakirov <[email-masked]>
Date: Wed, 19 Sep 2018 14:23:06 +0300

Hello,

> There should be a full functionality now.

I reviewed a little bit the patch. I have a few comments.

> <title><structname>pg_views</structname> Columns</title>

I think there is a typo here. It should be "pg_variable".

> GRANT { READ | WRITE | ALL [ PRIVILEGES ] }

Shouldn't we use here GRANT { SELECT | LET | ... } syntax for the
constistency. Same for REVOKE. I'm not experienced syntax developer
though. But we use SELECT and LET commands when working with variables.
So we should GRANT and REVOKE priveleges for this commands.

> [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]

I think we may join them and have the syntax { ON COMMIT DROP | RESET }
to get more simpler syntax. If we create a variable with ON COMMIT
DROP, PostgreSQL will drop it regardless of whether transaction was
committed or rollbacked:

=# ...
=# begin;
=# create variable int1 int on commit drop;
=# rollback;
=# -- There is no variable int1

CREATE TABLE syntax has similar options [1]. ON COMMIT controls
the behaviour of temporary tables at the end a transaction block,
whether it was committed or rollbacked. But I'm not sure is this a good
example of precedence.

> +	ONCOMMIT_DROP,				/* ON COMMIT DROP */

There is the extra comma here after ONCOMMIT_DROP.

1 - https://www.postgresql.org/docs/current/static/sql-createtable.html

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 19 Sep 2018 14:08:04 +0200

Hi
st 19. 9. 2018 v 13:23 odesílatel Arthur Zakirov <[email-masked]>

> I think there is a typo here. It should be "pg_variable".

I'll fix it.

> So we should GRANT and REVOKE priveleges for this commands.

I understand to your proposal, - and I have not strong opinion. Originally
I proposed {SELECT|UPDATE), but some people prefer {READ|WRITE}. Now I
prefer Peter's proposal (what is implemented now) - READ|WRITE, because it
is very illustrative - and the mentioned difference is good because the
variables are not tables (by default), are not persistent, so different
rights are good for me. I see "GRANT LET" like very low clear, because
nobody knows what LET command does. Unfortunately we cannot to use standard
"SET" command, because it is used in Postgres for different purpose.
READ|WRITE are totally clear, and for user it is another signal so
variables are different than tables (so it is not one row table).

I prefer current state, but if common opinion will be different, I have not
problem to change it.

> DROP, PostgreSQL will drop it regardless of whether transaction was

I though about it too. I'll try to explain my idea. Originally I was
surprised so postgres uses "ON COMMIT" syntax, but in documentation is used
term "at transaction end". But it has some sense. ON COMMIT DROP is allowed
only for temporary tables and ON COMMIT DELETE ROWS is allowed for tables.
With these clauses the PostgreSQL is more aggressive in cleaning. It
doesn't need to calculate with rollback, because the rollback does cleaning
by self. So syntax "ON COMMIT" is fully correct it is related only for
commit event. It has not sense on rollback event (and doesn't change a
behave on rollback event).

The content of variables is not transactional (by default). It is not
destroyed by rollback. So I have to calculate with rollback too. So the
most correct syntax should be "ON COMMIT ON ROLLBACK RESET" what is little
bit messy and I used "ON TRANSACTION END". It should be signal, so this
event is effective on rollback event and it is valid for not transaction
variable. This logic is not valid to transactional variables, where ON
COMMIT RESET has sense. But this behave is not default and then I prefer
more generic syntax.

Again I have not a problem to change it, but I am thinking so current
design is logically correct.

> =# -- There is no variable int1
PostgreSQL catalog is transactional (where the metadata is stored), so when
I am working with metadata, then I use ON COMMIT syntax, because the behave
of  ON ROLLBACK cannot be changed.

So I see two different cases - work with catalog (what is transactional)
and work with variable value, what is (like other variables in programming
languages) not transactional. "ON TRANSACTION END RESET" means - does reset
on any transaction end.

I hope so I explained it cleanly - if not, please, ask.

> There is the extra comma here after ONCOMMIT_DROP.

I'll fix it.

Regards

Pavel

---

From: Arthur Zakirov <[email-masked]>
Date: Wed, 19 Sep 2018 15:53:25 +0300

On Wed, Sep 19, 2018 at 02:08:04PM +0200, Pavel Stehule wrote:
> Unfortunately we cannot to use standard

I see. I grepped the thread before writhing this but somehow missed the
discussion.

> I hope so I explained it cleanly - if not, please, ask.

I understood what you mean, thank you. I thought that
{ ON COMMIT DROP | ON TRANSACTION END RESET } parameters are used only
for transactional variables in the first place. But is there any sense
in using this parameters with non-transactional variables? That is when
we create non-transactional variable we don't want that the variable
will rollback or reset its value after the end of a transaction.

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 19 Sep 2018 16:36:40 +0200

> will rollback or reset its value after the end of a transaction.

ON COMMIT DROP is used only for temp variables (transaction or not
transaction). The purpose is same like for tables. Sometimes you can to
have object with shorter life than is session.

ON TRANSACTION END RESET has sense mainly for not transaction variables. I
see two use cases.

1. protect some sensitive data - on transaction end guaranteed reset and
cleaning on end transaction. So you can be sure, so variable is not
initialized (has default value), or you are inside transaction.

2. automatic initialization - ON TRANSACTION END RESET ensure so variable
is in init state for any transaction.

Both cases has sense for transaction or not transaction variables.

I am thinking so transaction life time for content has sense. Is cheaper to
reset variable than drop it (what ON COMMIT DROP does)

What do you think?

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 20 Sep 2018 11:08:57 +0200

Hi

> I think there is a typo here. It should be "pg_variable".

fixed

> There is the extra comma here after ONCOMMIT_DROP.

fixed

Thank you for comments

attached updated patch

---

From: Arthur Zakirov <[email-masked]>
Date: Fri, 21 Sep 2018 22:46:18 +0300

> reset variable than drop it (what ON COMMIT DROP does)

Thanks, I understood the cases.

But I think there is more sense to use these options only with transactional
variables. It is more consistent and simple for me.

As a summary, it is 1 voice vs 1 voice :) So it is better to leave the
syntax as is without changes for now.

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 22 Sep 2018 05:35:45 +0200

> variables. It is more consistent and simple for me.

I agree so it can be hard to imagine - and if I return back to start
discussion about schema variables - it can be hard because it joins some
concepts - variables has persistent transactional metadata, but the content
is not transactional.

I don't think so the variability is a issue in this case. There is a lot of
examples, so lot of combinations are possible - global temp tables and
package variables (Oracle), local temp tables and local variables
(Postgres), session variables and memory tables (MSSQL). Any combination of
feature has cases where can be very practical and useful.

ON TRANSACTION END RESET can be useful, because we have not a session event
triggers (and in this moment I am not sure if it is necessary and practical
- their usage can be very fragile). But some work can do ON xxx clauses,
that should not to have negative impact on performance or fragility.

ON TRANSACTION END RESET ensure cleaned and initialized to default value
for any transaction. Other possibility is ON COMMAND END RESET (but I would
not to implement it now), ...

> syntax as is without changes for now.

:) now is enough time to think about syntax. Some features can be removed
and returned back later, where this concept will be more absorbed.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 22 Sep 2018 08:00:04 +0200

Hi

rebased against yesterday changes in tab-complete.c

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 29 Sep 2018 10:34:40 +0200

> rebased against yesterday changes in tab-complete.c

rebased against last changes in master

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 30 Sep 2018 00:19:23 +0200

> rebased against last changes in master

+ using content of schema variable for estimation
+ subtransaction support

I hope so now, there are almost complete functionality. Please, check it.

Regards

Pavel

---

From: Thomas Munro <[email-masked]>
Date: Wed, 3 Oct 2018 12:01:22 +1300

> I hope so now, there are almost complete functionality. Please, check it.

Hi Pavel,

FYI there is a regression test failure on Windows:

plpgsql ... FAILED

*** 4071,4077 ****
end;
$$ language plpgsql;
select stacked_diagnostics_test();
- NOTICE: sqlstate: 22012, message: division by zero, context:
[PL/pgSQL function zero_divide() line 4 at RETURN <- SQL statement
"SELECT zero_divide()" <- PL/pgSQL function stacked_diagnostics_test()
line 6 at PERFORM]
+ NOTICE: sqlstate: 42702, message: column reference "v" is ambiguous,
context: [PL/pgSQL function zero_divide() line 4 at RETURN <- SQL
statement "SELECT zero_divide()" <- PL/pgSQL function
stacked_diagnostics_test() line 6 at PERFORM]

https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.15234

-- 
Thomas Munro
http://www.enterprisedb.com

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 5 Oct 2018 03:34:14 +0200

> FYI there is a regression test failure on Windows:

please, check attached patch

Thank you for report

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 7 Oct 2018 19:13:44 +0200

Hi

> I hope so now, there are almost complete functionality. Please, check it.

new update

minor white space issue
one more regress test and 2 pg_dump tests

Regards

Pavel

---

From: Erik Rijkers <[email-masked]>
Date: Tue, 23 Oct 2018 14:50:18 +0200

> [schema-variables-20181007-01.patch.gz]

Hi,

I tried to test your schema-variables patch but got stuck here instead 
(after applying succesfully on top of e6f5d1acc):

make[2]: *** No rule to make target 
'../../../src/include/catalog/pg_variable.h', needed by 'bki-stamp'.  
Stop.
make[1]: *** [submake-catalog-headers] Error 2
make[1]: *** Waiting for unfinished jobs....
make: *** [submake-generated-headers] Error 2
Makefile:141: recipe for target 'submake-catalog-headers' failed
src/Makefile.global:370: recipe for target 'submake-generated-headers' 
failed

thanks,

Erik Rijkers

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 23 Oct 2018 20:05:07 +0200

Hi

> src/Makefile.global:370: recipe for target 'submake-generated-headers'
Unfortunately previous patch was completely broken. I am sorry

Please, check this patch.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 21 Nov 2018 08:24:50 +0100

Hi

just rebase

Regards

Pavel

---

From: Dmitry Dolgov <[email-masked]>
Date: Sat, 1 Dec 2018 00:17:21 +0100

> On Wed, Nov 21, 2018 at 8:25 AM Pavel Stehule <[email-masked]> wrote:
>
> just rebase

Thanks for working on this patch.

I'm a bit confused, but cfbot again says that there are some conflicts.
Probably they are the minor one, from src/bin/psql/help.c

For now I'm moving it to the next CF.

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 1 Dec 2018 07:32:41 +0100

> Probably they are the minor one, from src/bin/psql/help.c

rebased again

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 31 Dec 2018 14:23:44 +0100

> just rebase
rebase

Regards

Pavel

---

From: Erik Rijkers <[email-masked]>
Date: Mon, 31 Dec 2018 16:40:24 +0100

> [schema-variables-20181231-01.patch.gz]

Hi Pavel,

I gave this a quick try-out with the script I had from previous 
versions,
and found these two errors:

------------
drop schema if exists schema1 cascade;
create schema if not exists schema1;
drop variable if exists schema1.myvar1;  --> error 49
create variable schema1.myvar1 as text ;
select schema1.myvar1;
let schema1.myvar1 = 'variable value ""';
select schema1.myvar1;
alter variable schema1.myvar1 rename to myvar2;
select schema1.myvar2;
create variable schema1.myvar1 as text ;
let schema1.myvar1 = 'variable value ""';
select schema1.myvar1;
alter variable schema1.myvar1 rename to myvar2; --> error 4287
select schema1.myvar2;
------------

The above, ran with   psql -qXa  gives the following output:

drop schema if exists schema1 cascade;
create schema if not exists schema1;
drop variable if exists schema1.myvar1;  --> error 49
ERROR:  unrecognized object type: 49
create variable schema1.myvar1 as text ;
select schema1.myvar1;
  myvar1
--------

(1 row)

let schema1.myvar1 = 'variable value ""';
select schema1.myvar1;
       myvar1
-------------------
  variable value ""
(1 row)

alter variable schema1.myvar1 rename to myvar2;
select schema1.myvar2;
       myvar2
-------------------
  variable value ""
(1 row)

create variable schema1.myvar1 as text ;
let schema1.myvar1 = 'variable value ""';
select schema1.myvar1;
       myvar1
-------------------
  variable value ""
(1 row)

alter variable schema1.myvar1 rename to myvar2; --> error 4287
ERROR:  unsupported object class 4287
select schema1.myvar2;
       myvar2
-------------------
  variable value ""
(1 row)

thanks,

Erik Rijkers

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 31 Dec 2018 18:33:30 +0100

Hi

> and found these two errors:
Should be fixed now.

Thank you for report

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 22 Jan 2019 20:32:46 +0100

Hi

fresh rebased patch, no other changes

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 30 Jan 2019 17:34:24 +0100

Hi

just rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 31 Jan 2019 12:49:27 +0100

Hi

just rebase

regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 3 Mar 2019 21:27:13 +0100

Hi

> Pavel

rebase and fix compilation due changes related pg_dump

Regards

Pavel

---

From: David Steele <[email-masked]>
Date: Thu, 7 Mar 2019 08:52:16 +0200

> rebase and fix compilation due changes related pg_dump

This patch hasn't receive any review in a while and I'm not sure if 
that's because nobody is interested or the reviewers think it does not 
need any more review.

It seems to me that this patch as implemented does not quite satisfy any 
one.

I think we need to hear something from the reviewers soon or I'll push 
this patch to PG13 as Andres recommends [1].

-- 
-David
[email-masked]

[1] 
https://www.postgresql.org/message-id/20190216054526.zss2cufdxfeudr4i%40alap3.anarazel.de

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 7 Mar 2019 09:10:47 +0100 (CET)

Hello David,

> This patch hasn't receive any review in a while and I'm not sure if that's 
> because nobody is interested or the reviewers think it does not need any more 
> review.
>
> It seems to me that this patch as implemented does not quite satisfy any one.
>
> I think we need to hear something from the reviewers soon or I'll push this 
> patch to PG13 as Andres recommends [1].

I have discussed the feature extensively with Pavel on the initial thread.

My strong opinion based on the underlying use case is that it that such 
session variables should be transactional by default, and Pavel strong 
opinion is that they should not, to be closer to Oracle comparable 
feature.

According to the documentation, the current implementation does provide a 
transactional feature. However, it is not the default behavior, so I'm in 
disagreement on a key feature, although I do really appreciate that Pavel
implemented the transactional behavior.

Otherwise, ISTM that they could be named "SESSION VARIABLE" because the 
variable only exists in memory, in a session, and we could thing of adding 
other kind of variables later on.

I do intend to review it in depth when it is transactional by default.

Anyway, the patch is non trivial and very large, so targetting v12 now is 
indeed out of reach.

-- 
Fabien.

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 7 Mar 2019 09:32:25 +0100

čt 7. 3. 2019 v 9:10 odesílatel Fabien COELHO <[email-masked]> napsal:

> that's

I am sorry. I cannot to support this request. Variables are not
transactional. My opinion is strong in this part.

I would not to repeat this discussion from start. I am sorry.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 7 Mar 2019 09:37:58 +0100

Hi

>> opinion is that they should not, to be closer to Oracle comparable
It is closer to any known database Oracle, DB2, Firebird, MSSQL, MySQL,

Regards

Pavel

---

From: David Steele <[email-masked]>
Date: Thu, 7 Mar 2019 11:26:06 +0200

On 3/7/19 10:10 AM, Fabien COELHO wrote:
> 
> Anyway, the patch is non trivial and very large, so targetting v12 now 
> is indeed out of reach.

Agreed.  I have set the target version to PG13.

Regards,
-- 
-David
[email-masked]

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 24 Mar 2019 06:57:10 +0100

Hi

rebase against current master

Regards

Pavel

---

From: Erik Rijkers <[email-masked]>
Date: Sun, 24 Mar 2019 10:25:38 +0100

> rebase against current master

I ran into this:

(schema 'varschema2' does not exist):

drop variable varschema2.testv cascade;
ERROR:  schema "varschema2" does not exist
create variable if not exists testv as text;
server closed the connection unexpectedly
	This probably means the server terminated abnormally
	before or while processing the request.
connection to server was lost

(both statements are needed to force the crash)

thanks,

Erik Rijkers

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 24 Mar 2019 10:32:53 +0100

> (both statements are needed to force the crash)

I cannot to reproduce it.

please, try compilation with "make distclean"; configure ..

or if the problem persists, please send test case, or backtrace

Regards

Pavel

---

From: Erik Rijkers <[email-masked]>
Date: Mon, 25 Mar 2019 20:40:15 +0100

On 2019-03-24 10:32, Pavel Stehule wrote:
>  [backtrace and stuff]

Sorry, I don't have the wherewithal to get more info but I have repeated 
this now on 4 different machines (debian jessie/stretch; centos).

I did notice that sometimes those two offending lines
"
   drop variable varschema2.testv cascade;
   create variable if not exists testv as text;
"
have to be repeated a few times (never more than 4 or 5 times) before 
the crash occurs (signal 11: Segmentation fault).

Erik Rijkers

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 26 Mar 2019 06:40:22 +0100

Hi

> rebase against current master

fixed issue IF NOT EXISTS & related regress tests

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 26 Mar 2019 06:41:04 +0100

Should be fixed now.

Thank you for report

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 2 Apr 2019 20:02:12 +0200

> fixed issue IF NOT EXISTS & related regress tests

another rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 19 Apr 2019 08:32:25 +0200

fresh rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 9 May 2019 06:34:20 +0200

Hi

rebased patch

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 24 May 2019 19:12:13 +0200

Hi

> rebased patch

rebase after pgindent

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 30 Jun 2019 05:10:08 +0200

fresh rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 16 Jul 2019 14:50:49 +0200

Hi

>> čt 9. 5. 2019 v 6:34 odesílatel Pavel Stehule <[email-masked]>

just rebase again

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 10 Aug 2019 09:10:14 +0200

Hi

just rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 4 Oct 2019 06:12:41 +0200

Hi

fresh rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 10 Oct 2019 11:41:10 +0200

Hi

minor change - replace heap_tuple_fetch_attr by detoast_external_attr.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 3 Nov 2019 17:27:17 +0100

> minor change - replace heap_tuple_fetch_attr by detoast_external_attr.
similar update - heap_open, heap_close was replaced by table_open,
table_close

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 18 Nov 2019 19:47:58 +0100

fresh rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 14 Dec 2019 22:43:38 +0100

>> similar update - heap_open, heap_close was replaced by table_open,

only rebase

Regards

Pavel

---

From: Philippe BEAUDOIN <[email-masked]>
Date: Sun, 22 Dec 2019 12:03:10 +0000

The following review has been posted through the commitfest application:
make installcheck-world:  tested, passed
Implements feature:       tested, failed
Spec compliant:           not tested
Documentation:            tested, failed

Hi Pavel,

First of all, I would like to congratulate you for this great work. This patch is really cool. The lack of package variables is sometimes a blocking issue for Oracle to Postgres migrations, because the usual emulation with GUC is sometimes not enough, in particular when there are security concerns or when the database is used in a public cloud.

As I look forward to having this patch commited, I decided to spend some time to participate to the review, although I am not a C specialist and I have not a good knowledge of the Postgres internals. Here is my report.

A) Installation

The patch applies correctly and the compilation is fine. The "make check" doesn't report any issue.

B) Basic usage

I tried some simple schema variables use cases. No problem.

C) The interface

The SQL changes look good to me.

However, in the CREATE VARIABLE command, I would replace the "TRANSACTION" word by "TRANSACTIONAL".

I have also tried to replace this word by a ON ROLLBACK clause at the end of the statement, like for ON COMMIT, but I have not found a satisfying wording to propose.

D) Behaviour

I am ok with variables not being transactional by default. That's the most simple, the most efficient, it emulates the package variables of other RDBMS and it will probably fit the most common use cases.

Note that I am not strongly opposed to having by default transactional variables. But I don't know whether this change would be a great work. We would have at least to find another keyword in the CREATE VARIABLE statement. Something like "NON-TRANSACTIONAL VARIABLE" ?

It is possible to create a NOT NULL variable without DEFAULT. When trying to read the variable before a LET statement, one gets an error massage saying that the NULL value is not allowed (and the documentation is clear about this case). Just for the records, I wondered whether it wouldn't be better to forbid a NOT NULL variable creation that wouldn't have a DEFAULT value. But finally, I think this behaviour provides a good way to force the variable initialisation before its use. So let's keep it as is.

E) ACL and Rights

I played a little bit with the GRANT and REVOKE statements. 

I have got an error (Issue 1). The following statement chain:
  create variable public.sv1 int;
  grant read on variable sv1 to other_user;
  drop owned by other_user;
reports : ERROR:  unexpected object class 4287

I then tried to use DEFAULT PRIVILEGES. Despite this is not documented, I successfuly performed:
  alter default privileges in schema public grant read on variables to simple_user;
  alter default privileges in schema public grant write on variables to simple_user;

When variables are then created, the grants are properly given.
And the psql \ddp command perfectly returns:
             Default access privileges
  Owner   | Schema | Type |    Access privileges    
----------+--------+------+-------------------------
 postgres | public |      | simple_user=SW/postgres
(1 row)

So the ALTER DEFAULT PRIVILEGES documentation chapter has to reflect this new syntax (Issue 2).

BTW, in the ACL, the READ privilege is represented by a S letter. A comment in the source reports that the R letter was used in the past for rule privilege. Looking at the postgres sources, I see that this privilege on rules has been suppressed  in 8.2, so 13 years ago. As this R letter would be a so much better choice, I wonder whether it couldn't be reused now for this new purpose. Is it important to keep this letter frozen ?

F) Extension

I then created an extension, whose installation script creates a schema variable and functions that use it. The schema variable is correctly linked to the extension, so that dropping the extension drops the variable.

But there is an issue when dumping the database (Issue 3). The script generated by pg_dump includes the CREATE EXTENSION statement as expected but also a redundant CREATE VARIABLE statement for the variable that belongs to the extension. As a result, one of course gets an error at restore time.

G) Row Level Security

I did a test activating RLS on a table and creating a POLICY that references a schema variable in its USING and WITH CHECK clauses. Everything worked fine.

H) psql

A \dV meta-command displays all the created variables.
I would change a little bit the provided view. More precisely I would:
- rename "Constraint" into "Is nullable" and report it as a boolean
- rename "Special behave" into "Is transactional" and report it as a boolean
- change the order of columns so to have:
Schema | Name | Type | Is nullable | Default | Owner | Is transactional | Transaction end action
"Is nullable" being aside "Default"

I) Performance

I just quickly looked at the performance, and didn't notice any issue.

About variables read performance, I have noticed that:
  select sum(1) from generate_series(1,10000000);
and
  select sum(sv1) from generate_series(1,10000000);
have similar response times.

About planning, a condition with a variable used as a constant is indexable, as if it were a literal.

J) Documentation

There are some wordings to improve in the documentation. But I am not the best person to give advice about english language ;-).

However, aside the already mentionned lack of changes in the ALTER DEFAULT PRIVILEGES chapter, I also noticed :
- line 50 of the patch, the sentence "(hidden attribute; must be explicitly selected)" looks false as the oid column of pg_variable is displayed, as for other tables of the catalog;
- at several places, the word "behave" should be replaced by "behaviour"
- line 433, a get_schema_variable() function is mentionned; is it a function that can really be called by users ?

May be it would be interesting to also add a chapter in the Section V of the documentation, in order to more globally present the schema variables concept, aside the new or the modified statements.

K) Coding

I am not able to appreciate the way the feature has been coded. So I let this for other reviewers ;-)

To conclude, again, thanks a lot for this feature !
And if I may add this. I dream of an additional feature: adding a SHARED clause to the CREATE VARIABLE statement in order to be able to create memory spaces that could be shared by all connections on the database and accessible in SQL and PL, under the protection of ACL. But that's another story ;-)

Best regards. Philippe.

The new status of this patch is: Waiting on Author

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 22 Dec 2019 19:50:22 +0100

Hi

ne 22. 12. 2019 v 13:04 odesílatel Philippe BEAUDOIN <[email-masked]>
napsal:

> First of all, I would like to congratulate you for this great work. This
> patch is really cool. The lack of package variables is sometimes a blocking
> issue for Oracle to Postgres migrations, because the usual emulation with
> GUC is sometimes not enough, in particular when there are security concerns
> or when the database is used in a public cloud.
>
> As I look forward to having this patch commited, I decided to spend some
> time to participate to the review, although I am not a C specialist and I
> have not a good knowledge of the Postgres internals. Here is my report.
>
> A) Installation
>

There is not technical problem - the problem is in introduction new keyword
"transactional" that is near to "transaction". I am not sure if it is
practical to have two "similar" keyword and how much the CREATE statement
has to use correct English grammar.

I am not native speaker, so I am not able to see how bad is using
"TRANSACTION" instead "TRANSACTIONAL" in this context. So I see a risk to
have two important (it is not syntactic sugar) similar keywords.

Just I afraid so using TRANSACTIONAL instead just TRANSACTION is not too
user friendly. I have not strong opinion about this - and the
implementation is easy, but I am not feel comfortable with introduction
this keyword.

> I have also tried to replace this word by a ON ROLLBACK clause at the end
> of the statement, like for ON COMMIT, but I have not found a satisfying
> wording to propose.
>

>
> D) Behaviour
>
> I am ok with variables not being transactional by default. That's the most
> simple, the most efficient, it emulates the package variables of other
> RDBMS and it will probably fit the most common use cases.
>
> Note that I am not strongly opposed to having by default transactional
> variables. But I don't know whether this change would be a great work. We
> would have at least to find another keyword in the CREATE VARIABLE
> statement. Something like "NON-TRANSACTIONAL VARIABLE" ?
>

Variables almost everywhere (global user settings - GUC is only one planet
exception) are non transactional by default. I don't see any reason
introduce new different design than is wide used.

> It is possible to create a NOT NULL variable without DEFAULT. When trying
> to read the variable before a LET statement, one gets an error massage
> saying that the NULL value is not allowed (and the documentation is clear
> about this case). Just for the records, I wondered whether it wouldn't be
> better to forbid a NOT NULL variable creation that wouldn't have a DEFAULT
> value. But finally, I think this behaviour provides a good way to force the
> variable initialisation before its use. So let's keep it as is.
>

This is a question - and there are two possibilities

postgres=# do $$
declare x int not null;
begin
  raise notice '%', x;
end;
$$ ;
ERROR:  variable "x" must have a default value, since it's declared NOT NULL
LINE 2: declare x int not null;
                      ^

PLpgSQL requires it. But there is not a possibility to enforce future
setting.

So I know so behave of schema variables is little bit different, but I
think so this difference has interesting use case. You can check if the
variable was modified somewhere or not.

> reports : ERROR:  unexpected object class 4287

this is bug and should be fixed

> BTW, in the ACL, the READ privilege is represented by a S letter. A
> comment in the source reports that the R letter was used in the past for
> rule privilege. Looking at the postgres sources, I see that this privilege
> on rules has been suppressed  in 8.2, so 13 years ago. As this R letter
> would be a so much better choice, I wonder whether it couldn't be reused
> now for this new purpose. Is it important to keep this letter frozen ?
>

I have not a idea why it is. I'll recheck it - but in this moment I prefer
a consistency with existing ACL - it can be in future as one block if it
will be necessary for somebody.

>
> F) Extension
>
> I then created an extension, whose installation script creates a schema
> variable and functions that use it. The schema variable is correctly linked
> to the extension, so that dropping the extension drops the variable.
>
> But there is an issue when dumping the database (Issue 3). The script
> generated by pg_dump includes the CREATE EXTENSION statement as expected
> but also a redundant CREATE VARIABLE statement for the variable that
> belongs to the extension. As a result, one of course gets an error at
> restore time.
>

It is bug and should be fixed

> G) Row Level Security
>
> I did a test activating RLS on a table and creating a POLICY that
> references a schema variable in its USING and WITH CHECK clauses.
> Everything worked fine.
>
> H) psql
>

ok

> There are some wordings to improve in the documentation. But I am not the
> best person to give advice about english language ;-).
>
> - line 50 of the patch, the sentence "(hidden attribute; must be
> explicitly selected)" looks false as the oid column of pg_variable is
> displayed, as for other tables of the catalog;
> - line 433, a get_schema_variable() function is mentionned; is it a
> function that can really be called by users ?
>
> May be it would be interesting to also add a chapter in the Section V of
> the documentation, in order to more globally present the schema variables
> concept, aside the new or the modified statements.
>
> K) Coding
>
> And if I may add this. I dream of an additional feature: adding a SHARED
> clause to the CREATE VARIABLE statement in order to be able to create
> memory spaces that could be shared by all connections on the database and
> accessible in SQL and PL, under the protection of ACL. But that's another
> story ;-)
>

sure, it is another story :-).

Thank you for review - I'll try to fix bugs this week.

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 25 Dec 2019 22:45:49 +0100

Hi

> of the statement, like for ON COMMIT, but I have not found a satisfying

I propose compromise solution - I introduced new not reserved keyword
"TRANSACTIONAL". User can use TRANSACTION or TRANSACTIONAL. It is similar
relation like "TEMP" or "TEMPORAL"

> reports : ERROR:  unexpected object class 4287

should be fixed

>   alter default privileges in schema public grant write on variables to

should be fixed

>   Owner   | Schema | Type |    Access privileges

I use ACL_READ constant in my patch. The value of ACL_READ is defined
elsewhere. So the changing from S to R should be done by separate patch and
by separate discussion.

> belongs to the extension. As a result, one of course gets an error at

should be fixed now

> "Is nullable" being aside "Default"

I implemented your proposal

> function that can really be called by users ?

should be fixed

> concept, aside the new or the modified statements.

We can finalize documentation little bit later, when will be clear what
related functionality is implemented.

updated patch attached

> accessible in SQL and PL, under the protection of ACL. But that's another

Thank you very much for review

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 26 Dec 2019 19:13:41 +0100

Hi

fresh rebase

Regards

Pavel

---

From: Philippe BEAUDOIN <[email-masked]>
Date: Mon, 30 Dec 2019 16:26:10 +0000

Implements feature:       tested, passed

Hi Pavel,

I have tested the latest version of your patch.
Both issues I reported are now fixed. And you largely applied my proposals. That's great !

I have also spent some time to review more closely the documentation. I will send you a direct mail with an attached file for some minor comments on this topic.

Except these documentation remarks to come, I haven't any other issue or suggestion to report.
Note that I have not closely looked at the C code itself. But may be some other reviewers have already done that job.
If yes, my feeling is that the patch could soon be set as "Ready for commiter".

Best regards. Philippe.

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 30 Dec 2019 21:05:40 +0100

Hi

po 30. 12. 2019 v 17:27 odesílatel Philippe BEAUDOIN <[email-masked]>
napsal:

> I have also spent some time to review more closely the documentation. I
> will send you a direct mail with an attached file for some minor comments
> on this topic.
>
> Note that I have not closely looked at the C code itself. But may be some
> other reviewers have already done that job.

Thank you very much for your comments, and notes. Updated patch attached.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 17 Jan 2020 22:10:35 +0100

Hi

rebase

---

From: Tomas Vondra <[email-masked]>
Date: Wed, 22 Jan 2020 00:41:41 +0100

Hi,

I did a quick review of this patch today, so let me share a couple of
comments.

Firstly, the patch is pretty large - it has ~270kB. Not the largest
patch ever, but large. I think breaking the patch into smaller pieces
would significantly improve the chnce of it getting committed.

Is it possible to break the patch into smaller pieces that could be
applied incrementally? For example, we could move the "transactional"
behavior into a separate patch, but it's not clear to me how much code
would that actually move to that second patch. Any other parts that
could be moved to a separate patch?

I see one of the main contention points was a rather long discussion
about transactional vs. non-transactional behavior. I agree with Pavel's
position that the non-transactional behavior should be the default,
simply because it's better aligned with what the other databases are
doing (and supporting migrations seems like one of the main use cases
for this feature).

I do understand it may not be suitable for some other use cases,
mentioned by Fabien, but IMHO it's fine to require explicit
specification of transactional behavior. Well, we can't have both as
default, and I don't think there's an obvious reason why it should be
the other way around.

Now, a bunch of comments about the code (some of that nitpicking):

1) This hunk in doc/src/sgml/catalogs.sgml still talks about "table
creation" instead of schema creation:

      <row>
       <entry><structfield>vartypmod</structfield></entry>
       <entry><type>int4</type></entry>
       <entry></entry>
       <entry>
        <structfield>vartypmod</structfield> records type-specific data
        supplied at table creation time (for example, the maximum
        length of a <type>varchar</type> column).  It is passed to
        type-specific input functions and length coercion functions.
        The value will generally be -1 for types that do not need <structfield>vartypmod</structfield>.
       </entry>
      </row>

2) This hunk in doc/src/sgml/ref/alter_default_privileges.sgml uses
"role_name" instead of "variable_name"

     GRANT { READ | WRITE | ALL [ PRIVILEGES ] }
     ON VARIABLES
     TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]

3) I find the syntax in create_variable.sgml a bit too over-complicated:

<synopsis>
CREATE [ { TEMPORARY | TEMP } ] [ { TRANSACTIONAL | TRANSACTION } ] 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 { TRANSACTIONAL | TRANSACTION } END RESET } ]
</synopsis>

Do we really need both TRANSACTION and TRANSACTIONAL? Why not just one
that we already have in the grammar (i.e. TRANSACTION)?

4) I think we should rename schemavariable.h to schema_variable.h.

5) objectaddress.c has extra line after 'break;' in one switch.

6) The comment is wrong:

     /*
      * Find the ObjectAddress for a type or domain
      */
     static ObjectAddress
     get_object_address_variable(List *object, bool missing_ok)

7) I think the code/comments are really inconsistent in talking about
"variables" and "schema variables". For example in objectaddress.c we do
these two things:

         case OCLASS_VARIABLE:
             appendStringInfoString(&buffer, "schema variable");
             break;

vs.

         case DEFACLOBJ_VARIABLE:
                                    " on variables");
             break;

That's going to be confusing for people.

8) I'm rather confused by CMD_PLAN_UTILITY, which seems to be defined
merely to support LET. I'm not sure why that's necessary (Why wouldn't
CMD_UTILITY be sufficient?).

Having to add conditions checking for CMD_PLAN_UTILITY to various places
in planner.c is rather annoying, and I wonder how likely it's this will
unnecessarily break external code in extensions etc.

9) This comment in planner.c seems obsolete (not updated to reflect
addition of the CMD_PLAN_UTILITY check):

   /*
    * If this is an INSERT/UPDATE/DELETE, and we're not being called from
    * inheritance_planner, add the ModifyTable node.
    */
   if (parse->commandType != CMD_SELECT && parse->commandType != CMD_PLAN_UTILITY && !inheritance_update)

10) I kinda wonder what happens when a function is used in a WHERE
condition, but it depends on a variable and alsu mutates it on each
call ...

11) I think Query->hasSchemaVariable (in analyze.c) should be renamed to
hasSchemaVariables (which reflects the other fields referring to things
like window functions etc.)

12) I find it rather suspicious that we make decisions in utility.c
solely based on commandType (whether it's CMD_UTILITY or not). IMO
it's pretty strange/ugly that T_LetStmt can be both CMD_UTILITY and
CMD_PLAN_UTILITY:

     case T_LetStmt:
         {
             if (pstmt->commandType == CMD_UTILITY)
                 doLetStmtReset(pstmt);
             else
             {
                 Assert(pstmt->commandType == CMD_PLAN_UTILITY);
                 doLetStmtEval(pstmt, params, queryEnv, queryString);
             }

             if (completionTag)
                 strcpy(completionTag, "LET");
         }
         break;

13) Not sure why we moved DO_TABLE in addBoundaryDependencies
(pg_dump.c), seems unnecessary:

      case DO_CONVERSION:
-    case DO_TABLE:
+    case DO_VARIABLE:
      case DO_ATTRDEF:
+    case DO_TABLE:
      case DO_PROCLANG:

14) namespace.c defines VariableIsVisible twice:

     extern bool VariableIsVisible(Oid relid);
     ...
     extern bool VariableIsVisible(Oid varid);

15) I'd say lookup_variable and identify_variable should use camelcase
just like the other functions in the same file.

regards

-- 
Tomas Vondra                  http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 24 Jan 2020 06:08:45 +0100

> could be moved to a separate patch?

I am sending two patches - 0001 - schema variables, 0002 - transactional
variables

fixed

> | PUBLIC } [, ...] [ WITH GRANT OPTION ]

I think so this is correct

> CREATE [ { TEMPORARY | TEMP } ] [ { TRANSACTIONAL | TRANSACTION } ]
> 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 {
> TRANSACTIONAL | TRANSACTION } END RESET } ]
> </synopsis>
>

It was a Philippe's wish - the implementation is simple, and it is similar
like TEMP, TEMPORARY. I have not any opinion about it.

> 4) I think we should rename schemavariable.h to schema_variable.h.

done

fixed

fixed

fixed

> CMD_UTILITY be sufficient?).

Currently out utility statements cannot to hold a execution plan, and
cannot be prepared.

so this enhancing is motivated mainly by performance reasons. I would to
allow any SELECT query there, not just expressions only (see a limits of
CALL statement)

> CMD_PLAN_UTILITY && !inheritance_update)

"If this is an INSERT/UPDATE/DELETE," is related to parse->commandType !=
CMD_SELECT && parse->commandType != CMD_PLAN_UTILITY

> like window functions etc.)
done

fixed

fixed

fixed

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 26 Jan 2020 18:26:16 +0100

>                  strcpy(completionTag, "LET");
It looks strange, but it has sense, because the LET stmt supports reset to
default value.

I can write

1. LET var = DEFAULT;
2. LET var = (query);

In first case I have not any query, that I can assign, and in this case the
LET statement is really only UTILITY.

I did comment there

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 7 Feb 2020 17:09:47 +0100

Hi

rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 10 Feb 2020 19:47:35 +0100

Hi

another rebase, fix \dV statement (for 0001 patch)

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 15 Feb 2020 09:05:01 +0100

rebase

Pavel

---

From: remi duval <[email-masked]>
Date: Wed, 26 Feb 2020 14:53:55 +0000

The following review has been posted through the commitfest application:
make installcheck-world:  not tested
Implements feature:       tested, passed
Spec compliant:           tested, failed
Documentation:            tested, failed

Hello Pavel

First thanks for working on this patch cause it might be really helpful for those of us trying to migrate PL code between RDBMs.

I tried your patch for migrating an Oracle package body to PL/pgSQL after also testing a solution using set_config and current_setting (which works but I'm not really satisfied by this workaround solution).

So I compiled latest postgres sources from github on Linux (redhat 7.7) using only your patch number 1 (I did not try the second part of the patch).

For my use-case it's working great, performances are excellent (compared to other solution for porting "package variables").
I did not test all the features involved by the patch (especially ALTER variable).

I have some feedback however :

1) Failure when using pg_dump 13 on a 12.1 database

When exporting a 12.1 database using pg_dump from the latest development sources I have an error regarding variables export 

[pg12@TST-LINUX-PG-03 ~]$ /opt/postgres12/pg12/bin/pg_dump -h localhost -p 5432 -U postgres -f dump_pg12.sql database1
pg_dump: error: query failed: ERROR:  relation "pg_variable" does not exist
LINE 1: ...og.pg_get_expr(v.vardefexpr,0) as vardefexpr FROM pg_variabl...
                                                             ^
pg_dump: error: query was: SELECT v.tableoid, v.oid, v.varname, v.vareoxaction, v.varnamespace, 
(SELECT rolname FROM pg_catalog.pg_roles WHERE oid = varowner) AS rolname
, (SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM (SELECT acl, row_n 
FROM pg_catalog.unnest(coalesce(v.varacl,pg_catalog.acldefault('V',v.varowner)))
 WITH ORDINALITY AS perm(acl,row_n) 
 WHERE NOT EXISTS ( SELECT 1 FROM pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault('V',v.varowner))) AS init(init_acl) 
                    WHERE acl = init_acl)) as foo) as varacl, ...:

I think that it should have worked anyway cause the documentation states :
https://www.postgresql.org/docs/current/upgrading.html
"It is recommended that you use the pg_dump and pg_dumpall programs from the newer version of PostgreSQL, to take advantage of enhancements that might have been made in these programs." (that's what I did here)

I think there should be a way to avoid dumping the variable if they don't exist, should'nt it ?

2) Displaying the variables + completion
I created 2 variables using :
CREATE VARIABLE my_pkg.g_dat_deb varchar(11);
CREATE VARIABLE my_pkg.g_dat_fin varchar(11);
When I try to display them, I can only see them when prefixing by the schema :
bdd13=> \dV
"Did not find any schema variables."
bdd13=> \dV my_pkg.*
                                               List of variables
   Schema   |      Name      |         Type          | Is nullable | Default | Owner | Transactional end action
------------+----------------+-----------------------+-------------+---------+-------+--------------------------
 my_pkg| g_dat_deb   | character varying(11) | t           |         | myowner   |
 my_pkg| g_dat_fin     | character varying(11) | t           |         | myowner   |
(3 rows)

bdd13=> \dV my_pkg
Did not find any schema variable named "my_pck".
NB : Using this template, functions are returned, maybe variables should also be listed ? (here by querying on "my_pkg%")
cts_get13=> \dV my_p [TAB]
=> completion using [TAB] key is not working

Is this normal that I cannot see all the variables when not specifying any schema ?
Also the completion works for functions, but not for variable.
That's just some bonus but it might be good to have it.

I think the way variables are listed using \dV should match with \df for querying functions

3) Any way to define CONSTANTs ?
We already talked a bit about this subject and also Gilles Darold introduces it in this mailing-list topic but I'd like to insist on it.
I think it would be nice to have a way to say that a variable should not be changed once defined.
Maybe it's hard to implement and can be implemented later, but I just want to know if this concern is open.

Otherwise the documentation looks good to me.

Regards

Rémi

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 26 Feb 2020 21:40:02 +0100

st 26. 2. 2020 v 15:54 odesílatel remi duval <[email-masked]> napsal:

> First thanks for working on this patch cause it might be really helpful
> for those of us trying to migrate PL code between RDBMs.
>
> I tried your patch for migrating an Oracle package body to PL/pgSQL after
> also testing a solution using set_config and current_setting (which works
> but I'm not really satisfied by this workaround solution).
>
> So I compiled latest postgres sources from github on Linux (redhat 7.7)
> using only your patch number 1 (I did not try the second part of the patch).
>
> For my use-case it's working great, performances are excellent (compared
> to other solution for porting "package variables").

ALTER VARIABLE is not implemented yet

> When exporting a 12.1 database using pg_dump from the latest development
> sources I have an error regarding variables export
>
> [pg12@TST-LINUX-PG-03 ~]$ /opt/postgres12/pg12/bin/pg_dump -h localhost
> -p 5432 -U postgres -f dump_pg12.sql database1
> FROM
> pg_catalog.unnest(coalesce(v.varacl,pg_catalog.acldefault('V',v.varowner)))
>  WHERE NOT EXISTS ( SELECT 1 FROM
> pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault('V',v.varowner)))
> AS init(init_acl)
> "It is recommended that you use the pg_dump and pg_dumpall programs from
> the newer version of PostgreSQL, to take advantage of enhancements that
> might have been made in these programs." (that's what I did here)
>

There was a protection against dump 11, but now it should be Postgres 12.
Fixed.

>    Schema   |      Name      |         Type          | Is nullable |
> Default | Owner | Transactional end action
>

it is ok - it depends on SEARCH_PATH value

> NB : Using this template, functions are returned, maybe variables should
> also be listed ? (here by querying on "my_pkg%")

fixed

> We already talked a bit about this subject and also Gilles Darold
> introduces it in this mailing-list topic but I'd like to insist on it.

This topic is open. I tried to play with it. The problem is syntax. When I
try to reproduce syntax from PLpgSQL, then I need to introduce new reserved
keyword. So my initial idea was wrong.
We need to open discussion about implementable syntax. For this moment you
can use a workaround - any schema variable without WRITE right is constant.
Implementation is easy. Design of syntax is harder.

please see attached patch

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 27 Feb 2020 15:37:48 +0100

Hi

> to know if this concern is open.

I played little bit with it and I didn't find any nice solution, but maybe
I found the solution. I had ideas about some variants, but almost all time
I had a problem with parser's shifts because all potential keywords are not
reserved.

last variant, but maybe best is using keyword WITH

So the syntax can looks like

CREATE [ TEMP ] VARIABLE varname [ AS ] type [ NOT NULL ] [ DEFAULT
expression ] [ WITH [ OPTIONS ] '(' ... ')' ] ]

What do you think about this syntax? It doesn't need any new keyword, and
it easy to enhance it.

CREATE VARIABLE foo AS int DEFAULT 10 WITH OPTIONS ( CONSTANT);

?

Regards

Pavel

---

From: DUVAL REMI <[email-masked]>
Date: Thu, 27 Feb 2020 14:59:12 +0000

Hello Pavel.

That looks pretty good to me !

I’m adding Philippe Beaudoin who was also interested in this topic.

Recap : We were looking for a way to separate variable from constants in the “Schema Variables” proposition from Pavel.
Pavel was saying that there are some limitations regarding the keywords we can use, as the community don’t want to introduce too much new keywords in Postgres SQL (PL/pgSQL is a different list of keywords).
“CONSTANT” is not a keyword in SQL for Now (though it is one in PL/pgSQL).
Pavel’s syntax allow to use it as a keyword in the “WITH OPTIONS” clause that is already supported.
… I think it’s a good idea.

The list of keywords is defined in : postgresql\src\include\parser\kwlist.h

Pavel, I saw that in DB2, those variables are called “Global Variables”, is it something we can consider changing, or do you prefer to keep using the “Schema Variable” name ?

De : Pavel Stehule [mailto:[email-masked]]
Envoyé : jeudi 27 février 2020 15:38
À : DUVAL REMI <[email-masked]>
Cc : PostgreSQL Hackers <[email-masked]>
Objet : Re: proposal: schema variables

Hi

I played little bit with it and I didn't find any nice solution, but maybe I found the solution. I had ideas about some variants, but almost all time I had a problem with parser's shifts because all potential keywords are not reserved.

CREATE [ TEMP ] VARIABLE varname [ AS ] type [ NOT NULL ] [ DEFAULT expression ] [ WITH [ OPTIONS ] '(' ... ')' ] ]

What do you think about this syntax? It doesn't need any new keyword, and it easy to enhance it.

?

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 27 Feb 2020 16:09:44 +0100

čt 27. 2. 2020 v 15:59 odesílatel DUVAL REMI <[email-masked]> napsal:

> Recap : We were looking for a way to separate variable from constants in
> the “Schema Variables” proposition from Pavel.
>
> Pavel was saying that there are some limitations regarding the keywords we
> can use, as the community don’t want to introduce too much new keywords in
> Postgres SQL (PL/pgSQL is a different list of keywords).
>
> Pavel, I saw that in DB2, those variables are called “Global Variables”,
> is it something we can consider changing, or do you prefer to keep using
> the “Schema Variable” name ?
>

It is most hard question. Global variables has sense, but when we will use
it in plpgsql, then this name can be little bit confusing. Personally I
prefer "schema variable" although my opinion is not too strong. This name
more signalize so this is more generic, more database related than some
special kind of plpgsql variables. Now, I think so maybe is better to use
schema variables, because there is different syntax then global temp
tables. Variables are global by design. So in this moment I prefer the name
"schema variables". It can be used as global variables in plpgsql, but it
is one case.

Pavel

>
>
>
> *De :* Pavel Stehule [mailto:[email-masked]]
> *Envoyé :* jeudi 27 février 2020 15:38
> *À :* DUVAL REMI <[email-masked]>
> *Cc :* PostgreSQL Hackers <[email-masked]>
> *Objet :* Re: proposal: schema variables
>
>
>
>
>
> Hi
>
>
>
>

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 28 Feb 2020 16:30:28 +0100

> CREATE VARIABLE foo AS int DEFAULT 10 WITH OPTIONS ( CONSTANT);

After some more thinking and because in other patch I support syntax CREATE
TRANSACTION VARIABLE ... I change my opinion and implemented support for
syntax CREATE IMMUTABLE VARIABLE for define constants.

See attached patch

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 29 Feb 2020 10:09:47 +0100

pá 28. 2. 2020 v 16:30 odesílatel Pavel Stehule <[email-masked]>
napsal:

>> I played little bit with it and I didn't find any nice solution, but
>> maybe I found the solution. I had ideas about some variants, but almost all
>> time I had a problem with parser's shifts because all potential keywords
>> are not reserved.
>>
> After some more thinking and because in other patch I support syntax
> CREATE TRANSACTION VARIABLE ... I change my opinion and implemented support
> for

second try to fix pg_dump

Regards

Pavel

---

From: Asif Rehman <[email-masked]>
Date: Thu, 5 Mar 2020 19:10:49 +0500

Hi Pavel,

I have been reviewing the latest patch (schema-variables-20200229.patch.gz)
and here are few comments:

1- There is a compilation error, when compiled with --with-llvm enabled on
CentOS 7.

llvmjit_expr.c: In function ‘llvm_compile_expr’:
llvmjit_expr.c:1090:5: warning: initialization from incompatible pointer
type [enabled by default]
     build_EvalXFunc(b, mod, "ExecEvalParamVariable",
     ^
llvmjit_expr.c:1090:5: warning: (near initialization for ‘(anonymous)[0]’)
[enabled by default]
llvmjit_expr.c:1090:5: warning: initialization from incompatible pointer
type [enabled by default]
llvmjit_expr.c:1090:5: warning: (near initialization for ‘(anonymous)[0]’)
[enabled by default]
llvmjit_expr.c:1090:5: warning: initialization from incompatible pointer
type [enabled by default]
llvmjit_expr.c:1090:5: warning: (near initialization for ‘(anonymous)[0]’)
[enabled by default]
llvmjit_expr.c:1090:5: warning: passing argument 5 of ‘build_EvalXFuncInt’
from incompatible pointer type [enabled by default]
llvmjit_expr.c:60:21: note: expected ‘struct ExprEvalStep *’ but argument
is of type ‘LLVMValueRef’
 static LLVMValueRef build_EvalXFuncInt(LLVMBuilderRef b, LLVMModuleRef mod,
                     ^
llvmjit_expr.c:1092:29: error: ‘i’ undeclared (first use in this function)
     LLVMBuildBr(b, opblocks[i + 1]);
                             ^
llvmjit_expr.c:1092:29: note: each undeclared identifier is reported only
once for each function it appears in
make[2]: *** [llvmjit_expr.o] Error 1

After looking into it, it turns out that:
- parameter order was incorrect in build_EvalXFunc()
- LLVMBuildBr() is using the undeclared variable 'i' whereas it should be
using 'opno'.

2- Similarly, If the default expression is referencing a function or object,
dependency should be marked, so if the function is not dropped silently.
otherwise, a cache lookup error will come.

postgres=# create or replace function foofunc() returns timestamp as $$
begin return now(); end; $$ language plpgsql;
CREATE FUNCTION
postgres=# create schema test;
CREATE SCHEMA
postgres=# create variable test.v1 as timestamp default foofunc();
CREATE VARIABLE
postgres=# drop function foofunc();
DROP FUNCTION
postgres=# select test.v1;
ERROR:  cache lookup failed for function 16437

3- Variable DEFAULT expression is apparently being evaluated at the time of
first access. whereas I think that It should be at the time of variable
creation. consider the following example:

postgres=# create variable test.v2 as timestamp default now();
CREATE VARIABLE
postgres=# select now();
              now
-------------------------------
 2020-03-05 12:13:29.775373+00
(1 row)
postgres=# select test.v2;
             v2
----------------------------
 2020-03-05 12:13:37.192317 -- I was expecting this to be earlier than the
above timestamp.
(1 row)

postgres=# select test.v2;
             v2
----------------------------
 2020-03-05 12:13:37.192317
(1 row)
postgres=# let test.v2 = default;
LET
postgres=# select test.v2;
             v2
----------------------------
 2020-03-05 12:14:07.538615
(1 row)

To continue my testing of the patch I made few fixes for the above-mentioned
comments. The patch for those changes is attached if it could be of any use.

--
Asif Rehman
Highgo Software (Canada/China/Pakistan)
URL : www.highgo.ca

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 5 Mar 2020 18:54:29 +0100

> ERROR:  cache lookup failed for function 16437
Thank you for this analyze and patches. I merged them to attached patch

>  2020-03-05 12:13:37.192317 -- I was expecting this to be earlier than
> the above timestamp.
> (1 row)
>
This is expected and wanted - same behave has plpgsql variables.

CREATE OR REPLACE FUNCTION public.foo()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
declare x timestamp default current_timestamp;
begin
  raise notice '%', x;
end;
$function$

postgres=# select foo();
NOTICE:  2020-03-05 18:49:12.465054
┌─────┐
│ foo │
╞═════╡
│     │
└─────┘
(1 row)

postgres=# select foo();
NOTICE:  2020-03-05 18:49:13.255197
┌─────┐
│ foo │
╞═════╡
│     │
└─────┘
(1 row)

You can use

CREATE VARIABLE cuser AS text DEFAULT session_user;

Has not any sense to use a value from creating time

And a analogy with CREATE TABLE

CREATE TABLE fooo(a timestamp DEFAULT current_timestamp) -- there is not a
create time timestamp

I fixed buggy behave of IMMUTABLE variables

Regards

Pavel

---

From: DUVAL REMI <[email-masked]>
Date: Fri, 6 Mar 2020 15:44:14 +0000

Hello Pavel

I tested your patch again and I think things are better now, close to perfect for me.

1)      Patch review

-          We can define CONSTANTs with CREATE IMMUTABLE VARIABLE … I’m really pleased with this

-          The previous bug I mentioned to you by private mail (see detail below) has been fixed and a non-regression test case has been added for it.

-          I’m now able to export a 12.1 database using pg_dump from the latest git HEAD (internal version 130000).

NB: the condition is “if internal_version < 130000 => don’t try to export any schema variable”.

Also I was able to test a use case for a complex Oracle package I migrated to PostgreSQL (it has a total of 194 variables and constants in it !).
The Oracle package has been migrated to a PostgreSQL schema with one routine per Oracle subprogram.
I’m able to run my business test case using schema variables on those routines and it’s giving me the expected result.

NB: Previous bug was
database1=> CREATE VARIABLE T0_var AS char(14);
CREATE VARIABLE
database1=> CREATE IMMUTABLE VARIABLE Taille_var AS numeric DEFAULT 14;
CREATE VARIABLE
database1=> LET T0_var = LPAD('999', trunc(Taille_var::NUMERIC)::INTEGER, '0');
ERROR:  schema variable "taille_var" is declared IMMUTABLE
database1=> LET T0_var = LPAD('999', trunc(Taille_var::NUMERIC)::INTEGER, '0');
ERROR:  variable "taille_var" has not valid content

ð  It’s now fixed !

2)      Regarding documentation

It’s pretty good except some small details :

-          sql-createvariable.html => IMMUTABLE parameter : The value of the variable cannot be changed. => I think an article is needed here (the)

-          sql-createvariable.html => ON COMMIT DROP : The ON COMMIT DROP clause specifies the bahaviour of      temporary => behaviour
Also there are “tabs” between words (here between “of” and “temporary”), making the paragraph look strange.

-          sql-createvariable.html => Maybe we should mention that the IMMUTABLE parameter (CREATE IMMUTABLE VARIABLE …) is the way to define global CONSTANTs, because people will look for a way to create global constants in the documentation and it would be good if they can find the CONSTANT word in it.
Also maybe it’s worth mentioning that “schema variables” relates to “global variables” (for the same purpose of people searching in the documentation).

-          sql-altervariable.html => sentence “These restrictions enforce that altering the owner doesn't do anything you couldn't do by dropping and recreating the variable.“ => not sure I understand this one. Isn’t it “does not do anything you could do” instead of “you couln’t do” .. but maybe it’s me

Otherwise, this is a really nice feature and I’m looking forward to have it in PostgreSQL.

Thanks a lot

Duval Rémi

De : Pavel Stehule [mailto:[email-masked]]
Envoyé : jeudi 5 mars 2020 18:54
À : Asif Rehman <[email-masked]>
Cc : DUVAL REMI <[email-masked]>; PostgreSQL Hackers <[email-masked]>
Objet : Re: proposal: schema variables

čt 5. 3. 2020 v 15:11 odesílatel Asif Rehman <[email-masked]<mailto:[email-masked]>> napsal:

On Sat, Feb 29, 2020 at 2:10 PM Pavel Stehule <[email-masked]<mailto:[email-masked]>> wrote:

pá 28. 2. 2020 v 16:30 odesílatel Pavel Stehule <[email-masked]<mailto:[email-masked]>> napsal:

čt 27. 2. 2020 v 15:37 odesílatel Pavel Stehule <[email-masked]<mailto:[email-masked]>> napsal:

Hi

After some more thinking and because in other patch I support syntax CREATE TRANSACTION VARIABLE ... I change my opinion and implemented support for
syntax CREATE IMMUTABLE VARIABLE for define constants.

Regards

Pavel

See attached patch

Regards

Pavel

?

Regards

Pavel

Hi Pavel,

llvmjit_expr.c: In function ‘llvm_compile_expr’:
llvmjit_expr.c:1090:5: warning: initialization from incompatible pointer type [enabled by default]
     build_EvalXFunc(b, mod, "ExecEvalParamVariable",
     ^
llvmjit_expr.c:1090:5: warning: (near initialization for ‘(anonymous)[0]’) [enabled by default]
llvmjit_expr.c:1090:5: warning: initialization from incompatible pointer type [enabled by default]
llvmjit_expr.c:1090:5: warning: (near initialization for ‘(anonymous)[0]’) [enabled by default]
llvmjit_expr.c:1090:5: warning: initialization from incompatible pointer type [enabled by default]
llvmjit_expr.c:1090:5: warning: (near initialization for ‘(anonymous)[0]’) [enabled by default]
llvmjit_expr.c:1090:5: warning: passing argument 5 of ‘build_EvalXFuncInt’ from incompatible pointer type [enabled by default]
llvmjit_expr.c:60:21: note: expected ‘struct ExprEvalStep *’ but argument is of type ‘LLVMValueRef’
 static LLVMValueRef build_EvalXFuncInt(LLVMBuilderRef b, LLVMModuleRef mod,
                     ^
llvmjit_expr.c:1092:29: error: ‘i’ undeclared (first use in this function)
     LLVMBuildBr(b, opblocks[i + 1]);
                             ^
llvmjit_expr.c:1092:29: note: each undeclared identifier is reported only once for each function it appears in
make[2]: *** [llvmjit_expr.o] Error 1

postgres=# create or replace function foofunc() returns timestamp as $$ begin return now(); end; $$ language plpgsql;
CREATE FUNCTION

 2020-03-05 12:13:37.192317 -- I was expecting this to be earlier than the above timestamp.
(1 row)

postgres=# select foo();

postgres=# select foo();

You can use

CREATE TABLE fooo(a timestamp DEFAULT current_timestamp) -- there is not a create time timestamp

Regards

Pavel

--
Asif Rehman
Highgo Software (Canada/China/Pakistan)
URL : www.highgo.ca<http://www.highgo.ca>

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 6 Mar 2020 19:54:35 +0100

pá 6. 3. 2020 v 16:44 odesílatel DUVAL REMI <[email-masked]> napsal:

> *1)      **Patch review*
> -          The previous bug I mentioned to you by private mail (see
> detail below) has been fixed and a non-regression test case has been added
> for it.
>
> -          I’m now able to export a 12.1 database using pg_dump from the
> latest git HEAD (internal version 130000).
>
> Also I was able to test a use case for a complex Oracle package I migrated
> to PostgreSQL (it has a total of 194 variables and constants in it !).
>
> I’m able to run my business test case using schema variables on those
> routines and it’s giving me the expected result.
>
>
>
> NB: Previous bug was
>
> *ERROR:  schema variable "taille_var" is declared IMMUTABLE*
> *ERROR:  variable "taille_var" has not valid content*
>
> ð  It’s now fixed !
>
>
>
> *2)      **Regarding documentation *
>
>
>
> -          sql-createvariable.html => IMMUTABLE parameter : The value of
> *the* variable cannot be changed. => I think an article is needed here
> (the)
>

fixed

-          sql-createvariable.html => ON COMMIT DROP : The ON COMMIT DROP
> clause specifies the bahaviour of      temporary => behaviour

fixed

> -          sql-createvariable.html => Maybe we should mention that the
> IMMUTABLE parameter (CREATE IMMUTABLE VARIABLE …) is the way to define
> global CONSTANTs, because people will look for a way to create global
> constants in the documentation and it would be good if they can find the
> CONSTANT word in it.
> Also maybe it’s worth mentioning that “schema variables” relates to
> “global variables” (for the same purpose of people searching in the
> documentation).
>
probably it should be somewhere
https://www.postgresql.org/docs/current/plpgsql-porting.html

I wrote note there

> -          sql-altervariable.html => sentence “These restrictions enforce
> that altering the owner doesn't do anything you couldn't do by dropping and
> recreating the variable.“ => not sure I understand this one. Isn’t it
> “does not do anything you could do” instead of “you couln’t do” .. but
> maybe it’s me
>
This sentence is used more times (from alter_view0

To alter the owner, you must also be a direct or indirect member of the new
   owning role, and that role must have <literal>CREATE</literal> privilege
on
   the view's schema.  (These restrictions enforce that altering the owner
   doesn't do anything you couldn't do by dropping and recreating the view.
   However, a superuser can alter ownership of any view anyway.)

Thank you very much

updated patch attached

> *Envoyé :* jeudi 5 mars 2020 18:54
> *À :* Asif Rehman <[email-masked]>
> *Cc :* DUVAL REMI <[email-masked]>; PostgreSQL Hackers <
> [email-masked]>
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 2987a555a3..8c9bd28aa9 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5782,6 +5782,19 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE;
 </programlisting>
     </para>
    </sect3>
+
+   <sect3>
+    <title><command>Global variables and constants</command></title>
+
+    <para>
+     The <application>PL/pgSQL</application> language has not packages
+     and then it has not package variables and package constants. The
+     <productname>PostgreSQL</productname> has schema variables and
+     immutable schema variables. The schema variables can be created
+     by <command>CREATE VARIABLE</command> described in <xref
+     linkend="sql-createvariable"/>.
+    </para>
+   </sect3>
   </sect2>
 
   <sect2 id="plpgsql-porting-appendix">
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index 35a14c7c3c..1b1883a11a 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -58,7 +58,7 @@ CREATE [ { TEMPORARY | TEMP } ] [ IMMUTABLE ] VARIABLE [ IF NOT EXISTS ] <replac
     <term><literal>IMMUTABLE</literal></term>
     <listitem>
      <para>
-      The value of variable cannot be changed.
+      The value of the variable cannot be changed.
      </para>
     </listitem>
    </varlistentry>
@@ -128,13 +128,12 @@ CREATE [ { TEMPORARY | TEMP } ] [ IMMUTABLE ] VARIABLE [ IF NOT EXISTS ] <replac
     <term><literal>ON COMMIT DROP</literal>, <literal>ON TRANSACTIONAL END RESET</literal></term>
     <listitem>
      <para>
-      The <literal>ON COMMIT DROP</literal> clause specifies the bahaviour of
-      temporary schema variable at transaction commit. With this clause the
+      The <literal>ON COMMIT DROP</literal> clause specifies the behaviour
+      of temporary schema variable at transaction commit. With this clause the
       variable is dropped at commit time. The clause is only allowed
-      for temporary variables.
-      The <literal>ON TRANSACTIONAL END RESET</literal> clause enforces the
-      variable to be reset to its default value when the transaction is either
-      commited or rolled back.
+      for temporary variables. The <literal>ON TRANSACTIONAL END RESET</literal>
+      clause enforces the variable to be reset to its default value when
+      the transaction is either commited or rolled back.
      </para>
     </listitem>
    </varlistentry>

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 7 Mar 2020 22:15:15 +0100

Hi

I fixed the some ugly parts of this patch - now the LET x = DEFAULT, LET x
= NULL is processed by more usual way.
Statement LET is doesn't switch between STMT_UTILITY and STMT_PLAN_UTILITY
like before. It is only STMT_PLAN_UTILITY statement.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 8 Mar 2020 19:12:07 +0100

> like before. It is only STMT_PLAN_UTILITY statement.

I did some cleaning - I mainly replaced CMD_PLAN_UTILITY by CMD_LET because
there is not another similar statement in queue.

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 13 Mar 2020 19:44:33 +0100

Hi

ne 8. 3. 2020 v 19:12 odesílatel Pavel Stehule <[email-masked]>
napsal:

>> I fixed the some ugly parts of this patch - now the LET x = DEFAULT, LET
>> x = NULL is processed by more usual way.
>> Statement LET is doesn't switch between STMT_UTILITY and
>> STMT_PLAN_UTILITY like before. It is only STMT_PLAN_UTILITY statement.
>>
>
> I did some cleaning - I mainly replaced CMD_PLAN_UTILITY by CMD_LET
> because there is not another similar statement in queue.
>

 another cleaning - I repleaced CMD_LET by CMD_SELECT_UTILITY. This name is
more illustrative, and little bit cleaned code.

CMD_SELECT_UTILITY is mix of CMD_SELECT and CMD_UTILITY. It allows PREPARE
and parametrizations like CMD_SELECT. But execution is like CMD_UTILITY by
own utility routine with explicit dest receiver setting. This design
doesn't need to introduce new executor and planner nodes (like ModifyTable
and ModifyTablePath).

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Wed, 18 Mar 2020 06:58:30 +0100

Hi

fresh patch - rebase and fix issue reported by Remi - broken usage CREATE
VARIABLE inside PLpgSQL

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 20 Mar 2020 08:18:45 +0100

čt 19. 3. 2020 v 10:43 odesílatel DUVAL REMI <[email-masked]> napsal:

> Hello
>
>
>
> I played around with the ALTER VARIABLE statement to make sure it’s OK and
> it seems fine to me.
>
>
>
> Another last thing before commiting.
>
>
>
> I agree with Thomas Vondra, that this part might/should be simplified :
>
>
>
> [ { ON COMMIT DROP | ON { TRANSACTIONAL | TRANSACTION } END RESET } ]
>
>
>
> I would only allow “ON TRANSACTION END RESET”.
>
> I think we don’t need both here.
>
> Philippe Beaudoin was indeed talking about the TRANSACTIONAL keyword, but
> that would have make sense (and I think that’s what he meant) , if you
> could do something like “CREATE [NON] TRANSACTIONAL VARIABLE …”.
>
> But here I don’t think that the ON TRANSACTIONAL END RESET has any sense
> in English, and it only complicates the syntax.
>
>
>
> Maybe Thomas Vondra (if it’s him) would be more inclined to commit the
> patch if it has this more simple syntax has he requested.
>
>
>
> What do you think ?
>

I removed TRANSACTIONAL from this clause, see attachement change.diff

Updated patch attached.

I hope it would be the last touch, making it fully ready for a commiter.

Thank you very much for review and testing

Pavel

diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index 1b1883a11a..cf175e05c6 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -22,7 +22,7 @@ PostgreSQL documentation
  <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 { TRANSACTIONAL | TRANSACTION } END RESET } ]
+    [ NOT NULL ] [ DEFAULT <replaceable class="parameter">default_expr</replaceable> ] [ { ON COMMIT DROP | ON { TRANSACTION } END RESET } ]
 </synopsis>
  </refsynopsisdiv>
  <refsect1>
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 744342733d..c135a35d07 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4567,7 +4567,6 @@ OptSchemaVarDefExpr: DEFAULT b_expr					{ $$ = $2; }
 
 OnEOXActionOption:  ON COMMIT DROP					{ $$ = VARIABLE_EOX_DROP; }
 			| ON TRANSACTION END_P RESET			{ $$ = VARIABLE_EOX_RESET; }
-			| ON TRANSACTIONAL END_P RESET			{ $$ = VARIABLE_EOX_RESET; }
 			| /*EMPTY*/								{ $$ = VARIABLE_EOX_NOOP; }
 		;
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d39bcfe9cf..d26b0efcd9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5978,7 +5978,7 @@
   proname => 'pg_collation_is_visible', procost => '10', provolatile => 's',
   prorettype => 'bool', proargtypes => 'oid',
   prosrc => 'pg_collation_is_visible' },
-{ oid => '4191', descr => 'is schema variable visible in search path?',
+{ oid => '4142', descr => 'is schema variable visible in search path?',
   proname => 'pg_variable_is_visible', procost => '10', provolatile => 's',
   prorettype => 'bool', proargtypes => 'oid', prosrc => 'pg_variable_is_visible' },

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 20 Mar 2020 09:28:22 +0100

pá 20. 3. 2020 v 8:18 odesílatel Pavel Stehule <[email-masked]>
napsal:

>> I played around with the ALTER VARIABLE statement to make sure it’s OK
>> and it seems fine to me.
>>
>>
>>

documentation fix

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 22 Mar 2020 08:40:33 +0100

Hi

rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 10 Apr 2020 19:30:47 +0200

Hi

rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 21 May 2020 12:10:15 +0200

Hi

just rebase without any other changes

Regards

Pavel

---

From: Amit Kapila <[email-masked]>
Date: Thu, 21 May 2020 17:04:44 +0530

> just rebase without any other changes

You seem to forget attaching the rebased patch.

-- 
With Regards,
Amit Kapila.

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 21 May 2020 14:49:57 +0200

> You seem to forget attaching the rebased patch.

I am sorry

attached.

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 5 Jul 2020 15:33:34 +0200

fresh rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 6 Jul 2020 10:17:07 +0200

>>> You seem to forget attaching the rebased patch.

fix test build

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 11 Jul 2020 06:44:24 +0200

rebase

Pavel

---

From: Michael Paquier <[email-masked]>
Date: Thu, 24 Sep 2020 12:56:37 +0900

> rebase

Per the CF bot, this needs an extra rebase as it does not apply
anymore.  This has not attracted much the attention of committers as
well.
--
Michael

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 24 Sep 2020 05:58:55 +0200

> anymore.  This has not attracted much the attention of committers as

I'll fix it today

--

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 24 Sep 2020 20:49:50 +0200

>> anymore.  This has not attracted much the attention of committers as

fixed patch attached

Regards

Pavel

---

From: Michael Paquier <[email-masked]>
Date: Thu, 1 Oct 2020 12:38:24 +0900

> fixed patch attached

It looks like there are again conflicts within setrefs.c.
--
Michael

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 1 Oct 2020 07:08:38 +0200

> It looks like there are again conflicts within setrefs.c.

fresh patch

Regards

Pavel

--

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 10 Nov 2020 19:45:26 +0100

rebase

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 19 Dec 2020 07:57:35 +0100

Hi

only rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 26 Dec 2020 05:52:44 +0100

> only rebase

rebase and comments fixes

Regards

Pavel

---

From: Erik Rijkers <[email-masked]>
Date: Sat, 26 Dec 2020 07:18:21 +0100

On 2020-12-26 05:52, Pavel Stehule wrote:
> [schema-variables-20201222.patch.gz (~]
> 
>> Hi
>> 
>> only rebase
>> 
> 

Hi Pavel,

This file is the exact same as the file you sent Tuesday. Is it a 
mistake?

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 26 Dec 2020 07:23:58 +0100

> This file is the exact same as the file you sent Tuesday. Is it a

It is the same. Unfortunately,  it was sent in an isolated thread, and
commitfest applications didn't find the latest version. I tried to attach
new thread to the commitfest application, but without success.

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 1 Jan 2021 09:45:13 +0100

Hi

fresh rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 8 Jan 2021 07:20:50 +0100

Hi

just rebase

Regards

Pavel

---

From: Erik Rijkers <[email-masked]>
Date: Fri, 08 Jan 2021 18:54:55 +0100

On 2021-01-08 07:20, Pavel Stehule wrote:
> Hi
> 
> just rebase
> 
> [schema-variables-20200108.patch]

Hey Pavel,

My gcc 8.3.0 compile says:
(on debian 10/Buster)

utility.c: In function ‘CreateCommandTag’:
utility.c:2332:8: warning: this statement may fall through 
[-Wimplicit-fallthrough=]
     tag = CMDTAG_SELECT;
     ~~~~^~~~~~~~~~~~~~~
utility.c:2334:3: note: here
    case T_LetStmt:
    ^~~~

compile, check, check-world, runs without further problem.

I also changed a few typos/improvements in the documentation, see 
attached.

One thing I wasn't sure of: I have assumed that
   ON TRANSACTIONAL END RESET

should be
   ON TRANSACTION END RESET

and changed it accordingly, please double-check.

Erik Rijkers
--- doc/src/sgml/ref/create_variable.sgml.orig	2021-01-08 17:40:20.181823036 +0100
+++ doc/src/sgml/ref/create_variable.sgml	2021-01-08 17:59:46.976127524 +0100
@@ -16,7 +16,7 @@
 
  <refnamediv>
   <refname>CREATE VARIABLE</refname>
-  <refpurpose>define a new permissioned typed schema variable</refpurpose>
+  <refpurpose>define a schema variable</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
@@ -29,24 +29,24 @@
   <title>Description</title>
 
   <para>
-   The <command>CREATE VARIABLE</command> command creates a new schema variable.
+   The <command>CREATE VARIABLE</command> command creates a schema variable.
    Schema variables, like relations, exist within a schema and their access is
    controlled via <command>GRANT</command> and <command>REVOKE</command> commands.
-   Their changes are non-transactional by default.
+   Changing a schema variable is non-transactional by default.
   </para>
 
   <para>
    The value of a schema variable is local to the current session. Retrieving
    a variable's value returns either a NULL or a default value, unless its value
    is set to something else in the current session with a LET command. By default,
-   the content of variable is not transactional, alike regular variables in PL languages.
+   the content of a variable is not transactional. This is the same as in regular variables in PL languages.
   </para>
 
   <para>
-   Schema variables are retrieved by the regular <command>SELECT</command> SQL command.
-   Their value can be set with the <command>LET</command> SQL command.
-   Notably, while schema variables share properties with tables, they cannot be updated
-   with <command>UPDATE</command> commands.
+   Schema variables are retrieved by the <command>SELECT</command> SQL command.
+   Their value is set with the <command>LET</command> SQL command.
+   While schema variables share properties with tables, their value cannot be updated
+   with an <command>UPDATE</command> command.
   </para>
  </refsect1>
 
@@ -76,7 +76,7 @@
     <term><replaceable class="parameter">name</replaceable></term>
     <listitem>
      <para>
-      The name (optionally schema-qualified) of the variable to create.
+      The name, optionally schema-qualified, of the variable.
      </para>
     </listitem>
    </varlistentry>
@@ -85,7 +85,7 @@
     <term><replaceable class="parameter">data_type</replaceable></term>
     <listitem>
      <para>
-      The name (optionally schema-qualified) of the data type of the variable to be created.
+      The name, optionally schema-qualified, of the data type of the variable.
      </para>
     </listitem>
    </varlistentry>
@@ -105,7 +105,7 @@
     <term><literal>NOT NULL</literal></term>
     <listitem>
      <para>
-      The <literal>NOT NULL</literal> clause forbid to set the variable to
+      The <literal>NOT NULL</literal> clause forbids to set the variable to
       a null value. A variable created as NOT NULL and without an explicitly
       declared default value cannot be read until it is initialized by a LET
       command. This obliges the user to explicitly initialize the variable
@@ -118,22 +118,22 @@
     <term><literal>DEFAULT <replaceable>default_expr</replaceable></literal></term>
     <listitem>
      <para>
-      The <literal>DEFAULT</literal> clause assigns a default data to
-      schema variable.
+      The <literal>DEFAULT</literal> clause can be used to assign a default value to
+      a schema variable.
      </para>
     </listitem>
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ON COMMIT DROP</literal>, <literal>ON TRANSACTIONAL END RESET</literal></term>
+    <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 temporary schema variable at transaction commit. With this clause the
+      of a temporary schema variable at transaction commit. With this clause the
       variable is dropped at commit time. The clause is only allowed
-      for temporary variables. The <literal>ON TRANSACTIONAL END RESET</literal>
+      for temporary variables. The <literal>ON TRANSACTION END RESET</literal>
       clause enforces the variable to be reset to its default value when
-      the transaction is either commited or rolled back.
+      the transaction is committed or rolled back.
      </para>
     </listitem>
    </varlistentry>
@@ -145,7 +145,7 @@
   <title>Notes</title>
 
   <para>
-   Use <command>DROP VARIABLE</command> command to remove a variable.
+   Use the <command>DROP VARIABLE</command> command to remove a variable.
   </para>
  </refsect1>
 
--- doc/src/sgml/ref/discard.sgml.orig	2021-01-08 18:02:25.837531779 +0100
+++ doc/src/sgml/ref/discard.sgml	2021-01-08 18:40:09.973630164 +0100
@@ -104,6 +104,7 @@
 DISCARD PLANS;
 DISCARD TEMP;
 DISCARD SEQUENCES;
+DISCARD VARIABLES;
 </programlisting></para>
     </listitem>
    </varlistentry>
--- doc/src/sgml/ref/drop_variable.sgml.orig	2021-01-08 18:05:28.643147771 +0100
+++ doc/src/sgml/ref/drop_variable.sgml	2021-01-08 18:07:17.876113523 +0100
@@ -16,7 +16,7 @@
 
  <refnamediv>
   <refname>DROP VARIABLE</refname>
-  <refpurpose>removes a schema variable</refpurpose>
+  <refpurpose>remove a schema variable</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
@@ -52,7 +52,7 @@
     <term><replaceable class="parameter">name</replaceable></term>
     <listitem>
      <para>
-      The name (optionally schema-qualified) of a schema variable.
+      The name, optionally schema-qualified, of a schema variable.
      </para>
     </listitem>
    </varlistentry>
--- doc/src/sgml/ref/let.sgml.orig	2021-01-08 18:09:52.757482768 +0100
+++ doc/src/sgml/ref/let.sgml	2021-01-08 18:13:23.071342113 +0100
@@ -44,16 +44,16 @@
     <term><literal>schema_variable</literal></term>
     <listitem>
      <para>
-      The name of schema variable.
+      The name of the schema variable.
      </para>
     </listitem>
    </varlistentry>
 
    <varlistentry>
-    <term><literal>sql expression</literal></term>
+    <term><literal>sql_expression</literal></term>
     <listitem>
      <para>
-      An SQL expression, whose result is casted into the schema variable's type.
+      An SQL expression. The result is cast into the schema variable's type.
      </para>
     </listitem>
    </varlistentry>
@@ -62,7 +62,7 @@
     <term><literal>DEFAULT</literal></term>
     <listitem>
      <para>
-      Reset the schema variable to its default value, if it is defined.
+      Reset the schema variable to its default value, if that is defined.
       If no explicit default value has been assigned, the schema variable
       is set to NULL.
      </para>
--- doc/src/sgml/plpgsql.sgml.orig	2021-01-08 17:35:20.639186557 +0100
+++ doc/src/sgml/plpgsql.sgml	2021-01-08 17:37:36.108377973 +0100
@@ -5870,10 +5870,10 @@
     <title><command>Global variables and constants</command></title>
 
     <para>
-     The <application>PL/pgSQL</application> language has no packages
-     and then it has not package variables and package constants. The
+     The <application>PL/pgSQL</application> language has no packages,
+     and therefore no package variables or package constants. The
      <productname>PostgreSQL</productname> has schema variables and
-     immutable schema variables. The schema variables can be created
+     immutable schema variables. Schema variables can be created
      by <command>CREATE VARIABLE</command> described in <xref
      linkend="sql-createvariable"/>.
     </para>

---

From: Pavel Stehule <[email-masked]>
Date: Sun, 10 Jan 2021 17:54:48 +0100

> [-Wimplicit-fallthrough=]

yes, there was an error from the last rebase. Fixed

> and changed it accordingly, please double-check.

It looks well, I merged these changes to patch. Thank you very much for
these corectures

Updated patch attached

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 14 Jan 2021 07:35:29 +0100

Hi

rebase

Regards

Pavel

---

From: Erik Rijkers <[email-masked]>
Date: Thu, 14 Jan 2021 10:24:27 +0100

On 2021-01-14 07:35, Pavel Stehule wrote:
> [schema-variables-20210114.patch.gz]

Build is fine. My (small) list of tests run OK.

I did notice a few more documentation peculiarities:

'The PostgreSQL has schema variables'  should be
'PostgreSQL has schema variables'

A link to the LET command should be added to the 'See Also' of the 
CREATE VARIABLE, ALTER VARIABLE, and DROP VARIABLE pages. (After all, 
the LET command is the most interesting)
Similarly, an ALTER VARIABLE link should be added to the 'See Also' 
section of LET.

Somehow, the sgml in the doc files causes too large spacing in the html, 
example:
I copy from the LET html:
    'if that is defined.      If no explicit'
    (6 spaces between 'defined.' and 'If')
Can you have a look?  Sorry - I can't find the cause.  It occurs on a 
few more places in the newly added sgml/html.  The unwanted spaces are 
visible also in the pdf.
(firefox 78.6.1, debian)

Thanks,

Erik Rijkers

---

From: Josef Šimánek <[email-masked]>
Date: Thu, 14 Jan 2021 11:31:33 +0100

I did some testing locally. All runs smoothly, compiled without warning.

Later on (once merged) it would be nice to write down a documentation
page for the whole feature as a set next to documented individual
commands.
It took me a few moments to understand how this works.

I was looking how to create variable with non default value in one
command, but if I understand it correctly, that's not the purpose.
Variable lives in a schema with default value, but you can set it per
session via LET.

Thus "CREATE VARIABLE" statement should not be usually part of
"classic" queries, but it should be threatened more like TABLE or
other DDL statements.

On the other side LET is there to use the variable in "classic" queries.

Does that make sense? Feel free to ping me if any help with
documentation would be needed. I can try to prepare an initial
variables guide once I'll ensure I do  understand this feature well.

PS: Now it is clear to me why it is called "schema variables", not
"session variables".

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 18 Jan 2021 10:50:25 +0100

fixed

fixed

> few more places in the newly added sgml/html.  The unwanted spaces are

Should be fixed in the last version. Probably there were some problems with
invisible white chars.

Thank you for check

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 18 Jan 2021 10:57:51 +0100

Hi

> variables guide once I'll ensure I do  understand this feature well.

I invite any help with doc. Maybe there can be page in section advanced
features

https://www.postgresql.org/docs/current/tutorial-advanced.html

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 18 Jan 2021 10:59:27 +0100

po 18. 1. 2021 v 10:50 odesílatel Pavel Stehule <[email-masked]>
napsal:

> Should be fixed in the last version. Probably there were some problems
> with invisible white chars.
>
> Thank you for check
>
> Pavel
>
>
>
>> (firefox 78.6.1, debian)
>>
>
and here is the patch

Regards

Pavel

---

From: Erik Rijkers <[email-masked]>
Date: Mon, 18 Jan 2021 15:24:15 +0100

On 2021-01-18 10:59, Pavel Stehule wrote:
>> 
> and here is the patch
> [schema-variables-20200118.patch.gz ]

One small thing:

The drop variable synopsis is:

DROP VARIABLE [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ]

In that text following it, 'RESTRICT' is not documented. When used it 
does not give an error but I don't see how it 'works'.

Erik

---

From: Pavel Stehule <[email-masked]>
Date: Mon, 18 Jan 2021 19:17:06 +0100

> does not give an error but I don't see how it 'works'.

should be fixed now. Thank you for check

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Sat, 23 Jan 2021 10:50:29 +0100

Hi

only rebase

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 2 Feb 2021 09:43:57 +0100

Hi

rebase and set PK for pg_variable table

Regards

Pavel

---

From: Pavel Stehule <[email-masked]>
Date: Tue, 16 Feb 2021 18:46:13 +0100

Hi

rebase

Pavel