From 2f40be96a99cd1967eee8b294004b8d54e9f681b Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Fri, 21 Nov 2025 15:28:59 +0100 Subject: [PATCH 02/11] parsing session variable fences The session variables can be used in query only inside the variable fence. This is special syntax VARIABLE(varname), that eliminates a risk of collision between variable and column identifier. The session variables cannot be used as parameters of CALL or EXECUTE commands. These commands evaluates arguments by direct call of expression executor, and direct access to session variables from expression executor will be implemented later (in next step) --- doc/src/sgml/ddl.sgml | 10 ++ src/backend/commands/prepare.c | 8 ++ src/backend/commands/session_variable.c | 21 +++++ src/backend/nodes/nodeFuncs.c | 6 ++ src/backend/parser/analyze.c | 7 ++ src/backend/parser/gram.y | 17 +++- src/backend/parser/parse_expr.c | 120 ++++++++++++++++++++++++ src/backend/parser/parse_merge.c | 1 + src/backend/parser/parse_target.c | 7 ++ src/backend/utils/adt/ruleutils.c | 8 ++ src/include/commands/session_variable.h | 5 + src/include/nodes/parsenodes.h | 12 +++ src/include/nodes/primnodes.h | 5 + src/include/parser/parse_node.h | 1 + src/pl/plpgsql/src/pl_exec.c | 3 +- src/tools/pgindent/typedefs.list | 1 + 16 files changed, 229 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 86454e5732e..b96919999b1 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -5949,6 +5949,16 @@ SELECT ... FROM GRAPH_TABLE (myshop MATCH (IS person WHERE name = '...')-[]->... variable is stored in session memory and is private to each session. It is automatically released when the session ends. + + + In a query, a session variable can only be referenced using the special + VARIABLE(varname) syntax. This avoids any risk of + collision between variable names and column names. + + +SELECT VARIABLE(current_user_id); + + diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 876aad2100a..5e0774247a7 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -343,6 +343,14 @@ EvaluateParams(ParseState *pstate, PreparedStatement *pstmt, List *params, i++; } + /* + * The arguments of EXECUTE are evaluated by a direct expression executor + * call. This mode doesn't support session variables yet. It will be + * enabled later. This case should be blocked parser by + * expr_kind_allows_session_variables, so only assertions is used here. + */ + Assert(!pstate->p_hasSessionVariables); + /* Prepare the expressions for execution */ exprstates = ExecPrepareExprList(params, estate); diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c index a865a4c10c4..482737d6797 100644 --- a/src/backend/commands/session_variable.c +++ b/src/backend/commands/session_variable.c @@ -107,6 +107,27 @@ search_variable(char *varname) return svar; } +/* + * Returns the type, typmod and collid of the given session variable. + * + * Raises an error when the variable doesn't exists and *error is null. + */ +void +get_session_variable_type_typmod_collid(char *varname, + Oid *typid, + int32 *typmod, + Oid *collid) +{ + SVariable svar; + + svar = search_variable(varname); + + /* only owner can set content of variable */ + *typid = svar->vartype; + *typmod = svar->vartypmod; + *collid = svar->varcollation; +} + /* * Creates a new variable - does new entry in sessionvars * diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 6a850349cf7..7ebf63e46ba 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -1677,6 +1677,9 @@ exprLocation(const Node *expr) case T_ParamRef: loc = ((const ParamRef *) expr)->location; break; + case T_VariableFence: + loc = ((const VariableFence *) expr)->location; + break; case T_A_Const: loc = ((const A_Const *) expr)->location; break; @@ -4775,6 +4778,9 @@ raw_expression_tree_walker_impl(Node *node, return true; } break; + case T_VariableFence: + /* we assume the fields contain nothing interesting */ + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index ad31dee2686..8ad77b765c5 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -619,6 +619,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) qry->hasWindowFuncs = pstate->p_hasWindowFuncs; qry->hasTargetSRFs = pstate->p_hasTargetSRFs; qry->hasAggs = pstate->p_hasAggs; + qry->hasSessionVariables = pstate->p_hasSessionVariables; assign_query_collations(pstate, qry); @@ -1058,6 +1059,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) qry->hasTargetSRFs = pstate->p_hasTargetSRFs; qry->hasSubLinks = pstate->p_hasSubLinks; + qry->hasSessionVariables = pstate->p_hasSessionVariables; assign_query_collations(pstate, qry); @@ -1537,6 +1539,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt, qry->hasWindowFuncs = pstate->p_hasWindowFuncs; qry->hasTargetSRFs = pstate->p_hasTargetSRFs; qry->hasAggs = pstate->p_hasAggs; + qry->hasSessionVariables = pstate->p_hasSessionVariables; foreach(l, stmt->lockingClause) { @@ -1763,6 +1766,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); qry->hasSubLinks = pstate->p_hasSubLinks; + qry->hasSessionVariables = pstate->p_hasSessionVariables; assign_query_collations(pstate, qry); @@ -2014,6 +2018,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) qry->hasWindowFuncs = pstate->p_hasWindowFuncs; qry->hasTargetSRFs = pstate->p_hasTargetSRFs; qry->hasAggs = pstate->p_hasAggs; + qry->hasSessionVariables = pstate->p_hasSessionVariables; foreach(l, lockingClause) { @@ -2506,6 +2511,7 @@ transformReturnStmt(ParseState *pstate, ReturnStmt *stmt) qry->hasWindowFuncs = pstate->p_hasWindowFuncs; qry->hasTargetSRFs = pstate->p_hasTargetSRFs; qry->hasAggs = pstate->p_hasAggs; + qry->hasSessionVariables = pstate->p_hasSessionVariables; assign_query_collations(pstate, qry); @@ -2572,6 +2578,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) qry->hasTargetSRFs = pstate->p_hasTargetSRFs; qry->hasSubLinks = pstate->p_hasSubLinks; + qry->hasSessionVariables = pstate->p_hasSessionVariables; assign_query_collations(pstate, qry); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e5f4c9eb2bb..3b7430455be 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -535,7 +535,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type def_arg columnElem where_clause where_or_current_clause a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound columnref having_clause func_table xmltable array_expr - OptWhereClause operator_def_arg + OptWhereClause operator_def_arg variable_fence %type opt_column_and_period_list %type rowsfrom_item rowsfrom_list opt_col_def_list %type opt_ordinality opt_without_overlaps @@ -924,7 +924,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); */ %nonassoc UNBOUNDED NESTED /* ideally would have same precedence as IDENT */ %nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP - SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH + SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH VARIABLE %left Op OPERATOR RIGHT_ARROW '|' /* multi-character ops and user-defined operators */ %left '+' '-' %left '*' '/' '%' @@ -16393,6 +16393,8 @@ c_expr: columnref { $$ = $1; } else $$ = $2; } + | variable_fence + { $$ = $1; } | case_expr { $$ = $1; } | func_expr @@ -17797,6 +17799,17 @@ case_arg: a_expr { $$ = $1; } | /*EMPTY*/ { $$ = NULL; } ; +variable_fence: + VARIABLE '(' ColId ')' + { + VariableFence *vf = makeNode(VariableFence); + + vf->varname = $3; + vf->location = @3; + $$ = (Node *) vf; + } + ; + columnref: ColId { $$ = makeColumnRef($1, NIL, @1, yyscanner); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 312dfdc182a..de9e1e80103 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -18,6 +18,7 @@ #include "access/htup_details.h" #include "catalog/pg_aggregate.h" #include "catalog/pg_type.h" +#include "commands/session_variable.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" @@ -79,6 +80,7 @@ static Node *transformWholeRowRef(ParseState *pstate, static Node *transformIndirection(ParseState *pstate, A_Indirection *ind); static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); static Node *transformCollateClause(ParseState *pstate, CollateClause *c); +static Node *transformVariableFence(ParseState *pstate, VariableFence *vf); static Node *transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor); static Node *transformJsonArrayConstructor(ParseState *pstate, @@ -373,6 +375,10 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr); break; + case T_VariableFence: + result = transformVariableFence(pstate, (VariableFence *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -911,6 +917,120 @@ transformParamRef(ParseState *pstate, ParamRef *pref) return result; } +/* + * Returns true if the given expression kind is valid for session variables. + * Session variables can be used everywhere where external parameters can be + * used. Session variables are not allowed in DDL commands or in constraints. + * + * An identifier can be parsed as a session variable only for expression kinds + * where session variables are allowed. This is the primary usage of this + * function. + * + * The second usage of this function is to decide whether a "column does not + * exist" or a "column or variable does not exist" error message should be + * printed. When we are in an expression where session variables cannot be + * used, we raise the first form of error message. + */ +static bool +expr_kind_allows_session_variables(ParseExprKind p_expr_kind) +{ + bool result = false; + + switch (p_expr_kind) + { + case EXPR_KIND_NONE: + Assert(false); /* can't happen */ + return false; + + /* session variables allowed */ + case EXPR_KIND_OTHER: + case EXPR_KIND_JOIN_ON: + case EXPR_KIND_FROM_SUBSELECT: + case EXPR_KIND_FROM_FUNCTION: + case EXPR_KIND_WHERE: + case EXPR_KIND_HAVING: + case EXPR_KIND_FILTER: + case EXPR_KIND_WINDOW_PARTITION: + case EXPR_KIND_WINDOW_ORDER: + case EXPR_KIND_WINDOW_FRAME_RANGE: + case EXPR_KIND_WINDOW_FRAME_ROWS: + case EXPR_KIND_WINDOW_FRAME_GROUPS: + case EXPR_KIND_SELECT_TARGET: + case EXPR_KIND_UPDATE_TARGET: + case EXPR_KIND_UPDATE_SOURCE: + case EXPR_KIND_MERGE_WHEN: + case EXPR_KIND_MERGE_RETURNING: + case EXPR_KIND_GROUP_BY: + case EXPR_KIND_ORDER_BY: + case EXPR_KIND_DISTINCT_ON: + case EXPR_KIND_LIMIT: + case EXPR_KIND_OFFSET: + case EXPR_KIND_RETURNING: + case EXPR_KIND_VALUES: + case EXPR_KIND_VALUES_SINGLE: + case EXPR_KIND_PROPGRAPH_PROPERTY: + result = true; + break; + + /* session variables not allowed */ + case EXPR_KIND_INSERT_TARGET: + case EXPR_KIND_EXECUTE_PARAMETER: + case EXPR_KIND_CALL_ARGUMENT: + case EXPR_KIND_CHECK_CONSTRAINT: + case EXPR_KIND_DOMAIN_CHECK: + case EXPR_KIND_COLUMN_DEFAULT: + case EXPR_KIND_FUNCTION_DEFAULT: + case EXPR_KIND_INDEX_EXPRESSION: + case EXPR_KIND_INDEX_PREDICATE: + case EXPR_KIND_STATS_EXPRESSION: + case EXPR_KIND_TRIGGER_WHEN: + case EXPR_KIND_PARTITION_BOUND: + case EXPR_KIND_PARTITION_EXPRESSION: + case EXPR_KIND_GENERATED_COLUMN: + case EXPR_KIND_JOIN_USING: + case EXPR_KIND_CYCLE_MARK: + case EXPR_KIND_ALTER_COL_TRANSFORM: + case EXPR_KIND_POLICY: + case EXPR_KIND_COPY_WHERE: + result = false; + break; + } + + return result; +} + +static Node * +transformVariableFence(ParseState *pstate, VariableFence *vf) +{ + Param *param; + Oid typid; + int32 typmod; + Oid collid; + + /* VariableFence can be used only in context when variables are supported */ + if (!expr_kind_allows_session_variables(pstate->p_expr_kind)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("session variable reference is not supported here"), + parser_errposition(pstate, vf->location))); + + get_session_variable_type_typmod_collid(vf->varname, + &typid, &typmod, &collid); + + + param = makeNode(Param); + + param->paramkind = PARAM_VARIABLE; + param->paramvarname = pstrdup(vf->varname); + param->paramtype = typid; + param->paramtypmod = typmod; + param->paramcollid = collid; + + pstate->p_hasSessionVariables = true; + + return (Node *) param; +} + /* Test whether an a_expr is a plain NULL constant or not */ static bool exprIsNullConstant(Node *arg) diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c index 0a70d48fd4c..12f89aa2a5c 100644 --- a/src/backend/parser/parse_merge.c +++ b/src/backend/parser/parse_merge.c @@ -400,6 +400,7 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt) qry->hasTargetSRFs = false; qry->hasSubLinks = pstate->p_hasSubLinks; + qry->hasSessionVariables = pstate->p_hasSessionVariables; assign_query_collations(pstate, qry); diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 541fef5f183..d1a3ea6b1d5 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -2040,6 +2040,13 @@ FigureColnameInternal(Node *node, char **name) (int) ((JsonFuncExpr *) node)->op); } break; + case T_VariableFence: + { + /* return last field name */ + *name = ((VariableFence *) node)->varname; + return 2; + } + break; default: break; } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 7bc12589e40..d091ecb2fa5 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9360,6 +9360,14 @@ get_parameter(Param *param, deparse_context *context) } } + /* Note: can be be used by EXPLAIN */ + if (param->paramkind == PARAM_VARIABLE) + { + appendStringInfo(context->buf, "VARIABLE(%s)", + quote_identifier(param->paramvarname)); + return; + } + /* * Not PARAM_EXEC, or couldn't find referent: just print $N. * diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h index 1ed40d87a38..96be968c3d4 100644 --- a/src/include/commands/session_variable.h +++ b/src/include/commands/session_variable.h @@ -22,4 +22,9 @@ extern void CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt); extern void DropVariableByName(char *varname); +extern void get_session_variable_type_typmod_collid(char *varname, + Oid *typid, + int32 *typmod, + Oid *collid); + #endif diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index fdd2290a698..900ab7fa322 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -167,6 +167,8 @@ typedef struct Query bool hasRowSecurity pg_node_attr(query_jumble_ignore); /* parser has added an RTE_GROUP RTE */ bool hasGroupRTE pg_node_attr(query_jumble_ignore); + /* uses session variables */ + bool hasSessionVariables pg_node_attr(query_jumble_ignore); /* is a RETURN statement */ bool isReturn pg_node_attr(query_jumble_ignore); @@ -322,6 +324,16 @@ typedef struct ParamRef ParseLoc location; /* token location, or -1 if unknown */ } ParamRef; +/* + * VariableFence - ensure so fields will be interpretted as a variable + */ +typedef struct VariableFence +{ + NodeTag type; + char *varname; /* variable name */ + ParseLoc location; /* token location, or -1 if unknown */ +} VariableFence; + /* * A_Expr - infix, prefix, and postfix expressions */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index b67e56e6c5a..6d83accf985 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -379,6 +379,8 @@ typedef struct Const * of the `paramid' field contain the SubLink's subLinkId, and * the low-order 16 bits contain the column number. (This type * of Param is also converted to PARAM_EXEC during planning.) + * PARAM_VARIABLE: The parameter is a reference to a session variable + * (paramvarname holds the variable's name). */ typedef enum ParamKind { @@ -386,6 +388,7 @@ typedef enum ParamKind PARAM_EXEC, PARAM_SUBLINK, PARAM_MULTIEXPR, + PARAM_VARIABLE, } ParamKind; typedef struct Param @@ -400,6 +403,8 @@ typedef struct Param int32 paramtypmod; /* OID of collation, or InvalidOid if none */ Oid paramcollid; + /* OID of used session variable or InvalidOid if none */ + char *paramvarname pg_node_attr(query_jumble_ignore); /* token location, or -1 if unknown */ ParseLoc location; } Param; diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index fc2cbeb2083..28d9bb67511 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -244,6 +244,7 @@ struct ParseState bool p_hasTargetSRFs; bool p_hasSubLinks; bool p_hasModifyingCTE; + bool p_hasSessionVariables; Node *p_last_srf; /* most recent set-returning func/op found */ diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 65b0fd0790f..f432abaaa95 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -8355,7 +8355,8 @@ exec_is_simple_query(PLpgSQL_expr *expr) query->sortClause || query->limitOffset || query->limitCount || - query->setOperations) + query->setOperations || + query->hasSessionVariables) return false; /* diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index fb0578eec6d..a63aaf70c51 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3304,6 +3304,7 @@ ValidatorValidateCB ValuesScan ValuesScanState Var +VariableFence VarBit VarChar VarParamState -- 2.53.0