HEAD-get_relation_constraints-fix_v2.patch

text/plain

Filename: HEAD-get_relation_constraints-fix_v2.patch
Type: text/plain
Part: 0
Message: Re: speeding up planning with partitions

Patch

Format: unified
Series: patch v2
File+
src/backend/optimizer/plan/planner.c 3 2
src/backend/optimizer/util/plancat.c 51 55
src/test/regress/expected/partition_prune.out 48 0
src/test/regress/sql/partition_prune.sql 19 0
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3a1b846217..de655892f6 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 process the result relation as
+		 * a partition; see that function for more details.
 		 */
 		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 31a3784536..0698bafd04 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -66,7 +66,7 @@ static void get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel,
 static bool infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
 							  List *idxExprs);
 static List *get_relation_constraints(PlannerInfo *root,
-						 Oid relationObjectId, RelOptInfo *rel,
+						 Relation relation, RelOptInfo *rel,
 						 bool include_notnull);
 static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
 				  Relation heapRelation);
@@ -1155,19 +1155,13 @@ get_relation_data_width(Oid relid, int32 *attr_widths)
  */
 static List *
 get_relation_constraints(PlannerInfo *root,
-						 Oid relationObjectId, RelOptInfo *rel,
+						 Relation relation, RelOptInfo *rel,
 						 bool include_notnull)
 {
 	List	   *result = NIL;
 	Index		varno = rel->relid;
-	Relation	relation;
 	TupleConstr *constr;
 
-	/*
-	 * We assume the relation has already been safely locked.
-	 */
-	relation = table_open(relationObjectId, NoLock);
-
 	constr = relation->rd_att->constr;
 	if (constr != NULL)
 	{
@@ -1247,38 +1241,6 @@ 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.
-	 */
-	if (enable_partition_pruning && root->parse->commandType != CMD_SELECT)
-	{
-		List	   *pcqual = RelationGetPartitionQual(relation);
-
-		if (pcqual)
-		{
-			/*
-			 * Run the partition quals through const-simplification similar to
-			 * check constraints.  We skip canonicalize_qual, though, because
-			 * partition quals should be in canonical form already; also,
-			 * since the qual is in implicit-AND format, we'd have to
-			 * explicitly convert it to explicit-AND format and back again.
-			 */
-			pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
-
-			/* Fix Vars to have the desired varno */
-			if (varno != 1)
-				ChangeVarNodes((Node *) pcqual, 1, varno, 0);
-
-			result = list_concat(result, pcqual);
-		}
-	}
-
-	table_close(relation, NoLock);
-
 	return result;
 }
 
@@ -1385,6 +1347,7 @@ relation_excluded_by_constraints(PlannerInfo *root,
 	List	   *constraint_pred;
 	List	   *safe_constraints;
 	ListCell   *lc;
+	Relation	relation;
 
 	/* As of now, constraint exclusion works only with simple relations. */
 	Assert(IS_SIMPLE_REL(rel));
@@ -1415,26 +1378,14 @@ 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;
 			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.
+			 * OTHER_MEMBER_RELs, or BASERELs in cases where the relation is
+			 * is an inherited target relation.
 			 */
 			if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) &&
 				!(rel->reloptkind == RELOPT_BASEREL &&
@@ -1486,10 +1437,55 @@ relation_excluded_by_constraints(PlannerInfo *root,
 		return false;
 
 	/*
+	 * We assume the relation has already been safely locked.
+	 */
+	relation = table_open(rte->relid, NoLock);
+
+	/*
 	 * OK to fetch the constraint expressions.  Include "col IS NOT NULL"
 	 * expressions for attnotnull columns, in case we can refute those.
 	 */
-	constraint_pred = get_relation_constraints(root, rte->relid, rel, true);
+	constraint_pred = get_relation_constraints(root, relation, rel, true);
+
+	/*
+	 * Append partition predicates, if any.
+	 *
+	 * If the partition is accessed indirectly via its parent table, partition
+	 * pruning is performed with the parent table's partition bound descriptor,
+	 * so there is no need to include the partition constraint in that case.
+	 * We do need to include it if the partition is referenced directly in the
+	 * query, because no partition pruning would have occurred in that case,
+	 * except in the case where the partition is a target relation.  (See
+	 * inheritance_planner().)
+	 */
+	if (rel->reloptkind == RELOPT_BASEREL &&
+		!(root->inhTargetKind == INHKIND_PARTITIONED &&
+		  rel->relid == root->parse->resultRelation))
+	{
+		List	   *pcqual = RelationGetPartitionQual(relation);
+
+		if (pcqual)
+		{
+			Index		varno = rel->relid;
+
+			/*
+			 * Run the partition quals through const-simplification similar to
+			 * check constraints.  We skip canonicalize_qual, though, because
+			 * partition quals should be in canonical form already; also,
+			 * since the qual is in implicit-AND format, we'd have to
+			 * explicitly convert it to explicit-AND format and back again.
+			 */
+			pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+
+			/* Fix Vars to have the desired varno */
+			if (varno != 1)
+				ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+			constraint_pred = list_concat(constraint_pred, pcqual);
+		}
+	}
+
+	table_close(relation, NoLock);
 
 	/*
 	 * 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 7806ba1d47..e957badf09 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -3643,4 +3643,52 @@ 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;
+-- constraint exclusion doesn't apply
+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) select * from listp2 where a = 1;
+      QUERY PLAN       
+-----------------------
+ Seq Scan on listp2_10
+   Filter: (a = 1)
+(2 rows)
+
+-- constraint exclusion applies
+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) select * from listp2 where a = 1;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+explain (costs off) update listp set a = listp1.b from listp1 where listp.a = listp1.a and listp1.a = 2;
+           QUERY PLAN           
+--------------------------------
+ Update on listp
+   ->  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 2e4d2b483d..ff3312bb31 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -990,4 +990,23 @@ 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;
+
+-- constraint exclusion doesn't apply
+set constraint_exclusion to 'partition';
+explain (costs off) select * from listp1 where a = 2;
+explain (costs off) select * from listp2 where a = 1;
+-- constraint exclusion applies
+set constraint_exclusion to 'on';
+explain (costs off) select * from listp1 where a = 2;
+explain (costs off) select * from listp2 where a = 1;
+explain (costs off) update listp set a = listp1.b from listp1 where listp.a = listp1.a and listp1.a = 2;
+reset constraint_exclusion;
+reset enable_partition_pruning;
 drop table listp;