Skip to content

Commit d5b7c3b

Browse files
okbob@github.comCommitfest Bot
authored and
Commitfest Bot
committed
variable fence syntax support and variable fence usage guard support
this patch introduces a concept of variable fence - syntax for variable reference `VARIABLE(varname)` that is not in collision with column reference. When variable fence usage guard warning is active, then usage variable without variable fence in the case, where there can be column references, the the warning is raised. initial implementation of variable fence
1 parent 4652e2b commit d5b7c3b

File tree

14 files changed

+323
-7
lines changed

14 files changed

+323
-7
lines changed

doc/src/sgml/config.sgml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11434,6 +11434,71 @@ DETAIL: Session variables can be shadowed by columns, routine's variables and r
1143411434
</listitem>
1143511435
</varlistentry>
1143611436

11437+
<varlistentry id="guc-session-variables-use-fence-warning-guard" xreflabel="session_variables_use_fence_warning_guard">
11438+
<term><varname>session_variables_use_fence_warning_guard</varname> (<type>boolean</type>)
11439+
<indexterm>
11440+
<primary><varname>session_variables_use_fence_warning_guard</varname> configuration parameter</primary>
11441+
</indexterm>
11442+
</term>
11443+
<listitem>
11444+
<para>
11445+
When on, a warning is raised when a session variable identifier is used
11446+
inside a query without variable fence. The default is <literal>off</literal>.
11447+
The warning is raised only when variable is used in places, where an
11448+
collisions with column names is possible.
11449+
<programlisting>
11450+
CREATE TABLE foo(a int);
11451+
INSERT INTO foo VALUES(10);
11452+
CREATE VARIABLE b int;
11453+
LET b = 100;
11454+
SELECT a, b FROM foo;
11455+
</programlisting>
11456+
11457+
<screen>
11458+
a | b
11459+
----+-----
11460+
10 | 100
11461+
(1 row)
11462+
</screen>
11463+
11464+
<programlisting>
11465+
SET session_variables_use_fence_warning_guard TO on;
11466+
SELECT a, b FROM foo;
11467+
</programlisting>
11468+
11469+
<screen>
11470+
WARNING: session variable "b" is not used inside variable fence
11471+
LINE 1: SELECT a, b FROM foo;
11472+
^
11473+
DETAIL: The collision of session variable' names and column names is possible.
11474+
a | b
11475+
----+-----
11476+
10 | 100
11477+
(1 row)
11478+
</screen>
11479+
11480+
<programlisting>
11481+
SELECT a, VARIABLE(b) FROM foo;
11482+
</programlisting>
11483+
11484+
<screen>
11485+
a | b
11486+
----+-----
11487+
10 | 100
11488+
(1 row)
11489+
</screen>
11490+
</para>
11491+
11492+
<para>
11493+
This feature can significantly increase log size, so it's disabled by
11494+
default. Unless another collision resolution technique is used
11495+
(dedicated schema or using prefixes like <literal>_</literal>),
11496+
the use of variable fence syntax is strongly recommended, and this
11497+
warning should be enabled.
11498+
</para>
11499+
</listitem>
11500+
</varlistentry>
11501+
1143711502
<varlistentry id="guc-standard-conforming-strings" xreflabel="standard_conforming_strings">
1143811503
<term><varname>standard_conforming_strings</varname> (<type>boolean</type>)
1143911504
<indexterm><primary>strings</primary><secondary>standard conforming</secondary></indexterm>

doc/src/sgml/ddl.sgml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5457,6 +5457,21 @@ SELECT name FROM foo;
54575457
<literal>schema.variable</literal>. It is strongly recommended to rename
54585458
shadowed variables or use qualified names always.
54595459
</para>
5460+
5461+
<para><firstterm>Variable fence</firstterm> is special syntax for session
5462+
variable identifier. Only name or qualified name can be used inside the
5463+
variable fence, and this name is used as only session variable identifier.
5464+
<programlisting>
5465+
SELECT VARIABLE(current_user_id);
5466+
</programlisting>
5467+
</para>
5468+
5469+
<para>
5470+
When there is a risk of possible collisions between variable identifiers
5471+
and column names, then using variable fence syntax can be recommended.
5472+
Usage session variable without variable fence can be detected by
5473+
warning <xref linkend="guc-session-variables-use-fence-warning-guard"/>.
5474+
</para>
54605475
</sect1>
54615476

54625477
<sect1 id="ddl-others">

src/backend/nodes/nodeFuncs.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,9 @@ exprLocation(const Node *expr)
16731673
case T_ParamRef:
16741674
loc = ((const ParamRef *) expr)->location;
16751675
break;
1676+
case T_VariableFence:
1677+
loc = ((const VariableFence *) expr)->location;
1678+
break;
16761679
case T_A_Const:
16771680
loc = ((const A_Const *) expr)->location;
16781681
break;
@@ -4715,6 +4718,9 @@ raw_expression_tree_walker_impl(Node *node,
47154718
return true;
47164719
}
47174720
break;
4721+
case T_VariableFence:
4722+
/* we assume the fields contain nothing interesting */
4723+
break;
47184724
default:
47194725
elog(ERROR, "unrecognized node type: %d",
47204726
(int) nodeTag(node));

src/backend/parser/gram.y

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
525525
%type <node> def_arg columnElem where_clause where_or_current_clause
526526
a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
527527
columnref in_expr having_clause func_table xmltable array_expr
528-
OptWhereClause operator_def_arg
528+
OptWhereClause operator_def_arg variable_fence
529529
%type <list> opt_column_and_period_list
530530
%type <list> rowsfrom_item rowsfrom_list opt_col_def_list
531531
%type <boolean> opt_ordinality opt_without_overlaps
@@ -882,7 +882,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
882882
*/
883883
%nonassoc UNBOUNDED NESTED /* ideally would have same precedence as IDENT */
884884
%nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
885-
SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH
885+
SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH VARIABLE
886886
%left Op OPERATOR /* multi-character ops and user-defined operators */
887887
%left '+' '-'
888888
%left '*' '/' '%'
@@ -15698,6 +15698,19 @@ c_expr: columnref { $$ = $1; }
1569815698
else
1569915699
$$ = $2;
1570015700
}
15701+
| variable_fence opt_indirection
15702+
{
15703+
if ($2)
15704+
{
15705+
A_Indirection *n = makeNode(A_Indirection);
15706+
15707+
n->arg = (Node *) $1;
15708+
n->indirection = check_indirection($2, yyscanner);
15709+
$$ = (Node *) n;
15710+
}
15711+
else
15712+
$$ = $1;
15713+
}
1570115714
| case_expr
1570215715
{ $$ = $1; }
1570315716
| func_expr
@@ -17084,6 +17097,17 @@ case_arg: a_expr { $$ = $1; }
1708417097
| /*EMPTY*/ { $$ = NULL; }
1708517098
;
1708617099

17100+
variable_fence:
17101+
VARIABLE '(' any_name ')'
17102+
{
17103+
VariableFence *vf = makeNode(VariableFence);
17104+
17105+
vf->varname = $3;
17106+
vf->location = @3;
17107+
$$ = (Node *) vf;
17108+
}
17109+
;
17110+
1708717111
columnref: ColId
1708817112
{
1708917113
$$ = makeColumnRef($1, NIL, @1, yyscanner);

src/backend/parser/parse_expr.c

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
/* GUC parameters */
4747
bool Transform_null_equals = false;
4848
bool session_variables_ambiguity_warning = false;
49+
bool session_variables_use_fence_warning_guard = false;
4950

5051

5152
static Node *transformExprRecurse(ParseState *pstate, Node *expr);
@@ -81,6 +82,7 @@ static Node *transformWholeRowRef(ParseState *pstate,
8182
static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
8283
static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
8384
static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
85+
static Node *transformVariableFence(ParseState *pstate, VariableFence *vf);
8486
static Node *transformJsonObjectConstructor(ParseState *pstate,
8587
JsonObjectConstructor *ctor);
8688
static Node *transformJsonArrayConstructor(ParseState *pstate,
@@ -112,7 +114,7 @@ static Node *make_nulltest_from_distinct(ParseState *pstate,
112114
A_Expr *distincta, Node *arg);
113115
static Node *makeParamSessionVariable(ParseState *pstate,
114116
Oid varid, Oid typid, int32 typmod, Oid collid,
115-
char *attrname, int location);
117+
char *attrname, bool fenced, int location);
116118

117119

118120
/*
@@ -377,6 +379,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
377379
result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr);
378380
break;
379381

382+
case T_VariableFence:
383+
result = transformVariableFence(pstate, (VariableFence *) expr);
384+
break;
385+
380386
default:
381387
/* should not reach here */
382388
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -1028,7 +1034,20 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
10281034

10291035
node = makeParamSessionVariable(pstate,
10301036
varid, typid, typmod, collid,
1031-
attrname, cref->location);
1037+
attrname, false, cref->location);
1038+
1039+
/*
1040+
* The variable is not inside variable's fence, so raise warning
1041+
* when variable fence guard is active and the query has FROM
1042+
* clause.
1043+
*/
1044+
if (session_variables_use_fence_warning_guard && pstate->p_rtable)
1045+
ereport(WARNING,
1046+
(errcode(ERRCODE_AMBIGUOUS_COLUMN),
1047+
errmsg("session variable \"%s\" is not used inside variable fence",
1048+
NameListToString(cref->fields)),
1049+
errdetail("The collision of session variable' names and column names is possible."),
1050+
parser_errposition(pstate, cref->location)));
10321051
}
10331052
}
10341053
}
@@ -1071,14 +1090,15 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
10711090
static Node *
10721091
makeParamSessionVariable(ParseState *pstate,
10731092
Oid varid, Oid typid, int32 typmod, Oid collid,
1074-
char *attrname, int location)
1093+
char *attrname, bool fenced, int location)
10751094
{
10761095
Param *param;
10771096

10781097
param = makeNode(Param);
10791098

10801099
param->paramkind = PARAM_VARIABLE;
10811100
param->paramvarid = varid;
1101+
param->paramvarfenced = fenced;
10821102
param->paramtype = typid;
10831103
param->paramtypmod = typmod;
10841104
param->paramcollid = collid;
@@ -1157,6 +1177,54 @@ transformParamRef(ParseState *pstate, ParamRef *pref)
11571177
return result;
11581178
}
11591179

1180+
static Node *
1181+
transformVariableFence(ParseState *pstate, VariableFence *vf)
1182+
{
1183+
Node *result;
1184+
Oid varid = InvalidOid;
1185+
char *attrname = NULL;
1186+
bool not_unique;
1187+
1188+
/* VariableFence can be used only in context when variables are supported */
1189+
if (!expr_kind_allows_session_variables(pstate->p_expr_kind))
1190+
ereport(ERROR,
1191+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1192+
errmsg("session variable reference is not supported here"),
1193+
parser_errposition(pstate, vf->location)));
1194+
1195+
/* takes an AccessShareLock on the session variable */
1196+
varid = IdentifyVariable(vf->varname, &attrname, &not_unique, false);
1197+
1198+
if (not_unique)
1199+
ereport(ERROR,
1200+
(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
1201+
errmsg("session variable reference \"%s\" is ambiguous",
1202+
NameListToString(vf->varname)),
1203+
parser_errposition(pstate, vf->location)));
1204+
1205+
if (OidIsValid(varid))
1206+
{
1207+
Oid typid;
1208+
int32 typmod;
1209+
Oid collid;
1210+
1211+
get_session_variable_type_typmod_collid(varid, &typid, &typmod,
1212+
&collid);
1213+
1214+
result = makeParamSessionVariable(pstate,
1215+
varid, typid, typmod, collid,
1216+
attrname, true, vf->location);
1217+
}
1218+
else
1219+
ereport(ERROR,
1220+
(errcode(ERRCODE_UNDEFINED_OBJECT),
1221+
errmsg("session variable \"%s\" doesn't exist",
1222+
NameListToString(vf->varname)),
1223+
parser_errposition(pstate, vf->location)));
1224+
1225+
return result;
1226+
}
1227+
11601228
/* Test whether an a_expr is a plain NULL constant or not */
11611229
static bool
11621230
exprIsNullConstant(Node *arg)

src/backend/parser/parse_target.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2034,6 +2034,13 @@ FigureColnameInternal(Node *node, char **name)
20342034
(int) ((JsonFuncExpr *) node)->op);
20352035
}
20362036
break;
2037+
case T_VariableFence:
2038+
{
2039+
/* return last field name */
2040+
*name = strVal(llast(((VariableFence *) node)->varname));
2041+
return 2;
2042+
}
2043+
break;
20372044
default:
20382045
break;
20392046
}

src/backend/utils/adt/ruleutils.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8744,8 +8744,16 @@ get_parameter(Param *param, deparse_context *context)
87448744
/* translate paramvarid to session variable name */
87458745
if (param->paramkind == PARAM_VARIABLE)
87468746
{
8747-
appendStringInfo(context->buf, "%s",
8748-
generate_session_variable_name(param->paramvarid));
8747+
if (param->paramvarfenced)
8748+
{
8749+
appendStringInfo(context->buf, "VARIABLE(%s)",
8750+
generate_session_variable_name(param->paramvarid));
8751+
}
8752+
else
8753+
{
8754+
appendStringInfo(context->buf, "%s",
8755+
generate_session_variable_name(param->paramvarid));
8756+
}
87498757
return;
87508758
}
87518759

src/backend/utils/misc/guc_tables.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,15 @@ struct config_bool ConfigureNamesBool[] =
16531653
false,
16541654
NULL, NULL, NULL
16551655
},
1656+
{
1657+
{"session_variables_use_fence_warning_guard", PGC_USERSET, CLIENT_CONN_STATEMENT,
1658+
gettext_noop("Raise a warning when variable is not used inside variable fence."),
1659+
NULL
1660+
},
1661+
&session_variables_use_fence_warning_guard,
1662+
false,
1663+
NULL, NULL, NULL
1664+
},
16561665
{
16571666
{"default_transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT,
16581667
gettext_noop("Sets the default read-only status of new transactions."),

src/backend/utils/misc/postgresql.conf.sample

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,7 @@ autovacuum_worker_slots = 16 # autovacuum worker slots to allocate
765765
#default_transaction_deferrable = off
766766
#session_replication_role = 'origin'
767767
#session_variables_ambiguity_warning = off
768+
#session_variables_use_fence_warning_guard = off
768769
#statement_timeout = 0 # in milliseconds, 0 is disabled
769770
#transaction_timeout = 0 # in milliseconds, 0 is disabled
770771
#lock_timeout = 0 # in milliseconds, 0 is disabled

src/include/nodes/parsenodes.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,16 @@ typedef struct ParamRef
321321
ParseLoc location; /* token location, or -1 if unknown */
322322
} ParamRef;
323323

324+
/*
325+
* VariableFence - ensure so fields will be interpretted as a variable
326+
*/
327+
typedef struct VariableFence
328+
{
329+
NodeTag type;
330+
List *varname; /* variable name (String nodes) */
331+
ParseLoc location; /* token location, or -1 if unknown */
332+
} VariableFence;
333+
324334
/*
325335
* A_Expr - infix, prefix, and postfix expressions
326336
*/

src/include/nodes/primnodes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,8 @@ typedef struct Param
411411
* param is used just for execution of UPDATE operation.
412412
*/
413413
bool parambasenode;
414+
/* true when variable is used inside an fence */
415+
bool paramvarfenced;
414416
/* token location, or -1 if unknown */
415417
ParseLoc location;
416418
} Param;

src/include/parser/parse_expr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
/* GUC parameters */
1919
extern PGDLLIMPORT bool Transform_null_equals;
2020
extern PGDLLIMPORT bool session_variables_ambiguity_warning;
21+
extern PGDLLIMPORT bool session_variables_use_fence_warning_guard;
2122

2223
extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKind);
2324

0 commit comments

Comments
 (0)