From 89e22ee5a5d902af56d4f8574f2fba2471df58a2 Mon Sep 17 00:00:00 2001 From: Henson Choi Date: Tue, 13 Oct 2026 14:55:47 +0900 Subject: [PATCH v1 2/2] Write AS before an alias named SET of a DML target The target of UPDATE, DELETE and MERGE is parsed by relation_expr_opt_alias, where the keyword SET takes precedence over a bare alias. "set" is an unreserved keyword, so quote_identifier() leaves it bare, and a rule or a BEGIN ATOMIC function body with an alias named "set" on such a target could not be parsed back. Print AS in that case. A FROM-clause alias named "set" does not need it. Backpatch-through: 15 --- src/backend/utils/adt/ruleutils.c | 27 ++++++++----- src/test/regress/expected/rules.out | 59 ++++++++++++++++------------- 2 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 5e8e1683db0..035e13147d6 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -515,7 +515,7 @@ static void get_from_clause(Query *query, const char *prefix, static void get_from_clause_item(Node *jtnode, Query *query, deparse_context *context); static void get_rte_alias(RangeTblEntry *rte, int varno, bool use_as, - deparse_context *context); + bool dml_target, deparse_context *context); static void get_column_alias_list(deparse_columns *colinfo, deparse_context *context); static void get_from_clause_coldeflist(RangeTblFunction *rtfunc, @@ -6998,7 +6998,7 @@ get_insert_query_def(Query *query, deparse_context *context) generate_relation_name(rte->relid, NIL)); /* Print the relation alias, if needed; INSERT requires explicit AS */ - get_rte_alias(rte, query->resultRelation, true, context); + get_rte_alias(rte, query->resultRelation, true, false, context); /* always want a space here */ appendStringInfoChar(buf, ' '); @@ -7197,7 +7197,7 @@ get_update_query_def(Query *query, deparse_context *context) generate_relation_name(rte->relid, NIL)); /* Print the relation alias, if needed */ - get_rte_alias(rte, query->resultRelation, false, context); + get_rte_alias(rte, query->resultRelation, false, true, context); appendStringInfoString(buf, " SET "); @@ -7401,7 +7401,7 @@ get_delete_query_def(Query *query, deparse_context *context) generate_relation_name(rte->relid, NIL)); /* Print the relation alias, if needed */ - get_rte_alias(rte, query->resultRelation, false, context); + get_rte_alias(rte, query->resultRelation, false, true, context); /* Add the USING clause if given */ get_from_clause(query, " USING ", context); @@ -7450,7 +7450,7 @@ get_merge_query_def(Query *query, deparse_context *context) generate_relation_name(rte->relid, NIL)); /* Print the relation alias, if needed */ - get_rte_alias(rte, query->resultRelation, false, context); + get_rte_alias(rte, query->resultRelation, false, true, context); /* Print the source relation and join clause */ get_from_clause(query, " USING ", context); @@ -12703,7 +12703,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) } /* Print the relation alias, if needed */ - get_rte_alias(rte, varno, false, context); + get_rte_alias(rte, varno, false, false, context); /* Print the column definitions or aliases, if needed */ if (rtfunc1 && rtfunc1->funccolnames != NIL) @@ -12844,10 +12844,12 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) /* * get_rte_alias - print the relation's alias, if needed * - * If printed, the alias is preceded by a space, or by " AS " if use_as is true. + * If printed, the alias is preceded by a space, or by " AS " if use_as is + * true. " AS " is also used if dml_target is true and the alias is "set", + * which would otherwise be read as the SET keyword. */ static void -get_rte_alias(RangeTblEntry *rte, int varno, bool use_as, +get_rte_alias(RangeTblEntry *rte, int varno, bool use_as, bool dml_target, deparse_context *context) { deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces); @@ -12906,9 +12908,16 @@ get_rte_alias(RangeTblEntry *rte, int varno, bool use_as, printalias = true; } + /* + * The target of UPDATE, DELETE and MERGE is parsed by + * relation_expr_opt_alias, which gives the SET keyword precedence over a + * bare alias named "set" (an unreserved keyword, so quote_identifier + * leaves it bare). Write AS in that case; elsewhere "set" is fine. + */ if (printalias) appendStringInfo(context->buf, "%s%s", - use_as ? " AS " : " ", + (use_as || (dml_target && strcmp(refname, "set") == 0)) ? + " AS " : " ", quote_identifier(refname)); } diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index e7aa49de416..b075b22fdc8 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -4164,13 +4164,13 @@ create rule ruletest_setalias_r2 as on insert to ruletest_setalias do also delete from ruletest_setalias "set" where "set".a = 1; select rulename, pg_get_ruledef(oid) from pg_rewrite where rulename like 'ruletest_setalias_r%' order by rulename; - rulename | pg_get_ruledef -----------------------+-------------------------------------------------------------------------------------- - ruletest_setalias_r1 | CREATE RULE ruletest_setalias_r1 AS + - | ON INSERT TO public.ruletest_setalias DO UPDATE ruletest_setalias set SET b = 1+ + rulename | pg_get_ruledef +----------------------+----------------------------------------------------------------------------------------- + ruletest_setalias_r1 | CREATE RULE ruletest_setalias_r1 AS + + | ON INSERT TO public.ruletest_setalias DO UPDATE ruletest_setalias AS set SET b = 1+ | WHERE (set.a = 2); - ruletest_setalias_r2 | CREATE RULE ruletest_setalias_r2 AS + - | ON INSERT TO public.ruletest_setalias DO DELETE FROM ruletest_setalias set + + ruletest_setalias_r2 | CREATE RULE ruletest_setalias_r2 AS + + | ON INSERT TO public.ruletest_setalias DO DELETE FROM ruletest_setalias AS set + | WHERE (set.a = 1); (2 rows) @@ -4178,27 +4178,27 @@ select pg_get_ruledef(oid) as def from pg_rewrite where rulename = 'ruletest_setalias_r1' \gset drop rule ruletest_setalias_r1 on ruletest_setalias; :def -ERROR: syntax error at or near "b" -LINE 2: ...ruletest_setalias DO UPDATE ruletest_setalias set SET b = 1 - ^ select pg_get_ruledef(oid) from pg_rewrite where rulename = 'ruletest_setalias_r1'; - pg_get_ruledef ----------------- -(0 rows) + pg_get_ruledef +----------------------------------------------------------------------------------------- + CREATE RULE ruletest_setalias_r1 AS + + ON INSERT TO public.ruletest_setalias DO UPDATE ruletest_setalias AS set SET b = 1+ + WHERE (set.a = 2); +(1 row) select pg_get_ruledef(oid) as def from pg_rewrite where rulename = 'ruletest_setalias_r2' \gset drop rule ruletest_setalias_r2 on ruletest_setalias; :def -ERROR: syntax error at or near "set" -LINE 2: ...blic.ruletest_setalias DO DELETE FROM ruletest_setalias set - ^ select pg_get_ruledef(oid) from pg_rewrite where rulename = 'ruletest_setalias_r2'; - pg_get_ruledef ----------------- -(0 rows) + pg_get_ruledef +------------------------------------------------------------------------------------ + CREATE RULE ruletest_setalias_r2 AS + + ON INSERT TO public.ruletest_setalias DO DELETE FROM ruletest_setalias AS set+ + WHERE (set.a = 1); +(1 row) create table ruletest_setalias_m (a int, b int); create table ruletest_setalias_src (a int); @@ -4215,7 +4215,7 @@ select pg_get_functiondef('ruletest_setalias_f'::regproc); RETURNS void + LANGUAGE sql + BEGIN ATOMIC + - MERGE INTO ruletest_setalias_m set + + MERGE INTO ruletest_setalias_m AS set + USING ruletest_setalias_src s + ON (set.a = s.a) + WHEN MATCHED + @@ -4227,14 +4227,22 @@ select pg_get_functiondef('ruletest_setalias_f'::regproc); select pg_get_functiondef('ruletest_setalias_f'::regproc) as def \gset drop function ruletest_setalias_f(); :def; -ERROR: syntax error at or near "set" -LINE 5: MERGE INTO ruletest_setalias_m set - ^ select pg_get_functiondef(oid) from pg_proc where proname = 'ruletest_setalias_f'; - pg_get_functiondef --------------------- -(0 rows) + pg_get_functiondef +--------------------------------------------------------- + CREATE OR REPLACE FUNCTION public.ruletest_setalias_f()+ + RETURNS void + + LANGUAGE sql + + BEGIN ATOMIC + + MERGE INTO ruletest_setalias_m AS set + + USING ruletest_setalias_src s + + ON (set.a = s.a) + + WHEN MATCHED + + THEN UPDATE SET b = 1; + + END + + +(1 row) -- A FROM-clause alias named "set" does not need AS create view ruletest_setalias_v as @@ -4251,7 +4259,6 @@ select pg_get_viewdef('ruletest_setalias_v'::regclass); drop view ruletest_setalias_v; drop function ruletest_setalias_f(); -ERROR: function ruletest_setalias_f() does not exist drop table ruletest_setalias_src; drop table ruletest_setalias_m; drop table ruletest_setalias; -- 2.54.0 (Apple Git-157)