v20220721-0003-typecheck-check-of-consistency-of-format-of-stored-v.patch
text/x-patch
Filename: v20220721-0003-typecheck-check-of-consistency-of-format-of-stored-v.patch
Type: text/x-patch
Part: 10
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 v20220721-0003
Subject: typecheck - check of consistency of format of stored value.
| File | + | − |
|---|---|---|
| src/backend/commands/sessionvariable.c | 189 | 67 |
From 35bc6188511cb747377f341032ce08d69b356303 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 | 256 ++++++++++++++++++-------
1 file changed, 189 insertions(+), 67 deletions(-)
diff --git a/src/backend/commands/sessionvariable.c b/src/backend/commands/sessionvariable.c
index e8d096ff3f..c079758a8e 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 */
@@ -154,14 +206,14 @@ static List *xact_recheck_varids = 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 */
@@ -176,13 +228,19 @@ typedef struct 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 bool sync_sessionvars_all_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);
@@ -966,9 +1024,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)
@@ -988,14 +1047,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);
}
@@ -1023,15 +1092,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
@@ -1049,7 +1122,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);
@@ -1071,17 +1147,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;
@@ -1125,7 +1197,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);
@@ -1141,54 +1213,97 @@ prepare_variable_for_reading(Oid varid)
varid);
/* 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);
+
+
+ /* 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);
+ }
- /* Raise an error when we cannot initialize variable correctly */
- if (var.is_not_null && !var.defexpr)
+ /*
+ * 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;
+ MemoryContext oldcxt = CurrentMemoryContext;
- /* 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);
+ /*
+ * 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);
- /* 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;
}
@@ -1212,7 +1327,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();
@@ -1267,10 +1382,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;
}
@@ -1288,7 +1405,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));
@@ -1296,7 +1413,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;
}
@@ -1314,7 +1433,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));
@@ -1518,6 +1637,9 @@ ResetSessionVariables(void)
{
hash_destroy(sessionvars);
sessionvars = NULL;
+
+ hash_destroy(sessionvars_types);
+ sessionvars_types = NULL;
}
/* Release memory allocated by session variables */
--
2.36.1