*
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.101 2003/03/22 17:11:25 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.102 2003/04/24 23:43:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
                      RangeTblEntry *rte);
 static RelOptInfo *make_one_rel_by_joins(Query *root, int levels_needed,
                      List *initial_rels);
-static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery);
-static bool recurse_pushdown_safe(Node *setOp, Query *topquery);
-static bool qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual);
+static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery,
+                                     bool *differentTypes);
+static bool recurse_pushdown_safe(Node *setOp, Query *topquery,
+                                 bool *differentTypes);
+static void compare_tlist_datatypes(List *tlist, List *colTypes,
+                                   bool *differentTypes);
+static bool qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
+                                 bool *differentTypes);
 static void subquery_push_qual(Query *subquery, Index rti, Node *qual);
 static void recurse_push_qual(Node *setOp, Query *topquery,
                  Index rti, Node *qual);
                      Index rti, RangeTblEntry *rte)
 {
    Query      *subquery = rte->subquery;
+   bool       *differentTypes;
    List       *pathkeys;
 
+   /* We need a workspace for keeping track of set-op type coercions */
+   differentTypes = (bool *)
+       palloc0((length(subquery->targetList) + 1) * sizeof(bool));
+
    /*
     * If there are any restriction clauses that have been attached to the
     * subquery relation, consider pushing them down to become HAVING
     * push down a pushable qual, because it'd result in a worse plan?
     */
    if (rel->baserestrictinfo != NIL &&
-       subquery_is_pushdown_safe(subquery, subquery))
+       subquery_is_pushdown_safe(subquery, subquery, differentTypes))
    {
        /* OK to consider pushing down individual quals */
        List       *upperrestrictlist = NIL;
            RestrictInfo *rinfo = (RestrictInfo *) lfirst(lst);
            Node       *clause = (Node *) rinfo->clause;
 
-           if (qual_is_pushdown_safe(subquery, rti, clause))
+           if (qual_is_pushdown_safe(subquery, rti, clause, differentTypes))
            {
                /* Push it down */
                subquery_push_qual(subquery, rti, clause);
        rel->baserestrictinfo = upperrestrictlist;
    }
 
+   pfree(differentTypes);
+
    /* Generate the plan for the subquery */
    rel->subplan = subquery_planner(subquery, 0.0 /* default case */ );
 
  * since that could change the set of rows returned.
  *
  * 2. If the subquery contains EXCEPT or EXCEPT ALL set ops we cannot push
- * quals into it, because that would change the results.  For subqueries
- * using UNION/UNION ALL/INTERSECT/INTERSECT ALL, we can push the quals
- * into each component query, so long as all the component queries share
- * identical output types. (That restriction could probably be relaxed,
- * but it would take much more code to include type coercion code into
- * the quals, and I'm also concerned about possible semantic gotchas.)
+ * quals into it, because that would change the results.
+ *
+ * 3. For subqueries using UNION/UNION ALL/INTERSECT/INTERSECT ALL, we can
+ * push quals into each component query, but the quals can only reference
+ * subquery columns that suffer no type coercions in the set operation.
+ * Otherwise there are possible semantic gotchas.  So, we check the
+ * component queries to see if any of them have different output types;
+ * differentTypes[k] is set true if column k has different type in any
+ * component.
  */
 static bool
-subquery_is_pushdown_safe(Query *subquery, Query *topquery)
+subquery_is_pushdown_safe(Query *subquery, Query *topquery,
+                         bool *differentTypes)
 {
    SetOperationStmt *topop;
 
    {
        /* Top level, so check any component queries */
        if (subquery->setOperations != NULL)
-           if (!recurse_pushdown_safe(subquery->setOperations, topquery))
+           if (!recurse_pushdown_safe(subquery->setOperations, topquery,
+                                      differentTypes))
                return false;
    }
    else
        /* Setop component must not have more components (too weird) */
        if (subquery->setOperations != NULL)
            return false;
-       /* Setop component output types must match top level */
+       /* Check whether setop component output types match top level */
        topop = (SetOperationStmt *) topquery->setOperations;
        Assert(topop && IsA(topop, SetOperationStmt));
-       if (!tlist_same_datatypes(subquery->targetList,
-                                 topop->colTypes,
-                                 true))
-           return false;
-
+       compare_tlist_datatypes(subquery->targetList,
+                               topop->colTypes,
+                               differentTypes);
    }
    return true;
 }
  * Helper routine to recurse through setOperations tree
  */
 static bool
-recurse_pushdown_safe(Node *setOp, Query *topquery)
+recurse_pushdown_safe(Node *setOp, Query *topquery,
+                     bool *differentTypes)
 {
    if (IsA(setOp, RangeTblRef))
    {
        Query      *subquery = rte->subquery;
 
        Assert(subquery != NULL);
-       return subquery_is_pushdown_safe(subquery, topquery);
+       return subquery_is_pushdown_safe(subquery, topquery, differentTypes);
    }
    else if (IsA(setOp, SetOperationStmt))
    {
        if (op->op == SETOP_EXCEPT)
            return false;
        /* Else recurse */
-       if (!recurse_pushdown_safe(op->larg, topquery))
+       if (!recurse_pushdown_safe(op->larg, topquery, differentTypes))
            return false;
-       if (!recurse_pushdown_safe(op->rarg, topquery))
+       if (!recurse_pushdown_safe(op->rarg, topquery, differentTypes))
            return false;
    }
    else
    return true;
 }
 
+/*
+ * Compare tlist's datatypes against the list of set-operation result types.
+ * For any items that are different, mark the appropriate element of
+ * differentTypes[] to show that this column will have type conversions.
+ */
+static void
+compare_tlist_datatypes(List *tlist, List *colTypes,
+                       bool *differentTypes)
+{
+   List       *i;
+
+   foreach(i, tlist)
+   {
+       TargetEntry *tle = (TargetEntry *) lfirst(i);
+
+       if (tle->resdom->resjunk)
+           continue;           /* ignore resjunk columns */
+       if (colTypes == NIL)
+           elog(ERROR, "wrong number of tlist entries");
+       if (tle->resdom->restype != lfirsto(colTypes))
+           differentTypes[tle->resdom->resno] = true;
+       colTypes = lnext(colTypes);
+   }
+   if (colTypes != NIL)
+       elog(ERROR, "wrong number of tlist entries");
+}
+
 /*
  * qual_is_pushdown_safe - is a particular qual safe to push down?
  *
  * it will work correctly: sublinks will already have been transformed into
  * subplans in the qual, but not in the subquery).
  *
- * 2. If the subquery uses DISTINCT ON, we must not push down any quals that
+ * 2. The qual must not refer to any subquery output columns that were
+ * found to have inconsistent types across a set operation tree by
+ * subquery_is_pushdown_safe().
+ *
+ * 3. If the subquery uses DISTINCT ON, we must not push down any quals that
  * refer to non-DISTINCT output columns, because that could change the set
  * of rows returned.  This condition is vacuous for DISTINCT, because then
  * there are no non-DISTINCT output columns, but unfortunately it's fairly
  * parsetree representation.  It's cheaper to just make sure all the Vars
  * in the qual refer to DISTINCT columns.
  *
- * 3. We must not push down any quals that refer to subselect outputs that
+ * 4. We must not push down any quals that refer to subselect outputs that
  * return sets, else we'd introduce functions-returning-sets into the
  * subquery's WHERE/HAVING quals.
  */
 static bool
-qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual)
+qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
+                     bool *differentTypes)
 {
    bool        safe = true;
    List       *vars;
            continue;
        tested = bms_add_member(tested, var->varattno);
 
+       /* Check point 2 */
+       if (differentTypes[var->varattno])
+       {
+           safe = false;
+           break;
+       }
+
+       /* Must find the tlist element referenced by the Var */
        foreach(tl, subquery->targetList)
        {
            tle = (TargetEntry *) lfirst(tl);
        Assert(tl != NIL);
        Assert(!tle->resdom->resjunk);
 
-       /* If subquery uses DISTINCT or DISTINCT ON, check point 2 */
+       /* If subquery uses DISTINCT or DISTINCT ON, check point 3 */
        if (subquery->distinctClause != NIL &&
            !targetIsInSortList(tle, subquery->distinctClause))
        {
            break;
        }
 
-       /* Refuse functions returning sets (point 3) */
+       /* Refuse functions returning sets (point 4) */
        if (expression_returns_set((Node *) tle->expr))
        {
            safe = false;