>From b376378f7cbb06c0b8c48fbef28788861c19c045 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.

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            |    2 +-
 src/backend/executor/execMain.c        |   76 +++++++++-
 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  |  255 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   76 ++++++++++
 src/test/regress/expected/update.out   |   27 ++++
 src/test/regress/sql/inherit.sql       |   47 ++++++
 src/test/regress/sql/insert.sql        |   56 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 582 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9801f0f..44c273c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2527,7 +2527,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* 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..714b49c 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,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * 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;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,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 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!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 violates the partition boundary"
+							" specification of \"%s\"",
+							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 5790edc..5b0e8cf 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,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)
@@ -907,7 +907,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 8ecc116..8036d3f 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"
@@ -1127,6 +1128,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.
@@ -1212,6 +1214,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, false);
+	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 4fa3661..697c90f 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 d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ 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 start (1) end (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 start (10) end (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 start (21) end (30) inclusive 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 start (40) end 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_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf
+   ->  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_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         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_1_10_ab
+         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_10_20_ab
+         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_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_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
+         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
+         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
+         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)
+
+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 70107b5..89d5760 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ 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 start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+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 violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+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 violates the partition boundary specification of "part_b_10_b_20"
+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');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- 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 start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (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 violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+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 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+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..df6eb30 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 start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('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 violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+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 b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ 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 start (1) end (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 start (10) end (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 start (21) end (30) inclusive 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 start (40) end 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;
+
+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..4bf042e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ 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 start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('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');
+
+-- 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);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- 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 start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (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..4997877 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 start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('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

