From 697e1d3d5e271c40074005e5b0b5829b4f1e3b50 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 19 Oct 2026 17:51:10 -0400 Subject: [PATCH] Fix two issues in deparsing. If the target relation of UPDATE, DELETE, or MERGE has an alias, we printed that "bare" (without AS or forced quoting), the same as we do in FROM. However, if that alias is "set", there's an ambiguity with the SET keyword that must follow the relation specification. The parser would choose the wrong interpretation, so that the dumped query would fail to reload. The least surprising and most future-proof fix seems to be to always print AS in these contexts, as we already did in INSERT. Also, we were insufficiently careful about parenthesizing the left-hand argument of a SubLink (IN, op ANY/ALL, or row comparison), so that the printed expression might not parse back with the intended meaning depending on operator precedence. This is only a problem in "pretty-print" mode, since non-pretty mode fully parenthesizes every operator. So it isn't a hazard for pg_dump, but it still seems like something to fix. To do so, switch to using get_rule_expr_paren. Presently that will add parens in some cases where it wouldn't really be necessary, but that's better than the reverse, and it arguably is helpful for readability anyway. Author: Henson Choi Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CAAAe_zDqXin5NeB5atRCbZqUdcMOeyXRqhan+tVp0F30OCURnw@mail.gmail.com Discussion: https://postgr.es/m/CAAAe_zCy+D45Qd_br484F+V34y0BVA=hp_7cb=u6HysyMkE3UQ@mail.gmail.com Backpatch-through: 14 --- src/backend/utils/adt/ruleutils.c | 25 ++++++++++++++++------- src/test/regress/expected/create_view.out | 10 +++++++-- src/test/regress/expected/rules.out | 10 ++++----- src/test/regress/sql/create_view.sql | 5 ++++- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index b4424db01c3..8a9beed7854 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -6889,8 +6889,11 @@ get_update_query_def(Query *query, deparse_context *context, only_marker(rte), generate_relation_name(rte->relid, NIL)); - /* Print the relation alias, if needed */ - get_rte_alias(rte, query->resultRelation, false, context); + /* + * Print the relation alias, if needed. Always write AS, since a bare + * alias named "set" would be read as the SET keyword. + */ + get_rte_alias(rte, query->resultRelation, true, context); appendStringInfoString(buf, " SET "); @@ -7098,8 +7101,11 @@ get_delete_query_def(Query *query, deparse_context *context, only_marker(rte), generate_relation_name(rte->relid, NIL)); - /* Print the relation alias, if needed */ - get_rte_alias(rte, query->resultRelation, false, context); + /* + * Print the relation alias, if needed. Always write AS, since a bare + * alias named "set" would be read as the SET keyword. + */ + get_rte_alias(rte, query->resultRelation, true, context); /* Add the USING clause if given */ get_from_clause(query, " USING ", context); @@ -7151,8 +7157,11 @@ get_merge_query_def(Query *query, deparse_context *context, only_marker(rte), generate_relation_name(rte->relid, NIL)); - /* Print the relation alias, if needed */ - get_rte_alias(rte, query->resultRelation, false, context); + /* + * Print the relation alias, if needed. Always write AS, since a bare + * alias named "set" would be read as the SET keyword. + */ + get_rte_alias(rte, query->resultRelation, true, context); /* Print the source relation and join clause */ get_from_clause(query, " USING ", context); @@ -10807,7 +10816,9 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) /* single combining operator */ OpExpr *opexpr = (OpExpr *) sublink->testexpr; - get_rule_expr(linitial(opexpr->args), context, true); + /* Use get_rule_expr_paren to add parens if needed */ + get_rule_expr_paren(linitial(opexpr->args), context, true, + (Node *) sublink); opname = generate_operator_name(opexpr->opno, exprType(linitial(opexpr->args)), exprType(lsecond(opexpr->args))); diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out index 4fc636c85fc..8ec3230c582 100644 --- a/src/test/regress/expected/create_view.out +++ b/src/test/regress/expected/create_view.out @@ -2103,7 +2103,10 @@ select x + y + z as c1, (x > y) AND (y > z OR x > z) as c8, (x > y) OR (y > z AND NOT (x > z)) as c9, (x,y) <> ALL (values(1,2),(3,4)) as c10, - (x,y) <= ANY (values(1,2),(3,4)) as c11 + (x,y) <= ANY (values(1,2),(3,4)) as c11, + (x>0 OR y>0) IN (SELECT false) AS c12, + (x < y) <> ALL (SELECT false) AS c13, + true = (y = ANY (SELECT 1)) AS c14 from (values(1,2,3)) v(x,y,z); select pg_get_viewdef('tt26v', true); pg_get_viewdef @@ -2118,7 +2121,10 @@ select pg_get_viewdef('tt26v', true); v.x > v.y AND (v.y > v.z OR v.x > v.z) AS c8, + v.x > v.y OR v.y > v.z AND NOT v.x > v.z AS c9, + ((v.x, v.y) <> ALL ( VALUES (1,2), (3,4))) AS c10,+ - ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11 + + ((v.x, v.y) <= ANY ( VALUES (1,2), (3,4))) AS c11,+ + ((v.x > 0 OR v.y > 0) IN ( SELECT false)) AS c12, + + ((v.x < v.y) <> ALL ( SELECT false)) AS c13, + + true = ((v.y IN ( SELECT 1))) AS c14 + FROM ( VALUES (1,2,3)) v(x, y, z); (1 row) diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index bc9fe0f0358..34a39a15d1f 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -3033,7 +3033,7 @@ Rules: RETURNING trgt.f1, trgt.f2 r6 AS - ON UPDATE TO rules_src DO INSTEAD UPDATE rules_log trgt SET tag = 'updated'::text + ON UPDATE TO rules_src DO INSTEAD UPDATE rules_log AS trgt SET tag = 'updated'::text WHERE trgt.f1 = new.f1 r7 AS ON DELETE TO rules_src DO INSTEAD WITH wins AS ( @@ -3041,10 +3041,10 @@ Rules: VALUES (0) RETURNING trgt_1.f1 ), wupd AS ( - UPDATE int4_tbl trgt_1 SET f1 = trgt_1.f1 + 1 + UPDATE int4_tbl AS trgt_1 SET f1 = trgt_1.f1 + 1 RETURNING trgt_1.f1 ), wdel AS ( - DELETE FROM int4_tbl trgt_1 + DELETE FROM int4_tbl AS trgt_1 WHERE trgt_1.f1 = 0 RETURNING trgt_1.f1 ) @@ -3072,7 +3072,7 @@ create rule rr as on update to rule_t1 do instead UPDATE rule_dest trgt f2 | integer | | | | plain | | Rules: rr AS - ON UPDATE TO rule_t1 DO INSTEAD UPDATE rule_dest trgt SET (f2[1], f1, tag) = ( SELECT new.f2, + ON UPDATE TO rule_t1 DO INSTEAD UPDATE rule_dest AS trgt SET (f2[1], f1, tag) = ( SELECT new.f2, new.f1, 'updated'::character varying AS "varchar") WHERE trgt.f1 = new.f1 @@ -3618,7 +3618,7 @@ CREATE OR REPLACE FUNCTION public.merge_sf_test() RETURNS void LANGUAGE sql BEGIN ATOMIC - MERGE INTO sf_target t + MERGE INTO sf_target AS t USING rule_merge1 s ON (s.a = t.id) WHEN MATCHED diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql index d57219516e9..e8bba27e2c1 100644 --- a/src/test/regress/sql/create_view.sql +++ b/src/test/regress/sql/create_view.sql @@ -777,7 +777,10 @@ select x + y + z as c1, (x > y) AND (y > z OR x > z) as c8, (x > y) OR (y > z AND NOT (x > z)) as c9, (x,y) <> ALL (values(1,2),(3,4)) as c10, - (x,y) <= ANY (values(1,2),(3,4)) as c11 + (x,y) <= ANY (values(1,2),(3,4)) as c11, + (x>0 OR y>0) IN (SELECT false) AS c12, + (x < y) <> ALL (SELECT false) AS c13, + true = (y = ANY (SELECT 1)) AS c14 from (values(1,2,3)) v(x,y,z); select pg_get_viewdef('tt26v', true);