pg_dp_dummy_crash.patch

text/x-patch

Filename: pg_dp_dummy_crash.patch
Type: text/x-patch
Part: 0
Message: Crash in partition-wise join involving dummy partitioned relation

Patch

Format: unified
File+
src/backend/optimizer/path/allpaths.c 4 14
src/backend/optimizer/path/joinrels.c 0 16
src/backend/optimizer/util/relnode.c 4 1
src/include/nodes/relation.h 16 3
src/test/regress/expected/partition_join.out 25 18
src/test/regress/sql/partition_join.sql 1 1
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 5bff90e..6e842f9 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -3425,20 +3425,8 @@ generate_partition_wise_join_paths(PlannerInfo *root, RelOptInfo *rel)
 	if (!IS_JOIN_REL(rel))
 		return;
 
-	/*
-	 * If we've already proven this join is empty, we needn't consider any
-	 * more paths for it.
-	 */
-	if (IS_DUMMY_REL(rel))
-		return;
-
-	/*
-	 * We've nothing to do if the relation is not partitioned. An outer join
-	 * relation which had an empty inner relation in every pair will have the
-	 * rest of the partitioning properties set except the child-join
-	 * RelOptInfos. See try_partition_wise_join() for more details.
-	 */
-	if (rel->nparts <= 0 || rel->part_rels == NULL)
+	/* We've nothing to do if the relation is not partitioned. */
+	if (!IS_PARTITIONED_REL(rel))
 		return;
 
 	/* Guard against stack overflow due to overly deep partition hierarchy. */
@@ -3452,6 +3440,8 @@ generate_partition_wise_join_paths(PlannerInfo *root, RelOptInfo *rel)
 	{
 		RelOptInfo *child_rel = part_rels[cnt_parts];
 
+		Assert(child_rel != NULL);
+
 		/* Add partition-wise join paths for partitioned child-joins. */
 		generate_partition_wise_join_paths(root, child_rel);
 
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index a35d068..f74afdb 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -1319,17 +1319,6 @@ try_partition_wise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
 		return;
 
 	/*
-	 * set_rel_pathlist() may not create paths in children of an empty
-	 * partitioned table and so we can not add paths to child-joins. So, deem
-	 * such a join as unpartitioned. When a partitioned relation is deemed
-	 * empty because all its children are empty, dummy path will be set in
-	 * each of the children.  In such a case we could still consider the join
-	 * as partitioned, but it might not help much.
-	 */
-	if (IS_DUMMY_REL(rel1) || IS_DUMMY_REL(rel2))
-		return;
-
-	/*
 	 * Since this join relation is partitioned, all the base relations
 	 * participating in this join must be partitioned and so are all the
 	 * intermediate join relations.
@@ -1360,11 +1349,6 @@ try_partition_wise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
 
 	nparts = joinrel->nparts;
 
-	/* Allocate space to hold child-joins RelOptInfos, if not already done. */
-	if (!joinrel->part_rels)
-		joinrel->part_rels =
-			(RelOptInfo **) palloc0(sizeof(RelOptInfo *) * nparts);
-
 	/*
 	 * Create child-join relations for this partitioned join, if those don't
 	 * exist. Add paths to child-joins for a pair of child relations
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index ac5a7c9..5c36832 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -1662,11 +1662,14 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
 	 */
 	joinrel->part_scheme = part_scheme;
 	joinrel->boundinfo = outer_rel->boundinfo;
-	joinrel->nparts = outer_rel->nparts;
 	partnatts = joinrel->part_scheme->partnatts;
 	joinrel->partexprs = (List **) palloc0(sizeof(List *) * partnatts);
 	joinrel->nullable_partexprs =
 		(List **) palloc0(sizeof(List *) * partnatts);
+	joinrel->nparts = outer_rel->nparts;
+	joinrel->part_rels =
+		(RelOptInfo **) palloc0(sizeof(RelOptInfo *) * joinrel->nparts);
+
 
 	/*
 	 * Construct partition keys for the join.
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 6bf68f3..e082d05 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -668,11 +668,24 @@ typedef struct RelOptInfo
  *
  * A join between two partitioned relations with same partitioning scheme
  * without any matching partitions will not have any partition in it but will
- * have partition scheme set. So a relation is deemed to be partitioned if it
- * has a partitioning scheme, bounds and positive number of partitions.
+ * have partition scheme set.
+ *
+ * set_rel_pathlist() may not create paths in children of an empty partitioned
+ * table. Hence we can not add paths to child-joins produced as a result of
+ * joining an empty partitioned table with another similarly partitioned table.
+ * So, deem such a join as unpartitioned. As a result any N-way join involving
+ * an empty partitioned table will be deemed as unpartitioned and will not have
+ * child-RelOptInfos created. NOTE: When a partitioned relation is deemed empty
+ * because all its children are empty, dummy path will be set in each of the
+ * children.  In such a case we could still consider the join as partitioned,
+ * but it might not help much.
+ *
+ * So a relation is deemed to be partitioned if it has a partitioning scheme,
+ * bounds, positive number of partitions and child RelOptInfos.
  */
 #define IS_PARTITIONED_REL(rel) \
-	((rel)->part_scheme && (rel)->boundinfo && (rel)->nparts > 0)
+	((rel)->part_scheme && (rel)->boundinfo && (rel)->nparts > 0 && \
+	 (rel)->part_rels && !(IS_DUMMY_REL(rel)))
 
 /*
  * Convenience macro to make sure that a partitioned relation has all the
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 27ab852..333f938 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -1217,24 +1217,31 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1
 (2 rows)
 
 EXPLAIN (COSTS OFF)
-SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b;
-                 QUERY PLAN                 
---------------------------------------------
- Sort
-   Sort Key: a, t2.b
-   ->  Hash Left Join
-         Hash Cond: (t2.b = a)
-         ->  Append
-               ->  Seq Scan on prt2_p1 t2
-                     Filter: (a = 0)
-               ->  Seq Scan on prt2_p2 t2_1
-                     Filter: (a = 0)
-               ->  Seq Scan on prt2_p3 t2_2
-                     Filter: (a = 0)
-         ->  Hash
-               ->  Result
-                     One-Time Filter: false
-(14 rows)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b, prt1 t3 WHERE t2.b = t3.a;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Hash Left Join
+   Hash Cond: (t2.b = a)
+   ->  Append
+         ->  Hash Join
+               Hash Cond: (t3.a = t2.b)
+               ->  Seq Scan on prt1_p1 t3
+               ->  Hash
+                     ->  Seq Scan on prt2_p1 t2
+         ->  Hash Join
+               Hash Cond: (t3_1.a = t2_1.b)
+               ->  Seq Scan on prt1_p2 t3_1
+               ->  Hash
+                     ->  Seq Scan on prt2_p2 t2_1
+         ->  Hash Join
+               Hash Cond: (t3_2.a = t2_2.b)
+               ->  Seq Scan on prt1_p3 t3_2
+               ->  Hash
+                     ->  Seq Scan on prt2_p3 t2_2
+   ->  Hash
+         ->  Result
+               One-Time Filter: false
+(21 rows)
 
 EXPLAIN (COSTS OFF)
 SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b;
diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql
index 6efdf3c..55c5615 100644
--- a/src/test/regress/sql/partition_join.sql
+++ b/src/test/regress/sql/partition_join.sql
@@ -224,7 +224,7 @@ EXPLAIN (COSTS OFF)
 SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 LEFT JOIN prt2 t2 ON t1.a = t2.b;
 
 EXPLAIN (COSTS OFF)
-SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b, prt1 t3 WHERE t2.b = t3.a;
 
 EXPLAIN (COSTS OFF)
 SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b;