From 86d077e314b4a3c7a6f379376d90a59d973b3100 Mon Sep 17 00:00:00 2001
From: "dgrowley@gmail.com" <dgrowley@gmail.com>
Date: Sat, 21 Apr 2018 16:58:17 +1200
Subject: [PATCH v2] Add GUC to allow partition pruning to be disabled

This both disables the plan-time partition pruning and also run-time partition
pruning.  We may one day want to consider two individual switches for turning
off each of these features, but at the moment there seems to be no strong
argument for that.
---
 doc/src/sgml/config.sgml                      |  30 ++++--
 src/backend/nodes/outfuncs.c                  |   2 +-
 src/backend/optimizer/path/allpaths.c         |   3 +-
 src/backend/optimizer/path/costsize.c         |   1 +
 src/backend/optimizer/plan/createplan.c       |   7 +-
 src/backend/optimizer/plan/planner.c          |  13 ++-
 src/backend/optimizer/prep/prepjointree.c     |   2 +-
 src/backend/optimizer/util/plancat.c          |  29 ++++--
 src/backend/utils/misc/guc.c                  |   9 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/include/nodes/relation.h                  |  16 ++-
 src/include/optimizer/cost.h                  |   1 +
 src/test/regress/expected/partition_prune.out | 138 ++++++++++++++++++++++++++
 src/test/regress/expected/sysviews.out        |   3 +-
 src/test/regress/sql/partition_prune.sql      |  46 +++++++++
 15 files changed, 275 insertions(+), 26 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5d5f2d23c4..49173fcf9c 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3826,6 +3826,23 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-partition-pruning" xreflabel="enable_partition_pruning">
+      <term><varname>enable_partition_pruning</varname> (<type>boolean</type>)
+       <indexterm>
+        <primary><varname>enable_partition_pruning</varname> configuration parameter</primary>
+       </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Enables or disables the query planner's ability to eliminate a
+        partitioned table's partitions from query plans.  This also controls
+        the planner's ability to generate query plans which allow the query
+        executor to remove or ignore partitions during query execution.  The
+        default is <literal>on</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-enable-partitionwise-join" xreflabel="enable_partitionwise_join">
       <term><varname>enable_partitionwise_join</varname> (<type>boolean</type>)
       <indexterm>
@@ -4417,8 +4434,7 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
         <literal>partition</literal> (examine constraints only for inheritance child
         tables and <literal>UNION ALL</literal> subqueries).
         <literal>partition</literal> is the default setting.
-        It is often used with inheritance and partitioned tables to
-        improve performance.
+        It is often used with inheritance tables to improve performance.
       </para>
 
        <para>
@@ -4441,11 +4457,11 @@ SELECT * FROM parent WHERE key = 2400;
 
        <para>
         Currently, constraint exclusion is enabled by default
-        only for cases that are often used to implement table partitioning.
-        Turning it on for all tables imposes extra planning overhead that is
-        quite noticeable on simple queries, and most often will yield no
-        benefit for simple queries.  If you have no partitioned tables
-        you might prefer to turn it off entirely.
+        only for cases that are often used to implement table partitioning via
+        inheritance tables.  Turning it on for all tables imposes extra
+        planning overhead that is quite noticeable on simple queries, and most
+        often will yield no benefit for simple queries.  If you have no
+        inheritance partitioned tables you might prefer to turn it off entirely.
        </para>
 
        <para>
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 3991a0ce83..f2d00c5e31 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2291,7 +2291,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
 	WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
 	WRITE_FLOAT_FIELD(limit_tuples, "%.0f");
 	WRITE_UINT_FIELD(qual_security_level);
-	WRITE_BOOL_FIELD(hasInheritedTarget);
+	WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind);
 	WRITE_BOOL_FIELD(hasJoinRTEs);
 	WRITE_BOOL_FIELD(hasLateralRTEs);
 	WRITE_BOOL_FIELD(hasDeletedRTEs);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 3ba3f87eb7..9ed73da0f7 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -901,7 +901,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	 * store the relids of all partitions which could possibly contain a
 	 * matching tuple, and skip anything else in the loop below.
 	 */
-	if (rte->relkind == RELKIND_PARTITIONED_TABLE &&
+	if (enable_partition_pruning &&
+		rte->relkind == RELKIND_PARTITIONED_TABLE &&
 		rel->baserestrictinfo != NIL)
 	{
 		live_children = prune_append_rel_partitions(rel);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 957f751bd4..a2a7e0c520 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -138,6 +138,7 @@ bool		enable_partitionwise_join = false;
 bool		enable_partitionwise_aggregate = false;
 bool		enable_parallel_append = true;
 bool		enable_parallel_hash = true;
+bool		enable_partition_pruning = true;
 
 typedef struct
 {
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 280f21cd45..0317763f43 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1077,7 +1077,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
 		subplans = lappend(subplans, subplan);
 	}
 
-	if (rel->reloptkind == RELOPT_BASEREL &&
+	if (enable_partition_pruning &&
+		rel->reloptkind == RELOPT_BASEREL &&
 		best_path->partitioned_rels != NIL)
 	{
 		List	   *prunequal;
@@ -1979,7 +1980,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
 	 * create_modifytable_plan).  Fortunately we can't be because there would
 	 * never be grouping in an UPDATE/DELETE; but let's Assert that.
 	 */
-	Assert(!root->hasInheritedTarget);
+	Assert(root->inhTargetKind == INHKIND_NONE);
 	Assert(root->grouping_map == NULL);
 	root->grouping_map = grouping_map;
 
@@ -2141,7 +2142,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
 	 * create_modifytable_plan).  Fortunately we can't be because there would
 	 * never be aggregates in an UPDATE/DELETE; but let's Assert that.
 	 */
-	Assert(!root->hasInheritedTarget);
+	Assert(root->inhTargetKind == INHKIND_NONE);
 	Assert(root->minmax_aggs == NIL);
 	root->minmax_aggs = best_path->mmaggregates;
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 2e298f8357..c90cd3951f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -623,7 +623,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	root->grouping_map = NULL;
 	root->minmax_aggs = NIL;
 	root->qual_security_level = 0;
-	root->hasInheritedTarget = false;
+	root->inhTargetKind = INHKIND_NONE;
 	root->hasRecursion = hasRecursion;
 	if (hasRecursion)
 		root->wt_param_id = SS_assign_special_param(root);
@@ -1424,8 +1424,15 @@ inheritance_planner(PlannerInfo *root)
 		Assert(subroot->join_info_list == NIL);
 		/* and we haven't created PlaceHolderInfos, either */
 		Assert(subroot->placeholder_list == NIL);
-		/* hack to mark target relation as an inheritance partition */
-		subroot->hasInheritedTarget = true;
+
+		/*
+		 * Mark if we're planning a query to a partitioned table or an
+		 * inheritance parent.
+		 */
+		if (partitioned_relids)
+			subroot->inhTargetKind = INHKIND_PARTITIONED;
+		else
+			subroot->inhTargetKind = INHKIND_INHERITED;
 
 		/*
 		 * If the child is further partitioned, remember it as a parent. Since
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 45d82da459..c3f46a26c3 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -914,7 +914,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	subroot->grouping_map = NULL;
 	subroot->minmax_aggs = NIL;
 	subroot->qual_security_level = 0;
-	subroot->hasInheritedTarget = false;
+	subroot->inhTargetKind = INHKIND_NONE;
 	subroot->hasRecursion = false;
 	subroot->wt_param_id = -1;
 	subroot->non_recursive_path = NULL;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 1ff0ef4866..28d1021b83 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1272,7 +1272,7 @@ get_relation_constraints(PlannerInfo *root,
 	 * descriptor, instead of constraint exclusion which is driven by the
 	 * individual partition's partition constraint.
 	 */
-	if (root->parse->commandType != CMD_SELECT)
+	if (enable_partition_pruning && root->parse->commandType != CMD_SELECT)
 	{
 		List	   *pcqual = RelationGetPartitionQual(relation);
 
@@ -1415,13 +1415,28 @@ relation_excluded_by_constraints(PlannerInfo *root,
 			return true;
 	}
 
-	/* Skip further tests if constraint exclusion is disabled for the rel */
-	if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF ||
-		(constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION &&
-		 !(rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
-		   (root->hasInheritedTarget &&
+	/*
+	 * When constraint_exclusion is disabled, don't try to exclude the rel.
+	 *
+	 * We currently must ignore this off setting when the inheritance target
+	 * is a partitioned table as partition pruning is still implemented using
+	 * constraint exclusion.  Users must use the enable_partition_pruning GUC
+	 * to control the behavior in for partitioned tables.
+	 */
+	if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF &&
+		root->inhTargetKind != INHKIND_PARTITIONED)
+		return false;
+
+	/*
+	 * When constraint_exclusion is set to 'partition' we only handle
+	 * OTHER_MEMBER_RELs, or BASERELs in cases where the result target is an
+	 * inheritance parent or a partitioned table.
+	 */
+	if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION &&
+		!(rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
+		   (root->inhTargetKind != INHKIND_NONE &&
 			rel->reloptkind == RELOPT_BASEREL &&
-			rel->relid == root->parse->resultRelation))))
+			rel->relid == root->parse->resultRelation)))
 		return false;
 
 	/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index fa92ce2e68..ec61e04d21 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -951,6 +951,15 @@ static struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_partition_pruning", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enable plan-time and run-time partition pruning."),
+			gettext_noop("Allows the query planner and executor to compare partition bounds to conditions in the query to determine which partitions must be scanned.")
+		},
+		&enable_partition_pruning,
+		true,
+		NULL, NULL, NULL
+	},
 	{
 		{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("Enables genetic query optimization."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 66d0938827..3d88e80a20 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -308,6 +308,7 @@
 #enable_partitionwise_join = off
 #enable_partitionwise_aggregate = off
 #enable_parallel_hash = on
+#enable_partition_pruning = on
 
 # - Planner Cost Constants -
 
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 2108b6ab1d..8b153a9d4e 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -82,6 +82,17 @@ typedef enum UpperRelationKind
 	/* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */
 } UpperRelationKind;
 
+/*
+ * This enum identifies which type of relation is being planned through the
+ * inheritance planner.  INHKIND_NONE indicates the inheritance planner
+ * was not used.
+ */
+typedef enum InheritanceKind
+{
+	INHKIND_NONE,
+	INHKIND_INHERITED,
+	INHKIND_PARTITIONED
+} InheritanceKind;
 
 /*----------
  * PlannerGlobal
@@ -298,8 +309,9 @@ typedef struct PlannerInfo
 	Index		qual_security_level;	/* minimum security_level for quals */
 	/* Note: qual_security_level is zero if there are no securityQuals */
 
-	bool		hasInheritedTarget; /* true if parse->resultRelation is an
-									 * inheritance child rel */
+	InheritanceKind inhTargetKind; /* indicates if the target relation is an
+									* inheritance child or partition or a
+									* partitioned table */
 	bool		hasJoinRTEs;	/* true if any RTEs are RTE_JOIN kind */
 	bool		hasLateralRTEs; /* true if any RTEs are marked LATERAL */
 	bool		hasDeletedRTEs; /* true if any RTE was deleted from jointree */
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 6e6d0d3c79..55e6a8488f 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -71,6 +71,7 @@ extern PGDLLIMPORT bool enable_partitionwise_join;
 extern PGDLLIMPORT bool enable_partitionwise_aggregate;
 extern PGDLLIMPORT bool enable_parallel_append;
 extern PGDLLIMPORT bool enable_parallel_hash;
+extern PGDLLIMPORT bool enable_partition_pruning;
 extern PGDLLIMPORT int	constraint_exclusion;
 
 extern double clamp_row_est(double nrows);
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 9c65ee001d..a86169c4e9 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2737,3 +2737,141 @@ explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range;
 (2 rows)
 
 drop table pp_intrangepart;
+--
+-- Ensure the enable_partition_prune GUC properly disables partition pruning.
+--
+create table pp_lp (a int, value int) partition by list (a);
+create table pp_lp1 partition of pp_lp for values in(1);
+create table pp_lp2 partition of pp_lp for values in(2);
+set enable_partition_pruning = off;
+set constraint_exclusion = 'partition'; -- this should not affect the result.
+explain (costs off) select * from pp_lp where a = 1;
+        QUERY PLAN        
+--------------------------
+ Append
+   ->  Seq Scan on pp_lp1
+         Filter: (a = 1)
+   ->  Seq Scan on pp_lp2
+         Filter: (a = 1)
+(5 rows)
+
+explain (costs off) update pp_lp set value = 10 where a = 1;
+        QUERY PLAN        
+--------------------------
+ Update on pp_lp
+   Update on pp_lp1
+   Update on pp_lp2
+   ->  Seq Scan on pp_lp1
+         Filter: (a = 1)
+   ->  Seq Scan on pp_lp2
+         Filter: (a = 1)
+(7 rows)
+
+explain (costs off) delete from pp_lp where a = 1;
+        QUERY PLAN        
+--------------------------
+ Delete on pp_lp
+   Delete on pp_lp1
+   Delete on pp_lp2
+   ->  Seq Scan on pp_lp1
+         Filter: (a = 1)
+   ->  Seq Scan on pp_lp2
+         Filter: (a = 1)
+(7 rows)
+
+set constraint_exclusion = 'off'; -- this should not affect the result.
+explain (costs off) select * from pp_lp where a = 1;
+        QUERY PLAN        
+--------------------------
+ Append
+   ->  Seq Scan on pp_lp1
+         Filter: (a = 1)
+   ->  Seq Scan on pp_lp2
+         Filter: (a = 1)
+(5 rows)
+
+explain (costs off) update pp_lp set value = 10 where a = 1;
+        QUERY PLAN        
+--------------------------
+ Update on pp_lp
+   Update on pp_lp1
+   Update on pp_lp2
+   ->  Seq Scan on pp_lp1
+         Filter: (a = 1)
+   ->  Seq Scan on pp_lp2
+         Filter: (a = 1)
+(7 rows)
+
+explain (costs off) delete from pp_lp where a = 1;
+        QUERY PLAN        
+--------------------------
+ Delete on pp_lp
+   Delete on pp_lp1
+   Delete on pp_lp2
+   ->  Seq Scan on pp_lp1
+         Filter: (a = 1)
+   ->  Seq Scan on pp_lp2
+         Filter: (a = 1)
+(7 rows)
+
+drop table pp_lp;
+-- Ensure enable_partition_prune does not affect non-partitioned tables.
+create table inh_lp (a int, value int);
+create table inh_lp1 (a int, value int, check(a = 1)) inherits (inh_lp);
+NOTICE:  merging column "a" with inherited definition
+NOTICE:  merging column "value" with inherited definition
+create table inh_lp2 (a int, value int, check(a = 2)) inherits (inh_lp);
+NOTICE:  merging column "a" with inherited definition
+NOTICE:  merging column "value" with inherited definition
+set constraint_exclusion = 'partition';
+-- inh_lp2 should be removed in the following 3 cases.
+explain (costs off) select * from inh_lp where a = 1;
+        QUERY PLAN         
+---------------------------
+ Append
+   ->  Seq Scan on inh_lp
+         Filter: (a = 1)
+   ->  Seq Scan on inh_lp1
+         Filter: (a = 1)
+(5 rows)
+
+explain (costs off) update inh_lp set value = 10 where a = 1;
+        QUERY PLAN         
+---------------------------
+ Update on inh_lp
+   Update on inh_lp
+   Update on inh_lp1
+   ->  Seq Scan on inh_lp
+         Filter: (a = 1)
+   ->  Seq Scan on inh_lp1
+         Filter: (a = 1)
+(7 rows)
+
+explain (costs off) delete from inh_lp where a = 1;
+        QUERY PLAN         
+---------------------------
+ Delete on inh_lp
+   Delete on inh_lp
+   Delete on inh_lp1
+   ->  Seq Scan on inh_lp
+         Filter: (a = 1)
+   ->  Seq Scan on inh_lp1
+         Filter: (a = 1)
+(7 rows)
+
+-- Ensure we don't exclude normal relations when we only expect to exclude
+-- inheritance children
+explain (costs off) update inh_lp1 set value = 10 where a = 2;
+        QUERY PLAN         
+---------------------------
+ Update on inh_lp1
+   ->  Seq Scan on inh_lp1
+         Filter: (a = 2)
+(3 rows)
+
+drop table inh_lp cascade;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table inh_lp1
+drop cascades to table inh_lp2
+reset enable_partition_pruning;
+reset constraint_exclusion;
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index a19ee08749..a1c90eb905 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -83,12 +83,13 @@ select name, setting from pg_settings where name like 'enable%';
  enable_nestloop                | on
  enable_parallel_append         | on
  enable_parallel_hash           | on
+ enable_partition_pruning       | on
  enable_partitionwise_aggregate | off
  enable_partitionwise_join      | off
  enable_seqscan                 | on
  enable_sort                    | on
  enable_tidscan                 | on
-(16 rows)
+(17 rows)
 
 -- Test that the pg_timezone_names and pg_timezone_abbrevs views are
 -- more-or-less working.  We can't test their contents in any great detail
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index b38b39c71e..6ced422f17 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -698,3 +698,49 @@ create table pp_intrangepart2inf partition of pp_intrangepart for values in ('[2
 explain (costs off) select * from pp_intrangepart where a = '[1,2]'::int4range;
 explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range;
 drop table pp_intrangepart;
+
+--
+-- Ensure the enable_partition_prune GUC properly disables partition pruning.
+--
+
+create table pp_lp (a int, value int) partition by list (a);
+create table pp_lp1 partition of pp_lp for values in(1);
+create table pp_lp2 partition of pp_lp for values in(2);
+
+set enable_partition_pruning = off;
+
+set constraint_exclusion = 'partition'; -- this should not affect the result.
+
+explain (costs off) select * from pp_lp where a = 1;
+explain (costs off) update pp_lp set value = 10 where a = 1;
+explain (costs off) delete from pp_lp where a = 1;
+
+set constraint_exclusion = 'off'; -- this should not affect the result.
+
+explain (costs off) select * from pp_lp where a = 1;
+explain (costs off) update pp_lp set value = 10 where a = 1;
+explain (costs off) delete from pp_lp where a = 1;
+
+drop table pp_lp;
+
+-- Ensure enable_partition_prune does not affect non-partitioned tables.
+
+create table inh_lp (a int, value int);
+create table inh_lp1 (a int, value int, check(a = 1)) inherits (inh_lp);
+create table inh_lp2 (a int, value int, check(a = 2)) inherits (inh_lp);
+
+set constraint_exclusion = 'partition';
+
+-- inh_lp2 should be removed in the following 3 cases.
+explain (costs off) select * from inh_lp where a = 1;
+explain (costs off) update inh_lp set value = 10 where a = 1;
+explain (costs off) delete from inh_lp where a = 1;
+
+-- Ensure we don't exclude normal relations when we only expect to exclude
+-- inheritance children
+explain (costs off) update inh_lp1 set value = 10 where a = 2;
+
+drop table inh_lp cascade;
+
+reset enable_partition_pruning;
+reset constraint_exclusion;
-- 
2.16.2.windows.1

