From ceded996fcd472b06ddd591819a2f476bf42030a Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Fri, 21 Nov 2025 20:42:56 +0100 Subject: [PATCH 3/7] collect session variables used in plan and assign paramid In the plan stage we need to collect used session variables. On the order of this list, the param nodes gets paramid (fix_param_node). This number is used (later) as index to buffer of values of the used session variables. The buffer is prepared and filled by executor. Some unsupported optimizations are disabled: * parallel execution * simple expression execution in PL/pgSQL * SQL functions inlining Before execution of query with session variables we need to collect used session variables. This list is used for loading variables to executed query. plan --- doc/src/sgml/parallel.sgml | 6 ++ src/backend/catalog/dependency.c | 10 +++ src/backend/commands/session_variable.c | 39 ++++++++++ src/backend/optimizer/plan/planner.c | 11 +++ src/backend/optimizer/plan/setrefs.c | 94 ++++++++++++++++++++++- src/backend/optimizer/prep/prepjointree.c | 3 + src/backend/optimizer/util/clauses.c | 35 ++++++++- src/backend/utils/fmgr/fmgr.c | 10 ++- src/include/commands/session_variable.h | 2 + src/include/nodes/pathnodes.h | 5 ++ src/include/nodes/plannodes.h | 3 + src/include/optimizer/planmain.h | 2 + 12 files changed, 214 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/parallel.sgml b/doc/src/sgml/parallel.sgml index 1ce9abf86f5..00c0f715eea 100644 --- a/doc/src/sgml/parallel.sgml +++ b/doc/src/sgml/parallel.sgml @@ -515,6 +515,12 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%'; Plan nodes that reference a correlated SubPlan. + + + + Usage of a session variable. + + diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index fba5b2e0d09..62d26563d17 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -1902,6 +1902,16 @@ find_expr_references_walker(Node *node, { Param *param = (Param *) node; + /* + * catalog less session variable variable cannot be used in persistent + * catalog based object. + */ + if (param->paramkind == PARAM_VARIABLE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("session variable \"%s\" cannot be referenced in a persistent object", + param->paramvarname))); + /* A parameter must depend on the parameter's datatype */ add_object_address(TypeRelationId, param->paramtype, 0, context->addrs); diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c index 861c2a55f4e..0daa46fdde3 100644 --- a/src/backend/commands/session_variable.c +++ b/src/backend/commands/session_variable.c @@ -132,6 +132,45 @@ get_session_variable_type_typmod_collid(char *varname, *collid = svar->varcollation; } +/* + * Returns a copy of the value of the session variable (in the current memory + * context). + */ +Datum +GetSessionVariableWithTypecheck(char *varname, + Oid typid, int32 typmod, + bool *isnull) +{ + SVariable svar; + Datum result; + + svar = search_variable(varname); + + if (svar->vartype != typid || svar->vartypmod != typmod) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("session variable %s is not of a type %s but type %s", + varname, + format_type_with_typemod(typid, typmod), + format_type_with_typemod(svar->vartype, svar->vartypmod)))); + + /* only owner can get content of variable */ + if (svar->varowner != GetUserId() && !superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for session variable %s", + varname))); + + if (!svar->isnull) + result = datumCopy(svar->value, svar->typbyval, svar->typlen); + else + result = (Datum) 0; + + *isnull = svar->isnull; + + return result; +} + /* * Creates a new variable - does new entry in sessionvars * diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c4fd646b999..cc03b0311a4 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -374,6 +374,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, glob->dependsOnRole = false; glob->partition_directory = NULL; glob->rel_notnullatts_hash = NULL; + glob->sessionVariables = NIL; /* * Assess whether it's feasible to use parallel mode for this query. We @@ -617,6 +618,9 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, result->paramExecTypes = glob->paramExecTypes; /* utilityStmt should be null, but we might as well copy it */ result->utilityStmt = parse->utilityStmt; + + result->sessionVariables = glob->sessionVariables; + result->stmt_location = parse->stmt_location; result->stmt_len = parse->stmt_len; @@ -805,6 +809,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name, */ pull_up_subqueries(root); + /* + * Check if some subquery uses a session variable. The flag + * hasSessionVariables should be true if the query or some subquery uses a + * session variable. + */ + pull_up_has_session_variables(root); + /* * If this is a simple UNION ALL query, flatten it into an appendrel. We * do this now because it requires applying pull_up_subqueries to the leaf diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index ccdc9bc264a..48de8bf4f14 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -210,6 +210,8 @@ static List *set_returning_clause_references(PlannerInfo *root, static List *set_windowagg_runcondition_references(PlannerInfo *root, List *runcondition, Plan *plan); +static bool pull_up_has_session_variables_walker(Node *node, + PlannerInfo *root); /***************************************************************************** @@ -1341,6 +1343,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) return plan; } +/* + * Search usage of session variables in subqueries + */ +void +pull_up_has_session_variables(PlannerInfo *root) +{ + Query *query = root->parse; + + if (query->hasSessionVariables) + { + root->hasSessionVariables = true; + } + else + { + (void) query_tree_walker(query, + pull_up_has_session_variables_walker, + (void *) root, 0); + } +} + +static bool +pull_up_has_session_variables_walker(Node *node, PlannerInfo *root) +{ + if (node == NULL) + return false; + if (IsA(node, Query)) + { + Query *query = (Query *) node; + + if (query->hasSessionVariables) + { + root->hasSessionVariables = true; + return false; + } + + /* recurse into subselects */ + return query_tree_walker((Query *) node, + pull_up_has_session_variables_walker, + (void *) root, 0); + } + return expression_tree_walker(node, pull_up_has_session_variables_walker, + (void *) root); +} + /* * set_indexonlyscan_references * Do set_plan_references processing on an IndexOnlyScan @@ -2141,6 +2187,10 @@ fix_expr_common(PlannerInfo *root, Node *node) * If it's a PARAM_MULTIEXPR, replace it with the appropriate Param from * root->multiexpr_params; otherwise no change is needed. * Just for paranoia's sake, we make a copy of the node in either case. + * + * If it's a PARAM_VARIABLE, then we collect used session variables in + * the list root->glob->sessionVariable. Also, assign the parameter's + * "paramid" to the parameter's position in that list. */ static Node * fix_param_node(PlannerInfo *root, Param *p) @@ -2159,6 +2209,43 @@ fix_param_node(PlannerInfo *root, Param *p) elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", p->paramid); return copyObject(list_nth(params, colno - 1)); } + + if (p->paramkind == PARAM_VARIABLE) + { + int n = 0; + + /* we will modify object */ + p = (Param *) copyObject(p); + + /* + * Now, we can actualize list of session variables, and we can + * complete paramid parameter. + */ + foreach_node(Param, paramvar, root->glob->sessionVariables) + { + if (strcmp(paramvar->paramvarname, p->paramvarname) == 0) + { + p->paramid = paramvar->paramid; + + return (Node *) p; + } + + n += 1; + } + + p->paramid = n; + + /* + * Because session variables are catalogless, we cannot to use plan + * invalidation. Then we need to check type, typmod, collid any time, + * when we load values of session variables to parameter's buffer. + * For this purpose it is more easy to save complete Param node. + */ + root->glob->sessionVariables = lappend(root->glob->sessionVariables, p); + + return (Node *) p; + } + return (Node *) copyObject(p); } @@ -2220,7 +2307,9 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan, * replacing Aggref nodes that should be replaced by initplan output Params, * choosing the best implementation for AlternativeSubPlans, * looking up operator opcode info for OpExpr and related nodes, - * and adding OIDs from regclass Const nodes into root->glob->relationOids. + * adding OIDs from regclass Const nodes into root->glob->relationOids, + * assigning paramvarid to PARAM_VARIABLE params, and collecting the + * of session variables in the root->glob->sessionVariables list. * * 'node': the expression to be modified * 'rtoffset': how much to increment varnos by @@ -2242,7 +2331,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec) root->multiexpr_params != NIL || root->glob->lastPHId != 0 || root->minmax_aggs != NIL || - root->hasAlternativeSubPlans) + root->hasAlternativeSubPlans || + root->hasSessionVariables) { return fix_scan_expr_mutator(node, &context); } diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 7581695647d..282bb7d5e25 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -1649,6 +1649,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, /* If subquery had any RLS conditions, now main query does too */ parse->hasRowSecurity |= subquery->hasRowSecurity; + /* if the subquery had session variables, the main query does too */ + parse->hasSessionVariables |= subquery->hasSessionVariables; + /* * subquery won't be pulled up if it hasAggs, hasWindowFuncs, or * hasTargetSRFs, so no work needed on those flags diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 202ba8ed4bb..19ca377e340 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -25,6 +25,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" +#include "commands/session_variable.h" #include "executor/executor.h" #include "executor/functions.h" #include "funcapi.h" @@ -945,6 +946,13 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context) if (param->paramkind == PARAM_EXTERN) return false; + /* we don't support passing session variables to workers */ + if (param->paramkind == PARAM_VARIABLE) + { + if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context)) + return true; + } + if (param->paramkind != PARAM_EXEC || !list_member_int(context->safe_param_ids, param->paramid)) { @@ -2403,6 +2411,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context) * value of the Param. * 2. Fold stable, as well as immutable, functions to constants. * 3. Reduce PlaceHolderVar nodes to their contained expressions. + * 4. Current value of session variable can be used for estimation too. *-------------------- */ Node * @@ -2529,6 +2538,29 @@ eval_const_expressions_mutator(Node *node, } } } + else if (param->paramkind == PARAM_VARIABLE && + context->estimate) + { + int16 typLen; + bool typByVal; + Datum pval; + bool isnull; + + get_typlenbyval(param->paramtype, &typLen, &typByVal); + + pval = GetSessionVariableWithTypecheck(param->paramvarname, + param->paramtype, + param->paramtypmod, + &isnull); + + return (Node *) makeConst(param->paramtype, + param->paramtypmod, + param->paramcollid, + (int) typLen, + pval, + isnull, + typByVal); + } /* * Not replaceable, so just copy the Param (no need to @@ -4828,7 +4860,8 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid, querytree->limitOffset || querytree->limitCount || querytree->setOperations || - list_length(querytree->targetList) != 1) + (list_length(querytree->targetList) != 1) || + querytree->hasSessionVariables) goto fail; /* If the function result is composite, resolve it */ diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 0fe63c6bb83..d26e744e296 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -1991,9 +1991,13 @@ get_call_expr_arg_stable(Node *expr, int argnum) */ if (IsA(arg, Const)) return true; - if (IsA(arg, Param) && - ((Param *) arg)->paramkind == PARAM_EXTERN) - return true; + if (IsA(arg, Param)) + { + Param *p = (Param *) arg; + + if (p->paramkind == PARAM_EXTERN || p->paramkind == PARAM_VARIABLE) + return true; + } return false; } diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h index 96be968c3d4..3687490bcb1 100644 --- a/src/include/commands/session_variable.h +++ b/src/include/commands/session_variable.h @@ -22,6 +22,8 @@ extern void CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt); extern void DropVariableByName(char *varname); +extern Datum GetSessionVariableWithTypecheck(char *varname, Oid typid, int32 typmod, bool *isnull); + extern void get_session_variable_type_typmod_collid(char *varname, Oid *typid, int32 *typmod, diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 46a8655621d..97a00df8d1f 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -189,6 +189,9 @@ typedef struct PlannerGlobal /* extension state */ void **extension_state pg_node_attr(read_write_ignore); int extension_state_allocated; + + /* list of used session variables */ + List *sessionVariables; } PlannerGlobal; /* macro for fetching the Plan associated with a SubPlan node */ @@ -547,6 +550,8 @@ struct PlannerInfo bool hasRecursion; /* true if a planner extension may replan this subquery */ bool assumeReplanning; + /* true if session variables were used */ + bool hasSessionVariables; /* * The rangetable index for the RTE_GROUP RTE, or 0 if there is no diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index c4393a94321..61754ae4efc 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -158,6 +158,9 @@ typedef struct PlannedStmt */ List *extension_state; + /* PARAM_VARIABLE Params */ + List *sessionVariables; + /* statement location in source string (copied from Query) */ /* start location, or -1 if unknown */ ParseLoc stmt_location; diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 00addf15992..fb81ceb375f 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -132,4 +132,6 @@ extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid); extern void record_plan_type_dependency(PlannerInfo *root, Oid typid); extern bool extract_query_dependencies_walker(Node *node, PlannerInfo *context); +extern void pull_up_has_session_variables(PlannerInfo *root); + #endif /* PLANMAIN_H */ -- 2.52.0