From 48a047a6ecb1a8eb2d2adacddca376312f6acf3b Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.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 | 777 ++++++++++++++++++++++---
 1 file changed, 707 insertions(+), 70 deletions(-)

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

