From 54a8fb3e0cb3243fb485cb46624060fb83213e78 Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Mon, 4 Jul 2022 06:06:19 +0200 Subject: [PATCH 03/12] 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 | 799 ++++++++++++++++++++++--- 1 file changed, 729 insertions(+), 70 deletions(-) diff --git a/src/backend/commands/sessionvariable.c b/src/backend/commands/sessionvariable.c index e26903d135..7e5a9b03d3 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,55 @@ * 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; + char *typname; + + 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 +197,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 +212,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 +228,552 @@ 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); + Assert(svt1 != svt2); + + /* + * 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. 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->natts > 0 || svt2->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; +} + +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->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 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 = NULL; + + /* skip dropped attributies */ + while (attrn < tupdesc->natts) + { + attr = TupleDescAttr(tupdesc, attrn++); + + if (!attr->attisdropped) + break; + } + + if (attr && !attr->attisdropped) + { + SVariableType field_svt = svt->attrs[i].svartype; + int64 field_gennum = svt->attrs[i].gennum; + + if (field_svt->typid != attr->atttypid) + { + result = false; + break; + } + + if (field_gennum != field_svt->gennum) + { + result = false; + break; + } + + if (field_gennum != get_svariable_valid_type_gennum(field_svt)) + { + 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); + 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. @@ -409,9 +1004,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 +1027,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 +1072,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 +1102,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 +1127,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 +1168,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 +1180,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(); - if (!found) - init_session_variable(svar, &var); + oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); + + defexpr = expression_planner((Expr *) var.defexpr); + defexprs = ExecInitExpr(defexpr, NULL); + value = ExecEvalExprSwitchContext(defexprs, + GetPerTupleExprContext(estate), + &isnull); - /* 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 +1294,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 +1342,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 +1365,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 +1373,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 +1393,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 +1570,9 @@ ResetSessionVariables(void) { hash_destroy(sessionvars); sessionvars = NULL; + + hash_destroy(sessionvars_types); + sessionvars_types = NULL; } /* Release memory allocated by session variables */ -- 2.36.1