From a3fa55c7c8f52f43bf6f05f1ea078259d21c3b8c Mon Sep 17 00:00:00 2001
From: Rui Zhao <zhaorui126@gmail.com>
Date: Tue, 11 Aug 2026 17:26:48 +0800
Subject: [PATCH 1/2] Judge a partition child's names against the target
 table's schema

With schema_qualified => false, emit_partition_children() switched a child
that lives in another schema to full qualification.  That flag governs every
name the child emits, including its PARTITION OF reference back to the
parent -- so the parent came out qualified there while its own CREATE TABLE
came out bare, and the two resolved to different tables under a different
search_path.  Replaying such a script attached the child to the source
parent instead of to the copy.

Pass the caller's base namespace down instead.  Every name in the child's
DDL is then judged against the namespace of the table that was asked for,
which is what search_path stands in for: a child in another schema still
comes out fully qualified, its reference back to the parent does not.
---
 src/backend/utils/adt/ddlutils.c              | 24 ++++++++++---------
 .../regress/expected/pg_get_table_ddl.out     |  6 ++---
 2 files changed, 16 insertions(+), 14 deletions(-)

diff --git a/src/backend/utils/adt/ddlutils.c b/src/backend/utils/adt/ddlutils.c
index 2c6a7957bd..5eb549d776 100644
--- a/src/backend/utils/adt/ddlutils.c
+++ b/src/backend/utils/adt/ddlutils.c
@@ -3823,17 +3823,14 @@ emit_partition_children(TableDdlContext *ctx)
 		childctx.except_kinds = ctx->except_kinds;
 
 		/*
-		 * When schema_qualified is false, the contract is that the output
-		 * is replayable with the parent's schema in search_path.  That
-		 * contract cannot hold for a child that lives in a different schema:
-		 * its own name would be ambiguous (landing in whatever schema is
-		 * first in search_path) and references back to the parent would have
-		 * to be schema-qualified anyway.  Force full qualification so the
-		 * child's DDL is unambiguous regardless of the caller's search_path.
+		 * Judge every name in the child's DDL against the namespace of the
+		 * table the caller asked for, not the child's own: when
+		 * schema_qualified is false, that is the schema search_path stands
+		 * in for on replay.  A child living in another schema then still
+		 * comes out fully qualified, while its reference back to the parent
+		 * stays bare, matching how the parent emitted itself.
 		 */
-		if (!childctx.schema_qualified &&
-			get_rel_namespace(childoid) != ctx->base_namespace)
-			childctx.schema_qualified = true;
+		childctx.base_namespace = ctx->base_namespace;
 
 		if (childctx.only_kinds != NULL &&
 			bms_is_member((int) TABLE_DDL_KIND_PARTITION,
@@ -3980,7 +3977,12 @@ pg_get_table_ddl_internal(TableDdlContext *ctx)
 	 * statements via lappend.
 	 */
 	ctx->rel = rel;
-	ctx->base_namespace = RelationGetNamespace(rel);
+	/*
+	 * A partition child inherits the caller's base namespace; see
+	 * emit_partition_children.
+	 */
+	if (!OidIsValid(ctx->base_namespace))
+		ctx->base_namespace = RelationGetNamespace(rel);
 	ctx->save_nestlevel = -1;
 	ctx->qualname = lookup_relname_for_emit(ctx->relid, ctx->schema_qualified,
 											ctx->base_namespace);
diff --git a/src/test/regress/expected/pg_get_table_ddl.out b/src/test/regress/expected/pg_get_table_ddl.out
index d62ad82aed..717e2126b8 100644
--- a/src/test/regress/expected/pg_get_table_ddl.out
+++ b/src/test/regress/expected/pg_get_table_ddl.out
@@ -914,10 +914,10 @@ CREATE TABLE pgtbl_ddl_part_other.pt_c
 SELECT * FROM pg_get_table_ddl('pgtbl_ddl_part_s.pt'::regclass,
                                owner => false,
                                schema_qualified => false);
-                                           pg_get_table_ddl                                            
--------------------------------------------------------------------------------------------------------
+                                   pg_get_table_ddl                                   
+--------------------------------------------------------------------------------------
  CREATE TABLE pt (id integer, val integer) PARTITION BY RANGE (id);
- CREATE TABLE pgtbl_ddl_part_other.pt_c PARTITION OF pgtbl_ddl_part_s.pt FOR VALUES FROM (0) TO (100);
+ CREATE TABLE pgtbl_ddl_part_other.pt_c PARTITION OF pt FOR VALUES FROM (0) TO (100);
 (2 rows)
 
 DROP SCHEMA pgtbl_ddl_part_s CASCADE;
-- 
2.43.7

