From 40b1e836b9a17c870201600467ac4313aae7f11b Mon Sep 17 00:00:00 2001
From: Laurenz Albe <laurenz.albe@cybertec.at>
Date: Wed, 13 Nov 2024 15:03:53 +0100
Subject: [PATCH 14/22] Implementation of DEFAULT clause - default expressions
 for session variables

When the evaluation of default expression fails, we remove related entry from sessionvars
hash table. Then sessionvars can contain only sucessfully initialized values. Then we don't
need special flag for badly initialized session variables.
---
 doc/src/sgml/catalogs.sgml                    |  8 ++
 doc/src/sgml/ddl.sgml                         | 16 ++--
 doc/src/sgml/ref/create_variable.sgml         | 21 +++--
 doc/src/sgml/ref/discard.sgml                 |  3 +-
 src/backend/catalog/pg_variable.c             | 27 ++++++
 src/backend/commands/session_variable.c       | 87 ++++++++++++++++++-
 src/backend/parser/gram.y                     | 15 +++-
 src/backend/parser/parse_agg.c                |  2 +
 src/backend/parser/parse_expr.c               |  4 +
 src/backend/parser/parse_func.c               |  1 +
 src/bin/pg_dump/pg_dump.c                     | 14 +++
 src/bin/pg_dump/pg_dump.h                     |  1 +
 src/bin/pg_dump/t/002_pg_dump.pl              | 36 ++++++++
 src/bin/psql/describe.c                       |  4 +-
 src/include/catalog/pg_variable.h             |  3 +
 src/include/nodes/parsenodes.h                |  1 +
 src/include/parser/parse_node.h               |  1 +
 src/test/regress/expected/psql.out            | 36 ++++----
 .../regress/expected/session_variables.out    | 63 ++++++++++++++
 src/test/regress/sql/session_variables.sql    | 48 ++++++++++
 20 files changed, 350 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 20fc5d60cf5..e863cb910ef 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9873,6 +9873,14 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vardefexpr</structfield> <type>pg_node_tree</type>
+      </para>
+      <para>
+       The internal representation of the variable default value
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index cf96ec498d1..34d058ab155 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -5380,14 +5380,14 @@ SELECT current_user_id;
 
    <para>
     The value of a session variable is local to the current session. Retrieving
-    a variable's value returns a <literal>NULL</literal>, unless its value has
-    been set to something else in the current session using the
-    <command>LET</command> command.  Session variables are not transactional:
-    any changes made to the value of a session variable in a transaction won't
-    be undone if the transaction is rolled back (just like variables in
-    procedural languages).  Session variables themselves can be persistent
-    or temporary, but their values are neither persistent nor shared (like the
-    content of temporary tables).
+    a variable's value returns a <literal>NULL</literal> or a default value,
+    unless its value has  been set to something else in the current session
+    using the <command>LET</command> command.  Session variables are not
+    transactional: any changes made to the value of a session variable in
+    a transaction won't be undone if the transaction is rolled back (just like
+    variables in procedural languages).  Session variables themselves can be
+    persistent or temporary, but their values are neither persistent nor shared
+    (like the content of temporary tables).
    </para>
 
    <para>
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
index d813668a776..57391528119 100644
--- a/doc/src/sgml/ref/create_variable.sgml
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -27,7 +27,7 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 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> ]
-    [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
+    [ DEFAULT <replaceable class="parameter">default_expr</replaceable> ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ]
 </synopsis>
  </refsynopsisdiv>
  <refsect1>
@@ -41,10 +41,10 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="p
 
   <para>
    The value of a session variable is local to the current session.  Retrieving
-   a session variable's value returns NULL, unless its value is set to
-   something else in the current session with a <command>LET</command> command.
-   The content of a session variable is not transactional. This is the same as
-   regular variables in procedural languages.
+   a session variable's value returns NULL or a default value, unless its value
+   is set to something else in the current session with a <command>LET</command>
+   command.  The content of a session variable is not transactional.  This is the
+   same as regular variables in procedural languages.
   </para>
 
   <para>
@@ -105,6 +105,17 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] <replaceable class="p
     </listitem>
    </varlistentry>
 
+   <varlistentry id="sql-createvariable-default">
+    <term><literal>DEFAULT <replaceable>default_expr</replaceable></literal></term>
+    <listitem>
+     <para>
+      The <literal>DEFAULT</literal> clause can be used to assign a default
+      value to a session variable. This expression is evaluated when the session
+      variable is first accessed for reading and had not yet been assigned a value.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createvariable-on-commit-drop">
     <term><literal>ON COMMIT DROP</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml
index 61b967f9c9b..6b0cb95034f 100644
--- a/doc/src/sgml/ref/discard.sgml
+++ b/doc/src/sgml/ref/discard.sgml
@@ -71,7 +71,8 @@ DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP | VARIABLES }
     <listitem>
      <para>
       Resets the value of all session variables. If a variable
-      is later reused, it is re-initialized to <literal>NULL</literal>.
+      is later reused, it is re-initialized to either
+      <literal>NULL</literal> or its default value.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
index 00c90ea6fd8..8fba4cc493b 100644
--- a/src/backend/catalog/pg_variable.c
+++ b/src/backend/catalog/pg_variable.c
@@ -24,6 +24,9 @@
 #include "catalog/pg_variable.h"
 #include "commands/session_variable.h"
 #include "miscadmin.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
 #include "parser/parse_type.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -37,6 +40,7 @@ static ObjectAddress create_variable(const char *varName,
 									 Oid varOwner,
 									 Oid varCollation,
 									 bool if_not_exists,
+									 Node *varDefexpr,
 									 VariableXactEndAction varXactEndAction);
 
 
@@ -51,6 +55,7 @@ create_variable(const char *varName,
 				Oid varOwner,
 				Oid varCollation,
 				bool if_not_exists,
+				Node *varDefexpr,
 				VariableXactEndAction varXactEndAction)
 {
 	Acl		   *varacl;
@@ -114,6 +119,11 @@ create_variable(const char *varName,
 	values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation);
 	values[Anum_pg_variable_varxactendaction - 1] = CharGetDatum(varXactEndAction);
 
+	if (varDefexpr)
+		values[Anum_pg_variable_vardefexpr - 1] = CStringGetTextDatum(nodeToString(varDefexpr));
+	else
+		nulls[Anum_pg_variable_vardefexpr - 1] = true;
+
 	varacl = get_user_default_acl(OBJECT_VARIABLE, varOwner,
 								  varNamespace);
 	if (varacl != NULL)
@@ -150,6 +160,11 @@ create_variable(const char *varName,
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
 	free_object_addresses(addrs);
 
+	/* dependency on default expr */
+	if (varDefexpr)
+		recordDependencyOnExpr(&myself, (Node *) varDefexpr,
+							   NIL, DEPENDENCY_NORMAL);
+
 	/* dependency on owner */
 	recordDependencyOnOwner(VariableRelationId, varid, varOwner);
 
@@ -185,6 +200,7 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 	Oid			collation;
 	Oid			typcollation;
 	ObjectAddress variable;
+	Node	   *cooked_default = NULL;
 
 	/* check consistency of arguments */
 	if (stmt->XactEndAction == VARIABLE_XACTEND_DROP
@@ -226,6 +242,16 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 						format_type_be(typid)),
 				 parser_errposition(pstate, stmt->collClause->location)));
 
+	if (stmt->defexpr)
+	{
+		cooked_default = transformExpr(pstate, stmt->defexpr,
+									   EXPR_KIND_VARIABLE_DEFAULT);
+
+		cooked_default = coerce_to_specific_type(pstate,
+												 cooked_default, typid, "DEFAULT");
+		assign_expr_collations(pstate, cooked_default);
+	}
+
 	variable = create_variable(stmt->variable->relname,
 							   namespaceid,
 							   typid,
@@ -233,6 +259,7 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
 							   varowner,
 							   collation,
 							   stmt->if_not_exists,
+							   cooked_default,
 							   stmt->XactEndAction);
 
 	elog(DEBUG1, "record for session variable \"%s\" (oid:%d) was created in pg_variable",
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
index 31db420f470..f648e30dd8b 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -23,6 +23,7 @@
 #include "executor/svariableReceiver.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "optimizer/optimizer.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -518,11 +519,68 @@ AtEOSubXact_SessionVariables(bool isCommit,
 	}
 }
 
+/*
+ * evaluate an expression
+ */
+static void
+eval_assign_defexpr(SVariable svar, HeapTuple tup)
+{
+	Datum		defexpr_value;
+	bool		isnull;
+
+	Assert(svar);
+	Assert(svar->is_valid);
+	Assert(HeapTupleIsValid(tup));
+
+	defexpr_value = SysCacheGetAttr(VARIABLEOID,
+									tup,
+									Anum_pg_variable_vardefexpr,
+									&isnull);
+
+	if (!isnull)
+	{
+		EState	   *estate;
+		ExprState  *defexprs;
+		Expr	   *defexpr;
+		char	   *defexpr_str;
+		Datum		value;
+		MemoryContext oldcxt;
+
+		estate = CreateExecutorState();
+
+		defexpr_str = TextDatumGetCString(defexpr_value);
+		defexpr = (Expr *) stringToNode(defexpr_str);
+
+		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		defexpr = expression_planner((Expr *) defexpr);
+		defexprs = ExecInitExpr(defexpr, NULL);
+
+		value = ExecEvalExprSwitchContext(defexprs,
+										  GetPerTupleExprContext(estate),
+										  &isnull);
+
+		MemoryContextSwitchTo(oldcxt);
+
+		if (!isnull)
+		{
+			oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+
+			svar->value = datumCopy(value, svar->typbyval, svar->typlen);
+			svar->isnull = false;
+
+			MemoryContextSwitchTo(oldcxt);
+		}
+
+		FreeExecutorState(estate);
+	}
+}
+
 /*
  * Initialize attributes cached in "svar"
  */
 static void
-setup_session_variable(SVariable svar, Oid varid)
+setup_session_variable(SVariable svar, Oid varid, bool is_write)
 {
 	HeapTuple	tup;
 	Form_pg_variable varform;
@@ -572,6 +630,9 @@ setup_session_variable(SVariable svar, Oid varid)
 	svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID,
 											ObjectIdGetDatum(varid));
 
+	if (!is_write)
+		eval_assign_defexpr(svar, tup);
+
 	ReleaseSysCache(tup);
 }
 
@@ -601,7 +662,7 @@ set_session_variable(SVariable svar, Datum value, bool isnull)
 	 */
 	if (!svar->is_valid)
 	{
-		setup_session_variable(&locsvar, svar->varid);
+		setup_session_variable(&locsvar, svar->varid, false);
 		_svar = &locsvar;
 	}
 	else
@@ -734,7 +795,25 @@ get_session_variable(Oid varid)
 	 */
 	if (!svar->is_valid)
 	{
-		setup_session_variable(svar, varid);
+		/* in this case we want to use defexp if it is defined */
+		PG_TRY();
+		{
+			/*
+			 * In this case, the setup can execute default expression. When
+			 * the execution of default expression fails, then we need to
+			 * remove entry from session vars.
+			 */
+			setup_session_variable(svar, varid, false);
+		}
+		PG_CATCH();
+		{
+			/* this entry cannot be valid, remove from sessionvars */
+			hash_search(sessionvars, &varid, HASH_REMOVE, NULL);
+
+			/* propagate the error */
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
 
 		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by READ)",
 			 get_namespace_name(get_session_variable_namespace(varid)),
@@ -798,7 +877,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull)
 
 	if (!found)
 	{
-		setup_session_variable(svar, varid);
+		setup_session_variable(svar, varid, true);
 
 		elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by WRITE)",
 			 get_namespace_name(get_session_variable_namespace(svar->varid)),
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 83cf91d3efb..841d9f8dfdf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -637,6 +637,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <partboundspec> PartitionBoundSpec
 %type <list>		hash_partbound
 %type <defelt>		hash_partbound_elem
+%type <node>		OptSessionVarDefExpr
 
 %type <node>	json_format_clause
 				json_format_clause_opt
@@ -5229,30 +5230,36 @@ create_extension_opt_item:
  *****************************************************************************/
 
 CreateSessionVarStmt:
-			CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause XactEndActionOption
+			CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause OptSessionVarDefExpr XactEndActionOption
 				{
 					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
 					$4->relpersistence = $2;
 					n->variable = $4;
 					n->typeName = $6;
 					n->collClause = (CollateClause *) $7;
-					n->XactEndAction = $8;
+					n->defexpr = $8;
+					n->XactEndAction = $9;
 					n->if_not_exists = false;
 					$$ = (Node *) n;
 				}
-			| CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause XactEndActionOption
+			| CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OptSessionVarDefExpr XactEndActionOption
 				{
 					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
 					$7->relpersistence = $2;
 					n->variable = $7;
 					n->typeName = $9;
 					n->collClause = (CollateClause *) $10;
-					n->XactEndAction = $11;
+					n->defexpr = $11;
+					n->XactEndAction = $12;
 					n->if_not_exists = true;
 					$$ = (Node *) n;
 				}
 		;
 
+OptSessionVarDefExpr: DEFAULT b_expr					{ $$ = $2; }
+			| /* EMPTY */							{ $$ = NULL; }
+		;
+
 /*
  * Temporary session variables can be dropped on successful
  * transaction end like tables.
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 4737b04d171..54b765c8eb5 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -488,6 +488,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");
@@ -937,6 +938,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("window functions are not allowed in DEFAULT expressions");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index b85a97ca78e..ca968b5c6cd 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -588,6 +588,7 @@ expr_kind_allows_session_variables(ParseExprKind p_expr_kind)
 		case EXPR_KIND_JOIN_USING:
 		case EXPR_KIND_CYCLE_MARK:
 		case EXPR_KIND_ASSIGN_TARGET:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			result = false;
 			break;
 	}
@@ -673,6 +674,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_CYCLE_MARK:
 		case EXPR_KIND_ASSIGN_TARGET:
 		case EXPR_KIND_LET_TARGET:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			/* okay */
 			break;
 
@@ -2146,6 +2148,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:
@@ -3498,6 +3501,7 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "CHECK";
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			return "DEFAULT";
 		case EXPR_KIND_INDEX_EXPRESSION:
 			return "index expression";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index dc25f6dcd0e..d9f15c2618f 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2620,6 +2620,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("set-returning functions are not allowed in DEFAULT expressions");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f2f3779c672..a6c33d2edef 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5436,6 +5436,7 @@ getVariables(Archive *fout)
 	int			i_varnamespace;
 	int			i_vartype;
 	int			i_vartypname;
+	int			i_vardefexpr;
 	int			i_varxactendaction;
 	int			i_varowner;
 	int			i_varcollation;
@@ -5461,6 +5462,7 @@ getVariables(Archive *fout)
 					  "            THEN v.varcollation\n"
 					  "            ELSE 0\n"
 					  "       END AS varcollation,\n"
+					  "       pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n"
 					  "       v.varowner, v.varacl,\n"
 					  "       acldefault('V', v.varowner) AS acldefault\n"
 					  "FROM pg_catalog.pg_variable v\n"
@@ -5477,6 +5479,7 @@ getVariables(Archive *fout)
 	i_varnamespace = PQfnumber(res, "varnamespace");
 	i_vartype = PQfnumber(res, "vartype");
 	i_vartypname = PQfnumber(res, "vartypname");
+	i_vardefexpr = PQfnumber(res, "vardefexpr");
 	i_varxactendaction = PQfnumber(res, "varxactendaction");
 	i_varcollation = PQfnumber(res, "varcollation");
 
@@ -5512,6 +5515,11 @@ getVariables(Archive *fout)
 		varinfo[i].dacl.initprivs = NULL;
 		varinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_varowner));
 
+		if (PQgetisnull(res, i, i_vardefexpr))
+			varinfo[i].vardefexpr = NULL;
+		else
+			varinfo[i].vardefexpr = pg_strdup(PQgetvalue(res, i, i_vardefexpr));
+
 		/* do not try to dump ACL if no ACL exists */
 		if (!PQgetisnull(res, i, i_varacl))
 			varinfo[i].dobj.components |= DUMP_COMPONENT_ACL;
@@ -5544,6 +5552,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 	PQExpBuffer query;
 	char	   *qualvarname;
 	const char *vartypname;
+	const char *vardefexpr;
 	const char *varxactendaction;
 	Oid			varcollation;
 
@@ -5556,6 +5565,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 
 	qualvarname = pg_strdup(fmtQualifiedDumpable(varinfo));
 	vartypname = varinfo->vartypname;
+	vardefexpr = varinfo->vardefexpr;
 	varxactendaction = varinfo->varxactendaction;
 	varcollation = varinfo->varcollation;
 
@@ -5575,6 +5585,10 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo)
 							  fmtQualifiedDumpable(coll));
 	}
 
+	if (vardefexpr)
+		appendPQExpBuffer(query, " DEFAULT %s",
+						  vardefexpr);
+
 	if (strcmp(varxactendaction, "r") == 0)
 		appendPQExpBuffer(query, " ON TRANSACTION END RESET");
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index a9ac70d7616..2613700e19d 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -717,6 +717,7 @@ typedef struct _VariableInfo
 	DumpableAcl dacl;
 	Oid			vartype;
 	char	   *vartypname;
+	char	   *vardefexpr;
 	char	   *varxactendaction;
 	char	   *varacl;
 	char	   *rvaracl;
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 7b0cc2c5d94..fcf68966b0e 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4005,6 +4005,42 @@ my %tests = (
 		},
 	},
 
+	'CREATE VARIABLE test_variable DEFAULT' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE VARIABLE dump_test.variable3 AS integer DEFAULT 10;',
+		regexp => qr/^
+			\QCREATE VARIABLE dump_test.variable3 AS integer DEFAULT 10;\E/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_measurement    => 1,
+		},
+	},
+
+	'CREATE VARIABLE test_variable DEFAULT ON TRANSACTION END RESET' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 61,
+		create_sql   => 'CREATE VARIABLE dump_test.variable4 AS integer DEFAULT 10 ON TRANSACTION END RESET',
+		regexp => qr/^
+			\QCREATE VARIABLE dump_test.variable4 AS integer DEFAULT 10 ON TRANSACTION END RESET;\E/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_pre_data => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_measurement    => 1,
+		},
+	},
+
 	'CREATE VIEW test_view' => {
 		create_order => 61,
 		create_sql => 'CREATE VIEW dump_test.test_view
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 3f743676ca7..290dc624fe5 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5250,7 +5250,7 @@ listVariables(const char *pattern, bool verbose)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, false, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false};
 
 	if (pset.sversion < 180000)
 	{
@@ -5271,6 +5271,7 @@ listVariables(const char *pattern, bool verbose)
 					  "  (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type bt\n"
 					  "   WHERE c.oid = v.varcollation AND bt.oid = v.vartype AND v.varcollation <> bt.typcollation) as \"%s\",\n"
 					  "  pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n"
+					  "  pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\",\n"
 					  "  CASE v.varxactendaction\n"
 					  "    WHEN 'd' THEN 'ON COMMIT DROP'\n"
 					  "    WHEN 'r' THEN 'ON TRANSACTION END RESET'\n"
@@ -5280,6 +5281,7 @@ listVariables(const char *pattern, bool verbose)
 					  gettext_noop("Type"),
 					  gettext_noop("Collation"),
 					  gettext_noop("Owner"),
+					  gettext_noop("Default"),
 					  gettext_noop("Transactional end action"));
 
 	if (verbose)
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
index 008f2ee7c17..09dd44a44e1 100644
--- a/src/include/catalog/pg_variable.h
+++ b/src/include/catalog/pg_variable.h
@@ -68,6 +68,9 @@ CATALOG(pg_variable,9222,VariableRelationId)
 	/* access permissions */
 	aclitem		varacl[1] BKI_DEFAULT(_null_);
 
+	/* list of expression trees for variable default (NULL if none) */
+	pg_node_tree vardefexpr BKI_DEFAULT(_null_);
+
 #endif
 } FormData_pg_variable;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ec4a16aea24..acc7d73fb8d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3487,6 +3487,7 @@ typedef struct CreateSessionVarStmt
 	TypeName   *typeName;		/* the type of variable */
 	CollateClause *collClause;
 	bool		if_not_exists;	/* do nothing if it already exists */
+	Node	   *defexpr;		/* default expression */
 	char		XactEndAction;	/* on transaction end action */
 } CreateSessionVarStmt;
 
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 8b55f840d10..36f99c88ee3 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -84,6 +84,7 @@ typedef enum ParseExprKind
 	EXPR_KIND_CYCLE_MARK,		/* cycle mark value */
 	EXPR_KIND_ASSIGN_TARGET,	/* PL/pgSQL assignment target */
 	EXPR_KIND_LET_TARGET,		/* LET target */
+	EXPR_KIND_VARIABLE_DEFAULT, /* default value for session variable */
 } ParseExprKind;
 
 
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 61077a52b16..bc7b60a97ea 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5935,20 +5935,20 @@ CREATE ROLE regress_variable_owner;
 SET ROLE TO regress_variable_owner;
 CREATE VARIABLE var1 AS varchar COLLATE "C";
 \dV+ var1
-                                                          List of variables
- Schema | Name |       Type        | Collation |         Owner          | Transactional end action | Access privileges | Description 
---------+------+-------------------+-----------+------------------------+--------------------------+-------------------+-------------
- public | var1 | character varying | C         | regress_variable_owner |                          |                   | 
+                                                               List of variables
+ Schema | Name |       Type        | Collation |         Owner          | Default | Transactional end action | Access privileges | Description 
+--------+------+-------------------+-----------+------------------------+---------+--------------------------+-------------------+-------------
+ public | var1 | character varying | C         | regress_variable_owner |         |                          |                   | 
 (1 row)
 
 GRANT SELECT ON VARIABLE var1 TO PUBLIC;
 COMMENT ON VARIABLE var1 IS 'some description';
 \dV+ var1
-                                                                            List of variables
- Schema | Name |       Type        | Collation |         Owner          | Transactional end action |                Access privileges                 |   Description    
---------+------+-------------------+-----------+------------------------+--------------------------+--------------------------------------------------+------------------
- public | var1 | character varying | C         | regress_variable_owner |                          | regress_variable_owner=rw/regress_variable_owner+| some description
-        |      |                   |           |                        |                          | =r/regress_variable_owner                        | 
+                                                                                 List of variables
+ Schema | Name |       Type        | Collation |         Owner          | Default | Transactional end action |                Access privileges                 |   Description    
+--------+------+-------------------+-----------+------------------------+---------+--------------------------+--------------------------------------------------+------------------
+ public | var1 | character varying | C         | regress_variable_owner |         |                          | regress_variable_owner=rw/regress_variable_owner+| some description
+        |      |                   |           |                        |         |                          | =r/regress_variable_owner                        | 
 (1 row)
 
 DROP VARIABLE var1;
@@ -6416,9 +6416,9 @@ List of schemas
 (0 rows)
 
 \dV "no.such.variable"
-                          List of variables
- Schema | Name | Type | Collation | Owner | Transactional end action 
---------+------+------+-----------+-------+--------------------------
+                               List of variables
+ Schema | Name | Type | Collation | Owner | Default | Transactional end action 
+--------+------+------+-----------+-------+---------+--------------------------
 (0 rows)
 
 -- again, but with dotted schema qualifications.
@@ -6591,9 +6591,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta
 \dy "no.such.schema"."no.such.event.trigger"
 improper qualified name (too many dotted names): "no.such.schema"."no.such.event.trigger"
 \dV "no.such.schema"."no.such.variable"
-                          List of variables
- Schema | Name | Type | Collation | Owner | Transactional end action 
---------+------+------+-----------+-------+--------------------------
+                               List of variables
+ Schema | Name | Type | Collation | Owner | Default | Transactional end action 
+--------+------+------+-----------+-------+---------+--------------------------
 (0 rows)
 
 -- again, but with current database and dotted schema qualifications.
@@ -6730,9 +6730,9 @@ List of text search templates
 (0 rows)
 
 \dV regression."no.such.schema"."no.such.variable"
-                          List of variables
- Schema | Name | Type | Collation | Owner | Transactional end action 
---------+------+------+-----------+-------+--------------------------
+                               List of variables
+ Schema | Name | Type | Collation | Owner | Default | Transactional end action 
+--------+------+------+-----------+-------+---------+--------------------------
 (0 rows)
 
 -- again, but with dotted database and dotted schema qualifications.
diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out
index ad9d1a187e5..fc6d45cbdc4 100644
--- a/src/test/regress/expected/session_variables.out
+++ b/src/test/regress/expected/session_variables.out
@@ -2381,3 +2381,66 @@ SELECT var1 IS NULL;
 (1 row)
 
 DROP VARIABLE var1;
+CREATE OR REPLACE FUNCTION vartest_fx()
+RETURNS int AS $$
+BEGIN
+  RAISE NOTICE 'vartest_fx executed';
+  RETURN 0;
+END;
+$$ LANGUAGE plpgsql;
+CREATE VARIABLE var1 AS int DEFAULT vartest_fx();
+-- vartest_fx should be protected by dep, should fail
+DROP FUNCTION vartest_fx();
+ERROR:  cannot drop function vartest_fx() because other objects depend on it
+DETAIL:  session variable var1 depends on function vartest_fx()
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- should be ok
+SELECT var1;
+NOTICE:  vartest_fx executed
+ var1 
+------
+    0
+(1 row)
+
+-- the defexpr should be evaluated only once
+SELECT var1;
+ var1 
+------
+    0
+(1 row)
+
+DISCARD VARIABLES;
+-- in this case, the defexpr should not be evaluated
+LET var1 = 100;
+SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+DISCARD VARIABLES;
+CREATE OR REPLACE FUNCTION vartest_fx()
+RETURNS int AS $$
+BEGIN
+  RAISE EXCEPTION 'vartest_fx is executing';
+  RETURN 0;
+END;
+$$ LANGUAGE plpgsql;
+-- should to fail, but not to crash
+SELECT var1;
+ERROR:  vartest_fx is executing
+CONTEXT:  PL/pgSQL function vartest_fx() line 3 at RAISE
+-- again
+SELECT var1;
+ERROR:  vartest_fx is executing
+CONTEXT:  PL/pgSQL function vartest_fx() line 3 at RAISE
+-- but we can write
+LET var1 = 100;
+SELECT var1;
+ var1 
+------
+  100
+(1 row)
+
+DROP VARIABLE var1;
+DROP FUNCTION vartest_fx();
diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql
index 9329078bdd9..988d8d327fe 100644
--- a/src/test/regress/sql/session_variables.sql
+++ b/src/test/regress/sql/session_variables.sql
@@ -1632,3 +1632,51 @@ ROLLBACK;
 SELECT var1 IS NULL;
 
 DROP VARIABLE var1;
+
+CREATE OR REPLACE FUNCTION vartest_fx()
+RETURNS int AS $$
+BEGIN
+  RAISE NOTICE 'vartest_fx executed';
+  RETURN 0;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE VARIABLE var1 AS int DEFAULT vartest_fx();
+
+-- vartest_fx should be protected by dep, should fail
+DROP FUNCTION vartest_fx();
+
+-- should be ok
+SELECT var1;
+
+-- the defexpr should be evaluated only once
+SELECT var1;
+
+DISCARD VARIABLES;
+
+-- in this case, the defexpr should not be evaluated
+LET var1 = 100;
+SELECT var1;
+
+DISCARD VARIABLES;
+
+CREATE OR REPLACE FUNCTION vartest_fx()
+RETURNS int AS $$
+BEGIN
+  RAISE EXCEPTION 'vartest_fx is executing';
+  RETURN 0;
+END;
+$$ LANGUAGE plpgsql;
+
+-- should to fail, but not to crash
+SELECT var1;
+
+-- again
+SELECT var1;
+
+-- but we can write
+LET var1 = 100;
+SELECT var1;
+
+DROP VARIABLE var1;
+DROP FUNCTION vartest_fx();
-- 
2.47.1

