Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wNSrq-000pnj-1L for pgsql-hackers@arkaria.postgresql.org; Thu, 14 May 2026 10:00:34 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wNSro-00BhoG-0b for pgsql-hackers@arkaria.postgresql.org; Thu, 14 May 2026 10:00:32 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wNSrn-00Bho7-2N for pgsql-hackers@lists.postgresql.org; Thu, 14 May 2026 10:00:31 +0000 Received: from mail-pl1-x631.google.com ([2607:f8b0:4864:20::631]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wNSrk-00000000VyG-3dAQ for pgsql-hackers@postgresql.org; Thu, 14 May 2026 10:00:30 +0000 Received: by mail-pl1-x631.google.com with SMTP id d9443c01a7336-2baef9f5ecdso49672075ad.1 for ; Thu, 14 May 2026 03:00:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778752827; x=1779357627; darn=postgresql.org; h=to:cc:date:message-id:subject:mime-version:from:from:to:cc:subject :date:message-id:reply-to; bh=tlPEpgBfWOrtVQGj2m8G5pr/WzsQWJWGsmRwJsWYXoE=; b=HHsuLi0bi7yv2MMvWlOaq2LAA7uFdpp+Lp6qRVI9NCJZ0ZcHR7L6b4KQAPbTSsSZGJ dgDM/aXFQ+Oo14KMNRij3qh7qg3rieRa+Yt83Ndc3kSuRL1CEPJEozW/H1uWZ82bI4UF KcNwzDqH+Kr7NH8gOudXi0If7+DsychBwWwm+nGWOIv7JVxHftMG/XmFnJCzPy7DrHnR I56vziQiNWl1u4KnAO6dwf0424K+i8HoGTpSMoFwtpkkHfUdrnH/EstUDa/H6Q8xQOMR F59+WenptFAlMMoONVHusj4byxs+9QhLmzRhh3B/IzECuOdp3dqyaXngiwLwMLLfwTZc iHHw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778752827; x=1779357627; h=to:cc:date:message-id:subject:mime-version:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=tlPEpgBfWOrtVQGj2m8G5pr/WzsQWJWGsmRwJsWYXoE=; b=hs5Q6drWd6mA9AeXSLk9SkwAI7bsi2F2pGZ9sNU/SctbsewSs1lcFWJ5Tj7uO9AH+/ IdN+rJeIU+cjwGsyEjfR8PfOqXB2LoGlscj79MvBsIOX+FT3FPwfULRLsKmqk9F9Ac08 wdK4/b8AWxy+8sfSR3bW6ZcQTTIfZ3b/NCobRACJxePpwaBG3+lh+QEppo1eVsbyqlJI vJENDi/TUdX2TfdX/Exe9wgN7NJ6hEl6K+MrO4szoROjOT3llUhx2zgM6sKIdJV2Bjro zli6EZIyJiYrwBgyFMIZNP7kL5SNPOHZP5T2THY9XaM2BPOb2twM0cSYeanye4xwcujB 7/xw== X-Gm-Message-State: AOJu0Ywdkizs0BSF371Uz1rVHh7n2aN8fX44QfgOo5VEfxfKSCGS+KCr K7SAz+tVwYX31+otqlBpuy9mNmE5QZ85qUte0jwxSz+mSEE6jrct1LskboZLwdZvVto= X-Gm-Gg: Acq92OE7OCn70ztu2jL+h0VCEXdCWHO/5HXOAxlb9eQElUxO4LfavWmrHZ8LBRFEaok fQSi2M8Fj5g0rlOTfy4RP0wRUaVIQFAvF47mhzpg4whUYgg84Z6sC4c559Bur98usAPxDfEB3Gh yUeVR77eX731amATjs/rvQ8HmqE81jfGo27xVNEbLzbfVaL3Yw+vkjhuQ7h0sOgD6tE9lXiJHoo YXFcyzbmWv6sOTjYZ1jbbjQNgibbVMFwYi2kupUtejWfB0xI1aDCJNyQnh88448ED3EO5Y9smUd NTAtozGAlVrU4E5CihjDRzuE1lyauZUGUDi+g1VXc4ojmAB2xKDsvUh4BI29qeTbyIQrrqKfWxv 2swfFSpC1PE7qEuyxzRrO+kT0ZwcaAxAAkdNR+5cywdUbmYEx3LlvIRINZjneiKzw8UBsouS4KX w0dj1xODIEU+hUBVLguxDOTdaUEdmZxqI= X-Received: by 2002:a17:903:3e25:b0:2b0:6a22:5165 with SMTP id d9443c01a7336-2bd2718bf33mr59139315ad.7.1778752827092; Thu, 14 May 2026 03:00:27 -0700 (PDT) Received: from smtpclient.apple ([45.32.121.103]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2bd5c26355csm19586105ad.35.2026.05.14.03.00.24 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 14 May 2026 03:00:26 -0700 (PDT) From: Chao Li Content-Type: multipart/mixed; boundary="Apple-Mail=_4D774B5B-4773-4A3A-93F9-C3D7D2EB3AFC" Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3864.400.21\)) Subject: Fix bug of COPY TO support partition table Message-Id: <85EA70F3-C3DB-477B-B856-EA569FDAAE7C@gmail.com> Date: Thu, 14 May 2026 17:59:46 +0800 Cc: Masahiko Sawada , jian he To: PostgreSQL-development X-Mailer: Apple Mail (2.3864.400.21) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --Apple-Mail=_4D774B5B-4773-4A3A-93F9-C3D7D2EB3AFC Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 Hi, I just tested the "Support COPY TO for partitioned tables" patch. While = tracing a normal case, I noticed this suspicious code: ``` /* * If we are exporting partition data here, we check if = converting tuples * to the root table's rowtype, because a partition might have = column * order different than its root table. */ if (root_rel !=3D NULL) { root_slot =3D table_slot_create(root_rel, NULL); map =3D = build_attrmap_by_name_if_req(RelationGetDescr(root_rel), <<=3D=3D = input is root table = RelationGetDescr(rel), <=3D=3D output is partition = false); } while (table_scan_getnextslot(scandesc, ForwardScanDirection, = slot)) { TupleTableSlot *copyslot; CHECK_FOR_INTERRUPTS(); if (map !=3D NULL) copyslot =3D execute_attr_map_slot(map, slot, = root_slot); <=3D=3D input is partition, output is root table else { /* Deconstruct the tuple */ slot_getallattrs(slot); copyslot =3D slot; } ``` The code comment says that partition tuples should be converted to the = root table's rowtype before output. Also, `execute_attr_map_slot()` = takes the partition slot as input and the root slot as output. However, = the attribute map is built in the opposite direction, with the root = relation as input and the partition relation as output. In normal cases, partitions have the same attribute list as the root = table, so this bug is not triggered. I built the following repro by = dropping a column from the partition, which makes the partition=E2=80=99s = physical tuple descriptor different from the root table's descriptor: ``` evantest=3D# create table p (a int, b int) partition by range (a); CREATE TABLE evantest=3D# create table c (x int, a int, b int); CREATE TABLE evantest=3D# alter table c drop column x; ALTER TABLE evantest=3D# alter table p attach partition c for values from (1) to = (10); ALTER TABLE evantest=3D# insert into p values (1, 11), (2, 22); INSERT 0 2 evantest=3D# copy p to stdout; \N \N \N \N ``` As shown above, COPY incorrectly outputs all NULLs. The fix is straightforward, just reverse the order of `rel` and = `root_rel` when calling `build_attrmap_by_name_if_req()`. After the fix, = COPY works correctly: ``` evantest=3D# copy p to stdout; 1 11 2 22 ``` Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/ --Apple-Mail=_4D774B5B-4773-4A3A-93F9-C3D7D2EB3AFC Content-Disposition: attachment; filename=v1-0001-Fix-COPY-TO-attribute-mapping-for-partitioned-tab.patch Content-Type: application/octet-stream; x-unix-mode=0644; name="v1-0001-Fix-COPY-TO-attribute-mapping-for-partitioned-tab.patch" Content-Transfer-Encoding: quoted-printable =46rom=2029b28080867ddbf371135e6a9c3dc81a2ea89e13=20Mon=20Sep=2017=20= 00:00:00=202001=0AFrom:=20"Chao=20Li=20(Evan)"=20=0A= Date:=20Thu,=2014=20May=202026=2017:51:35=20+0800=0ASubject:=20[PATCH=20= v1]=20Fix=20COPY=20TO=20attribute=20mapping=20for=20partitioned=20tables=0A= =0ACOPY=20TO=20on=20a=20partitioned=20table=20reads=20tuples=20from=20= partitions=20and=20maps=0Athem=20to=20the=20root=20table's=20tuple=20= descriptor=20before=20output.=20=20However,=20the=0Aattribute=20map=20= was=20built=20in=20the=20wrong=20direction,=20from=20the=20root=20table=20= to=0Athe=20partition.=0A=0AThis=20could=20produce=20incorrect=20results=20= when=20a=20partition=20has=20a=20different=0Aphysical=20tuple=20= descriptor=20from=20the=20root=20table,=20for=20example=20because=20it=20= has=0Adropped=20columns.=0A=0ABuild=20the=20attribute=20map=20from=20the=20= partition=20descriptor=20to=20the=20root=20table=0Adescriptor=20instead.=0A= =0AAdd=20a=20regression=20test=20covering=20COPY=20TO=20from=20a=20= partition=20whose=20physical=0Adescriptor=20includes=20a=20dropped=20= column.=0A=0AAuthor:=20Chao=20Li=20=0A---=0A=20= src/backend/commands/copyto.c=20=20=20=20=20=20|=20=204=20++--=0A=20= src/test/regress/expected/copy.out=20|=2011=20+++++++++++=0A=20= src/test/regress/sql/copy.sql=20=20=20=20=20=20|=20=209=20+++++++++=0A=20= 3=20files=20changed,=2022=20insertions(+),=202=20deletions(-)=0A=0Adiff=20= --git=20a/src/backend/commands/copyto.c=20= b/src/backend/commands/copyto.c=0Aindex=201085d0d5b8d..ffed63a2986=20= 100644=0A---=20a/src/backend/commands/copyto.c=0A+++=20= b/src/backend/commands/copyto.c=0A@@=20-1348,8=20+1348,8=20@@=20= CopyRelationTo(CopyToState=20cstate,=20Relation=20rel,=20Relation=20= root_rel,=20uint64=20*proc=0A=20=09if=20(root_rel=20!=3D=20NULL)=0A=20=09= {=0A=20=09=09root_slot=20=3D=20table_slot_create(root_rel,=20NULL);=0A-=09= =09map=20=3D=20build_attrmap_by_name_if_req(RelationGetDescr(root_rel),=0A= -=09=09=09=09=09=09=09=09=09=09=20=20=20RelationGetDescr(rel),=0A+=09=09= map=20=3D=20build_attrmap_by_name_if_req(RelationGetDescr(rel),=0A+=09=09= =09=09=09=09=09=09=09=09=20=20=20RelationGetDescr(root_rel),=0A=20=09=09=09= =09=09=09=09=09=09=09=20=20=20false);=0A=20=09}=0A=20=0Adiff=20--git=20= a/src/test/regress/expected/copy.out=20= b/src/test/regress/expected/copy.out=0Aindex=201714faab39c..37498cdd6e7=20= 100644=0A---=20a/src/test/regress/expected/copy.out=0A+++=20= b/src/test/regress/expected/copy.out=0A@@=20-594,3=20+594,14=20@@=20id=09= val=0A=205=0915=0A=206=0916=0A=20DROP=20TABLE=20PP;=0A+--=20Check=20if=20= COPY=20TO=20handles=20dropped=20columns=20in=20partitions.=0A+CREATE=20= TABLE=20pp_dropcol=20(id=20int,=20val=20int)=20PARTITION=20BY=20RANGE=20= (id);=0A+CREATE=20TABLE=20pp_dropcol_1=20(dropme=20int,=20id=20int,=20= val=20int);=0A+ALTER=20TABLE=20pp_dropcol_1=20DROP=20COLUMN=20dropme;=0A= +ALTER=20TABLE=20pp_dropcol=20ATTACH=20PARTITION=20pp_dropcol_1=20FOR=20= VALUES=20FROM=20(1)=20TO=20(10);=0A+INSERT=20INTO=20pp_dropcol=20VALUES=20= (1,=2011),=20(2,=2012);=0A+COPY=20pp_dropcol=20TO=20stdout(header);=0A= +id=09val=0A+1=0911=0A+2=0912=0A+DROP=20TABLE=20pp_dropcol;=0Adiff=20= --git=20a/src/test/regress/sql/copy.sql=20= b/src/test/regress/sql/copy.sql=0Aindex=20eaad290b257..094fd76c12b=20= 100644=0A---=20a/src/test/regress/sql/copy.sql=0A+++=20= b/src/test/regress/sql/copy.sql=0A@@=20-535,3=20+535,12=20@@=20CREATE=20= TABLE=20pp_510=20PARTITION=20OF=20pp_2=20FOR=20VALUES=20FROM=20(5)=20TO=20= (10);=0A=20INSERT=20INTO=20pp=20SELECT=20g,=2010=20+=20g=20FROM=20= generate_series(1,6)=20g;=0A=20COPY=20pp=20TO=20stdout(header);=0A=20= DROP=20TABLE=20PP;=0A+=0A+--=20Check=20if=20COPY=20TO=20handles=20= dropped=20columns=20in=20partitions.=0A+CREATE=20TABLE=20pp_dropcol=20= (id=20int,=20val=20int)=20PARTITION=20BY=20RANGE=20(id);=0A+CREATE=20= TABLE=20pp_dropcol_1=20(dropme=20int,=20id=20int,=20val=20int);=0A+ALTER=20= TABLE=20pp_dropcol_1=20DROP=20COLUMN=20dropme;=0A+ALTER=20TABLE=20= pp_dropcol=20ATTACH=20PARTITION=20pp_dropcol_1=20FOR=20VALUES=20FROM=20= (1)=20TO=20(10);=0A+INSERT=20INTO=20pp_dropcol=20VALUES=20(1,=2011),=20= (2,=2012);=0A+COPY=20pp_dropcol=20TO=20stdout(header);=0A+DROP=20TABLE=20= pp_dropcol;=0A--=20=0A2.50.1=20(Apple=20Git-155)=0A=0A= --Apple-Mail=_4D774B5B-4773-4A3A-93F9-C3D7D2EB3AFC--