</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 (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>
<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>
<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>
<listitem>
<para>
- Ensure that the <xref linkend="guc-constraint-exclusion"/>
+ Ensure that the <xref linkend="guc-enable-partition-pruning"/>
configuration parameter is not disabled in <filename>postgresql.conf</filename>.
If it is, queries will not be optimized as desired.
</para>
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);
* 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);
bool enable_partitionwise_aggregate = false;
bool enable_parallel_append = true;
bool enable_parallel_hash = true;
+bool enable_partition_pruning = true;
typedef struct
{
subplans = lappend(subplans, subplan);
}
- if (rel->reloptkind == RELOPT_BASEREL &&
+ if (enable_partition_pruning &&
+ rel->reloptkind == RELOPT_BASEREL &&
best_path->partitioned_rels != NIL)
{
List *prunequal;
* 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;
* 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;
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);
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.
+ */
+ subroot->inhTargetKind =
+ partitioned_relids ? INHKIND_PARTITIONED : INHKIND_INHERITED;
/*
* If the child is further partitioned, remember it as a parent. Since
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;
* 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);
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 &&
- rel->reloptkind == RELOPT_BASEREL &&
- rel->relid == root->parse->resultRelation))))
- return false;
+ /*
+ * Skip further tests, depending on constraint_exclusion.
+ */
+ switch (constraint_exclusion)
+ {
+ case CONSTRAINT_EXCLUSION_OFF:
+ /*
+ * Don't prune if feature turned off -- except if the relation is
+ * a partition. While partprune.c-style partition pruning is not
+ * yet in use for all cases (update/delete is not handled), it
+ * would be a UI horror to use different user-visible controls
+ * depending on such a volatile implementation detail. Therefore,
+ * for partitioned tables we use enable_partition_pruning to
+ * control this behavior.
+ */
+ if (root->inhTargetKind == INHKIND_PARTITIONED)
+ break;
+ return false;
+
+ case CONSTRAINT_EXCLUSION_PARTITION:
+ /*
+ * 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 ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) &&
+ !(rel->reloptkind == RELOPT_BASEREL &&
+ root->inhTargetKind != INHKIND_NONE &&
+ rel->relid == root->parse->resultRelation))
+ return false;
+ break;
+
+ case CONSTRAINT_EXCLUSION_ON:
+ break; /* always try to exclude */
+ }
/*
* Check for self-contradictory restriction clauses. We dare not make
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."),
#enable_partitionwise_join = off
#enable_partitionwise_aggregate = off
#enable_parallel_hash = on
+#enable_partition_pruning = on
# - Planner Cost Constants -
/* 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
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 */
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);
(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);
+explain (costs off) select * from pp_lp where a = 1;
+ QUERY PLAN
+--------------------------
+ Append
+ -> Seq Scan on pp_lp1
+ Filter: (a = 1)
+(3 rows)
+
+explain (costs off) update pp_lp set value = 10 where a = 1;
+ QUERY PLAN
+--------------------------
+ Update on pp_lp
+ Update on pp_lp1
+ -> Seq Scan on pp_lp1
+ Filter: (a = 1)
+(4 rows)
+
+explain (costs off) delete from pp_lp where a = 1;
+ QUERY PLAN
+--------------------------
+ Delete on pp_lp
+ Delete on pp_lp1
+ -> Seq Scan on pp_lp1
+ Filter: (a = 1)
+(4 rows)
+
+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)
+
+\set VERBOSITY terse \\ -- suppress cascade details
+drop table inh_lp cascade;
+NOTICE: drop cascades to 2 other objects
+\set VERBOSITY default
+reset enable_partition_pruning;
+reset constraint_exclusion;
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
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);
+
+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 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;
+
+\set VERBOSITY terse \\ -- suppress cascade details
+drop table inh_lp cascade;
+\set VERBOSITY default
+
+reset enable_partition_pruning;
+reset constraint_exclusion;