Improve create_unique_path to not be fooled by unrelated clauses that happen
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 27 Feb 2009 00:06:27 +0000 (00:06 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 27 Feb 2009 00:06:27 +0000 (00:06 +0000)
to be syntactically part of a semijoin clause.  For example given
WHERE EXISTS(SELECT ... WHERE upper.var = lower.var AND some-condition)
where some-condition is just a restriction on the lower relation, we can
use unique-ification on lower.var after having applied some-condition within
the scan on lower.

src/backend/optimizer/util/pathnode.c

index 5ac0b6c260f50392dc12b5abcb26fdc7a68930a7..28b2828c2dd34077f5ad5a889c997c4bb9b570df 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.149 2009/01/01 17:23:44 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.150 2009/02/27 00:06:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -765,12 +765,26 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
     */
    oldcontext = MemoryContextSwitchTo(root->planner_cxt);
 
-   /*
+   /*----------
     * Look to see whether the semijoin's join quals consist of AND'ed
     * equality operators, with (only) RHS variables on only one side of
     * each one.  If so, we can figure out how to enforce uniqueness for
     * the RHS.
     *
+    * Note that the input join_quals list is the list of quals that are
+    * *syntactically* associated with the semijoin, which in practice means
+    * the synthesized comparison list for an IN or the WHERE of an EXISTS.
+    * Particularly in the latter case, it might contain clauses that aren't
+    * *semantically* associated with the join, but refer to just one side or
+    * the other.  We can ignore such clauses here, as they will just drop
+    * down to be processed within one side or the other.  (It is okay to
+    * consider only the syntactically-associated clauses here because for a
+    * semijoin, no higher-level quals could refer to the RHS, and so there
+    * can be no other quals that are semantically associated with this join.
+    * We do things this way because it is useful to be able to run this test
+    * before we have extracted the list of quals that are actually
+    * semantically associated with the particular join.)
+    *
     * Note that the in_operators list consists of the joinqual operators
     * themselves (but commuted if needed to put the RHS value on the right).
     * These could be cross-type operators, in which case the operator
@@ -778,6 +792,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
     * We assume here that that operator will be available from the btree
     * or hash opclass when the time comes ... if not, create_unique_plan()
     * will fail.
+    *----------
     */
    in_operators = NIL;
    uniq_exprs = NIL;
@@ -791,19 +806,52 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
        Node       *right_expr;
        Relids      left_varnos;
        Relids      right_varnos;
+       Relids      all_varnos;
 
-       /* must be binary opclause... */
-       if (!IsA(op, OpExpr))
-           goto no_unique_path;
-       if (list_length(op->args) != 2)
+       /* Is it a binary opclause? */
+       if (!IsA(op, OpExpr) ||
+           list_length(op->args) != 2)
+       {
+           /* No, but does it reference both sides? */
+           all_varnos = pull_varnos((Node *) op);
+           if (!bms_overlap(all_varnos, sjinfo->syn_righthand) ||
+               bms_is_subset(all_varnos, sjinfo->syn_righthand))
+           {
+               /*
+                * Clause refers to only one rel, so ignore it --- unless it
+                * contains volatile functions, in which case we'd better
+                * punt.
+                */
+               if (contain_volatile_functions((Node *) op))
+                   goto no_unique_path;
+               continue;
+           }
+           /* Non-operator clause referencing both sides, must punt */
            goto no_unique_path;
+       }
+
+       /* Extract data from binary opclause */
        opno = op->opno;
        left_expr = linitial(op->args);
        right_expr = lsecond(op->args);
-
-       /* check rel membership of arguments */
        left_varnos = pull_varnos(left_expr);
        right_varnos = pull_varnos(right_expr);
+       all_varnos = bms_union(left_varnos, right_varnos);
+
+       /* Does it reference both sides? */
+       if (!bms_overlap(all_varnos, sjinfo->syn_righthand) ||
+           bms_is_subset(all_varnos, sjinfo->syn_righthand))
+       {
+           /*
+            * Clause refers to only one rel, so ignore it --- unless it
+            * contains volatile functions, in which case we'd better punt.
+            */
+           if (contain_volatile_functions((Node *) op))
+               goto no_unique_path;
+           continue;
+       }
+
+       /* check rel membership of arguments */
        if (!bms_is_empty(right_varnos) &&
            bms_is_subset(right_varnos, sjinfo->syn_righthand) &&
            !bms_overlap(left_varnos, sjinfo->syn_righthand))
@@ -845,6 +893,10 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
        uniq_exprs = lappend(uniq_exprs, copyObject(right_expr));
    }
 
+   /* Punt if we didn't find at least one column to unique-ify */
+   if (uniq_exprs == NIL)
+       goto no_unique_path;
+
    /*
     * The expressions we'd need to unique-ify mustn't be volatile.
     */