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.
This commit is contained in:
Liu Zonghao
2024-04-23 17:12:36 +08:00
committed by yaoxin
parent 3f02e65687
commit b186cd1555
7 changed files with 52 additions and 20 deletions

View File

@ -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|

View File

@ -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);

View File

@ -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,

View File

@ -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;
}

View File

@ -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...

View File

@ -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;

View File

@ -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 */