]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Deparse FOR PORTION OF using the range column's current name.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 27 Jul 2026 13:37:04 +0000 (09:37 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 27 Jul 2026 13:37:04 +0000 (09:37 -0400)
Commit 8e72d914c recorded the range column's name in ForPortionOfExpr
and used that for deparsing FOR PORTION OF.  This gives the wrong
answer if the ForPortionOfExpr is saved in a rule or SQL function and
then the column gets renamed.  Drop the ForPortionOfExpr.range_name
field; instead fetch the current column name from the catalogs when
needed.

Also drop ForPortionOfState.fp_rangeName, which wasn't being used
anywhere.

Full disclosure: an earlier draft of this patch was made with
Claude Opus 4.8.

Reported-by: John Naylor <johncnaylorls@gmail.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/CANWCAZYFEpJ5Oi45gi4q9Y6LYa4_oiAXxuNNWe-1ym-i0fF8Pw@mail.gmail.com
Backpatch-through: 19

src/backend/executor/nodeModifyTable.c
src/backend/optimizer/plan/planner.c
src/backend/parser/analyze.c
src/backend/utils/adt/ruleutils.c
src/include/catalog/catversion.h
src/include/nodes/execnodes.h
src/include/nodes/primnodes.h
src/test/regress/expected/for_portion_of.out
src/test/regress/sql/for_portion_of.sql

index b9781eb3b95ba7a9c9078e8995384d22ba598026..1dbf0ffff9ed668bafee3d4f546ef8e370104769 100644 (file)
@@ -5641,7 +5641,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
                /* Create state for FOR PORTION OF operation */
 
                fpoState = makeNode(ForPortionOfState);
-               fpoState->fp_rangeName = forPortionOf->range_name;
                fpoState->fp_rangeType = forPortionOf->rangeType;
                fpoState->fp_rangeAttno = forPortionOf->rangeVar->varattno;
                fpoState->fp_targetRange = targetRange;
@@ -5928,7 +5927,6 @@ ExecInitForPortionOf(ModifyTableState *mtstate, EState *estate,
 
        leafState = makeNode(ForPortionOfState);
 
-       leafState->fp_rangeName = fpoState->fp_rangeName;
        leafState->fp_rangeType = fpoState->fp_rangeType;
        leafState->fp_targetRange = fpoState->fp_targetRange;
        map = ExecGetChildToRootMap(resultRelInfo);
index 3225185d16f8f2551034694cb91a5aa87263d91c..a0ff9159ae08c8aee7558adfa29cb24f231c7e07 100644 (file)
@@ -871,7 +871,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name,
                        ereport(ERROR,
                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                         errmsg("cannot use generated column \"%s\" in FOR PORTION OF",
-                                                       forPortionOf->range_name)));
+                                                       get_attname(rte->relid,
+                                                                               forPortionOf->rangeVar->varattno,
+                                                                               false))));
        }
 
        /*
index 562e4facd74f46ae3bfa7346ba5b1dc73c9b0645..581457c69c91628036ac958acceb9a3c943659bb 100644 (file)
@@ -1606,7 +1606,6 @@ transformForPortionOfClause(ParseState *pstate,
        else
                result->rangeTargetList = NIL;
 
-       result->range_name = forPortionOf->range_name;
        result->location = forPortionOf->location;
        result->targetLocation = forPortionOf->target_location;
 
index 043e43b630964d8e5f080ecc23c3a04b64547875..908134594cb1a06d838d6300bc42f31dbbb09939 100644 (file)
@@ -527,6 +527,7 @@ static void get_rte_alias(RangeTblEntry *rte, int varno, bool use_as,
 static void get_column_alias_list(deparse_columns *colinfo,
                                                                  deparse_context *context);
 static void get_for_portion_of(ForPortionOfExpr *forPortionOf,
+                                                          RangeTblEntry *rte,
                                                           deparse_context *context);
 static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
                                                                           deparse_columns *colinfo,
@@ -7556,7 +7557,7 @@ get_update_query_def(Query *query, deparse_context *context)
                                         generate_relation_name(rte->relid, NIL));
 
        /* Print the FOR PORTION OF, if needed */
-       get_for_portion_of(query->forPortionOf, context);
+       get_for_portion_of(query->forPortionOf, rte, context);
 
        /* Print the relation alias, if needed */
        get_rte_alias(rte, query->resultRelation, false, context);
@@ -7763,7 +7764,7 @@ get_delete_query_def(Query *query, deparse_context *context)
                                         generate_relation_name(rte->relid, NIL));
 
        /* Print the FOR PORTION OF, if needed */
-       get_for_portion_of(query->forPortionOf, context);
+       get_for_portion_of(query->forPortionOf, rte, context);
 
        /* Print the relation alias, if needed */
        get_rte_alias(rte, query->resultRelation, false, context);
@@ -13479,12 +13480,18 @@ get_rte_alias(RangeTblEntry *rte, int varno, bool use_as,
  * alias and SET will be on their own line with a leading space.
  */
 static void
-get_for_portion_of(ForPortionOfExpr *forPortionOf, deparse_context *context)
+get_for_portion_of(ForPortionOfExpr *forPortionOf, RangeTblEntry *rte,
+                                  deparse_context *context)
 {
        if (forPortionOf)
        {
+               char       *range_name;
+
+               range_name = get_attname(rte->relid,
+                                                                forPortionOf->rangeVar->varattno,
+                                                                false);
                appendStringInfo(context->buf, " FOR PORTION OF %s",
-                                                quote_identifier(forPortionOf->range_name));
+                                                quote_identifier(range_name));
 
                /*
                 * Try to write it as FROM ... TO ... if we received it that way,
index 83d462f4d4aae940a5e7d461312c7817c2ba63a9..f20ea73ed39966d217bbdcaf8d959103d0f525e8 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202607271
+#define CATALOG_VERSION_NO     202607273
 
 #endif
index e64fd8c7ea300910513a381ee53d7c1871c68989..e95ac3eda35727f716576d6d975ebf2bfe8caf8c 100644 (file)
@@ -476,7 +476,6 @@ typedef struct ForPortionOfState
 {
        NodeTag         type;
 
-       char       *fp_rangeName;       /* the column named in FOR PORTION OF */
        Oid                     fp_rangeType;   /* the base type (not domain) of the FOR
                                                                 * PORTION OF expression */
        int                     fp_rangeAttno;  /* the attno of the range column */
index 1f712666511161408f6fb5ca79cb0eb20012969a..44f828cbb372ead5b5ec6a87ff869b6dc141b2a3 100644 (file)
@@ -2438,7 +2438,6 @@ typedef struct ForPortionOfExpr
 {
        NodeTag         type;
        Var                *rangeVar;           /* Range column */
-       char       *range_name;         /* Range name */
        Node       *targetFrom;         /* FOR PORTION OF FROM bound, if given */
        Node       *targetTo;           /* FOR PORTION OF TO bound, if given */
        Node       *targetRange;        /* FOR PORTION OF bounds as a range/multirange */
index 0e217f104efea0aaebc98ee165a06b1c471b68fb..3c592f90e70ce9970c35b2ac359d99d7f604a66d 100644 (file)
@@ -2255,6 +2255,39 @@ SELECT * FROM fpo_rule ORDER BY f1;
 (2 rows)
 
 DROP TABLE fpo_rule;
+-- Deparsing FOR PORTION OF must use the range column's current name,
+-- not the name it had when the rule was created.
+CREATE TABLE fpo_rename (f1 bigint, f2 int4range);
+CREATE TABLE fpo_rename_src (x int);
+CREATE RULE fpo_rename_rule1 AS ON UPDATE TO fpo_rename_src
+  DO INSTEAD UPDATE fpo_rename FOR PORTION OF f2 FROM 3 TO 6 SET f1 = 2;
+CREATE RULE fpo_rename_rule2 AS ON DELETE TO fpo_rename_src
+  DO INSTEAD DELETE FROM fpo_rename FOR PORTION OF f2 (int4range(3, 6));
+\d+ fpo_rename_src
+                              Table "public.fpo_rename_src"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ x      | integer |           |          |         | plain   |              | 
+Rules:
+    fpo_rename_rule1 AS
+    ON UPDATE TO fpo_rename_src DO INSTEAD  UPDATE fpo_rename FOR PORTION OF f2 FROM 3 TO 6 SET f1 = 2
+    fpo_rename_rule2 AS
+    ON DELETE TO fpo_rename_src DO INSTEAD  DELETE FROM fpo_rename FOR PORTION OF f2 (int4range(3, 6))
+
+ALTER TABLE fpo_rename RENAME COLUMN f1 TO ff1;
+ALTER TABLE fpo_rename RENAME COLUMN f2 TO ff2;
+\d+ fpo_rename_src
+                              Table "public.fpo_rename_src"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ x      | integer |           |          |         | plain   |              | 
+Rules:
+    fpo_rename_rule1 AS
+    ON UPDATE TO fpo_rename_src DO INSTEAD  UPDATE fpo_rename FOR PORTION OF ff2 FROM 3 TO 6 SET ff1 = 2
+    fpo_rename_rule2 AS
+    ON DELETE TO fpo_rename_src DO INSTEAD  DELETE FROM fpo_rename FOR PORTION OF ff2 (int4range(3, 6))
+
+DROP TABLE fpo_rename, fpo_rename_src;
 -- UPDATE/DELETE FOR PORTION OF on a GENERATED VIRTUAL range column:
 CREATE TABLE fpo_gen_virtual (
   a int,
index a8d29a76b226609040ac10d4067562d808bee342..5f7b04fdf6b40846a84c33168f684693fd75d088 100644 (file)
@@ -1495,6 +1495,22 @@ SELECT * FROM fpo_rule ORDER BY f1;
 
 DROP TABLE fpo_rule;
 
+-- Deparsing FOR PORTION OF must use the range column's current name,
+-- not the name it had when the rule was created.
+CREATE TABLE fpo_rename (f1 bigint, f2 int4range);
+CREATE TABLE fpo_rename_src (x int);
+CREATE RULE fpo_rename_rule1 AS ON UPDATE TO fpo_rename_src
+  DO INSTEAD UPDATE fpo_rename FOR PORTION OF f2 FROM 3 TO 6 SET f1 = 2;
+CREATE RULE fpo_rename_rule2 AS ON DELETE TO fpo_rename_src
+  DO INSTEAD DELETE FROM fpo_rename FOR PORTION OF f2 (int4range(3, 6));
+
+\d+ fpo_rename_src
+ALTER TABLE fpo_rename RENAME COLUMN f1 TO ff1;
+ALTER TABLE fpo_rename RENAME COLUMN f2 TO ff2;
+\d+ fpo_rename_src
+
+DROP TABLE fpo_rename, fpo_rename_src;
+
 -- UPDATE/DELETE FOR PORTION OF on a GENERATED VIRTUAL range column:
 CREATE TABLE fpo_gen_virtual (
   a int,