]> granicus.if.org Git - postgresql/commitdiff
Fix possible crash in partition-wise join.
authorRobert Haas <rhaas@postgresql.org>
Mon, 5 Feb 2018 22:31:57 +0000 (17:31 -0500)
committerRobert Haas <rhaas@postgresql.org>
Mon, 5 Feb 2018 22:31:57 +0000 (17:31 -0500)
The previous code assumed that we'd always succeed in creating
child-joins for a joinrel for which partition-wise join was considered,
but that's not guaranteed, at least in the case where dummy rels
are involved.

Ashutosh Bapat, with some wordsmithing by me.

Discussion: http://postgr.es/m/CAFjFpRf8=uyMYYfeTBjWDMs1tR5t--FgOe2vKZPULxxdYQ4RNw@mail.gmail.com

src/backend/optimizer/path/allpaths.c
src/backend/optimizer/path/joinrels.c
src/backend/optimizer/util/relnode.c
src/include/nodes/relation.h
src/test/regress/expected/partition_join.out
src/test/regress/sql/partition_join.sql

index 5bff90e1bcab8a4a41aa1bf727b369d2787a2aec..6e842f93d0fd765371cd75ed4f463d53e1a90985 100644 (file)
@@ -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);
 
index a35d068911921510c899cb9937853c8b8c42c462..f74afdb4dda873f21287e9f67b304bc327975d60 100644 (file)
@@ -1318,17 +1318,6 @@ try_partition_wise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
        if (!IS_PARTITIONED_REL(joinrel))
                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
@@ -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
index ac5a7c9553d1b2e8d9fa8ff25e53b5c07751b6d6..5c368321e6ecf14a63e0cff6fa28756fb7796f9d 100644 (file)
@@ -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.
index 6bf68f31da10300e26ebca1e797e51ce1d12b825..b1c63173c22ffe2d701dfdc2f1498dce094bf98f 100644 (file)
@@ -666,13 +666,17 @@ typedef struct RelOptInfo
 /*
  * Is given relation partitioned?
  *
- * 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.
+ * It's not enough to test whether rel->part_scheme is set, because it might
+ * be that the basic partitioning properties of the input relations matched
+ * but the partition bounds did not.
+ *
+ * We treat dummy relations as unpartitioned.  We could alternatively
+ * treat them as partitioned, but it's not clear whether that's a useful thing
+ * to do.
  */
 #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
index 27ab8521f845f779e50255d661b1023392e66a06..333f93889cfddfb3a1da49593ff88b951a88ceb1 100644 (file)
@@ -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;
index 6efdf3c51751a3075f8301e4014842ab13816ace..55c5615d06c3e9438f7e9f475935a39255aaac5f 100644 (file)
@@ -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;