]> granicus.if.org Git - postgresql/blobdiff - src/backend/executor/nodeSubplan.c
pgindent run for 8.2.
[postgresql] / src / backend / executor / nodeSubplan.c
index 7d40fe8b1ed9f70aad6398c8cba9f2a597bbdb24..2a5fd6d955da4eb18c37f5095272122e6817caff 100644 (file)
@@ -3,11 +3,11 @@
  * nodeSubplan.c
  *       routines to support subselects
  *
- * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.67 2005/03/16 21:38:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.80 2006/10/04 00:29:53 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
  */
 #include "postgres.h"
 
+#include <math.h>
+
 #include "access/heapam.h"
 #include "executor/executor.h"
 #include "executor/nodeSubplan.h"
 #include "nodes/makefuncs.h"
-#include "parser/parse_expr.h"
+#include "optimizer/clauses.h"
 #include "utils/array.h"
-#include "utils/datum.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 
 
 static Datum ExecHashSubPlan(SubPlanState *node,
@@ -85,15 +87,15 @@ ExecHashSubPlan(SubPlanState *node,
                elog(ERROR, "hashed subplan with direct correlation not supported");
 
        /*
-        * If first time through or we need to rescan the subplan, build the
-        * hash table.
+        * If first time through or we need to rescan the subplan, build the hash
+        * table.
         */
        if (node->hashtable == NULL || planstate->chgParam != NULL)
                buildSubPlanHash(node);
 
        /*
-        * The result for an empty subplan is always FALSE; no need to
-        * evaluate lefthand side.
+        * The result for an empty subplan is always FALSE; no need to evaluate
+        * lefthand side.
         */
        *isNull = false;
        if (!node->havehashrows && !node->havenullrows)
@@ -107,34 +109,32 @@ ExecHashSubPlan(SubPlanState *node,
        slot = ExecProject(node->projLeft, NULL);
 
        /*
-        * Note: because we are typically called in a per-tuple context, we
-        * have to explicitly clear the projected tuple before returning.
-        * Otherwise, we'll have a double-free situation: the per-tuple
-        * context will probably be reset before we're called again, and then
-        * the tuple slot will think it still needs to free the tuple.
+        * Note: because we are typically called in a per-tuple context, we have
+        * to explicitly clear the projected tuple before returning. Otherwise,
+        * we'll have a double-free situation: the per-tuple context will probably
+        * be reset before we're called again, and then the tuple slot will think
+        * it still needs to free the tuple.
         */
 
        /*
-        * Since the hashtable routines will use innerecontext's per-tuple
-        * memory as working memory, be sure to reset it for each tuple.
+        * Since the hashtable routines will use innerecontext's per-tuple memory
+        * as working memory, be sure to reset it for each tuple.
         */
        ResetExprContext(innerecontext);
 
        /*
-        * If the LHS is all non-null, probe for an exact match in the main
-        * hash table.  If we find one, the result is TRUE. Otherwise, scan
-        * the partly-null table to see if there are any rows that aren't
-        * provably unequal to the LHS; if so, the result is UNKNOWN.  (We
-        * skip that part if we don't care about UNKNOWN.) Otherwise, the
-        * result is FALSE.
+        * If the LHS is all non-null, probe for an exact match in the main hash
+        * table.  If we find one, the result is TRUE. Otherwise, scan the
+        * partly-null table to see if there are any rows that aren't provably
+        * unequal to the LHS; if so, the result is UNKNOWN.  (We skip that part
+        * if we don't care about UNKNOWN.) Otherwise, the result is FALSE.
         *
         * Note: the reason we can avoid a full scan of the main hash table is
-        * that the combining operators are assumed never to yield NULL when
-        * both inputs are non-null.  If they were to do so, we might need to
-        * produce UNKNOWN instead of FALSE because of an UNKNOWN result in
-        * comparing the LHS to some main-table entry --- which is a
-        * comparison we will not even make, unless there's a chance match of
-        * hash keys.
+        * that the combining operators are assumed never to yield NULL when both
+        * inputs are non-null.  If they were to do so, we might need to produce
+        * UNKNOWN instead of FALSE because of an UNKNOWN result in comparing the
+        * LHS to some main-table entry --- which is a comparison we will not even
+        * make, unless there's a chance match of hash keys.
         */
        if (slotNoNulls(slot))
        {
@@ -156,14 +156,14 @@ ExecHashSubPlan(SubPlanState *node,
        }
 
        /*
-        * When the LHS is partly or wholly NULL, we can never return TRUE. If
-        * we don't care about UNKNOWN, just return FALSE.  Otherwise, if the
-        * LHS is wholly NULL, immediately return UNKNOWN.      (Since the
-        * combining operators are strict, the result could only be FALSE if
-        * the sub-select were empty, but we already handled that case.)
-        * Otherwise, we must scan both the main and partly-null tables to see
-        * if there are any rows that aren't provably unequal to the LHS; if
-        * so, the result is UNKNOWN.  Otherwise, the result is FALSE.
+        * When the LHS is partly or wholly NULL, we can never return TRUE. If we
+        * don't care about UNKNOWN, just return FALSE.  Otherwise, if the LHS is
+        * wholly NULL, immediately return UNKNOWN.  (Since the combining
+        * operators are strict, the result could only be FALSE if the sub-select
+        * were empty, but we already handled that case.) Otherwise, we must scan
+        * both the main and partly-null tables to see if there are any rows that
+        * aren't provably unequal to the LHS; if so, the result is UNKNOWN.
+        * Otherwise, the result is FALSE.
         */
        if (node->hashnulls == NULL)
        {
@@ -206,7 +206,6 @@ ExecScanSubPlan(SubPlanState *node,
        SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
        PlanState  *planstate = node->planstate;
        SubLinkType subLinkType = subplan->subLinkType;
-       bool            useOr = subplan->useOr;
        MemoryContext oldcontext;
        TupleTableSlot *slot;
        Datum           result;
@@ -216,9 +215,9 @@ ExecScanSubPlan(SubPlanState *node,
        ArrayBuildState *astate = NULL;
 
        /*
-        * We are probably in a short-lived expression-evaluation context.
-        * Switch to the child plan's per-query context for manipulating its
-        * chgParam, calling ExecProcNode on it, etc.
+        * We are probably in a short-lived expression-evaluation context. Switch
+        * to the child plan's per-query context for manipulating its chgParam,
+        * calling ExecProcNode on it, etc.
         */
        oldcontext = MemoryContextSwitchTo(node->sub_estate->es_query_cxt);
 
@@ -244,24 +243,21 @@ ExecScanSubPlan(SubPlanState *node,
        ExecReScan(planstate, NULL);
 
        /*
-        * For all sublink types except EXPR_SUBLINK and ARRAY_SUBLINK, the
-        * result is boolean as are the results of the combining operators. We
-        * combine results within a tuple (if there are multiple columns)
-        * using OR semantics if "useOr" is true, AND semantics if not. We
-        * then combine results across tuples (if the subplan produces more
-        * than one) using OR semantics for ANY_SUBLINK or AND semantics for
-        * ALL_SUBLINK. (MULTIEXPR_SUBLINK doesn't allow multiple tuples from
-        * the subplan.) NULL results from the combining operators are handled
-        * according to the usual SQL semantics for OR and AND.  The result
-        * for no input tuples is FALSE for ANY_SUBLINK, TRUE for ALL_SUBLINK,
-        * NULL for MULTIEXPR_SUBLINK.
+        * For all sublink types except EXPR_SUBLINK and ARRAY_SUBLINK, the result
+        * is boolean as are the results of the combining operators. We combine
+        * results across tuples (if the subplan produces more than one) using OR
+        * semantics for ANY_SUBLINK or AND semantics for ALL_SUBLINK.
+        * (ROWCOMPARE_SUBLINK doesn't allow multiple tuples from the subplan.)
+        * NULL results from the combining operators are handled according to the
+        * usual SQL semantics for OR and AND.  The result for no input tuples is
+        * FALSE for ANY_SUBLINK, TRUE for ALL_SUBLINK, NULL for
+        * ROWCOMPARE_SUBLINK.
         *
         * For EXPR_SUBLINK we require the subplan to produce no more than one
-        * tuple, else an error is raised. For ARRAY_SUBLINK we allow the
-        * subplan to produce more than one tuple. In either case, if zero
-        * tuples are produced, we return NULL. Assuming we get a tuple, we
-        * just use its first column (there can be only one non-junk column in
-        * this case).
+        * tuple, else an error is raised. For ARRAY_SUBLINK we allow the subplan
+        * to produce more than one tuple. In either case, if zero tuples are
+        * produced, we return NULL. Assuming we get a tuple, we just use its
+        * first column (there can be only one non-junk column in this case).
         */
        result = BoolGetDatum(subLinkType == ALL_SUBLINK);
        *isNull = false;
@@ -271,9 +267,9 @@ ExecScanSubPlan(SubPlanState *node,
                 slot = ExecProcNode(planstate))
        {
                TupleDesc       tdesc = slot->tts_tupleDescriptor;
-               Datum           rowresult = BoolGetDatum(!useOr);
-               bool            rownull = false;
-               int                     col = 1;
+               Datum           rowresult;
+               bool            rownull;
+               int                     col;
                ListCell   *plst;
 
                if (subLinkType == EXISTS_SUBLINK)
@@ -293,12 +289,12 @@ ExecScanSubPlan(SubPlanState *node,
                        found = true;
 
                        /*
-                        * We need to copy the subplan's tuple in case the result is
-                        * of pass-by-ref type --- our return value will point into
-                        * this copied tuple!  Can't use the subplan's instance of the
-                        * tuple since it won't still be valid after next
-                        * ExecProcNode() call. node->curTuple keeps track of the
-                        * copied tuple for eventual freeing.
+                        * We need to copy the subplan's tuple in case the result is of
+                        * pass-by-ref type --- our return value will point into this
+                        * copied tuple!  Can't use the subplan's instance of the tuple
+                        * since it won't still be valid after next ExecProcNode() call.
+                        * node->curTuple keeps track of the copied tuple for eventual
+                        * freeing.
                         */
                        MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
                        if (node->curTuple)
@@ -306,7 +302,7 @@ ExecScanSubPlan(SubPlanState *node,
                        node->curTuple = ExecCopySlotTuple(slot);
                        MemoryContextSwitchTo(node->sub_estate->es_query_cxt);
 
-                       result = heap_getattr(node->curTuple, col, tdesc, isNull);
+                       result = heap_getattr(node->curTuple, 1, tdesc, isNull);
                        /* keep scanning subplan to make sure there's only one tuple */
                        continue;
                }
@@ -326,8 +322,8 @@ ExecScanSubPlan(SubPlanState *node,
                        continue;
                }
 
-               /* cannot allow multiple input tuples for MULTIEXPR sublink either */
-               if (subLinkType == MULTIEXPR_SUBLINK && found)
+               /* cannot allow multiple input tuples for ROWCOMPARE sublink either */
+               if (subLinkType == ROWCOMPARE_SUBLINK && found)
                        ereport(ERROR,
                                        (errcode(ERRCODE_CARDINALITY_VIOLATION),
                                         errmsg("more than one row returned by a subquery used as an expression")));
@@ -335,70 +331,25 @@ ExecScanSubPlan(SubPlanState *node,
                found = true;
 
                /*
-                * For ALL, ANY, and MULTIEXPR sublinks, iterate over combining
-                * operators for columns of tuple.
+                * For ALL, ANY, and ROWCOMPARE sublinks, load up the Params
+                * representing the columns of the sub-select, and then evaluate the
+                * combining expression.
                 */
-               Assert(list_length(node->exprs) == list_length(subplan->paramIds));
-
-               forboth(l, node->exprs, plst, subplan->paramIds)
+               col = 1;
+               foreach(plst, subplan->paramIds)
                {
-                       ExprState  *exprstate = (ExprState *) lfirst(l);
                        int                     paramid = lfirst_int(plst);
                        ParamExecData *prmdata;
-                       Datum           expresult;
-                       bool            expnull;
 
-                       /*
-                        * Load up the Param representing this column of the
-                        * sub-select.
-                        */
                        prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
                        Assert(prmdata->execPlan == NULL);
-                       prmdata->value = slot_getattr(slot, col,
-                                                                                 &(prmdata->isnull));
-
-                       /*
-                        * Now we can eval the combining operator for this column.
-                        */
-                       expresult = ExecEvalExprSwitchContext(exprstate, econtext,
-                                                                                                 &expnull, NULL);
-
-                       /*
-                        * Combine the result into the row result as appropriate.
-                        */
-                       if (col == 1)
-                       {
-                               rowresult = expresult;
-                               rownull = expnull;
-                       }
-                       else if (useOr)
-                       {
-                               /* combine within row per OR semantics */
-                               if (expnull)
-                                       rownull = true;
-                               else if (DatumGetBool(expresult))
-                               {
-                                       rowresult = BoolGetDatum(true);
-                                       rownull = false;
-                                       break;          /* needn't look at any more columns */
-                               }
-                       }
-                       else
-                       {
-                               /* combine within row per AND semantics */
-                               if (expnull)
-                                       rownull = true;
-                               else if (!DatumGetBool(expresult))
-                               {
-                                       rowresult = BoolGetDatum(false);
-                                       rownull = false;
-                                       break;          /* needn't look at any more columns */
-                               }
-                       }
-
+                       prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
                        col++;
                }
 
+               rowresult = ExecEvalExprSwitchContext(node->testexpr, econtext,
+                                                                                         &rownull, NULL);
+
                if (subLinkType == ANY_SUBLINK)
                {
                        /* combine across rows per OR semantics */
@@ -425,7 +376,7 @@ ExecScanSubPlan(SubPlanState *node,
                }
                else
                {
-                       /* must be MULTIEXPR_SUBLINK */
+                       /* must be ROWCOMPARE_SUBLINK */
                        result = rowresult;
                        *isNull = rownull;
                }
@@ -435,12 +386,12 @@ ExecScanSubPlan(SubPlanState *node,
        {
                /*
                 * deal with empty subplan result.      result/isNull were previously
-                * initialized correctly for all sublink types except EXPR, ARRAY,
-                * and MULTIEXPR; for those, return NULL.
+                * initialized correctly for all sublink types except EXPR, ARRAY, and
+                * ROWCOMPARE; for those, return NULL.
                 */
                if (subLinkType == EXPR_SUBLINK ||
                        subLinkType == ARRAY_SUBLINK ||
-                       subLinkType == MULTIEXPR_SUBLINK)
+                       subLinkType == ROWCOMPARE_SUBLINK)
                {
                        result = (Datum) 0;
                        *isNull = true;
@@ -466,7 +417,7 @@ buildSubPlanHash(SubPlanState *node)
 {
        SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
        PlanState  *planstate = node->planstate;
-       int                     ncols = list_length(node->exprs);
+       int                     ncols = list_length(subplan->paramIds);
        ExprContext *innerecontext = node->innerecontext;
        MemoryContext tempcxt = innerecontext->ecxt_per_tuple_memory;
        MemoryContext oldcontext;
@@ -474,19 +425,18 @@ buildSubPlanHash(SubPlanState *node)
        TupleTableSlot *slot;
 
        Assert(subplan->subLinkType == ANY_SUBLINK);
-       Assert(!subplan->useOr);
 
        /*
-        * If we already had any hash tables, destroy 'em; then create empty
-        * hash table(s).
+        * If we already had any hash tables, destroy 'em; then create empty hash
+        * table(s).
         *
         * If we need to distinguish accurately between FALSE and UNKNOWN (i.e.,
-        * NULL) results of the IN operation, then we have to store subplan
-        * output rows that are partly or wholly NULL.  We store such rows in
-        * a separate hash table that we expect will be much smaller than the
-        * main table.  (We can use hashing to eliminate partly-null rows that
-        * are not distinct.  We keep them separate to minimize the cost of
-        * the inevitable full-table searches; see findPartialMatch.)
+        * NULL) results of the IN operation, then we have to store subplan output
+        * rows that are partly or wholly NULL.  We store such rows in a separate
+        * hash table that we expect will be much smaller than the main table. (We
+        * can use hashing to eliminate partly-null rows that are not distinct. We
+        * keep them separate to minimize the cost of the inevitable full-table
+        * searches; see findPartialMatch.)
         *
         * If it's not necessary to distinguish FALSE and UNKNOWN, then we don't
         * need to store subplan output rows that contain NULL.
@@ -531,9 +481,8 @@ buildSubPlanHash(SubPlanState *node)
        }
 
        /*
-        * We are probably in a short-lived expression-evaluation context.
-        * Switch to the child plan's per-query context for calling
-        * ExecProcNode.
+        * We are probably in a short-lived expression-evaluation context. Switch
+        * to the child plan's per-query context for calling ExecProcNode.
         */
        oldcontext = MemoryContextSwitchTo(node->sub_estate->es_query_cxt);
 
@@ -543,9 +492,8 @@ buildSubPlanHash(SubPlanState *node)
        ExecReScan(planstate, NULL);
 
        /*
-        * Scan the subplan and load the hash table(s).  Note that when there
-        * are duplicate rows coming out of the sub-select, only one copy is
-        * stored.
+        * Scan the subplan and load the hash table(s).  Note that when there are
+        * duplicate rows coming out of the sub-select, only one copy is stored.
         */
        for (slot = ExecProcNode(planstate);
                 !TupIsNull(slot);
@@ -556,8 +504,8 @@ buildSubPlanHash(SubPlanState *node)
                bool            isnew;
 
                /*
-                * Load up the Params representing the raw sub-select outputs,
-                * then form the projection tuple to store in the hashtable.
+                * Load up the Params representing the raw sub-select outputs, then
+                * form the projection tuple to store in the hashtable.
                 */
                foreach(plst, subplan->paramIds)
                {
@@ -587,18 +535,18 @@ buildSubPlanHash(SubPlanState *node)
                }
 
                /*
-                * Reset innerecontext after each inner tuple to free any memory
-                * used in hash computation or comparison routines.
+                * Reset innerecontext after each inner tuple to free any memory used
+                * in hash computation or comparison routines.
                 */
                ResetExprContext(innerecontext);
        }
 
        /*
-        * Since the projected tuples are in the sub-query's context and not
-        * the main context, we'd better clear the tuple slot before there's
-        * any chance of a reset of the sub-query's context.  Else we will
-        * have the potential for a double free attempt.  (XXX possibly
-        * no longer needed, but can't hurt.)
+        * Since the projected tuples are in the sub-query's context and not the
+        * main context, we'd better clear the tuple slot before there's any
+        * chance of a reset of the sub-query's context.  Else we will have the
+        * potential for a double free attempt.  (XXX possibly no longer needed,
+        * but can't hurt.)
         */
        ExecClearTuple(node->projRight->pi_slot);
 
@@ -624,8 +572,7 @@ findPartialMatch(TupleHashTable hashtable, TupleTableSlot *slot)
        ResetTupleHashIterator(hashtable, &hashiter);
        while ((entry = ScanTupleHashTable(&hashiter)) != NULL)
        {
-               ExecStoreTuple(entry->firstTuple, hashtable->tableslot,
-                                          InvalidBuffer, false);
+               ExecStoreMinimalTuple(entry->firstTuple, hashtable->tableslot, false);
                if (!execTuplesUnequal(hashtable->tableslot, slot,
                                                           numCols, keyColIdx,
                                                           hashtable->eqfunctions,
@@ -677,10 +624,14 @@ slotNoNulls(TupleTableSlot *slot)
 
 /* ----------------------------------------------------------------
  *             ExecInitSubPlan
+ *
+ * Note: the eflags are those passed to the parent plan node of this
+ * subplan; they don't directly describe the execution conditions the
+ * subplan will face.
  * ----------------------------------------------------------------
  */
 void
-ExecInitSubPlan(SubPlanState *node, EState *estate)
+ExecInitSubPlan(SubPlanState *node, EState *estate, int eflags)
 {
        SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
        EState     *sp_estate;
@@ -709,10 +660,10 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
        /*
         * create an EState for the subplan
         *
-        * The subquery needs its own EState because it has its own rangetable.
-        * It shares our Param ID space, however.  XXX if rangetable access
-        * were done differently, the subquery could share our EState, which
-        * would eliminate some thrashing about in this module...
+        * The subquery needs its own EState because it has its own rangetable. It
+        * shares our Param ID space, however.  XXX if rangetable access were done
+        * differently, the subquery could share our EState, which would eliminate
+        * some thrashing about in this module...
         */
        sp_estate = CreateExecutorState();
        node->sub_estate = sp_estate;
@@ -730,21 +681,28 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
 
        /*
         * Start up the subplan (this is a very cut-down form of InitPlan())
+        *
+        * The subplan will never need to do BACKWARD scan or MARK/RESTORE. If it
+        * is a parameterless subplan (not initplan), we suggest that it be
+        * prepared to handle REWIND efficiently; otherwise there is no need.
         */
-       node->planstate = ExecInitNode(subplan->plan, sp_estate);
+       eflags &= EXEC_FLAG_EXPLAIN_ONLY;
+       if (subplan->parParam == NIL && subplan->setParam == NIL)
+               eflags |= EXEC_FLAG_REWIND;
+
+       node->planstate = ExecInitNode(subplan->plan, sp_estate, eflags);
 
        node->needShutdown = true;      /* now we need to shutdown the subplan */
 
        MemoryContextSwitchTo(oldcontext);
 
        /*
-        * If this plan is un-correlated or undirect correlated one and want
-        * to set params for parent plan then mark parameters as needing
-        * evaluation.
+        * If this plan is un-correlated or undirect correlated one and want to
+        * set params for parent plan then mark parameters as needing evaluation.
         *
         * Note that in the case of un-correlated subqueries we don't care about
-        * setting parent->chgParam here: indices take care about it, for
-        * others - it doesn't matter...
+        * setting parent->chgParam here: indices take care about it, for others -
+        * it doesn't matter...
         */
        if (subplan->setParam != NIL)
        {
@@ -760,8 +718,8 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
        }
 
        /*
-        * If we are going to hash the subquery output, initialize relevant
-        * stuff.  (We don't create the hashtable until needed, though.)
+        * If we are going to hash the subquery output, initialize relevant stuff.
+        * (We don't create the hashtable until needed, though.)
         */
        if (subplan->useHashTable)
        {
@@ -770,11 +728,12 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
                TupleDesc       tupDesc;
                TupleTable      tupTable;
                TupleTableSlot *slot;
-               List       *lefttlist,
+               List       *oplist,
+                                  *lefttlist,
                                   *righttlist,
                                   *leftptlist,
                                   *rightptlist;
-               ListCell   *lexpr;
+               ListCell   *l;
 
                /* We need a memory context to hold the hash table(s) */
                node->tablecxt =
@@ -786,34 +745,53 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
                /* and a short-lived exprcontext for function evaluation */
                node->innerecontext = CreateExprContext(estate);
                /* Silly little array of column numbers 1..n */
-               ncols = list_length(node->exprs);
+               ncols = list_length(subplan->paramIds);
                node->keyColIdx = (AttrNumber *) palloc(ncols * sizeof(AttrNumber));
                for (i = 0; i < ncols; i++)
                        node->keyColIdx[i] = i + 1;
 
                /*
                 * We use ExecProject to evaluate the lefthand and righthand
-                * expression lists and form tuples.  (You might think that we
-                * could use the sub-select's output tuples directly, but that is
-                * not the case if we had to insert any run-time coercions of the
-                * sub-select's output datatypes; anyway this avoids storing any
-                * resjunk columns that might be in the sub-select's output.) Run
-                * through the combining expressions to build tlists for the
-                * lefthand and righthand sides.  We need both the ExprState list
-                * (for ExecProject) and the underlying parse Exprs (for
-                * ExecTypeFromTL).
+                * expression lists and form tuples.  (You might think that we could
+                * use the sub-select's output tuples directly, but that is not the
+                * case if we had to insert any run-time coercions of the sub-select's
+                * output datatypes; anyway this avoids storing any resjunk columns
+                * that might be in the sub-select's output.) Run through the
+                * combining expressions to build tlists for the lefthand and
+                * righthand sides.  We need both the ExprState list (for ExecProject)
+                * and the underlying parse Exprs (for ExecTypeFromTL).
                 *
                 * We also extract the combining operators themselves to initialize
                 * the equality and hashing functions for the hash tables.
                 */
+               if (IsA(node->testexpr->expr, OpExpr))
+               {
+                       /* single combining operator */
+                       oplist = list_make1(node->testexpr);
+               }
+               else if (and_clause((Node *) node->testexpr->expr))
+               {
+                       /* multiple combining operators */
+                       Assert(IsA(node->testexpr, BoolExprState));
+                       oplist = ((BoolExprState *) node->testexpr)->args;
+               }
+               else
+               {
+                       /* shouldn't see anything else in a hashable subplan */
+                       elog(ERROR, "unrecognized testexpr type: %d",
+                                (int) nodeTag(node->testexpr->expr));
+                       oplist = NIL;           /* keep compiler quiet */
+               }
+               Assert(list_length(oplist) == ncols);
+
                lefttlist = righttlist = NIL;
                leftptlist = rightptlist = NIL;
                node->eqfunctions = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo));
                node->hashfunctions = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo));
                i = 1;
-               foreach(lexpr, node->exprs)
+               foreach(l, oplist)
                {
-                       FuncExprState *fstate = (FuncExprState *) lfirst(lexpr);
+                       FuncExprState *fstate = (FuncExprState *) lfirst(l);
                        OpExpr     *opexpr = (OpExpr *) fstate->xprstate.expr;
                        ExprState  *exstate;
                        Expr       *expr;
@@ -828,12 +806,10 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
                        /* Process lefthand argument */
                        exstate = (ExprState *) linitial(fstate->args);
                        expr = exstate->expr;
-                       tle = makeTargetEntry(makeResdom(i,
-                                                                                        exprType((Node *) expr),
-                                                                                        exprTypmod((Node *) expr),
-                                                                                        NULL,
-                                                                                        false),
-                                                                 expr);
+                       tle = makeTargetEntry(expr,
+                                                                 i,
+                                                                 NULL,
+                                                                 false);
                        tlestate = makeNode(GenericExprState);
                        tlestate->xprstate.expr = (Expr *) tle;
                        tlestate->xprstate.evalfunc = NULL;
@@ -844,12 +820,10 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
                        /* Process righthand argument */
                        exstate = (ExprState *) lsecond(fstate->args);
                        expr = exstate->expr;
-                       tle = makeTargetEntry(makeResdom(i,
-                                                                                        exprType((Node *) expr),
-                                                                                        exprTypmod((Node *) expr),
-                                                                                        NULL,
-                                                                                        false),
-                                                                 expr);
+                       tle = makeTargetEntry(expr,
+                                                                 i,
+                                                                 NULL,
+                                                                 false);
                        tlestate = makeNode(GenericExprState);
                        tlestate->xprstate.expr = (Expr *) tle;
                        tlestate->xprstate.evalfunc = NULL;
@@ -872,32 +846,32 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
                }
 
                /*
-                * Create a tupletable to hold these tuples.  (Note: we never
-                * bother to free the tupletable explicitly; that's okay because
-                * it will never store raw disk tuples that might have associated
-                * buffer pins.  The only resource involved is memory, which will
-                * be cleaned up by freeing the query context.)
+                * Create a tupletable to hold these tuples.  (Note: we never bother
+                * to free the tupletable explicitly; that's okay because it will
+                * never store raw disk tuples that might have associated buffer pins.
+                * The only resource involved is memory, which will be cleaned up by
+                * freeing the query context.)
                 */
                tupTable = ExecCreateTupleTable(2);
 
                /*
-                * Construct tupdescs, slots and projection nodes for left and
-                * right sides.  The lefthand expressions will be evaluated in the
-                * parent plan node's exprcontext, which we don't have access to
-                * here.  Fortunately we can just pass NULL for now and fill it in
-                * later (hack alert!).  The righthand expressions will be
-                * evaluated in our own innerecontext.
+                * Construct tupdescs, slots and projection nodes for left and right
+                * sides.  The lefthand expressions will be evaluated in the parent
+                * plan node's exprcontext, which we don't have access to here.
+                * Fortunately we can just pass NULL for now and fill it in later
+                * (hack alert!).  The righthand expressions will be evaluated in our
+                * own innerecontext.
                 */
                tupDesc = ExecTypeFromTL(leftptlist, false);
                slot = ExecAllocTableSlot(tupTable);
-               ExecSetSlotDescriptor(slot, tupDesc, true);
+               ExecSetSlotDescriptor(slot, tupDesc);
                node->projLeft = ExecBuildProjectionInfo(lefttlist,
                                                                                                 NULL,
                                                                                                 slot);
 
                tupDesc = ExecTypeFromTL(rightptlist, false);
                slot = ExecAllocTableSlot(tupTable);
-               ExecSetSlotDescriptor(slot, tupDesc, true);
+               ExecSetSlotDescriptor(slot, tupDesc);
                node->projRight = ExecBuildProjectionInfo(righttlist,
                                                                                                  node->innerecontext,
                                                                                                  slot);
@@ -978,7 +952,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 
                if (found &&
                        (subLinkType == EXPR_SUBLINK ||
-                        subLinkType == MULTIEXPR_SUBLINK))
+                        subLinkType == ROWCOMPARE_SUBLINK))
                        ereport(ERROR,
                                        (errcode(ERRCODE_CARDINALITY_VIOLATION),
                                         errmsg("more than one row returned by a subquery used as an expression")));
@@ -986,11 +960,10 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
                found = true;
 
                /*
-                * We need to copy the subplan's tuple into our own context, in
-                * case any of the params are pass-by-ref type --- the pointers
-                * stored in the param structs will point at this copied tuple!
-                * node->curTuple keeps track of the copied tuple for eventual
-                * freeing.
+                * We need to copy the subplan's tuple into our own context, in case
+                * any of the params are pass-by-ref type --- the pointers stored in
+                * the param structs will point at this copied tuple! node->curTuple
+                * keeps track of the copied tuple for eventual freeing.
                 */
                MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
                if (node->curTuple)