proposal: plpgsql, new check for extra_errors - strict_expr_check
Hi,
assigned patch try to solve issue reported by Mor Lehr (Missing semicolon
in anonymous plpgsql block does not raise syntax error).
/messages/by-id/CALyvM2bp_CXMH_Gyq87pmHJRuZDEhV40u9VP8rX=CAnEt2wUXg@mail.gmail.com
by introducing a new extra error check. With this check only a_expr exprs
are allowed as plpgsql expressions. This is a small step to behaviour
described in SQL/PSM standard (although the language is different, the
expression syntax and features are almost similar. With this check the
undocumented (but supported syntax)
var := column FROM tab
is disallowed. Only ANSI syntax for embedded queries (inside assignment
statement) is allowed
var := (SELECT column FROM tab);
With this check, the reported issue (by Mor Lehr) is detected
default setting
CREATE TABLE foo3(id serial PRIMARY key, txt text);
INSERT INTO foo3 (txt) VALUES ('aaa'),('bbb');
DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$
-- without reaction - just don't work
(2024-06-16 16:05:55) postgres=# set plpgsql.extra_errors to
'strict_expr_check';
SET
(2024-06-16 16:06:43) postgres=# DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
ERROR: syntax error at or near "DELETE"
LINE 11: DELETE FROM foo3 WHERE id=1;
^
This patch has three parts
1. Introduction strict_expr_check
2. set strict_expr_check as default, and impact on regress tests
3. revert @2
I don't propose to be strict_expr_check active by default.
Comments, notes?
Regards
Pavel
Attachments:
0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchtext/x-patch; charset=US-ASCII; name=0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchDownload
From 51a8cedad8cb3ddbb8a6f5828def01b4e2bb1cac Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.com>
Date: Wed, 12 Jun 2024 21:34:05 +0200
Subject: [PATCH 1/3] use strict rules for parsing PL/pgSQL expressions
Originally the rule PLpgSQL_Expr allows almost all SQL clauses. It was designed
to allow old undocumented syntax
var := col FROM tab;
The reason for support of this "strange" syntax was technical. The PLpgSQL parser
cannot use SQL parser accurately (it was really primitive), and people found
this undocumented syntax. Lattery, when it was possible to do exact parsing, from
compatibility reasons, the parsing of PL/pgSQL expressions allows described syntax.
Unfortunately, with support almost all SQL clauses, the PLpgSQL can accept
really broken code like
DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
proposed patch introduce new extra error check strict_expr_check, that solve
this issue.
---
doc/src/sgml/plpgsql.sgml | 18 ++++
src/backend/executor/spi.c | 7 ++
src/backend/parser/gram.y | 149 +++++++++++++++++++++++++++
src/backend/parser/parser.c | 6 ++
src/include/parser/parser.h | 22 ++++
src/interfaces/ecpg/preproc/parse.pl | 10 +-
src/pl/plpgsql/src/pl_comp.c | 31 ++++++
src/pl/plpgsql/src/pl_gram.y | 48 ++++++---
src/pl/plpgsql/src/pl_handler.c | 2 +
src/pl/plpgsql/src/plpgsql.h | 19 ++++
10 files changed, 298 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 6f880b705f..e4b5febf15 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5347,6 +5347,24 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="plpgsql-extra-checks-strict-expr-check">
+ <term><varname>strict_expr_check</varname></term>
+ <listitem>
+ <para>
+ Enabling this check will cause <application>PL/pgSQL</application> to
+ check if a <application>PL/pgSQL</application> expression is just an
+ expression without any SQL clauses like <literal>FROM</literal>,
+ <literal>ORDER BY</literal>. This undocumented form of expressions
+ is allowed for compatibility reasons, but in some special cases
+ it doesn't to allow to detect broken code.
+ </para>
+
+ <para>
+ This check is allowed only <varname>plpgsql.extra_errors</varname>.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
The following example shows the effect of <varname>plpgsql.extra_warnings</varname>
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index e516c0a67c..94a48b837d 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2976,11 +2976,18 @@ _SPI_error_callback(void *arg)
switch (carg->mode)
{
case RAW_PARSE_PLPGSQL_EXPR:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST:
+ case RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST:
+ /* ToDo: fix to PL/pgSQL expression - in another patch */
errcontext("SQL expression \"%s\"", query);
break;
case RAW_PARSE_PLPGSQL_ASSIGN1:
case RAW_PARSE_PLPGSQL_ASSIGN2:
case RAW_PARSE_PLPGSQL_ASSIGN3:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3:
errcontext("PL/pgSQL assignment \"%s\"", query);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4d582950b7..bbc13a1a58 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -324,6 +324,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
PLpgSQL_Expr PLAssignStmt
+ PLpgSQLStrictExpr PLpgSQLStrictExprs PLpgSQLStrictNamedExprs
+ PLAssignStmtStrictExpr
+
+%type <target> plpgsql_strict_expr plpgsql_strict_named_expr
+%type <list> plpgsql_strict_expr_list plpgsql_strict_named_expr_list
%type <str> opt_single_name
%type <list> opt_qualified_name
@@ -827,6 +832,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%token MODE_PLPGSQL_ASSIGN1
%token MODE_PLPGSQL_ASSIGN2
%token MODE_PLPGSQL_ASSIGN3
+%token MODE_PLPGSQL_STRICT_EXPR
+%token MODE_PLPGSQL_STRICT_EXPR_LIST
+%token MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST
+%token MODE_PLPGSQL_STRICT_EXPR_ASSIGN1
+%token MODE_PLPGSQL_STRICT_EXPR_ASSIGN2
+%token MODE_PLPGSQL_STRICT_EXPR_ASSIGN3
/* Precedence: lowest to highest */
@@ -958,6 +969,46 @@ parse_toplevel:
pg_yyget_extra(yyscanner)->parsetree =
list_make1(makeRawStmt((Node *) n, 0));
}
+ | MODE_PLPGSQL_STRICT_EXPR PLpgSQLStrictExpr
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_LIST PLpgSQLStrictExprs
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST PLpgSQLStrictNamedExprs
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_ASSIGN1 PLAssignStmtStrictExpr
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+
+ n->nnames = 1;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_ASSIGN2 PLAssignStmtStrictExpr
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+
+ n->nnames = 2;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_ASSIGN3 PLAssignStmtStrictExpr
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+
+ n->nnames = 3;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+
;
/*
@@ -17496,6 +17547,104 @@ plassign_equals: COLON_EQUALS
| '='
;
+/*
+ * In "strict" mode plpgsql expressions are just an a_expr. From compatibility
+ * reasons (with default mode) it returns SelectStmt still.
+ */
+PLpgSQLStrictExpr: a_expr
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ ResTarget *rt = makeNode(ResTarget);
+
+ rt->name = NULL;
+ rt->indirection = NIL;
+ rt->val = (Node *) $1;
+ rt->location = @1;
+
+ n->targetList = list_make1((Node *) rt);
+ $$ = (Node *) n;
+ }
+ ;
+
+PLAssignStmtStrictExpr: plassign_target opt_indirection plassign_equals PLpgSQLStrictExpr
+ {
+ PLAssignStmt *n = makeNode(PLAssignStmt);
+
+ n->name = $1;
+ n->indirection = check_indirection($2, yyscanner);
+ /* nnames will be filled by calling production */
+ n->val = (SelectStmt *) $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+/*
+ * Used for unnamed plpgsql cursor's argument and plpgsql case in
+ * "strict" mode.
+ */
+PLpgSQLStrictExprs: plpgsql_strict_expr_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $1;
+ $$ = (Node *) n;
+ }
+ ;
+
+plpgsql_strict_expr_list:
+ plpgsql_strict_expr
+ {
+ $$ = list_make1($1);
+ }
+ | plpgsql_strict_expr_list ',' plpgsql_strict_expr
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
+plpgsql_strict_expr: a_expr
+ {
+ $$ = makeNode(ResTarget);
+ $$->name = NULL;
+ $$->indirection = NIL;
+ $$->val = (Node *) $1;
+ $$->location = @1;
+ }
+ ;
+
+/*
+ * Used for named cursor's arguments in "strict" mode
+ */
+PLpgSQLStrictNamedExprs: plpgsql_strict_named_expr_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $1;
+ $$ = (Node *) n;
+ }
+ ;
+
+plpgsql_strict_named_expr_list:
+ plpgsql_strict_named_expr
+ {
+ $$ = list_make1($1);
+ }
+ | plpgsql_strict_named_expr_list ',' plpgsql_strict_named_expr
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
+plpgsql_strict_named_expr: a_expr AS ColLabel
+ {
+ $$ = makeNode(ResTarget);
+ $$->name = $3;
+ $$->indirection = NIL;
+ $$->val = (Node *) $1;
+ $$->location = @1;
+ }
+ ;
/*
* Name classification hierarchy.
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 118488c3f3..9d2dde6793 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -62,6 +62,12 @@ raw_parser(const char *str, RawParseMode mode)
[RAW_PARSE_PLPGSQL_ASSIGN1] = MODE_PLPGSQL_ASSIGN1,
[RAW_PARSE_PLPGSQL_ASSIGN2] = MODE_PLPGSQL_ASSIGN2,
[RAW_PARSE_PLPGSQL_ASSIGN3] = MODE_PLPGSQL_ASSIGN3,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR] = MODE_PLPGSQL_STRICT_EXPR,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST] = MODE_PLPGSQL_STRICT_EXPR_LIST,
+ [RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST] = MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN3,
};
yyextra.have_lookahead = true;
diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
index be184ec506..8e41907bd4 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -33,6 +33,22 @@
* RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement,
* and return a one-element List containing a RawStmt node. "n"
* gives the number of dotted names comprising the target ColumnRef.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR: parse a PL/pgSQL expression, and
+ * return a one-element List containing a RwaStmt node. The result is
+ * compatible with RAW_PARSE_PLPGSQL_EXPR, but parser allows only
+ * a_expr (instead almost all complete query).
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST: parse a comma separated list
+ * of PL/pgSQL expressions (only a_expr are allowed). It is used by
+ * PLpGSQL CASE and OPEN commands.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST: parse a comma separated
+ * list of a_expr node with labels. It is used for evaluation of
+ * named arguments of PLpgSQL OPEN (cursor) statement.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGNn: parse a PL/pgSQL assignment
+ * statement, but only a_expr are allowed).
*/
typedef enum
{
@@ -42,6 +58,12 @@ typedef enum
RAW_PARSE_PLPGSQL_ASSIGN1,
RAW_PARSE_PLPGSQL_ASSIGN2,
RAW_PARSE_PLPGSQL_ASSIGN3,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3,
} RawParseMode;
/* Values for the backslash_quote GUC */
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index fe8d3e5178..433f406778 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -89,7 +89,15 @@ my %replace_types = (
'PLpgSQL_Expr' => 'ignore',
'PLAssignStmt' => 'ignore',
'plassign_target' => 'ignore',
- 'plassign_equals' => 'ignore',);
+ 'plassign_equals' => 'ignore',
+ 'plpgsql_strict_expr' => 'ignore',
+ 'plpgsql_strict_named_expr' => 'ignore',
+ 'plpgsql_strict_expr_list' => 'ignore',
+ 'plpgsql_strict_named_expr_list' => 'ignore',
+ 'PLpgSQLStrictExpr' => 'ignore',
+ 'PLpgSQLStrictExprs' => 'ignore',
+ 'PLpgSQLStrictNamedExprs' => 'ignore',
+ 'PLAssignStmtStrictExpr' => 'ignore',);
# these replace_line commands excise certain keywords from the core keyword
# lists. Be sure to account for these in ColLabel and related productions.
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index f1bce708d6..8f9d3ed64d 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -84,6 +84,23 @@ static const ExceptionLabelMap exception_label_map[] = {
{NULL, 0}
};
+static const PLpgSQL_parse_modes default_parse_modes = {
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_ASSIGN1,
+ RAW_PARSE_PLPGSQL_ASSIGN2,
+ RAW_PARSE_PLPGSQL_ASSIGN3
+};
+
+static const PLpgSQL_parse_modes strict_expr_parse_modes = {
+ RAW_PARSE_PLPGSQL_STRICT_EXPR,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3
+};
/* ----------
* static prototypes
@@ -355,6 +372,11 @@ do_compile(FunctionCallInfo fcinfo,
function->extra_warnings = forValidator ? plpgsql_extra_warnings : 0;
function->extra_errors = forValidator ? plpgsql_extra_errors : 0;
+ if (function->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->pmodes = &strict_expr_parse_modes;
+ else
+ function->pmodes = &default_parse_modes;
+
if (is_dml_trigger)
function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
else if (is_event_trigger)
@@ -898,6 +920,15 @@ plpgsql_compile_inline(char *proc_source)
function->extra_warnings = 0;
function->extra_errors = 0;
+ /*
+ * Although function->extra_errors is disabled, we want to
+ * do strict_expr_check inside annoymous block too.
+ */
+ if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->pmodes = &strict_expr_parse_modes;
+ else
+ function->pmodes = &default_parse_modes;
+
function->nstatements = 0;
function->requires_procedure_resowner = false;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index a29d2dfacd..d86792195d 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -178,6 +178,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <expr> expr_until_semi
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
+%type <expr> expressions_until_then
%type <var> cursor_variable
%type <datum> decl_cursor_arg
@@ -957,13 +958,13 @@ stmt_assign : T_DATUM
switch ($1.ident ? 1 : list_length($1.idents))
{
case 1:
- pmode = RAW_PARSE_PLPGSQL_ASSIGN1;
+ pmode = plpgsql_curr_compile->pmodes->pmode_assign1;
break;
case 2:
- pmode = RAW_PARSE_PLPGSQL_ASSIGN2;
+ pmode = plpgsql_curr_compile->pmodes->pmode_assign2;
break;
case 3:
- pmode = RAW_PARSE_PLPGSQL_ASSIGN3;
+ pmode = plpgsql_curr_compile->pmodes->pmode_assign3;
break;
default:
elog(ERROR, "unexpected number of names");
@@ -1239,7 +1240,7 @@ case_when_list : case_when_list case_when
}
;
-case_when : K_WHEN expr_until_then proc_sect
+case_when : K_WHEN expressions_until_then proc_sect
{
PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
@@ -1269,6 +1270,14 @@ opt_case_else :
}
;
+expressions_until_then :
+ {
+ $$ = read_sql_construct(K_THEN, 0, 0, "THEN",
+ plpgsql_curr_compile->pmodes->pmode_expr_list,
+ true, true, NULL, NULL);
+ }
+ ;
+
stmt_loop : opt_loop_label K_LOOP loop_body
{
PLpgSQL_stmt_loop *new;
@@ -1488,7 +1497,8 @@ for_control : for_variable K_IN
* Relabel first expression as an expression;
* then we can check its syntax.
*/
- expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ expr1->parseMode = plpgsql_curr_compile->pmodes->pmode_expr;
+
check_sql_expr(expr1->query, expr1->parseMode,
expr1loc);
@@ -1858,6 +1868,8 @@ stmt_raise : K_RAISE
*/
if (tok == SCONST)
{
+ RawParseMode pmode_expr;
+
/* old style message and parameters */
new->message = yylval.str;
/*
@@ -1870,13 +1882,15 @@ stmt_raise : K_RAISE
if (tok != ',' && tok != ';' && tok != K_USING)
yyerror("syntax error");
+ pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
+
while (tok == ',')
{
PLpgSQL_expr *expr;
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &tok);
new->params = lappend(new->params, expr);
@@ -2010,10 +2024,13 @@ stmt_dynexecute : K_EXECUTE
PLpgSQL_stmt_dynexecute *new;
PLpgSQL_expr *expr;
int endtoken;
+ RawParseMode pmode_expr;
+
+ pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &endtoken);
@@ -2052,7 +2069,7 @@ stmt_dynexecute : K_EXECUTE
{
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &endtoken);
new->params = lappend(new->params, expr);
@@ -2637,7 +2654,7 @@ static PLpgSQL_expr *
read_sql_expression(int until, const char *expected)
{
return read_sql_construct(until, 0, 0, expected,
- RAW_PARSE_PLPGSQL_EXPR,
+ plpgsql_curr_compile->pmodes->pmode_expr,
true, true, NULL, NULL);
}
@@ -2647,7 +2664,7 @@ read_sql_expression2(int until, int until2, const char *expected,
int *endtoken)
{
return read_sql_construct(until, until2, 0, expected,
- RAW_PARSE_PLPGSQL_EXPR,
+ plpgsql_curr_compile->pmodes->pmode_expr,
true, true, NULL, endtoken);
}
@@ -3840,6 +3857,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
char **argv;
StringInfoData ds;
bool any_named = false;
+ RawParseMode pmode_expr;
tok = yylex();
if (cursor->cursor_explicit_argrow < 0)
@@ -3866,6 +3884,8 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
cursor->refname),
parser_errposition(yylloc)));
+ pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
+
/*
* Read the arguments, one by one.
*/
@@ -3936,7 +3956,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
*/
item = read_sql_construct(',', ')', 0,
",\" or \")",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &endtoken);
@@ -3977,7 +3997,9 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
- expr->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ expr->parseMode = any_named ?
+ plpgsql_curr_compile->pmodes->pmode_named_expr_list
+ : plpgsql_curr_compile->pmodes->pmode_expr_list;
expr->plan = NULL;
expr->paramnos = NULL;
expr->target_param = -1;
@@ -4151,7 +4173,7 @@ make_case(int location, PLpgSQL_expr *t_expr,
StringInfoData ds;
/* We expect to have expressions not statements */
- Assert(expr->parseMode == RAW_PARSE_PLPGSQL_EXPR);
+ Assert(expr->parseMode == plpgsql_curr_compile->pmodes->pmode_expr_list);
/* Do the string hacking */
initStringInfo(&ds);
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index fce459ade0..f34b3befc4 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -94,6 +94,8 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
+ else if (pg_strcasecmp(tok, "strict_expr_check") == 0)
+ extrachecks |= PLPGSQL_XCHECK_STRICTEXPRCHECK;
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
{
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 50c3b28472..b2fb67e851 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -960,6 +960,21 @@ typedef enum PLpgSQL_trigtype
PLPGSQL_NOT_TRIGGER,
} PLpgSQL_trigtype;
+/*
+ * Raw parse modes, that should be used for expressions,
+ * assignment, expression's list. When extra_errors.strict_expr_check
+ * is active, then only a_expr parsing is allowed.
+ */
+typedef struct PLpgSQL_parse_modes
+{
+ RawParseMode pmode_expr;
+ RawParseMode pmode_expr_list;
+ RawParseMode pmode_named_expr_list;
+ RawParseMode pmode_assign1;
+ RawParseMode pmode_assign2;
+ RawParseMode pmode_assign3;
+} PLpgSQL_parse_modes;
+
/*
* Complete compiled function
*/
@@ -1010,6 +1025,9 @@ typedef struct PLpgSQL_function
unsigned int nstatements; /* counter for assigning stmtids */
bool requires_procedure_resowner; /* contains CALL or DO? */
+ /* Raw parse modes configuration */
+ const PLpgSQL_parse_modes *pmodes;
+
/* these fields change when the function is used */
struct PLpgSQL_execstate *cur_estate;
unsigned long use_count;
@@ -1204,6 +1222,7 @@ extern bool plpgsql_check_asserts;
#define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
+#define PLPGSQL_XCHECK_STRICTEXPRCHECK (1 << 4)
#define PLPGSQL_XCHECK_ALL ((int) ~0)
extern int plpgsql_extra_warnings;
--
2.45.2
0003-set-plpgsql.extra_errors-to-none.patchtext/x-patch; charset=US-ASCII; name=0003-set-plpgsql.extra_errors-to-none.patchDownload
From 2b3546e14260021d52a58803cba0183dc808ace0 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.com>
Date: Sun, 16 Jun 2024 15:52:38 +0200
Subject: [PATCH 3/3] set plpgsql.extra_errors to "none"
Purpose of previous commit was to run tests with active strict_expr_check.
Now, reset to default and revert all changes from previous commit.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 ++++++++-----------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 52 insertions(+), 56 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 280ff3e022..0015053e0f 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 2c127a821f..14e236d57a 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index 6819c0435f..ad60e0e8be 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,38 +50,34 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select from onecol)
- ^
-QUERY: a := (select from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select f1, f1 from onecol)
- ^
-QUERY: a := (select f1, f1 from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: more than one row returned by a subquery used as an expression
-CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query returned more than one row
+CONTEXT: query: a := f1 from onecol
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index a17cfe01e6..f34b3befc4 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -50,7 +50,7 @@ bool plpgsql_check_asserts = true;
char *plpgsql_extra_warnings_string = NULL;
char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+int plpgsql_extra_errors;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -193,7 +193,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "strict_expr_check",
+ "none",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 699713696d..4b9ff51594 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,31 +46,31 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
+begin a := from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index da1add175a..f3e27c19af 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
+ wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
+ IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 1cf06e9ebf..673361e840 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2627,7 +2627,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index faae99515f..4e59188196 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 3a9a1828f2..074af8f33a 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index e2899a0e88..8c4da95508 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -327,7 +327,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 3458dad174..7f5757e89c 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 8d89b06e38..8c8fa27a6a 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1667,7 +1667,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a3dbd93468..4b2f11dcc6 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index fbc21b1f52..18c91572ae 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index f2977c8ad3..0c08a6cc42 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -209,7 +209,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 1981955ac6..51ae1b31b3 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.45.2
0002-simply-check-of-strict-expr-check-on-regress-test.patchtext/x-patch; charset=US-ASCII; name=0002-simply-check-of-strict-expr-check-on-regress-test.patchDownload
From c41b3e5ebee92968f2f3276b02c46ca08c91973d Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.com>
Date: Sun, 16 Jun 2024 08:13:53 +0200
Subject: [PATCH 2/3] simply check of strict-expr-check on regress test
This patch enable strict-expr-check by default to be possible to see
the impact of this option on regress test. Next commit will revert
this option. The strict-expr-check should not be enabled by default.
This commit is done just for testing.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 +++++++++++--------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 0015053e0f..280ff3e022 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 14e236d57a..2c127a821f 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index ad60e0e8be..6819c0435f 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,34 +50,38 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 0 columns
-CONTEXT: PL/pgSQL assignment "a := from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select from onecol)
+ ^
+QUERY: a := (select from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 2 columns
-CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select f1, f1 from onecol)
+ ^
+QUERY: a := (select f1, f1 from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: query returned more than one row
-CONTEXT: query: a := f1 from onecol
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: more than one row returned by a subquery used as an expression
+CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index f34b3befc4..a17cfe01e6 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -50,7 +50,7 @@ bool plpgsql_check_asserts = true;
char *plpgsql_extra_warnings_string = NULL;
char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors;
+int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -193,7 +193,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "none",
+ "strict_expr_check",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 4b9ff51594..699713696d 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,31 +46,31 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index f3e27c19af..da1add175a 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
+ wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
+ IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 673361e840..1cf06e9ebf 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2627,7 +2627,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4e59188196..faae99515f 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 074af8f33a..3a9a1828f2 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 8c4da95508..e2899a0e88 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -327,7 +327,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 7f5757e89c..3458dad174 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 8c8fa27a6a..8d89b06e38 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1667,7 +1667,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 4b2f11dcc6..a3dbd93468 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 18c91572ae..fbc21b1f52 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 0c08a6cc42..f2977c8ad3 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -209,7 +209,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 51ae1b31b3..1981955ac6 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.45.2
Can you remove or just ignore double ; too ?
postgres=# do $$
declare var_x integer;
begin
var_x = 99;;
delete from x where x = var_x;
end; $$;
ERROR: syntax error at or near ";"
LINE 1: do $$ declare var_x integer; begin var_x = 99;; delete from ...
Atenciosamente,
Em dom., 16 de jun. de 2024 às 11:12, Pavel Stehule <pavel.stehule@gmail.com>
escreveu:
Show quoted text
Hi,
assigned patch try to solve issue reported by Mor Lehr (Missing semicolon
in anonymous plpgsql block does not raise syntax error)./messages/by-id/CALyvM2bp_CXMH_Gyq87pmHJRuZDEhV40u9VP8rX=CAnEt2wUXg@mail.gmail.com
by introducing a new extra error check. With this check only a_expr exprs
are allowed as plpgsql expressions. This is a small step to behaviour
described in SQL/PSM standard (although the language is different, the
expression syntax and features are almost similar. With this check the
undocumented (but supported syntax)var := column FROM tab
is disallowed. Only ANSI syntax for embedded queries (inside assignment
statement) is allowedvar := (SELECT column FROM tab);
With this check, the reported issue (by Mor Lehr) is detected
default setting
CREATE TABLE foo3(id serial PRIMARY key, txt text);
INSERT INTO foo3 (txt) VALUES ('aaa'),('bbb');DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$-- without reaction - just don't work
(2024-06-16 16:05:55) postgres=# set plpgsql.extra_errors to
'strict_expr_check';
SET
(2024-06-16 16:06:43) postgres=# DO $$DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
ERROR: syntax error at or near "DELETE"
LINE 11: DELETE FROM foo3 WHERE id=1;
^This patch has three parts
1. Introduction strict_expr_check
2. set strict_expr_check as default, and impact on regress tests
3. revert @2I don't propose to be strict_expr_check active by default.
Comments, notes?
Regards
Pavel
Hi
ne 16. 6. 2024 v 16:22 odesílatel Marcos Pegoraro <marcos@f10.com.br>
napsal:
Can you remove or just ignore double ; too ?
I don't know - it is a different issue.
PLpgSQL allows zero statements inside block, so you can write BEGIN END or
IF 1 THEN END IF but it doesn't allow empty statement
like ;;
probably it just needs one more rule in gram.y - but in this case, I am not
sure if we should support it.
What is the expected benefit? Generally PL/pgSQL has very strict syntax -
and using double semicolons makes no sense.
Show quoted text
postgres=# do $$
declare var_x integer;
begin
var_x = 99;;
delete from x where x = var_x;
end; $$;
ERROR: syntax error at or near ";"
LINE 1: do $$ declare var_x integer; begin var_x = 99;; delete from ...Atenciosamente,
Em dom., 16 de jun. de 2024 às 11:12, Pavel Stehule <
pavel.stehule@gmail.com> escreveu:Hi,
assigned patch try to solve issue reported by Mor Lehr (Missing semicolon
in anonymous plpgsql block does not raise syntax error)./messages/by-id/CALyvM2bp_CXMH_Gyq87pmHJRuZDEhV40u9VP8rX=CAnEt2wUXg@mail.gmail.com
by introducing a new extra error check. With this check only a_expr exprs
are allowed as plpgsql expressions. This is a small step to behaviour
described in SQL/PSM standard (although the language is different, the
expression syntax and features are almost similar. With this check the
undocumented (but supported syntax)var := column FROM tab
is disallowed. Only ANSI syntax for embedded queries (inside assignment
statement) is allowedvar := (SELECT column FROM tab);
With this check, the reported issue (by Mor Lehr) is detected
default setting
CREATE TABLE foo3(id serial PRIMARY key, txt text);
INSERT INTO foo3 (txt) VALUES ('aaa'),('bbb');DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$-- without reaction - just don't work
(2024-06-16 16:05:55) postgres=# set plpgsql.extra_errors to
'strict_expr_check';
SET
(2024-06-16 16:06:43) postgres=# DO $$DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
ERROR: syntax error at or near "DELETE"
LINE 11: DELETE FROM foo3 WHERE id=1;
^This patch has three parts
1. Introduction strict_expr_check
2. set strict_expr_check as default, and impact on regress tests
3. revert @2I don't propose to be strict_expr_check active by default.
Comments, notes?
Regards
Pavel
Em dom., 16 de jun. de 2024 às 11:37, Pavel Stehule <pavel.stehule@gmail.com>
escreveu:
What is the expected benefit? Generally PL/pgSQL has very strict syntax -
and using double semicolons makes no sense.exactly, makes no sense. That is because it should be ignored, right ?
But ok, if this is a different issue, that´s fine.
regards
Marcos
ne 16. 6. 2024 v 16:43 odesílatel Marcos Pegoraro <marcos@f10.com.br>
napsal:
Em dom., 16 de jun. de 2024 às 11:37, Pavel Stehule <
pavel.stehule@gmail.com> escreveu:What is the expected benefit? Generally PL/pgSQL has very strict syntax -
and using double semicolons makes no sense.exactly, makes no sense. That is because it should be ignored, right ?
But ok, if this is a different issue, that´s fine.
I don't follow this idea - when it does not make sense, then why do you use
it? It can be a signal of some issue in your code.
The source code should not contain a code that should be ignored.
But I am not a authority - can be interesting if this is allowed in PL/SQL
or Ada
Regards
Pavel
Show quoted text
regards
Marcos
Em dom., 16 de jun. de 2024 às 12:11, Pavel Stehule <pavel.stehule@gmail.com>
escreveu:
I don't follow this idea - when it does not make sense, then why do you
use it? It can be a signal of some issue in your code.
I don't use it, but sometimes it occurs, and there are lots of languages
which ignore it, so it would be cool if plpgsql does it too.
If you do this, works
set search_path to public;;;
but if you do the same inside a block, it does not.
regards
Marcos
ne 16. 6. 2024 v 19:36 odesílatel Marcos Pegoraro <marcos@f10.com.br>
napsal:
Em dom., 16 de jun. de 2024 às 12:11, Pavel Stehule <
pavel.stehule@gmail.com> escreveu:I don't follow this idea - when it does not make sense, then why do you
use it? It can be a signal of some issue in your code.I don't use it, but sometimes it occurs, and there are lots of languages
which ignore it, so it would be cool if plpgsql does it too.If you do this, works
set search_path to public;;;
psql allows it, but it is a shell - not a programming language.
but if you do the same inside a block, it does not.
It is a different language. I have not too strong an opinion about it - it
is hard to say what is the correct design when you should work with a mix
of languages like SQL and Ada (PL/pgSQL), and when related standard SQL/PSM
is not widely used. Personally, I don't see any nice features that allow it
to accept dirty code. I have negative experiences when a language is
tolerant.
Regards
Pavel
Show quoted text
regards
Marcos
ne 16. 6. 2024 v 16:11 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
Hi,
assigned patch try to solve issue reported by Mor Lehr (Missing semicolon
in anonymous plpgsql block does not raise syntax error)./messages/by-id/CALyvM2bp_CXMH_Gyq87pmHJRuZDEhV40u9VP8rX=CAnEt2wUXg@mail.gmail.com
by introducing a new extra error check. With this check only a_expr exprs
are allowed as plpgsql expressions. This is a small step to behaviour
described in SQL/PSM standard (although the language is different, the
expression syntax and features are almost similar. With this check the
undocumented (but supported syntax)var := column FROM tab
is disallowed. Only ANSI syntax for embedded queries (inside assignment
statement) is allowedvar := (SELECT column FROM tab);
With this check, the reported issue (by Mor Lehr) is detected
default setting
CREATE TABLE foo3(id serial PRIMARY key, txt text);
INSERT INTO foo3 (txt) VALUES ('aaa'),('bbb');DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$-- without reaction - just don't work
(2024-06-16 16:05:55) postgres=# set plpgsql.extra_errors to
'strict_expr_check';
SET
(2024-06-16 16:06:43) postgres=# DO $$DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
ERROR: syntax error at or near "DELETE"
LINE 11: DELETE FROM foo3 WHERE id=1;
^This patch has three parts
1. Introduction strict_expr_check
2. set strict_expr_check as default, and impact on regress tests
3. revert @2I don't propose to be strict_expr_check active by default.
Comments, notes?
fresh rebase
Show quoted text
Regards
Pavel
Attachments:
0002-simply-check-of-strict-expr-check-on-regress-test.patchtext/x-patch; charset=US-ASCII; name=0002-simply-check-of-strict-expr-check-on-regress-test.patchDownload
From ebe7c73534f160d75356ac605cdb6a147e806f8f Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.com>
Date: Sun, 16 Jun 2024 08:13:53 +0200
Subject: [PATCH 2/3] simply check of strict-expr-check on regress test
This patch enable strict-expr-check by default to be possible to see
the impact of this option on regress test. Next commit will revert
this option. The strict-expr-check should not be enabled by default.
This commit is done just for testing.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 +++++++++++--------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 0015053e0f2..280ff3e022e 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 14e236d57ab..2c127a821f1 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index ad60e0e8be3..6819c0435f6 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,34 +50,38 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 0 columns
-CONTEXT: PL/pgSQL assignment "a := from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select from onecol)
+ ^
+QUERY: a := (select from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 2 columns
-CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select f1, f1 from onecol)
+ ^
+QUERY: a := (select f1, f1 from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: query returned more than one row
-CONTEXT: query: a := f1 from onecol
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: more than one row returned by a subquery used as an expression
+CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index e7037fa8e81..fa6e2ab1d1f 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -50,7 +50,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors;
+int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -193,7 +193,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "none",
+ "strict_expr_check",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 4b9ff515948..699713696d4 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,31 +46,31 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index f3e27c19af5..da1add175ac 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
+ wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
+ IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 79cf82b5aed..fd9e10e909c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2627,7 +2627,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4e59188196c..faae99515f7 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 0a6945581bd..0e25138b918 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 8c4da955084..e2899a0e888 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -327,7 +327,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 7f5757e89c4..3458dad1749 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 28cabc49e9f..845e23d8ae2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1667,7 +1667,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 4b2f11dcc64..a3dbd93468e 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 18c91572ae1..fbc21b1f52e 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 0c08a6cc42e..f2977c8ad38 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -209,7 +209,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 51ae1b31b30..1981955ac65 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.46.0
0003-set-plpgsql.extra_errors-to-none.patchtext/x-patch; charset=US-ASCII; name=0003-set-plpgsql.extra_errors-to-none.patchDownload
From 86b6dcb7a7e5dec3e5796270fa83a2465e803fed Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.com>
Date: Sun, 16 Jun 2024 15:52:38 +0200
Subject: [PATCH 3/3] set plpgsql.extra_errors to "none"
Purpose of previous commit was to run tests with active strict_expr_check.
Now, reset to default and revert all changes from previous commit.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 ++++++++-----------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 52 insertions(+), 56 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 280ff3e022e..0015053e0f2 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 2c127a821f1..14e236d57ab 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index 6819c0435f6..ad60e0e8be3 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,38 +50,34 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select from onecol)
- ^
-QUERY: a := (select from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select f1, f1 from onecol)
- ^
-QUERY: a := (select f1, f1 from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: more than one row returned by a subquery used as an expression
-CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query returned more than one row
+CONTEXT: query: a := f1 from onecol
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index fa6e2ab1d1f..e7037fa8e81 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -50,7 +50,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+int plpgsql_extra_errors;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -193,7 +193,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "strict_expr_check",
+ "none",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 699713696d4..4b9ff515948 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,31 +46,31 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
+begin a := from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index da1add175ac..f3e27c19af5 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
+ wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
+ IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index fd9e10e909c..79cf82b5aed 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2627,7 +2627,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index faae99515f7..4e59188196c 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 0e25138b918..0a6945581bd 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index e2899a0e888..8c4da955084 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -327,7 +327,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 3458dad1749..7f5757e89c4 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 845e23d8ae2..28cabc49e9f 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1667,7 +1667,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a3dbd93468e..4b2f11dcc64 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index fbc21b1f52e..18c91572ae1 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index f2977c8ad38..0c08a6cc42e 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -209,7 +209,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 1981955ac65..51ae1b31b30 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.46.0
0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchtext/x-patch; charset=US-ASCII; name=0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchDownload
From 6c61739847d5d1af9da1153e1429c81bb8706aab Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.com>
Date: Wed, 12 Jun 2024 21:34:05 +0200
Subject: [PATCH 1/3] use strict rules for parsing PL/pgSQL expressions
Originally the rule PLpgSQL_Expr allows almost all SQL clauses. It was designed
to allow old undocumented syntax
var := col FROM tab;
The reason for support of this "strange" syntax was technical. The PLpgSQL parser
cannot use SQL parser accurately (it was really primitive), and people found
this undocumented syntax. Lattery, when it was possible to do exact parsing, from
compatibility reasons, the parsing of PL/pgSQL expressions allows described syntax.
Unfortunately, with support almost all SQL clauses, the PLpgSQL can accept
really broken code like
DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
proposed patch introduce new extra error check strict_expr_check, that solve
this issue.
---
doc/src/sgml/plpgsql.sgml | 18 ++++
src/backend/executor/spi.c | 6 ++
src/backend/parser/gram.y | 149 +++++++++++++++++++++++++++
src/backend/parser/parser.c | 6 ++
src/include/parser/parser.h | 22 ++++
src/interfaces/ecpg/preproc/parse.pl | 10 +-
src/pl/plpgsql/src/pl_comp.c | 31 ++++++
src/pl/plpgsql/src/pl_gram.y | 48 ++++++---
src/pl/plpgsql/src/pl_handler.c | 2 +
src/pl/plpgsql/src/plpgsql.h | 19 ++++
10 files changed, 297 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 78e4983139b..a27700d6cb3 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5386,6 +5386,24 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="plpgsql-extra-checks-strict-expr-check">
+ <term><varname>strict_expr_check</varname></term>
+ <listitem>
+ <para>
+ Enabling this check will cause <application>PL/pgSQL</application> to
+ check if a <application>PL/pgSQL</application> expression is just an
+ expression without any SQL clauses like <literal>FROM</literal>,
+ <literal>ORDER BY</literal>. This undocumented form of expressions
+ is allowed for compatibility reasons, but in some special cases
+ it doesn't to allow to detect broken code.
+ </para>
+
+ <para>
+ This check is allowed only <varname>plpgsql.extra_errors</varname>.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
The following example shows the effect of <varname>plpgsql.extra_warnings</varname>
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 90d98345764..76c25c9948d 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2978,11 +2978,17 @@ _SPI_error_callback(void *arg)
switch (carg->mode)
{
case RAW_PARSE_PLPGSQL_EXPR:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST:
+ case RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST:
errcontext("PL/pgSQL expression \"%s\"", query);
break;
case RAW_PARSE_PLPGSQL_ASSIGN1:
case RAW_PARSE_PLPGSQL_ASSIGN2:
case RAW_PARSE_PLPGSQL_ASSIGN3:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3:
errcontext("PL/pgSQL assignment \"%s\"", query);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 84cef57a707..5abce73b030 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -323,6 +323,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
PLpgSQL_Expr PLAssignStmt
+ PLpgSQLStrictExpr PLpgSQLStrictExprs PLpgSQLStrictNamedExprs
+ PLAssignStmtStrictExpr
+
+%type <target> plpgsql_strict_expr plpgsql_strict_named_expr
+%type <list> plpgsql_strict_expr_list plpgsql_strict_named_expr_list
%type <str> opt_single_name
%type <list> opt_qualified_name
@@ -824,6 +829,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%token MODE_PLPGSQL_ASSIGN1
%token MODE_PLPGSQL_ASSIGN2
%token MODE_PLPGSQL_ASSIGN3
+%token MODE_PLPGSQL_STRICT_EXPR
+%token MODE_PLPGSQL_STRICT_EXPR_LIST
+%token MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST
+%token MODE_PLPGSQL_STRICT_EXPR_ASSIGN1
+%token MODE_PLPGSQL_STRICT_EXPR_ASSIGN2
+%token MODE_PLPGSQL_STRICT_EXPR_ASSIGN3
/* Precedence: lowest to highest */
@@ -955,6 +966,46 @@ parse_toplevel:
pg_yyget_extra(yyscanner)->parsetree =
list_make1(makeRawStmt((Node *) n, 0));
}
+ | MODE_PLPGSQL_STRICT_EXPR PLpgSQLStrictExpr
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_LIST PLpgSQLStrictExprs
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST PLpgSQLStrictNamedExprs
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_ASSIGN1 PLAssignStmtStrictExpr
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+
+ n->nnames = 1;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_ASSIGN2 PLAssignStmtStrictExpr
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+
+ n->nnames = 2;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_ASSIGN3 PLAssignStmtStrictExpr
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+
+ n->nnames = 3;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+
;
/*
@@ -17426,6 +17477,104 @@ plassign_equals: COLON_EQUALS
| '='
;
+/*
+ * In "strict" mode plpgsql expressions are just an a_expr. From compatibility
+ * reasons (with default mode) it returns SelectStmt still.
+ */
+PLpgSQLStrictExpr: a_expr
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ ResTarget *rt = makeNode(ResTarget);
+
+ rt->name = NULL;
+ rt->indirection = NIL;
+ rt->val = (Node *) $1;
+ rt->location = @1;
+
+ n->targetList = list_make1((Node *) rt);
+ $$ = (Node *) n;
+ }
+ ;
+
+PLAssignStmtStrictExpr: plassign_target opt_indirection plassign_equals PLpgSQLStrictExpr
+ {
+ PLAssignStmt *n = makeNode(PLAssignStmt);
+
+ n->name = $1;
+ n->indirection = check_indirection($2, yyscanner);
+ /* nnames will be filled by calling production */
+ n->val = (SelectStmt *) $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+/*
+ * Used for unnamed plpgsql cursor's argument and plpgsql case in
+ * "strict" mode.
+ */
+PLpgSQLStrictExprs: plpgsql_strict_expr_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $1;
+ $$ = (Node *) n;
+ }
+ ;
+
+plpgsql_strict_expr_list:
+ plpgsql_strict_expr
+ {
+ $$ = list_make1($1);
+ }
+ | plpgsql_strict_expr_list ',' plpgsql_strict_expr
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
+plpgsql_strict_expr: a_expr
+ {
+ $$ = makeNode(ResTarget);
+ $$->name = NULL;
+ $$->indirection = NIL;
+ $$->val = (Node *) $1;
+ $$->location = @1;
+ }
+ ;
+
+/*
+ * Used for named cursor's arguments in "strict" mode
+ */
+PLpgSQLStrictNamedExprs: plpgsql_strict_named_expr_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $1;
+ $$ = (Node *) n;
+ }
+ ;
+
+plpgsql_strict_named_expr_list:
+ plpgsql_strict_named_expr
+ {
+ $$ = list_make1($1);
+ }
+ | plpgsql_strict_named_expr_list ',' plpgsql_strict_named_expr
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
+plpgsql_strict_named_expr: a_expr AS ColLabel
+ {
+ $$ = makeNode(ResTarget);
+ $$->name = $3;
+ $$->indirection = NIL;
+ $$->val = (Node *) $1;
+ $$->location = @1;
+ }
+ ;
/*
* Name classification hierarchy.
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 118488c3f30..9d2dde67939 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -62,6 +62,12 @@ raw_parser(const char *str, RawParseMode mode)
[RAW_PARSE_PLPGSQL_ASSIGN1] = MODE_PLPGSQL_ASSIGN1,
[RAW_PARSE_PLPGSQL_ASSIGN2] = MODE_PLPGSQL_ASSIGN2,
[RAW_PARSE_PLPGSQL_ASSIGN3] = MODE_PLPGSQL_ASSIGN3,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR] = MODE_PLPGSQL_STRICT_EXPR,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST] = MODE_PLPGSQL_STRICT_EXPR_LIST,
+ [RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST] = MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN3,
};
yyextra.have_lookahead = true;
diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
index be184ec5066..8e41907bd4d 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -33,6 +33,22 @@
* RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement,
* and return a one-element List containing a RawStmt node. "n"
* gives the number of dotted names comprising the target ColumnRef.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR: parse a PL/pgSQL expression, and
+ * return a one-element List containing a RwaStmt node. The result is
+ * compatible with RAW_PARSE_PLPGSQL_EXPR, but parser allows only
+ * a_expr (instead almost all complete query).
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST: parse a comma separated list
+ * of PL/pgSQL expressions (only a_expr are allowed). It is used by
+ * PLpGSQL CASE and OPEN commands.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST: parse a comma separated
+ * list of a_expr node with labels. It is used for evaluation of
+ * named arguments of PLpgSQL OPEN (cursor) statement.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGNn: parse a PL/pgSQL assignment
+ * statement, but only a_expr are allowed).
*/
typedef enum
{
@@ -42,6 +58,12 @@ typedef enum
RAW_PARSE_PLPGSQL_ASSIGN1,
RAW_PARSE_PLPGSQL_ASSIGN2,
RAW_PARSE_PLPGSQL_ASSIGN3,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3,
} RawParseMode;
/* Values for the backslash_quote GUC */
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index fe8d3e51780..433f4067784 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -89,7 +89,15 @@ my %replace_types = (
'PLpgSQL_Expr' => 'ignore',
'PLAssignStmt' => 'ignore',
'plassign_target' => 'ignore',
- 'plassign_equals' => 'ignore',);
+ 'plassign_equals' => 'ignore',
+ 'plpgsql_strict_expr' => 'ignore',
+ 'plpgsql_strict_named_expr' => 'ignore',
+ 'plpgsql_strict_expr_list' => 'ignore',
+ 'plpgsql_strict_named_expr_list' => 'ignore',
+ 'PLpgSQLStrictExpr' => 'ignore',
+ 'PLpgSQLStrictExprs' => 'ignore',
+ 'PLpgSQLStrictNamedExprs' => 'ignore',
+ 'PLAssignStmtStrictExpr' => 'ignore',);
# these replace_line commands excise certain keywords from the core keyword
# lists. Be sure to account for these in ColLabel and related productions.
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index f1bce708d62..8f9d3ed64d2 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -84,6 +84,23 @@ static const ExceptionLabelMap exception_label_map[] = {
{NULL, 0}
};
+static const PLpgSQL_parse_modes default_parse_modes = {
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_ASSIGN1,
+ RAW_PARSE_PLPGSQL_ASSIGN2,
+ RAW_PARSE_PLPGSQL_ASSIGN3
+};
+
+static const PLpgSQL_parse_modes strict_expr_parse_modes = {
+ RAW_PARSE_PLPGSQL_STRICT_EXPR,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3
+};
/* ----------
* static prototypes
@@ -355,6 +372,11 @@ do_compile(FunctionCallInfo fcinfo,
function->extra_warnings = forValidator ? plpgsql_extra_warnings : 0;
function->extra_errors = forValidator ? plpgsql_extra_errors : 0;
+ if (function->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->pmodes = &strict_expr_parse_modes;
+ else
+ function->pmodes = &default_parse_modes;
+
if (is_dml_trigger)
function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
else if (is_event_trigger)
@@ -898,6 +920,15 @@ plpgsql_compile_inline(char *proc_source)
function->extra_warnings = 0;
function->extra_errors = 0;
+ /*
+ * Although function->extra_errors is disabled, we want to
+ * do strict_expr_check inside annoymous block too.
+ */
+ if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->pmodes = &strict_expr_parse_modes;
+ else
+ function->pmodes = &default_parse_modes;
+
function->nstatements = 0;
function->requires_procedure_resowner = false;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 8182ce28aa1..d3bf9ab6a5f 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -183,6 +183,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <expr> expr_until_semi
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
+%type <expr> expressions_until_then
%type <var> cursor_variable
%type <datum> decl_cursor_arg
@@ -962,13 +963,13 @@ stmt_assign : T_DATUM
switch ($1.ident ? 1 : list_length($1.idents))
{
case 1:
- pmode = RAW_PARSE_PLPGSQL_ASSIGN1;
+ pmode = plpgsql_curr_compile->pmodes->pmode_assign1;
break;
case 2:
- pmode = RAW_PARSE_PLPGSQL_ASSIGN2;
+ pmode = plpgsql_curr_compile->pmodes->pmode_assign2;
break;
case 3:
- pmode = RAW_PARSE_PLPGSQL_ASSIGN3;
+ pmode = plpgsql_curr_compile->pmodes->pmode_assign3;
break;
default:
elog(ERROR, "unexpected number of names");
@@ -1244,7 +1245,7 @@ case_when_list : case_when_list case_when
}
;
-case_when : K_WHEN expr_until_then proc_sect
+case_when : K_WHEN expressions_until_then proc_sect
{
PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
@@ -1274,6 +1275,14 @@ opt_case_else :
}
;
+expressions_until_then :
+ {
+ $$ = read_sql_construct(K_THEN, 0, 0, "THEN",
+ plpgsql_curr_compile->pmodes->pmode_expr_list,
+ true, true, NULL, NULL);
+ }
+ ;
+
stmt_loop : opt_loop_label K_LOOP loop_body
{
PLpgSQL_stmt_loop *new;
@@ -1493,7 +1502,8 @@ for_control : for_variable K_IN
* Relabel first expression as an expression;
* then we can check its syntax.
*/
- expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ expr1->parseMode = plpgsql_curr_compile->pmodes->pmode_expr;
+
check_sql_expr(expr1->query, expr1->parseMode,
expr1loc);
@@ -1863,6 +1873,8 @@ stmt_raise : K_RAISE
*/
if (tok == SCONST)
{
+ RawParseMode pmode_expr;
+
/* old style message and parameters */
new->message = yylval.str;
/*
@@ -1875,13 +1887,15 @@ stmt_raise : K_RAISE
if (tok != ',' && tok != ';' && tok != K_USING)
yyerror("syntax error");
+ pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
+
while (tok == ',')
{
PLpgSQL_expr *expr;
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &tok);
new->params = lappend(new->params, expr);
@@ -2015,10 +2029,13 @@ stmt_dynexecute : K_EXECUTE
PLpgSQL_stmt_dynexecute *new;
PLpgSQL_expr *expr;
int endtoken;
+ RawParseMode pmode_expr;
+
+ pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &endtoken);
@@ -2057,7 +2074,7 @@ stmt_dynexecute : K_EXECUTE
{
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &endtoken);
new->params = lappend(new->params, expr);
@@ -2642,7 +2659,7 @@ static PLpgSQL_expr *
read_sql_expression(int until, const char *expected)
{
return read_sql_construct(until, 0, 0, expected,
- RAW_PARSE_PLPGSQL_EXPR,
+ plpgsql_curr_compile->pmodes->pmode_expr,
true, true, NULL, NULL);
}
@@ -2652,7 +2669,7 @@ read_sql_expression2(int until, int until2, const char *expected,
int *endtoken)
{
return read_sql_construct(until, until2, 0, expected,
- RAW_PARSE_PLPGSQL_EXPR,
+ plpgsql_curr_compile->pmodes->pmode_expr,
true, true, NULL, endtoken);
}
@@ -3845,6 +3862,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
char **argv;
StringInfoData ds;
bool any_named = false;
+ RawParseMode pmode_expr;
tok = yylex();
if (cursor->cursor_explicit_argrow < 0)
@@ -3871,6 +3889,8 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
cursor->refname),
parser_errposition(yylloc)));
+ pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
+
/*
* Read the arguments, one by one.
*/
@@ -3941,7 +3961,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
*/
item = read_sql_construct(',', ')', 0,
",\" or \")",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &endtoken);
@@ -3982,7 +4002,9 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
- expr->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ expr->parseMode = any_named ?
+ plpgsql_curr_compile->pmodes->pmode_named_expr_list
+ : plpgsql_curr_compile->pmodes->pmode_expr_list;
expr->plan = NULL;
expr->paramnos = NULL;
expr->target_param = -1;
@@ -4156,7 +4178,7 @@ make_case(int location, PLpgSQL_expr *t_expr,
StringInfoData ds;
/* We expect to have expressions not statements */
- Assert(expr->parseMode == RAW_PARSE_PLPGSQL_EXPR);
+ Assert(expr->parseMode == plpgsql_curr_compile->pmodes->pmode_expr_list);
/* Do the string hacking */
initStringInfo(&ds);
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 980f0961bc8..e7037fa8e81 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -94,6 +94,8 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
+ else if (pg_strcasecmp(tok, "strict_expr_check") == 0)
+ extrachecks |= PLPGSQL_XCHECK_STRICTEXPRCHECK;
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
{
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 50c3b28472b..b2fb67e8514 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -960,6 +960,21 @@ typedef enum PLpgSQL_trigtype
PLPGSQL_NOT_TRIGGER,
} PLpgSQL_trigtype;
+/*
+ * Raw parse modes, that should be used for expressions,
+ * assignment, expression's list. When extra_errors.strict_expr_check
+ * is active, then only a_expr parsing is allowed.
+ */
+typedef struct PLpgSQL_parse_modes
+{
+ RawParseMode pmode_expr;
+ RawParseMode pmode_expr_list;
+ RawParseMode pmode_named_expr_list;
+ RawParseMode pmode_assign1;
+ RawParseMode pmode_assign2;
+ RawParseMode pmode_assign3;
+} PLpgSQL_parse_modes;
+
/*
* Complete compiled function
*/
@@ -1010,6 +1025,9 @@ typedef struct PLpgSQL_function
unsigned int nstatements; /* counter for assigning stmtids */
bool requires_procedure_resowner; /* contains CALL or DO? */
+ /* Raw parse modes configuration */
+ const PLpgSQL_parse_modes *pmodes;
+
/* these fields change when the function is used */
struct PLpgSQL_execstate *cur_estate;
unsigned long use_count;
@@ -1204,6 +1222,7 @@ extern bool plpgsql_check_asserts;
#define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
+#define PLPGSQL_XCHECK_STRICTEXPRCHECK (1 << 4)
#define PLPGSQL_XCHECK_ALL ((int) ~0)
extern int plpgsql_extra_warnings;
--
2.46.0
Hi
fresh rebase
Regards
Pavel
Attachments:
0002-simply-check-of-strict-expr-check-on-regress-test.patchtext/x-patch; charset=US-ASCII; name=0002-simply-check-of-strict-expr-check-on-regress-test.patchDownload
From 0a1e5a4de6d45e2b4e0f047d4aa9dab5ddeb6b2d Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.com>
Date: Sun, 16 Jun 2024 08:13:53 +0200
Subject: [PATCH 2/3] simply check of strict-expr-check on regress test
This patch enable strict-expr-check by default to be possible to see
the impact of this option on regress test. Next commit will revert
this option. The strict-expr-check should not be enabled by default.
This commit is done just for testing.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 +++++++++++--------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 0015053e0f2..280ff3e022e 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 14e236d57ab..2c127a821f1 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index ad60e0e8be3..6819c0435f6 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,34 +50,38 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 0 columns
-CONTEXT: PL/pgSQL assignment "a := from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select from onecol)
+ ^
+QUERY: a := (select from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 2 columns
-CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select f1, f1 from onecol)
+ ^
+QUERY: a := (select f1, f1 from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: query returned more than one row
-CONTEXT: query: a := f1 from onecol
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: more than one row returned by a subquery used as an expression
+CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 5cb779a85b6..adc07a60b79 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -50,7 +50,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors;
+int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -193,7 +193,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "none",
+ "strict_expr_check",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 4b9ff515948..699713696d4 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,31 +46,31 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index f3e27c19af5..da1add175ac 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
+ wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
+ IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 143ae7c09c0..97b319b7841 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2626,7 +2626,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4e59188196c..faae99515f7 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 0a6945581bd..0e25138b918 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 8c4da955084..e2899a0e888 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -327,7 +327,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 7f5757e89c4..3458dad1749 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c5dd43a15c4..0a25ac6e92f 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1665,7 +1665,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 4b2f11dcc64..a3dbd93468e 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 18c91572ae1..fbc21b1f52e 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 0c08a6cc42e..f2977c8ad38 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -209,7 +209,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 51ae1b31b30..1981955ac65 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.47.0
0003-set-plpgsql.extra_errors-to-none.patchtext/x-patch; charset=US-ASCII; name=0003-set-plpgsql.extra_errors-to-none.patchDownload
From b5933575b1fb2780517f5fc5bd98306e54b3da83 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.com>
Date: Sun, 16 Jun 2024 15:52:38 +0200
Subject: [PATCH 3/3] set plpgsql.extra_errors to "none"
Purpose of previous commit was to run tests with active strict_expr_check.
Now, reset to default and revert all changes from previous commit.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 ++++++++-----------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 52 insertions(+), 56 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 280ff3e022e..0015053e0f2 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 2c127a821f1..14e236d57ab 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index 6819c0435f6..ad60e0e8be3 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,38 +50,34 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select from onecol)
- ^
-QUERY: a := (select from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select f1, f1 from onecol)
- ^
-QUERY: a := (select f1, f1 from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: more than one row returned by a subquery used as an expression
-CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query returned more than one row
+CONTEXT: query: a := f1 from onecol
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index adc07a60b79..5cb779a85b6 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -50,7 +50,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+int plpgsql_extra_errors;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -193,7 +193,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "strict_expr_check",
+ "none",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 699713696d4..4b9ff515948 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,31 +46,31 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
+begin a := from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index da1add175ac..f3e27c19af5 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
+ wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
+ IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 97b319b7841..143ae7c09c0 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2626,7 +2626,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index faae99515f7..4e59188196c 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 0e25138b918..0a6945581bd 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index e2899a0e888..8c4da955084 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -327,7 +327,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 3458dad1749..7f5757e89c4 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 0a25ac6e92f..c5dd43a15c4 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1665,7 +1665,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a3dbd93468e..4b2f11dcc64 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index fbc21b1f52e..18c91572ae1 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index f2977c8ad38..0c08a6cc42e 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -209,7 +209,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 1981955ac65..51ae1b31b30 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.47.0
0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchtext/x-patch; charset=US-ASCII; name=0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchDownload
From 4496bde2064941e348ea2eed42ea73ac17f40dd8 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.com>
Date: Wed, 12 Jun 2024 21:34:05 +0200
Subject: [PATCH 1/3] use strict rules for parsing PL/pgSQL expressions
Originally the rule PLpgSQL_Expr allows almost all SQL clauses. It was designed
to allow old undocumented syntax
var := col FROM tab;
The reason for support of this "strange" syntax was technical. The PLpgSQL parser
cannot use SQL parser accurately (it was really primitive), and people found
this undocumented syntax. Lattery, when it was possible to do exact parsing, from
compatibility reasons, the parsing of PL/pgSQL expressions allows described syntax.
Unfortunately, with support almost all SQL clauses, the PLpgSQL can accept
really broken code like
DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
proposed patch introduce new extra error check strict_expr_check, that solve
this issue.
---
doc/src/sgml/plpgsql.sgml | 18 ++++
src/backend/executor/spi.c | 6 ++
src/backend/parser/gram.y | 149 +++++++++++++++++++++++++++
src/backend/parser/parser.c | 6 ++
src/include/parser/parser.h | 22 ++++
src/interfaces/ecpg/preproc/parse.pl | 10 +-
src/pl/plpgsql/src/pl_comp.c | 31 ++++++
src/pl/plpgsql/src/pl_gram.y | 48 ++++++---
src/pl/plpgsql/src/pl_handler.c | 2 +
src/pl/plpgsql/src/plpgsql.h | 19 ++++
10 files changed, 297 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 78e4983139b..a27700d6cb3 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5386,6 +5386,24 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="plpgsql-extra-checks-strict-expr-check">
+ <term><varname>strict_expr_check</varname></term>
+ <listitem>
+ <para>
+ Enabling this check will cause <application>PL/pgSQL</application> to
+ check if a <application>PL/pgSQL</application> expression is just an
+ expression without any SQL clauses like <literal>FROM</literal>,
+ <literal>ORDER BY</literal>. This undocumented form of expressions
+ is allowed for compatibility reasons, but in some special cases
+ it doesn't to allow to detect broken code.
+ </para>
+
+ <para>
+ This check is allowed only <varname>plpgsql.extra_errors</varname>.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
The following example shows the effect of <varname>plpgsql.extra_warnings</varname>
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 2fb2e73604e..97443e93d44 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2984,11 +2984,17 @@ _SPI_error_callback(void *arg)
switch (carg->mode)
{
case RAW_PARSE_PLPGSQL_EXPR:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST:
+ case RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST:
errcontext("PL/pgSQL expression \"%s\"", query);
break;
case RAW_PARSE_PLPGSQL_ASSIGN1:
case RAW_PARSE_PLPGSQL_ASSIGN2:
case RAW_PARSE_PLPGSQL_ASSIGN3:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3:
errcontext("PL/pgSQL assignment \"%s\"", query);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index baca4059d2e..247ed336fc3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
PLpgSQL_Expr PLAssignStmt
+ PLpgSQLStrictExpr PLpgSQLStrictExprs PLpgSQLStrictNamedExprs
+ PLAssignStmtStrictExpr
+
+%type <target> plpgsql_strict_expr plpgsql_strict_named_expr
+%type <list> plpgsql_strict_expr_list plpgsql_strict_named_expr_list
%type <str> opt_single_name
%type <list> opt_qualified_name
@@ -812,6 +817,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%token MODE_PLPGSQL_ASSIGN1
%token MODE_PLPGSQL_ASSIGN2
%token MODE_PLPGSQL_ASSIGN3
+%token MODE_PLPGSQL_STRICT_EXPR
+%token MODE_PLPGSQL_STRICT_EXPR_LIST
+%token MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST
+%token MODE_PLPGSQL_STRICT_EXPR_ASSIGN1
+%token MODE_PLPGSQL_STRICT_EXPR_ASSIGN2
+%token MODE_PLPGSQL_STRICT_EXPR_ASSIGN3
/* Precedence: lowest to highest */
@@ -943,6 +954,46 @@ parse_toplevel:
pg_yyget_extra(yyscanner)->parsetree =
list_make1(makeRawStmt((Node *) n, @2));
}
+ | MODE_PLPGSQL_STRICT_EXPR PLpgSQLStrictExpr
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_LIST PLpgSQLStrictExprs
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST PLpgSQLStrictNamedExprs
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_ASSIGN1 PLAssignStmtStrictExpr
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+
+ n->nnames = 1;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_ASSIGN2 PLAssignStmtStrictExpr
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+
+ n->nnames = 2;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_ASSIGN3 PLAssignStmtStrictExpr
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+
+ n->nnames = 3;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+
;
/*
@@ -17488,6 +17539,104 @@ plassign_equals: COLON_EQUALS
| '='
;
+/*
+ * In "strict" mode plpgsql expressions are just an a_expr. From compatibility
+ * reasons (with default mode) it returns SelectStmt still.
+ */
+PLpgSQLStrictExpr: a_expr
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ ResTarget *rt = makeNode(ResTarget);
+
+ rt->name = NULL;
+ rt->indirection = NIL;
+ rt->val = (Node *) $1;
+ rt->location = @1;
+
+ n->targetList = list_make1((Node *) rt);
+ $$ = (Node *) n;
+ }
+ ;
+
+PLAssignStmtStrictExpr: plassign_target opt_indirection plassign_equals PLpgSQLStrictExpr
+ {
+ PLAssignStmt *n = makeNode(PLAssignStmt);
+
+ n->name = $1;
+ n->indirection = check_indirection($2, yyscanner);
+ /* nnames will be filled by calling production */
+ n->val = (SelectStmt *) $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+/*
+ * Used for unnamed plpgsql cursor's argument and plpgsql case in
+ * "strict" mode.
+ */
+PLpgSQLStrictExprs: plpgsql_strict_expr_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $1;
+ $$ = (Node *) n;
+ }
+ ;
+
+plpgsql_strict_expr_list:
+ plpgsql_strict_expr
+ {
+ $$ = list_make1($1);
+ }
+ | plpgsql_strict_expr_list ',' plpgsql_strict_expr
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
+plpgsql_strict_expr: a_expr
+ {
+ $$ = makeNode(ResTarget);
+ $$->name = NULL;
+ $$->indirection = NIL;
+ $$->val = (Node *) $1;
+ $$->location = @1;
+ }
+ ;
+
+/*
+ * Used for named cursor's arguments in "strict" mode
+ */
+PLpgSQLStrictNamedExprs: plpgsql_strict_named_expr_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $1;
+ $$ = (Node *) n;
+ }
+ ;
+
+plpgsql_strict_named_expr_list:
+ plpgsql_strict_named_expr
+ {
+ $$ = list_make1($1);
+ }
+ | plpgsql_strict_named_expr_list ',' plpgsql_strict_named_expr
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
+plpgsql_strict_named_expr: a_expr AS ColLabel
+ {
+ $$ = makeNode(ResTarget);
+ $$->name = $3;
+ $$->indirection = NIL;
+ $$->val = (Node *) $1;
+ $$->location = @1;
+ }
+ ;
/*
* Name classification hierarchy.
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 118488c3f30..9d2dde67939 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -62,6 +62,12 @@ raw_parser(const char *str, RawParseMode mode)
[RAW_PARSE_PLPGSQL_ASSIGN1] = MODE_PLPGSQL_ASSIGN1,
[RAW_PARSE_PLPGSQL_ASSIGN2] = MODE_PLPGSQL_ASSIGN2,
[RAW_PARSE_PLPGSQL_ASSIGN3] = MODE_PLPGSQL_ASSIGN3,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR] = MODE_PLPGSQL_STRICT_EXPR,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST] = MODE_PLPGSQL_STRICT_EXPR_LIST,
+ [RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST] = MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN3,
};
yyextra.have_lookahead = true;
diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
index be184ec5066..8e41907bd4d 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -33,6 +33,22 @@
* RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement,
* and return a one-element List containing a RawStmt node. "n"
* gives the number of dotted names comprising the target ColumnRef.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR: parse a PL/pgSQL expression, and
+ * return a one-element List containing a RwaStmt node. The result is
+ * compatible with RAW_PARSE_PLPGSQL_EXPR, but parser allows only
+ * a_expr (instead almost all complete query).
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST: parse a comma separated list
+ * of PL/pgSQL expressions (only a_expr are allowed). It is used by
+ * PLpGSQL CASE and OPEN commands.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST: parse a comma separated
+ * list of a_expr node with labels. It is used for evaluation of
+ * named arguments of PLpgSQL OPEN (cursor) statement.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGNn: parse a PL/pgSQL assignment
+ * statement, but only a_expr are allowed).
*/
typedef enum
{
@@ -42,6 +58,12 @@ typedef enum
RAW_PARSE_PLPGSQL_ASSIGN1,
RAW_PARSE_PLPGSQL_ASSIGN2,
RAW_PARSE_PLPGSQL_ASSIGN3,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3,
} RawParseMode;
/* Values for the backslash_quote GUC */
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 86943ae2537..9b6ad467ac6 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -70,7 +70,15 @@ my %replace_types = (
'PLpgSQL_Expr' => 'ignore',
'PLAssignStmt' => 'ignore',
'plassign_target' => 'ignore',
- 'plassign_equals' => 'ignore',);
+ 'plassign_equals' => 'ignore',
+ 'plpgsql_strict_expr' => 'ignore',
+ 'plpgsql_strict_named_expr' => 'ignore',
+ 'plpgsql_strict_expr_list' => 'ignore',
+ 'plpgsql_strict_named_expr_list' => 'ignore',
+ 'PLpgSQLStrictExpr' => 'ignore',
+ 'PLpgSQLStrictExprs' => 'ignore',
+ 'PLpgSQLStrictNamedExprs' => 'ignore',
+ 'PLAssignStmtStrictExpr' => 'ignore',);
my %replace_types_used;
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index f1bce708d62..8f9d3ed64d2 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -84,6 +84,23 @@ static const ExceptionLabelMap exception_label_map[] = {
{NULL, 0}
};
+static const PLpgSQL_parse_modes default_parse_modes = {
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_ASSIGN1,
+ RAW_PARSE_PLPGSQL_ASSIGN2,
+ RAW_PARSE_PLPGSQL_ASSIGN3
+};
+
+static const PLpgSQL_parse_modes strict_expr_parse_modes = {
+ RAW_PARSE_PLPGSQL_STRICT_EXPR,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3
+};
/* ----------
* static prototypes
@@ -355,6 +372,11 @@ do_compile(FunctionCallInfo fcinfo,
function->extra_warnings = forValidator ? plpgsql_extra_warnings : 0;
function->extra_errors = forValidator ? plpgsql_extra_errors : 0;
+ if (function->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->pmodes = &strict_expr_parse_modes;
+ else
+ function->pmodes = &default_parse_modes;
+
if (is_dml_trigger)
function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
else if (is_event_trigger)
@@ -898,6 +920,15 @@ plpgsql_compile_inline(char *proc_source)
function->extra_warnings = 0;
function->extra_errors = 0;
+ /*
+ * Although function->extra_errors is disabled, we want to
+ * do strict_expr_check inside annoymous block too.
+ */
+ if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->pmodes = &strict_expr_parse_modes;
+ else
+ function->pmodes = &default_parse_modes;
+
function->nstatements = 0;
function->requires_procedure_resowner = false;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 8182ce28aa1..d3bf9ab6a5f 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -183,6 +183,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <expr> expr_until_semi
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
+%type <expr> expressions_until_then
%type <var> cursor_variable
%type <datum> decl_cursor_arg
@@ -962,13 +963,13 @@ stmt_assign : T_DATUM
switch ($1.ident ? 1 : list_length($1.idents))
{
case 1:
- pmode = RAW_PARSE_PLPGSQL_ASSIGN1;
+ pmode = plpgsql_curr_compile->pmodes->pmode_assign1;
break;
case 2:
- pmode = RAW_PARSE_PLPGSQL_ASSIGN2;
+ pmode = plpgsql_curr_compile->pmodes->pmode_assign2;
break;
case 3:
- pmode = RAW_PARSE_PLPGSQL_ASSIGN3;
+ pmode = plpgsql_curr_compile->pmodes->pmode_assign3;
break;
default:
elog(ERROR, "unexpected number of names");
@@ -1244,7 +1245,7 @@ case_when_list : case_when_list case_when
}
;
-case_when : K_WHEN expr_until_then proc_sect
+case_when : K_WHEN expressions_until_then proc_sect
{
PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
@@ -1274,6 +1275,14 @@ opt_case_else :
}
;
+expressions_until_then :
+ {
+ $$ = read_sql_construct(K_THEN, 0, 0, "THEN",
+ plpgsql_curr_compile->pmodes->pmode_expr_list,
+ true, true, NULL, NULL);
+ }
+ ;
+
stmt_loop : opt_loop_label K_LOOP loop_body
{
PLpgSQL_stmt_loop *new;
@@ -1493,7 +1502,8 @@ for_control : for_variable K_IN
* Relabel first expression as an expression;
* then we can check its syntax.
*/
- expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ expr1->parseMode = plpgsql_curr_compile->pmodes->pmode_expr;
+
check_sql_expr(expr1->query, expr1->parseMode,
expr1loc);
@@ -1863,6 +1873,8 @@ stmt_raise : K_RAISE
*/
if (tok == SCONST)
{
+ RawParseMode pmode_expr;
+
/* old style message and parameters */
new->message = yylval.str;
/*
@@ -1875,13 +1887,15 @@ stmt_raise : K_RAISE
if (tok != ',' && tok != ';' && tok != K_USING)
yyerror("syntax error");
+ pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
+
while (tok == ',')
{
PLpgSQL_expr *expr;
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &tok);
new->params = lappend(new->params, expr);
@@ -2015,10 +2029,13 @@ stmt_dynexecute : K_EXECUTE
PLpgSQL_stmt_dynexecute *new;
PLpgSQL_expr *expr;
int endtoken;
+ RawParseMode pmode_expr;
+
+ pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &endtoken);
@@ -2057,7 +2074,7 @@ stmt_dynexecute : K_EXECUTE
{
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &endtoken);
new->params = lappend(new->params, expr);
@@ -2642,7 +2659,7 @@ static PLpgSQL_expr *
read_sql_expression(int until, const char *expected)
{
return read_sql_construct(until, 0, 0, expected,
- RAW_PARSE_PLPGSQL_EXPR,
+ plpgsql_curr_compile->pmodes->pmode_expr,
true, true, NULL, NULL);
}
@@ -2652,7 +2669,7 @@ read_sql_expression2(int until, int until2, const char *expected,
int *endtoken)
{
return read_sql_construct(until, until2, 0, expected,
- RAW_PARSE_PLPGSQL_EXPR,
+ plpgsql_curr_compile->pmodes->pmode_expr,
true, true, NULL, endtoken);
}
@@ -3845,6 +3862,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
char **argv;
StringInfoData ds;
bool any_named = false;
+ RawParseMode pmode_expr;
tok = yylex();
if (cursor->cursor_explicit_argrow < 0)
@@ -3871,6 +3889,8 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
cursor->refname),
parser_errposition(yylloc)));
+ pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
+
/*
* Read the arguments, one by one.
*/
@@ -3941,7 +3961,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
*/
item = read_sql_construct(',', ')', 0,
",\" or \")",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &endtoken);
@@ -3982,7 +4002,9 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
- expr->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ expr->parseMode = any_named ?
+ plpgsql_curr_compile->pmodes->pmode_named_expr_list
+ : plpgsql_curr_compile->pmodes->pmode_expr_list;
expr->plan = NULL;
expr->paramnos = NULL;
expr->target_param = -1;
@@ -4156,7 +4178,7 @@ make_case(int location, PLpgSQL_expr *t_expr,
StringInfoData ds;
/* We expect to have expressions not statements */
- Assert(expr->parseMode == RAW_PARSE_PLPGSQL_EXPR);
+ Assert(expr->parseMode == plpgsql_curr_compile->pmodes->pmode_expr_list);
/* Do the string hacking */
initStringInfo(&ds);
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index adfbbc8a7b7..5cb779a85b6 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -94,6 +94,8 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
+ else if (pg_strcasecmp(tok, "strict_expr_check") == 0)
+ extrachecks |= PLPGSQL_XCHECK_STRICTEXPRCHECK;
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
{
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 50c3b28472b..b2fb67e8514 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -960,6 +960,21 @@ typedef enum PLpgSQL_trigtype
PLPGSQL_NOT_TRIGGER,
} PLpgSQL_trigtype;
+/*
+ * Raw parse modes, that should be used for expressions,
+ * assignment, expression's list. When extra_errors.strict_expr_check
+ * is active, then only a_expr parsing is allowed.
+ */
+typedef struct PLpgSQL_parse_modes
+{
+ RawParseMode pmode_expr;
+ RawParseMode pmode_expr_list;
+ RawParseMode pmode_named_expr_list;
+ RawParseMode pmode_assign1;
+ RawParseMode pmode_assign2;
+ RawParseMode pmode_assign3;
+} PLpgSQL_parse_modes;
+
/*
* Complete compiled function
*/
@@ -1010,6 +1025,9 @@ typedef struct PLpgSQL_function
unsigned int nstatements; /* counter for assigning stmtids */
bool requires_procedure_resowner; /* contains CALL or DO? */
+ /* Raw parse modes configuration */
+ const PLpgSQL_parse_modes *pmodes;
+
/* these fields change when the function is used */
struct PLpgSQL_execstate *cur_estate;
unsigned long use_count;
@@ -1204,6 +1222,7 @@ extern bool plpgsql_check_asserts;
#define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
+#define PLPGSQL_XCHECK_STRICTEXPRCHECK (1 << 4)
#define PLPGSQL_XCHECK_ALL ((int) ~0)
extern int plpgsql_extra_warnings;
--
2.47.0
Hi
fresh rebase
Regards
Pavel
Attachments:
0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchtext/x-patch; charset=US-ASCII; name=0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchDownload
From 4e582d825af9d3463be332a90b9e959980480293 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Wed, 12 Jun 2024 21:34:05 +0200
Subject: [PATCH 1/3] use strict rules for parsing PL/pgSQL expressions
Originally the rule PLpgSQL_Expr allows almost all SQL clauses. It was designed
to allow old undocumented syntax
var := col FROM tab;
The reason for support of this "strange" syntax was technical. The PLpgSQL parser
cannot use SQL parser accurately (it was really primitive), and people found
this undocumented syntax. Lattery, when it was possible to do exact parsing, from
compatibility reasons, the parsing of PL/pgSQL expressions allows described syntax.
Unfortunately, with support almost all SQL clauses, the PLpgSQL can accept
really broken code like
DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
proposed patch introduce new extra error check strict_expr_check, that solve
this issue.
---
doc/src/sgml/plpgsql.sgml | 18 ++++
src/backend/executor/spi.c | 6 ++
src/backend/parser/gram.y | 149 +++++++++++++++++++++++++++
src/backend/parser/parser.c | 6 ++
src/include/parser/parser.h | 22 ++++
src/interfaces/ecpg/preproc/parse.pl | 10 +-
src/pl/plpgsql/src/pl_comp.c | 31 ++++++
src/pl/plpgsql/src/pl_gram.y | 49 ++++++---
src/pl/plpgsql/src/pl_handler.c | 2 +
src/pl/plpgsql/src/plpgsql.h | 19 ++++
10 files changed, 298 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 78e4983139..a27700d6cb 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5386,6 +5386,24 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="plpgsql-extra-checks-strict-expr-check">
+ <term><varname>strict_expr_check</varname></term>
+ <listitem>
+ <para>
+ Enabling this check will cause <application>PL/pgSQL</application> to
+ check if a <application>PL/pgSQL</application> expression is just an
+ expression without any SQL clauses like <literal>FROM</literal>,
+ <literal>ORDER BY</literal>. This undocumented form of expressions
+ is allowed for compatibility reasons, but in some special cases
+ it doesn't to allow to detect broken code.
+ </para>
+
+ <para>
+ This check is allowed only <varname>plpgsql.extra_errors</varname>.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
The following example shows the effect of <varname>plpgsql.extra_warnings</varname>
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index ecb2e4ccaa..f9d2703162 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2984,11 +2984,17 @@ _SPI_error_callback(void *arg)
switch (carg->mode)
{
case RAW_PARSE_PLPGSQL_EXPR:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST:
+ case RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST:
errcontext("PL/pgSQL expression \"%s\"", query);
break;
case RAW_PARSE_PLPGSQL_ASSIGN1:
case RAW_PARSE_PLPGSQL_ASSIGN2:
case RAW_PARSE_PLPGSQL_ASSIGN3:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2:
+ case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3:
errcontext("PL/pgSQL assignment \"%s\"", query);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6079de70e0..1b72db6952 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -312,6 +312,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
PLpgSQL_Expr PLAssignStmt
+ PLpgSQLStrictExpr PLpgSQLStrictExprs PLpgSQLStrictNamedExprs
+ PLAssignStmtStrictExpr
+
+%type <target> plpgsql_strict_expr plpgsql_strict_named_expr
+%type <list> plpgsql_strict_expr_list plpgsql_strict_named_expr_list
%type <str> opt_single_name
%type <list> opt_qualified_name
@@ -814,6 +819,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%token MODE_PLPGSQL_ASSIGN1
%token MODE_PLPGSQL_ASSIGN2
%token MODE_PLPGSQL_ASSIGN3
+%token MODE_PLPGSQL_STRICT_EXPR
+%token MODE_PLPGSQL_STRICT_EXPR_LIST
+%token MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST
+%token MODE_PLPGSQL_STRICT_EXPR_ASSIGN1
+%token MODE_PLPGSQL_STRICT_EXPR_ASSIGN2
+%token MODE_PLPGSQL_STRICT_EXPR_ASSIGN3
/* Precedence: lowest to highest */
@@ -945,6 +956,46 @@ parse_toplevel:
pg_yyget_extra(yyscanner)->parsetree =
list_make1(makeRawStmt((Node *) n, @2));
}
+ | MODE_PLPGSQL_STRICT_EXPR PLpgSQLStrictExpr
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_LIST PLpgSQLStrictExprs
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST PLpgSQLStrictNamedExprs
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_ASSIGN1 PLAssignStmtStrictExpr
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+
+ n->nnames = 1;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_ASSIGN2 PLAssignStmtStrictExpr
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+
+ n->nnames = 2;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+ | MODE_PLPGSQL_STRICT_EXPR_ASSIGN3 PLAssignStmtStrictExpr
+ {
+ PLAssignStmt *n = (PLAssignStmt *) $2;
+
+ n->nnames = 3;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
+
;
/*
@@ -17562,6 +17613,104 @@ plassign_equals: COLON_EQUALS
| '='
;
+/*
+ * In "strict" mode plpgsql expressions are just an a_expr. From compatibility
+ * reasons (with default mode) it returns SelectStmt still.
+ */
+PLpgSQLStrictExpr: a_expr
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ ResTarget *rt = makeNode(ResTarget);
+
+ rt->name = NULL;
+ rt->indirection = NIL;
+ rt->val = (Node *) $1;
+ rt->location = @1;
+
+ n->targetList = list_make1((Node *) rt);
+ $$ = (Node *) n;
+ }
+ ;
+
+PLAssignStmtStrictExpr: plassign_target opt_indirection plassign_equals PLpgSQLStrictExpr
+ {
+ PLAssignStmt *n = makeNode(PLAssignStmt);
+
+ n->name = $1;
+ n->indirection = check_indirection($2, yyscanner);
+ /* nnames will be filled by calling production */
+ n->val = (SelectStmt *) $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+/*
+ * Used for unnamed plpgsql cursor's argument and plpgsql case in
+ * "strict" mode.
+ */
+PLpgSQLStrictExprs: plpgsql_strict_expr_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $1;
+ $$ = (Node *) n;
+ }
+ ;
+
+plpgsql_strict_expr_list:
+ plpgsql_strict_expr
+ {
+ $$ = list_make1($1);
+ }
+ | plpgsql_strict_expr_list ',' plpgsql_strict_expr
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
+plpgsql_strict_expr: a_expr
+ {
+ $$ = makeNode(ResTarget);
+ $$->name = NULL;
+ $$->indirection = NIL;
+ $$->val = (Node *) $1;
+ $$->location = @1;
+ }
+ ;
+
+/*
+ * Used for named cursor's arguments in "strict" mode
+ */
+PLpgSQLStrictNamedExprs: plpgsql_strict_named_expr_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $1;
+ $$ = (Node *) n;
+ }
+ ;
+
+plpgsql_strict_named_expr_list:
+ plpgsql_strict_named_expr
+ {
+ $$ = list_make1($1);
+ }
+ | plpgsql_strict_named_expr_list ',' plpgsql_strict_named_expr
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
+plpgsql_strict_named_expr: a_expr AS ColLabel
+ {
+ $$ = makeNode(ResTarget);
+ $$->name = $3;
+ $$->indirection = NIL;
+ $$->val = (Node *) $1;
+ $$->location = @1;
+ }
+ ;
/*
* Name classification hierarchy.
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 33a040506b..19fe3b4194 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -62,6 +62,12 @@ raw_parser(const char *str, RawParseMode mode)
[RAW_PARSE_PLPGSQL_ASSIGN1] = MODE_PLPGSQL_ASSIGN1,
[RAW_PARSE_PLPGSQL_ASSIGN2] = MODE_PLPGSQL_ASSIGN2,
[RAW_PARSE_PLPGSQL_ASSIGN3] = MODE_PLPGSQL_ASSIGN3,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR] = MODE_PLPGSQL_STRICT_EXPR,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST] = MODE_PLPGSQL_STRICT_EXPR_LIST,
+ [RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST] = MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+ [RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN3,
};
yyextra.have_lookahead = true;
diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
index 350196cd64..e96dce37db 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -33,6 +33,22 @@
* RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement,
* and return a one-element List containing a RawStmt node. "n"
* gives the number of dotted names comprising the target ColumnRef.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR: parse a PL/pgSQL expression, and
+ * return a one-element List containing a RwaStmt node. The result is
+ * compatible with RAW_PARSE_PLPGSQL_EXPR, but parser allows only
+ * a_expr (instead almost all complete query).
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST: parse a comma separated list
+ * of PL/pgSQL expressions (only a_expr are allowed). It is used by
+ * PLpGSQL CASE and OPEN commands.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST: parse a comma separated
+ * list of a_expr node with labels. It is used for evaluation of
+ * named arguments of PLpgSQL OPEN (cursor) statement.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGNn: parse a PL/pgSQL assignment
+ * statement, but only a_expr are allowed).
*/
typedef enum
{
@@ -42,6 +58,12 @@ typedef enum
RAW_PARSE_PLPGSQL_ASSIGN1,
RAW_PARSE_PLPGSQL_ASSIGN2,
RAW_PARSE_PLPGSQL_ASSIGN3,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3,
} RawParseMode;
/* Values for the backslash_quote GUC */
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index ad9aec63cb..3a209cffd3 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -70,7 +70,15 @@ my %replace_types = (
'PLpgSQL_Expr' => 'ignore',
'PLAssignStmt' => 'ignore',
'plassign_target' => 'ignore',
- 'plassign_equals' => 'ignore',);
+ 'plassign_equals' => 'ignore',
+ 'plpgsql_strict_expr' => 'ignore',
+ 'plpgsql_strict_named_expr' => 'ignore',
+ 'plpgsql_strict_expr_list' => 'ignore',
+ 'plpgsql_strict_named_expr_list' => 'ignore',
+ 'PLpgSQLStrictExpr' => 'ignore',
+ 'PLpgSQLStrictExprs' => 'ignore',
+ 'PLpgSQLStrictNamedExprs' => 'ignore',
+ 'PLAssignStmtStrictExpr' => 'ignore',);
my %replace_types_used;
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 9dc8218292..172f0196ce 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -83,6 +83,23 @@ static const ExceptionLabelMap exception_label_map[] = {
{NULL, 0}
};
+static const PLpgSQL_parse_modes default_parse_modes = {
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_EXPR,
+ RAW_PARSE_PLPGSQL_ASSIGN1,
+ RAW_PARSE_PLPGSQL_ASSIGN2,
+ RAW_PARSE_PLPGSQL_ASSIGN3
+};
+
+static const PLpgSQL_parse_modes strict_expr_parse_modes = {
+ RAW_PARSE_PLPGSQL_STRICT_EXPR,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+ RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3
+};
/* ----------
* static prototypes
@@ -362,6 +379,11 @@ do_compile(FunctionCallInfo fcinfo,
function->extra_warnings = forValidator ? plpgsql_extra_warnings : 0;
function->extra_errors = forValidator ? plpgsql_extra_errors : 0;
+ if (function->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->pmodes = &strict_expr_parse_modes;
+ else
+ function->pmodes = &default_parse_modes;
+
if (is_dml_trigger)
function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
else if (is_event_trigger)
@@ -907,6 +929,15 @@ plpgsql_compile_inline(char *proc_source)
function->extra_warnings = 0;
function->extra_errors = 0;
+ /*
+ * Although function->extra_errors is disabled, we want to
+ * do strict_expr_check inside annoymous block too.
+ */
+ if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->pmodes = &strict_expr_parse_modes;
+ else
+ function->pmodes = &default_parse_modes;
+
function->nstatements = 0;
function->requires_procedure_resowner = false;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 063ed81f05..9fbcf5477d 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -188,6 +188,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <expr> expr_until_semi
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
+%type <expr> expressions_until_then
%type <var> cursor_variable
%type <datum> decl_cursor_arg
@@ -969,13 +970,13 @@ stmt_assign : T_DATUM
switch ($1.ident ? 1 : list_length($1.idents))
{
case 1:
- pmode = RAW_PARSE_PLPGSQL_ASSIGN1;
+ pmode = plpgsql_curr_compile->pmodes->pmode_assign1;
break;
case 2:
- pmode = RAW_PARSE_PLPGSQL_ASSIGN2;
+ pmode = plpgsql_curr_compile->pmodes->pmode_assign2;
break;
case 3:
- pmode = RAW_PARSE_PLPGSQL_ASSIGN3;
+ pmode = plpgsql_curr_compile->pmodes->pmode_assign3;
break;
default:
elog(ERROR, "unexpected number of names");
@@ -1252,7 +1253,7 @@ case_when_list : case_when_list case_when
}
;
-case_when : K_WHEN expr_until_then proc_sect
+case_when : K_WHEN expressions_until_then proc_sect
{
PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
@@ -1282,6 +1283,15 @@ opt_case_else :
}
;
+expressions_until_then :
+ {
+ $$ = read_sql_construct(K_THEN, 0, 0, "THEN",
+ plpgsql_curr_compile->pmodes->pmode_expr_list,
+ true, true, NULL, NULL,
+ &yylval, &yylloc, yyscanner);
+ }
+ ;
+
stmt_loop : opt_loop_label K_LOOP loop_body
{
PLpgSQL_stmt_loop *new;
@@ -1501,7 +1511,8 @@ for_control : for_variable K_IN
* Relabel first expression as an expression;
* then we can check its syntax.
*/
- expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ expr1->parseMode = plpgsql_curr_compile->pmodes->pmode_expr;
+
check_sql_expr(expr1->query, expr1->parseMode,
expr1loc, yyscanner);
@@ -1873,6 +1884,8 @@ stmt_raise : K_RAISE
*/
if (tok == SCONST)
{
+ RawParseMode pmode_expr;
+
/* old style message and parameters */
new->message = yylval.str;
/*
@@ -1885,13 +1898,15 @@ stmt_raise : K_RAISE
if (tok != ',' && tok != ';' && tok != K_USING)
yyerror(&yylloc, yyscanner, "syntax error");
+ pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
+
while (tok == ',')
{
PLpgSQL_expr *expr;
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &tok,
&yylval, &yylloc, yyscanner);
@@ -2026,10 +2041,13 @@ stmt_dynexecute : K_EXECUTE
PLpgSQL_stmt_dynexecute *new;
PLpgSQL_expr *expr;
int endtoken;
+ RawParseMode pmode_expr;
+
+ pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
@@ -2069,7 +2087,7 @@ stmt_dynexecute : K_EXECUTE
{
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
@@ -2655,7 +2673,7 @@ static PLpgSQL_expr *
read_sql_expression(int until, const char *expected, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
return read_sql_construct(until, 0, 0, expected,
- RAW_PARSE_PLPGSQL_EXPR,
+ plpgsql_curr_compile->pmodes->pmode_expr,
true, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2666,7 +2684,7 @@ read_sql_expression2(int until, int until2, const char *expected,
int *endtoken, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
return read_sql_construct(until, until2, 0, expected,
- RAW_PARSE_PLPGSQL_EXPR,
+ plpgsql_curr_compile->pmodes->pmode_expr,
true, true, NULL, endtoken,
yylvalp, yyllocp, yyscanner);
}
@@ -3869,6 +3887,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
char **argv;
StringInfoData ds;
bool any_named = false;
+ RawParseMode pmode_expr;
tok = yylex(yylvalp, yyllocp, yyscanner);
if (cursor->cursor_explicit_argrow < 0)
@@ -3895,6 +3914,8 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
cursor->refname),
parser_errposition(*yyllocp)));
+ pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
+
/*
* Read the arguments, one by one.
*/
@@ -3965,7 +3986,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
*/
item = read_sql_construct(',', ')', 0,
",\" or \")",
- RAW_PARSE_PLPGSQL_EXPR,
+ pmode_expr,
true, true,
NULL, &endtoken,
yylvalp, yyllocp, yyscanner);
@@ -4007,7 +4028,9 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
- expr->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ expr->parseMode = any_named ?
+ plpgsql_curr_compile->pmodes->pmode_named_expr_list
+ : plpgsql_curr_compile->pmodes->pmode_expr_list;
expr->plan = NULL;
expr->paramnos = NULL;
expr->target_param = -1;
@@ -4181,7 +4204,7 @@ make_case(int location, PLpgSQL_expr *t_expr,
StringInfoData ds;
/* We expect to have expressions not statements */
- Assert(expr->parseMode == RAW_PARSE_PLPGSQL_EXPR);
+ Assert(expr->parseMode == plpgsql_curr_compile->pmodes->pmode_expr_list);
/* Do the string hacking */
initStringInfo(&ds);
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 5af38d5773..3ce196de58 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -94,6 +94,8 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
+ else if (pg_strcasecmp(tok, "strict_expr_check") == 0)
+ extrachecks |= PLPGSQL_XCHECK_STRICTEXPRCHECK;
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
{
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index c3ce4161a3..8a4b9f7636 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -960,6 +960,21 @@ typedef enum PLpgSQL_trigtype
PLPGSQL_NOT_TRIGGER,
} PLpgSQL_trigtype;
+/*
+ * Raw parse modes, that should be used for expressions,
+ * assignment, expression's list. When extra_errors.strict_expr_check
+ * is active, then only a_expr parsing is allowed.
+ */
+typedef struct PLpgSQL_parse_modes
+{
+ RawParseMode pmode_expr;
+ RawParseMode pmode_expr_list;
+ RawParseMode pmode_named_expr_list;
+ RawParseMode pmode_assign1;
+ RawParseMode pmode_assign2;
+ RawParseMode pmode_assign3;
+} PLpgSQL_parse_modes;
+
/*
* Complete compiled function
*/
@@ -1010,6 +1025,9 @@ typedef struct PLpgSQL_function
unsigned int nstatements; /* counter for assigning stmtids */
bool requires_procedure_resowner; /* contains CALL or DO? */
+ /* Raw parse modes configuration */
+ const PLpgSQL_parse_modes *pmodes;
+
/* these fields change when the function is used */
struct PLpgSQL_execstate *cur_estate;
unsigned long use_count;
@@ -1204,6 +1222,7 @@ extern bool plpgsql_check_asserts;
#define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
+#define PLPGSQL_XCHECK_STRICTEXPRCHECK (1 << 4)
#define PLPGSQL_XCHECK_ALL ((int) ~0)
extern int plpgsql_extra_warnings;
--
2.47.1
0002-simply-check-of-strict-expr-check-on-regress-test.patchtext/x-patch; charset=US-ASCII; name=0002-simply-check-of-strict-expr-check-on-regress-test.patchDownload
From d7bda22eb655450cf0ac5fdd910bd574f5737e88 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 08:13:53 +0200
Subject: [PATCH 2/3] simply check of strict-expr-check on regress test
This patch enable strict-expr-check by default to be possible to see
the impact of this option on regress test. Next commit will revert
this option. The strict-expr-check should not be enabled by default.
This commit is done just for testing.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 +++++++++++--------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 0015053e0f..280ff3e022 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 14e236d57a..2c127a821f 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index ad60e0e8be..6819c0435f 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,34 +50,38 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 0 columns
-CONTEXT: PL/pgSQL assignment "a := from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select from onecol)
+ ^
+QUERY: a := (select from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 2 columns
-CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select f1, f1 from onecol)
+ ^
+QUERY: a := (select f1, f1 from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: query returned more than one row
-CONTEXT: query: a := f1 from onecol
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: more than one row returned by a subquery used as an expression
+CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 3ce196de58..5d5b25b830 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -50,7 +50,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors;
+int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -193,7 +193,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "none",
+ "strict_expr_check",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 4b9ff51594..699713696d 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,31 +46,31 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index f408d4f69b..7d004b0403 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
+ wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
+ IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index dd8cdec290..754ac0cb8c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2622,7 +2622,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4e59188196..faae99515f 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 0a6945581b..0e25138b91 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index a4c7be487e..c98e6f2247 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -327,7 +327,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 7f5757e89c..3458dad174 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 84e93ef575..76b7519678 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1664,7 +1664,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 4b2f11dcc6..a3dbd93468 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 18c91572ae..fbc21b1f52 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 5c786b16c6..16454725f5 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -209,7 +209,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 51ae1b31b3..1981955ac6 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.47.1
0003-set-plpgsql.extra_errors-to-none.patchtext/x-patch; charset=US-ASCII; name=0003-set-plpgsql.extra_errors-to-none.patchDownload
From 3bcc9d2b5640e7815179ab375fde1b3acaa0ec0a Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 15:52:38 +0200
Subject: [PATCH 3/3] set plpgsql.extra_errors to "none"
Purpose of previous commit was to run tests with active strict_expr_check.
Now, reset to default and revert all changes from previous commit.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 ++++++++-----------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 52 insertions(+), 56 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 280ff3e022..0015053e0f 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 2c127a821f..14e236d57a 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index 6819c0435f..ad60e0e8be 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,38 +50,34 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select from onecol)
- ^
-QUERY: a := (select from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select f1, f1 from onecol)
- ^
-QUERY: a := (select f1, f1 from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: more than one row returned by a subquery used as an expression
-CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query returned more than one row
+CONTEXT: query: a := f1 from onecol
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 5d5b25b830..3ce196de58 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -50,7 +50,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+int plpgsql_extra_errors;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -193,7 +193,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "strict_expr_check",
+ "none",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 699713696d..4b9ff51594 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,31 +46,31 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
+begin a := from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index 7d004b0403..f408d4f69b 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
+ wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
+ IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 754ac0cb8c..dd8cdec290 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2622,7 +2622,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index faae99515f..4e59188196 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 0e25138b91..0a6945581b 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index c98e6f2247..a4c7be487e 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -327,7 +327,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 3458dad174..7f5757e89c 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 76b7519678..84e93ef575 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1664,7 +1664,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a3dbd93468..4b2f11dcc6 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index fbc21b1f52..18c91572ae 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 16454725f5..5c786b16c6 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -209,7 +209,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 1981955ac6..51ae1b31b3 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.47.1
Hi
I rewrote this patch. Instead of enhancing the main SQL parser, it does
post parser checks of the parse tree.
Now the patch is significantly less invasive (changes are just in plpgsql -
mostly in grammar), and it is smaller (without regress tests it has half
size).
This patch allows the detection of usage of undocumented syntax for plpgsql
expressions. Using this undocumented
syntax can be the reason why badly written code (missing semicolon) can be
quietly executed without any raising of error.
Only patch 01 is important - patches 02, 03 are prepared for review.
Patch 02 activates a new check by default, and fixes the regress test to be
executed. This is important for checking for possible false alarms.
Patch 03 disables this check and returns regress tests to their original
state.
Regards
Pavel
Attachments:
v20250207-0002-simply-check-of-strict-expr-check-on-regress-test.patchtext/x-patch; charset=US-ASCII; name=v20250207-0002-simply-check-of-strict-expr-check-on-regress-test.patchDownload
From 2ae8854a83655206004a01e04230cb99756a143e Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 08:13:53 +0200
Subject: [PATCH 2/3] simply check of strict-expr-check on regress test
This patch enable strict-expr-check by default to be possible to see
the impact of this option on regress test. Next commit will revert
this option. The strict-expr-check should not be enabled by default.
This commit is done just for testing.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 +++++++++++--------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 0015053e0f2..280ff3e022e 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 14e236d57ab..2c127a821f1 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index ad60e0e8be3..6819c0435f6 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,34 +50,38 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 0 columns
-CONTEXT: PL/pgSQL assignment "a := from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select from onecol)
+ ^
+QUERY: a := (select from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 2 columns
-CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select f1, f1 from onecol)
+ ^
+QUERY: a := (select f1, f1 from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: query returned more than one row
-CONTEXT: query: a := f1 from onecol
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: more than one row returned by a subquery used as an expression
+CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 3ce196de58f..5d5b25b8308 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -50,7 +50,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors;
+int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -193,7 +193,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "none",
+ "strict_expr_check",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 4b9ff515948..699713696d4 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,31 +46,31 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index f408d4f69b6..7d004b04031 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
+ wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
+ IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 362f38856d2..67b28c98902 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2622,7 +2622,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4e59188196c..faae99515f7 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 4e21bd714c7..9930bf5f13c 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index a4c7be487ef..c98e6f2247d 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -327,7 +327,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 7f5757e89c4..3458dad1749 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 84e93ef575e..76b7519678b 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1664,7 +1664,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 4b2f11dcc64..a3dbd93468e 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index a5d40bd95c7..ef102a75378 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 5c786b16c6f..16454725f5a 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -209,7 +209,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 51ae1b31b30..1981955ac65 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.48.1
v20250207-0003-set-plpgsql.extra_errors-to-none.patchtext/x-patch; charset=US-ASCII; name=v20250207-0003-set-plpgsql.extra_errors-to-none.patchDownload
From 353075bf47ecf8ac42b8f9cdd7df692a421534c3 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 15:52:38 +0200
Subject: [PATCH 3/3] set plpgsql.extra_errors to "none"
Purpose of previous commit was to run tests with active strict_expr_check.
Now, reset to default and revert all changes from previous commit.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 ++++++++-----------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 52 insertions(+), 56 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 280ff3e022e..0015053e0f2 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 2c127a821f1..14e236d57ab 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index 6819c0435f6..ad60e0e8be3 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,38 +50,34 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select from onecol)
- ^
-QUERY: a := (select from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select f1, f1 from onecol)
- ^
-QUERY: a := (select f1, f1 from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: more than one row returned by a subquery used as an expression
-CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query returned more than one row
+CONTEXT: query: a := f1 from onecol
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 5d5b25b8308..3ce196de58f 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -50,7 +50,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+int plpgsql_extra_errors;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -193,7 +193,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "strict_expr_check",
+ "none",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 699713696d4..4b9ff515948 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,31 +46,31 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
+begin a := from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index 7d004b04031..f408d4f69b6 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
+ wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
+ IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 67b28c98902..362f38856d2 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2622,7 +2622,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index faae99515f7..4e59188196c 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 9930bf5f13c..4e21bd714c7 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index c98e6f2247d..a4c7be487ef 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -327,7 +327,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 3458dad1749..7f5757e89c4 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 76b7519678b..84e93ef575e 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1664,7 +1664,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a3dbd93468e..4b2f11dcc64 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index ef102a75378..a5d40bd95c7 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 16454725f5a..5c786b16c6f 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -209,7 +209,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 1981955ac65..51ae1b31b30 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.48.1
v20250207-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchtext/x-patch; charset=US-ASCII; name=v20250207-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchDownload
From f8dc7c84c4db7625493d7061ac53acbbedd6da58 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Wed, 12 Jun 2024 21:34:05 +0200
Subject: [PATCH 1/3] use strict rules for parsing PL/pgSQL expressions
Originally the rule PLpgSQL_Expr allows almost all SQL clauses. It was designed
to allow old undocumented syntax
var := col FROM tab;
The reason for support of this "strange" syntax was technical. The PLpgSQL parser
cannot use SQL parser accurately (it was really primitive), and people found
this undocumented syntax. Lattery, when it was possible to do exact parsing, from
compatibility reasons, the parsing of PL/pgSQL expressions allows described syntax.
Unfortunately, with support almost all SQL clauses, the PLpgSQL can accept
really broken code like
DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
proposed patch introduce new extra error check strict_expr_check, that solve
this issue.
---
doc/src/sgml/plpgsql.sgml | 18 ++++
src/pl/plpgsql/src/pl_comp.c | 7 ++
src/pl/plpgsql/src/pl_gram.y | 138 ++++++++++++++++++++++----
src/pl/plpgsql/src/pl_handler.c | 2 +
src/pl/plpgsql/src/plpgsql.h | 1 +
src/test/regress/expected/plpgsql.out | 14 +++
src/test/regress/sql/plpgsql.sql | 14 +++
7 files changed, 176 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 78e4983139b..a27700d6cb3 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5386,6 +5386,24 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="plpgsql-extra-checks-strict-expr-check">
+ <term><varname>strict_expr_check</varname></term>
+ <listitem>
+ <para>
+ Enabling this check will cause <application>PL/pgSQL</application> to
+ check if a <application>PL/pgSQL</application> expression is just an
+ expression without any SQL clauses like <literal>FROM</literal>,
+ <literal>ORDER BY</literal>. This undocumented form of expressions
+ is allowed for compatibility reasons, but in some special cases
+ it doesn't to allow to detect broken code.
+ </para>
+
+ <para>
+ This check is allowed only <varname>plpgsql.extra_errors</varname>.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
The following example shows the effect of <varname>plpgsql.extra_warnings</varname>
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index a2de0880fb7..8174721a5a1 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -904,6 +904,13 @@ plpgsql_compile_inline(char *proc_source)
function->extra_warnings = 0;
function->extra_errors = 0;
+ /*
+ * Although function->extra_errors is disabled, we want to
+ * do strict_expr_check inside annoymous block too.
+ */
+ if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+
function->nstatements = 0;
function->requires_procedure_resowner = false;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 64d2c362bf9..84a7fd4b762 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -18,6 +18,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
#include "parser/parser.h"
#include "parser/parse_type.h"
#include "parser/scanner.h"
@@ -67,6 +68,7 @@ static PLpgSQL_expr *read_sql_construct(int until,
const char *expected,
RawParseMode parsemode,
bool isexpression,
+ bool allowlist,
bool valid_sql,
int *startloc,
int *endtoken,
@@ -102,7 +104,7 @@ static PLpgSQL_row *make_scalar_list1(char *initial_name,
PLpgSQL_datum *initial_datum,
int lineno, int location, yyscan_t yyscanner);
static void check_sql_expr(const char *stmt,
- RawParseMode parseMode, int location, yyscan_t yyscanner);
+ RawParseMode parseMode, bool allowlist, int location, yyscan_t yyscanner);
static void plpgsql_sql_error_callback(void *arg);
static PLpgSQL_type *parse_datatype(const char *string, int location, yyscan_t yyscanner);
static void check_labels(const char *start_label,
@@ -113,6 +115,7 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor, int until,
YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static List *read_raise_options(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
+static bool is_strict_expr(List *parsetree, int *errpos, bool allowlist);
%}
@@ -189,6 +192,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <expr> expr_until_semi
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
+%type <expr> expressions_until_then
%type <var> cursor_variable
%type <datum> decl_cursor_arg
@@ -906,7 +910,7 @@ stmt_perform : K_PERFORM
*/
new->expr = read_sql_construct(';', 0, 0, ";",
RAW_PARSE_DEFAULT,
- false, false,
+ false, false, false,
&startloc, NULL,
&yylval, &yylloc, yyscanner);
/* overwrite "perform" ... */
@@ -916,7 +920,7 @@ stmt_perform : K_PERFORM
strlen(new->expr->query));
/* offset syntax error position to account for that */
check_sql_expr(new->expr->query, new->expr->parseMode,
- startloc + 1, yyscanner);
+ false, startloc + 1, yyscanner);
$$ = (PLpgSQL_stmt *) new;
}
@@ -993,7 +997,7 @@ stmt_assign : T_DATUM
plpgsql_push_back_token(T_DATUM, &yylval, &yylloc, yyscanner);
new->expr = read_sql_construct(';', 0, 0, ";",
pmode,
- false, true,
+ false, false, true,
NULL, NULL,
&yylval, &yylloc, yyscanner);
@@ -1253,7 +1257,7 @@ case_when_list : case_when_list case_when
}
;
-case_when : K_WHEN expr_until_then proc_sect
+case_when : K_WHEN expressions_until_then proc_sect
{
PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
@@ -1283,6 +1287,15 @@ opt_case_else :
}
;
+expressions_until_then :
+ {
+ $$ = read_sql_construct(K_THEN, 0, 0, "THEN",
+ RAW_PARSE_PLPGSQL_EXPR, /* expr_list */
+ true, true, true, NULL, NULL,
+ &yylval, &yylloc, yyscanner);
+ }
+ ;
+
stmt_loop : opt_loop_label K_LOOP loop_body
{
PLpgSQL_stmt_loop *new;
@@ -1486,6 +1499,7 @@ for_control : for_variable K_IN
RAW_PARSE_DEFAULT,
true,
false,
+ false,
&expr1loc,
&tok,
&yylval, &yylloc, yyscanner);
@@ -1504,7 +1518,7 @@ for_control : for_variable K_IN
*/
expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
check_sql_expr(expr1->query, expr1->parseMode,
- expr1loc, yyscanner);
+ false, expr1loc, yyscanner);
/* Read and check the second one */
expr2 = read_sql_expression2(K_LOOP, K_BY,
@@ -1561,7 +1575,7 @@ for_control : for_variable K_IN
/* Check syntax as a regular query */
check_sql_expr(expr1->query, expr1->parseMode,
- expr1loc, yyscanner);
+ false, expr1loc, yyscanner);
new = palloc0(sizeof(PLpgSQL_stmt_fors));
new->cmd_type = PLPGSQL_STMT_FORS;
@@ -1893,7 +1907,7 @@ stmt_raise : K_RAISE
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &tok,
&yylval, &yylloc, yyscanner);
new->params = lappend(new->params, expr);
@@ -2031,7 +2045,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
@@ -2071,7 +2085,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
new->params = lappend(new->params, expr);
@@ -2657,7 +2671,7 @@ read_sql_expression(int until, const char *expected, YYSTYPE *yylvalp, YYLTYPE *
{
return read_sql_construct(until, 0, 0, expected,
RAW_PARSE_PLPGSQL_EXPR,
- true, true, NULL, NULL,
+ true, false, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2668,7 +2682,7 @@ read_sql_expression2(int until, int until2, const char *expected,
{
return read_sql_construct(until, until2, 0, expected,
RAW_PARSE_PLPGSQL_EXPR,
- true, true, NULL, endtoken,
+ true, false, true, NULL, endtoken,
yylvalp, yyllocp, yyscanner);
}
@@ -2678,7 +2692,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
return read_sql_construct(';', 0, 0, ";",
RAW_PARSE_DEFAULT,
- false, true, NULL, NULL,
+ false, false, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2691,6 +2705,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
* expected: text to use in complaining that terminator was not found
* parsemode: raw_parser() mode to use
* isexpression: whether to say we're reading an "expression" or a "statement"
+ * allowlist: the result can be list of expressions
* valid_sql: whether to check the syntax of the expr
* startloc: if not NULL, location of first token is stored at *startloc
* endtoken: if not NULL, ending token is stored at *endtoken
@@ -2703,6 +2718,7 @@ read_sql_construct(int until,
const char *expected,
RawParseMode parsemode,
bool isexpression,
+ bool allowlist,
bool valid_sql,
int *startloc,
int *endtoken,
@@ -2804,7 +2820,7 @@ read_sql_construct(int until,
pfree(ds.data);
if (valid_sql)
- check_sql_expr(expr->query, expr->parseMode, startlocation, yyscanner);
+ check_sql_expr(expr->query, expr->parseMode, allowlist, startlocation, yyscanner);
return expr;
}
@@ -3131,7 +3147,7 @@ make_execsql_stmt(int firsttoken, int location, PLword *word, YYSTYPE *yylvalp,
expr->ns = plpgsql_ns_top();
pfree(ds.data);
- check_sql_expr(expr->query, expr->parseMode, location, yyscanner);
+ check_sql_expr(expr->query, expr->parseMode, false, location, yyscanner);
execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
@@ -3731,11 +3747,15 @@ make_scalar_list1(char *initial_name,
* If no error cursor is provided, we'll just point at "location".
*/
static void
-check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t yyscanner)
+check_sql_expr(const char *stmt,
+ RawParseMode parseMode, bool allowlist,
+ int location, yyscan_t yyscanner)
{
sql_error_callback_arg cbarg;
ErrorContextCallback syntax_errcontext;
MemoryContext oldCxt;
+ List *parsetree;
+ int errpos;
if (!plpgsql_check_syntax)
return;
@@ -3749,11 +3769,25 @@ check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t
error_context_stack = &syntax_errcontext;
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
- (void) raw_parser(stmt, parseMode);
+ parsetree = raw_parser(stmt, parseMode);
MemoryContextSwitchTo(oldCxt);
/* Restore former ereport callback */
error_context_stack = syntax_errcontext.previous;
+
+ if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_STRICTEXPRCHECK ||
+ plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ {
+ /* do this check only for expressions */
+ if (parseMode == RAW_PARSE_DEFAULT)
+ return;
+
+ if (!is_strict_expr(parsetree, &errpos, allowlist))
+ ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK ? ERROR : WARNING,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("syntax of expression is not strict"),
+ parser_errposition(errpos != -1 ? location + errpos : location)));
+ }
}
static void
@@ -3787,6 +3821,74 @@ plpgsql_sql_error_callback(void *arg)
errposition(0);
}
+/*
+ * Returns true, when the only targetList is in parsetree. Cursors
+ * can require list of expressions or list of named expressions.
+ */
+static bool
+is_strict_expr(List *parsetree, int *errpos, bool allowlist)
+{
+ RawStmt *rawstmt;
+ SelectStmt *select;
+ int targets = 0;
+ ListCell *lc;
+
+ /* Top should be RawStmt */
+ rawstmt = castNode(RawStmt, linitial(parsetree));
+
+ if (IsA(rawstmt->stmt, SelectStmt))
+ {
+ select = (SelectStmt *) rawstmt->stmt;
+ }
+ else if (IsA(rawstmt->stmt, PLAssignStmt))
+ {
+ select = castNode(SelectStmt, ((PLAssignStmt *) rawstmt->stmt)->val);
+ }
+ else
+ elog(ERROR, "unexpected node type");
+
+ if (!select->targetList)
+ {
+ *errpos = -1;
+ return false;
+ }
+ else
+ *errpos = exprLocation((Node *) select->targetList);
+
+ if (select->distinctClause ||
+ select->fromClause ||
+ select->whereClause ||
+ select->groupClause ||
+ select->groupDistinct ||
+ select->havingClause ||
+ select->windowClause ||
+ select->sortClause ||
+ select->limitOffset ||
+ select->limitCount ||
+ select->limitOption ||
+ select->lockingClause)
+ return false;
+
+ foreach(lc, select->targetList)
+ {
+ ResTarget *rt = castNode(ResTarget, lfirst(lc));
+
+ if (targets++ >= 1 && !allowlist)
+ {
+ *errpos = exprLocation((Node *) rt);
+ return false;
+ }
+
+ if (rt->name)
+ {
+ *errpos = exprLocation((Node *) rt);
+ return false;
+ }
+ }
+
+ return true;
+}
+
/*
* Parse a SQL datatype name and produce a PLpgSQL_type structure.
*
@@ -3967,7 +4069,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
item = read_sql_construct(',', ')', 0,
",\" or \")",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
yylvalp, yyllocp, yyscanner);
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 5af38d5773b..3ce196de58f 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -94,6 +94,8 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
+ else if (pg_strcasecmp(tok, "strict_expr_check") == 0)
+ extrachecks |= PLPGSQL_XCHECK_STRICTEXPRCHECK;
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
{
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 441df5354e2..1cb2e80210e 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -1204,6 +1204,7 @@ extern bool plpgsql_check_asserts;
#define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
+#define PLPGSQL_XCHECK_STRICTEXPRCHECK (1 << 4)
#define PLPGSQL_XCHECK_ALL ((int) ~0)
extern int plpgsql_extra_warnings;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 0a6945581bd..4e21bd714c7 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -3083,6 +3083,20 @@ select shadowtest(1);
t
(1 row)
+-- test of strict expression check
+set plpgsql.extra_errors to 'strict_expr_check';
+create or replace function strict_expr_check_func()
+returns void as $$
+declare var int;
+begin
+ var = 1
+ delete from pg_class where false;
+end;
+$$ language plpgsql;
+ERROR: syntax of expression is not strict
+LINE 5: var = 1
+ ^
+reset plpgsql.extra_errors;
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
do $$
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 18c91572ae1..a5d40bd95c7 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -2617,6 +2617,20 @@ declare f1 int; begin return 1; end $$ language plpgsql;
select shadowtest(1);
+-- test of strict expression check
+set plpgsql.extra_errors to 'strict_expr_check';
+
+create or replace function strict_expr_check_func()
+returns void as $$
+declare var int;
+begin
+ var = 1
+ delete from pg_class where false;
+end;
+$$ language plpgsql;
+
+reset plpgsql.extra_errors;
+
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
--
2.48.1
Le 07/02/2025 à 23:00, Pavel Stehule a écrit :
Hi
I rewrote this patch. Instead of enhancing the main SQL parser, it
does post parser checks of the parse tree.Now the patch is significantly less invasive (changes are just in
plpgsql - mostly in grammar), and it is smaller (without regress tests
it has half size).This patch allows the detection of usage of undocumented syntax for
plpgsql expressions. Using this undocumented
syntax can be the reason why badly written code (missing semicolon)
can be quietly executed without any raising of error.Only patch 01 is important - patches 02, 03 are prepared for review.
Patch 02 activates a new check by default, and fixes the regress test
to be executed. This is important for checking for possible false alarms.
Patch 03 disables this check and returns regress tests to their
original state.Regards
Pavel
Hi Pavel,
I'm reviewing this patch too and I'm facing some documentation issues
in patch
v20250207-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patch
+ it doesn't to allow to detect broken code.
I'm not very good at english but I think it should be: it doesn't allow
to detect broken code.
Here I think the sentence is not complete:
+ This check is allowed only <varname>plpgsql.extra_errors</varname>.
Do you mean: This check is allowed only when
<varname>plpgsql.extra_errors</varname> is set to 'strict_expr_check'.
Please fix these to be sure of what the code is supposed to do.
Thanks
--
Gilles Darold
http://www.darold.net/
Hi
čt 27. 2. 2025 v 16:33 odesílatel Gilles Darold <gilles@darold.net> napsal:
Le 07/02/2025 à 23:00, Pavel Stehule a écrit :
Hi
I rewrote this patch. Instead of enhancing the main SQL parser, it
does post parser checks of the parse tree.Now the patch is significantly less invasive (changes are just in
plpgsql - mostly in grammar), and it is smaller (without regress tests
it has half size).This patch allows the detection of usage of undocumented syntax for
plpgsql expressions. Using this undocumented
syntax can be the reason why badly written code (missing semicolon)
can be quietly executed without any raising of error.Only patch 01 is important - patches 02, 03 are prepared for review.
Patch 02 activates a new check by default, and fixes the regress test
to be executed. This is important for checking for possible false alarms.
Patch 03 disables this check and returns regress tests to their
original state.Regards
Pavel
Hi Pavel,
I'm reviewing this patch too and I'm facing some documentation issues
in patch
v20250207-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patch+ it doesn't to allow to detect broken code.
I'm not very good at english but I think it should be: it doesn't allow
to detect broken code.
fixed
Here I think the sentence is not complete:
+ This check is allowed only
<varname>plpgsql.extra_errors</varname>.Do you mean: This check is allowed only when
<varname>plpgsql.extra_errors</varname> is set to 'strict_expr_check'.Please fix these to be sure of what the code is supposed to do.
fixed
Regards
Pavel
Show quoted text
Thanks
--
Gilles Darold
http://www.darold.net/
Attachments:
v20250227-0003-set-plpgsql.extra_errors-to-none.patchtext/x-patch; charset=US-ASCII; name=v20250227-0003-set-plpgsql.extra_errors-to-none.patchDownload
From 1c068d87037687114a80b612d9adc11a174cc392 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 15:52:38 +0200
Subject: [PATCH 3/3] set plpgsql.extra_errors to "none"
Purpose of previous commit was to run tests with active strict_expr_check.
Now, reset to default and revert all changes from previous commit.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 ++++++++-----------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 52 insertions(+), 56 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 280ff3e022e..0015053e0f2 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 2c127a821f1..14e236d57ab 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index caf07e834e5..4c6b3ce998a 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,7 +50,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
do $$ declare a int[] := array[1,2,3];
begin
@@ -64,34 +64,30 @@ end$$;
NOTICE: a = {1,1,2,3,42,3,1,1,2,3,42,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select from onecol)
- ^
-QUERY: a := (select from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select f1, f1 from onecol)
- ^
-QUERY: a := (select f1, f1 from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: more than one row returned by a subquery used as an expression
-CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query returned more than one row
+CONTEXT: query: a := f1 from onecol
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 5d5b25b8308..3ce196de58f 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -50,7 +50,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+int plpgsql_extra_errors;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -193,7 +193,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "strict_expr_check",
+ "none",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 09a76a8416b..da984a99414 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,7 +46,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
do $$ declare a int[] := array[1,2,3];
begin
@@ -61,26 +61,26 @@ end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
+begin a := from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index 7d004b04031..f408d4f69b6 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
+ wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
+ IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 67b28c98902..362f38856d2 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2622,7 +2622,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index faae99515f7..4e59188196c 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 9930bf5f13c..4e21bd714c7 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 5fc57590126..904d3e623f5 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -350,7 +350,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 3458dad1749..7f5757e89c4 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 76b7519678b..84e93ef575e 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1664,7 +1664,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a3dbd93468e..4b2f11dcc64 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index ef102a75378..a5d40bd95c7 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index dc6c99951be..88b33ccaef8 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -223,7 +223,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 1981955ac65..51ae1b31b30 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.48.1
v20250227-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchtext/x-patch; charset=US-ASCII; name=v20250227-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchDownload
From 9b28e940dd6b8e905f5e091faa38ec35832fff1c Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Wed, 12 Jun 2024 21:34:05 +0200
Subject: [PATCH 1/3] use strict rules for parsing PL/pgSQL expressions
Originally the rule PLpgSQL_Expr allows almost all SQL clauses. It was designed
to allow old undocumented syntax
var := col FROM tab;
The reason for support of this "strange" syntax was technical. The PLpgSQL parser
cannot use SQL parser accurately (it was really primitive), and people found
this undocumented syntax. Lattery, when it was possible to do exact parsing, from
compatibility reasons, the parsing of PL/pgSQL expressions allows described syntax.
Unfortunately, with support almost all SQL clauses, the PLpgSQL can accept
really broken code like
DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
proposed patch introduce new extra error check strict_expr_check, that solve
this issue.
---
doc/src/sgml/plpgsql.sgml | 19 ++++
src/pl/plpgsql/src/pl_comp.c | 7 ++
src/pl/plpgsql/src/pl_gram.y | 138 ++++++++++++++++++++++----
src/pl/plpgsql/src/pl_handler.c | 2 +
src/pl/plpgsql/src/plpgsql.h | 1 +
src/test/regress/expected/plpgsql.out | 14 +++
src/test/regress/sql/plpgsql.sql | 14 +++
7 files changed, 177 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 78e4983139b..5e45915e7d2 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5386,6 +5386,25 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="plpgsql-extra-checks-strict-expr-check">
+ <term><varname>strict_expr_check</varname></term>
+ <listitem>
+ <para>
+ Enabling this check will cause <application>PL/pgSQL</application> to
+ check if a <application>PL/pgSQL</application> expression is just an
+ expression without any SQL clauses like <literal>FROM</literal>,
+ <literal>ORDER BY</literal>. This undocumented form of expressions
+ is allowed for compatibility reasons, but in some special cases
+ it doesn't allow to detect broken code.
+ </para>
+
+ <para>
+ This check is allowed only when <varname>plpgsql.extra_errors</varname>
+ is set to <literal>"strict_expr_check"</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
The following example shows the effect of <varname>plpgsql.extra_warnings</varname>
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index f36a244140e..90900ec378b 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -908,6 +908,13 @@ plpgsql_compile_inline(char *proc_source)
function->extra_warnings = 0;
function->extra_errors = 0;
+ /*
+ * Although function->extra_errors is disabled, we want to
+ * do strict_expr_check inside annoymous block too.
+ */
+ if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+
function->nstatements = 0;
function->requires_procedure_resowner = false;
function->has_exception_block = false;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 8048e040f81..8a6508b0249 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -18,6 +18,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
#include "parser/parser.h"
#include "parser/parse_type.h"
#include "parser/scanner.h"
@@ -71,6 +72,7 @@ static PLpgSQL_expr *read_sql_construct(int until,
const char *expected,
RawParseMode parsemode,
bool isexpression,
+ bool allowlist,
bool valid_sql,
int *startloc,
int *endtoken,
@@ -106,7 +108,7 @@ static PLpgSQL_row *make_scalar_list1(char *initial_name,
PLpgSQL_datum *initial_datum,
int lineno, int location, yyscan_t yyscanner);
static void check_sql_expr(const char *stmt,
- RawParseMode parseMode, int location, yyscan_t yyscanner);
+ RawParseMode parseMode, bool allowlist, int location, yyscan_t yyscanner);
static void plpgsql_sql_error_callback(void *arg);
static PLpgSQL_type *parse_datatype(const char *string, int location, yyscan_t yyscanner);
static void check_labels(const char *start_label,
@@ -117,6 +119,7 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor, int until,
YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static List *read_raise_options(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
+static bool is_strict_expr(List *parsetree, int *errpos, bool allowlist);
%}
@@ -193,6 +196,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <expr> expr_until_semi
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
+%type <expr> expressions_until_then
%type <var> cursor_variable
%type <datum> decl_cursor_arg
@@ -914,7 +918,7 @@ stmt_perform : K_PERFORM
*/
new->expr = read_sql_construct(';', 0, 0, ";",
RAW_PARSE_DEFAULT,
- false, false,
+ false, false, false,
&startloc, NULL,
&yylval, &yylloc, yyscanner);
/* overwrite "perform" ... */
@@ -924,7 +928,7 @@ stmt_perform : K_PERFORM
strlen(new->expr->query));
/* offset syntax error position to account for that */
check_sql_expr(new->expr->query, new->expr->parseMode,
- startloc + 1, yyscanner);
+ false, startloc + 1, yyscanner);
$$ = (PLpgSQL_stmt *) new;
}
@@ -1001,7 +1005,7 @@ stmt_assign : T_DATUM
plpgsql_push_back_token(T_DATUM, &yylval, &yylloc, yyscanner);
new->expr = read_sql_construct(';', 0, 0, ";",
pmode,
- false, true,
+ false, false, true,
NULL, NULL,
&yylval, &yylloc, yyscanner);
mark_expr_as_assignment_source(new->expr, $1.datum);
@@ -1262,7 +1266,7 @@ case_when_list : case_when_list case_when
}
;
-case_when : K_WHEN expr_until_then proc_sect
+case_when : K_WHEN expressions_until_then proc_sect
{
PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
@@ -1292,6 +1296,15 @@ opt_case_else :
}
;
+expressions_until_then :
+ {
+ $$ = read_sql_construct(K_THEN, 0, 0, "THEN",
+ RAW_PARSE_PLPGSQL_EXPR, /* expr_list */
+ true, true, true, NULL, NULL,
+ &yylval, &yylloc, yyscanner);
+ }
+ ;
+
stmt_loop : opt_loop_label K_LOOP loop_body
{
PLpgSQL_stmt_loop *new;
@@ -1495,6 +1508,7 @@ for_control : for_variable K_IN
RAW_PARSE_DEFAULT,
true,
false,
+ false,
&expr1loc,
&tok,
&yylval, &yylloc, yyscanner);
@@ -1513,7 +1527,7 @@ for_control : for_variable K_IN
*/
expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
check_sql_expr(expr1->query, expr1->parseMode,
- expr1loc, yyscanner);
+ false, expr1loc, yyscanner);
/* Read and check the second one */
expr2 = read_sql_expression2(K_LOOP, K_BY,
@@ -1570,7 +1584,7 @@ for_control : for_variable K_IN
/* Check syntax as a regular query */
check_sql_expr(expr1->query, expr1->parseMode,
- expr1loc, yyscanner);
+ false, expr1loc, yyscanner);
new = palloc0(sizeof(PLpgSQL_stmt_fors));
new->cmd_type = PLPGSQL_STMT_FORS;
@@ -1902,7 +1916,7 @@ stmt_raise : K_RAISE
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &tok,
&yylval, &yylloc, yyscanner);
new->params = lappend(new->params, expr);
@@ -2040,7 +2054,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
@@ -2080,7 +2094,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
new->params = lappend(new->params, expr);
@@ -2713,7 +2727,7 @@ read_sql_expression(int until, const char *expected, YYSTYPE *yylvalp, YYLTYPE *
{
return read_sql_construct(until, 0, 0, expected,
RAW_PARSE_PLPGSQL_EXPR,
- true, true, NULL, NULL,
+ true, false, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2724,7 +2738,7 @@ read_sql_expression2(int until, int until2, const char *expected,
{
return read_sql_construct(until, until2, 0, expected,
RAW_PARSE_PLPGSQL_EXPR,
- true, true, NULL, endtoken,
+ true, false, true, NULL, endtoken,
yylvalp, yyllocp, yyscanner);
}
@@ -2734,7 +2748,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
return read_sql_construct(';', 0, 0, ";",
RAW_PARSE_DEFAULT,
- false, true, NULL, NULL,
+ false, false, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2747,6 +2761,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
* expected: text to use in complaining that terminator was not found
* parsemode: raw_parser() mode to use
* isexpression: whether to say we're reading an "expression" or a "statement"
+ * allowlist: the result can be list of expressions
* valid_sql: whether to check the syntax of the expr
* startloc: if not NULL, location of first token is stored at *startloc
* endtoken: if not NULL, ending token is stored at *endtoken
@@ -2759,6 +2774,7 @@ read_sql_construct(int until,
const char *expected,
RawParseMode parsemode,
bool isexpression,
+ bool allowlist,
bool valid_sql,
int *startloc,
int *endtoken,
@@ -2854,7 +2870,7 @@ read_sql_construct(int until,
pfree(ds.data);
if (valid_sql)
- check_sql_expr(expr->query, expr->parseMode, startlocation, yyscanner);
+ check_sql_expr(expr->query, expr->parseMode, allowlist, startlocation, yyscanner);
return expr;
}
@@ -3175,7 +3191,7 @@ make_execsql_stmt(int firsttoken, int location, PLword *word, YYSTYPE *yylvalp,
expr = make_plpgsql_expr(ds.data, RAW_PARSE_DEFAULT);
pfree(ds.data);
- check_sql_expr(expr->query, expr->parseMode, location, yyscanner);
+ check_sql_expr(expr->query, expr->parseMode, false, location, yyscanner);
execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
@@ -3775,11 +3791,15 @@ make_scalar_list1(char *initial_name,
* If no error cursor is provided, we'll just point at "location".
*/
static void
-check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t yyscanner)
+check_sql_expr(const char *stmt,
+ RawParseMode parseMode, bool allowlist,
+ int location, yyscan_t yyscanner)
{
sql_error_callback_arg cbarg;
ErrorContextCallback syntax_errcontext;
MemoryContext oldCxt;
+ List *parsetree;
+ int errpos;
if (!plpgsql_check_syntax)
return;
@@ -3793,11 +3813,25 @@ check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t
error_context_stack = &syntax_errcontext;
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
- (void) raw_parser(stmt, parseMode);
+ parsetree = raw_parser(stmt, parseMode);
MemoryContextSwitchTo(oldCxt);
/* Restore former ereport callback */
error_context_stack = syntax_errcontext.previous;
+
+ if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_STRICTEXPRCHECK ||
+ plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ {
+ /* do this check only for expressions */
+ if (parseMode == RAW_PARSE_DEFAULT)
+ return;
+
+ if (!is_strict_expr(parsetree, &errpos, allowlist))
+ ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK ? ERROR : WARNING,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("syntax of expression is not strict"),
+ parser_errposition(errpos != -1 ? location + errpos : location)));
+ }
}
static void
@@ -3831,6 +3865,74 @@ plpgsql_sql_error_callback(void *arg)
errposition(0);
}
+/*
+ * Returns true, when the only targetList is in parsetree. Cursors
+ * can require list of expressions or list of named expressions.
+ */
+static bool
+is_strict_expr(List *parsetree, int *errpos, bool allowlist)
+{
+ RawStmt *rawstmt;
+ SelectStmt *select;
+ int targets = 0;
+ ListCell *lc;
+
+ /* Top should be RawStmt */
+ rawstmt = castNode(RawStmt, linitial(parsetree));
+
+ if (IsA(rawstmt->stmt, SelectStmt))
+ {
+ select = (SelectStmt *) rawstmt->stmt;
+ }
+ else if (IsA(rawstmt->stmt, PLAssignStmt))
+ {
+ select = castNode(SelectStmt, ((PLAssignStmt *) rawstmt->stmt)->val);
+ }
+ else
+ elog(ERROR, "unexpected node type");
+
+ if (!select->targetList)
+ {
+ *errpos = -1;
+ return false;
+ }
+ else
+ *errpos = exprLocation((Node *) select->targetList);
+
+ if (select->distinctClause ||
+ select->fromClause ||
+ select->whereClause ||
+ select->groupClause ||
+ select->groupDistinct ||
+ select->havingClause ||
+ select->windowClause ||
+ select->sortClause ||
+ select->limitOffset ||
+ select->limitCount ||
+ select->limitOption ||
+ select->lockingClause)
+ return false;
+
+ foreach(lc, select->targetList)
+ {
+ ResTarget *rt = castNode(ResTarget, lfirst(lc));
+
+ if (targets++ >= 1 && !allowlist)
+ {
+ *errpos = exprLocation((Node *) rt);
+ return false;
+ }
+
+ if (rt->name)
+ {
+ *errpos = exprLocation((Node *) rt);
+ return false;
+ }
+ }
+
+ return true;
+}
+
/*
* Parse a SQL datatype name and produce a PLpgSQL_type structure.
*
@@ -4011,7 +4113,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
item = read_sql_construct(',', ')', 0,
",\" or \")",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
yylvalp, yyllocp, yyscanner);
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 5af38d5773b..3ce196de58f 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -94,6 +94,8 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
+ else if (pg_strcasecmp(tok, "strict_expr_check") == 0)
+ extrachecks |= PLPGSQL_XCHECK_STRICTEXPRCHECK;
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
{
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index d73996e09c0..93dee19313b 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -1227,6 +1227,7 @@ extern bool plpgsql_check_asserts;
#define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
+#define PLPGSQL_XCHECK_STRICTEXPRCHECK (1 << 4)
#define PLPGSQL_XCHECK_ALL ((int) ~0)
extern int plpgsql_extra_warnings;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 0a6945581bd..4e21bd714c7 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -3083,6 +3083,20 @@ select shadowtest(1);
t
(1 row)
+-- test of strict expression check
+set plpgsql.extra_errors to 'strict_expr_check';
+create or replace function strict_expr_check_func()
+returns void as $$
+declare var int;
+begin
+ var = 1
+ delete from pg_class where false;
+end;
+$$ language plpgsql;
+ERROR: syntax of expression is not strict
+LINE 5: var = 1
+ ^
+reset plpgsql.extra_errors;
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
do $$
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 18c91572ae1..a5d40bd95c7 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -2617,6 +2617,20 @@ declare f1 int; begin return 1; end $$ language plpgsql;
select shadowtest(1);
+-- test of strict expression check
+set plpgsql.extra_errors to 'strict_expr_check';
+
+create or replace function strict_expr_check_func()
+returns void as $$
+declare var int;
+begin
+ var = 1
+ delete from pg_class where false;
+end;
+$$ language plpgsql;
+
+reset plpgsql.extra_errors;
+
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
--
2.48.1
v20250227-0002-simply-check-of-strict-expr-check-on-regress-test.patchtext/x-patch; charset=US-ASCII; name=v20250227-0002-simply-check-of-strict-expr-check-on-regress-test.patchDownload
From f0093580c395542588c658cf61fed8d33260de97 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 08:13:53 +0200
Subject: [PATCH 2/3] simply check of strict-expr-check on regress test
This patch enable strict-expr-check by default to be possible to see
the impact of this option on regress test. Next commit will revert
this option. The strict-expr-check should not be enabled by default.
This commit is done just for testing.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 +++++++++++--------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 0015053e0f2..280ff3e022e 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 14e236d57ab..2c127a821f1 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index 4c6b3ce998a..caf07e834e5 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,7 +50,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
do $$ declare a int[] := array[1,2,3];
begin
@@ -64,30 +64,34 @@ end$$;
NOTICE: a = {1,1,2,3,42,3,1,1,2,3,42,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 0 columns
-CONTEXT: PL/pgSQL assignment "a := from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select from onecol)
+ ^
+QUERY: a := (select from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 2 columns
-CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select f1, f1 from onecol)
+ ^
+QUERY: a := (select f1, f1 from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: query returned more than one row
-CONTEXT: query: a := f1 from onecol
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: more than one row returned by a subquery used as an expression
+CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 3ce196de58f..5d5b25b8308 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -50,7 +50,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors;
+int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -193,7 +193,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "none",
+ "strict_expr_check",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index da984a99414..09a76a8416b 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,7 +46,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
do $$ declare a int[] := array[1,2,3];
begin
@@ -61,26 +61,26 @@ end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index f408d4f69b6..7d004b04031 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
+ wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
+ IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 362f38856d2..67b28c98902 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2622,7 +2622,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4e59188196c..faae99515f7 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 4e21bd714c7..9930bf5f13c 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 904d3e623f5..5fc57590126 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -350,7 +350,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 7f5757e89c4..3458dad1749 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 84e93ef575e..76b7519678b 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1664,7 +1664,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 4b2f11dcc64..a3dbd93468e 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index a5d40bd95c7..ef102a75378 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 88b33ccaef8..dc6c99951be 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -223,7 +223,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 51ae1b31b30..1981955ac65 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.48.1
Hi
only rebase
Regards
Pavel
Attachments:
v20250607-0003-set-plpgsql.extra_errors-to-none.patchtext/x-patch; charset=US-ASCII; name=v20250607-0003-set-plpgsql.extra_errors-to-none.patchDownload
From d589a48d746d05368ee49e6a1e0202da9b75b0f6 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 15:52:38 +0200
Subject: [PATCH 3/3] set plpgsql.extra_errors to "none"
Purpose of previous commit was to run tests with active strict_expr_check.
Now, reset to default and revert all changes from previous commit.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 ++++++++-----------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 52 insertions(+), 56 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 280ff3e022e..0015053e0f2 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 2c127a821f1..14e236d57ab 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index caf07e834e5..4c6b3ce998a 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,7 +50,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
do $$ declare a int[] := array[1,2,3];
begin
@@ -64,34 +64,30 @@ end$$;
NOTICE: a = {1,1,2,3,42,3,1,1,2,3,42,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select from onecol)
- ^
-QUERY: a := (select from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select f1, f1 from onecol)
- ^
-QUERY: a := (select f1, f1 from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: more than one row returned by a subquery used as an expression
-CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query returned more than one row
+CONTEXT: query: a := f1 from onecol
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 11e4dd1b62c..b3ba3163e9a 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -53,7 +53,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+int plpgsql_extra_errors;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -196,7 +196,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "strict_expr_check",
+ "none",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 09a76a8416b..da984a99414 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,7 +46,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
do $$ declare a int[] := array[1,2,3];
begin
@@ -61,26 +61,26 @@ end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
+begin a := from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index 7d004b04031..f408d4f69b6 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
+ wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
+ IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index e2e6dc7e8ef..476266e3f4b 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2691,7 +2691,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index faae99515f7..4e59188196c 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 5545858c968..8f4f5cb1183 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 4d41e791ed1..6359e5fb689 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -350,7 +350,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 3458dad1749..7f5757e89c4 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 9542043d1e8..5ce9d1e429f 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1678,7 +1678,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a3dbd93468e..4b2f11dcc64 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 238e0ec67f2..dd0d908d422 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 6c6e2719b7d..da4f2fe9c93 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -223,7 +223,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 1981955ac65..51ae1b31b30 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.49.0
v20250607-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchtext/x-patch; charset=US-ASCII; name=v20250607-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchDownload
From 22a7ce068dd53c54e037c07488a14716ef979b34 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Wed, 12 Jun 2024 21:34:05 +0200
Subject: [PATCH 1/3] use strict rules for parsing PL/pgSQL expressions
Originally the rule PLpgSQL_Expr allows almost all SQL clauses. It was designed
to allow old undocumented syntax
var := col FROM tab;
The reason for support of this "strange" syntax was technical. The PLpgSQL parser
cannot use SQL parser accurately (it was really primitive), and people found
this undocumented syntax. Lattery, when it was possible to do exact parsing, from
compatibility reasons, the parsing of PL/pgSQL expressions allows described syntax.
Unfortunately, with support almost all SQL clauses, the PLpgSQL can accept
really broken code like
DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
proposed patch introduce new extra error check strict_expr_check, that solve
this issue.
---
doc/src/sgml/plpgsql.sgml | 19 ++++
src/pl/plpgsql/src/pl_comp.c | 7 ++
src/pl/plpgsql/src/pl_gram.y | 138 ++++++++++++++++++++++----
src/pl/plpgsql/src/pl_handler.c | 2 +
src/pl/plpgsql/src/plpgsql.h | 1 +
src/test/regress/expected/plpgsql.out | 14 +++
src/test/regress/sql/plpgsql.sql | 14 +++
7 files changed, 177 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index e937491e6b8..cbe5c2be28b 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5388,6 +5388,25 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="plpgsql-extra-checks-strict-expr-check">
+ <term><varname>strict_expr_check</varname></term>
+ <listitem>
+ <para>
+ Enabling this check will cause <application>PL/pgSQL</application> to
+ check if a <application>PL/pgSQL</application> expression is just an
+ expression without any SQL clauses like <literal>FROM</literal>,
+ <literal>ORDER BY</literal>. This undocumented form of expressions
+ is allowed for compatibility reasons, but in some special cases
+ it doesn't allow to detect broken code.
+ </para>
+
+ <para>
+ This check is allowed only when <varname>plpgsql.extra_errors</varname>
+ is set to <literal>"strict_expr_check"</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
The following example shows the effect of <varname>plpgsql.extra_warnings</varname>
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index b80c59447fb..840c6ee0c06 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -796,6 +796,13 @@ plpgsql_compile_inline(char *proc_source)
function->extra_warnings = 0;
function->extra_errors = 0;
+ /*
+ * Although function->extra_errors is disabled, we want to
+ * do strict_expr_check inside annoymous block too.
+ */
+ if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+
function->nstatements = 0;
function->requires_procedure_resowner = false;
function->has_exception_block = false;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 5612e66d023..dcc581afdbf 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -18,6 +18,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
#include "parser/parser.h"
#include "parser/parse_type.h"
#include "parser/scanner.h"
@@ -71,6 +72,7 @@ static PLpgSQL_expr *read_sql_construct(int until,
const char *expected,
RawParseMode parsemode,
bool isexpression,
+ bool allowlist,
bool valid_sql,
int *startloc,
int *endtoken,
@@ -106,7 +108,7 @@ static PLpgSQL_row *make_scalar_list1(char *initial_name,
PLpgSQL_datum *initial_datum,
int lineno, int location, yyscan_t yyscanner);
static void check_sql_expr(const char *stmt,
- RawParseMode parseMode, int location, yyscan_t yyscanner);
+ RawParseMode parseMode, bool allowlist, int location, yyscan_t yyscanner);
static void plpgsql_sql_error_callback(void *arg);
static PLpgSQL_type *parse_datatype(const char *string, int location, yyscan_t yyscanner);
static void check_labels(const char *start_label,
@@ -117,6 +119,7 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor, int until,
YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static List *read_raise_options(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
+static bool is_strict_expr(List *parsetree, int *errpos, bool allowlist);
%}
@@ -193,6 +196,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <expr> expr_until_semi
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
+%type <expr> expressions_until_then
%type <var> cursor_variable
%type <datum> decl_cursor_arg
@@ -914,7 +918,7 @@ stmt_perform : K_PERFORM
*/
new->expr = read_sql_construct(';', 0, 0, ";",
RAW_PARSE_DEFAULT,
- false, false,
+ false, false, false,
&startloc, NULL,
&yylval, &yylloc, yyscanner);
/* overwrite "perform" ... */
@@ -924,7 +928,7 @@ stmt_perform : K_PERFORM
strlen(new->expr->query));
/* offset syntax error position to account for that */
check_sql_expr(new->expr->query, new->expr->parseMode,
- startloc + 1, yyscanner);
+ false, startloc + 1, yyscanner);
$$ = (PLpgSQL_stmt *) new;
}
@@ -1001,7 +1005,7 @@ stmt_assign : T_DATUM
plpgsql_push_back_token(T_DATUM, &yylval, &yylloc, yyscanner);
new->expr = read_sql_construct(';', 0, 0, ";",
pmode,
- false, true,
+ false, false, true,
NULL, NULL,
&yylval, &yylloc, yyscanner);
mark_expr_as_assignment_source(new->expr, $1.datum);
@@ -1262,7 +1266,7 @@ case_when_list : case_when_list case_when
}
;
-case_when : K_WHEN expr_until_then proc_sect
+case_when : K_WHEN expressions_until_then proc_sect
{
PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
@@ -1292,6 +1296,15 @@ opt_case_else :
}
;
+expressions_until_then :
+ {
+ $$ = read_sql_construct(K_THEN, 0, 0, "THEN",
+ RAW_PARSE_PLPGSQL_EXPR, /* expr_list */
+ true, true, true, NULL, NULL,
+ &yylval, &yylloc, yyscanner);
+ }
+ ;
+
stmt_loop : opt_loop_label K_LOOP loop_body
{
PLpgSQL_stmt_loop *new;
@@ -1495,6 +1508,7 @@ for_control : for_variable K_IN
RAW_PARSE_DEFAULT,
true,
false,
+ false,
&expr1loc,
&tok,
&yylval, &yylloc, yyscanner);
@@ -1513,7 +1527,7 @@ for_control : for_variable K_IN
*/
expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
check_sql_expr(expr1->query, expr1->parseMode,
- expr1loc, yyscanner);
+ false, expr1loc, yyscanner);
/* Read and check the second one */
expr2 = read_sql_expression2(K_LOOP, K_BY,
@@ -1570,7 +1584,7 @@ for_control : for_variable K_IN
/* Check syntax as a regular query */
check_sql_expr(expr1->query, expr1->parseMode,
- expr1loc, yyscanner);
+ false, expr1loc, yyscanner);
new = palloc0(sizeof(PLpgSQL_stmt_fors));
new->cmd_type = PLPGSQL_STMT_FORS;
@@ -1902,7 +1916,7 @@ stmt_raise : K_RAISE
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &tok,
&yylval, &yylloc, yyscanner);
new->params = lappend(new->params, expr);
@@ -2040,7 +2054,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
@@ -2080,7 +2094,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
new->params = lappend(new->params, expr);
@@ -2713,7 +2727,7 @@ read_sql_expression(int until, const char *expected, YYSTYPE *yylvalp, YYLTYPE *
{
return read_sql_construct(until, 0, 0, expected,
RAW_PARSE_PLPGSQL_EXPR,
- true, true, NULL, NULL,
+ true, false, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2724,7 +2738,7 @@ read_sql_expression2(int until, int until2, const char *expected,
{
return read_sql_construct(until, until2, 0, expected,
RAW_PARSE_PLPGSQL_EXPR,
- true, true, NULL, endtoken,
+ true, false, true, NULL, endtoken,
yylvalp, yyllocp, yyscanner);
}
@@ -2734,7 +2748,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
return read_sql_construct(';', 0, 0, ";",
RAW_PARSE_DEFAULT,
- false, true, NULL, NULL,
+ false, false, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2747,6 +2761,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
* expected: text to use in complaining that terminator was not found
* parsemode: raw_parser() mode to use
* isexpression: whether to say we're reading an "expression" or a "statement"
+ * allowlist: the result can be list of expressions
* valid_sql: whether to check the syntax of the expr
* startloc: if not NULL, location of first token is stored at *startloc
* endtoken: if not NULL, ending token is stored at *endtoken
@@ -2759,6 +2774,7 @@ read_sql_construct(int until,
const char *expected,
RawParseMode parsemode,
bool isexpression,
+ bool allowlist,
bool valid_sql,
int *startloc,
int *endtoken,
@@ -2854,7 +2870,7 @@ read_sql_construct(int until,
pfree(ds.data);
if (valid_sql)
- check_sql_expr(expr->query, expr->parseMode, startlocation, yyscanner);
+ check_sql_expr(expr->query, expr->parseMode, allowlist, startlocation, yyscanner);
return expr;
}
@@ -3175,7 +3191,7 @@ make_execsql_stmt(int firsttoken, int location, PLword *word, YYSTYPE *yylvalp,
expr = make_plpgsql_expr(ds.data, RAW_PARSE_DEFAULT);
pfree(ds.data);
- check_sql_expr(expr->query, expr->parseMode, location, yyscanner);
+ check_sql_expr(expr->query, expr->parseMode, false, location, yyscanner);
execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
@@ -3775,11 +3791,15 @@ make_scalar_list1(char *initial_name,
* If no error cursor is provided, we'll just point at "location".
*/
static void
-check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t yyscanner)
+check_sql_expr(const char *stmt,
+ RawParseMode parseMode, bool allowlist,
+ int location, yyscan_t yyscanner)
{
sql_error_callback_arg cbarg;
ErrorContextCallback syntax_errcontext;
MemoryContext oldCxt;
+ List *parsetree;
+ int errpos;
if (!plpgsql_check_syntax)
return;
@@ -3793,11 +3813,25 @@ check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t
error_context_stack = &syntax_errcontext;
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
- (void) raw_parser(stmt, parseMode);
+ parsetree = raw_parser(stmt, parseMode);
MemoryContextSwitchTo(oldCxt);
/* Restore former ereport callback */
error_context_stack = syntax_errcontext.previous;
+
+ if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_STRICTEXPRCHECK ||
+ plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ {
+ /* do this check only for expressions */
+ if (parseMode == RAW_PARSE_DEFAULT)
+ return;
+
+ if (!is_strict_expr(parsetree, &errpos, allowlist))
+ ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK ? ERROR : WARNING,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("syntax of expression is not strict"),
+ parser_errposition(errpos != -1 ? location + errpos : location)));
+ }
}
static void
@@ -3831,6 +3865,74 @@ plpgsql_sql_error_callback(void *arg)
errposition(0);
}
+/*
+ * Returns true, when the only targetList is in parsetree. Cursors
+ * can require list of expressions or list of named expressions.
+ */
+static bool
+is_strict_expr(List *parsetree, int *errpos, bool allowlist)
+{
+ RawStmt *rawstmt;
+ SelectStmt *select;
+ int targets = 0;
+ ListCell *lc;
+
+ /* Top should be RawStmt */
+ rawstmt = castNode(RawStmt, linitial(parsetree));
+
+ if (IsA(rawstmt->stmt, SelectStmt))
+ {
+ select = (SelectStmt *) rawstmt->stmt;
+ }
+ else if (IsA(rawstmt->stmt, PLAssignStmt))
+ {
+ select = castNode(SelectStmt, ((PLAssignStmt *) rawstmt->stmt)->val);
+ }
+ else
+ elog(ERROR, "unexpected node type");
+
+ if (!select->targetList)
+ {
+ *errpos = -1;
+ return false;
+ }
+ else
+ *errpos = exprLocation((Node *) select->targetList);
+
+ if (select->distinctClause ||
+ select->fromClause ||
+ select->whereClause ||
+ select->groupClause ||
+ select->groupDistinct ||
+ select->havingClause ||
+ select->windowClause ||
+ select->sortClause ||
+ select->limitOffset ||
+ select->limitCount ||
+ select->limitOption ||
+ select->lockingClause)
+ return false;
+
+ foreach(lc, select->targetList)
+ {
+ ResTarget *rt = castNode(ResTarget, lfirst(lc));
+
+ if (targets++ >= 1 && !allowlist)
+ {
+ *errpos = exprLocation((Node *) rt);
+ return false;
+ }
+
+ if (rt->name)
+ {
+ *errpos = exprLocation((Node *) rt);
+ return false;
+ }
+ }
+
+ return true;
+}
+
/*
* Parse a SQL datatype name and produce a PLpgSQL_type structure.
*
@@ -4014,7 +4116,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
item = read_sql_construct(',', ')', 0,
",\" or \")",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
yylvalp, yyllocp, yyscanner);
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index e9a72929947..b3ba3163e9a 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -97,6 +97,8 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
+ else if (pg_strcasecmp(tok, "strict_expr_check") == 0)
+ extrachecks |= PLPGSQL_XCHECK_STRICTEXPRCHECK;
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
{
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 41e52b8ce71..459f5f2e223 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -1195,6 +1195,7 @@ extern bool plpgsql_check_asserts;
#define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
+#define PLPGSQL_XCHECK_STRICTEXPRCHECK (1 << 4)
#define PLPGSQL_XCHECK_ALL ((int) ~0)
extern int plpgsql_extra_warnings;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d8ce39dba3c..8f4f5cb1183 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -3084,6 +3084,20 @@ select shadowtest(1);
t
(1 row)
+-- test of strict expression check
+set plpgsql.extra_errors to 'strict_expr_check';
+create or replace function strict_expr_check_func()
+returns void as $$
+declare var int;
+begin
+ var = 1
+ delete from pg_class where false;
+end;
+$$ language plpgsql;
+ERROR: syntax of expression is not strict
+LINE 5: var = 1
+ ^
+reset plpgsql.extra_errors;
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
do $$
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index d413d995d17..dd0d908d422 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -2618,6 +2618,20 @@ declare f1 int; begin return 1; end $$ language plpgsql;
select shadowtest(1);
+-- test of strict expression check
+set plpgsql.extra_errors to 'strict_expr_check';
+
+create or replace function strict_expr_check_func()
+returns void as $$
+declare var int;
+begin
+ var = 1
+ delete from pg_class where false;
+end;
+$$ language plpgsql;
+
+reset plpgsql.extra_errors;
+
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
--
2.49.0
v20250607-0002-simply-check-of-strict-expr-check-on-regress-test.patchtext/x-patch; charset=US-ASCII; name=v20250607-0002-simply-check-of-strict-expr-check-on-regress-test.patchDownload
From eec72af4c0f8acf2a8ceec765a8f000f9efb70d2 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 08:13:53 +0200
Subject: [PATCH 2/3] simply check of strict-expr-check on regress test
This patch enable strict-expr-check by default to be possible to see
the impact of this option on regress test. Next commit will revert
this option. The strict-expr-check should not be enabled by default.
This commit is done just for testing.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 +++++++++++--------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 0015053e0f2..280ff3e022e 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 14e236d57ab..2c127a821f1 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index 4c6b3ce998a..caf07e834e5 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,7 +50,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
do $$ declare a int[] := array[1,2,3];
begin
@@ -64,30 +64,34 @@ end$$;
NOTICE: a = {1,1,2,3,42,3,1,1,2,3,42,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 0 columns
-CONTEXT: PL/pgSQL assignment "a := from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select from onecol)
+ ^
+QUERY: a := (select from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 2 columns
-CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select f1, f1 from onecol)
+ ^
+QUERY: a := (select f1, f1 from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: query returned more than one row
-CONTEXT: query: a := f1 from onecol
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: more than one row returned by a subquery used as an expression
+CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index b3ba3163e9a..11e4dd1b62c 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -53,7 +53,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors;
+int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -196,7 +196,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "none",
+ "strict_expr_check",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index da984a99414..09a76a8416b 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,7 +46,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
do $$ declare a int[] := array[1,2,3];
begin
@@ -61,26 +61,26 @@ end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index f408d4f69b6..7d004b04031 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
+ wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
+ IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 476266e3f4b..e2e6dc7e8ef 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2691,7 +2691,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4e59188196c..faae99515f7 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 8f4f5cb1183..5545858c968 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 6359e5fb689..4d41e791ed1 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -350,7 +350,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 7f5757e89c4..3458dad1749 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 5ce9d1e429f..9542043d1e8 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1678,7 +1678,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 4b2f11dcc64..a3dbd93468e 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index dd0d908d422..238e0ec67f2 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index da4f2fe9c93..6c6e2719b7d 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -223,7 +223,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 51ae1b31b30..1981955ac65 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.49.0
Hi
fresh rebase
Regards
Pavel
Attachments:
v20250704-0003-set-plpgsql.extra_errors-to-none.patchtext/x-patch; charset=US-ASCII; name=v20250704-0003-set-plpgsql.extra_errors-to-none.patchDownload
From f88defc5b54a0dd138acd28e6aa2ddc2828b1ad5 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 15:52:38 +0200
Subject: [PATCH 3/3] set plpgsql.extra_errors to "none"
Purpose of previous commit was to run tests with active strict_expr_check.
Now, reset to default and revert all changes from previous commit.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 ++++++++-----------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 52 insertions(+), 56 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 280ff3e022e..0015053e0f2 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 2c127a821f1..14e236d57ab 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index caf07e834e5..4c6b3ce998a 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,7 +50,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
do $$ declare a int[] := array[1,2,3];
begin
@@ -64,34 +64,30 @@ end$$;
NOTICE: a = {1,1,2,3,42,3,1,1,2,3,42,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select from onecol)
- ^
-QUERY: a := (select from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select f1, f1 from onecol)
- ^
-QUERY: a := (select f1, f1 from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: more than one row returned by a subquery used as an expression
-CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query returned more than one row
+CONTEXT: query: a := f1 from onecol
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 11e4dd1b62c..b3ba3163e9a 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -53,7 +53,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+int plpgsql_extra_errors;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -196,7 +196,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "strict_expr_check",
+ "none",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 09a76a8416b..da984a99414 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,7 +46,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
do $$ declare a int[] := array[1,2,3];
begin
@@ -61,26 +61,26 @@ end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
+begin a := from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index 7d004b04031..f408d4f69b6 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
+ wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
+ IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 5a0497c1859..08984dd98f1 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2691,7 +2691,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index faae99515f7..4e59188196c 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 5545858c968..8f4f5cb1183 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 4d41e791ed1..6359e5fb689 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -350,7 +350,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 3458dad1749..7f5757e89c4 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index e857aed4a07..fc6e36d0e78 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1678,7 +1678,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a3dbd93468e..4b2f11dcc64 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 238e0ec67f2..dd0d908d422 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 6c6e2719b7d..da4f2fe9c93 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -223,7 +223,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 1981955ac65..51ae1b31b30 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.50.0
v20250704-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchtext/x-patch; charset=US-ASCII; name=v20250704-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchDownload
From c4c0220eb550526f4484e796ae6ee7b640caf60d Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Wed, 12 Jun 2024 21:34:05 +0200
Subject: [PATCH 1/3] use strict rules for parsing PL/pgSQL expressions
Originally the rule PLpgSQL_Expr allows almost all SQL clauses. It was designed
to allow old undocumented syntax
var := col FROM tab;
The reason for support of this "strange" syntax was technical. The PLpgSQL parser
cannot use SQL parser accurately (it was really primitive), and people found
this undocumented syntax. Lattery, when it was possible to do exact parsing, from
compatibility reasons, the parsing of PL/pgSQL expressions allows described syntax.
Unfortunately, with support almost all SQL clauses, the PLpgSQL can accept
really broken code like
DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
proposed patch introduce new extra error check strict_expr_check, that solve
this issue.
---
doc/src/sgml/plpgsql.sgml | 19 ++++
src/pl/plpgsql/src/pl_comp.c | 7 ++
src/pl/plpgsql/src/pl_gram.y | 138 ++++++++++++++++++++++----
src/pl/plpgsql/src/pl_handler.c | 2 +
src/pl/plpgsql/src/plpgsql.h | 1 +
src/test/regress/expected/plpgsql.out | 14 +++
src/test/regress/sql/plpgsql.sql | 14 +++
7 files changed, 177 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index e937491e6b8..cbe5c2be28b 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5388,6 +5388,25 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="plpgsql-extra-checks-strict-expr-check">
+ <term><varname>strict_expr_check</varname></term>
+ <listitem>
+ <para>
+ Enabling this check will cause <application>PL/pgSQL</application> to
+ check if a <application>PL/pgSQL</application> expression is just an
+ expression without any SQL clauses like <literal>FROM</literal>,
+ <literal>ORDER BY</literal>. This undocumented form of expressions
+ is allowed for compatibility reasons, but in some special cases
+ it doesn't allow to detect broken code.
+ </para>
+
+ <para>
+ This check is allowed only when <varname>plpgsql.extra_errors</varname>
+ is set to <literal>"strict_expr_check"</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
The following example shows the effect of <varname>plpgsql.extra_warnings</varname>
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index ee961425a5b..500d81388e5 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -796,6 +796,13 @@ plpgsql_compile_inline(char *proc_source)
function->extra_warnings = 0;
function->extra_errors = 0;
+ /*
+ * Although function->extra_errors is disabled, we want to
+ * do strict_expr_check inside annoymous block too.
+ */
+ if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+
function->nstatements = 0;
function->requires_procedure_resowner = false;
function->has_exception_block = false;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 7b672ea5179..df3cee9f7a9 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -18,6 +18,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
#include "parser/parser.h"
#include "parser/parse_type.h"
#include "parser/scanner.h"
@@ -71,6 +72,7 @@ static PLpgSQL_expr *read_sql_construct(int until,
const char *expected,
RawParseMode parsemode,
bool isexpression,
+ bool allowlist,
bool valid_sql,
int *startloc,
int *endtoken,
@@ -106,7 +108,7 @@ static PLpgSQL_row *make_scalar_list1(char *initial_name,
PLpgSQL_datum *initial_datum,
int lineno, int location, yyscan_t yyscanner);
static void check_sql_expr(const char *stmt,
- RawParseMode parseMode, int location, yyscan_t yyscanner);
+ RawParseMode parseMode, bool allowlist, int location, yyscan_t yyscanner);
static void plpgsql_sql_error_callback(void *arg);
static PLpgSQL_type *parse_datatype(const char *string, int location, yyscan_t yyscanner);
static void check_labels(const char *start_label,
@@ -117,6 +119,7 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor, int until,
YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static List *read_raise_options(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
+static bool is_strict_expr(List *parsetree, int *errpos, bool allowlist);
%}
@@ -193,6 +196,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <expr> expr_until_semi
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
+%type <expr> expressions_until_then
%type <var> cursor_variable
%type <datum> decl_cursor_arg
@@ -914,7 +918,7 @@ stmt_perform : K_PERFORM
*/
new->expr = read_sql_construct(';', 0, 0, ";",
RAW_PARSE_DEFAULT,
- false, false,
+ false, false, false,
&startloc, NULL,
&yylval, &yylloc, yyscanner);
/* overwrite "perform" ... */
@@ -924,7 +928,7 @@ stmt_perform : K_PERFORM
strlen(new->expr->query));
/* offset syntax error position to account for that */
check_sql_expr(new->expr->query, new->expr->parseMode,
- startloc + 1, yyscanner);
+ false, startloc + 1, yyscanner);
$$ = (PLpgSQL_stmt *) new;
}
@@ -1001,7 +1005,7 @@ stmt_assign : T_DATUM
plpgsql_push_back_token(T_DATUM, &yylval, &yylloc, yyscanner);
new->expr = read_sql_construct(';', 0, 0, ";",
pmode,
- false, true,
+ false, false, true,
NULL, NULL,
&yylval, &yylloc, yyscanner);
mark_expr_as_assignment_source(new->expr, $1.datum);
@@ -1262,7 +1266,7 @@ case_when_list : case_when_list case_when
}
;
-case_when : K_WHEN expr_until_then proc_sect
+case_when : K_WHEN expressions_until_then proc_sect
{
PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
@@ -1292,6 +1296,15 @@ opt_case_else :
}
;
+expressions_until_then :
+ {
+ $$ = read_sql_construct(K_THEN, 0, 0, "THEN",
+ RAW_PARSE_PLPGSQL_EXPR, /* expr_list */
+ true, true, true, NULL, NULL,
+ &yylval, &yylloc, yyscanner);
+ }
+ ;
+
stmt_loop : opt_loop_label K_LOOP loop_body
{
PLpgSQL_stmt_loop *new;
@@ -1496,6 +1509,7 @@ for_control : for_variable K_IN
RAW_PARSE_DEFAULT,
true,
false,
+ false,
&expr1loc,
&tok,
&yylval, &yylloc, yyscanner);
@@ -1514,7 +1528,7 @@ for_control : for_variable K_IN
*/
expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
check_sql_expr(expr1->query, expr1->parseMode,
- expr1loc, yyscanner);
+ false, expr1loc, yyscanner);
/* Read and check the second one */
expr2 = read_sql_expression2(K_LOOP, K_BY,
@@ -1571,7 +1585,7 @@ for_control : for_variable K_IN
/* Check syntax as a regular query */
check_sql_expr(expr1->query, expr1->parseMode,
- expr1loc, yyscanner);
+ false, expr1loc, yyscanner);
new = palloc0(sizeof(PLpgSQL_stmt_fors));
new->cmd_type = PLPGSQL_STMT_FORS;
@@ -1903,7 +1917,7 @@ stmt_raise : K_RAISE
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &tok,
&yylval, &yylloc, yyscanner);
new->params = lappend(new->params, expr);
@@ -2041,7 +2055,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
@@ -2081,7 +2095,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
new->params = lappend(new->params, expr);
@@ -2717,7 +2731,7 @@ read_sql_expression(int until, const char *expected, YYSTYPE *yylvalp, YYLTYPE *
{
return read_sql_construct(until, 0, 0, expected,
RAW_PARSE_PLPGSQL_EXPR,
- true, true, NULL, NULL,
+ true, false, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2728,7 +2742,7 @@ read_sql_expression2(int until, int until2, const char *expected,
{
return read_sql_construct(until, until2, 0, expected,
RAW_PARSE_PLPGSQL_EXPR,
- true, true, NULL, endtoken,
+ true, false, true, NULL, endtoken,
yylvalp, yyllocp, yyscanner);
}
@@ -2738,7 +2752,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
return read_sql_construct(';', 0, 0, ";",
RAW_PARSE_DEFAULT,
- false, true, NULL, NULL,
+ false, false, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2751,6 +2765,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
* expected: text to use in complaining that terminator was not found
* parsemode: raw_parser() mode to use
* isexpression: whether to say we're reading an "expression" or a "statement"
+ * allowlist: the result can be list of expressions
* valid_sql: whether to check the syntax of the expr
* startloc: if not NULL, location of first token is stored at *startloc
* endtoken: if not NULL, ending token is stored at *endtoken
@@ -2763,6 +2778,7 @@ read_sql_construct(int until,
const char *expected,
RawParseMode parsemode,
bool isexpression,
+ bool allowlist,
bool valid_sql,
int *startloc,
int *endtoken,
@@ -2858,7 +2874,7 @@ read_sql_construct(int until,
pfree(ds.data);
if (valid_sql)
- check_sql_expr(expr->query, expr->parseMode, startlocation, yyscanner);
+ check_sql_expr(expr->query, expr->parseMode, allowlist, startlocation, yyscanner);
return expr;
}
@@ -3179,7 +3195,7 @@ make_execsql_stmt(int firsttoken, int location, PLword *word, YYSTYPE *yylvalp,
expr = make_plpgsql_expr(ds.data, RAW_PARSE_DEFAULT);
pfree(ds.data);
- check_sql_expr(expr->query, expr->parseMode, location, yyscanner);
+ check_sql_expr(expr->query, expr->parseMode, false, location, yyscanner);
execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
@@ -3780,11 +3796,15 @@ make_scalar_list1(char *initial_name,
* If no error cursor is provided, we'll just point at "location".
*/
static void
-check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t yyscanner)
+check_sql_expr(const char *stmt,
+ RawParseMode parseMode, bool allowlist,
+ int location, yyscan_t yyscanner)
{
sql_error_callback_arg cbarg;
ErrorContextCallback syntax_errcontext;
MemoryContext oldCxt;
+ List *parsetree;
+ int errpos;
if (!plpgsql_check_syntax)
return;
@@ -3798,11 +3818,25 @@ check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t
error_context_stack = &syntax_errcontext;
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
- (void) raw_parser(stmt, parseMode);
+ parsetree = raw_parser(stmt, parseMode);
MemoryContextSwitchTo(oldCxt);
/* Restore former ereport callback */
error_context_stack = syntax_errcontext.previous;
+
+ if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_STRICTEXPRCHECK ||
+ plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ {
+ /* do this check only for expressions */
+ if (parseMode == RAW_PARSE_DEFAULT)
+ return;
+
+ if (!is_strict_expr(parsetree, &errpos, allowlist))
+ ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK ? ERROR : WARNING,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("syntax of expression is not strict"),
+ parser_errposition(errpos != -1 ? location + errpos : location)));
+ }
}
static void
@@ -3836,6 +3870,74 @@ plpgsql_sql_error_callback(void *arg)
errposition(0);
}
+/*
+ * Returns true, when the only targetList is in parsetree. Cursors
+ * can require list of expressions or list of named expressions.
+ */
+static bool
+is_strict_expr(List *parsetree, int *errpos, bool allowlist)
+{
+ RawStmt *rawstmt;
+ SelectStmt *select;
+ int targets = 0;
+ ListCell *lc;
+
+ /* Top should be RawStmt */
+ rawstmt = castNode(RawStmt, linitial(parsetree));
+
+ if (IsA(rawstmt->stmt, SelectStmt))
+ {
+ select = (SelectStmt *) rawstmt->stmt;
+ }
+ else if (IsA(rawstmt->stmt, PLAssignStmt))
+ {
+ select = castNode(SelectStmt, ((PLAssignStmt *) rawstmt->stmt)->val);
+ }
+ else
+ elog(ERROR, "unexpected node type");
+
+ if (!select->targetList)
+ {
+ *errpos = -1;
+ return false;
+ }
+ else
+ *errpos = exprLocation((Node *) select->targetList);
+
+ if (select->distinctClause ||
+ select->fromClause ||
+ select->whereClause ||
+ select->groupClause ||
+ select->groupDistinct ||
+ select->havingClause ||
+ select->windowClause ||
+ select->sortClause ||
+ select->limitOffset ||
+ select->limitCount ||
+ select->limitOption ||
+ select->lockingClause)
+ return false;
+
+ foreach(lc, select->targetList)
+ {
+ ResTarget *rt = castNode(ResTarget, lfirst(lc));
+
+ if (targets++ >= 1 && !allowlist)
+ {
+ *errpos = exprLocation((Node *) rt);
+ return false;
+ }
+
+ if (rt->name)
+ {
+ *errpos = exprLocation((Node *) rt);
+ return false;
+ }
+ }
+
+ return true;
+}
+
/*
* Parse a SQL datatype name and produce a PLpgSQL_type structure.
*
@@ -4019,7 +4121,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
item = read_sql_construct(',', ')', 0,
",\" or \")",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
yylvalp, yyllocp, yyscanner);
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index e9a72929947..b3ba3163e9a 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -97,6 +97,8 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
+ else if (pg_strcasecmp(tok, "strict_expr_check") == 0)
+ extrachecks |= PLPGSQL_XCHECK_STRICTEXPRCHECK;
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
{
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 41e52b8ce71..459f5f2e223 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -1195,6 +1195,7 @@ extern bool plpgsql_check_asserts;
#define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
+#define PLPGSQL_XCHECK_STRICTEXPRCHECK (1 << 4)
#define PLPGSQL_XCHECK_ALL ((int) ~0)
extern int plpgsql_extra_warnings;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d8ce39dba3c..8f4f5cb1183 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -3084,6 +3084,20 @@ select shadowtest(1);
t
(1 row)
+-- test of strict expression check
+set plpgsql.extra_errors to 'strict_expr_check';
+create or replace function strict_expr_check_func()
+returns void as $$
+declare var int;
+begin
+ var = 1
+ delete from pg_class where false;
+end;
+$$ language plpgsql;
+ERROR: syntax of expression is not strict
+LINE 5: var = 1
+ ^
+reset plpgsql.extra_errors;
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
do $$
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index d413d995d17..dd0d908d422 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -2618,6 +2618,20 @@ declare f1 int; begin return 1; end $$ language plpgsql;
select shadowtest(1);
+-- test of strict expression check
+set plpgsql.extra_errors to 'strict_expr_check';
+
+create or replace function strict_expr_check_func()
+returns void as $$
+declare var int;
+begin
+ var = 1
+ delete from pg_class where false;
+end;
+$$ language plpgsql;
+
+reset plpgsql.extra_errors;
+
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
--
2.50.0
v20250704-0002-simply-check-of-strict-expr-check-on-regress-test.patchtext/x-patch; charset=US-ASCII; name=v20250704-0002-simply-check-of-strict-expr-check-on-regress-test.patchDownload
From bb1dc76db4f2009f52f5faa938ba092044012617 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 08:13:53 +0200
Subject: [PATCH 2/3] simply check of strict-expr-check on regress test
This patch enable strict-expr-check by default to be possible to see
the impact of this option on regress test. Next commit will revert
this option. The strict-expr-check should not be enabled by default.
This commit is done just for testing.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 +++++++++++--------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 0015053e0f2..280ff3e022e 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 14e236d57ab..2c127a821f1 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index 4c6b3ce998a..caf07e834e5 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,7 +50,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
do $$ declare a int[] := array[1,2,3];
begin
@@ -64,30 +64,34 @@ end$$;
NOTICE: a = {1,1,2,3,42,3,1,1,2,3,42,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 0 columns
-CONTEXT: PL/pgSQL assignment "a := from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select from onecol)
+ ^
+QUERY: a := (select from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 2 columns
-CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select f1, f1 from onecol)
+ ^
+QUERY: a := (select f1, f1 from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: query returned more than one row
-CONTEXT: query: a := f1 from onecol
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: more than one row returned by a subquery used as an expression
+CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index b3ba3163e9a..11e4dd1b62c 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -53,7 +53,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors;
+int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -196,7 +196,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "none",
+ "strict_expr_check",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index da984a99414..09a76a8416b 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,7 +46,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
do $$ declare a int[] := array[1,2,3];
begin
@@ -61,26 +61,26 @@ end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index f408d4f69b6..7d004b04031 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
+ wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
+ IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 08984dd98f1..5a0497c1859 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2691,7 +2691,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4e59188196c..faae99515f7 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 8f4f5cb1183..5545858c968 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 6359e5fb689..4d41e791ed1 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -350,7 +350,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 7f5757e89c4..3458dad1749 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index fc6e36d0e78..e857aed4a07 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1678,7 +1678,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 4b2f11dcc64..a3dbd93468e 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index dd0d908d422..238e0ec67f2 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index da4f2fe9c93..6c6e2719b7d 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -223,7 +223,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 51ae1b31b30..1981955ac65 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.50.0
The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: not tested
Documentation: tested, passed
This is properly isolated, only available when turned on.
I ran this through the examples as specified. Then through OUR 1,000 + procedures.
I used \set ON_ERROR_STOP 'on'
and loaded a script that recreates all of our procedures/functions.
In both modes. And it found nothing. (As expected, but that is 1,000 procedures from the wild).
I forced 1 procedure to have the problem.
As expected, when enabled, this STOPPED and pointed out the error.
Without this setting, the code loaded clean.
Small note: The doc says this check is allowed only when plpgsql.extra_errors is set to "strict_expr_check"...
But it also is obviously in effect when that is set to 'all' for reasons that should be completely obvious.
The new status of this patch is: Ready for Committer
Hi
st 30. 7. 2025 v 0:17 odesílatel Kirk Wolak <wolakk@gmail.com> napsal:
The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: not tested
Documentation: tested, passedThis is properly isolated, only available when turned on.
I ran this through the examples as specified. Then through OUR 1,000 +
procedures.
I used \set ON_ERROR_STOP 'on'
and loaded a script that recreates all of our procedures/functions.
In both modes. And it found nothing. (As expected, but that is 1,000
procedures from the wild).
I forced 1 procedure to have the problem.
As expected, when enabled, this STOPPED and pointed out the error.
Without this setting, the code loaded clean.Small note: The doc says this check is allowed only when
plpgsql.extra_errors is set to "strict_expr_check"...
But it also is obviously in effect when that is set to 'all' for reasons
that should be completely obvious.
I removed this sentence from doc. Now, it is consistent with other plpgsql
extra checks.
The new status of this patch is: Ready for Committer
thank you for review
updated patch attached (only documentation was changed, one sentence
removed)
Regards
Pavel
Attachments:
v20250731-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchtext/x-patch; charset=US-ASCII; name=v20250731-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchDownload
From 146831874159996c699df714acd07e60a241fe14 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Wed, 12 Jun 2024 21:34:05 +0200
Subject: [PATCH 1/3] use strict rules for parsing PL/pgSQL expressions
Originally the rule PLpgSQL_Expr allows almost all SQL clauses. It was designed
to allow old undocumented syntax
var := col FROM tab;
The reason for support of this "strange" syntax was technical. The PLpgSQL parser
cannot use SQL parser accurately (it was really primitive), and people found
this undocumented syntax. Lattery, when it was possible to do exact parsing, from
compatibility reasons, the parsing of PL/pgSQL expressions allows described syntax.
Unfortunately, with support almost all SQL clauses, the PLpgSQL can accept
really broken code like
DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
proposed patch introduce new extra error check strict_expr_check, that solve
this issue.
---
doc/src/sgml/plpgsql.sgml | 14 +++
src/pl/plpgsql/src/pl_comp.c | 7 ++
src/pl/plpgsql/src/pl_gram.y | 138 ++++++++++++++++++++++----
src/pl/plpgsql/src/pl_handler.c | 2 +
src/pl/plpgsql/src/plpgsql.h | 1 +
src/test/regress/expected/plpgsql.out | 14 +++
src/test/regress/sql/plpgsql.sql | 14 +++
7 files changed, 172 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index e937491e6b8..556f5777a41 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5388,6 +5388,20 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="plpgsql-extra-checks-strict-expr-check">
+ <term><varname>strict_expr_check</varname></term>
+ <listitem>
+ <para>
+ Enabling this check will cause <application>PL/pgSQL</application> to
+ check if a <application>PL/pgSQL</application> expression is just an
+ expression without any SQL clauses like <literal>FROM</literal>,
+ <literal>ORDER BY</literal>. This undocumented form of expressions
+ is allowed for compatibility reasons, but in some special cases
+ it doesn't allow to detect broken code.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
The following example shows the effect of <varname>plpgsql.extra_warnings</varname>
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index ee961425a5b..500d81388e5 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -796,6 +796,13 @@ plpgsql_compile_inline(char *proc_source)
function->extra_warnings = 0;
function->extra_errors = 0;
+ /*
+ * Although function->extra_errors is disabled, we want to
+ * do strict_expr_check inside annoymous block too.
+ */
+ if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+
function->nstatements = 0;
function->requires_procedure_resowner = false;
function->has_exception_block = false;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 7b672ea5179..df3cee9f7a9 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -18,6 +18,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
#include "parser/parser.h"
#include "parser/parse_type.h"
#include "parser/scanner.h"
@@ -71,6 +72,7 @@ static PLpgSQL_expr *read_sql_construct(int until,
const char *expected,
RawParseMode parsemode,
bool isexpression,
+ bool allowlist,
bool valid_sql,
int *startloc,
int *endtoken,
@@ -106,7 +108,7 @@ static PLpgSQL_row *make_scalar_list1(char *initial_name,
PLpgSQL_datum *initial_datum,
int lineno, int location, yyscan_t yyscanner);
static void check_sql_expr(const char *stmt,
- RawParseMode parseMode, int location, yyscan_t yyscanner);
+ RawParseMode parseMode, bool allowlist, int location, yyscan_t yyscanner);
static void plpgsql_sql_error_callback(void *arg);
static PLpgSQL_type *parse_datatype(const char *string, int location, yyscan_t yyscanner);
static void check_labels(const char *start_label,
@@ -117,6 +119,7 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor, int until,
YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static List *read_raise_options(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
+static bool is_strict_expr(List *parsetree, int *errpos, bool allowlist);
%}
@@ -193,6 +196,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <expr> expr_until_semi
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
+%type <expr> expressions_until_then
%type <var> cursor_variable
%type <datum> decl_cursor_arg
@@ -914,7 +918,7 @@ stmt_perform : K_PERFORM
*/
new->expr = read_sql_construct(';', 0, 0, ";",
RAW_PARSE_DEFAULT,
- false, false,
+ false, false, false,
&startloc, NULL,
&yylval, &yylloc, yyscanner);
/* overwrite "perform" ... */
@@ -924,7 +928,7 @@ stmt_perform : K_PERFORM
strlen(new->expr->query));
/* offset syntax error position to account for that */
check_sql_expr(new->expr->query, new->expr->parseMode,
- startloc + 1, yyscanner);
+ false, startloc + 1, yyscanner);
$$ = (PLpgSQL_stmt *) new;
}
@@ -1001,7 +1005,7 @@ stmt_assign : T_DATUM
plpgsql_push_back_token(T_DATUM, &yylval, &yylloc, yyscanner);
new->expr = read_sql_construct(';', 0, 0, ";",
pmode,
- false, true,
+ false, false, true,
NULL, NULL,
&yylval, &yylloc, yyscanner);
mark_expr_as_assignment_source(new->expr, $1.datum);
@@ -1262,7 +1266,7 @@ case_when_list : case_when_list case_when
}
;
-case_when : K_WHEN expr_until_then proc_sect
+case_when : K_WHEN expressions_until_then proc_sect
{
PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
@@ -1292,6 +1296,15 @@ opt_case_else :
}
;
+expressions_until_then :
+ {
+ $$ = read_sql_construct(K_THEN, 0, 0, "THEN",
+ RAW_PARSE_PLPGSQL_EXPR, /* expr_list */
+ true, true, true, NULL, NULL,
+ &yylval, &yylloc, yyscanner);
+ }
+ ;
+
stmt_loop : opt_loop_label K_LOOP loop_body
{
PLpgSQL_stmt_loop *new;
@@ -1496,6 +1509,7 @@ for_control : for_variable K_IN
RAW_PARSE_DEFAULT,
true,
false,
+ false,
&expr1loc,
&tok,
&yylval, &yylloc, yyscanner);
@@ -1514,7 +1528,7 @@ for_control : for_variable K_IN
*/
expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
check_sql_expr(expr1->query, expr1->parseMode,
- expr1loc, yyscanner);
+ false, expr1loc, yyscanner);
/* Read and check the second one */
expr2 = read_sql_expression2(K_LOOP, K_BY,
@@ -1571,7 +1585,7 @@ for_control : for_variable K_IN
/* Check syntax as a regular query */
check_sql_expr(expr1->query, expr1->parseMode,
- expr1loc, yyscanner);
+ false, expr1loc, yyscanner);
new = palloc0(sizeof(PLpgSQL_stmt_fors));
new->cmd_type = PLPGSQL_STMT_FORS;
@@ -1903,7 +1917,7 @@ stmt_raise : K_RAISE
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &tok,
&yylval, &yylloc, yyscanner);
new->params = lappend(new->params, expr);
@@ -2041,7 +2055,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
@@ -2081,7 +2095,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
new->params = lappend(new->params, expr);
@@ -2717,7 +2731,7 @@ read_sql_expression(int until, const char *expected, YYSTYPE *yylvalp, YYLTYPE *
{
return read_sql_construct(until, 0, 0, expected,
RAW_PARSE_PLPGSQL_EXPR,
- true, true, NULL, NULL,
+ true, false, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2728,7 +2742,7 @@ read_sql_expression2(int until, int until2, const char *expected,
{
return read_sql_construct(until, until2, 0, expected,
RAW_PARSE_PLPGSQL_EXPR,
- true, true, NULL, endtoken,
+ true, false, true, NULL, endtoken,
yylvalp, yyllocp, yyscanner);
}
@@ -2738,7 +2752,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
return read_sql_construct(';', 0, 0, ";",
RAW_PARSE_DEFAULT,
- false, true, NULL, NULL,
+ false, false, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2751,6 +2765,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
* expected: text to use in complaining that terminator was not found
* parsemode: raw_parser() mode to use
* isexpression: whether to say we're reading an "expression" or a "statement"
+ * allowlist: the result can be list of expressions
* valid_sql: whether to check the syntax of the expr
* startloc: if not NULL, location of first token is stored at *startloc
* endtoken: if not NULL, ending token is stored at *endtoken
@@ -2763,6 +2778,7 @@ read_sql_construct(int until,
const char *expected,
RawParseMode parsemode,
bool isexpression,
+ bool allowlist,
bool valid_sql,
int *startloc,
int *endtoken,
@@ -2858,7 +2874,7 @@ read_sql_construct(int until,
pfree(ds.data);
if (valid_sql)
- check_sql_expr(expr->query, expr->parseMode, startlocation, yyscanner);
+ check_sql_expr(expr->query, expr->parseMode, allowlist, startlocation, yyscanner);
return expr;
}
@@ -3179,7 +3195,7 @@ make_execsql_stmt(int firsttoken, int location, PLword *word, YYSTYPE *yylvalp,
expr = make_plpgsql_expr(ds.data, RAW_PARSE_DEFAULT);
pfree(ds.data);
- check_sql_expr(expr->query, expr->parseMode, location, yyscanner);
+ check_sql_expr(expr->query, expr->parseMode, false, location, yyscanner);
execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
@@ -3780,11 +3796,15 @@ make_scalar_list1(char *initial_name,
* If no error cursor is provided, we'll just point at "location".
*/
static void
-check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t yyscanner)
+check_sql_expr(const char *stmt,
+ RawParseMode parseMode, bool allowlist,
+ int location, yyscan_t yyscanner)
{
sql_error_callback_arg cbarg;
ErrorContextCallback syntax_errcontext;
MemoryContext oldCxt;
+ List *parsetree;
+ int errpos;
if (!plpgsql_check_syntax)
return;
@@ -3798,11 +3818,25 @@ check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t
error_context_stack = &syntax_errcontext;
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
- (void) raw_parser(stmt, parseMode);
+ parsetree = raw_parser(stmt, parseMode);
MemoryContextSwitchTo(oldCxt);
/* Restore former ereport callback */
error_context_stack = syntax_errcontext.previous;
+
+ if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_STRICTEXPRCHECK ||
+ plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ {
+ /* do this check only for expressions */
+ if (parseMode == RAW_PARSE_DEFAULT)
+ return;
+
+ if (!is_strict_expr(parsetree, &errpos, allowlist))
+ ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK ? ERROR : WARNING,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("syntax of expression is not strict"),
+ parser_errposition(errpos != -1 ? location + errpos : location)));
+ }
}
static void
@@ -3836,6 +3870,74 @@ plpgsql_sql_error_callback(void *arg)
errposition(0);
}
+/*
+ * Returns true, when the only targetList is in parsetree. Cursors
+ * can require list of expressions or list of named expressions.
+ */
+static bool
+is_strict_expr(List *parsetree, int *errpos, bool allowlist)
+{
+ RawStmt *rawstmt;
+ SelectStmt *select;
+ int targets = 0;
+ ListCell *lc;
+
+ /* Top should be RawStmt */
+ rawstmt = castNode(RawStmt, linitial(parsetree));
+
+ if (IsA(rawstmt->stmt, SelectStmt))
+ {
+ select = (SelectStmt *) rawstmt->stmt;
+ }
+ else if (IsA(rawstmt->stmt, PLAssignStmt))
+ {
+ select = castNode(SelectStmt, ((PLAssignStmt *) rawstmt->stmt)->val);
+ }
+ else
+ elog(ERROR, "unexpected node type");
+
+ if (!select->targetList)
+ {
+ *errpos = -1;
+ return false;
+ }
+ else
+ *errpos = exprLocation((Node *) select->targetList);
+
+ if (select->distinctClause ||
+ select->fromClause ||
+ select->whereClause ||
+ select->groupClause ||
+ select->groupDistinct ||
+ select->havingClause ||
+ select->windowClause ||
+ select->sortClause ||
+ select->limitOffset ||
+ select->limitCount ||
+ select->limitOption ||
+ select->lockingClause)
+ return false;
+
+ foreach(lc, select->targetList)
+ {
+ ResTarget *rt = castNode(ResTarget, lfirst(lc));
+
+ if (targets++ >= 1 && !allowlist)
+ {
+ *errpos = exprLocation((Node *) rt);
+ return false;
+ }
+
+ if (rt->name)
+ {
+ *errpos = exprLocation((Node *) rt);
+ return false;
+ }
+ }
+
+ return true;
+}
+
/*
* Parse a SQL datatype name and produce a PLpgSQL_type structure.
*
@@ -4019,7 +4121,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
item = read_sql_construct(',', ')', 0,
",\" or \")",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
yylvalp, yyllocp, yyscanner);
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index e9a72929947..b3ba3163e9a 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -97,6 +97,8 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
+ else if (pg_strcasecmp(tok, "strict_expr_check") == 0)
+ extrachecks |= PLPGSQL_XCHECK_STRICTEXPRCHECK;
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
{
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 41e52b8ce71..459f5f2e223 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -1195,6 +1195,7 @@ extern bool plpgsql_check_asserts;
#define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
+#define PLPGSQL_XCHECK_STRICTEXPRCHECK (1 << 4)
#define PLPGSQL_XCHECK_ALL ((int) ~0)
extern int plpgsql_extra_warnings;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d8ce39dba3c..8f4f5cb1183 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -3084,6 +3084,20 @@ select shadowtest(1);
t
(1 row)
+-- test of strict expression check
+set plpgsql.extra_errors to 'strict_expr_check';
+create or replace function strict_expr_check_func()
+returns void as $$
+declare var int;
+begin
+ var = 1
+ delete from pg_class where false;
+end;
+$$ language plpgsql;
+ERROR: syntax of expression is not strict
+LINE 5: var = 1
+ ^
+reset plpgsql.extra_errors;
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
do $$
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index d413d995d17..dd0d908d422 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -2618,6 +2618,20 @@ declare f1 int; begin return 1; end $$ language plpgsql;
select shadowtest(1);
+-- test of strict expression check
+set plpgsql.extra_errors to 'strict_expr_check';
+
+create or replace function strict_expr_check_func()
+returns void as $$
+declare var int;
+begin
+ var = 1
+ delete from pg_class where false;
+end;
+$$ language plpgsql;
+
+reset plpgsql.extra_errors;
+
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
--
2.50.1
v20250731-0002-simply-check-of-strict-expr-check-on-regress-test.patchtext/x-patch; charset=US-ASCII; name=v20250731-0002-simply-check-of-strict-expr-check-on-regress-test.patchDownload
From 631d7daa2afce9765aeb8d23fab39a5951e50cfd Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 08:13:53 +0200
Subject: [PATCH 2/3] simply check of strict-expr-check on regress test
This patch enable strict-expr-check by default to be possible to see
the impact of this option on regress test. Next commit will revert
this option. The strict-expr-check should not be enabled by default.
This commit is done just for testing.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 +++++++++++--------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 0015053e0f2..280ff3e022e 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 14e236d57ab..2c127a821f1 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index 4c6b3ce998a..caf07e834e5 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,7 +50,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
do $$ declare a int[] := array[1,2,3];
begin
@@ -64,30 +64,34 @@ end$$;
NOTICE: a = {1,1,2,3,42,3,1,1,2,3,42,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 0 columns
-CONTEXT: PL/pgSQL assignment "a := from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select from onecol)
+ ^
+QUERY: a := (select from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 2 columns
-CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select f1, f1 from onecol)
+ ^
+QUERY: a := (select f1, f1 from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: query returned more than one row
-CONTEXT: query: a := f1 from onecol
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: more than one row returned by a subquery used as an expression
+CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index b3ba3163e9a..11e4dd1b62c 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -53,7 +53,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors;
+int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -196,7 +196,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "none",
+ "strict_expr_check",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index da984a99414..09a76a8416b 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,7 +46,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
do $$ declare a int[] := array[1,2,3];
begin
@@ -61,26 +61,26 @@ end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index f408d4f69b6..7d004b04031 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
+ wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
+ IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 08984dd98f1..5a0497c1859 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2691,7 +2691,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4e59188196c..faae99515f7 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 8f4f5cb1183..5545858c968 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 6359e5fb689..4d41e791ed1 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -350,7 +350,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 7f5757e89c4..3458dad1749 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index fc6e36d0e78..e857aed4a07 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1678,7 +1678,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 4b2f11dcc64..a3dbd93468e 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index dd0d908d422..238e0ec67f2 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index da4f2fe9c93..6c6e2719b7d 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -223,7 +223,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 51ae1b31b30..1981955ac65 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.50.1
v20250731-0003-set-plpgsql.extra_errors-to-none.patchtext/x-patch; charset=US-ASCII; name=v20250731-0003-set-plpgsql.extra_errors-to-none.patchDownload
From 08668da727f2da49c43806c994ab787980151401 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 15:52:38 +0200
Subject: [PATCH 3/3] set plpgsql.extra_errors to "none"
Purpose of previous commit was to run tests with active strict_expr_check.
Now, reset to default and revert all changes from previous commit.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 ++++++++-----------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 52 insertions(+), 56 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 280ff3e022e..0015053e0f2 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 2c127a821f1..14e236d57ab 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index caf07e834e5..4c6b3ce998a 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,7 +50,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
do $$ declare a int[] := array[1,2,3];
begin
@@ -64,34 +64,30 @@ end$$;
NOTICE: a = {1,1,2,3,42,3,1,1,2,3,42,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select from onecol)
- ^
-QUERY: a := (select from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select f1, f1 from onecol)
- ^
-QUERY: a := (select f1, f1 from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: more than one row returned by a subquery used as an expression
-CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query returned more than one row
+CONTEXT: query: a := f1 from onecol
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 11e4dd1b62c..b3ba3163e9a 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -53,7 +53,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+int plpgsql_extra_errors;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -196,7 +196,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "strict_expr_check",
+ "none",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 09a76a8416b..da984a99414 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,7 +46,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
do $$ declare a int[] := array[1,2,3];
begin
@@ -61,26 +61,26 @@ end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
+begin a := from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index 7d004b04031..f408d4f69b6 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
+ wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
+ IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 5a0497c1859..08984dd98f1 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2691,7 +2691,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index faae99515f7..4e59188196c 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 5545858c968..8f4f5cb1183 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 4d41e791ed1..6359e5fb689 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -350,7 +350,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 3458dad1749..7f5757e89c4 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index e857aed4a07..fc6e36d0e78 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1678,7 +1678,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a3dbd93468e..4b2f11dcc64 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 238e0ec67f2..dd0d908d422 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 6c6e2719b7d..da4f2fe9c93 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -223,7 +223,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 1981955ac65..51ae1b31b30 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.50.1
Hi
only rebase
Regards
Pavel
Attachments:
v20251122-0003-set-plpgsql.extra_errors-to-none.patchtext/x-patch; charset=US-ASCII; name=v20251122-0003-set-plpgsql.extra_errors-to-none.patchDownload
From 65dacc20db9dd3a019e8babdb2fb602490804f05 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 15:52:38 +0200
Subject: [PATCH 3/3] set plpgsql.extra_errors to "none"
Purpose of previous commit was to run tests with active strict_expr_check.
Now, reset to default and revert all changes from previous commit.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 ++++++++-----------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 52 insertions(+), 56 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 280ff3e022e..0015053e0f2 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 2c127a821f1..14e236d57ab 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$');
+ archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$';
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index caf07e834e5..4c6b3ce998a 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,7 +50,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
do $$ declare a int[] := array[1,2,3];
begin
@@ -64,34 +64,30 @@ end$$;
NOTICE: a = {1,1,2,3,42,3,1,1,2,3,42,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select from onecol)
- ^
-QUERY: a := (select from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: subquery must return only one column
-LINE 1: a := (select f1, f1 from onecol)
- ^
-QUERY: a := (select f1, f1 from onecol)
-CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
-ERROR: more than one row returned by a subquery used as an expression
-CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query returned more than one row
+CONTEXT: query: a := f1 from onecol
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 11e4dd1b62c..b3ba3163e9a 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -53,7 +53,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+int plpgsql_extra_errors;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -196,7 +196,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "strict_expr_check",
+ "none",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 09a76a8416b..da984a99414 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,7 +46,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
do $$ declare a int[] := array[1,2,3];
begin
@@ -61,26 +61,26 @@ end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := (select from onecol); raise notice 'a = %', a; end$$;
+begin a := from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index 7d004b04031..f408d4f69b6 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
+ wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
+ IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index faf078b22f5..5e98bbf2425 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2692,7 +2692,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index faae99515f7..4e59188196c 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index b3f7f88a74e..a6a02d5454f 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 905dc4af044..5a4077f8ed5 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -384,7 +384,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 3458dad1749..7f5757e89c4 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 01a3fa7118f..417202430a5 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1678,7 +1678,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
+ v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a3dbd93468e..4b2f11dcc64 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return (select f1 from v1);
+ return f1 from v1;
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 238e0ec67f2..dd0d908d422 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if (select count(*) = 0 from Room where roomno = new.roomno) then
+ if count(*) = 0 from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if (select count(*) > 0 from Hub where name = old.hubname) then
+ if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := (select comment from Hub H, HSlot HS
+ retval := comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname);
+ and H.name = HS.hubname;
retval := retval || '' slot '';
- retval := (select retval || slotno::text from HSlot
- where slotname = psrec.slotlink);
+ retval := retval || slotno::text from HSlot
+ where slotname = psrec.slotlink;
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index fe862fc53a5..94e2139c504 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -244,7 +244,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
+ relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 1981955ac65..51ae1b31b30 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' stable;
+'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return (select max(a) from xacttest); end' volatile;
+'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.51.1
v20251122-0002-simply-check-of-strict-expr-check-on-regress-test.patchtext/x-patch; charset=US-ASCII; name=v20251122-0002-simply-check-of-strict-expr-check-on-regress-test.patchDownload
From 1928e841060d33ae46c5f1ec9d2d172bf2f10fab Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sun, 16 Jun 2024 08:13:53 +0200
Subject: [PATCH 2/3] simply check of strict-expr-check on regress test
This patch enable strict-expr-check by default to be possible to see
the impact of this option on regress test. Next commit will revert
this option. The strict-expr-check should not be enabled by default.
This commit is done just for testing.
---
.../basic_archive/expected/basic_archive.out | 4 +--
contrib/basic_archive/sql/basic_archive.sql | 4 +--
src/pl/plpgsql/src/expected/plpgsql_array.out | 34 +++++++++++--------
src/pl/plpgsql/src/pl_handler.c | 4 +--
src/pl/plpgsql/src/sql/plpgsql_array.sql | 14 ++++----
.../recovery/t/026_overwrite_contrecord.pl | 4 +--
src/test/regress/expected/alter_table.out | 2 +-
src/test/regress/expected/plancache.out | 2 +-
src/test/regress/expected/plpgsql.out | 12 +++----
src/test/regress/expected/stats_ext.out | 2 +-
src/test/regress/expected/transactions.out | 4 +--
src/test/regress/sql/alter_table.sql | 2 +-
src/test/regress/sql/plancache.sql | 2 +-
src/test/regress/sql/plpgsql.sql | 12 +++----
src/test/regress/sql/stats_ext.sql | 2 +-
src/test/regress/sql/transactions.sql | 4 +--
16 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out
index 0015053e0f2..280ff3e022e 100644
--- a/contrib/basic_archive/expected/basic_archive.out
+++ b/contrib/basic_archive/expected/basic_archive.out
@@ -11,8 +11,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql
index 14e236d57ab..2c127a821f1 100644
--- a/contrib/basic_archive/sql/basic_archive.sql
+++ b/contrib/basic_archive/sql/basic_archive.sql
@@ -7,8 +7,8 @@ DECLARE
loops int := 0;
BEGIN
LOOP
- archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a
- WHERE a ~ '^[0-9A-F]{24}$';
+ archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a
+ WHERE a ~ '^[0-9A-F]{24}$');
IF archived OR loops > 120 * 10 THEN EXIT; END IF;
PERFORM pg_sleep(0.1);
loops := loops + 1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index 4c6b3ce998a..caf07e834e5 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -50,7 +50,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3}
do $$ declare a int[] := array[1,2,3];
begin
@@ -64,30 +64,34 @@ end$$;
NOTICE: a = {1,1,2,3,42,3,1,1,2,3,42,3}
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 0 columns
-CONTEXT: PL/pgSQL assignment "a := from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select from onecol)
+ ^
+QUERY: a := (select from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: assignment source returned 2 columns
-CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
-PL/pgSQL function inline_code_block line 2 at assignment
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: subquery must return only one column
+LINE 1: a := (select f1, f1 from onecol)
+ ^
+QUERY: a := (select f1, f1 from onecol)
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
-ERROR: query returned more than one row
-CONTEXT: query: a := f1 from onecol
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
+ERROR: more than one row returned by a subquery used as an expression
+CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)"
PL/pgSQL function inline_code_block line 2 at assignment
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2}
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index b3ba3163e9a..11e4dd1b62c 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -53,7 +53,7 @@ bool plpgsql_check_asserts = true;
static char *plpgsql_extra_warnings_string = NULL;
static char *plpgsql_extra_errors_string = NULL;
int plpgsql_extra_warnings;
-int plpgsql_extra_errors;
+int plpgsql_extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
/* Hook for plugins */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
@@ -196,7 +196,7 @@ _PG_init(void)
gettext_noop("List of programming constructs that should produce an error."),
NULL,
&plpgsql_extra_errors_string,
- "none",
+ "strict_expr_check",
PGC_USERSET, GUC_LIST_INPUT,
plpgsql_extra_checks_check_hook,
plpgsql_extra_errors_assign_hook,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index da984a99414..09a76a8416b 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -46,7 +46,7 @@ do $$ declare a quadarray;
begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[];
-begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$;
do $$ declare a int[] := array[1,2,3];
begin
@@ -61,26 +61,26 @@ end$$;
create temp table onecol as select array[1,2] as f1;
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+begin a := (select * from onecol for update); raise notice 'a = %', a; end$$;
-- error cases:
do $$ declare a int[];
-begin a := from onecol; raise notice 'a = %', a; end$$;
+begin a := (select from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$;
insert into onecol values(array[11]);
do $$ declare a int[];
-begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol); raise notice 'a = %', a; end$$;
do $$ declare a int[];
-begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$;
do $$ declare a real;
begin a[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index f408d4f69b6..7d004b04031 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -33,7 +33,7 @@ $node->safe_psql(
'postgres', q{
DO $$
DECLARE
- wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size';
+ wal_segsize int := (SELECT setting::int FROM pg_settings WHERE name = 'wal_segment_size');
remain int;
iters int := 0;
BEGIN
@@ -43,7 +43,7 @@ BEGIN
from generate_series(1, 10) g;
remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize;
- IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN
+ IF (SELECT remain < 2 * setting::int from pg_settings where name = 'block_size') THEN
RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain;
EXIT;
END IF;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 5e98bbf2425..faf078b22f5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2692,7 +2692,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4e59188196c..faae99515f7 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -139,7 +139,7 @@ create temp view v1 as
select 2+2 as f1;
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
cache_test_2
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index a6a02d5454f..b3f7f88a74e 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -121,7 +121,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -286,7 +286,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -942,12 +942,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 5a4077f8ed5..905dc4af044 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -384,7 +384,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 7f5757e89c4..3458dad1749 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -543,7 +543,7 @@ select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
@@ -558,7 +558,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 417202430a5..01a3fa7118f 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1678,7 +1678,7 @@ LANGUAGE plpgsql AS $$
DECLARE
v_relfilenode oid;
BEGIN
- v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename;
+ v_relfilenode := (SELECT relfilenode FROM pg_class WHERE oid = p_tablename);
EXECUTE p_ddl;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 4b2f11dcc64..a3dbd93468e 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -81,7 +81,7 @@ create temp view v1 as
create function cache_test_2() returns int as $$
begin
- return f1 from v1;
+ return (select f1 from v1);
end$$ language plpgsql;
select cache_test_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index dd0d908d422..238e0ec67f2 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -161,7 +161,7 @@ create trigger tg_room_ad after delete
-- ************************************************************
create function tg_wslot_biu() returns trigger as $$
begin
- if count(*) = 0 from Room where roomno = new.roomno then
+ if (select count(*) = 0 from Room where roomno = new.roomno) then
raise exception 'Room % does not exist', new.roomno;
end if;
return new;
@@ -348,7 +348,7 @@ begin
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
- if count(*) > 0 from Hub where name = old.hubname then
+ if (select count(*) > 0 from Hub where name = old.hubname) then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
@@ -1071,12 +1071,12 @@ begin
return retval || pslot_backlink_view(psrec.slotlink);
end if;
if sltype = ''HS'' then
- retval := comment from Hub H, HSlot HS
+ retval := (select comment from Hub H, HSlot HS
where HS.slotname = psrec.slotlink
- and H.name = HS.hubname;
+ and H.name = HS.hubname);
retval := retval || '' slot '';
- retval := retval || slotno::text from HSlot
- where slotname = psrec.slotlink;
+ retval := (select retval || slotno::text from HSlot
+ where slotname = psrec.slotlink);
return retval;
end if;
return psrec.slotlink;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 94e2139c504..fe862fc53a5 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -244,7 +244,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt;
CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1;
DO $$
DECLARE
- relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass;
+ relname text := (SELECT reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass);
BEGIN
EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname;
EXCEPTION WHEN wrong_object_type THEN
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 51ae1b31b30..1981955ac65 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -320,7 +320,7 @@ rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' stable;
+'begin return (select max(a) from xacttest); end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
@@ -328,7 +328,7 @@ select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
-'begin return max(a) from xacttest; end' volatile;
+'begin return (select max(a) from xacttest); end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
--
2.51.1
v20251122-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchtext/x-patch; charset=US-ASCII; name=v20251122-0001-use-strict-rules-for-parsing-PL-pgSQL-expressions.patchDownload
From 0a6874d631054b6d0881700bd03d8891d5c0f4a6 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Wed, 12 Jun 2024 21:34:05 +0200
Subject: [PATCH 1/3] use strict rules for parsing PL/pgSQL expressions
Originally the rule PLpgSQL_Expr allows almost all SQL clauses. It was designed
to allow old undocumented syntax
var := col FROM tab;
The reason for support of this "strange" syntax was technical. The PLpgSQL parser
cannot use SQL parser accurately (it was really primitive), and people found
this undocumented syntax. Lattery, when it was possible to do exact parsing, from
compatibility reasons, the parsing of PL/pgSQL expressions allows described syntax.
Unfortunately, with support almost all SQL clauses, the PLpgSQL can accept
really broken code like
DO $$
DECLARE
l_cnt int;
BEGIN
l_cnt := 1
DELETE FROM foo3 WHERE id=1;
END; $$;
proposed patch introduce new extra error check strict_expr_check, that solve
this issue.
---
doc/src/sgml/plpgsql.sgml | 14 +++
src/pl/plpgsql/src/pl_comp.c | 7 ++
src/pl/plpgsql/src/pl_gram.y | 138 ++++++++++++++++++++++----
src/pl/plpgsql/src/pl_handler.c | 2 +
src/pl/plpgsql/src/plpgsql.h | 1 +
src/test/regress/expected/plpgsql.out | 14 +++
src/test/regress/sql/plpgsql.sql | 14 +++
7 files changed, 172 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 561f6e50d63..52862ad4509 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5388,6 +5388,20 @@ a_output := a_output || $$ IF v_$$ || referrer_keys.kind || $$ LIKE '$$
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="plpgsql-extra-checks-strict-expr-check">
+ <term><varname>strict_expr_check</varname></term>
+ <listitem>
+ <para>
+ Enabling this check will cause <application>PL/pgSQL</application> to
+ check if a <application>PL/pgSQL</application> expression is just an
+ expression without any SQL clauses like <literal>FROM</literal>,
+ <literal>ORDER BY</literal>. This undocumented form of expressions
+ is allowed for compatibility reasons, but in some special cases
+ it doesn't allow to detect broken code.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
The following example shows the effect of <varname>plpgsql.extra_warnings</varname>
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index f6976689a69..89df7c006db 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -800,6 +800,13 @@ plpgsql_compile_inline(char *proc_source)
function->extra_warnings = 0;
function->extra_errors = 0;
+ /*
+ * Although function->extra_errors is disabled, we want to
+ * do strict_expr_check inside annoymous block too.
+ */
+ if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ function->extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
+
function->nstatements = 0;
function->requires_procedure_resowner = false;
function->has_exception_block = false;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 17568d82554..2095e35b3b0 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -18,6 +18,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
#include "parser/parser.h"
#include "parser/parse_type.h"
#include "parser/scanner.h"
@@ -71,6 +72,7 @@ static PLpgSQL_expr *read_sql_construct(int until,
const char *expected,
RawParseMode parsemode,
bool isexpression,
+ bool allowlist,
bool valid_sql,
int *startloc,
int *endtoken,
@@ -106,7 +108,7 @@ static PLpgSQL_row *make_scalar_list1(char *initial_name,
PLpgSQL_datum *initial_datum,
int lineno, int location, yyscan_t yyscanner);
static void check_sql_expr(const char *stmt,
- RawParseMode parseMode, int location, yyscan_t yyscanner);
+ RawParseMode parseMode, bool allowlist, int location, yyscan_t yyscanner);
static void plpgsql_sql_error_callback(void *arg);
static PLpgSQL_type *parse_datatype(const char *string, int location, yyscan_t yyscanner);
static void check_labels(const char *start_label,
@@ -117,6 +119,7 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor, int until,
YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static List *read_raise_options(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
+static bool is_strict_expr(List *parsetree, int *errpos, bool allowlist);
%}
@@ -193,6 +196,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <expr> expr_until_semi
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
+%type <expr> expressions_until_then
%type <var> cursor_variable
%type <datum> decl_cursor_arg
@@ -914,7 +918,7 @@ stmt_perform : K_PERFORM
*/
new->expr = read_sql_construct(';', 0, 0, ";",
RAW_PARSE_DEFAULT,
- false, false,
+ false, false, false,
&startloc, NULL,
&yylval, &yylloc, yyscanner);
/* overwrite "perform" ... */
@@ -924,7 +928,7 @@ stmt_perform : K_PERFORM
strlen(new->expr->query));
/* offset syntax error position to account for that */
check_sql_expr(new->expr->query, new->expr->parseMode,
- startloc + 1, yyscanner);
+ false, startloc + 1, yyscanner);
$$ = (PLpgSQL_stmt *) new;
}
@@ -1001,7 +1005,7 @@ stmt_assign : T_DATUM
plpgsql_push_back_token(T_DATUM, &yylval, &yylloc, yyscanner);
new->expr = read_sql_construct(';', 0, 0, ";",
pmode,
- false, true,
+ false, false, true,
NULL, NULL,
&yylval, &yylloc, yyscanner);
mark_expr_as_assignment_source(new->expr, $1.datum);
@@ -1262,7 +1266,7 @@ case_when_list : case_when_list case_when
}
;
-case_when : K_WHEN expr_until_then proc_sect
+case_when : K_WHEN expressions_until_then proc_sect
{
PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
@@ -1292,6 +1296,15 @@ opt_case_else :
}
;
+expressions_until_then :
+ {
+ $$ = read_sql_construct(K_THEN, 0, 0, "THEN",
+ RAW_PARSE_PLPGSQL_EXPR, /* expr_list */
+ true, true, true, NULL, NULL,
+ &yylval, &yylloc, yyscanner);
+ }
+ ;
+
stmt_loop : opt_loop_label K_LOOP loop_body
{
PLpgSQL_stmt_loop *new;
@@ -1496,6 +1509,7 @@ for_control : for_variable K_IN
RAW_PARSE_DEFAULT,
true,
false,
+ false,
&expr1loc,
&tok,
&yylval, &yylloc, yyscanner);
@@ -1514,7 +1528,7 @@ for_control : for_variable K_IN
*/
expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
check_sql_expr(expr1->query, expr1->parseMode,
- expr1loc, yyscanner);
+ false, expr1loc, yyscanner);
/* Read and check the second one */
expr2 = read_sql_expression2(K_LOOP, K_BY,
@@ -1571,7 +1585,7 @@ for_control : for_variable K_IN
/* Check syntax as a regular query */
check_sql_expr(expr1->query, expr1->parseMode,
- expr1loc, yyscanner);
+ false, expr1loc, yyscanner);
new = palloc0(sizeof(PLpgSQL_stmt_fors));
new->cmd_type = PLPGSQL_STMT_FORS;
@@ -1903,7 +1917,7 @@ stmt_raise : K_RAISE
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &tok,
&yylval, &yylloc, yyscanner);
new->params = lappend(new->params, expr);
@@ -2041,7 +2055,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
@@ -2081,7 +2095,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
&yylval, &yylloc, yyscanner);
new->params = lappend(new->params, expr);
@@ -2717,7 +2731,7 @@ read_sql_expression(int until, const char *expected, YYSTYPE *yylvalp, YYLTYPE *
{
return read_sql_construct(until, 0, 0, expected,
RAW_PARSE_PLPGSQL_EXPR,
- true, true, NULL, NULL,
+ true, false, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2728,7 +2742,7 @@ read_sql_expression2(int until, int until2, const char *expected,
{
return read_sql_construct(until, until2, 0, expected,
RAW_PARSE_PLPGSQL_EXPR,
- true, true, NULL, endtoken,
+ true, false, true, NULL, endtoken,
yylvalp, yyllocp, yyscanner);
}
@@ -2738,7 +2752,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
{
return read_sql_construct(';', 0, 0, ";",
RAW_PARSE_DEFAULT,
- false, true, NULL, NULL,
+ false, false, true, NULL, NULL,
yylvalp, yyllocp, yyscanner);
}
@@ -2751,6 +2765,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
* expected: text to use in complaining that terminator was not found
* parsemode: raw_parser() mode to use
* isexpression: whether to say we're reading an "expression" or a "statement"
+ * allowlist: the result can be list of expressions
* valid_sql: whether to check the syntax of the expr
* startloc: if not NULL, location of first token is stored at *startloc
* endtoken: if not NULL, ending token is stored at *endtoken
@@ -2763,6 +2778,7 @@ read_sql_construct(int until,
const char *expected,
RawParseMode parsemode,
bool isexpression,
+ bool allowlist,
bool valid_sql,
int *startloc,
int *endtoken,
@@ -2858,7 +2874,7 @@ read_sql_construct(int until,
pfree(ds.data);
if (valid_sql)
- check_sql_expr(expr->query, expr->parseMode, startlocation, yyscanner);
+ check_sql_expr(expr->query, expr->parseMode, allowlist, startlocation, yyscanner);
return expr;
}
@@ -3179,7 +3195,7 @@ make_execsql_stmt(int firsttoken, int location, PLword *word, YYSTYPE *yylvalp,
expr = make_plpgsql_expr(ds.data, RAW_PARSE_DEFAULT);
pfree(ds.data);
- check_sql_expr(expr->query, expr->parseMode, location, yyscanner);
+ check_sql_expr(expr->query, expr->parseMode, false, location, yyscanner);
execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
@@ -3780,11 +3796,15 @@ make_scalar_list1(char *initial_name,
* If no error cursor is provided, we'll just point at "location".
*/
static void
-check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t yyscanner)
+check_sql_expr(const char *stmt,
+ RawParseMode parseMode, bool allowlist,
+ int location, yyscan_t yyscanner)
{
sql_error_callback_arg cbarg;
ErrorContextCallback syntax_errcontext;
MemoryContext oldCxt;
+ List *parsetree;
+ int errpos;
if (!plpgsql_check_syntax)
return;
@@ -3798,11 +3818,25 @@ check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t
error_context_stack = &syntax_errcontext;
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
- (void) raw_parser(stmt, parseMode);
+ parsetree = raw_parser(stmt, parseMode);
MemoryContextSwitchTo(oldCxt);
/* Restore former ereport callback */
error_context_stack = syntax_errcontext.previous;
+
+ if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_STRICTEXPRCHECK ||
+ plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+ {
+ /* do this check only for expressions */
+ if (parseMode == RAW_PARSE_DEFAULT)
+ return;
+
+ if (!is_strict_expr(parsetree, &errpos, allowlist))
+ ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK ? ERROR : WARNING,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("syntax of expression is not strict"),
+ parser_errposition(errpos != -1 ? location + errpos : location)));
+ }
}
static void
@@ -3836,6 +3870,74 @@ plpgsql_sql_error_callback(void *arg)
errposition(0);
}
+/*
+ * Returns true, when the only targetList is in parsetree. Cursors
+ * can require list of expressions or list of named expressions.
+ */
+static bool
+is_strict_expr(List *parsetree, int *errpos, bool allowlist)
+{
+ RawStmt *rawstmt;
+ SelectStmt *select;
+ int targets = 0;
+ ListCell *lc;
+
+ /* Top should be RawStmt */
+ rawstmt = castNode(RawStmt, linitial(parsetree));
+
+ if (IsA(rawstmt->stmt, SelectStmt))
+ {
+ select = (SelectStmt *) rawstmt->stmt;
+ }
+ else if (IsA(rawstmt->stmt, PLAssignStmt))
+ {
+ select = castNode(SelectStmt, ((PLAssignStmt *) rawstmt->stmt)->val);
+ }
+ else
+ elog(ERROR, "unexpected node type");
+
+ if (!select->targetList)
+ {
+ *errpos = -1;
+ return false;
+ }
+ else
+ *errpos = exprLocation((Node *) select->targetList);
+
+ if (select->distinctClause ||
+ select->fromClause ||
+ select->whereClause ||
+ select->groupClause ||
+ select->groupDistinct ||
+ select->havingClause ||
+ select->windowClause ||
+ select->sortClause ||
+ select->limitOffset ||
+ select->limitCount ||
+ select->limitOption ||
+ select->lockingClause)
+ return false;
+
+ foreach(lc, select->targetList)
+ {
+ ResTarget *rt = castNode(ResTarget, lfirst(lc));
+
+ if (targets++ >= 1 && !allowlist)
+ {
+ *errpos = exprLocation((Node *) rt);
+ return false;
+ }
+
+ if (rt->name)
+ {
+ *errpos = exprLocation((Node *) rt);
+ return false;
+ }
+ }
+
+ return true;
+}
+
/*
* Parse a SQL datatype name and produce a PLpgSQL_type structure.
*
@@ -4025,7 +4127,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
item = read_sql_construct(',', ')', 0,
",\" or \")",
RAW_PARSE_PLPGSQL_EXPR,
- true, true,
+ true, false, true,
NULL, &endtoken,
yylvalp, yyllocp, yyscanner);
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index e9a72929947..b3ba3163e9a 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -97,6 +97,8 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
+ else if (pg_strcasecmp(tok, "strict_expr_check") == 0)
+ extrachecks |= PLPGSQL_XCHECK_STRICTEXPRCHECK;
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
{
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 5f193a37183..0ee6f118e74 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -1195,6 +1195,7 @@ extern bool plpgsql_check_asserts;
#define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
+#define PLPGSQL_XCHECK_STRICTEXPRCHECK (1 << 4)
#define PLPGSQL_XCHECK_ALL ((int) ~0)
extern int plpgsql_extra_warnings;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 474be478ce8..a6a02d5454f 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -3087,6 +3087,20 @@ select shadowtest(1);
t
(1 row)
+-- test of strict expression check
+set plpgsql.extra_errors to 'strict_expr_check';
+create or replace function strict_expr_check_func()
+returns void as $$
+declare var int;
+begin
+ var = 1
+ delete from pg_class where false;
+end;
+$$ language plpgsql;
+ERROR: syntax of expression is not strict
+LINE 5: var = 1
+ ^
+reset plpgsql.extra_errors;
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
do $$
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index d413d995d17..dd0d908d422 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -2618,6 +2618,20 @@ declare f1 int; begin return 1; end $$ language plpgsql;
select shadowtest(1);
+-- test of strict expression check
+set plpgsql.extra_errors to 'strict_expr_check';
+
+create or replace function strict_expr_check_func()
+returns void as $$
+declare var int;
+begin
+ var = 1
+ delete from pg_class where false;
+end;
+$$ language plpgsql;
+
+reset plpgsql.extra_errors;
+
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
--
2.51.1