On 2017/08/05 11:05, Robert Haas wrote:
> On Thu, Aug 3, 2017 at 8:45 PM, Amit Langote
> <langote_amit...@lab.ntt.co.jp> wrote:
>>> 0003 needs a rebase.
>>
>> Rebased patch attached.
> 
> Committed.

Thank you.

> I think 0004 is a new feature, so I'm leaving that for v11.

Sure.

By the way, bulk of 0004 is refactoring which it seems is what Jeevan's
default partition patch set also includes as one of the patches [1].  It
got a decent amount review from Ashutosh.  I broke it down into a separate
patch, so that the patch to add the new feature is its own tiny patch.

I also spotted a couple of comments referring to attachRel that we just
recently renamed.

So, attached are:

0001: s/attachRel/attachrel/g
0002: Refactoring to introduce a PartConstraintImpliedByRelConstraint
0003: Add the feature to skip the scan of individual leaf partitions

Totally fine if you postpone 0002 and 0003 to when the tree opens up for
PG 11.

Thanks,
Amit

[1]
https://www.postgresql.org/message-id/CAOgcT0OARciE2X%2BU0rjSKp9VuC279dYcCGkc3nCWKhHQ1_m2rw%40mail.gmail.com
From 26e7205fd7c35c0b497c1a7c31152393d3551b23 Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Mon, 7 Aug 2017 10:45:39 +0900
Subject: [PATCH 1/3] Typo: attachRel is now attachrel

---
 src/backend/commands/tablecmds.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1b8d4b3d17..d27c43bdc7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13491,7 +13491,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, 
PartitionCmd *cmd)
         * Prevent circularity by seeing if rel is a partition of attachrel. (In
         * particular, this disallows making a rel a partition of itself.)
         *
-        * We do that by checking if rel is a member of the list of attachRel's
+        * We do that by checking if rel is a member of the list of attachrel's
         * partitions provided the latter is partitioned at all.  We want to 
avoid
         * having to construct this list again, so we request the strongest lock
         * on all partitions.  We need the strongest lock, because we may decide
@@ -13746,7 +13746,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, 
PartitionCmd *cmd)
                        {
                                /*
                                 * Adjust the constraint that we constructed 
above for
-                                * attachRel so that it matches this 
partition's attribute
+                                * attachrel so that it matches this 
partition's attribute
                                 * numbers.
                                 */
                                my_partconstr = 
map_partition_varattnos(my_partconstr, 1,
-- 
2.11.0

From fc6de6ab2800d509ec2af35c96722672e47e99cc Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Thu, 15 Jun 2017 19:22:31 +0900
Subject: [PATCH 2/3] Some refactoring of code in ATExecAttachPartition()

Take the code to check using table's constraints if the partition
constraint validation can be skipped and put it into a separate
function PartConstraintImpliedByRelConstraint().
---
 src/backend/commands/tablecmds.c | 187 +++++++++++++++++++++------------------
 1 file changed, 101 insertions(+), 86 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d27c43bdc7..818335ffe9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -473,6 +473,8 @@ static void CreateInheritance(Relation child_rel, Relation 
parent_rel);
 static void RemoveInheritance(Relation child_rel, Relation parent_rel);
 static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
                                          PartitionCmd *cmd);
+static bool PartConstraintImpliedByRelConstraint(Relation partrel,
+                                         List *partConstraint);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
@@ -13422,15 +13424,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, 
PartitionCmd *cmd)
        Relation        attachrel,
                                catalog;
        List       *attachrel_children;
-       TupleConstr *attachrel_constr;
-       List       *partConstraint,
-                          *existConstraint;
+       List       *partConstraint;
        SysScanDesc scan;
        ScanKeyData skey;
        AttrNumber      attno;
        int                     natts;
        TupleDesc       tupleDesc;
-       bool            skip_validate = false;
        ObjectAddress address;
        const char *trigger_name;
        bool            found_whole_row;
@@ -13625,88 +13624,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, 
PartitionCmd *cmd)
                elog(ERROR, "unexpected whole-row reference found in partition 
key");
 
        /*
-        * 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
-        * the table's check constraints and NOT NULL constraints in the list of
-        * clauses passed to predicate_implied_by().
-        *
-        * There is a case in which we cannot rely on just the result of the
-        * proof.
+        * Based on the table's existing constraints, determine if we can skip
+        * scanning the table to validate the partition constraint.
         */
-       attachrel_constr = tupleDesc->constr;
-       existConstraint = NIL;
-       if (attachrel_constr != NULL)
-       {
-               int                     num_check = attachrel_constr->num_check;
-               int                     i;
-
-               if (attachrel_constr->has_not_null)
-               {
-                       int                     natts = 
attachrel->rd_att->natts;
-
-                       for (i = 1; i <= natts; i++)
-                       {
-                               Form_pg_attribute att = 
attachrel->rd_att->attrs[i - 1];
-
-                               if (att->attnotnull && !att->attisdropped)
-                               {
-                                       NullTest   *ntest = makeNode(NullTest);
-
-                                       ntest->arg = (Expr *) makeVar(1,
-                                                                               
                  i,
-                                                                               
                  att->atttypid,
-                                                                               
                  att->atttypmod,
-                                                                               
                  att->attcollation,
-                                                                               
                  0);
-                                       ntest->nulltesttype = IS_NOT_NULL;
-
-                                       /*
-                                        * argisrow=false is correct even for a 
composite column,
-                                        * because attnotnull does not 
represent a SQL-spec IS NOT
-                                        * NULL test in such a case, just IS 
DISTINCT FROM NULL.
-                                        */
-                                       ntest->argisrow = false;
-                                       ntest->location = -1;
-                                       existConstraint = 
lappend(existConstraint, ntest);
-                               }
-                       }
-               }
-
-               for (i = 0; i < num_check; i++)
-               {
-                       Node       *cexpr;
-
-                       /*
-                        * If this constraint hasn't been fully validated yet, 
we must
-                        * ignore it here.
-                        */
-                       if (!attachrel_constr->check[i].ccvalid)
-                               continue;
-
-                       cexpr = stringToNode(attachrel_constr->check[i].ccbin);
-
-                       /*
-                        * Run each expression through const-simplification and
-                        * canonicalization.  It is necessary, because we will 
be
-                        * comparing it to similarly-processed qual clauses, 
and may fail
-                        * to detect valid matches without this.
-                        */
-                       cexpr = eval_const_expressions(NULL, cexpr);
-                       cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
-
-                       existConstraint = list_concat(existConstraint,
-                                                                               
  make_ands_implicit((Expr *) cexpr));
-               }
-
-               existConstraint = 
list_make1(make_ands_explicit(existConstraint));
-
-               /* And away we go ... */
-               if (predicate_implied_by(partConstraint, existConstraint, true))
-                       skip_validate = true;
-       }
-
-       if (skip_validate)
+       if (PartConstraintImpliedByRelConstraint(attachrel, partConstraint))
        {
                /* No need to scan the table after all. */
                ereport(INFO,
@@ -13715,9 +13636,18 @@ ATExecAttachPartition(List **wqueue, Relation rel, 
PartitionCmd *cmd)
        }
        else
        {
-               /* Constraints proved insufficient, so we need to scan the 
table. */
                ListCell   *lc;
 
+               /*
+                * Constraints proved insufficient, so we need to scan the 
table.
+                * However, if the table is partitioned, validation scans of the
+                * individual leaf partitions may still be skipped if they have
+                * constraints that would make scanning them unnecessary.
+                *
+                * Note that attachrel's OID is in the attachrel_children list. 
 Since
+                * we already determined above that its validation scan cannot 
be
+                * skipped, we need not check that again in the loop below.
+                */
                foreach(lc, attachrel_children)
                {
                        AlteredTableInfo *tab;
@@ -13742,6 +13672,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, 
PartitionCmd *cmd)
                                continue;
                        }
 
+                       /*
+                        * Check if the partition's existing constraints imply 
the
+                        * partition constraint and if so, skip the validation 
scan.
+                        */
                        if (part_rel != attachrel)
                        {
                                /*
@@ -13776,6 +13710,87 @@ ATExecAttachPartition(List **wqueue, Relation rel, 
PartitionCmd *cmd)
 }
 
 /*
+ * PartConstraintImpliedByRelConstraint
+ *             Does partrel's existing constraints imply the partition 
constraint?
+ *
+ * Existing constraints includes its check constraints and column-level
+ * NOT NULL constraints and partConstraint describes the partition constraint.
+ */
+static bool
+PartConstraintImpliedByRelConstraint(Relation partrel, List *partConstraint)
+{
+       List *existConstraint = NIL;
+       TupleConstr *constr = RelationGetDescr(partrel)->constr;
+       int             num_check,
+                       i;
+
+       if (constr && constr->has_not_null)
+       {
+               int             natts = partrel->rd_att->natts;
+
+               for (i = 1; i <= natts; i++)
+               {
+                       Form_pg_attribute att = partrel->rd_att->attrs[i - 1];
+
+                       if (att->attnotnull && !att->attisdropped)
+                       {
+                               NullTest   *ntest = makeNode(NullTest);
+
+                               ntest->arg = (Expr *) makeVar(1,
+                                                                               
          i,
+                                                                               
          att->atttypid,
+                                                                               
          att->atttypmod,
+                                                                               
          att->attcollation,
+                                                                               
          0);
+                               ntest->nulltesttype = IS_NOT_NULL;
+
+                               /*
+                                * argisrow=false is correct even for a 
composite column,
+                                * because attnotnull does not represent a 
SQL-spec IS NOT
+                                * NULL test in such a case, just IS DISTINCT 
FROM NULL.
+                                */
+                               ntest->argisrow = false;
+                               ntest->location = -1;
+                               existConstraint = lappend(existConstraint, 
ntest);
+                       }
+               }
+       }
+
+       num_check = (constr != NULL) ? constr->num_check : 0;
+       for (i = 0; i < num_check; i++)
+       {
+               Node       *cexpr;
+
+               /*
+                * If this constraint hasn't been fully validated yet, we must
+                * ignore it here.
+                */
+               if (!constr->check[i].ccvalid)
+                       continue;
+
+               cexpr = stringToNode(constr->check[i].ccbin);
+
+               /*
+                * Run each expression through const-simplification and
+                * canonicalization.  It is necessary, because we will be 
comparing
+                * it to similarly-processed partition constraint expressions, 
and
+                * may fail to detect valid matches without this.
+                */
+               cexpr = eval_const_expressions(NULL, cexpr);
+               cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
+
+               existConstraint = list_concat(existConstraint,
+                                                                         
make_ands_implicit((Expr *) cexpr));
+       }
+
+       if (existConstraint != NIL)
+               existConstraint = 
list_make1(make_ands_explicit(existConstraint));
+
+       /* And away we go ... */
+       return predicate_implied_by(partConstraint, existConstraint, true);
+}
+
+/*
  * ALTER TABLE DETACH PARTITION
  *
  * Return the address of the relation that is no longer a partition of rel.
-- 
2.11.0

From f1905e74dcb5a4c26385fb4fb2558acf2b731ff1 Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Mon, 7 Aug 2017 10:51:47 +0900
Subject: [PATCH 3/3] Teach ATExecAttachPartition to skip validation in more
 cases

In cases where the table being attached is a partitioned table and
the table itself does not have constraints that would allow validation
on the whole table to be skipped, we can still skip the validations
of individual partitions if they each happen to have the requisite
constraints.

Per an idea of Robert Haas', with code refactoring suggestions from
Ashutosh Bapat.
---
 src/backend/commands/tablecmds.c          | 11 +++++++++++
 src/test/regress/expected/alter_table.out | 12 ++++++++++++
 src/test/regress/sql/alter_table.sql      | 12 ++++++++++++
 3 files changed, 35 insertions(+)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 818335ffe9..eae90dcf4a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13689,6 +13689,17 @@ ATExecAttachPartition(List **wqueue, Relation rel, 
PartitionCmd *cmd)
                                /* There can never be a whole-row reference 
here */
                                if (found_whole_row)
                                        elog(ERROR, "unexpected whole-row 
reference found in partition key");
+
+                               if 
(PartConstraintImpliedByRelConstraint(part_rel,
+                                                                               
                                 my_partconstr))
+                               {
+                                       ereport(INFO,
+                                                       (errmsg("partition 
constraint for table \"%s\" is implied by existing constraints",
+                                                                       
RelationGetRelationName(part_rel))));
+                                       if (part_rel != attachrel)
+                                               heap_close(part_rel, NoLock);
+                                       continue;
+                               }
                        }
 
                        /* Grab a work queue entry. */
diff --git a/src/test/regress/expected/alter_table.out 
b/src/test/regress/expected/alter_table.out
index 58192d2c6a..e636ff4561 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3392,6 +3392,18 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 ERROR:  partition constraint is violated by some row
+-- If the partitioned table being attached does not have a constraint that
+-- would allow validation scan to be skipped, but an individual partition
+-- does, then the partition's validation scan is skipped.  Note that the
+-- following leaf partition only allows rows that have a = 7 (and b = 'b' but
+-- that's irrelevant).
+CREATE TABLE part_7_b PARTITION OF part_7 (
+       CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7)
+) FOR VALUES IN ('b');
+-- The faulting row in part_7_a_null will still cause the command to fail
+ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+INFO:  partition constraint for table "part_7_b" is implied by existing 
constraints
+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 9a20dd141a..1e1fe6f03d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2216,6 +2216,18 @@ 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);
 
+-- If the partitioned table being attached does not have a constraint that
+-- would allow validation scan to be skipped, but an individual partition
+-- does, then the partition's validation scan is skipped.  Note that the
+-- following leaf partition only allows rows that have a = 7 (and b = 'b' but
+-- that's irrelevant).
+CREATE TABLE part_7_b PARTITION OF part_7 (
+       CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7)
+) FOR VALUES IN ('b');
+-- The faulting row in part_7_a_null will still cause the command to fail
+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