From 29b28080867ddbf371135e6a9c3dc81a2ea89e13 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <lic@highgo.com>
Date: Thu, 14 May 2026 17:51:35 +0800
Subject: [PATCH v1] Fix COPY TO attribute mapping for partitioned tables

COPY TO on a partitioned table reads tuples from partitions and maps
them to the root table's tuple descriptor before output.  However, the
attribute map was built in the wrong direction, from the root table to
the partition.

This could produce incorrect results when a partition has a different
physical tuple descriptor from the root table, for example because it has
dropped columns.

Build the attribute map from the partition descriptor to the root table
descriptor instead.

Add a regression test covering COPY TO from a partition whose physical
descriptor includes a dropped column.

Author: Chao Li <lic@highgo.com>
---
 src/backend/commands/copyto.c      |  4 ++--
 src/test/regress/expected/copy.out | 11 +++++++++++
 src/test/regress/sql/copy.sql      |  9 +++++++++
 3 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index 1085d0d5b8d..ffed63a2986 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -1348,8 +1348,8 @@ CopyRelationTo(CopyToState cstate, Relation rel, Relation root_rel, uint64 *proc
 	if (root_rel != NULL)
 	{
 		root_slot = table_slot_create(root_rel, NULL);
-		map = build_attrmap_by_name_if_req(RelationGetDescr(root_rel),
-										   RelationGetDescr(rel),
+		map = build_attrmap_by_name_if_req(RelationGetDescr(rel),
+										   RelationGetDescr(root_rel),
 										   false);
 	}
 
diff --git a/src/test/regress/expected/copy.out b/src/test/regress/expected/copy.out
index 1714faab39c..37498cdd6e7 100644
--- a/src/test/regress/expected/copy.out
+++ b/src/test/regress/expected/copy.out
@@ -594,3 +594,14 @@ id	val
 5	15
 6	16
 DROP TABLE PP;
+-- Check if COPY TO handles dropped columns in partitions.
+CREATE TABLE pp_dropcol (id int, val int) PARTITION BY RANGE (id);
+CREATE TABLE pp_dropcol_1 (dropme int, id int, val int);
+ALTER TABLE pp_dropcol_1 DROP COLUMN dropme;
+ALTER TABLE pp_dropcol ATTACH PARTITION pp_dropcol_1 FOR VALUES FROM (1) TO (10);
+INSERT INTO pp_dropcol VALUES (1, 11), (2, 12);
+COPY pp_dropcol TO stdout(header);
+id	val
+1	11
+2	12
+DROP TABLE pp_dropcol;
diff --git a/src/test/regress/sql/copy.sql b/src/test/regress/sql/copy.sql
index eaad290b257..094fd76c12b 100644
--- a/src/test/regress/sql/copy.sql
+++ b/src/test/regress/sql/copy.sql
@@ -535,3 +535,12 @@ CREATE TABLE pp_510 PARTITION OF pp_2 FOR VALUES FROM (5) TO (10);
 INSERT INTO pp SELECT g, 10 + g FROM generate_series(1,6) g;
 COPY pp TO stdout(header);
 DROP TABLE PP;
+
+-- Check if COPY TO handles dropped columns in partitions.
+CREATE TABLE pp_dropcol (id int, val int) PARTITION BY RANGE (id);
+CREATE TABLE pp_dropcol_1 (dropme int, id int, val int);
+ALTER TABLE pp_dropcol_1 DROP COLUMN dropme;
+ALTER TABLE pp_dropcol ATTACH PARTITION pp_dropcol_1 FOR VALUES FROM (1) TO (10);
+INSERT INTO pp_dropcol VALUES (1, 11), (2, 12);
+COPY pp_dropcol TO stdout(header);
+DROP TABLE pp_dropcol;
-- 
2.50.1 (Apple Git-155)

