From ee2f51ff9309223093f39cc87e571f93a9f190f4 Mon Sep 17 00:00:00 2001
From: DaeMyung Kang <charsyam@gmail.com>
Date: Sun, 4 Oct 2026 21:47:58 +0900
Subject: [PATCH] Fix default partition validation for reordered child columns

When a new partition is added and the default partition is itself
partitioned, check_default_partition_contents() scans each leaf of the
default partition for rows that would belong to the new partition.
Before scanning a child, it skips the scan if the child's constraints
imply the updated default partition constraint.

The constraint was mapped to the child's attribute numbers for the
scan, but the implication check was passed the unmapped list, which
uses the default partition's attribute numbers.  If the child's column
order differs, the check looks at the wrong columns and can skip a
needed scan.  The new partition is then created even though the
default partition still holds rows that belong to it.  Those rows stay
in the default partition, and queries that prune to the new partition
no longer return them.

Use the constraint mapped to the child's attribute numbers for both
the implication check and the scan.  The child-side mapping added in
23cccb17fe0 was applied to the scan expression but not to the
implication check.

Add a regression test with a reordered child whose CHECK constraint
previously caused the scan to be skipped.
---
 src/backend/partitioning/partbounds.c      | 12 +++++++-----
 src/test/regress/expected/create_table.out | 16 ++++++++++++++++
 src/test/regress/sql/create_table.sql      | 11 +++++++++++
 3 files changed, 34 insertions(+), 5 deletions(-)

diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index f618f59f7a8..608f615e3a9 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -3297,16 +3297,18 @@ check_default_partition_contents(Relation parent, Relation default_rel,
 		/* Lock already taken above. */
 		if (part_relid != RelationGetRelid(default_rel))
 		{
+			List	   *part_constraints;
+
 			part_rel = table_open(part_relid, NoLock);
 
 			/*
 			 * Map the Vars in the constraint expression from default_rel's
-			 * the sub-partition's.
+			 * attribute numbers to the sub-partition's.
 			 */
-			partition_constraint = make_ands_explicit(def_part_constraints);
-			partition_constraint = (Expr *)
-				map_partition_varattnos((List *) partition_constraint, 1,
+			part_constraints =
+				map_partition_varattnos(def_part_constraints, 1,
 										part_rel, default_rel);
+			partition_constraint = make_ands_explicit(part_constraints);
 
 			/*
 			 * If the partition constraints on default partition child imply
@@ -3314,7 +3316,7 @@ check_default_partition_contents(Relation parent, Relation default_rel,
 			 * partition, we can avoid scanning the child table.
 			 */
 			if (PartConstraintImpliedByRelConstraint(part_rel,
-													 def_part_constraints))
+													 part_constraints))
 			{
 				ereport(DEBUG1,
 						(errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2df8761ae8e..e562dbde9ca 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -1103,6 +1103,22 @@ insert into defcheck_def values (0, 0);
 create table defcheck_0 partition of defcheck for values in (0);
 ERROR:  updated partition constraint for default partition "defcheck_def" would be violated by some row
 drop table defcheck;
+-- A child of the default partition may have a different column order.
+-- Its CHECK constraint must not skip validation of rows in the new bound.
+create table defmap (a int, b int) partition by list (a);
+create table defmap_def partition of defmap default partition by range (b);
+create table defmap_child (b int check (b > 0), a int);
+alter table defmap_def attach partition defmap_child for values from (1) to (100);
+insert into defmap values (0, 2);
+create table defmap_0 partition of defmap for values in (0); -- error
+ERROR:  updated partition constraint for default partition "defmap_def" would be violated by some row
+select tableoid::regclass, a, b from defmap where a = 0;
+   tableoid   | a | b 
+--------------+---+---
+ defmap_child | 0 | 2
+(1 row)
+
+drop table defmap;
 -- tests of column drop with partition tables and indexes using
 -- predicates and expressions.
 create table part_column_drop (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 80e424e6bda..0949ea79e8c 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -729,6 +729,17 @@ insert into defcheck_def values (0, 0);
 create table defcheck_0 partition of defcheck for values in (0);
 drop table defcheck;
 
+-- A child of the default partition may have a different column order.
+-- Its CHECK constraint must not skip validation of rows in the new bound.
+create table defmap (a int, b int) partition by list (a);
+create table defmap_def partition of defmap default partition by range (b);
+create table defmap_child (b int check (b > 0), a int);
+alter table defmap_def attach partition defmap_child for values from (1) to (100);
+insert into defmap values (0, 2);
+create table defmap_0 partition of defmap for values in (0); -- error
+select tableoid::regclass, a, b from defmap where a = 0;
+drop table defmap;
+
 -- tests of column drop with partition tables and indexes using
 -- predicates and expressions.
 create table part_column_drop (
-- 
2.43.0

