From b186cd15556e76e735e759cca0e09ca47d894e66 Mon Sep 17 00:00:00 2001 From: Liu Zonghao Date: Tue, 23 Apr 2024 17:12:36 +0800 Subject: [PATCH] Allow plan cache result type to revalidate on the fly Usually, when a relation gets changed by a DB user, an invalidation message is sent to the shared context. Plancache will revalidate this behavior multiple times during the execution so that if the base relation changes, the cached plan will be updated accordingly. However, Postgres does not allow the resulting attributes to change because changing the query's result on the fly requires extra hand- ling on the user application, which is out of the DB kernel's control. Also, it will generate unwanted behavior in a concurrent transactional application. Due to the user's heavy request, we are allowing this in this patch and introduced a new POSTMASTER GUC to control this behavior. This GUC is a POSTMASTER type because the result-checking behavior is a flag (i.e., an intrinsic attribute) of the plan cache, initially only for transactional statements. We need to ensure that when the user changes the GUC, no plan cache is working at the time. --- src/bin/gs_guc/cluster_guc.conf | 1 + src/common/backend/utils/cache/plancache.cpp | 11 +++++-- src/common/backend/utils/misc/guc/guc_sql.cpp | 12 ++++++++ .../optimizer/commands/prepare.cpp | 29 +++++++++++-------- src/gausskernel/process/tcop/postgres.cpp | 14 +++++---- .../knl/knl_guc/knl_instance_attr_sql.h | 1 + src/include/utils/guc.h | 4 +++ 7 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/bin/gs_guc/cluster_guc.conf b/src/bin/gs_guc/cluster_guc.conf index 7b968837f..7ade9b857 100755 --- a/src/bin/gs_guc/cluster_guc.conf +++ b/src/bin/gs_guc/cluster_guc.conf @@ -608,6 +608,7 @@ zero_damaged_pages|bool|0,0|NULL|NULL| enable_bloom_filter|bool|0,0|NULL|NULL| cstore_insert_mode|enum|auto,main,delta|NULL|NULL| plan_cache_mode|enum|auto,force_generic_plan,force_custom_plan|NULL|NULL| +plan_cache_type_validation|bool|0,0|NULL|NULL| remote_read_mode|enum|off,non_authentication,authentication|NULL|NULL| enable_debug_vacuum|bool|0,0|NULL|NULL| enable_early_free|bool|0,0|NULL|NULL| diff --git a/src/common/backend/utils/cache/plancache.cpp b/src/common/backend/utils/cache/plancache.cpp index f6ddd9742..bf845433c 100644 --- a/src/common/backend/utils/cache/plancache.cpp +++ b/src/common/backend/utils/cache/plancache.cpp @@ -1056,8 +1056,15 @@ List* RevalidateCachedQuery(CachedPlanSource* plansource, bool has_lp) } else if (resultDesc == NULL || plansource->resultDesc == NULL || !equalTupleDescs(resultDesc, plansource->resultDesc)) { /* can we give a better error message? */ - if (plansource->fixed_result) - ereport(ERROR, (errcode(ERRCODE_INVALID_CACHE_PLAN), errmsg("cached plan must not change result type"))); + if (plansource->fixed_result) { + ereport(ERROR, (errcode(ERRCODE_INVALID_CACHE_PLAN), + errmsg("cached plan must not change result type"))); + } else if (!FORCE_VALIDATE_PLANCACHE_RESULT) { + /* If result type validation is turned off, better notice the caller */ + ereport(NOTICE, (errmsg("cached plan's result type has changed"), + errdetail("plan cache result type validation is turned off"))); + } + oldcxt = MemoryContextSwitchTo(plansource->context); if (resultDesc) resultDesc = CreateTupleDescCopy(resultDesc); diff --git a/src/common/backend/utils/misc/guc/guc_sql.cpp b/src/common/backend/utils/misc/guc/guc_sql.cpp index 3d25df15a..a750be81c 100755 --- a/src/common/backend/utils/misc/guc/guc_sql.cpp +++ b/src/common/backend/utils/misc/guc/guc_sql.cpp @@ -1205,6 +1205,18 @@ static void InitSqlConfigureNamesBool() NULL, NULL}, + {{"plan_cache_type_validation", + PGC_POSTMASTER, + NODE_ALL, + XC_HOUSEKEEPING_OPTIONS, + gettext_noop("Turns off pbe result type check: allow user to change the plan cache result on the fly."), + NULL}, + &g_instance.attr.attr_sql.plan_cache_type_validation, + true, + NULL, + NULL, + NULL}, + {{"lo_compat_privileges", PGC_SUSET, NODE_ALL, diff --git a/src/gausskernel/optimizer/commands/prepare.cpp b/src/gausskernel/optimizer/commands/prepare.cpp index 63076ab36..78ae86ca4 100755 --- a/src/gausskernel/optimizer/commands/prepare.cpp +++ b/src/gausskernel/optimizer/commands/prepare.cpp @@ -199,6 +199,7 @@ void PrepareQuery(PrepareStmt* stmt, const char* queryString) int nargs; Query* query = NULL; List* query_list = NIL; + bool fixed_result = FORCE_VALIDATE_PLANCACHE_RESULT; int i; /* @@ -324,7 +325,7 @@ void PrepareQuery(PrepareStmt* stmt, const char* queryString) NULL, NULL, 0, /* default cursor options */ - true, /* fixed result */ + fixed_result, /* fixed result */ stmt->name); /* @@ -376,9 +377,9 @@ void ExecuteQuery(ExecuteStmt* stmt, IntoClause* intoClause, const char* querySt t_thrd.postgres_cxt.cur_command_tag = transform_node_tag(psrc->raw_parse_tree); /* Shouldn't find a non-fixed-result cached plan */ - if (!entry->plansource->fixed_result) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("EXECUTE does not support variable-result cached plans"))); + if (!entry->plansource->fixed_result && FORCE_VALIDATE_PLANCACHE_RESULT) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("EXECUTE does not support variable-result cached plans"))); /* Evaluate parameters, if any */ if (entry->plansource->num_params > 0) { @@ -1087,10 +1088,16 @@ bool HaveActiveCoordinatorPreparedStatement(const char* stmt_name) TupleDesc FetchPreparedStatementResultDesc(PreparedStatement *stmt) { /* - * Since we don't allow prepared statements' result tupdescs to change, - * there's no need to worry about revalidating the cached plan here. + * User are allowed to change the result type of plan cache + * on the fly, so make sure to revalidate the descriptor + * before we pass it to the portal. */ - Assert(stmt->plansource->fixed_result); + if (FORCE_VALIDATE_PLANCACHE_RESULT) { + Assert(stmt->plansource->fixed_result); + } else { + RevalidateCachedQuery(stmt->plansource); + } + if (stmt->plansource->resultDesc) return CreateTupleDescCopy(stmt->plansource->resultDesc); else @@ -1414,11 +1421,9 @@ CachedPlanSource* GetCachedPlanSourceFromExplainExecute(const char* stmt_name) Assert(psrc != NULL); /* Shouldn't find a non-fixed-result cached plan */ - if (!psrc->fixed_result) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("EXPLAIN EXECUTE does not support variable-result cached plans"))); - } + if (!psrc->fixed_result && FORCE_VALIDATE_PLANCACHE_RESULT) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("EXPLAIN EXECUTE does not support variable-result cached plans"))); return psrc; } diff --git a/src/gausskernel/process/tcop/postgres.cpp b/src/gausskernel/process/tcop/postgres.cpp index 4fca906b8..94a435fc3 100755 --- a/src/gausskernel/process/tcop/postgres.cpp +++ b/src/gausskernel/process/tcop/postgres.cpp @@ -3560,6 +3560,7 @@ static void exec_parse_message(const char* query_string, /* string to execute */ CachedPlanSource* psrc = NULL; bool is_named = false; bool save_log_statement_stats = u_sess->attr.attr_common.log_statement_stats; + bool fixed_result = FORCE_VALIDATE_PLANCACHE_RESULT; char msec_str[PRINTF_DST_MAX]; char* mask_string = NULL; #ifdef ENABLE_MULTIPLE_NODES @@ -3942,8 +3943,9 @@ static void exec_parse_message(const char* query_string, /* string to execute */ /* Finish filling in the CachedPlanSource */ CompleteCachedPlan(psrc, querytree_list, unnamed_stmt_context, paramTypes, paramModes, numParams, NULL, NULL, 0, /* default cursor options */ - true, stmt_name, single_exec_node, - is_read_only); /* fixed result */ + fixed_result, /* fixed result */ + stmt_name, single_exec_node, + is_read_only); /* For ctas query, rewrite is not called in PARSE, so we must set invalidation to revalidate the cached plan. */ if (is_ctas) { @@ -5818,8 +5820,8 @@ void exec_describe_statement_message(const char* stmt_name) Assert(NULL != psrc); - /* Prepared statements shouldn't have changeable result descs */ - Assert(psrc->fixed_result); + /* Prepared statements shouldn't have changeable result descs unless being forced */ + Assert(!FORCE_VALIDATE_PLANCACHE_RESULT || psrc->fixed_result); #ifdef ENABLE_MOT /* set current transaction storage engine */ @@ -12025,8 +12027,8 @@ static void exec_batch_bind_execute(StringInfo input_message) switch (describe_type) { case 'S': { StringInfoData buf; - /* Prepared statements shouldn't have changeable result descs */ - Assert(psrc->fixed_result); + /* Prepared statements shouldn't have changeable result descs unless being forced */ + Assert(!FORCE_VALIDATE_PLANCACHE_RESULT || psrc->fixed_result); /* * First describe the parameters... diff --git a/src/include/knl/knl_guc/knl_instance_attr_sql.h b/src/include/knl/knl_guc/knl_instance_attr_sql.h index ff5a7626b..ba7c05108 100644 --- a/src/include/knl/knl_guc/knl_instance_attr_sql.h +++ b/src/include/knl/knl_guc/knl_instance_attr_sql.h @@ -47,6 +47,7 @@ typedef struct knl_instance_attr_sql { bool enable_orc_cache; bool enable_default_cfunc_libpath; bool enableRemoteExcute; + bool plan_cache_type_validation; int udf_memory_limit; int UDFWorkerMemHardLimit; int job_queue_processes; diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index ac85dbfcc..dacf02821 100755 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -474,6 +474,10 @@ typedef enum { g_instance.shmem_cxt.numaNodeNum <= PARTITION_OPFUSION_MAX_NUMA_NODE && \ !g_instance.attr.attr_common.enable_global_syscache) +/* Wrapper macro for forced plan cache result type check */ +#define FORCE_VALIDATE_PLANCACHE_RESULT \ + ((bool)g_instance.attr.attr_sql.plan_cache_type_validation) + typedef enum { SUMMARY = 0, /* not collect multi column statistics info */ DETAIL = 1, /* collect multi column statistics info */