Subject: [PATCH 5/8] Teach a few places to use partition check quals.

From: amit <[email-masked]>
Date: Wed, 27 Jul 2016 16:00:09 +0900

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    3 +-
 src/backend/executor/execMain.c        |   69 ++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  271 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   82 ++++++++++
 src/test/regress/expected/update.out   |   27 +++
 src/test/regress/sql/inherit.sql       |   48 ++++++
 src/test/regress/sql/insert.sql        |   59 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 602 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28b6f63..7a2bf94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2519,7 +2519,8 @@ CopyFrom(CopyState cstate)
 			else
 			{
 				/* Check the constraints of the tuple */
-				if (cstate->rel->rd_att->constr)
+				if (cstate->rel->rd_att->constr ||
+					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, estate);
 
 				if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..c7a6347 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,46 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition constraint.
+ *
+ * Note: This is called *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * As in case of the catalogued constraints, we treat a NULL result as
+	 * success here, not a failure.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1746,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1779,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1803,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck &&
+		!ExecPartitionCheck(resultRelInfo, slot, estate))
+	{
+		char	   *val_desc;
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupdesc,
+												 modifiedCols,
+												 64);
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("new row for relation \"%s\" violates partition constraint",
+						RelationGetRelationName(rel)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -922,7 +922,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index ad07baa..a2cbf14 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1139,6 +1140,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, true);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index b331828..28ecc2c 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,274 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_cd
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..9ae6b09 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,85 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (null, 1).
+-- ok
+insert into part_null values (null, 0);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part_a_1_a_10
+drop cascades to table part_a_10_a_20
+drop cascades to table part_b_1_b_10
+drop cascades to table part_b_10_b_20
+drop table list_parted cascade;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_null
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..bdb4e2c 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part_a_1_a_10
+drop cascades to table part_a_10_a_20
+drop cascades to table part_b_1_b_10
+drop cascades to table part_b_10_b_20
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..aad62af 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,51 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..b6e821e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,62 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+-- ok
+insert into part_null values (null, 0);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..5392fa5 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

--------------38C0D572AC793CAA7276C062
Content-Type: text/x-diff;
 name="0006-Introduce-a-PartitionDispatch-data-structure-16.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0006-Introduce-a-PartitionDispatch-data-structure-16.patch"