From a9f9ac7b471934399aae14938567239b10e60ed6 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Mon, 4 Apr 2022 21:19:03 +0200
Subject: [PATCH 01/15] catalog support for session variables

Implementation new system object class - session variable with new access rights SELECT, UPDATE,
with routines for creating session variable, initialization of session variable from system
catalog, and lookups routines for identification of session variables.
---
 src/backend/access/transam/xact.c       |  11 +
 src/backend/catalog/Makefile            |   4 +-
 src/backend/catalog/aclchk.c            | 302 ++++++++++++++++
 src/backend/catalog/dependency.c        |  13 +-
 src/backend/catalog/meson.build         |   1 +
 src/backend/catalog/namespace.c         | 453 ++++++++++++++++++++++++
 src/backend/catalog/objectaddress.c     | 126 ++++++-
 src/backend/catalog/pg_shdepend.c       |   2 +
 src/backend/catalog/pg_variable.c       | 372 +++++++++++++++++++
 src/backend/commands/Makefile           |   1 +
 src/backend/commands/alter.c            |   9 +
 src/backend/commands/dropcmds.c         |   4 +
 src/backend/commands/event_trigger.c    |   6 +
 src/backend/commands/seclabel.c         |   1 +
 src/backend/commands/session_variable.c | 242 +++++++++++++
 src/backend/commands/tablecmds.c        |  45 ++-
 src/backend/parser/gram.y               | 145 +++++++-
 src/backend/parser/parse_agg.c          |   2 +
 src/backend/parser/parse_expr.c         |   5 +
 src/backend/parser/parse_func.c         |   1 +
 src/backend/parser/parse_utilcmd.c      |  12 +
 src/backend/tcop/utility.c              |  16 +
 src/backend/utils/adt/acl.c             |   8 +-
 src/backend/utils/cache/lsyscache.c     | 113 ++++++
 src/backend/utils/cache/syscache.c      |  23 ++
 src/backend/utils/fmgr/fmgr.c           |   6 +-
 src/include/catalog/dependency.h        |   5 +-
 src/include/catalog/meson.build         |   1 +
 src/include/catalog/namespace.h         |   7 +
 src/include/catalog/pg_default_acl.h    |   1 +
 src/include/catalog/pg_proc.dat         |   3 +
 src/include/catalog/pg_variable.h       | 120 +++++++
 src/include/commands/session_variable.h |  41 +++
 src/include/nodes/parsenodes.h          |  20 ++
 src/include/parser/kwlist.h             |   2 +
 src/include/parser/parse_node.h         |   1 +
 src/include/tcop/cmdtaglist.h           |   3 +
 src/include/utils/acl.h                 |   6 +-
 src/include/utils/lsyscache.h           |   9 +
 src/include/utils/syscache.h            |   6 +-
 src/test/regress/expected/oidjoins.out  |   4 +
 src/tools/pgindent/typedefs.list        |   4 +
 42 files changed, 2137 insertions(+), 19 deletions(-)
 create mode 100644 src/backend/catalog/pg_variable.c
 create mode 100644 src/backend/commands/session_variable.c
 create mode 100644 src/include/catalog/pg_variable.h
 create mode 100644 src/include/commands/session_variable.h

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 883d6c0f703..6643739e53d 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -36,6 +36,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/session_variable.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "common/pg_prng.h"
@@ -2213,6 +2214,9 @@ CommitTransaction(void)
 	 */
 	smgrDoPendingSyncs(true, is_parallel_worker);
 
+	/* Let ON COMMIT DROP */
+	AtPreEOXact_SessionVariable_on_xact_actions(true);
+
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
@@ -2789,6 +2793,9 @@ AbortTransaction(void)
 	AtAbort_Portals();
 	smgrDoPendingSyncs(false, is_parallel_worker);
 	AtEOXact_LargeObject(false);
+
+	/* 'false' means it's abort */
+	AtPreEOXact_SessionVariable_on_xact_actions(false);
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
@@ -4996,6 +5003,8 @@ CommitSubTransaction(void)
 	AtEOSubXact_SPI(true, s->subTransactionId);
 	AtEOSubXact_on_commit_actions(true, s->subTransactionId,
 								  s->parent->subTransactionId);
+	AtEOSubXact_SessionVariable_on_xact_actions(true, s->subTransactionId,
+												s->parent->subTransactionId);
 	AtEOSubXact_Namespace(true, s->subTransactionId,
 						  s->parent->subTransactionId);
 	AtEOSubXact_Files(true, s->subTransactionId,
@@ -5160,6 +5169,8 @@ AbortSubTransaction(void)
 		AtEOSubXact_SPI(false, s->subTransactionId);
 		AtEOSubXact_on_commit_actions(false, s->subTransactionId,
 									  s->parent->subTransactionId);
+		AtEOSubXact_SessionVariable_on_xact_actions(false, s->subTransactionId,
+													s->parent->subTransactionId);
 		AtEOSubXact_Namespace(false, s->subTransactionId,
 							  s->parent->subTransactionId);
 		AtEOSubXact_Files(false, s->subTransactionId,
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 89a0221ec9b..f89e12eaf29 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -45,6 +45,7 @@ OBJS = \
 	pg_shdepend.o \
 	pg_subscription.o \
 	pg_type.o \
+	pg_variable.o \
 	storage.o \
 	toasting.o
 
@@ -72,7 +73,8 @@ CATALOG_HEADERS := \
 	pg_collation.h pg_parameter_acl.h pg_partitioned_table.h \
 	pg_range.h pg_transform.h \
 	pg_sequence.h pg_publication.h pg_publication_namespace.h \
-	pg_publication_rel.h pg_subscription.h pg_subscription_rel.h
+	pg_publication_rel.h pg_subscription.h pg_subscription_rel.h \
+	pg_variable.h
 
 GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index aa5a2ed9483..11b2788529b 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -59,6 +59,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
@@ -115,6 +116,7 @@ static void ExecGrant_Namespace(InternalGrant *istmt);
 static void ExecGrant_Tablespace(InternalGrant *istmt);
 static void ExecGrant_Type(InternalGrant *istmt);
 static void ExecGrant_Parameter(InternalGrant *istmt);
+static void ExecGrant_Variable(InternalGrant *istmt);
 
 static void SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames);
 static void SetDefaultACL(InternalDefaultACL *iacls);
@@ -266,6 +268,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
 		case OBJECT_PARAMETER_ACL:
 			whole_mask = ACL_ALL_RIGHTS_PARAMETER_ACL;
 			break;
+		case OBJECT_VARIABLE:
+			whole_mask = ACL_ALL_RIGHTS_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d", objtype);
 			/* not reached, but keep compiler quiet */
@@ -510,6 +515,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
 			all_privileges = ACL_ALL_RIGHTS_PARAMETER_ACL;
 			errormsg = gettext_noop("invalid privilege type %s for parameter");
 			break;
+		case OBJECT_VARIABLE:
+			all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			errormsg = gettext_noop("invalid privilege type %s for session variable");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) stmt->objtype);
@@ -615,6 +624,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 		case OBJECT_PARAMETER_ACL:
 			ExecGrant_Parameter(istmt);
 			break;
+		case OBJECT_VARIABLE:
+			ExecGrant_Variable(istmt);
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
@@ -805,6 +817,19 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
 					objects = lappend_oid(objects, parameterId);
 			}
 			break;
+		case OBJECT_VARIABLE:
+			foreach(cell, objnames)
+			{
+				RangeVar   *varvar = (RangeVar *) lfirst(cell);
+				Oid			relOid;
+
+				relOid = LookupVariable(varvar->schemaname,
+										varvar->relname,
+										false,
+										false);
+				objects = lappend_oid(objects, relOid);
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) objtype);
@@ -894,6 +919,33 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 					table_close(rel, AccessShareLock);
 				}
 				break;
+			case OBJECT_VARIABLE:
+				{
+					ScanKeyData key;
+					Relation	rel;
+					TableScanDesc scan;
+					HeapTuple	tuple;
+
+					ScanKeyInit(&key,
+								Anum_pg_variable_varnamespace,
+								BTEqualStrategyNumber, F_OIDEQ,
+								ObjectIdGetDatum(namespaceId));
+
+					rel = table_open(VariableRelationId, AccessShareLock);
+					scan = table_beginscan_catalog(rel, 1, &key);
+
+					while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+					{
+						Oid			oid = ((Form_pg_proc) GETSTRUCT(tuple))->oid;
+
+						objects = lappend_oid(objects, oid);
+					}
+
+					table_endscan(scan);
+					table_close(rel, AccessShareLock);
+				}
+				break;
+
 			default:
 				/* should not happen */
 				elog(ERROR, "unrecognized GrantStmt.objtype: %d",
@@ -1053,6 +1105,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
 			all_privileges = ACL_ALL_RIGHTS_SCHEMA;
 			errormsg = gettext_noop("invalid privilege type %s for schema");
 			break;
+		case OBJECT_VARIABLE:
+			all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+			errormsg = gettext_noop("invalid privilege type %s for session variable");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) action->objtype);
@@ -1244,6 +1300,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);
@@ -1475,6 +1537,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",
@@ -1535,6 +1600,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case ParameterAclRelationId:
 				istmt.objtype = OBJECT_PARAMETER_ACL;
 				break;
+			case VariableRelationId:
+				istmt.objtype = OBJECT_VARIABLE;
+				break;
 			default:
 				elog(ERROR, "unexpected object class %u", classid);
 				break;
@@ -3367,6 +3435,129 @@ ExecGrant_Parameter(InternalGrant *istmt)
 	table_close(relation, RowExclusiveLock);
 }
 
+static void
+ExecGrant_Variable(InternalGrant *istmt)
+{
+	Relation	relation;
+	ListCell   *cell;
+
+	if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
+		istmt->privileges = ACL_ALL_RIGHTS_VARIABLE;
+
+	relation = table_open(VariableRelationId, RowExclusiveLock);
+
+	foreach(cell, istmt->objects)
+	{
+		Oid			varId = lfirst_oid(cell);
+		Form_pg_variable pg_variable_tuple;
+		Datum		aclDatum;
+		bool		isNull;
+		AclMode		avail_goptions;
+		AclMode		this_privileges;
+		Acl		   *old_acl;
+		Acl		   *new_acl;
+		Oid			grantorId;
+		Oid			ownerId;
+		HeapTuple	tuple;
+		HeapTuple	newtuple;
+		Datum		values[Natts_pg_variable];
+		bool		nulls[Natts_pg_variable];
+		bool		replaces[Natts_pg_variable];
+		int			noldmembers;
+		int			nnewmembers;
+		Oid		   *oldmembers;
+		Oid		   *newmembers;
+
+		tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varId));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for session variable %u", varId);
+
+		pg_variable_tuple = (Form_pg_variable) GETSTRUCT(tuple);
+
+		/*
+		 * Get owner ID and working copy of existing ACL. If there's no ACL,
+		 * substitute the proper default.
+		 */
+		ownerId = pg_variable_tuple->varowner;
+		aclDatum = SysCacheGetAttr(VARIABLEOID, tuple, Anum_pg_variable_varacl,
+								   &isNull);
+		if (isNull)
+		{
+			old_acl = acldefault(OBJECT_VARIABLE, ownerId);
+			/* There are no old member roles according to the catalogs */
+			noldmembers = 0;
+			oldmembers = NULL;
+		}
+		else
+		{
+			old_acl = DatumGetAclPCopy(aclDatum);
+			/* Get the roles mentioned in the existing ACL */
+			noldmembers = aclmembers(old_acl, &oldmembers);
+		}
+
+		/* Determine ID to do the grant as, and available grant options */
+		select_best_grantor(GetUserId(), istmt->privileges,
+							old_acl, ownerId,
+							&grantorId, &avail_goptions);
+
+		/*
+		 * Restrict the privileges to what we can actually grant, and emit the
+		 * standards-mandated warning and error messages.
+		 */
+		this_privileges =
+			restrict_and_check_grant(istmt->is_grant, avail_goptions,
+									 istmt->all_privs, istmt->privileges,
+									 varId, grantorId, OBJECT_VARIABLE,
+									 NameStr(pg_variable_tuple->varname),
+									 0, NULL);
+
+		/*
+		 * Generate new ACL.
+		 */
+		new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
+									   istmt->grant_option, istmt->behavior,
+									   istmt->grantees, this_privileges,
+									   grantorId, ownerId);
+
+		/*
+		 * We need the members of both old and new ACLs so we can correct the
+		 * shared dependency information.
+		 */
+		nnewmembers = aclmembers(new_acl, &newmembers);
+
+		/* finished building new ACL value, now insert it */
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, false, sizeof(nulls));
+		MemSet(replaces, false, sizeof(replaces));
+
+		replaces[Anum_pg_variable_varacl - 1] = true;
+		values[Anum_pg_variable_varacl - 1] = PointerGetDatum(new_acl);
+
+		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
+									 nulls, replaces);
+
+		CatalogTupleUpdate(relation, &newtuple->t_self, newtuple);
+
+		/* Update initial privileges for extensions */
+		recordExtensionInitPriv(varId, VariableRelationId, 0, new_acl);
+
+		/* Update the shared dependency ACL info */
+		updateAclDependencies(VariableRelationId, varId, 0,
+							  ownerId,
+							  noldmembers, oldmembers,
+							  nnewmembers, newmembers);
+
+		ReleaseSysCache(tuple);
+
+		pfree(new_acl);
+
+		/* prevent error when processing duplicate objects */
+		CommandCounterIncrement();
+	}
+
+	table_close(relation, RowExclusiveLock);
+}
+
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3568,6 +3759,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("permission denied for type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("permission denied for session variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("permission denied for view %s");
 						break;
@@ -3679,6 +3873,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_TYPE:
 						msg = gettext_noop("must be owner of type %s");
 						break;
+					case OBJECT_VARIABLE:
+						msg = gettext_noop("must be owner of session variable %s");
+						break;
 					case OBJECT_VIEW:
 						msg = gettext_noop("must be owner of view %s");
 						break;
@@ -3827,6 +4024,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
 			return ACL_NO_RIGHTS;
 		case OBJECT_TYPE:
 			return pg_type_aclmask(table_oid, roleid, mask, how);
+		case OBJECT_VARIABLE:
+			return pg_variable_aclmask(table_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized objtype: %d",
 				 (int) objtype);
@@ -4810,6 +5009,66 @@ pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how)
 	return result;
 }
 
+/*
+ * Exported routine for examining a user's privileges for a variable.
+ */
+AclMode
+pg_variable_aclmask(Oid var_oid, Oid roleid, AclMode mask, AclMaskHow how)
+{
+	AclMode		result;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+	Acl		   *acl;
+	Oid			ownerId;
+
+	Form_pg_variable varForm;
+
+	/* Bypass permission checks for superusers */
+	if (superuser_arg(roleid))
+		return mask;
+
+	/*
+	 * Must get the variables's tuple from pg_variable
+	 */
+	tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(var_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("variable with OID %u does not exist",
+						var_oid)));
+	varForm = (Form_pg_variable) GETSTRUCT(tuple);
+
+	/*
+	 * Now get the variable's owner and ACL from the tuple
+	 */
+	ownerId = varForm->varowner;
+
+	aclDatum = SysCacheGetAttr(VARIABLEOID, tuple,
+							   Anum_pg_variable_varacl, &isNull);
+	if (isNull)
+	{
+		/* No ACL, so build default ACL */
+		acl = acldefault(OBJECT_VARIABLE, ownerId);
+		aclDatum = (Datum) 0;
+	}
+	else
+	{
+		/* detoast rel's ACL if necessary */
+		acl = DatumGetAclP(aclDatum);
+	}
+
+	result = aclmask(acl, roleid, ownerId, mask, how);
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
 /*
  * Exported routine for checking a user's access privileges to a column
  *
@@ -5110,6 +5369,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 var_oid, Oid roleid, AclMode mode)
+{
+	if (pg_variable_aclmask(var_oid, roleid, mode, ACLMASK_ANY) != 0)
+		return ACLCHECK_OK;
+	else
+		return ACLCHECK_NO_PRIV;
+}
+
 /*
  * Ownership check for a relation (specified by OID).
  */
@@ -5727,6 +5998,33 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid)
 	return has_privs_of_role(roleid, ownerId);
 }
 
+/*
+ * Ownership check for a session variable (specified by OID).
+ */
+bool
+pg_variable_ownercheck(Oid db_oid, Oid roleid)
+{
+	HeapTuple	tuple;
+	Oid			ownerId;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return true;
+
+	tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(db_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_DATABASE),
+				 errmsg("session variable with OID %u does not exist", db_oid)));
+
+	ownerId = ((Form_pg_variable) GETSTRUCT(tuple))->varowner;
+
+	ReleaseSysCache(tuple);
+
+	return has_privs_of_role(roleid, ownerId);
+}
+
+
 /*
  * Check whether specified role has CREATEROLE privilege (or is a superuser)
  *
@@ -5855,6 +6153,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 7f3e64b5ae6..731b9805adb 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -65,12 +65,15 @@
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
 #include "commands/policy.h"
 #include "commands/publicationcmds.h"
+#include "commands/schemacmds.h"
+#include "commands/session_variable.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
 #include "commands/trigger.h"
@@ -188,7 +191,8 @@ static const Oid object_classes[] = {
 	PublicationRelationId,		/* OCLASS_PUBLICATION */
 	PublicationRelRelationId,	/* OCLASS_PUBLICATION_REL */
 	SubscriptionRelationId,		/* OCLASS_SUBSCRIPTION */
-	TransformRelationId			/* OCLASS_TRANSFORM */
+	TransformRelationId,		/* OCLASS_TRANSFORM */
+	VariableRelationId			/* OCLASS_VARIABLE */
 };
 
 
@@ -1508,6 +1512,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			DropObjectById(object);
 			break;
 
+		case OCLASS_VARIABLE:
+			DropVariable(object->objectId);
+			break;
+
 			/*
 			 * These global object types are not supported here.
 			 */
@@ -2966,6 +2974,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/meson.build b/src/backend/catalog/meson.build
index 08747914516..27306e35928 100644
--- a/src/backend/catalog/meson.build
+++ b/src/backend/catalog/meson.build
@@ -30,6 +30,7 @@ backend_sources += files(
   'pg_shdepend.c',
   'pg_subscription.c',
   'pg_type.c',
+  'pg_variable.c',
   'storage.c',
   'toasting.c',
 )
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 539df1da949..1a9c92b3a8c 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -39,6 +39,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "commands/dbcommands.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
@@ -764,6 +765,69 @@ RelationIsVisible(Oid relid)
 	return visible;
 }
 
+/*
+ * VariableIsVisible
+ *		Determine whether a variable (identified by OID) is visible in the
+ *		current search path. Visible means "would be found by searching
+ *		for the unqualified variable name".
+ */
+bool
+VariableIsVisible(Oid varid)
+{
+	HeapTuple	vartup;
+	Form_pg_variable varform;
+	Oid			varnamespace;
+	bool		visible;
+
+	vartup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+	if (!HeapTupleIsValid(vartup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+	varform = (Form_pg_variable) GETSTRUCT(vartup);
+
+	recomputeNamespacePath();
+
+	/*
+	 * Quick check: if it ain't in the path at all, it ain't visible. Items in
+	 * the system namespace are surely in the path and so we needn't even do
+	 * list_member_oid() for them.
+	 */
+	varnamespace = varform->varnamespace;
+	if (varnamespace != PG_CATALOG_NAMESPACE &&
+		!list_member_oid(activeSearchPath, varnamespace))
+		visible = false;
+	else
+	{
+		/*
+		 * If it is in the path, it might still not be visible; it could be
+		 * hidden by another variable of the same name earlier in the path. So
+		 * we must do a slow check for conflicting relations.
+		 */
+		char	   *varname = NameStr(varform->varname);
+		ListCell   *l;
+
+		visible = false;
+		foreach(l, activeSearchPath)
+		{
+			Oid			namespaceId = lfirst_oid(l);
+
+			if (namespaceId == varnamespace)
+			{
+				/* Found it first in path */
+				visible = true;
+				break;
+			}
+			if (OidIsValid(get_varname_varid(varname, namespaceId)))
+			{
+				/* Found something else first in path */
+				break;
+			}
+		}
+	}
+
+	ReleaseSysCache(vartup);
+
+	return visible;
+}
 
 /*
  * TypenameGetTypid
@@ -2839,6 +2903,384 @@ TSConfigIsVisible(Oid cfgid)
 	return visible;
 }
 
+/*
+ * Returns oid of session variable specified by possibly qualified identifier.
+ *
+ * If not found, returns InvalidOid if missing_ok, else throws error.
+ * When rowtype_only argument is true the session variables of not
+ * composite types are ignored. This should to reduce possible collisions.
+ */
+Oid
+LookupVariable(const char *nspname,
+			   const char *varname,
+			   bool rowtype_only,
+			   bool missing_ok)
+{
+	Oid			namespaceId;
+	Oid			varoid = InvalidOid;
+	ListCell   *l;
+
+	if (nspname)
+	{
+		namespaceId = LookupExplicitNamespace(nspname, missing_ok);
+
+		/*
+		 * If nspname is not a known namespace, then nspname.varname cannot be
+		 * any usable session variable.
+		 */
+		if (OidIsValid(namespaceId))
+		{
+			varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+									 PointerGetDatum(varname),
+									 ObjectIdGetDatum(namespaceId));
+
+			if (OidIsValid(varoid))
+			{
+				if (rowtype_only && !type_is_rowtype(get_session_variable_type(varoid)))
+					varoid = InvalidOid;
+			}
+		}
+	}
+	else
+	{
+		/* Iterate over schemas in search_path */
+		recomputeNamespacePath();
+
+		foreach(l, activeSearchPath)
+		{
+			namespaceId = lfirst_oid(l);
+
+			varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+									 PointerGetDatum(varname),
+									 ObjectIdGetDatum(namespaceId));
+
+			if (OidIsValid(varoid))
+			{
+				if (rowtype_only)
+				{
+					if (!type_is_rowtype(get_session_variable_type(varoid)))
+					{
+						varoid = InvalidOid;
+						continue;
+					}
+				}
+
+				break;
+			}
+		}
+	}
+
+	if (!OidIsValid(varoid) && !missing_ok)
+	{
+		if (nspname)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("session variable \"%s.%s\" does not exist",
+							nspname, varname)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("session variable \"%s\" does not exist",
+							varname)));
+	}
+
+	return varoid;
+}
+
+/*
+ * The input list contains names with indirection expressions used as the left
+ * part of LET statement. The following routine returns a new list with only
+ * initial strings (names) - without indirection expressions.
+ */
+List *
+NamesFromList(List *names)
+{
+	ListCell   *l;
+	List	   *result = NIL;
+
+	foreach(l, names)
+	{
+		Node	   *n = lfirst(l);
+
+		if (IsA(n, String))
+		{
+			result = lappend(result, n);
+		}
+		else
+			break;
+	}
+
+	return result;
+}
+
+/*
+ * IdentifyVariable - try to find variable identified by list of names.
+ *
+ * Before this call we don't know, how these fields should be mapped to
+ * schema name, variable name and attribute name. In this routine
+ * we try to apply passed names to all possible combinations of schema name,
+ * variable name and attribute name, and we count valid combinations.
+ *
+ * Returns oid of identified variable. When last field of names list is
+ * identified as an attribute, then output attrname argument is set to
+ * an string of this field.
+ *
+ * When there is not any valid combination, then we are sure, so the
+ * list of names cannot to identify any session variable. In this case
+ * we return InvalidOid.
+ *
+ * We can find more valid combination than one.
+ * Example: users can have session variable x in schema y, and
+ * session variable y with attribute x inside some schema from
+ * search path. In this situation the meaning of expression "y"."x"
+ * is ambiguous. In this case this routine returns oid of variable
+ * x in schema y, and the output parameter "not_unique" is set to
+ * true. In this case this variable is locked.
+ *
+ * The AccessShareLock is created on related session variable. The lock
+ * will be kept for the whole transaction.
+ *
+ * Note: the out attrname should be used only when the session variable
+ * is identified. When the session variable is not identified, then this
+ * output variable can hold reference to some string, but isn't sure
+ * about its semantics.
+ */
+Oid
+IdentifyVariable(List *names, char **attrname, bool *not_unique)
+{
+	Node	   *field1 = NULL;
+	Node	   *field2 = NULL;
+	Node	   *field3 = NULL;
+	Node	   *field4 = NULL;
+	char	   *a = NULL;
+	char	   *b = NULL;
+	char	   *c = NULL;
+	char	   *d = NULL;
+	Oid			varid = InvalidOid;
+	Oid			old_varid = InvalidOid;
+	uint64		inval_count;
+	bool		retry = false;
+
+	/*
+	 * DDL operations can change the results of a name lookup.  Since all such
+	 * operations will generate invalidation messages, we keep track of
+	 * whether any such messages show up while we're performing the operation,
+	 * and retry until either (1) no more invalidation messages show up or (2)
+	 * the answer doesn't change.
+	 */
+	for (;;)
+	{
+		Oid			varoid_without_attr = InvalidOid;
+		Oid			varoid_with_attr = InvalidOid;
+
+		*not_unique = false;
+		*attrname = NULL;
+		varid = InvalidOid;
+
+		inval_count = SharedInvalidMessageCounter;
+
+		switch (list_length(names))
+		{
+			case 1:
+				field1 = linitial(names);
+
+				Assert(IsA(field1, String));
+
+				varid = LookupVariable(NULL, strVal(field1), false, true);
+				break;
+
+			case 2:
+				field1 = linitial(names);
+				field2 = lsecond(names);
+
+				Assert(IsA(field1, String));
+				a = strVal(field1);
+
+				if (IsA(field2, String))
+				{
+					b = strVal(field2);
+
+					/*
+					 * a.b can mean "schema"."variable" or "variable"."field",
+					 * Check both variants, and returns InvalidOid with
+					 * not_unique flag, when both interpretations are
+					 * possible. Second node can be star. In this case, the
+					 * only allowed possibility is "variable"."*".
+					 */
+					varoid_without_attr = LookupVariable(a, b, false, true);
+					varoid_with_attr = LookupVariable(NULL, a, true, true);
+				}
+				else
+				{
+					/*
+					 * Session variables doesn't support unboxing by star
+					 * syntax. But this syntax have to be calculated here,
+					 * because can come from non session variables related
+					 * expressions.
+					 */
+					Assert(IsA(field2, A_Star));
+					break;
+				}
+
+				if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+				{
+					*not_unique = true;
+					varid = varoid_without_attr;
+				}
+				else if (OidIsValid(varoid_without_attr))
+				{
+					varid = varoid_without_attr;
+				}
+				else if (OidIsValid(varoid_with_attr))
+				{
+					*attrname = b;
+					varid = varoid_with_attr;
+				}
+				break;
+
+			case 3:
+				field1 = linitial(names);
+				field2 = lsecond(names);
+				field3 = lthird(names);
+
+				Assert(IsA(field1, String));
+				Assert(IsA(field2, String));
+
+				a = strVal(field1);
+				b = strVal(field2);
+
+				if (IsA(field3, String))
+				{
+					c = strVal(field3);
+
+					/*
+					 * a.b.c can mean "catalog"."schema"."variable" or
+					 * "schema"."variable"."field", Check both variants, and
+					 * returns InvalidOid with not_unique flag, when both
+					 * interpretations are possible. When third node is star,
+					 * the only possible interpretation is
+					 * "schema"."variable"."*".
+					 */
+					varoid_without_attr = LookupVariable(b, c, false, true);
+					varoid_with_attr = LookupVariable(a, b, true, true);
+				}
+				else
+				{
+					Assert(IsA(field3, A_Star));
+					break;
+				}
+
+				if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr))
+				{
+					*not_unique = true;
+					varid = varoid_without_attr;
+				}
+				else if (OidIsValid(varoid_without_attr))
+				{
+
+					/*
+					 * In this case, "a" is used as catalog name - check it.
+					 */
+					if (strcmp(a, get_database_name(MyDatabaseId)) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cross-database references are not implemented: %s",
+										NameListToString(names))));
+
+					varid = varoid_without_attr;
+				}
+				else if (OidIsValid(varoid_with_attr))
+				{
+					*attrname = c;
+					varid = varoid_with_attr;
+				}
+				break;
+
+			case 4:
+				field1 = linitial(names);
+				field2 = lsecond(names);
+				field3 = lthird(names);
+				field4 = lfourth(names);
+
+				Assert(IsA(field1, String));
+				Assert(IsA(field2, String));
+				Assert(IsA(field3, String));
+
+				a = strVal(field1);
+				b = strVal(field2);
+				c = strVal(field3);
+
+				/*
+				 * In this case, "a" is used as catalog name - check it.
+				 */
+				if (strcmp(a, get_database_name(MyDatabaseId)) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("cross-database references are not implemented: %s",
+									NameListToString(names))));
+
+				if (IsA(field4, String))
+				{
+					d = strVal(field4);
+				}
+				else
+				{
+					Assert(IsA(field4, A_Star));
+					return InvalidOid;
+				}
+
+				*attrname = d;
+				varid = LookupVariable(b, c, true, true);
+				break;
+
+			default:
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("improper qualified name (too many dotted names): %s",
+								NameListToString(names))));
+				break;
+		}
+
+		/*
+		 * If, upon retry, we get back the same OID we did last time, then the
+		 * invalidation messages we processed did not change the final answer.
+		 * So we're done.
+		 *
+		 * If we got a different OID, we've locked the variable that used to
+		 * have this name rather than the one that does now.  So release the
+		 * lock.
+		 */
+		if (retry)
+		{
+			if (old_varid == varid)
+				break;
+
+			if (OidIsValid(old_varid))
+				UnlockDatabaseObject(VariableRelationId, old_varid, 0, AccessShareLock);
+		}
+
+		/*
+		 * Lock the variable.  This will also accept any pending invalidation
+		 * messages.  If we got back InvalidOid, indicating not found, then
+		 * there's nothing to lock, but we accept invalidation messages
+		 * anyway, to flush any negative catcache entries that may be
+		 * lingering.
+		 */
+		if (!OidIsValid(varid))
+			AcceptInvalidationMessages();
+		else if (OidIsValid(varid))
+			LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
+
+		if (inval_count == SharedInvalidMessageCounter)
+			break;
+
+		retry = true;
+		old_varid = varid;
+		varid = InvalidOid;
+	}
+
+	return varid;
+}
 
 /*
  * DeconstructQualifiedName
@@ -4656,3 +5098,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 284ca55469e..26f557eb470 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -64,6 +64,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"
@@ -633,6 +634,20 @@ static const ObjectPropertyType ObjectProperty[] =
 		OBJECT_USER_MAPPING,
 		false
 	},
+	{
+		"session variable",
+		VariableRelationId,
+		VariableObjectIndexId,
+		VARIABLEOID,
+		VARIABLENAMENSP,
+		Anum_pg_variable_oid,
+		Anum_pg_variable_varname,
+		Anum_pg_variable_varnamespace,
+		Anum_pg_variable_varowner,
+		Anum_pg_variable_varacl,
+		OBJECT_VARIABLE,
+		true
+	}
 };
 
 /*
@@ -869,6 +884,10 @@ static const struct object_type_map
 	/* OCLASS_STATISTIC_EXT */
 	{
 		"statistics object", OBJECT_STATISTIC_EXT
+	},
+	/* OCLASS_VARIABLE */
+	{
+		"session variable", OBJECT_VARIABLE
 	}
 };
 
@@ -894,6 +913,7 @@ static ObjectAddress get_object_address_attrdef(ObjectType objtype,
 												bool missing_ok);
 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,
@@ -1163,6 +1183,9 @@ get_object_address(ObjectType objtype, Node *object,
 															 missing_ok);
 				address.objectSubId = 0;
 				break;
+			case OBJECT_VARIABLE:
+				address = get_object_address_variable(castNode(List, object), missing_ok);
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				/* placate compiler, in case it thinks elog might return */
@@ -2039,16 +2062,20 @@ get_object_address_defacl(List *object, bool missing_ok)
 		case DEFACLOBJ_NAMESPACE:
 			objtype_str = "schemas";
 			break;
+		case DEFACLOBJ_VARIABLE:
+			objtype_str = "variables";
+			break;
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("unrecognized default ACL object type \"%c\"", objtype),
-					 errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
+					 errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
 							 DEFACLOBJ_RELATION,
 							 DEFACLOBJ_SEQUENCE,
 							 DEFACLOBJ_FUNCTION,
 							 DEFACLOBJ_TYPE,
-							 DEFACLOBJ_NAMESPACE)));
+							 DEFACLOBJ_NAMESPACE,
+							 DEFACLOBJ_VARIABLE)));
 	}
 
 	/*
@@ -2132,6 +2159,24 @@ textarray_to_strvaluelist(ArrayType *arr)
 	return list;
 }
 
+/*
+ * Find the ObjectAddress for a session variable
+ */
+static ObjectAddress
+get_object_address_variable(List *object, bool missing_ok)
+{
+	ObjectAddress address;
+	char	   *nspname = NULL;
+	char	   *varname = NULL;
+
+	ObjectAddressSet(address, VariableRelationId, InvalidOid);
+
+	DeconstructQualifiedName(object, &nspname, &varname);
+	address.objectId = LookupVariable(nspname, varname, false, missing_ok);
+
+	return address;
+}
+
 /*
  * SQL-callable version of get_object_address
  */
@@ -2325,6 +2370,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:
@@ -2635,6 +2681,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
 							   NameListToString(castNode(List, object)));
 			break;
+		case OBJECT_VARIABLE:
+			if (!pg_variable_ownercheck(address.objectId, roleid))
+				aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
+							   NameListToString(castNode(List, object)));
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d",
 				 (int) objtype);
@@ -3536,6 +3587,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *nspname;
+				HeapTuple	tup;
+				Form_pg_variable varform;
+
+				tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for session variable %u",
+						 object->objectId);
+
+				varform = (Form_pg_variable) GETSTRUCT(tup);
+
+				if (VariableIsVisible(object->objectId))
+					nspname = NULL;
+				else
+					nspname = get_namespace_name(varform->varnamespace);
+
+				appendStringInfo(&buffer, _("session variable %s"),
+								 quote_qualified_identifier(nspname,
+															NameStr(varform->varname)));
+
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		case OCLASS_TSPARSER:
 			{
 				HeapTuple	tup;
@@ -3888,6 +3965,16 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 										 _("default privileges on new schemas belonging to role %s"),
 										 rolename);
 						break;
+					case DEFACLOBJ_VARIABLE:
+						if (nspname)
+							appendStringInfo(&buffer,
+											 _("default privileges on new session variables belonging to role %s in schema %s"),
+											 rolename, nspname);
+						else
+							appendStringInfo(&buffer,
+											 _("default privileges on new session variables belonging to role %s"),
+											 rolename);
+						break;
 					default:
 						/* shouldn't get here */
 						if (nspname)
@@ -4664,6 +4751,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			appendStringInfoString(&buffer, "transform");
 			break;
 
+		case OCLASS_VARIABLE:
+			appendStringInfoString(&buffer, "session variable");
+			break;
+
 			/*
 			 * There's intentionally no default: case here; we want the
 			 * compiler to warn if a new OCLASS hasn't been handled above.
@@ -5771,6 +5862,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 						appendStringInfoString(&buffer,
 											   " on schemas");
 						break;
+					case DEFACLOBJ_VARIABLE:
+						appendStringInfoString(&buffer,
+											   " on session variables");
+						break;
 				}
 
 				if (objname)
@@ -6014,6 +6109,33 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+		case OCLASS_VARIABLE:
+			{
+				char	   *schema;
+				char	   *varname;
+				HeapTuple	tup;
+				Form_pg_variable varform;
+
+				tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for session variable %u",
+						 object->objectId);
+
+				varform = (Form_pg_variable) GETSTRUCT(tup);
+
+				schema = get_namespace_name_or_temp(varform->varnamespace);
+				varname = NameStr(varform->varname);
+
+				appendStringInfo(&buffer, "%s",
+								 quote_qualified_identifier(schema, varname));
+
+				if (objname)
+					*objname = list_make2(schema, varname);
+
+				ReleaseSysCache(tup);
+				break;
+			}
+
 			/*
 			 * There's intentionally no default: case here; we want the
 			 * compiler to warn if a new OCLASS hasn't been handled above.
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index bc26bf1ef5a..d18e859db57 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_ts_dict.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "commands/alter.h"
 #include "commands/collationcmds.h"
 #include "commands/conversioncmds.h"
@@ -1613,6 +1614,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 				case DatabaseRelationId:
 				case TSConfigRelationId:
 				case TSDictionaryRelationId:
+				case VariableRelationId:
 					{
 						Oid			classId = sdepForm->classid;
 						Relation	catalog;
diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c
new file mode 100644
index 00000000000..973d8fd7d02
--- /dev/null
+++ b/src/backend/catalog/pg_variable.c
@@ -0,0 +1,372 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.c
+ *		session variables
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		src/backend/catalog/pg_variable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
+#include "miscadmin.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_type.h"
+#include "storage/lmgr.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/pg_lsn.h"
+#include "utils/syscache.h"
+
+
+static ObjectAddress create_variable(const char *varName,
+									 Oid varNamespace,
+									 Oid varType,
+									 int32 varTypmod,
+									 Oid varOwner,
+									 Oid varCollation,
+									 Node *varDefexpr,
+									 VariableEOXAction eoxaction,
+									 bool is_not_null,
+									 bool if_not_exists,
+									 bool is_immutable);
+
+
+/*
+ * Creates entry in pg_variable table
+ */
+static ObjectAddress
+create_variable(const char *varName,
+				Oid varNamespace,
+				Oid varType,
+				int32 varTypmod,
+				Oid varOwner,
+				Oid varCollation,
+				Node *varDefexpr,
+				VariableEOXAction eoxaction,
+				bool is_not_null,
+				bool if_not_exists,
+				bool is_immutable)
+{
+	Acl		   *varacl;
+	NameData	varname;
+	bool		nulls[Natts_pg_variable];
+	Datum		values[Natts_pg_variable];
+	Relation	rel;
+	HeapTuple	tup;
+	TupleDesc	tupdesc;
+	ObjectAddress myself,
+				referenced;
+	ObjectAddresses *addrs;
+	Oid			varid;
+
+	Assert(varName);
+	Assert(OidIsValid(varNamespace));
+	Assert(OidIsValid(varType));
+	Assert(OidIsValid(varOwner));
+
+	rel = table_open(VariableRelationId, RowExclusiveLock);
+
+	/*
+	 * Check for duplicates. Note that this does not really prevent
+	 * duplicates, it's here just to provide nicer error message in common
+	 * case. The real protection is the unique key on the catalog.
+	 */
+	if (SearchSysCacheExists2(VARIABLENAMENSP,
+							  PointerGetDatum(varName),
+							  ObjectIdGetDatum(varNamespace)))
+	{
+		if (if_not_exists)
+			ereport(NOTICE,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("session variable \"%s\" already exists, skipping",
+							varName)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("session variable \"%s\" already exists",
+							varName)));
+
+		table_close(rel, RowExclusiveLock);
+
+		return InvalidObjectAddress;
+	}
+
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	namestrcpy(&varname, varName);
+
+	varid = GetNewOidWithIndex(rel, VariableObjectIndexId, Anum_pg_variable_oid);
+
+	values[Anum_pg_variable_oid - 1] = ObjectIdGetDatum(varid);
+	values[Anum_pg_variable_create_lsn - 1] = LSNGetDatum(GetXLogInsertRecPtr());
+	values[Anum_pg_variable_varname - 1] = NameGetDatum(&varname);
+	values[Anum_pg_variable_varnamespace - 1] = ObjectIdGetDatum(varNamespace);
+	values[Anum_pg_variable_vartype - 1] = ObjectIdGetDatum(varType);
+	values[Anum_pg_variable_vartypmod - 1] = Int32GetDatum(varTypmod);
+	values[Anum_pg_variable_varowner - 1] = ObjectIdGetDatum(varOwner);
+	values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation);
+	values[Anum_pg_variable_varisnotnull - 1] = BoolGetDatum(is_not_null);
+	values[Anum_pg_variable_varisimmutable - 1] = BoolGetDatum(is_immutable);
+	values[Anum_pg_variable_vareoxaction - 1] = CharGetDatum(eoxaction);
+
+	/* varacl will be determined later */
+
+	if (varDefexpr)
+		values[Anum_pg_variable_vardefexpr - 1] = CStringGetTextDatum(nodeToString(varDefexpr));
+	else
+		nulls[Anum_pg_variable_vardefexpr - 1] = true;
+
+	tupdesc = RelationGetDescr(rel);
+
+	varacl = get_user_default_acl(OBJECT_VARIABLE, varOwner,
+								  varNamespace);
+
+	if (varacl != NULL)
+		values[Anum_pg_variable_varacl - 1] = PointerGetDatum(varacl);
+	else
+		nulls[Anum_pg_variable_varacl - 1] = true;
+
+	tup = heap_form_tuple(tupdesc, values, nulls);
+	CatalogTupleInsert(rel, tup);
+	Assert(OidIsValid(varid));
+
+	addrs = new_object_addresses();
+
+	ObjectAddressSet(myself, VariableRelationId, varid);
+
+	/* dependency on namespace */
+	ObjectAddressSet(referenced, NamespaceRelationId, varNamespace);
+	add_exact_object_address(&referenced, addrs);
+
+	/* dependency on used type */
+	ObjectAddressSet(referenced, TypeRelationId, varType);
+	add_exact_object_address(&referenced, addrs);
+
+	/* dependency on collation */
+	if (OidIsValid(varCollation) &&
+		varCollation != DEFAULT_COLLATION_OID)
+	{
+		ObjectAddressSet(referenced, CollationRelationId, varCollation);
+		add_exact_object_address(&referenced, addrs);
+	}
+
+	/* dependency on default expr */
+	if (varDefexpr)
+		recordDependencyOnExpr(&myself, (Node *) varDefexpr,
+							   NIL, DEPENDENCY_NORMAL);
+
+	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
+	free_object_addresses(addrs);
+
+	/* dependency on owner */
+	recordDependencyOnOwner(VariableRelationId, varid, varOwner);
+
+	/* dependencies on roles mentioned in default ACL */
+	recordDependencyOnNewAcl(VariableRelationId, varid, 0, varOwner, varacl);
+
+	/* dependency on extension */
+	recordDependencyOnCurrentExtension(&myself, false);
+
+	heap_freetuple(tup);
+
+	/* Post creation hook for new function */
+	InvokeObjectPostCreateHook(VariableRelationId, varid, 0);
+
+	table_close(rel, RowExclusiveLock);
+
+	return myself;
+}
+
+/*
+ * Creates a new variable
+ *
+ * Used by CREATE VARIABLE command
+ */
+ObjectAddress
+CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt)
+{
+	Oid			namespaceid;
+	AclResult	aclresult;
+	Oid			typid;
+	int32		typmod;
+	Oid			varowner = GetUserId();
+	Oid			collation;
+	Oid			typcollation;
+	ObjectAddress variable;
+
+	Node	   *cooked_default = NULL;
+
+	/*
+	 * Check consistency of arguments
+	 */
+	if (stmt->eoxaction == VARIABLE_EOX_DROP
+		&& stmt->variable->relpersistence != RELPERSISTENCE_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("ON COMMIT DROP can only be used on temporary variables")));
+
+	if (stmt->is_not_null && stmt->is_immutable && !stmt->defexpr)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("IMMUTABLE NOT NULL variable requires default expression")));
+
+	namespaceid =
+		RangeVarGetAndCheckCreationNamespace(stmt->variable, NoLock, NULL);
+
+	typenameTypeIdAndMod(pstate, stmt->typeName, &typid, &typmod);
+	typcollation = get_typcollation(typid);
+
+	aclresult = pg_type_aclcheck(typid, GetUserId(), ACL_USAGE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error_type(aclresult, typid);
+
+	if (stmt->collClause)
+		collation = LookupCollation(pstate,
+									stmt->collClause->collname,
+									stmt->collClause->location);
+	else
+		collation = typcollation;;
+
+	/* Complain if COLLATE is applied to an uncollatable type */
+	if (OidIsValid(collation) && !OidIsValid(typcollation))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("collations are not supported by type %s",
+						format_type_be(typid)),
+				 parser_errposition(pstate, stmt->collClause->location)));
+
+	if (stmt->defexpr)
+	{
+		cooked_default = transformExpr(pstate, stmt->defexpr,
+									   EXPR_KIND_VARIABLE_DEFAULT);
+
+		cooked_default = coerce_to_specific_type(pstate,
+												 cooked_default, typid, "DEFAULT");
+		assign_expr_collations(pstate, cooked_default);
+	}
+
+	variable = create_variable(stmt->variable->relname,
+							   namespaceid,
+							   typid,
+							   typmod,
+							   varowner,
+							   collation,
+							   cooked_default,
+							   stmt->eoxaction,
+							   stmt->is_not_null,
+							   stmt->if_not_exists,
+							   stmt->is_immutable);
+
+	elog(DEBUG1, "record for session variable \"%s\" (oid:%d) was created in pg_variable",
+		 stmt->variable->relname, variable.objectId);
+
+	/* We want SessionVariableCreatePostprocess to see the catalog changes. */
+	CommandCounterIncrement();
+
+	SessionVariableCreatePostprocess(variable.objectId, stmt->eoxaction);
+
+	return variable;
+}
+
+/*
+ * Drop variable by OID, and register the needed session variable
+ * cleanup.
+ */
+void
+DropVariable(Oid varid)
+{
+	Relation	rel;
+	HeapTuple	tup;
+
+	rel = table_open(VariableRelationId, RowExclusiveLock);
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+
+	CatalogTupleDelete(rel, &tup->t_self);
+
+	ReleaseSysCache(tup);
+
+	table_close(rel, RowExclusiveLock);
+
+	/* Do the necessary cleanup if needed in local memory */
+	SessionVariableDropPostprocess(varid);
+}
+
+/*
+ * Fetch attributes (without acl) of session variable from the syscache.
+ * We don't work with acl directly, so we don't need to read it here.
+ * Skip deserialization of defexpr when fast_only is true.
+ */
+void
+InitVariable(Variable *var, Oid varid, bool fast_only)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	Datum		defexpr_datum;
+	bool		defexpr_isnull;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	var->oid = varid;
+	var->create_lsn = varform->create_lsn;
+	var->name = pstrdup(NameStr(varform->varname));
+	var->namespaceid = varform->varnamespace;
+	var->typid = varform->vartype;
+	var->typmod = varform->vartypmod;
+	var->owner = varform->varowner;
+	var->collation = varform->varcollation;
+	var->is_immutable = varform->varisimmutable;
+	var->is_not_null = varform->varisnotnull;
+	var->eoxaction = varform->vareoxaction;
+
+	/* Get defexpr */
+	defexpr_datum = SysCacheGetAttr(VARIABLEOID,
+									tup,
+									Anum_pg_variable_vardefexpr,
+									&defexpr_isnull);
+
+	var->has_defexpr = !defexpr_isnull;
+
+	/*
+	 * Deserialize defexpr only when it is requested. We need to deserialize
+	 * Node with default expression, only when we read from session variable,
+	 * and this session variable has not assigned value, and this session
+	 * variable has default expression. For other cases, we skip skip this
+	 * operation.
+	 */
+	if (!fast_only && !defexpr_isnull)
+		var->defexpr = stringToNode(TextDatumGetCString(defexpr_datum));
+	else
+		var->defexpr = NULL;
+
+	ReleaseSysCache(tup);
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91c..642454c745c 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -48,6 +48,7 @@ OBJS = \
 	proclang.o \
 	publicationcmds.o \
 	schemacmds.o \
+	session_variable.o \
 	seclabel.o \
 	sequence.o \
 	statscmds.o \
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 55219bb0974..c39a2787588 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -40,6 +40,7 @@
 #include "catalog/pg_ts_dict.h"
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
+#include "catalog/pg_variable.h"
 #include "commands/alter.h"
 #include "commands/collationcmds.h"
 #include "commands/conversioncmds.h"
@@ -141,6 +142,10 @@ report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
 			Assert(OidIsValid(nspOid));
 			msgfmt = gettext_noop("text search configuration \"%s\" already exists in schema \"%s\"");
 			break;
+		case VariableRelationId:
+			Assert(OidIsValid(nspOid));
+			msgfmt = gettext_noop("session variable \"%s\" already exists in schema \"%s\"");
+			break;
 		default:
 			elog(ERROR, "unsupported object class %u", classId);
 			break;
@@ -393,6 +398,7 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_PUBLICATION:
 		case OBJECT_SUBSCRIPTION:
+		case OBJECT_VARIABLE:
 			{
 				ObjectAddress address;
 				Relation	catalog;
@@ -536,6 +542,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_VARIABLE:
 			{
 				Relation	catalog;
 				Relation	relation;
@@ -626,6 +633,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 		case OCLASS_TSDICT:
 		case OCLASS_TSTEMPLATE:
 		case OCLASS_TSCONFIG:
+		case OCLASS_VARIABLE:
 			{
 				Relation	catalog;
 
@@ -886,6 +894,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 		case OBJECT_TABLESPACE:
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSCONFIGURATION:
+		case OBJECT_VARIABLE:
 			{
 				Relation	catalog;
 				Relation	relation;
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 26157eb4e3f..b55b3b05286 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -480,6 +480,10 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 			msg = gettext_noop("publication \"%s\" does not exist, skipping");
 			name = strVal(object);
 			break;
+		case OBJECT_VARIABLE:
+			msg = gettext_noop("session variable \"%s\" does not exist, skipping");
+			name = NameListToString(castNode(List, object));
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d", (int) objtype);
 			break;
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 8d36b664880..875f19538ba 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -991,6 +991,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_TYPE:
 		case OBJECT_USER_MAPPING:
+		case OBJECT_VARIABLE:
 		case OBJECT_VIEW:
 			return true;
 
@@ -1057,6 +1058,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_PUBLICATION_REL:
 		case OCLASS_SUBSCRIPTION:
 		case OCLASS_TRANSFORM:
+		case OCLASS_VARIABLE:
 			return true;
 
 			/*
@@ -2049,6 +2051,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:
@@ -2132,6 +2136,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 			return "TABLESPACES";
 		case OBJECT_TYPE:
 			return "TYPES";
+		case OBJECT_VARIABLE:
+			return "VARIABLES";
 			/* these currently aren't used */
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_AGGREGATE:
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 7ae19b98bb9..9b113745356 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -92,6 +92,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_USER_MAPPING:
+		case OBJECT_VARIABLE:
 			return false;
 
 			/*
diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c
new file mode 100644
index 00000000000..3eafc05f898
--- /dev/null
+++ b/src/backend/commands/session_variable.c
@@ -0,0 +1,242 @@
+/*-------------------------------------------------------------------------
+ *
+ * session_variable.c
+ *	  session variable creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/sessionvariable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+
+/*
+ * The life cycle of temporary session variable can be
+ * limmited by using clause ON COMMIT DROP.
+ */
+typedef enum SVariableXActAction
+{
+	SVAR_ON_COMMIT_DROP,		/* used for ON COMMIT DROP */
+} SVariableXActAction;
+
+typedef struct SVariableXActActionItem
+{
+	Oid			varid;			/* varid of session variable */
+
+	/*
+	 * creating_subid is the ID of the creating subxact. If the action was
+	 * unregistered during the current transaction, deleting_subid is the ID
+	 * of the deleting subxact, otherwise InvalidSubTransactionId.
+	 */
+	SubTransactionId creating_subid;
+	SubTransactionId deleting_subid;
+} SVariableXActActionItem;
+
+/* List holds fields of SVariableXActActionItem type */
+static List *xact_drop_actions = NIL;
+
+static void register_session_variable_xact_action(Oid varid, SVariableXActAction action);
+static void unregister_session_variable_xact_action(Oid varid, SVariableXActAction action);
+
+
+/*
+ * Do the necessary work to setup local memory management of a new
+ * variable.
+ *
+ * Caller should already have created the necessary entry in catalog
+ * and made them visible.
+ */
+void
+SessionVariableCreatePostprocess(Oid varid, char eoxaction)
+{
+	/*
+	 * For temporary variables, we need to create a new end of xact action to
+	 * ensure deletion from catalog.
+	 */
+	if (eoxaction == VARIABLE_EOX_DROP)
+	{
+		Assert(isTempNamespace(get_session_variable_namespace(varid)));
+
+		register_session_variable_xact_action(varid, SVAR_ON_COMMIT_DROP);
+	}
+}
+
+/*
+ * Handle the local memory cleanup for a DROP VARIABLE command.
+ *
+ * Caller should take care of removing the pg_variable entry first.
+ */
+void
+SessionVariableDropPostprocess(Oid varid)
+{
+	/*
+	 * The entry was removed from catalog already, we must not do it
+	 * again at end of xact time.
+	 */
+	unregister_session_variable_xact_action(varid, SVAR_ON_COMMIT_DROP);
+}
+
+/*
+ * Registration of actions to be executed on session variables at transaction
+ * end time. We want to drop temporary session variables with clause ON COMMIT
+ * DROP, or we want to reset values of session variables with clause ON
+ * TRANSACTION END RESET or we want to clean (reset) local memory allocated by
+ * values of dropped session variables.
+ */
+
+/*
+ * Register a session variable xact action.
+ */
+static void
+register_session_variable_xact_action(Oid varid,
+									  SVariableXActAction action)
+{
+	SVariableXActActionItem *xact_ai;
+	MemoryContext oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+	xact_ai = (SVariableXActActionItem *)
+		palloc(sizeof(SVariableXActActionItem));
+
+	xact_ai->varid = varid;
+
+	xact_ai->creating_subid = GetCurrentSubTransactionId();
+	xact_ai->deleting_subid = InvalidSubTransactionId;
+
+	Assert(action == SVAR_ON_COMMIT_DROP);
+	xact_drop_actions = lcons(xact_ai, xact_drop_actions);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Unregister an action on a given session variable from action list. In this
+ * moment, the action is just marked as deleted by setting deleting_subid. The
+ * calling even might be rollbacked, in which case we should not lose this
+ * action.
+ */
+static void
+unregister_session_variable_xact_action(Oid varid,
+										SVariableXActAction action)
+{
+	ListCell   *l;
+
+	Assert(action == SVAR_ON_COMMIT_DROP);
+
+	foreach(l, xact_drop_actions)
+	{
+		SVariableXActActionItem *xact_ai =
+		(SVariableXActActionItem *) lfirst(l);
+
+		if (xact_ai->varid == varid)
+			xact_ai->deleting_subid = GetCurrentSubTransactionId();
+	}
+}
+
+/*
+ * Perform ON TRANSACTION END RESET or ON COMMIT DROP
+ * and COMMIT/ROLLBACK of transaction session variables.
+ */
+void
+AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit)
+{
+	ListCell   *l;
+
+	foreach(l, xact_drop_actions)
+	{
+		SVariableXActActionItem *xact_ai =
+		(SVariableXActActionItem *) lfirst(l);
+
+		/* Iterate only over entries that are still pending */
+		if (xact_ai->deleting_subid == InvalidSubTransactionId)
+		{
+
+			/*
+			 * ON COMMIT DROP is allowed only for temp session variables. So
+			 * we should explicitly delete only when current transaction was
+			 * committed. When it's rollback, then session variable is removed
+			 * automatically.
+			 */
+			if (isCommit)
+			{
+				ObjectAddress object;
+
+				object.classId = VariableRelationId;
+				object.objectId = xact_ai->varid;
+				object.objectSubId = 0;
+
+				/*
+				 * Since this is an automatic drop, rather than one directly
+				 * initiated by the user, we pass the
+				 * PERFORM_DELETION_INTERNAL flag.
+				 */
+				elog(DEBUG1, "session variable (oid:%u) will be deleted (forced by SVAR_ON_COMMIT_DROP action)",
+					 xact_ai->varid);
+
+				performDeletion(&object, DROP_CASCADE,
+								PERFORM_DELETION_INTERNAL |
+								PERFORM_DELETION_QUIETLY);
+			}
+		}
+	}
+
+	/*
+	 * Any drop action left is an entry that was unregistered and not
+	 * rollbacked, so we can simply remove them.
+	 */
+	list_free_deep(xact_drop_actions);
+	xact_drop_actions = NIL;
+}
+
+/*
+ * Post-subcommit or post-subabort cleanup of xact action list.
+ *
+ * During subabort, we can immediately remove entries created during this
+ * subtransaction. During subcommit, just transfer entries marked during
+ * this subtransaction as being the parent's responsibility.
+ */
+void
+AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySubid,
+											SubTransactionId parentSubid)
+{
+	ListCell   *cur_item;
+
+	foreach(cur_item, xact_drop_actions)
+	{
+		SVariableXActActionItem *xact_ai =
+		(SVariableXActActionItem *) lfirst(cur_item);
+
+		if (!isCommit && xact_ai->creating_subid == mySubid)
+		{
+			/* cur_item must be removed */
+			xact_drop_actions = foreach_delete_current(xact_drop_actions, cur_item);
+			pfree(xact_ai);
+		}
+		else
+		{
+			/* cur_item must be preserved */
+			if (xact_ai->creating_subid == mySubid)
+				xact_ai->creating_subid = parentSubid;
+			if (xact_ai->deleting_subid == mySubid)
+				xact_ai->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId;
+		}
+	}
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6007e10730a..4c484e2135f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
@@ -6435,6 +6436,8 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
  * Eventually, we'd like to propagate the check or rewrite operation
  * into such tables, but for now, just error out if we find any.
  *
+ * Check if the type "typeOid" is used as type of some session variable too.
+ *
  * Caller should provide either the associated relation of a rowtype,
  * or a type name (not both) for use in the error message, if any.
  *
@@ -6496,10 +6499,47 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 		}
 
+		/* Don't allow change of type usedby session's variable */
+		if (pg_depend->classid == VariableRelationId)
+		{
+			Oid			varid = pg_depend->objid;
+
+			if (origTypeName)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it",
+								origTypeName,
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter foreign table \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+			else if (origRelation->rd_rel->relkind == RELKIND_RELATION ||
+					 origRelation->rd_rel->relkind == RELKIND_MATVIEW ||
+					 origRelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter table \"%s\" because session variable \"%s.%s\" uses it",
+								RelationGetRelationName(origRelation),
+								get_namespace_name(get_session_variable_namespace(varid)),
+								get_session_variable_name(varid))));
+		}
+
 		/* Else, ignore dependees that aren't user columns of relations */
 		/* (we assume system columns are never of interesting types) */
-		if (pg_depend->classid != RelationRelationId ||
-			pg_depend->objsubid <= 0)
+		else if (pg_depend->classid != RelationRelationId ||
+				 pg_depend->objsubid <= 0)
 			continue;
 
 		rel = relation_open(pg_depend->objid, AccessShareLock);
@@ -12726,6 +12766,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_PUBLICATION_REL:
 			case OCLASS_SUBSCRIPTION:
 			case OCLASS_TRANSFORM:
+			case OCLASS_VARIABLE:
 
 				/*
 				 * We don't expect any of these sorts of objects to depend on
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index deb101710e4..0fbe1d1e5ac 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -53,6 +53,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_trigger.h"
+#include "catalog/pg_variable.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "gramparse.h"
@@ -294,8 +295,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
 		CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt
 		CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
-		CreateSchemaStmt CreateSeqStmt CreateStmt CreateStatsStmt CreateTableSpaceStmt
-		CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
+		CreateSchemaStmt CreateSessionVarStmt CreateSeqStmt CreateStmt CreateStatsStmt
+		CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
 		CreateAssertionStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
 		CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
 		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
@@ -475,6 +476,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	 OptTemp
 %type <ival>	 OptNoLog
 %type <oncommit> OnCommitOption
+%type <ival>	 OnEOXActionOption
 
 %type <ival>	for_locking_strength
 %type <node>	for_locking_item
@@ -644,6 +646,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <partboundspec> PartitionBoundSpec
 %type <list>		hash_partbound
 %type <defelt>		hash_partbound_elem
+%type <node>		OptSessionVarDefExpr
+%type <boolean>		OptNotNull OptImmutable
 
 
 /*
@@ -754,8 +758,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UESCAPE UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
-	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
 
@@ -1002,6 +1006,7 @@ stmt:
 			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
+			| CreateSessionVarStmt
 			| CreateSeqStmt
 			| CreateStmt
 			| CreateSubscriptionStmt
@@ -1546,6 +1551,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateSessionVarStmt
 		;
 
 
@@ -5010,6 +5016,69 @@ create_extension_opt_item:
 				}
 		;
 
+/*****************************************************************************
+ *
+ *		QUERY :
+ *				CREATE VARIABLE varname [AS] type
+ *
+ *****************************************************************************/
+
+CreateSessionVarStmt:
+			CREATE OptTemp OptImmutable VARIABLE qualified_name opt_as Typename opt_collate_clause OptNotNull OptSessionVarDefExpr OnEOXActionOption
+				{
+					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
+					$5->relpersistence = $2;
+					n->is_immutable = $3;
+					n->variable = $5;
+					n->typeName = $7;
+					n->collClause = (CollateClause *) $8;
+					n->is_not_null = $9;
+					n->defexpr = $10;
+					n->eoxaction = $11;
+					n->if_not_exists = false;
+					$$ = (Node *) n;
+				}
+			| CREATE OptTemp OptImmutable VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OptNotNull OptSessionVarDefExpr OnEOXActionOption
+				{
+					CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt);
+					$8->relpersistence = $2;
+					n->is_immutable = $3;
+					n->variable = $8;
+					n->typeName = $10;
+					n->collClause = (CollateClause *) $11;
+					n->is_not_null = $12;
+					n->defexpr = $13;
+					n->eoxaction = $14;
+					n->if_not_exists = true;
+					$$ = (Node *) n;
+				}
+		;
+
+OptSessionVarDefExpr: DEFAULT b_expr					{ $$ = $2; }
+			| /* EMPTY */							{ $$ = NULL; }
+		;
+
+/*
+ * Temporary session variables can be dropped on successful
+ * transaction end like tables.  RESET can only be forced on
+ * transaction end. Since the session variables are not
+ * transactional, we have to handle ROLLBACK too.
+ * The clause ON TRANSACTION END is clearer than some
+ * ON COMMIT ROLLBACK RESET clause.
+ */
+OnEOXActionOption:  ON COMMIT DROP					{ $$ = VARIABLE_EOX_DROP; }
+			| ON TRANSACTION END_P RESET			{ $$ = VARIABLE_EOX_RESET; }
+			| /*EMPTY*/								{ $$ = VARIABLE_EOX_NOOP; }
+		;
+
+OptNotNull: NOT NULL_P								{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
+OptImmutable: IMMUTABLE								{ $$ = true; }
+			| /* EMPTY */							{ $$ = false; }
+		;
+
 /*****************************************************************************
  *
  * ALTER EXTENSION name UPDATE [ TO version ]
@@ -6787,6 +6856,7 @@ object_type_any_name:
 			| TEXT_P SEARCH DICTIONARY				{ $$ = OBJECT_TSDICTIONARY; }
 			| TEXT_P SEARCH TEMPLATE				{ $$ = OBJECT_TSTEMPLATE; }
 			| TEXT_P SEARCH CONFIGURATION			{ $$ = OBJECT_TSCONFIGURATION; }
+			| VARIABLE								{ $$ = OBJECT_VARIABLE; }
 		;
 
 /*
@@ -7663,6 +7733,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));
@@ -7708,6 +7786,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;
+				}
 		;
 
 
@@ -7905,6 +7991,7 @@ defacl_privilege_target:
 			| SEQUENCES		{ $$ = OBJECT_SEQUENCE; }
 			| TYPES_P		{ $$ = OBJECT_TYPE; }
 			| SCHEMAS		{ $$ = OBJECT_SCHEMA; }
+			| VARIABLES		{ $$ = OBJECT_VARIABLE; }
 		;
 
 
@@ -9687,6 +9774,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
@@ -10048,6 +10154,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;
+				}
+
 		;
 
 /*****************************************************************************
@@ -10327,6 +10452,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;
+				}
 		;
 
 
@@ -16941,6 +17074,8 @@ unreserved_keyword:
 			| VALIDATE
 			| VALIDATOR
 			| VALUE_P
+			| VARIABLE
+			| VARIABLES
 			| VARYING
 			| VERSION_P
 			| VIEW
@@ -17552,6 +17687,8 @@ bare_label_keyword:
 			| VALUE_P
 			| VALUES
 			| VARCHAR
+			| VARIABLE
+			| VARIABLES
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3ef9e8ee5e1..dbe630a51bc 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -471,6 +471,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 
 			if (isAgg)
 				err = _("aggregate functions are not allowed in DEFAULT expressions");
@@ -915,6 +916,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("window functions are not allowed in DEFAULT expressions");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e5fc708c8a5..5b1fadf183e 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"
@@ -505,6 +506,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_COPY_WHERE:
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_VARIABLE_DEFAULT:
+
 			/* okay */
 			break;
 
@@ -1726,6 +1729,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("cannot use subquery in DEFAULT expression");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
@@ -3066,6 +3070,7 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "CHECK";
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			return "DEFAULT";
 		case EXPR_KIND_INDEX_EXPRESSION:
 			return "index expression";
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 827989f3799..005bc0400f0 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2621,6 +2621,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 			break;
 		case EXPR_KIND_COLUMN_DEFAULT:
 		case EXPR_KIND_FUNCTION_DEFAULT:
+		case EXPR_KIND_VARIABLE_DEFAULT:
 			err = _("set-returning functions are not allowed in DEFAULT expressions");
 			break;
 		case EXPR_KIND_INDEX_EXPRESSION:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 8140e79d8f1..339977a1427 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -107,6 +107,7 @@ typedef struct
 	List	   *indexes;		/* CREATE INDEX items */
 	List	   *triggers;		/* CREATE TRIGGER items */
 	List	   *grants;			/* GRANT items */
+	List	   *variables;		/* CREATE VARIABLE items */
 } CreateSchemaStmtContext;
 
 
@@ -3839,6 +3840,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 	cxt.indexes = NIL;
 	cxt.triggers = NIL;
 	cxt.grants = NIL;
+	cxt.variables = NIL;
 
 	/*
 	 * Run through each schema element in the schema element list. Separate
@@ -3907,6 +3909,15 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 				cxt.grants = lappend(cxt.grants, element);
 				break;
 
+			case T_CreateSessionVarStmt:
+				{
+					CreateSessionVarStmt *elp = (CreateSessionVarStmt *) element;
+
+					setSchemaName(cxt.schemaname, &elp->variable->schemaname);
+					cxt.variables = lappend(cxt.variables, element);
+				}
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -3920,6 +3931,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
 	result = list_concat(result, cxt.indexes);
 	result = list_concat(result, cxt.triggers);
 	result = list_concat(result, cxt.grants);
+	result = list_concat(result, cxt.variables);
 
 	return result;
 }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 247d0816ad8..d1a7d995e15 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -49,6 +49,7 @@
 #include "commands/proclang.h"
 #include "commands/publicationcmds.h"
 #include "commands/schemacmds.h"
+#include "commands/session_variable.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
 #include "commands/subscriptioncmds.h"
@@ -189,6 +190,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_CreateRangeStmt:
 		case T_CreateRoleStmt:
 		case T_CreateSchemaStmt:
+		case T_CreateSessionVarStmt:
 		case T_CreateSeqStmt:
 		case T_CreateStatsStmt:
 		case T_CreateStmt:
@@ -1393,6 +1395,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				}
 				break;
 
+			case T_CreateSessionVarStmt:
+				address = CreateVariable(pstate, (CreateSessionVarStmt *) parsetree);
+				break;
+
 				/*
 				 * ************* object creation / destruction **************
 				 */
@@ -2332,6 +2338,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_STATISTIC_EXT:
 			tag = CMDTAG_ALTER_STATISTICS;
 			break;
+		case OBJECT_VARIABLE:
+			tag = CMDTAG_ALTER_VARIABLE;
+			break;
 		default:
 			tag = CMDTAG_UNKNOWN;
 			break;
@@ -2640,6 +2649,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_STATISTIC_EXT:
 					tag = CMDTAG_DROP_STATISTICS;
 					break;
+				case OBJECT_VARIABLE:
+					tag = CMDTAG_DROP_VARIABLE;
+					break;
 				default:
 					tag = CMDTAG_UNKNOWN;
 			}
@@ -3216,6 +3228,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_CreateSessionVarStmt:
+			tag = CMDTAG_CREATE_VARIABLE;
+			break;
+
 		default:
 			elog(WARNING, "unrecognized node type: %d",
 				 (int) nodeTag(parsetree));
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 4fac402e5b6..80a46b0925a 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -806,6 +806,10 @@ acldefault(ObjectType objtype, Oid ownerId)
 			world_default = ACL_NO_RIGHTS;
 			owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL;
 			break;
+		case OBJECT_VARIABLE:
+			world_default = ACL_NO_RIGHTS;
+			owner_default = ACL_ALL_RIGHTS_VARIABLE;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			world_default = ACL_NO_RIGHTS;	/* keep compiler quiet */
@@ -903,6 +907,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);
 	}
@@ -1605,7 +1612,6 @@ makeaclitem(PG_FUNCTION_ARGS)
 	PG_RETURN_ACLITEM_P(result);
 }
 
-
 /*
  * convert_any_priv_string: recognize privilege strings for has_foo_privilege
  *
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a16a63f4957..f4b1f57823e 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -36,6 +36,7 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3679,3 +3680,115 @@ get_subscription_name(Oid subid, bool missing_ok)
 
 	return subname;
 }
+
+/*				---------- PG_VARIABLE CACHE ----------				 */
+
+/*
+ * get_varname_varid
+ *		Given name and namespace of variable, look up the OID.
+ */
+Oid
+get_varname_varid(const char *varname, Oid varnamespace)
+{
+	return GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid,
+						   PointerGetDatum(varname),
+						   ObjectIdGetDatum(varnamespace));
+}
+
+/*
+ * get_session_variable_name
+ *		Returns a palloc'd copy of the name of a given session variable.
+ */
+char *
+get_session_variable_name(Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	char	   *varname;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	varname = pstrdup(NameStr(varform->varname));
+
+	ReleaseSysCache(tup);
+
+	return varname;
+}
+
+/*
+ * get_session_variable_namespace
+ *		Returns the pg_namespace OID associated with a given session variable.
+ */
+Oid
+get_session_variable_namespace(Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	Oid			varnamespace;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	varnamespace = varform->varnamespace;
+
+	ReleaseSysCache(tup);
+
+	return varnamespace;
+}
+
+/*
+ * Returns the type, typmod and collid of the given session variable.
+ */
+Oid
+get_session_variable_type(Oid varid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+	Oid			vartype;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	vartype = varform->vartype;
+
+	ReleaseSysCache(tup);
+
+	return vartype;
+}
+
+/*
+ * Returns the type, typmod and collid of the given session variable.
+ */
+void
+get_session_variable_type_typmod_collid(Oid varid, Oid *typid, int32 *typmod,
+										Oid *collid)
+{
+	HeapTuple	tup;
+	Form_pg_variable varform;
+
+	tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for session variable %u", varid);
+
+	varform = (Form_pg_variable) GETSTRUCT(tup);
+
+	*typid = varform->vartype;
+	*typmod = varform->vartypmod;
+	*collid = varform->varcollation;
+
+	ReleaseSysCache(tup);
+}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index eec644ec848..b6d756a056d 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -75,6 +75,7 @@
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "catalog/pg_variable.h"
 #include "lib/qunique.h"
 #include "utils/catcache.h"
 #include "utils/rel.h"
@@ -1037,6 +1038,28 @@ static const struct cachedesc cacheinfo[] = {
 			0
 		},
 		2
+	},
+	{VariableRelationId,		/* VARIABLENAMENSP */
+		VariableNameNspIndexId,
+		2,
+		{
+			Anum_pg_variable_varname,
+			Anum_pg_variable_varnamespace,
+			0,
+			0
+		},
+		8
+	},
+	{VariableRelationId,		/* VARIABLEOID */
+		VariableObjectIndexId,
+		1,
+		{
+			Anum_pg_variable_oid,
+			0,
+			0,
+			0
+		},
+		8
 	}
 };
 
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index a9dd068095b..af74fe345ed 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1896,9 +1896,9 @@ get_call_expr_arg_stable(Node *expr, int argnum)
 	arg = (Node *) list_nth(args, argnum);
 
 	/*
-	 * Either a true Const or an external Param will have a value that doesn't
-	 * change during the execution of the query.  In future we might want to
-	 * consider other cases too, e.g. now().
+	 * Either a true Const or an external Param or variable will have a value
+	 * that doesn't change during the execution of the query.  In future we
+	 * might want to consider other cases too, e.g. now().
 	 */
 	if (IsA(arg, Const))
 		return true;
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 98a1a842890..84a851bfa6d 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -127,10 +127,11 @@ typedef enum ObjectClass
 	OCLASS_PUBLICATION_NAMESPACE,	/* pg_publication_namespace */
 	OCLASS_PUBLICATION_REL,		/* pg_publication_rel */
 	OCLASS_SUBSCRIPTION,		/* pg_subscription */
-	OCLASS_TRANSFORM			/* pg_transform */
+	OCLASS_TRANSFORM,			/* pg_transform */
+	OCLASS_VARIABLE				/* pg_variable */
 } ObjectClass;
 
-#define LAST_OCLASS		OCLASS_TRANSFORM
+#define LAST_OCLASS		OCLASS_VARIABLE
 
 /* flag bits for performDeletion/performMultipleDeletions: */
 #define PERFORM_DELETION_INTERNAL			0x0001	/* internal action */
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index 45ffa99692e..d041d728fb9 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -63,6 +63,7 @@ catalog_headers = [
   'pg_publication_rel.h',
   'pg_subscription.h',
   'pg_subscription_rel.h',
+  'pg_variable.h',
 ]
 
 bki_data = [
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 2a2a2e6489e..e6873d369af 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -96,6 +96,8 @@ extern Oid	TypenameGetTypid(const char *typname);
 extern Oid	TypenameGetTypidExtended(const char *typname, bool temp_ok);
 extern bool TypeIsVisible(Oid typid);
 
+extern bool VariableIsVisible(Oid varid);
+
 extern FuncCandidateList FuncnameGetCandidates(List *names,
 											   int nargs, List *argnames,
 											   bool expand_variadic,
@@ -164,6 +166,11 @@ extern void SetTempNamespaceState(Oid tempNamespaceId,
 								  Oid tempToastNamespaceId);
 extern void ResetTempTableNamespace(void);
 
+extern List *NamesFromList(List *names);
+extern Oid	LookupVariable(const char *nspname, const char *varname, bool rowtype_only,
+						   bool missing_ok);
+extern Oid	IdentifyVariable(List *names, char **attrname, bool *not_unique);
+
 extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context);
 extern OverrideSearchPath *CopyOverrideSearchPath(OverrideSearchPath *path);
 extern bool OverrideSearchPathMatchesCurrent(OverrideSearchPath *path);
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index 2a791556362..672c5715620 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -66,6 +66,7 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_default_acl_oid_index, 828, DefaultAclOidIndexId, o
 #define DEFACLOBJ_FUNCTION		'f' /* function */
 #define DEFACLOBJ_TYPE			'T' /* type */
 #define DEFACLOBJ_NAMESPACE		'n' /* namespace */
+#define DEFACLOBJ_VARIABLE		'V' /* variable */
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 20f5aa56eab..3bbae2fae46 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6282,6 +6282,9 @@
   proname => 'pg_collation_is_visible', procost => '10', provolatile => 's',
   prorettype => 'bool', proargtypes => 'oid',
   prosrc => 'pg_collation_is_visible' },
+{ oid => '9221', descr => 'is session variable visible in search path?',
+  proname => 'pg_variable_is_visible', procost => '10', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'oid', prosrc => 'pg_variable_is_visible' },
 
 { oid => '2854', descr => 'get OID of current session\'s temp schema, if any',
   proname => 'pg_my_temp_schema', provolatile => 's', proparallel => 'r',
diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h
new file mode 100644
index 00000000000..cde36b0ceab
--- /dev/null
+++ b/src/include/catalog/pg_variable.h
@@ -0,0 +1,120 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_variable.h
+ *	  definition of session variables system catalog (pg_variables)
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_variable.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_VARIABLE_H
+#define PG_VARIABLE_H
+
+#include "catalog/genbki.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_variable_d.h"
+#include "utils/acl.h"
+
+/* ----------------
+ *		pg_variable definition.  cpp turns this into
+ *		typedef struct FormData_pg_variable
+ * ----------------
+ */
+CATALOG(pg_variable,9222,VariableRelationId)
+{
+	Oid			oid;			/* oid */
+
+	/* OID of entry in pg_type for variable's type */
+	Oid			vartype BKI_LOOKUP(pg_type);
+
+	/* Used for identity check [oid, create_lsn] */
+	XLogRecPtr	create_lsn;
+
+	/* variable name */
+	NameData	varname;
+
+	/* OID of namespace containing variable class */
+	Oid			varnamespace BKI_LOOKUP(pg_namespace);
+
+	/* typmode for variable's type */
+	int32		vartypmod;
+
+	/* variable owner */
+	Oid			varowner BKI_LOOKUP(pg_authid);
+
+	/* variable collation */
+	Oid			varcollation BKI_LOOKUP_OPT(pg_collation);
+
+	/* Don't allow NULL */
+	bool		varisnotnull;
+
+	/* Don't allow changes */
+	bool		varisimmutable;
+
+	/* action on transaction end */
+	char		vareoxaction;
+
+#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_);
+
+	/* access permissions */
+	aclitem		varacl[1] BKI_DEFAULT(_null_);
+
+#endif
+} FormData_pg_variable;
+
+typedef enum VariableEOXAction
+{
+	VARIABLE_EOX_NOOP = 'n',	/* NOOP */
+	VARIABLE_EOX_DROP = 'd',	/* ON COMMIT DROP */
+	VARIABLE_EOX_RESET = 'r',	/* ON TRANSACTION END RESET */
+}			VariableEOXAction;
+
+/* ----------------
+ *		Form_pg_variable corresponds to a pointer to a tuple with
+ *		the format of pg_variable relation.
+ * ----------------
+ */
+typedef FormData_pg_variable *Form_pg_variable;
+
+DECLARE_TOAST(pg_variable, 9223, 9224);
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_variable_oid_index, 9225, VariableOidIndexId, on pg_variable using btree(oid oid_ops));
+#define VariableObjectIndexId 9225
+
+DECLARE_UNIQUE_INDEX(pg_variable_varname_nsp_index, 9226, VariableNameNspIndexId, on pg_variable using btree(varname name_ops, varnamespace oid_ops));
+#define VariableNameNspIndexId  9226
+
+typedef struct Variable
+{
+	Oid			oid;
+	Oid			typid;
+	XLogRecPtr	create_lsn;
+	char	   *name;
+	Oid			namespaceid;
+	int32		typmod;
+	Oid			owner;
+	Oid			collation;
+	bool		is_not_null;
+	bool		is_immutable;
+	VariableEOXAction eoxaction;
+	bool		has_defexpr;
+	Node	   *defexpr;
+} Variable;
+
+extern ObjectAddress CreateVariable(ParseState *pstate,
+									CreateSessionVarStmt *stmt);
+extern void DropVariable(Oid varid);
+extern void InitVariable(Variable *var, Oid varid, bool fast_only);
+
+#endif							/* PG_VARIABLE_H */
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
new file mode 100644
index 00000000000..51934e8d1a8
--- /dev/null
+++ b/src/include/commands/session_variable.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * sessionvariable.h
+ *	  prototypes for sessionvariable.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/session_variable.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SESSIONVARIABLE_H
+#define SESSIONVARIABLE_H
+
+#include "catalog/objectaddress.h"
+#include "catalog/pg_variable.h"
+#include "nodes/params.h"
+#include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
+#include "tcop/cmdtag.h"
+#include "utils/queryenvironment.h"
+
+extern void ResetSessionVariables(void);
+extern void SessionVariableCreatePostprocess(Oid varid, char eoxaction);
+extern void SessionVariableDropPostprocess(Oid varid);
+
+extern Datum CopySessionVariable(Oid varid, bool *isNull, Oid *typid);
+extern Datum CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid);
+extern Datum GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid);
+
+extern void SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid);
+extern void SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typid);
+
+extern void AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit);
+extern void AtEOSubXact_SessionVariable_on_xact_actions(bool isCommit, SubTransactionId mySubid,
+														SubTransactionId parentSubid);
+
+#endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7caff62af7f..e85878e13ec 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1909,6 +1909,7 @@ typedef enum ObjectType
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
 	OBJECT_USER_MAPPING,
+	OBJECT_VARIABLE,
 	OBJECT_VIEW
 } ObjectType;
 
@@ -3036,6 +3037,25 @@ typedef struct AlterStatsStmt
 	bool		missing_ok;		/* skip error if statistics object is missing */
 } AlterStatsStmt;
 
+
+/* ----------------------
+ *		{Create|Alter} VARIABLE Statement
+ * ----------------------
+ */
+typedef struct CreateSessionVarStmt
+{
+	NodeTag		type;
+	RangeVar   *variable;		/* the variable to create */
+	TypeName   *typeName;		/* the type of variable */
+	CollateClause *collClause;
+	Node	   *defexpr;		/* default expression */
+	char		eoxaction;		/* on commit action */
+	bool		if_not_exists;	/* do nothing if it already exists */
+	bool		is_not_null;	/* Disallow nulls */
+	bool		is_immutable;	/* Don't allow changes */
+} CreateSessionVarStmt;
+
+
 /* ----------------------
  *		Create Function Statement
  * ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 957ee18d849..0eb85fabc0d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -454,6 +454,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("variables", VARIABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 962ebf65de1..4d65a9e9e8e 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -81,6 +81,7 @@ typedef enum ParseExprKind
 	EXPR_KIND_COPY_WHERE,		/* WHERE condition in COPY FROM */
 	EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */
 	EXPR_KIND_CYCLE_MARK,		/* cycle mark value */
+	EXPR_KIND_VARIABLE_DEFAULT	/* default value for session variable */
 } ParseExprKind;
 
 
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 9e94f44c5f4..6c1be5e9d4b 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -68,6 +68,7 @@ PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false)
 PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_VARIABLE, "ALTER VARIABLE", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false)
 PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false)
 PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false)
@@ -123,6 +124,7 @@ PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_VARIABLE, "CREATE VARIABLE", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false)
 PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false)
 PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false)
@@ -175,6 +177,7 @@ PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_VARIABLE, "DROP VARIABLE", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false)
 PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false)
 PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false)
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 9a4df3a5dac..8070eb14528 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -168,6 +168,7 @@ typedef struct ArrayType Acl;
 #define ACL_ALL_RIGHTS_SCHEMA		(ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE	(ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE			(ACL_USAGE)
+#define ACL_ALL_RIGHTS_VARIABLE		(ACL_SELECT|ACL_UPDATE)
 
 /* operation codes for pg_*_aclmask */
 typedef enum
@@ -267,7 +268,8 @@ extern AclMode pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
 										 AclMode mask, AclMaskHow how);
 extern AclMode pg_type_aclmask(Oid type_oid, Oid roleid,
 							   AclMode mask, AclMaskHow how);
-
+extern AclMode pg_variable_aclmask(Oid var_oid, Oid roleid,
+								   AclMode mask, AclMaskHow how);
 extern AclResult pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum,
 									   Oid roleid, AclMode mode);
 extern AclResult pg_attribute_aclcheck_ext(Oid table_oid, AttrNumber attnum,
@@ -292,6 +294,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 var_oid, Oid roleid, AclMode mode);
 
 extern void aclcheck_error(AclResult aclerr, ObjectType objtype,
 						   const char *objectname);
@@ -328,6 +331,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 50f02883052..10d97ffbf34 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -132,6 +132,7 @@ extern char get_func_prokind(Oid funcid);
 extern bool get_func_leakproof(Oid funcid);
 extern RegProcedure get_func_support(Oid funcid);
 extern Oid	get_relname_relid(const char *relname, Oid relnamespace);
+extern Oid	get_varname_varid(const char *varname, Oid varnamespace);
 extern char *get_rel_name(Oid relid);
 extern Oid	get_rel_namespace(Oid relid);
 extern Oid	get_rel_type_id(Oid relid);
@@ -203,6 +204,14 @@ extern char *get_publication_name(Oid pubid, bool missing_ok);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
 extern char *get_subscription_name(Oid subid, bool missing_ok);
 
+extern char *get_session_variable_name(Oid varid);
+extern Oid	get_session_variable_namespace(Oid varid);
+extern Oid	get_session_variable_type(Oid varid);
+extern void get_session_variable_type_typmod_collid(Oid varid,
+													Oid *typid,
+													int32 *typmod,
+													Oid *collid);
+
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
 #define type_is_array_domain(typid)  (get_base_element_type(typid) != InvalidOid)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 4463ea66bea..3d18dc88ac4 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -113,9 +113,11 @@ enum SysCacheIdentifier
 	TYPENAMENSP,
 	TYPEOID,
 	USERMAPPINGOID,
-	USERMAPPINGUSERSERVER
+	USERMAPPINGUSERSERVER,
+	VARIABLENAMENSP,
+	VARIABLEOID
 
-#define SysCacheSize (USERMAPPINGUSERSERVER + 1)
+#define SysCacheSize (VARIABLEOID + 1)
 };
 
 extern void InitCatalogCache(void);
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be3..d9953321402 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -266,3 +266,7 @@ NOTICE:  checking pg_subscription {subdbid} => pg_database {oid}
 NOTICE:  checking pg_subscription {subowner} => pg_authid {oid}
 NOTICE:  checking pg_subscription_rel {srsubid} => pg_subscription {oid}
 NOTICE:  checking pg_subscription_rel {srrelid} => pg_class {oid}
+NOTICE:  checking pg_variable {vartype} => pg_type {oid}
+NOTICE:  checking pg_variable {varnamespace} => pg_namespace {oid}
+NOTICE:  checking pg_variable {varowner} => pg_authid {oid}
+NOTICE:  checking pg_variable {varcollation} => pg_collation {oid}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9683b0a88e5..6778bbb80d1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -500,6 +500,7 @@ CreateRoleStmt
 CreateSchemaStmt
 CreateSchemaStmtContext
 CreateSeqStmt
+CreateSessionVarStmt
 CreateStatsStmt
 CreateStmt
 CreateStmtContext
@@ -883,6 +884,7 @@ Form_pg_ts_parser
 Form_pg_ts_template
 Form_pg_type
 Form_pg_user_mapping
+FormData_pg_variable
 FormatNode
 FreeBlockNumberArray
 FreeListData
@@ -2636,6 +2638,8 @@ SupportRequestRows
 SupportRequestSelectivity
 SupportRequestSimplify
 SupportRequestWFuncMonotonic
+SVariableXActAction
+SVariableXActActionItem
 Syn
 SyncOps
 SyncRepConfigData
-- 
2.38.1

