When a relation has been proven empty by constraint exclusion, propagate that
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 24 Mar 2008 21:53:12 +0000 (21:53 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 24 Mar 2008 21:53:12 +0000 (21:53 +0000)
knowledge up through any joins it participates in.  We were doing that already
in some special cases but not in the general case.  Also, defend against zero
row estimates for the input relations in cost_mergejoin --- this fix may have
eliminated the only scenario in which that can happen, but be safe.  Per
report from Alex Solovey.

src/backend/optimizer/path/allpaths.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/path/joinpath.c
src/backend/optimizer/path/joinrels.c
src/include/nodes/relation.h

index 89c0fc244dabf5e4349b4781aad2eb58f6c4468f..a199a001b6fe1b79b011121d780a20e0218e4586 100644 (file)
@@ -428,7 +428,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
  *       Build a dummy path for a relation that's been excluded by constraints
  *
  * Rather than inventing a special "dummy" path type, we represent this as an
- * AppendPath with no members.
+ * AppendPath with no members (see also IS_DUMMY_PATH macro).
  */
 static void
 set_dummy_rel_pathlist(RelOptInfo *rel)
index fc2e25456d264ae08069f7387a02a2f7cd6d19b6..dfd398db24bb2ac099bb2eaa31e3dda1d3676b78 100644 (file)
@@ -1385,6 +1385,12 @@ cost_mergejoin(MergePath *path, PlannerInfo *root)
        Selectivity joininfactor;
        Path            sort_path;              /* dummy for result of cost_sort */
 
+       /* Protect some assumptions below that rowcounts aren't zero */
+       if (outer_path_rows <= 0)
+               outer_path_rows = 1;
+       if (inner_path_rows <= 0)
+               inner_path_rows = 1;
+
        if (!enable_mergejoin)
                startup_cost += disable_cost;
 
index 0e75bfa45394100857212e86c7e9f0bbf48478e1..eefa7a944639f092e551ad1d7503015105aa1dc9 100644 (file)
@@ -837,11 +837,9 @@ best_appendrel_indexscan(PlannerInfo *root, RelOptInfo *rel,
 
                /*
                 * Check to see if child was rejected by constraint exclusion. If so,
-                * it will have a cheapest_total_path that's an Append path with no
-                * members (see set_plain_rel_pathlist).
+                * it will have a cheapest_total_path that's a "dummy" path.
                 */
-               if (IsA(childrel->cheapest_total_path, AppendPath) &&
-                       ((AppendPath *) childrel->cheapest_total_path)->subpaths == NIL)
+               if (IS_DUMMY_PATH(childrel->cheapest_total_path))
                        continue;                       /* OK, we can ignore it */
 
                /*
index d91f1116e2d0a43e87df5dbfbcc6a3434915fa83..53dd53ab132b62e4c417e13c68d94061d350442f 100644 (file)
@@ -27,6 +27,8 @@ static List *make_rels_by_clauseless_joins(PlannerInfo *root,
                                                          ListCell *other_rels);
 static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel);
 static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel);
+static bool is_dummy_rel(RelOptInfo *rel);
+static void mark_dummy_join(RelOptInfo *rel);
 
 
 /*
@@ -571,35 +573,75 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
                                                         &restrictlist);
 
        /*
-        * Consider paths using each rel as both outer and inner.
+        * If we've already proven this join is empty, we needn't consider
+        * any more paths for it.
+        */
+       if (is_dummy_rel(joinrel))
+       {
+               bms_free(joinrelids);
+               return joinrel;
+       }
+
+       /*
+        * Consider paths using each rel as both outer and inner.  Depending
+        * on the join type, a provably empty outer or inner rel might mean
+        * the join is provably empty too; in which case throw away any
+        * previously computed paths and mark the join as dummy.  (We do it
+        * this way since it's conceivable that dummy-ness of a multi-element
+        * join might only be noticeable for certain construction paths.)
         */
        switch (jointype)
        {
                case JOIN_INNER:
+                       if (is_dummy_rel(rel1) || is_dummy_rel(rel2))
+                       {
+                               mark_dummy_join(joinrel);
+                               break;
+                       }
                        add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_INNER,
                                                                 restrictlist);
                        add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_INNER,
                                                                 restrictlist);
                        break;
                case JOIN_LEFT:
+                       if (is_dummy_rel(rel1))
+                       {
+                               mark_dummy_join(joinrel);
+                               break;
+                       }
                        add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_LEFT,
                                                                 restrictlist);
                        add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_RIGHT,
                                                                 restrictlist);
                        break;
                case JOIN_FULL:
+                       if (is_dummy_rel(rel1) && is_dummy_rel(rel2))
+                       {
+                               mark_dummy_join(joinrel);
+                               break;
+                       }
                        add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_FULL,
                                                                 restrictlist);
                        add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_FULL,
                                                                 restrictlist);
                        break;
                case JOIN_RIGHT:
+                       if (is_dummy_rel(rel2))
+                       {
+                               mark_dummy_join(joinrel);
+                               break;
+                       }
                        add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_RIGHT,
                                                                 restrictlist);
                        add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_LEFT,
                                                                 restrictlist);
                        break;
                case JOIN_IN:
+                       if (is_dummy_rel(rel1) || is_dummy_rel(rel2))
+                       {
+                               mark_dummy_join(joinrel);
+                               break;
+                       }
                        add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_IN,
                                                                 restrictlist);
                        /* REVERSE_IN isn't supported by joinpath.c */
@@ -609,6 +651,11 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
                                                                 restrictlist);
                        break;
                case JOIN_REVERSE_IN:
+                       if (is_dummy_rel(rel1) || is_dummy_rel(rel2))
+                       {
+                               mark_dummy_join(joinrel);
+                               break;
+                       }
                        /* REVERSE_IN isn't supported by joinpath.c */
                        add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_IN,
                                                                 restrictlist);
@@ -618,12 +665,22 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
                                                                 restrictlist);
                        break;
                case JOIN_UNIQUE_OUTER:
+                       if (is_dummy_rel(rel1) || is_dummy_rel(rel2))
+                       {
+                               mark_dummy_join(joinrel);
+                               break;
+                       }
                        add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_UNIQUE_OUTER,
                                                                 restrictlist);
                        add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_UNIQUE_INNER,
                                                                 restrictlist);
                        break;
                case JOIN_UNIQUE_INNER:
+                       if (is_dummy_rel(rel1) || is_dummy_rel(rel2))
+                       {
+                               mark_dummy_join(joinrel);
+                               break;
+                       }
                        add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_UNIQUE_INNER,
                                                                 restrictlist);
                        add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_UNIQUE_OUTER,
@@ -883,3 +940,39 @@ has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel)
 
        return false;
 }
+
+
+/*
+ * is_dummy_rel --- has relation been proven empty?
+ *
+ * If so, it will have a single path that is dummy.
+ */
+static bool
+is_dummy_rel(RelOptInfo *rel)
+{
+       return (rel->cheapest_total_path != NULL &&
+                       IS_DUMMY_PATH(rel->cheapest_total_path));
+}
+
+/*
+ * Mark a joinrel as proven empty.
+ */
+static void
+mark_dummy_join(RelOptInfo *rel)
+{
+       /* Set dummy size estimate */
+       rel->rows = 0;
+
+       /* Evict any previously chosen paths */
+       rel->pathlist = NIL;
+
+       /* Set up the dummy path */
+       add_path(rel, (Path *) create_append_path(rel, NIL));
+
+       /*
+        * Although set_cheapest will be done again later, we do it immediately
+        * in order to keep is_dummy_rel as cheap as possible (ie, not have
+        * to examine the pathlist).
+        */
+       set_cheapest(rel);
+}
index f282b0590f834780dbd5547ed7f4a58ea36b8a47..f521055dc3590f0f77808032baa3c05a56eabe28 100644 (file)
@@ -693,6 +693,8 @@ typedef struct TidPath
  *
  * Note: it is possible for "subpaths" to contain only one, or even no,
  * elements.  These cases are optimized during create_append_plan.
+ * In particular, an AppendPath with no subpaths is a "dummy" path that
+ * is created to represent the case that a relation is provably empty.
  */
 typedef struct AppendPath
 {
@@ -700,6 +702,9 @@ typedef struct AppendPath
        List       *subpaths;           /* list of component Paths */
 } AppendPath;
 
+#define IS_DUMMY_PATH(p) \
+       (IsA((p), AppendPath) && ((AppendPath *) (p))->subpaths == NIL)
+
 /*
  * ResultPath represents use of a Result plan node to compute a variable-free
  * targetlist with no underlying tables (a "SELECT expressions" query).