Add support for doing FULL JOIN ON FALSE. While this is really a rather
authorTom Lane <[email protected]>
Tue, 5 Jan 2010 23:25:36 +0000 (23:25 +0000)
committerTom Lane <[email protected]>
Tue, 5 Jan 2010 23:25:36 +0000 (23:25 +0000)
peculiar variant of UNION ALL, and so wouldn't likely get written directly
as-is, it's possible for it to arise as a result of simplification of
less-obviously-silly queries.  In particular, now that we can do flattening
of subqueries that have constant outputs and are underneath an outer join,
it's possible for the case to result from simplification of queries of the
type exhibited in bug #5263.  Back-patch to 8.4 to avoid a functionality
regression for this type of query.

src/backend/executor/nodeMergejoin.c
src/backend/optimizer/path/joinpath.c
src/include/nodes/execnodes.h
src/test/regress/expected/join.out
src/test/regress/sql/join.sql

index 0aba1a7fbcb4a3f3da5834e91c9a248ed63b25f0..22b2e5437faa79e830165712efe3c56d90ad45b1 100644 (file)
@@ -398,8 +398,13 @@ MJCompare(MergeJoinState *mergestate)
     * want to report that the tuples are equal.  Instead, if result is still
     * 0, change it to +1.  This will result in advancing the inner side of
     * the join.
+    *
+    * Likewise, if there was a constant-false joinqual, do not report
+    * equality.  We have to check this as part of the mergequals, else the
+    * rescan logic will do the wrong thing.
     */
-   if (nulleqnull && result == 0)
+   if (result == 0 &&
+       (nulleqnull || mergestate->mj_ConstFalseJoin))
        result = 1;
 
    MemoryContextSwitchTo(oldContext);
@@ -487,6 +492,32 @@ MJFillInner(MergeJoinState *node)
 }
 
 
+/*
+ * Check that a qual condition is constant true or constant false.
+ * If it is constant false (or null), set *is_const_false to TRUE.
+ *
+ * Constant true would normally be represented by a NIL list, but we allow an
+ * actual bool Const as well.  We do expect that the planner will have thrown
+ * away any non-constant terms that have been ANDed with a constant false.
+ */
+static bool
+check_constant_qual(List *qual, bool *is_const_false)
+{
+   ListCell   *lc;
+
+   foreach(lc, qual)
+   {
+       Const  *con = (Const *) lfirst(lc);
+
+       if (!con || !IsA(con, Const))
+           return false;
+       if (con->constisnull || !DatumGetBool(con->constvalue))
+           *is_const_false = true;
+   }
+   return true;
+}
+
+
 /* ----------------------------------------------------------------
  *     ExecMergeTupleDump
  *
@@ -1025,9 +1056,13 @@ ExecMergeJoin(MergeJoinState *node)
                     * state for the rescanned inner tuples.  We know all of
                     * them will match this new outer tuple and therefore
                     * won't be emitted as fill tuples.  This works *only*
-                    * because we require the extra joinquals to be nil when
-                    * doing a right or full join --- otherwise some of the
-                    * rescanned tuples might fail the extra joinquals.
+                    * because we require the extra joinquals to be constant
+                    * when doing a right or full join --- otherwise some of
+                    * the rescanned tuples might fail the extra joinquals.
+                    * This obviously won't happen for a constant-true extra
+                    * joinqual, while the constant-false case is handled by
+                    * forcing the merge clause to never match, so we never
+                    * get here.
                     */
                    ExecRestrPos(innerPlan);
 
@@ -1439,6 +1474,7 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
    mergestate->js.joinqual = (List *)
        ExecInitExpr((Expr *) node->join.joinqual,
                     (PlanState *) mergestate);
+   mergestate->mj_ConstFalseJoin = false;
    /* mergeclauses are handled below */
 
    /*
@@ -1498,10 +1534,11 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
                              ExecGetResultType(outerPlanState(mergestate)));
 
            /*
-            * Can't handle right or full join with non-nil extra joinclauses.
-            * This should have been caught by planner.
+            * Can't handle right or full join with non-constant extra
+            * joinclauses.  This should have been caught by planner.
             */
-           if (node->join.joinqual != NIL)
+           if (!check_constant_qual(node->join.joinqual,
+                                    &mergestate->mj_ConstFalseJoin))
                ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                         errmsg("RIGHT JOIN is only supported with merge-joinable join conditions")));
@@ -1517,9 +1554,11 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
                              ExecGetResultType(innerPlanState(mergestate)));
 
            /*
-            * Can't handle right or full join with non-nil extra joinclauses.
+            * Can't handle right or full join with non-constant extra
+            * joinclauses.  This should have been caught by planner.
             */
-           if (node->join.joinqual != NIL)
+           if (!check_constant_qual(node->join.joinqual,
+                                    &mergestate->mj_ConstFalseJoin))
                ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                         errmsg("FULL JOIN is only supported with merge-joinable join conditions")));
index fede1d34916c2e37e418484e5a3bb681e480dcae..61602d4f9152cb256005de135b927541b00ea408 100644 (file)
@@ -1184,10 +1184,18 @@ select_mergejoin_clauses(PlannerInfo *root,
        if (isouterjoin && restrictinfo->is_pushed_down)
            continue;
 
+       /* Check that clause is a mergeable operator clause */
        if (!restrictinfo->can_join ||
            restrictinfo->mergeopfamilies == NIL)
        {
-           have_nonmergeable_joinclause = true;
+           /*
+            * The executor can handle extra joinquals that are constants,
+            * but not anything else, when doing right/full merge join.  (The
+            * reason to support constants is so we can do FULL JOIN ON
+            * FALSE.)
+            */
+           if (!restrictinfo->clause || !IsA(restrictinfo->clause, Const))
+               have_nonmergeable_joinclause = true;
            continue;           /* not mergejoinable */
        }
 
index 21e0b9ad162bf130b16fbec812ac1ffd03f93331..bc6c0e5c45e2a7a7750ba440b840c9c5e8c9648f 100644 (file)
@@ -1396,6 +1396,7 @@ typedef struct NestLoopState
  *     Clauses            info for each mergejoinable clause
  *     JoinState          current "state" of join.  see execdefs.h
  *     ExtraMarks         true to issue extra Mark operations on inner scan
+ *     ConstFalseJoin     true if we have a constant-false joinqual
  *     FillOuter          true if should emit unjoined outer tuples anyway
  *     FillInner          true if should emit unjoined inner tuples anyway
  *     MatchedOuter       true if found a join match for current outer tuple
@@ -1419,6 +1420,7 @@ typedef struct MergeJoinState
    MergeJoinClause mj_Clauses; /* array of length mj_NumClauses */
    int         mj_JoinState;
    bool        mj_ExtraMarks;
+   bool        mj_ConstFalseJoin;
    bool        mj_FillOuter;
    bool        mj_FillInner;
    bool        mj_MatchedOuter;
index fed424cb33b99ae65df93f7fe6ff93a418a51c60..f2b346e50841bb05287dba3100321efb2fc6b4b9 100644 (file)
@@ -2443,3 +2443,51 @@ group by t1.q2 order by 1;
   4567890123456789 |     6
 (4 rows)
 
+--
+-- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE
+--
+select * from int4_tbl a full join int4_tbl b on true;
+     f1      |     f1      
+-------------+-------------
+           0 |           0
+           0 |      123456
+           0 |     -123456
+           0 |  2147483647
+           0 | -2147483647
+      123456 |           0
+      123456 |      123456
+      123456 |     -123456
+      123456 |  2147483647
+      123456 | -2147483647
+     -123456 |           0
+     -123456 |      123456
+     -123456 |     -123456
+     -123456 |  2147483647
+     -123456 | -2147483647
+  2147483647 |           0
+  2147483647 |      123456
+  2147483647 |     -123456
+  2147483647 |  2147483647
+  2147483647 | -2147483647
+ -2147483647 |           0
+ -2147483647 |      123456
+ -2147483647 |     -123456
+ -2147483647 |  2147483647
+ -2147483647 | -2147483647
+(25 rows)
+
+select * from int4_tbl a full join int4_tbl b on false;
+     f1      |     f1      
+-------------+-------------
+             |           0
+             |      123456
+             |     -123456
+             |  2147483647
+             | -2147483647
+           0 |            
+      123456 |            
+     -123456 |            
+  2147483647 |            
+ -2147483647 |            
+(10 rows)
+
index 46ff66c6909803437816825dd4d119117b09a5f3..977765551d5a020c6bda8f55b16539ff67212c3a 100644 (file)
@@ -561,3 +561,9 @@ from int8_tbl t1 left join
   (select q1, case when q2=1 then 1 else q2 end as q2 from int8_tbl) t2
   on (t1.q2 = t2.q1)
 group by t1.q2 order by 1;
+
+--
+-- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE
+--
+select * from int4_tbl a full join int4_tbl b on true;
+select * from int4_tbl a full join int4_tbl b on false;