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

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

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

diff --git a/src/backend/commands/sessionvariable.c b/src/backend/commands/sessionvariable.c
index 204582ecbc..ffa6052146 100644
--- a/src/backend/commands/sessionvariable.c
+++ b/src/backend/commands/sessionvariable.c
@@ -14,12 +14,15 @@
  */
 #include "postgres.h"
 #include "miscadmin.h"
+#include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_extension.h"
+#include "catalog/pg_type.h"
 #include "catalog/pg_variable.h"
 #include "commands/session_variable.h"
 #include "executor/executor.h"
@@ -31,14 +34,18 @@
 #include "parser/parse_expr.h"
 #include "parser/parse_type.h"
 #include "rewrite/rewriteHandler.h"
+#include "storage/itemptr.h"
 #include "storage/lmgr.h"
+#include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 /*
  * Values of session variables are stored in local memory, in
@@ -66,7 +73,7 @@
  *   operation or by owner's extension update. We store fingerprint
  *   of any type used by session variables, and before any
  *   read we check if this fingerprint is still valid (see
- *   SVariableType related operations). Built-in non composite
+ *   SVariableType related operations). Built-in non row
  *   types are trusty every time.
  *
  *   For other cases we can check xmin, and if xmin is different,
@@ -76,6 +83,18 @@
  *   version). For domains we should to force cast to domain type
  *   to reply all constraints before returning the value.
  *
+ *   We have not too much possibilities for check of identity
+ *   of row types (against oid's overflow) because for
+ *   custom types, the value of relid is constant offset
+ *   from type's oid. But it can work well for row types
+ *   based on tables. On second hand we can every time to convert
+ *   stored value to expected (current) tupledesc (if it is
+ *   possible. Unfortunatelly we cannot to use already prepared
+ *   conversions (based on mapping of undeleted fields or based
+ *   on names). In this case we expect so target tupledesc is
+ *   just modified original tupledesc. So mapping should be done
+ *   really just on position.
+ *
  *  b) the variable can be dropped - we don't purge memory
  *   when the variable is dropped immediately, so theoretically
  *   we can return dropped value. The protection against this
@@ -151,6 +170,58 @@
  * support session variables (the reading of session variables is
  * fully transparent for PL/pgSQL).
  */
+struct SVariableTypeDataField;
+
+typedef struct SVaribleTypeData
+{
+	Oid			typid;
+	Oid			relid;			/* OID of relation containing this attribute *
+								 * used for check of oid's overflow */
+
+	int64		gennum;			/* generation number helps with change detection */
+	LocalTransactionId verified_lxid; /* lxid of transaction when the typ fingerprint
+									   * was created or verified */
+	int			natts;
+	struct		SVariableTypeDataField *attrs;
+
+	int16		typlen;
+	bool		typbyval;
+	bool		is_domain;
+	bool		is_rowtype;
+	char	   *typname;
+
+	struct		SVaribleTypeData *base_type;
+	int64		base_type_gennum;
+	void	   *domain_check_extra;		/* domain check routine */
+	LocalTransactionId domain_check_extra_lxid; /* lxid when value of domain's type *
+												 * was checked */
+
+	TransactionId	typ_xmin;	/* xmin of related tuple of pg_type */
+	ItemPointerData typ_tid;
+
+	Oid			extid;			/* OID of owner extension if exists */
+	TransactionId ext_xmin;		/* xmin of related tuple of pg_extension */
+	ItemPointerData ext_tid;
+	char	   *extname;
+	char	   *extversion;
+
+} SVariableTypeData;
+
+typedef SVariableTypeData * SVariableType;
+
+/*
+ * Following structure is used as type's finger print for
+ * fields of row type. It helps with fast type identity
+ * check for row value.
+ */
+typedef struct SVariableTypeDataField
+{
+	SVariableType svartype;
+	int64		gennum;
+	int32		typmod;
+	bool		isdropped;
+} SVariableTypeDataField;
+
 typedef enum SVariableXActAction
 {
 	SVAR_ON_COMMIT_DROP,		/* used for ON COMMIT DROP */
@@ -211,18 +282,38 @@ static LocalTransactionId synced_lxid = InvalidLocalTransactionId;
 typedef struct SVariableData
 {
 	Oid			varid;			/* pg_variable OID of this sequence (hash key) */
+	Oid			relid;			/* used for fast row type's identity check */
 
-	char	   *name;		/* for debug purposes */
-	char	   *nsname;		/* for debug purposes */
+	/*
+	 * Next two items can be used just for debug purposes.
+	 * The stored nsnane and name can be obsolete, when schema
+	 * or variable name are altered. This behaviour can be
+	 * acceptable for debug purposes (and it is necessary, because
+	 * sometimes we cannot to access to catalogue.
+	 */
+	char	   *name;			/* session variable name (at time of initialization) */
+	char	   *nsname;			/* session variable schema name */
 
 	SVariableType svartype;
 	int64		gennum;
 
+	/*
+	 * We need to store details about fields, because
+	 * we can convert old row value to new format,
+	 * when the difference is just dropped or new
+	 * fields. But for converted fields we need to
+	 * check type too.
+	 */
+	int			natts;
+	struct		SVariableTypeDataField *attrs;
+
 	bool		isnull;
 	bool		freeval;
 	Datum		value;
+	TupleDesc	tupdesc;		/* for row type we save actual tupdesc *
+								 * at save time, for conversion to uptime *
+								 * tupdesc in read time */
 
-	bool		is_rowtype;		/* true when variable is composite */
 	bool		is_not_null;	/* don't allow null values */
 	bool		is_immutable;	/* true when variable is immutable */
 	bool		has_defexpr;	/* true when variable has a default value */
@@ -230,19 +321,27 @@ typedef struct SVariableData
 	bool		is_valid;		/* true when variable was successfully
 								 * initialized */
 
-	uint32		hashvalue;
+	uint32		hashvalue;		/* used for pairing sinval message */
 
 	bool		eox_reset;		/* true, when lifecycle is limitted by transaction */
 }			SVariableData;
 
 typedef SVariableData * SVariable;
 
-static HTAB *sessionvars = NULL;	/* hash table for session variables */
+static HTAB *sessionvars = NULL;		/* hash table for session variables */
+static HTAB *sessionvars_types = NULL;	/* hash table for type fingerprints of session
+										 * variables */
+
 static MemoryContext SVariableMemoryContext = NULL;
 
 static bool first_time = true;
 
 
+static SVariableType get_svariabletype(Oid typid);
+static int64 get_svariable_valid_type_gennum(SVariableType svt);
+
+static void init_session_variable(SVariable svar, Variable *var);
+
 static void register_session_variable_xact_action(Oid varid, SVariableXActAction action);
 static void unregister_session_variable_xact_action(Oid varid, SVariableXActAction action);
 
@@ -264,12 +363,572 @@ SvariableXActActionName(SVariableXActAction action)
 	}
 }
 
+/*
+ * In this case we know, so fast comparing fails.
+ */
+static bool
+svariabletypes_equals(SVariableType svt1, SVariableType svt2)
+{
+	Assert(svt1->typid == svt2->typid);
+	Assert(svt1 != svt2);
+
+	/*
+	 * for trustworthy check we need to know base type, extension,
+	 * or row fields. Just typlen, and typbyval is not enough
+	 * trustworthy, because (-1, false) is very common. In this case
+	 * we can check only the name (for other cases we don't check the
+	 * name, because the name can be altered).
+	 */
+	if (!(OidIsValid(svt1->extid) ||
+		  svt1->base_type ||
+		  svt1->is_rowtype))
+	{
+		if (strcmp(svt1->typname, svt2->typname) != 0)
+			return false;
+	}
+
+	if (svt1->typlen != svt2->typlen)
+		return false;
+
+	if (svt1->typbyval != svt2->typbyval)
+		return false;
+
+	if (svt1->is_domain != svt2->is_domain)
+		return false;
+
+	if (svt1->is_rowtype != svt2->is_rowtype)
+		return false;
+
+	if (svt1->is_domain)
+	{
+		if (svt1->base_type->typid != svt2->base_type->typid)
+			return false;
+
+		if (svt1->base_type_gennum != svt2->base_type_gennum)
+			return false;
+	}
+
+	if (OidIsValid(svt1->extid))
+	{
+		if (svt1->extid != svt2->extid)
+			return false;
+
+		if (strcmp(svt1->extname, svt2->extname) != 0)
+			return false;
+
+		if (strcmp(svt1->extversion, svt2->extversion) != 0)
+			return false;
+	}
+
+	if (svt1->is_rowtype)
+	{
+		int		i;
+
+		if (svt1->relid != svt2->relid)
+			return false;
+
+		if (svt1->natts != svt2->natts)
+			return false;
+
+		for (i = 0; i < svt1->natts; i++)
+		{
+			if (svt1->attrs[i].isdropped != svt2->attrs[i].isdropped)
+				return false;
+
+			if (!svt1->attrs[i].isdropped)
+			{
+				if (svt1->attrs[i].svartype->typid != svt2->attrs[i].svartype->typid)
+					return false;
+
+				if (svt1->attrs[i].typmod != svt2->attrs[i].typmod)
+					return false;
+
+				if (svt1->attrs[i].gennum != svt2->attrs[i].gennum)
+					return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+static void
+svariabletype_free(SVariableType svt)
+{
+	pfree(svt->typname);
+
+	if (OidIsValid(svt->extid))
+	{
+		pfree(svt->extname);
+		pfree(svt->extversion);
+	}
+
+	if (svt->natts > 0)
+		pfree(svt->attrs);
+}
+
+/*
+ * Update fields used for fast check
+ */
+static void
+svariabletype_refresh(SVariableType svt1, SVariableType svt2)
+{
+	svt1->typ_xmin = svt2->typ_xmin;
+	svt1->typ_tid = svt2->typ_tid;
+
+	svt1->ext_xmin = svt2->ext_xmin;
+	svt1->ext_tid = svt2->ext_tid;
+}
+
+/*
+ * Update all fields and increase generation number
+ */
+static void
+svariabletype_update(SVariableType svt1, SVariableType svt2)
+{
+	int		gennum = svt1->gennum;
+
+	svariabletype_free(svt1);
+
+	memcpy(svt1, svt2, sizeof(SVariableTypeData));
+
+	svt1->gennum = gennum + 1;
+}
+
+/*
+ * When type owner's extension is not changed, then we can belive
+ * so type is still valid. For this check we need to hold few
+ * information about extension in memory. We can do fast check
+ * based on xmin, tid or slow check on oid, name and version.
+ */
+static void
+svariabletype_assign_extension(SVariableType svt, Oid extid)
+{
+	Relation	pg_extension;
+	ScanKeyData entry[1];
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	Form_pg_extension ext;
+	bool		isnull;
+	Datum		datum;
+	MemoryContext oldcxt;
+
+	Assert(OidIsValid(extid));
+
+	/* There's no syscache for pg_extension, so do it the hard way */
+	pg_extension = table_open(ExtensionRelationId, AccessShareLock);
+
+	ScanKeyInit(&entry[0],
+				Anum_pg_extension_oid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(extid));
+
+	scan = systable_beginscan(pg_extension,
+							  ExtensionOidIndexId, true,
+							  NULL, 1, entry);
+
+	tuple = systable_getnext(scan);
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("extension with OID %u does not exist", svt->extid)));
+
+	ext = (Form_pg_extension) GETSTRUCT(tuple);
+	svt->extid = extid;
+	svt->ext_xmin = HeapTupleHeaderGetRawXmin(tuple->t_data);
+	svt->ext_tid = tuple->t_self;
+
+	datum = heap_getattr(tuple, Anum_pg_extension_extversion,
+						 RelationGetDescr(pg_extension), &isnull);
+
+	if (isnull)
+		elog(ERROR, "extversion is null");
+
+	oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+
+	svt->extversion = TextDatumGetCString(datum);
+	svt->extname = pstrdup(NameStr(ext->extname));
+
+	MemoryContextSwitchTo(oldcxt);
+
+	systable_endscan(scan);
+	table_close(pg_extension, AccessShareLock);
+}
+
+static bool
+svariabletype_verify_ext_fast(SVariableType svt)
+{
+	Relation	pg_extension;
+	ScanKeyData entry[1];
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	bool		result = true;
+
+	Assert(OidIsValid(svt->extid));
+
+	/* There's no syscache for pg_extension, so do it the hard way */
+	pg_extension = table_open(ExtensionRelationId, AccessShareLock);
+
+	ScanKeyInit(&entry[0],
+				Anum_pg_extension_oid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(svt->extid));
+
+	scan = systable_beginscan(pg_extension,
+							  ExtensionOidIndexId, true,
+							  NULL, 1, entry);
+
+	tuple = systable_getnext(scan);
+	if (HeapTupleIsValid(tuple))
+	{
+		if (svt->ext_xmin != HeapTupleHeaderGetRawXmin(tuple->t_data) ||
+			!ItemPointerEquals(&svt->ext_tid, &tuple->t_self))
+			result = false;
+	}
+	else
+		result = false;
+
+	systable_endscan(scan);
+	table_close(pg_extension, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * We hold data like typlen, typbyval for usual purposes. More
+ * we hold xmin, tid, extid like type's fingerprint. This is
+ * used later for type verification stored value of session variable.
+ */
+static void
+svariabletype_init(SVariableType svt, HeapTuple tuple, Oid typid)
+{
+	Form_pg_type typ;
+	MemoryContext oldcxt;
+
+	memset(svt, 0, sizeof(SVariableTypeData));
+
+	svt->typid = typid;
+	svt->gennum = 1;
+
+	typ = (Form_pg_type) GETSTRUCT(tuple);
+
+	/* save basic attributtes */
+	svt->relid = typ->typrelid;
+
+	svt->typlen = typ->typlen;
+	svt->typbyval = typ->typbyval;
+
+	/* save info about type */
+	svt->typ_xmin = HeapTupleHeaderGetRawXmin(tuple->t_data);
+	svt->typ_tid = tuple->t_self;
+
+	oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
+
+	svt->typname = pstrdup(NameStr(typ->typname));
+
+	MemoryContextSwitchTo(oldcxt);
+
+	if (typ->typtype == TYPTYPE_DOMAIN)
+	{
+		Oid			basetypid;
+
+		svt->is_domain = true;
+		basetypid = getBaseType(typid);
+
+		svt->base_type = get_svariabletype(basetypid);
+		svt->base_type_gennum = get_svariable_valid_type_gennum(svt->base_type);
+		svt->is_rowtype = svt->base_type->is_rowtype;
+	}
+	else
+	{
+		svt->is_domain = false;
+		svt->is_rowtype = typ->typtype == TYPTYPE_COMPOSITE;
+	}
+
+	/*
+	 * Store fingerprints of fields of row types.
+	 * Probably buildin types should not be changed, but
+	 * just be safe, and store fingerprints for all composite
+	 * types (including buildin composite types).
+	*/
+	if (svt->is_rowtype)
+	{
+		Oid			rowtypid;
+		TupleDesc	tupdesc;
+		int			i;
+
+		if (svt->is_domain)
+			rowtypid = svt->base_type->typid;
+		else
+			rowtypid = svt->typid;
+
+		tupdesc = lookup_rowtype_tupdesc(rowtypid, -1);
+
+		svt->attrs = MemoryContextAlloc(SVariableMemoryContext,
+											 tupdesc->natts * sizeof(SVariableTypeDataField));
+
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			SVariableType field_svartype;
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+			if (!attr->attisdropped)
+			{
+				field_svartype = get_svariabletype(attr->atttypid);
+
+				svt->attrs[i].svartype = field_svartype;
+				svt->attrs[i].gennum = get_svariable_valid_type_gennum(field_svartype);
+				svt->attrs[i].typmod = attr->atttypmod;
+				svt->attrs[i].isdropped = false;
+			}
+			else
+				svt->attrs[i].isdropped = true;
+		}
+
+		svt->natts = tupdesc->natts;
+
+		ReleaseTupleDesc(tupdesc);
+	}
+
+	svt->domain_check_extra_lxid = InvalidLocalTransactionId;
+
+	svt->verified_lxid = MyProc->lxid;
+
+	/* try to find related extension */
+	svt->extid = getExtensionOfObject(TypeRelationId, typid);
+
+	if (OidIsValid(svt->extid))
+		svariabletype_assign_extension(svt, svt->extid);
+}
+
+/*
+ * the field check can ignore dropped fields
+ */
+static bool
+svartype_verify_composite_fast(SVariableType svt)
+{
+	bool	result = true;
+	TupleDesc	tupdesc;
+	int		i;
+
+	Assert(svt);
+	Assert(svt->is_rowtype);
+
+	tupdesc = lookup_rowtype_tupdesc_noerror(svt->typid, -1, true);
+	if (!tupdesc)
+		return false;
+
+	if (svt->natts == tupdesc->natts)
+	{
+		for (i = 0; i < svt->natts; i++)
+		{
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+			if (svt->relid != attr->attrelid)
+			{
+				result = false;
+				break;
+			}
+
+			if (!svt->attrs[i].isdropped)
+			{
+				SVariableType field_svt = svt->attrs[i].svartype;
+				int64		field_gennum = svt->attrs[i].gennum;
+
+				if (attr->attisdropped)
+				{
+					result = false;
+					break;
+				}
+
+				if (field_svt->typid != attr->atttypid)
+				{
+					result = false;
+					break;
+				}
+
+				if (field_gennum != field_svt->gennum)
+				{
+					result = false;
+					break;
+				}
+
+				if (svt->attrs[i].typmod != attr->atttypmod)
+				{
+					result = false;
+					break;
+				}
+
+				if (field_gennum != get_svariable_valid_type_gennum(field_svt))
+				{
+					result = false;
+					break;
+				}
+			}
+			else
+			{
+				if (!attr->attisdropped)
+				{
+					result = false;
+					break;
+				}
+			}
+		}
+	}
+	else
+		result = false;
+
+	ReleaseTupleDesc(tupdesc);
+
+	return result;
+}
+
+/*
+ * Check type fingerprint and if it is not valid, then does an update, and
+ * increase generation number. We can trust just to buildin types. Composite
+ * types are checked recusively until we iterarate to buildin types. External
+ * types are valid if related record is without change, or version string is
+ * equal. Although the record can be untouched, we need to check extension record,
+ * because format can be changed by extension updade.
+ */
+static int64
+get_svariable_valid_type_gennum(SVariableType svt)
+{
+	HeapTuple	tuple;
+	bool		fast_check = true;
+
+	Assert(svt);
+
+	/* Buildin scalar objects are trustworthy */
+	if (svt->typid < FirstNormalObjectId && !svt->is_rowtype)
+		return svt->gennum;
+
+	/* don't repeat check in one transaction */
+	if (svt->verified_lxid == MyProc->lxid)
+		return svt->gennum;
+
+	/*
+	 * First we check the type record. If it was not changed, then
+	 * we can trust to stored fingerprint. In this case we can do
+	 * fast check of extension (because the format of type can be
+	 * changed when extension was updated). When fast check of type
+	 * fails, we have to run slow check.
+	 */
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(svt->typid));
+	if (!(svt->typ_xmin == HeapTupleHeaderGetRawXmin(tuple->t_data) &&
+		  ItemPointerEquals(&svt->typ_tid, &tuple->t_self)))
+		fast_check = false;
+
+	if (fast_check && OidIsValid(svt->extid))
+	{
+		if (!svariabletype_verify_ext_fast(svt))
+			fast_check = false;
+	}
+
+	/* When type or extension records are up to date, check base type */
+	if (fast_check && svt->is_domain)
+	{
+		if (svt->base_type_gennum != svt->base_type->gennum)
+		{
+			fast_check = false;
+		}
+		else if (get_svariable_valid_type_gennum(svt->base_type) !=
+			  svt->base_type_gennum)
+		{
+			fast_check = false;
+		}
+	}
+
+	if (fast_check && svt->is_rowtype)
+	{
+		if (!svartype_verify_composite_fast(svt))
+			fast_check = false;
+	}
+
+	if (!fast_check)
+	{
+		SVariableTypeData nsvtd;
+
+		/*
+		 * Slow check. We create new SVariableType value. Compare it with
+		 * previous value, and if it is different, then we replace old by
+		 * new and we increase gennum
+		 */
+		svariabletype_init(&nsvtd, tuple, svt->typid);
+
+		if (svariabletypes_equals(svt, &nsvtd))
+		{
+			svariabletype_refresh(svt, &nsvtd);
+			svariabletype_free(&nsvtd);
+		}
+		else
+			svariabletype_update(svt, &nsvtd);
+	}
+
+	ReleaseSysCache(tuple);
+
+	svt->verified_lxid = MyProc->lxid;
+
+	return svt->gennum;
+}
+
+static SVariableType
+get_svariabletype(Oid typid)
+{
+	SVariableType svt;
+	bool		found;
+
+	Assert(sessionvars_types);
+
+	svt = (SVariableType) hash_search(sessionvars_types, &typid,
+										   HASH_ENTER, &found);
+
+	if (!found)
+	{
+		HeapTuple	tuple;
+
+		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for type %u", typid);
+
+		svariabletype_init(svt, tuple, typid);
+
+		ReleaseSysCache(tuple);
+	}
+
+	return svt;
+}
+
+/*
+ * Returns true, when type of stored value is still valid
+ */
+static bool
+session_variable_use_valid_type(SVariable svar)
+{
+	Assert(svar);
+	Assert(svar->svartype);
+
+	/*
+	 * when referenced type is not valid of obsolete, the
+	 * value is stored in maybe not up the data format.
+	 */
+	if (svar->gennum != svar->svartype->gennum)
+		return false;
+
+	/* enforce type verification, get fresh generation number */
+	if (svar->gennum != get_svariable_valid_type_gennum(svar->svartype))
+		return false;
+
+	return true;
+}
+
 /*
  * Releases stored data from session variable, but preserve the hash entry
- * in sessionvars.
+ * in sessionvars. When we replace row value by new value with same type
+ * fingerprint, we can keep field desc data.
  */
 static void
-free_session_variable_value(SVariable svar)
+free_session_variable_value(SVariable svar, bool keep_desc_fields)
 {
 	if (svar->freeval)
 		pfree(DatumGetPointer(svar->value));
@@ -279,6 +938,21 @@ free_session_variable_value(SVariable svar)
 	svar->isnull = true;
 	svar->freeval = false;
 
+	if (!keep_desc_fields)
+	{
+		if (svar->natts > 0)
+		{
+			pfree(svar->attrs);
+			svar->natts = 0;
+		}
+
+		if (svar->tupdesc)
+		{
+			FreeTupleDesc(svar->tupdesc);
+			svar->tupdesc = NULL;
+		}
+	}
+
 	/*
 	 * We can mark this session variable as valid when
 	 * it has not default expression, and when null is
@@ -295,7 +969,7 @@ free_session_variable_value(SVariable svar)
 static void
 remove_session_variable(SVariable svar)
 {
-	free_session_variable_value(svar);
+	free_session_variable_value(svar, false);
 
 	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) is removing from memory",
 				 svar->nsname, svar->name,
@@ -508,9 +1182,10 @@ sync_sessionvars_all()
  * Create the hash table for storing session variables
  */
 static void
-create_sessionvars_hashtable(void)
+create_sessionvars_hashtables(void)
 {
-	HASHCTL		ctl;
+	HASHCTL		vars_ctl;
+	HASHCTL		types_ctl;
 
 	/* set callbacks */
 	if (first_time)
@@ -530,14 +1205,24 @@ create_sessionvars_hashtable(void)
 
 	Assert(SVariableMemoryContext);
 
-	memset(&ctl, 0, sizeof(ctl));
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(SVariableData);
-	ctl.hcxt = SVariableMemoryContext;
+	memset(&vars_ctl, 0, sizeof(vars_ctl));
+	vars_ctl.keysize = sizeof(Oid);
+	vars_ctl.entrysize = sizeof(SVariableData);
+	vars_ctl.hcxt = SVariableMemoryContext;
 
 	Assert(sessionvars == NULL);
 
-	sessionvars = hash_create("Session variables", 64, &ctl,
+	sessionvars = hash_create("Session variables", 64, &vars_ctl,
+								   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+
+	memset(&types_ctl, 0, sizeof(types_ctl));
+	types_ctl.keysize = sizeof(Oid);
+	types_ctl.entrysize = sizeof(SVariableTypeData);
+	types_ctl.hcxt = SVariableMemoryContext;
+
+	Assert(sessionvars_types == NULL);
+
+	sessionvars_types = hash_create("Session variable types", 64, &types_ctl,
 								   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
 }
 
@@ -548,31 +1233,56 @@ create_sessionvars_hashtable(void)
  * init_mode is true, when the value of session variable is being initialized
  * by default expression or by null. Only in this moment we can allow to
  * modify immutable variables with default expression.
+ *
+ * This routine have to do successfull change or leave memory without
+ * change.
  */
 static void
 set_session_variable(SVariable svar, Datum value,
 					 bool isnull, Oid typid,
 					 bool init_mode)
 {
-	MemoryContext oldcxt;
+	TupleDesc	tupdesc = NULL;
+	TupleDesc	newtupdesc = NULL;
 	Datum		newval = value;
+	SVariableData _svardata, *_svar;
+	bool		fresh_svar = false;
+
+	Assert(svar && svar->svartype);
+
+	/* We have to be sure so cached data are still valid. */
+	if (svar->svartype->typid == typid)
+	{
+		if (!session_variable_use_valid_type(svar))
+			fresh_svar = true;
+	}
+	else
+		fresh_svar = true;
+
+	if (fresh_svar)
+	{
+		Variable	var;
+
+		initVariable(&var, svar->varid, true);
+		init_session_variable(&_svardata, &var);
+		_svar = &_svardata;
+	}
+	else
+	{
+		/* We can trust the cached entry */
+		Assert(svar->svartype->typid == typid);
+		Assert(svar->gennum == svar->svartype->gennum);
+
+		_svar = svar;
+	}
 
 	/* Don't allow assignment of null to NOT NULL variable */
-	if (isnull && svar->is_not_null)
+	if (isnull && _svar->is_not_null)
 		ereport(ERROR,
 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
 				 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
-						get_namespace_name(get_session_variable_namespace(svar->varid)),
-						get_session_variable_name(svar->varid))));
-
-	if (svar->typid != typid)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATATYPE_MISMATCH),
-				 errmsg("type \"%s\" of assigned value is different than type \"%s\" of session variable \"%s.%s\"",
-						format_type_be(typid),
-						format_type_be(svar->typid),
-						get_namespace_name(get_session_variable_namespace(svar->varid)),
-						get_session_variable_name(svar->varid))));
+						get_namespace_name(get_session_variable_namespace(_svar->varid)),
+						get_session_variable_name(_svar->varid))));
 
 	/*
 	 * Don't allow updating of immutable session variable that has assigned
@@ -581,25 +1291,57 @@ set_session_variable(SVariable svar, Datum value,
 	 * is being initialized.
 	 */
 	if (!init_mode &&
-		(svar->is_immutable && (svar->is_valid || svar->has_defexpr)))
+		(_svar->is_immutable &&
+		 (_svar->is_valid || _svar->has_defexpr)))
 		ereport(ERROR,
 				(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
 				 errmsg("session variable \"%s.%s\" is declared IMMUTABLE",
-						get_namespace_name(get_session_variable_namespace(svar->varid)),
-						get_session_variable_name(svar->varid))));
+						get_namespace_name(get_session_variable_namespace(_svar->varid)),
+						get_session_variable_name(_svar->varid))));
+
+	/* We don't need tupdesc for NULL value */
+	if (!isnull &&
+		 _svar->svartype->is_rowtype && !_svar->tupdesc)
+		tupdesc = lookup_rowtype_tupdesc(_svar->svartype->typid, -1);
 
 	/* copy value to session persistent context */
-	oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
 	if (!isnull)
-		newval = datumCopy(value, svar->typbyval, svar->typlen);
-	MemoryContextSwitchTo(oldcxt);
+	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(SVariableMemoryContext);
 
-	free_session_variable_value(svar);
+		newval = datumCopy(value,
+						   _svar->svartype->typbyval,
+						   _svar->svartype->typlen);
 
-	svar->value = newval;
-	svar->isnull = isnull;
-	svar->freeval = newval != value;
-	svar->is_valid = true;
+		if (tupdesc)
+		{
+			newtupdesc = palloc(TupleDescSize(tupdesc));
+			TupleDescCopy(newtupdesc, tupdesc);
+		}
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/* When we have new svar, we want full clean of old svar */
+	free_session_variable_value(svar, !fresh_svar);
+
+	_svar->value = newval;
+	if (newtupdesc)
+		_svar->tupdesc = newtupdesc;
+
+	_svar->isnull = isnull;
+	_svar->freeval = newval != value;
+	_svar->is_valid = true;
+
+	if (fresh_svar)
+		memcpy(svar, _svar, sizeof(SVariableData));
+
+	if (tupdesc)
+		ReleaseTupleDesc(tupdesc);
+
+	elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new value",
+				 svar->nsname, svar->name,
+				 svar->varid);
 }
 
 /*
@@ -621,12 +1363,23 @@ init_session_variable(SVariable svar, Variable *var)
 
 	svar->svartype = get_svariabletype(var->typid);
 	svar->gennum = get_svariable_valid_type_gennum(svar->svartype);
+	svar->relid = svar->svartype->relid;
+
+	svar->natts = svar->svartype->natts;
+	if (svar->natts > 0)
+	{
+		size_t		bytes = svar->natts * sizeof(SVariableTypeDataField);
 
+		svar->attrs = palloc(bytes);
+		memcpy(svar->attrs, svar->svartype->attrs, bytes);
+	}
+	else
+		svar->attrs = NULL;
+
+	svar->value = (Datum) 0;
 	svar->isnull = true;
 	svar->freeval = false;
-	svar->value = (Datum) 0;
 
-	svar->is_rowtype = type_is_rowtype(var->typid);
 	svar->is_not_null = var->is_not_null;
 	svar->is_immutable = var->is_immutable;
 	svar->has_defexpr = var->has_defexpr;
@@ -650,9 +1403,128 @@ init_session_variable(SVariable svar, Variable *var)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
+	svar->tupdesc = NULL;
+
 	MemoryContextSwitchTo(oldcxt);
 }
 
+/*
+ * Try to convert the composite value to the current expected format. This
+ * routine is called when we know so expected composite format is not identical
+ * with composite format of saved value. If there are difference just in
+ * number (and positions) of dropped fields, we can try to build new
+ * composite value, where dropped fields are replaced by NULLs. Unfortunately
+ * we cannot to use TupleConversionMap infra, because this conversion should
+ * ba based on position (not on position of not dropped fields).
+ */
+static Datum
+get_rowtype_value_in_expected_format(SVariable svar)
+{
+	SVariableType target_svt = svar->svartype;
+	Datum		value = (Datum) 0;
+	int		i;
+
+	Assert(svar->natts > 0 && target_svt->is_rowtype);
+	Assert(svar->relid == target_svt->relid);
+
+	/*
+	 * Before conversion we need to refresh assigned target type.
+	 * We don't update gennum in svariable, so the conversion will
+	 * be forced every time until value update.
+	 */
+	get_svariable_valid_type_gennum(target_svt);
+
+	/* check if we can do this transformation */
+	for (i = 0; i < target_svt->natts; i++)
+	{
+		if (i >= svar->natts)
+			continue;
+
+		if (target_svt->attrs[i].isdropped || svar->attrs[i].isdropped)
+			continue;
+
+		if (svar->attrs[i].svartype->typid != target_svt->attrs[i].svartype->typid ||
+			svar->attrs[i].typmod != target_svt->attrs[i].typmod)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("record type of stored value of \"%s.%s\" does not match expected record type",
+								get_namespace_name(get_session_variable_namespace(svar->varid)),
+								get_session_variable_name(svar->varid)),
+						 errdetail("Expected format of %d field is %s.",
+								   i,
+								   format_type_with_typemod(target_svt->attrs[i].svartype->typid,
+															target_svt->attrs[i].typmod))));
+
+		if (svar->attrs[i].gennum != target_svt->attrs[i].svartype->gennum)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("format of stored value of %d field of \"%s.%s\" session variable can be outdated",
+							i,
+							get_namespace_name(get_session_variable_namespace(svar->varid)),
+							get_session_variable_name(svar->varid))));
+	}
+
+	/* We finish checks. When stored value is null, we can leave */
+	if (!svar->isnull)
+	{
+		Datum	   *invalues;
+		bool	   *inisnull;
+		Datum	   *outvalues;
+		bool	   *outisnull;
+		TupleDesc  outtupdesc;
+		HeapTuple	tuple;
+		HeapTupleHeader td;
+		HeapTupleData tmptup;
+
+		td = DatumGetHeapTupleHeader(svar->value);
+		tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+		tmptup.t_data = td;
+
+		/* returns fresh tupdesc */
+		outtupdesc = lookup_rowtype_tupdesc(target_svt->typid, -1);
+
+		Assert(outtupdesc->natts == target_svt->natts);
+		Assert(svar->tupdesc->natts == svar->natts);
+
+		invalues = palloc(svar->natts * sizeof(Datum));
+		inisnull = palloc(svar->natts * sizeof(bool));
+
+		outvalues = palloc(target_svt->natts * sizeof(Datum));
+		outisnull = palloc(target_svt->natts * sizeof(bool));
+
+		heap_deform_tuple(&tmptup, svar->tupdesc, invalues, inisnull);
+
+		for (i = 0; i < target_svt->natts; i++)
+		{
+			if (i > svar->natts)
+				outisnull[i] = true;
+
+			if (svar->attrs[i].isdropped || target_svt->attrs[i].isdropped)
+				outisnull[i] = true;
+
+			outvalues[i] = invalues[i];
+			outisnull[i] = inisnull[i];
+		}
+
+		tuple = heap_form_tuple(outtupdesc, outvalues, outisnull);
+		value = heap_copy_tuple_as_datum(tuple, outtupdesc);
+
+		heap_freetuple(tuple);
+
+		ReleaseTupleDesc(outtupdesc);
+
+		pfree(invalues);
+		pfree(inisnull);
+		pfree(outvalues);
+		pfree(outisnull);
+	}
+
+	elog(DEBUG1, "returned value of record type's session variable \"%s.%s\" was converted",
+				 svar->nsname, svar->name);
+
+	return value;
+}
+
 /*
  * Search the given session variable in the hash table. If it doesn't
  * exist, then insert it (and calculate defexpr if it exists).
@@ -663,7 +1535,7 @@ init_session_variable(SVariable svar, Variable *var)
  * related session variable until the end of the transaction.
  */
 static SVariable
-prepare_variable_for_reading(Oid varid)
+prepare_variable_for_reading(Oid varid, Datum *value, bool *isnull)
 {
 	SVariable svar;
 	Variable	var;
@@ -672,7 +1544,7 @@ prepare_variable_for_reading(Oid varid)
 	var.oid = InvalidOid;
 
 	if (!sessionvars)
-		create_sessionvars_hashtable();
+		create_sessionvars_hashtables();
 
 	/* Protect used session variable against drop until transaction end */
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
@@ -684,54 +1556,120 @@ prepare_variable_for_reading(Oid varid)
 										HASH_ENTER, &found);
 
 	/* Return content if it is available and valid */
-	if (found && svar->is_valid)
-		return svar;
+	if (!found || !svar->is_valid)
+	{
+		/* We need to load defexpr. */
+		initVariable(&var, varid, false);
 
-	/* We need to load defexpr. */
-	initVariable(&var, varid, false);
+		if (!found)
+		{
+			init_session_variable(svar, &var);
 
-	if (!found)
-		init_session_variable(svar, &var);
+			elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has new entry in memory (emitted by READ)",
+						 svar->nsname, svar->name,
+						 varid);
+		}
 
-	/* Raise an error when we cannot initialize variable correctly */
-	if (var.is_not_null && !var.defexpr)
-		ereport(ERROR,
-				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-				 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
-						get_namespace_name(get_session_variable_namespace(varid)),
-						get_session_variable_name(varid)),
-				 errdetail("The session variable was not initialized yet.")));
+		/* Raise an error when we cannot initialize variable correctly */
+		if (var.is_not_null && !var.defexpr)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"",
+							get_namespace_name(get_session_variable_namespace(varid)),
+							get_session_variable_name(varid)),
+					 errdetail("The session variable was not initialized yet.")));
 
-	if (svar->has_defexpr)
-	{
-		Datum		value = (Datum) 0;
-		bool		isnull;
-		EState	   *estate = NULL;
-		Expr	   *defexpr;
-		ExprState  *defexprs;
-		MemoryContext oldcxt;
+		if (svar->has_defexpr)
+		{
+			Datum		value = (Datum) 0;
+			bool		isnull;
+			EState	   *estate = NULL;
+			Expr	   *defexpr;
+			ExprState  *defexprs;
+			MemoryContext oldcxt;
 
-		/* Prepare default expr */
-		estate = CreateExecutorState();
+			/* Prepare default expr */
+			estate = CreateExecutorState();
 
-		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+			oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
 
-		defexpr = expression_planner((Expr *) var.defexpr);
-		defexprs = ExecInitExpr(defexpr, NULL);
-		value = ExecEvalExprSwitchContext(defexprs,
-										  GetPerTupleExprContext(estate),
-										  &isnull);
+			defexpr = expression_planner((Expr *) var.defexpr);
+			defexprs = ExecInitExpr(defexpr, NULL);
+			value = ExecEvalExprSwitchContext(defexprs,
+											  GetPerTupleExprContext(estate),
+											  &isnull);
 
 
-		/* Store result before releasing Executor memory */
-		set_session_variable(svar, value, isnull, svar->typid, true);
+			/* Store result before releasing Executor memory */
+			set_session_variable(svar, value, isnull, svar->svartype->typid, true);
 
-		MemoryContextSwitchTo(oldcxt);
+			MemoryContextSwitchTo(oldcxt);
 
-		FreeExecutorState(estate);
+			FreeExecutorState(estate);
+		}
+		else
+			set_session_variable(svar, (Datum) 0, true, svar->svartype->typid, true);
+	}
+
+	*value = svar->value;
+	*isnull = svar->isnull;
+
+	/*
+	 * Check if stored value has still valid type.
+	 */
+	if (!session_variable_use_valid_type(svar))
+	{
+		/*
+		 * We can try to convert composite value to target type. We don't
+		 * try to save converted value, because an change of target composite
+		 * type can be aborted, and still the life cycle of session variable
+		 * is short.
+		 */
+		if (svar->natts > 0 && svar->svartype->natts > 0 &&
+			svar->relid == svar->svartype->relid)
+		{
+			*value = get_rowtype_value_in_expected_format(svar);
+		}
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("format of stored value of \"%s.%s\" session variable can be outdated",
+							get_namespace_name(get_session_variable_namespace(varid)),
+							get_session_variable_name(varid))));
+		}
+	}
+
+	/*
+	 * Although the value of domain type should be valid (it is
+	 * checked when it is assigned to session variable), we have to
+	 * check related constraints anytime. It can be more expensive
+	 * than in PL/pgSQL. PL/pgSQL forces domain checks when value
+	 * is assigned to the variable or when value is returned from
+	 * function. Fortunately, domain types manage cache of constraints by
+	 * self.
+	 */
+	if (svar->svartype->is_domain)
+	{
+		MemoryContext oldcxt = CurrentMemoryContext;
+
+		/*
+		 * Store domain_check extra in CurTransactionContext. When we are
+		 * in other transaction, the domain_check_extra cache is not valid.
+		 */
+		if (svar->svartype->domain_check_extra_lxid != MyProc->lxid)
+			svar->svartype->domain_check_extra = NULL;
+
+		domain_check(svar->value,
+					 svar->isnull,
+					 svar->svartype->typid,
+					 &svar->svartype->domain_check_extra,
+					 CurTransactionContext);
+
+		svar->svartype->domain_check_extra_lxid = MyProc->lxid;
+
+		MemoryContextSwitchTo(oldcxt);
 	}
-	else
-		set_session_variable(svar, (Datum) 0, true, svar->typid, true);
 
 	return svar;
 }
@@ -755,7 +1693,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid)
 	LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock);
 
 	if (!sessionvars)
-		create_sessionvars_hashtable();
+		create_sessionvars_hashtables();
 
 	/* Ensure so all entries in sessionvars hash table are valid */
 	sync_sessionvars_all();
@@ -804,18 +1742,21 @@ SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typ
 Datum
 CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 {
-	SVariable svar;
+	SVariable	svar;
+	Datum		result = (Datum) 0;
 
-	svar = prepare_variable_for_reading(varid);
+	svar = prepare_variable_for_reading(varid, &result, isNull);
 	Assert(svar != NULL && svar->is_valid);
 
-	*isNull = svar->isnull;
-	*typid = svar->typid;
+	*typid = svar->svartype->typid;
 
-	if (!svar->isnull)
-		return datumCopy(svar->value, svar->typbyval, svar->typlen);
+	/* force copy of not null (not yet copy) value */
+	if (!isNull && svar->value == result)
+		result = datumCopy(svar->value,
+						   svar->svartype->typbyval,
+						   svar->svartype->typlen);
 
-	return (Datum) 0;
+	return (Datum) result;
 }
 
 /*
@@ -826,22 +1767,25 @@ CopySessionVariable(Oid varid, bool *isNull, Oid *typid)
 Datum
 CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 {
-	SVariable svar;
+	SVariable	svar;
+	Datum		result = (Datum) 0;
 
-	svar = prepare_variable_for_reading(varid);
+	svar = prepare_variable_for_reading(varid, &result, isNull);
 	Assert(svar != NULL && svar->is_valid);
 
-	if (expected_typid != svar->typid)
+	if (expected_typid != svar->svartype->typid)
 		elog(ERROR, "type of variable \"%s.%s\" is different than expected",
 			 get_namespace_name(get_session_variable_namespace(varid)),
 			 get_session_variable_name(varid));
 
 	*isNull = svar->isnull;
 
-	if (!svar->isnull)
-		return datumCopy(svar->value, svar->typbyval, svar->typlen);
+	if (!*isNull && svar->value == result )
+		result = datumCopy(svar->value,
+						 svar->svartype->typbyval,
+						 svar->svartype->typlen);
 
-	return (Datum) 0;
+	return (Datum) result;
 }
 
 /*
@@ -852,22 +1796,18 @@ CopySessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 Datum
 GetSessionVariableWithTypeCheck(Oid varid, bool *isNull, Oid expected_typid)
 {
-	SVariable svar;
+	SVariable	svar;
+	Datum		result = (Datum) 0;
 
-	svar = prepare_variable_for_reading(varid);
+	svar = prepare_variable_for_reading(varid, &result, isNull);
 	Assert(svar != NULL && svar->is_valid);
 
-	if (expected_typid != svar->typid)
+	if (expected_typid != svar->svartype->typid)
 		elog(ERROR, "type of variable \"%s.%s\" is different than expected",
 			 get_namespace_name(get_session_variable_namespace(varid)),
 			 get_session_variable_name(varid));
 
-	*isNull = svar->isnull;
-
-	if (svar->isnull)
-		return (Datum) 0;
-
-	return svar->value;
+	return result;
 }
 
 /*
@@ -1070,6 +2010,9 @@ ResetSessionVariables(void)
 	{
 		hash_destroy(sessionvars);
 		sessionvars = NULL;
+
+		hash_destroy(sessionvars_types);
+		sessionvars_types = NULL;
 	}
 
 	/* Release memory allocated by session variables */
@@ -1371,6 +2314,8 @@ pg_debug_show_used_session_variables(PG_FUNCTION_ARGS)
 
 		while ((svar = (SVariable) hash_seq_search(&status)) != NULL)
 		{
+			char	   *name;
+			char	   *nsname;
 			Datum		values[NUM_PG_DEBUG_SHOW_USED_SESSION_VARIABLES_ATTS];
 			bool		nulls[NUM_PG_DEBUG_SHOW_USED_SESSION_VARIABLES_ATTS];
 			HeapTuple	tp;
-- 
2.37.2

