From c9f09d70b02b95e929261d0a293c692e7247fc2f Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Tue, 8 Sep 2026 10:18:13 -0700
Subject: [PATCH v1] Fix tablesync failure for partitioned tables on older
 publishers.

Commit 266543a made initial table synchronization use "COPY table TO"
for a partitioned table, but decided that from the relation kind
alone. A partitioned table is only accepted there since v19, so a v19
subscriber replicating from an older publisher with
publish_via_partition_root failed at table sync.

Check the publisher version, and fall back to the "COPY (SELECT ...)
TO" variant otherwise.

Backpatch to v19, where 266543a changed the initial table
synchronization.

Reported-by: Noah Misch <noah@leadboat.com>
Reviewed-by:
Discussion: https://postgr.es/m/20260905040020.57.noahmisch@microsoft.com
Backpatch-through: 19
---
 src/backend/replication/logical/tablesync.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index e5101997cd3..4015e861a64 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -1084,6 +1084,7 @@ copy_table(Relation rel)
 	ParseState *pstate;
 	List	   *options = NIL;
 	bool		gencol_published = false;
+	int			server_version = walrcv_server_version(LogRepWorkerWalRcvConn);
 
 	/* Get the publisher relation info. */
 	fetch_remote_table_info(get_namespace_name(RelationGetNamespace(rel)),
@@ -1100,9 +1101,14 @@ copy_table(Relation rel)
 	/* Start copy on the publisher. */
 	initStringInfo(&cmd);
 
-	/* Regular or partitioned table with no row filter or generated columns */
-	if ((lrel.relkind == RELKIND_RELATION || lrel.relkind == RELKIND_PARTITIONED_TABLE)
-		&& qual == NIL && !gencol_published)
+	/*
+	 * Regular or partitioned table with no row filter or generated columns.
+	 *
+	 * "COPY table TO" on a partitioned table is supported since v19.
+	 */
+	if ((lrel.relkind == RELKIND_RELATION ||
+		 (lrel.relkind == RELKIND_PARTITIONED_TABLE && server_version >= 190000)) &&
+		qual == NIL && !gencol_published)
 	{
 		appendStringInfo(&cmd, "COPY %s",
 						 quote_qualified_identifier(lrel.nspname, lrel.relname));
@@ -1182,8 +1188,7 @@ copy_table(Relation rel)
 	 * Prior to v16, initial table synchronization will use text format even
 	 * if the binary option is enabled for a subscription.
 	 */
-	if (walrcv_server_version(LogRepWorkerWalRcvConn) >= 160000 &&
-		MySubscription->binary)
+	if (server_version >= 160000 && MySubscription->binary)
 	{
 		appendStringInfoString(&cmd, " WITH (FORMAT binary)");
 		options = list_make1(makeDefElem("format",
-- 
2.55.0

