From 09263e60da9cc0f1936c1ca5ae9d3e3c4ec3d790 Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Fri, 30 May 2025 07:27:27 +0200 Subject: [PATCH 06/15] session variable fences parsing 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 | 11 ++ src/backend/catalog/namespace.c | 289 ++++++++++++++++++++++++++++ src/backend/commands/prepare.c | 9 + src/backend/nodes/nodeFuncs.c | 6 + src/backend/parser/analyze.c | 9 + src/backend/parser/gram.y | 28 ++- src/backend/parser/parse_expr.c | 208 +++++++++++++++++++- src/backend/parser/parse_merge.c | 1 + src/backend/parser/parse_target.c | 7 + src/backend/utils/adt/ruleutils.c | 46 +++++ src/backend/utils/cache/lsyscache.c | 24 +++ src/include/catalog/namespace.h | 2 + src/include/nodes/parsenodes.h | 12 ++ src/include/nodes/primnodes.h | 5 + src/include/parser/parse_node.h | 1 + src/include/utils/lsyscache.h | 4 + src/pl/plpgsql/src/pl_exec.c | 3 +- 17 files changed, 661 insertions(+), 4 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 66fa267facc..613608e620d 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -5385,6 +5385,17 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; The session variable holds value in session memory. This value is private to each session and is released when the session ends. + + + In an query the session variable can be used only inside + variable fence. This is special syntax for + session variable identifier, and can be used only for session variable + identifier. The special syntax for accessing session variables removes + risk of collisions between variable identifiers and column names. + +SELECT VARIABLE(current_user_id); + + diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 4307cad15c7..beafee2a341 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -3497,6 +3497,295 @@ NamesFromList(List *names) return result; } +/* ----- + * IdentifyVariable - try to find a variable from a list of identifiers + * + * Returns the OID of the variable found, or InvalidOid. + * + * "names" is a list of up to four identifiers; possible meanings are: + * - variable (searched on the search_path) + * - schema.variable + * - variable.attribute (searched on the search_path) + * - schema.variable.attribute + * - database.schema.variable + * - database.schema.variable.attribute + * + * If there is more than one way to identify a variable, "not_unique" will be + * set to true. + * + * Unless "noerror" is true, an error is raised if there are more than four + * identifiers in the list, or if the named database is not the current one. + * This is useful if we want to identify a shadowed variable. + * + * If an attribute is identified, it is stored in "attrname", otherwise the + * parameter is set to NULL. + * + * The identified session variable will be locked with an AccessShareLock. + * ----- + */ +Oid +IdentifyVariable(List *names, char **attrname, bool *not_unique, bool noerror) +{ + Oid varid = InvalidOid; + Oid old_varid = InvalidOid; + uint64 inval_count; + bool retry = false; + + /* + * DDL operations can change the results of a name lookup. Since all such + * operations will generate invalidation messages, we keep track of + * whether any such messages show up while we're performing the operation, + * and retry until either (1) no more invalidation messages show up or (2) + * the answer doesn't change. + */ + for (;;) + { + Node *field1 = NULL; + Node *field2 = NULL; + Node *field3 = NULL; + Node *field4 = NULL; + char *a = NULL; + char *b = NULL; + char *c = NULL; + char *d = NULL; + Oid varoid_without_attr = InvalidOid; + Oid varoid_with_attr = InvalidOid; + + *not_unique = false; + *attrname = NULL; + varid = InvalidOid; + + inval_count = SharedInvalidMessageCounter; + + switch (list_length(names)) + { + case 1: + field1 = linitial(names); + + Assert(IsA(field1, String)); + + varid = LookupVariable(NULL, strVal(field1), true); + break; + + case 2: + field1 = linitial(names); + field2 = lsecond(names); + + Assert(IsA(field1, String)); + a = strVal(field1); + + if (IsA(field2, String)) + { + /* when both fields are of string type */ + b = strVal(field2); + + /* + * a.b can mean "schema"."variable" or + * "variable"."attribute". Check both variants, and + * returns InvalidOid with not_unique flag, when both + * interpretations are possible. + */ + varoid_without_attr = LookupVariable(a, b, true); + varoid_with_attr = LookupVariable(NULL, a, true); + } + else + { + /* the last field of list can be star too */ + Assert(IsA(field2, A_Star)); + + /* + * The syntax ident.* is used only by relation aliases, + * and then this identifier cannot be a reference to + * session variable. + */ + return InvalidOid; + } + + if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr)) + { + *not_unique = true; + varid = varoid_without_attr; + } + else if (OidIsValid(varoid_without_attr)) + { + varid = varoid_without_attr; + } + else if (OidIsValid(varoid_with_attr)) + { + *attrname = b; + varid = varoid_with_attr; + } + break; + + case 3: + { + bool field1_is_catalog = false; + + field1 = linitial(names); + field2 = lsecond(names); + field3 = lthird(names); + + Assert(IsA(field1, String)); + Assert(IsA(field2, String)); + + a = strVal(field1); + b = strVal(field2); + + if (IsA(field3, String)) + { + c = strVal(field3); + + /* + * a.b.c can mean catalog.schema.variable or + * schema.variable.attribute. + * + * Check both variants, and set not_unique flag, when + * both interpretations are possible. + * + * When third node is star, only possible + * interpretation is schema.variable.*, but this + * pattern is not supported now. + */ + varoid_with_attr = LookupVariable(a, b, true); + + /* + * check pattern catalog.schema.variable only when + * there is possibility to success. + */ + if (strcmp(a, get_database_name(MyDatabaseId)) == 0) + { + field1_is_catalog = true; + varoid_without_attr = LookupVariable(b, c, true); + } + } + else + { + Assert(IsA(field3, A_Star)); + return InvalidOid; + } + + if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr)) + { + *not_unique = true; + varid = varoid_without_attr; + } + else if (OidIsValid(varoid_without_attr)) + { + varid = varoid_without_attr; + } + else if (OidIsValid(varoid_with_attr)) + { + *attrname = c; + varid = varoid_with_attr; + } + + /* + * When we didn't find variable, we can (when it is + * allowed) raise cross-database reference error. + */ + if (!OidIsValid(varid) && !noerror && !field1_is_catalog) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cross-database references are not implemented: %s", + NameListToString(names)))); + } + break; + + case 4: + { + field1 = linitial(names); + field2 = lsecond(names); + field3 = lthird(names); + field4 = lfourth(names); + + Assert(IsA(field1, String)); + Assert(IsA(field2, String)); + Assert(IsA(field3, String)); + + a = strVal(field1); + b = strVal(field2); + c = strVal(field3); + + /* + * In this case, "a" is used as catalog name - check it. + */ + if (strcmp(a, get_database_name(MyDatabaseId)) != 0) + { + if (!noerror) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cross-database references are not implemented: %s", + NameListToString(names)))); + } + + if (IsA(field4, String)) + { + d = strVal(field4); + } + else + { + Assert(IsA(field4, A_Star)); + return InvalidOid; + } + + *attrname = d; + varid = LookupVariable(b, c, true); + } + break; + + default: + if (!noerror) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("improper qualified name (too many dotted names): %s", + NameListToString(names)))); + return InvalidOid; + } + + /* + * If, upon retry, we get back the same OID we did last time, then the + * invalidation messages we processed did not change the final answer. + * So we're done. + * + * If we got a different OID, we've locked the variable that used to + * have this name rather than the one that does now. So release the + * lock. + */ + if (retry) + { + if (old_varid == varid) + break; + + if (OidIsValid(old_varid)) + UnlockDatabaseObject(VariableRelationId, old_varid, 0, AccessShareLock); + } + + /* + * Lock the variable. This will also accept any pending invalidation + * messages. If we got back InvalidOid, indicating not found, then + * there's nothing to lock, but we accept invalidation messages + * anyway, to flush any negative catcache entries that may be + * lingering. + */ + if (!OidIsValid(varid)) + AcceptInvalidationMessages(); + else + LockDatabaseObject(VariableRelationId, varid, 0, AccessShareLock); + + /* + * If no invalidation message were processed, we're done! + */ + if (inval_count == SharedInvalidMessageCounter) + break; + + retry = true; + old_varid = varid; + varid = InvalidOid; + } + + return varid; +} + /* * DeconstructQualifiedName * Given a possibly-qualified name expressed as a list of String nodes, diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 34b6410d6a2..c516e8f36b8 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -341,6 +341,15 @@ 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/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 7bc823507f1..cd609c6e479 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -1673,6 +1673,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; @@ -4705,6 +4708,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 a16fdd65601..0d5efea5ca9 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -687,6 +687,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); @@ -1112,6 +1113,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); @@ -1577,6 +1579,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) { @@ -1803,6 +1806,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); @@ -2054,6 +2058,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) { @@ -2529,6 +2534,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); @@ -2596,6 +2602,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); @@ -3072,6 +3079,8 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt) (LockingClause *) lfirst(l), false); } + qry->hasSessionVariables = pstate->p_hasSessionVariables; + assign_query_collations(pstate, qry); /* this must be done after collations, for reliable comparison of exprs */ diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 61a5f762d85..28bd26cc922 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -525,7 +525,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 in_expr 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 @@ -882,7 +882,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 /* multi-character ops and user-defined operators */ %left '+' '-' %left '*' '/' '%' @@ -15659,6 +15659,19 @@ c_expr: columnref { $$ = $1; } else $$ = $2; } + | variable_fence opt_indirection + { + if ($2) + { + A_Indirection *n = makeNode(A_Indirection); + + n->arg = (Node *) $1; + n->indirection = check_indirection($2, yyscanner); + $$ = (Node *) n; + } + else + $$ = $1; + } | case_expr { $$ = $1; } | func_expr @@ -17045,6 +17058,17 @@ case_arg: a_expr { $$ = $1; } | /*EMPTY*/ { $$ = NULL; } ; +variable_fence: + VARIABLE '(' any_name ')' + { + 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 1f8e2d54673..819ddb0557b 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -15,6 +15,7 @@ #include "postgres.h" +#include "catalog/namespace.h" #include "catalog/pg_aggregate.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" @@ -77,6 +78,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, @@ -106,7 +108,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, int location); static Node *make_nulltest_from_distinct(ParseState *pstate, A_Expr *distincta, Node *arg); - +static Node *makeParamSessionVariable(ParseState *pstate, + Oid varid, Oid typid, int32 typmod, Oid collid, + char *attrname, int location); /* * transformExpr - @@ -370,6 +374,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)); @@ -903,6 +911,135 @@ 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: + 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) +{ + Node *result; + Oid varid = InvalidOid; + char *attrname = NULL; + bool not_unique; + + /* 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))); + + /* takes an AccessShareLock on the session variable */ + varid = IdentifyVariable(vf->varname, &attrname, ¬_unique, false); + + if (not_unique) + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_PARAMETER), + errmsg("session variable reference \"%s\" is ambiguous", + NameListToString(vf->varname)), + parser_errposition(pstate, vf->location))); + + if (OidIsValid(varid)) + { + Oid typid; + int32 typmod; + Oid collid; + + get_session_variable_type_typmod_collid(varid, &typid, &typmod, + &collid); + + result = makeParamSessionVariable(pstate, + varid, typid, typmod, collid, + attrname, vf->location); + } + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("session variable \"%s\" doesn't exist", + NameListToString(vf->varname)), + parser_errposition(pstate, vf->location))); + + return result; +} + /* Test whether an a_expr is a plain NULL constant or not */ static bool exprIsNullConstant(Node *arg) @@ -3118,6 +3255,75 @@ make_nulltest_from_distinct(ParseState *pstate, A_Expr *distincta, Node *arg) return (Node *) nt; } +/* + * Generate param variable for reference to session variable + */ +static Node * +makeParamSessionVariable(ParseState *pstate, + Oid varid, Oid typid, int32 typmod, Oid collid, + char *attrname, int location) +{ + Param *param; + + param = makeNode(Param); + + param->paramkind = PARAM_VARIABLE; + param->paramvarid = varid; + param->paramtype = typid; + param->paramtypmod = typmod; + param->paramcollid = collid; + + pstate->p_hasSessionVariables = true; + + if (attrname != NULL) + { + TupleDesc tupdesc; + int i; + + tupdesc = lookup_rowtype_tupdesc_noerror(typid, typmod, true); + if (!tupdesc) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("variable \"%s.%s\" is of type \"%s\", which is not a composite type", + get_namespace_name(get_session_variable_namespace(varid)), + get_session_variable_name(varid), + format_type_be(typid)), + parser_errposition(pstate, location))); + + for (i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + + if (strcmp(attrname, NameStr(att->attname)) == 0 && + !att->attisdropped) + { + /* success, so generate a FieldSelect expression */ + FieldSelect *fselect = makeNode(FieldSelect); + + fselect->arg = (Expr *) param; + fselect->fieldnum = i + 1; + fselect->resulttype = att->atttypid; + fselect->resulttypmod = att->atttypmod; + /* save attribute's collation for parse_collate.c */ + fselect->resultcollid = att->attcollation; + + ReleaseTupleDesc(tupdesc); + return (Node *) fselect; + } + } + + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("could not identify column \"%s\" in variable \"%s.%s\"", + attrname, + get_namespace_name(get_session_variable_namespace(varid)), + get_session_variable_name(varid)), + parser_errposition(pstate, location))); + } + + return (Node *) param; +} + /* * Produce a string identifying an expression by kind. * diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c index 51d7703eff7..244efcddf32 100644 --- a/src/backend/parser/parse_merge.c +++ b/src/backend/parser/parse_merge.c @@ -405,6 +405,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 4aba0d9d4d5..cf01bff65a9 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -2034,6 +2034,13 @@ FigureColnameInternal(Node *node, char **name) (int) ((JsonFuncExpr *) node)->op); } break; + case T_VariableFence: + { + /* return last field name */ + *name = strVal(llast(((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 3d6e6bdbfd2..52b8673eed9 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -37,6 +37,7 @@ #include "catalog/pg_statistic_ext.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" +#include "catalog/pg_variable.h" #include "commands/defrem.h" #include "commands/tablespace.h" #include "common/keywords.h" @@ -534,6 +535,7 @@ static char *generate_function_name(Oid funcid, int nargs, static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2); static void add_cast_to(StringInfo buf, Oid typid); static char *generate_qualified_type_name(Oid typid); +static char *generate_session_variable_name(Oid varid); static text *string_to_text(char *str); static char *flatten_reloptions(Oid relid); static void get_reloptions(StringInfo buf, Datum reloptions); @@ -8803,6 +8805,14 @@ get_parameter(Param *param, deparse_context *context) } } + /* translate paramvarid to session variable name */ + if (param->paramkind == PARAM_VARIABLE) + { + appendStringInfo(context->buf, "VARIABLE(%s)", + generate_session_variable_name(param->paramvarid)); + return; + } + /* * Not PARAM_EXEC, or couldn't find referent: just print $N. * @@ -13566,6 +13576,42 @@ generate_collation_name(Oid collid) return result; } +/* + * generate_session_variable_name + * Compute the name to display for a session variable specified by OID + * + * The result includes all necessary quoting and schema-prefixing. + */ +static char * +generate_session_variable_name(Oid varid) +{ + HeapTuple tup; + Form_pg_variable varform; + char *varname; + char *nspname; + char *result; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid)); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for variable %u", varid); + + varform = (Form_pg_variable) GETSTRUCT(tup); + + varname = NameStr(varform->varname); + + if (!VariableIsVisible(varid)) + nspname = get_namespace_name_or_temp(varform->varnamespace); + else + nspname = NULL; + + result = quote_qualified_identifier(nspname, varname); + + ReleaseSysCache(tup); + + return result; +} + /* * Given a C string, produce a TEXT datum. * diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index fdb7eaef75a..2c1488a4ffa 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -3919,3 +3919,27 @@ get_session_variable_namespace(Oid varid) return varnamespace; } + +/* + * Returns the type, typmod and collid of the given session variable. + */ +void +get_session_variable_type_typmod_collid(Oid varid, Oid *typid, int32 *typmod, + Oid *collid) +{ + HeapTuple tup; + Form_pg_variable varform; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid)); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for session variable %u", varid); + + varform = (Form_pg_variable) GETSTRUCT(tup); + + *typid = varform->vartype; + *typmod = varform->vartypmod; + *collid = varform->varcollation; + + ReleaseSysCache(tup); +} diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index bdac0c13bec..381c8e74fb8 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -98,6 +98,8 @@ extern Oid TypenameGetTypidExtended(const char *typname, bool temp_ok); extern bool TypeIsVisible(Oid typid); extern bool VariableIsVisible(Oid varid); +extern Oid IdentifyVariable(List *names, char **attrname, + bool *not_unique, bool noerror); extern FuncCandidateList FuncnameGetCandidates(List *names, int nargs, List *argnames, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 191713de7f4..76cde246f63 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); @@ -321,6 +323,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; + List *varname; /* variable name (String nodes) */ + 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 7d3b4198f26..2b1b3ac8a33 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -378,6 +378,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 + * (paramvarid holds the variable's OID). */ typedef enum ParamKind { @@ -385,6 +387,7 @@ typedef enum ParamKind PARAM_EXEC, PARAM_SUBLINK, PARAM_MULTIEXPR, + PARAM_VARIABLE, } ParamKind; typedef struct Param @@ -397,6 +400,8 @@ typedef struct Param int32 paramtypmod pg_node_attr(query_jumble_ignore); /* OID of collation, or InvalidOid if none */ Oid paramcollid pg_node_attr(query_jumble_ignore); + /* OID of used session variable or InvalidOid if none */ + Oid paramvarid 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 994284019fb..cb4eb8418b7 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/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 87e1fe636b9..f5160de67d9 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -214,6 +214,10 @@ extern char *get_subscription_name(Oid subid, bool missing_ok); extern char *get_session_variable_name(Oid varid); extern Oid get_session_variable_namespace(Oid varid); +extern void get_session_variable_type_typmod_collid(Oid varid, + Oid *typid, + int32 *typmod, + Oid *collid); #define type_is_array(typid) (get_element_type(typid) != InvalidOid) /* type_is_array_domain accepts both plain arrays and domains over arrays */ diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index bb99781c56e..1cff5efa6f7 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -8264,7 +8264,8 @@ exec_is_simple_query(PLpgSQL_expr *expr) query->sortClause || query->limitOffset || query->limitCount || - query->setOperations) + query->setOperations || + query->hasSessionVariables) return false; /* -- 2.49.0