1
0
mirror of https://github.com/postgres/postgres.git synced 2025-09-02 04:21:28 +03:00

Clean up handling of constraint_exclusion and enable_partition_pruning.

The interaction of these parameters was a bit confused/confusing,
and in fact v11 entirely misses the opportunity to apply partition
constraints when a partition is accessed directly (rather than
indirectly from its parent).

In HEAD, establish the principle that enable_partition_pruning controls
partition pruning and nothing else.  When accessing a partition via its
parent, we do partition pruning (if enabled by enable_partition_pruning)
and then there is no need to consider partition constraints in the
constraint_exclusion logic.  When accessing a partition directly, its
partition constraints are applied by the constraint_exclusion logic,
only if constraint_exclusion = on.

In v11, we can't have such a clean division of these GUCs' effects,
partly because we don't want to break compatibility too much in a
released branch, and partly because the clean coding requires
inheritance_planner to have applied partition pruning to a partitioned
target table, which it doesn't in v11.  However, we can tweak things
enough to cover the missed case, which seems like a good idea since
it's potentially a performance regression from v10.  This patch keeps
v11's previous behavior in which enable_partition_pruning overrides
constraint_exclusion for an inherited target table, though.

In HEAD, also teach relation_excluded_by_constraints that it's okay to use
inheritable constraints when trying to prune a traditional inheritance
tree.  This might not be thought worthy of effort given that that feature
is semi-deprecated now, but we have enough infrastructure that it only
takes a couple more lines of code to do it correctly.

Amit Langote and Tom Lane

Discussion: https://postgr.es/m/9813f079-f16b-61c8-9ab7-4363cab28d80@lab.ntt.co.jp
Discussion: https://postgr.es/m/29069.1555970894@sss.pgh.pa.us
This commit is contained in:
Tom Lane
2019-04-30 15:03:35 -04:00
parent ad23adc5a1
commit e03ff73969
6 changed files with 156 additions and 67 deletions

View File

@@ -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