Thanks for taking a look.

On 2017/06/14 20:06, Ashutosh Bapat wrote:
> On Wed, Jun 14, 2017 at 9:20 AM, Amit Langote
> <langote_amit...@lab.ntt.co.jp> wrote:
>>
>> By the way, I mentioned an existing problem in one of the earlier emails
>> on this thread about differing attribute numbers in the table being
>> attached causing predicate_implied_by() to give up due to structural
>> inequality of Vars.  To clarify: table's check constraints will bear the
>> table's attribute numbers whereas the partition constraint generated using
>> get_qual_for_partbound() (the predicate) bears the parent's attribute
>> numbers.  That results in Var arguments of the expressions passed to
>> predicate_implied_by() not matching and causing the latter to return
>> failure prematurely.  Attached find a patch to fix that that applies on
>> top of your patch (added a test too).
> 
> +    * Adjust the generated constraint to match this partition's attribute
> +    * numbers.  Save the original to be used later if we decide to proceed
> +    * with the validation scan after all.
> +    */
> +   partConstraintOrig = copyObject(partConstraint);
> +   partConstraint = map_partition_varattnos(partConstraint, 1, attachRel,
> +                                            rel);
> +
> If the partition has different column order than the parent, its heap will 
> also
> have different column order. I am not able to understand the purpose of using
> original constraints for validation using scan. Shouldn't we just use the
> mapped constraint expressions?

Actually, I dropped the approach of using partConstraintOrig altogether
from the latest updated patch.  I will explain the problem I was trying to
solve with that approach, which is now replaced in the new patch by, I
think, a more correct solution.

If we end up having to perform the validation scan and the table being
attached is a partitioned table, we will scan its leaf partitions.  Each
of those leaf partitions may have different attribute numbers for the
partitioning columns, so we will need to do the mapping, which actually we
do even today.  With this patch however, we apply mapping to the generated
partition constraint so that it no longer bears the original parent's
attribute numbers but those of the table being attached.  Down below where
we map to the leaf partition's attribute numbers, we still pass the root
partitioned table as the parent.  But it may so happen that the attnos
appearing in the Vars can no longer be matched with any of the root
table's attribute numbers, resulting in the following code in
map_variable_attnos_mutator() to trigger an error:

if (attno > context->map_length || context->attno_map[attno - 1] == 0)
    elog(ERROR, "unexpected varattno %d in expression to be mapped",
                 attno);

Consider this example:

root: (a, b, c) partition by list (a)
intermediate: (b, c, ..dropped.., a) partition by list (b)
leaf: (b, c, a) partition of intermediate

When attaching intermediate to root, we will generate the partition
constraint and after mapping, its Vars will have attno = 4.  When trying
to map the same for leaf, we currently do map_partition_varattnos(expr, 1,
leaf, root).  So, the innards of map_variable_attnos will try to look for
an attribute with attno = 4 in root which there isn't, so the above error
will occur.  We should really be passing intermediate as parent to the
mapping routine.  With the previous patch's approach, we would pass root
as the parent along with partConstraintOrig which would bear the root
parent's attnos.

Please find attached the updated patch.  In addition to the already
described fixes, the patch also rearranges code so that a redundant AT
work queue entry is avoided.  (Currently, we end up creating one for
attachRel even if it's partitioned, although it's harmless because
ATRewriteTables() knows to skip partitioned tables.)

Thanks,
Amit
From 2b25013e69d262d3c2cd83cbf7f7219d0cb2d96e Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Wed, 14 Jun 2017 11:32:01 +0900
Subject: [PATCH] Cope with differing attnos in ATExecAttachPartition code

If the table being attached has different attnos from the parent for
the partitioning columns which are present in the partition constraint
expressions, then predicate_implied_by() will prematurely return false
due to the structural inequality of the corresponding Var expressions
in the partition constraint and those in the table's check constraint
expressions.  Fix this by mapping the partition constraint's expressions
to bear the partition's attnos.

Further, if the validation scan needs to be performed after all and
the table being attached is a partitioned table, we will need to map
the constraint expression again to change the attnos to the individual
leaf partition's attnos from those of the table being attached.

Another minor fix:

Avoid creating an AT work queue entry for the table being attached if
it's partitioned.  Current coding does not lead to that happening.
---
 src/backend/commands/tablecmds.c          | 34 ++++++++++++++++-------
 src/test/regress/expected/alter_table.out | 46 +++++++++++++++++++++++++++++++
 src/test/regress/sql/alter_table.sql      | 39 ++++++++++++++++++++++++++
 3 files changed, 109 insertions(+), 10 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b4425bc6af..89ac0cca2e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13580,6 +13580,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, 
PartitionCmd *cmd)
        partConstraint = list_make1(make_ands_explicit(partConstraint));
 
        /*
+        * Adjust the generated constraint to match this partition's attribute
+        * numbers.
+        */
+       partConstraint = map_partition_varattnos(partConstraint, 1, attachRel,
+                                                                               
         rel);
+
+       /*
         * Check if we can do away with having to scan the table being attached 
to
         * validate the partition constraint, by *proving* that the existing
         * constraints of the table *imply* the partition predicate.  We include
@@ -13691,18 +13698,19 @@ ATExecAttachPartition(List **wqueue, Relation rel, 
PartitionCmd *cmd)
                        Relation        part_rel;
                        Expr       *constr;
 
+                       /* Skip the original table if it's partitioned. */
+                       if (part_relid == RelationGetRelid(attachRel) &&
+                               attachRel->rd_rel->relkind == 
RELKIND_PARTITIONED_TABLE)
+                               continue;
+
                        /* Lock already taken */
-                       if (part_relid != RelationGetRelid(attachRel))
-                               part_rel = heap_open(part_relid, NoLock);
-                       else
-                               part_rel = attachRel;
+                       part_rel = heap_open(part_relid, NoLock);
 
                        /*
                         * Skip if it's a partitioned table.  Only 
RELKIND_RELATION
                         * relations (ie, leaf partitions) need to be scanned.
                         */
-                       if (part_rel != attachRel &&
-                               part_rel->rd_rel->relkind == 
RELKIND_PARTITIONED_TABLE)
+                       if (part_rel->rd_rel->relkind == 
RELKIND_PARTITIONED_TABLE)
                        {
                                heap_close(part_rel, NoLock);
                                continue;
@@ -13711,14 +13719,20 @@ ATExecAttachPartition(List **wqueue, Relation rel, 
PartitionCmd *cmd)
                        /* Grab a work queue entry */
                        tab = ATGetQueueEntry(wqueue, part_rel);
 
-                       /* Adjust constraint to match this partition */
+                       /*
+                        * Adjust the constraint to match this partition.
+                        *
+                        * Since partConstraint contains attachRel's attnos due 
to the
+                        * mapping we did just before attempting the proof 
above, we pass
+                        * attachRel as the parent to map_partition_varattnos, 
not 'rel'
+                        * which is the root parent.
+                        */
                        constr = linitial(partConstraint);
                        tab->partition_constraint = (Expr *)
                                map_partition_varattnos((List *) constr, 1,
-                                                                               
part_rel, rel);
+                                                                               
part_rel, attachRel);
                        /* keep our lock until commit */
-                       if (part_rel != attachRel)
-                               heap_close(part_rel, NoLock);
+                       heap_close(part_rel, NoLock);
                }
        }
 
diff --git a/src/test/regress/expected/alter_table.out 
b/src/test/regress/expected/alter_table.out
index 13d6a4b747..182e94ee41 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3347,6 +3347,52 @@ ALTER TABLE part_5 DROP CONSTRAINT check_a;
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT 
NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 INFO:  partition constraint for table "part_5" is implied by existing 
constraints
+-- Check the case where attnos of the partitioning columns in the table being
+-- attached differs from the parent.  It should not affect the constraint-
+-- checking logic that allows to skip the scan.
+CREATE TABLE part_6 (
+       c int,
+       LIKE list_parted2,
+       CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 6)
+);
+ALTER TABLE part_6 DROP c;
+ALTER TABLE list_parted2 ATTACH PARTITION part_6 FOR VALUES IN (6);
+INFO:  partition constraint for table "part_6" is implied by existing 
constraints
+-- Similar to above, but the table being attached is a partitioned table
+-- whose partition has still different attnos for the root partitioning
+-- columns.
+CREATE TABLE part_7 (
+       a int,
+       b char,
+       CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7)
+) PARTITION BY LIST (b);
+CREATE TABLE part_7_a_null (
+       c int,
+       d int,
+       b char,
+       a int,  -- 'a' will have attnum = 4
+       CONSTRAINT check_b CHECK (b IS NULL OR b = 'a'),
+       CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7)
+);
+ALTER TABLE part_7_a_null DROP c, DROP d;
+ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
+INFO:  partition constraint for table "part_7_a_null" is implied by existing 
constraints
+ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+INFO:  partition constraint for table "part_7" is implied by existing 
constraints
+-- Same example, but check this time that the constraint correctly detects
+-- violating rows
+ALTER TABLE list_parted2 DETACH PARTITION part_7;
+ALTER TABLE part_7 DROP CONSTRAINT check_a;    -- thusly, scan won't be skipped
+INSERT INTO part_7 (a, b) VALUES (8, null), (9, 'a');
+SELECT tableoid::regclass, a, b FROM part_7 order by a;
+   tableoid    | a | b 
+---------------+---+---
+ part_7_a_null | 8 | 
+ part_7_a_null | 9 | a
+(2 rows)
+
+ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+ERROR:  partition constraint is violated by some row
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/sql/alter_table.sql 
b/src/test/regress/sql/alter_table.sql
index 5dd1402ea6..240f6c4e4c 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2178,6 +2178,45 @@ ALTER TABLE part_5 DROP CONSTRAINT check_a;
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT 
NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 
+-- Check the case where attnos of the partitioning columns in the table being
+-- attached differs from the parent.  It should not affect the constraint-
+-- checking logic that allows to skip the scan.
+CREATE TABLE part_6 (
+       c int,
+       LIKE list_parted2,
+       CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 6)
+);
+ALTER TABLE part_6 DROP c;
+ALTER TABLE list_parted2 ATTACH PARTITION part_6 FOR VALUES IN (6);
+
+-- Similar to above, but the table being attached is a partitioned table
+-- whose partition has still different attnos for the root partitioning
+-- columns.
+CREATE TABLE part_7 (
+       a int,
+       b char,
+       CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7)
+) PARTITION BY LIST (b);
+CREATE TABLE part_7_a_null (
+       c int,
+       d int,
+       b char,
+       a int,  -- 'a' will have attnum = 4
+       CONSTRAINT check_b CHECK (b IS NULL OR b = 'a'),
+       CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7)
+);
+ALTER TABLE part_7_a_null DROP c, DROP d;
+ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
+ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+
+-- Same example, but check this time that the constraint correctly detects
+-- violating rows
+ALTER TABLE list_parted2 DETACH PARTITION part_7;
+ALTER TABLE part_7 DROP CONSTRAINT check_a;    -- thusly, scan won't be skipped
+INSERT INTO part_7 (a, b) VALUES (8, null), (9, 'a');
+SELECT tableoid::regclass, a, b FROM part_7 order by a;
+ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
-- 
2.11.0

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to