v20220820-1-0003-typecheck-check-of-consistency-of-format-of-stored-v.patch
text/x-patch
Filename: v20220820-1-0003-typecheck-check-of-consistency-of-format-of-stored-v.patch
Type: text/x-patch
Part: 9
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v20220820-0003
Subject: typecheck - check of consistency of format of stored value.
| File | + | − |
|---|---|---|
| src/backend/commands/sessionvariable.c | 1051 | 103 |
From 2c2a1d28dfcb59dca8b0f20e7c6c1ac0e9f6c109 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/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 | 1154 +++++++++++++++++++++---
1 file changed, 1051 insertions(+), 103 deletions(-)
diff --git a/src/backend/commands/sessionvariable.c b/src/backend/commands/sessionvariable.c
index 204582ecbc..0960afe4f9 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);
+
+ newval = datumCopy(value,
+ _svar->svartype->typbyval,
+ _svar->svartype->typlen);
+
+ if (tupdesc)
+ {
+ newtupdesc = palloc(TupleDescSize(tupdesc));
+ TupleDescCopy(newtupdesc, tupdesc);
+ }
- free_session_variable_value(svar);
+ 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);
- svar->value = newval;
- svar->isnull = isnull;
- svar->freeval = newval != value;
- svar->is_valid = true;
+ 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,131 @@ 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;
+
+ else if (svar->attrs[i].isdropped || target_svt->attrs[i].isdropped)
+ outisnull[i] = true;
+
+ else
+ {
+ 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 +1538,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 +1547,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 +1559,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 +1696,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 +1745,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 +1770,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 +1799,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 +2013,9 @@ ResetSessionVariables(void)
{
hash_destroy(sessionvars);
sessionvars = NULL;
+
+ hash_destroy(sessionvars_types);
+ sessionvars_types = NULL;
}
/* Release memory allocated by session variables */
@@ -1371,6 +2317,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