diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 8ddab75b5ab..84341a30e56 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5084,10 +5084,11 @@ ANY num_sync ( .)
Refer to for
- more information on using constraint exclusion and partitioning.
+ more information on using constraint exclusion to implement
+ partitioning.
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index cba2ea9b2b7..a0a7435a03d 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -4534,25 +4534,12 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- Currently, pruning of partitions during the planning of an
- UPDATE or DELETE command is
- implemented using the constraint exclusion method (however, it is
- controlled by the enable_partition_pruning rather than
- constraint_exclusion) — see the following section
- for details and caveats that apply.
-
-
Execution-time partition pruning currently only occurs for the
Append and MergeAppend node types.
It is not yet implemented for the ModifyTable node
- type.
-
-
-
- Both of these behaviors are likely to be changed in a future release
- of PostgreSQL.
+ type, but that is likely to be changed in a future release of
+ PostgreSQL.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 0a6710c73b9..eb6f5a354dc 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1513,8 +1513,9 @@ inheritance_planner(PlannerInfo *root)
parent_rte->securityQuals = NIL;
/*
- * Mark whether we're planning a query to a partitioned table or an
- * inheritance parent.
+ * HACK: setting this to a value other than INHKIND_NONE signals to
+ * relation_excluded_by_constraints() to treat the result relation as
+ * being an appendrel member.
*/
subroot->inhTargetKind =
(rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 33013313040..3215c299ce4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -67,7 +67,9 @@ static bool infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
List *idxExprs);
static List *get_relation_constraints(PlannerInfo *root,
Oid relationObjectId, RelOptInfo *rel,
- bool include_notnull);
+ bool include_noinherit,
+ bool include_notnull,
+ bool include_partition);
static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
Relation heapRelation);
static List *get_relation_statistics(RelOptInfo *rel, Relation relation);
@@ -1134,16 +1136,22 @@ get_relation_data_width(Oid relid, int32 *attr_widths)
/*
* get_relation_constraints
*
- * Retrieve the validated CHECK constraint expressions of the given relation.
+ * Retrieve the applicable constraint expressions of the given relation.
*
* Returns a List (possibly empty) of constraint expressions. Each one
* has been canonicalized, and its Vars are changed to have the varno
* indicated by rel->relid. This allows the expressions to be easily
* compared to expressions taken from WHERE.
*
+ * If include_noinherit is true, it's okay to include constraints that
+ * are marked NO INHERIT.
+ *
* If include_notnull is true, "col IS NOT NULL" expressions are generated
* and added to the result for each column that's marked attnotnull.
*
+ * If include_partition is true, and the relation is a partition,
+ * also include the partitioning constraints.
+ *
* Note: at present this is invoked at most once per relation per planner
* run, and in many cases it won't be invoked at all, so there seems no
* point in caching the data in RelOptInfo.
@@ -1151,7 +1159,9 @@ get_relation_data_width(Oid relid, int32 *attr_widths)
static List *
get_relation_constraints(PlannerInfo *root,
Oid relationObjectId, RelOptInfo *rel,
- bool include_notnull)
+ bool include_noinherit,
+ bool include_notnull,
+ bool include_partition)
{
List *result = NIL;
Index varno = rel->relid;
@@ -1175,10 +1185,13 @@ get_relation_constraints(PlannerInfo *root,
/*
* If this constraint hasn't been fully validated yet, we must
- * ignore it here.
+ * ignore it here. Also ignore if NO INHERIT and we weren't told
+ * that that's safe.
*/
if (!constr->check[i].ccvalid)
continue;
+ if (constr->check[i].ccnoinherit && !include_noinherit)
+ continue;
cexpr = stringToNode(constr->check[i].ccbin);
@@ -1243,13 +1256,9 @@ get_relation_constraints(PlannerInfo *root,
}
/*
- * Append partition predicates, if any.
- *
- * For selects, partition pruning uses the parent table's partition bound
- * descriptor, instead of constraint exclusion which is driven by the
- * individual partition's partition constraint.
+ * Add partitioning constraints, if requested.
*/
- if (enable_partition_pruning && root->parse->commandType != CMD_SELECT)
+ if (include_partition && relation->rd_rel->relispartition)
{
List *pcqual = RelationGetPartitionQual(relation);
@@ -1366,7 +1375,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
*
* Detect whether the relation need not be scanned because it has either
* self-inconsistent restrictions, or restrictions inconsistent with the
- * relation's validated CHECK constraints.
+ * relation's applicable constraints.
*
* Note: this examines only rel->relid, rel->reloptkind, and
* rel->baserestrictinfo; therefore it can be called before filling in
@@ -1376,6 +1385,9 @@ bool
relation_excluded_by_constraints(PlannerInfo *root,
RelOptInfo *rel, RangeTblEntry *rte)
{
+ bool include_noinherit;
+ bool include_notnull;
+ bool include_partition = false;
List *safe_restrictions;
List *constraint_pred;
List *safe_constraints;
@@ -1384,6 +1396,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
/* As of now, constraint exclusion works only with simple relations. */
Assert(IS_SIMPLE_REL(rel));
+ /*
+ * If there are no base restriction clauses, we have no hope of proving
+ * anything below, so fall out quickly.
+ */
+ if (rel->baserestrictinfo == NIL)
+ return false;
+
/*
* Regardless of the setting of constraint_exclusion, detect
* constant-FALSE-or-NULL restriction clauses. Because const-folding will
@@ -1410,35 +1429,41 @@ relation_excluded_by_constraints(PlannerInfo *root,
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;
+ /* In 'off' mode, never make any further tests */
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.
+ * appendrel members. Normally, they are RELOPT_OTHER_MEMBER_REL
+ * relations, but we also consider inherited target relations as
+ * appendrel members for the purposes of constraint exclusion
+ * (since, indeed, they were appendrel members earlier in
+ * inheritance_planner).
+ *
+ * In both cases, partition pruning was already applied, so there
+ * is no need to consider the rel's partition constraints here.
*/
- if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) &&
- !(rel->reloptkind == RELOPT_BASEREL &&
- root->inhTargetKind != INHKIND_NONE &&
- rel->relid == root->parse->resultRelation))
- return false;
- break;
+ if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
+ (rel->relid == root->parse->resultRelation &&
+ root->inhTargetKind != INHKIND_NONE))
+ break; /* appendrel member, so process it */
+ return false;
case CONSTRAINT_EXCLUSION_ON:
+
+ /*
+ * In 'on' mode, always apply constraint exclusion. If we are
+ * considering a baserel that is a partition (i.e., it was
+ * directly named rather than expanded from a parent table), then
+ * its partition constraints haven't been considered yet, so
+ * include them in the processing here.
+ */
+ if (rel->reloptkind == RELOPT_BASEREL &&
+ !(rel->relid == root->parse->resultRelation &&
+ root->inhTargetKind != INHKIND_NONE))
+ include_partition = true;
break; /* always try to exclude */
}
@@ -1467,24 +1492,33 @@ relation_excluded_by_constraints(PlannerInfo *root,
return true;
/*
- * Only plain relations have constraints. In a partitioning hierarchy,
- * but not with regular table inheritance, it's OK to assume that any
- * constraints that hold for the parent also hold for every child; for
- * instance, table inheritance allows the parent to have constraints
- * marked NO INHERIT, but table partitioning does not. We choose to check
- * whether the partitioning parents can be excluded here; doing so
- * consumes some cycles, but potentially saves us the work of excluding
- * each child individually.
+ * Only plain relations have constraints, so stop here for other rtekinds.
*/
- if (rte->rtekind != RTE_RELATION ||
- (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
+ if (rte->rtekind != RTE_RELATION)
return false;
/*
- * OK to fetch the constraint expressions. Include "col IS NOT NULL"
- * expressions for attnotnull columns, in case we can refute those.
+ * If we are scanning just this table, we can use NO INHERIT constraints,
+ * but not if we're scanning its children too. (Note that partitioned
+ * tables should never have NO INHERIT constraints; but it's not necessary
+ * for us to assume that here.)
*/
- constraint_pred = get_relation_constraints(root, rte->relid, rel, true);
+ include_noinherit = !rte->inh;
+
+ /*
+ * Currently, attnotnull constraints must be treated as NO INHERIT unless
+ * this is a partitioned table. In future we might track their
+ * inheritance status more accurately, allowing this to be refined.
+ */
+ include_notnull = (!rte->inh || rte->relkind == RELKIND_PARTITIONED_TABLE);
+
+ /*
+ * Fetch the appropriate set of constraint expressions.
+ */
+ constraint_pred = get_relation_constraints(root, rte->relid, rel,
+ include_noinherit,
+ include_notnull,
+ include_partition);
/*
* We do not currently enforce that CHECK constraints contain only
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 0789b316eb4..bd64bed8fc7 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -3639,4 +3639,46 @@ select * from listp where a = (select 2) and b <> 10;
-> Result (never executed)
(4 rows)
+--
+-- check that a partition directly accessed in a query is excluded with
+-- constraint_exclusion = on
+--
+-- turn off partition pruning, so that it doesn't interfere
+set enable_partition_pruning to off;
+-- setting constraint_exclusion to 'partition' disables exclusion
+set constraint_exclusion to 'partition';
+explain (costs off) select * from listp1 where a = 2;
+ QUERY PLAN
+--------------------
+ Seq Scan on listp1
+ Filter: (a = 2)
+(2 rows)
+
+explain (costs off) update listp1 set a = 1 where a = 2;
+ QUERY PLAN
+--------------------------
+ Update on listp1
+ -> Seq Scan on listp1
+ Filter: (a = 2)
+(3 rows)
+
+-- constraint exclusion enabled
+set constraint_exclusion to 'on';
+explain (costs off) select * from listp1 where a = 2;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+explain (costs off) update listp1 set a = 1 where a = 2;
+ QUERY PLAN
+--------------------------------
+ Update on listp1
+ -> Result
+ One-Time Filter: false
+(3 rows)
+
+reset constraint_exclusion;
+reset enable_partition_pruning;
drop table listp;
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index c30e58eef73..246c6274af6 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -990,4 +990,24 @@ create table listp2_10 partition of listp2 for values in (10);
explain (analyze, costs off, summary off, timing off)
select * from listp where a = (select 2) and b <> 10;
+--
+-- check that a partition directly accessed in a query is excluded with
+-- constraint_exclusion = on
+--
+
+-- turn off partition pruning, so that it doesn't interfere
+set enable_partition_pruning to off;
+
+-- setting constraint_exclusion to 'partition' disables exclusion
+set constraint_exclusion to 'partition';
+explain (costs off) select * from listp1 where a = 2;
+explain (costs off) update listp1 set a = 1 where a = 2;
+-- constraint exclusion enabled
+set constraint_exclusion to 'on';
+explain (costs off) select * from listp1 where a = 2;
+explain (costs off) update listp1 set a = 1 where a = 2;
+
+reset constraint_exclusion;
+reset enable_partition_pruning;
+
drop table listp;