From ee2f51ff9309223093f39cc87e571f93a9f190f4 Mon Sep 17 00:00:00 2001 From: DaeMyung Kang 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