set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
                 Index rti, RangeTblEntry *rte)
 {
-   if (rte->inh)
+   if (rel->reloptkind == RELOPT_BASEREL &&
+       relation_excluded_by_constraints(root, rel, rte))
+   {
+       /*
+        * We proved we don't need to scan the rel via constraint exclusion,
+        * so set up a single dummy path for it.  Here we only check this for
+        * regular baserels; if it's an otherrel, CE was already checked in
+        * set_append_rel_pathlist().
+        */
+       set_dummy_rel_pathlist(rel);
+   }
+   else if (rte->inh)
    {
        /* It's an "append relation", process accordingly */
        set_append_rel_pathlist(root, rel, rti, rte);
 static void
 set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
-   /*
-    * If we can prove we don't need to scan the rel via constraint exclusion,
-    * set up a single dummy path for it.  We only need to check for regular
-    * baserels; if it's an otherrel, CE was already checked in
-    * set_append_rel_pathlist().
-    */
-   if (rel->reloptkind == RELOPT_BASEREL &&
-       relation_excluded_by_constraints(root, rel, rte))
-   {
-       set_dummy_rel_pathlist(rel);
-       return;
-   }
-
    /*
     * Test any partial indexes of rel for applicability.  We must do this
     * first since partial unique indexes can affect size estimates.
         * otherrels.  So we just leave the child's attr_needed empty.
         */
 
-       /* Remember which childrels are live, for MergeAppend logic below */
-       live_childrels = lappend(live_childrels, childrel);
-
        /*
-        * Compute the child's access paths, and add the cheapest one to the
-        * Append path we are constructing for the parent.
+        * Compute the child's access paths.
         */
        set_rel_pathlist(root, childrel, childRTindex, childRTE);
 
+       /*
+        * It is possible that constraint exclusion detected a contradiction
+        * within a child subquery, even though we didn't prove one above.
+        * If what we got back was a dummy path, we can skip this child.
+        */
+       if (IS_DUMMY_PATH(childrel->cheapest_total_path))
+           continue;
+
+       /*
+        * Child is live, so add its cheapest access path to the Append path
+        * we are constructing for the parent.
+        */
        subpaths = accumulate_append_subpath(subpaths,
                                             childrel->cheapest_total_path);
 
+       /* Remember which childrels are live, for MergeAppend logic below */
+       live_childrels = lappend(live_childrels, childrel);
+
        /*
         * Collect a list of all the available path orderings for all the
         * children.  We use this as a heuristic to indicate which sort
                                    &subroot);
    rel->subroot = subroot;
 
+   /*
+    * It's possible that constraint exclusion proved the subquery empty.
+    * If so, it's convenient to turn it back into a dummy path so that we
+    * will recognize appropriate optimizations at this level.
+    */
+   if (is_dummy_plan(rel->subplan))
+   {
+       set_dummy_rel_pathlist(rel);
+       return;
+   }
+
    /* Mark rel with estimated output rows, width, etc */
    set_subquery_size_estimates(root, rel);
 
 
 static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
 static Plan *inheritance_planner(PlannerInfo *root);
 static Plan *grouping_planner(PlannerInfo *root, double tuple_fraction);
-static bool is_dummy_plan(Plan *plan);
 static void preprocess_rowmarks(PlannerInfo *root);
 static double preprocess_limit(PlannerInfo *root,
                 double tuple_fraction,
  * is deemed not to need scanning due to constraint exclusion.
  *
  * Currently, such dummy plans are Result nodes with constant FALSE
- * filter quals.
+ * filter quals (see set_dummy_rel_pathlist and create_append_plan).
+ *
+ * XXX this probably ought to be somewhere else, but not clear where.
  */
-static bool
+bool
 is_dummy_plan(Plan *plan)
 {
    if (IsA(plan, Result))