First cut at implementing IN (and NOT IN) via hashtables. There is
authorTom Lane
Sun, 12 Jan 2003 04:03:34 +0000 (04:03 +0000)
committerTom Lane
Sun, 12 Jan 2003 04:03:34 +0000 (04:03 +0000)
more to be done yet, but this is a good start.

src/backend/executor/execGrouping.c
src/backend/executor/execQual.c
src/backend/executor/execUtils.c
src/backend/executor/nodeSubplan.c
src/backend/optimizer/plan/subselect.c
src/include/executor/executor.h
src/include/nodes/execnodes.h

index e3f7720ca75a35e7a6c4b46c322a126b85414790..0d4d5ed20f38896769bc4a4ff5c6cdb73ddcc8ab 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/execGrouping.c,v 1.1 2003/01/10 23:54:24 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/execGrouping.c,v 1.2 2003/01/12 04:03:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 /*****************************************************************************
  *     Utility routines for grouping tuples together
- *
- * These routines actually implement SQL's notion of "distinct/not distinct".
- * Two tuples match if they are not distinct in all the compared columns,
- * i.e., the column values are either both null, or both non-null and equal.
  *****************************************************************************/
 
 /*
  * execTuplesMatch
  *     Return true if two tuples match in all the indicated fields.
- *     This is used to detect group boundaries in nodeGroup and nodeAgg,
- *     and to decide whether two tuples are distinct or not in nodeUnique.
+ *
+ * This actually implements SQL's notion of "not distinct".  Two nulls
+ * match, a null and a not-null don't match.
  *
  * tuple1, tuple2: the tuples to compare
  * tupdesc: tuple descriptor applying to both tuples
@@ -112,11 +109,88 @@ execTuplesMatch(HeapTuple tuple1,
    return result;
 }
 
+/*
+ * execTuplesUnequal
+ *     Return true if two tuples are definitely unequal in the indicated
+ *     fields.
+ *
+ * Nulls are neither equal nor unequal to anything else.  A true result
+ * is obtained only if there are non-null fields that compare not-equal.
+ *
+ * Parameters are identical to execTuplesMatch.
+ */
+bool
+execTuplesUnequal(HeapTuple tuple1,
+                 HeapTuple tuple2,
+                 TupleDesc tupdesc,
+                 int numCols,
+                 AttrNumber *matchColIdx,
+                 FmgrInfo *eqfunctions,
+                 MemoryContext evalContext)
+{
+   MemoryContext oldContext;
+   bool        result;
+   int         i;
+
+   /* Reset and switch into the temp context. */
+   MemoryContextReset(evalContext);
+   oldContext = MemoryContextSwitchTo(evalContext);
+
+   /*
+    * We cannot report a match without checking all the fields, but we
+    * can report a non-match as soon as we find unequal fields.  So,
+    * start comparing at the last field (least significant sort key).
+    * That's the most likely to be different if we are dealing with
+    * sorted input.
+    */
+   result = false;
+
+   for (i = numCols; --i >= 0;)
+   {
+       AttrNumber  att = matchColIdx[i];
+       Datum       attr1,
+                   attr2;
+       bool        isNull1,
+                   isNull2;
+
+       attr1 = heap_getattr(tuple1,
+                            att,
+                            tupdesc,
+                            &isNull1);
+
+       if (isNull1)
+           continue;           /* can't prove anything here */
+
+       attr2 = heap_getattr(tuple2,
+                            att,
+                            tupdesc,
+                            &isNull2);
+
+       if (isNull2)
+           continue;           /* can't prove anything here */
+
+       /* Apply the type-specific equality function */
+
+       if (!DatumGetBool(FunctionCall2(&eqfunctions[i],
+                                       attr1, attr2)))
+       {
+           result = true;      /* they are unequal */
+           break;
+       }
+   }
+
+   MemoryContextSwitchTo(oldContext);
+
+   return result;
+}
+
 
 /*
  * execTuplesMatchPrepare
- *     Look up the equality functions needed for execTuplesMatch.
- *     The result is a palloc'd array.
+ *     Look up the equality functions needed for execTuplesMatch or
+ *     execTuplesUnequal.
+ *
+ * The result is a palloc'd array.
  */
 FmgrInfo *
 execTuplesMatchPrepare(TupleDesc tupdesc,
@@ -266,8 +340,13 @@ BuildTupleHashTable(int numCols, AttrNumber *keyColIdx,
  * Find or create a hashtable entry for the tuple group containing the
  * given tuple.
  *
- * On return, *isnew is true if the entry is newly created, false if it
- * existed already.  Any extra space in a new entry has been zeroed.
+ * If isnew is NULL, we do not create new entries; we return NULL if no
+ * match is found.
+ *
+ * If isnew isn't NULL, then a new entry is created if no existing entry
+ * matches.  On return, *isnew is true if the entry is newly created,
+ * false if it existed already.  Any extra space in a new entry has been
+ * zeroed.
  */
 TupleHashEntry
 LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot *slot,
@@ -318,26 +397,30 @@ LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot *slot,
                            hashtable->eqfunctions,
                            hashtable->tempcxt))
        {
+           if (isnew)
+               *isnew = false;
            MemoryContextSwitchTo(oldContext);
-           *isnew = false;
            return entry;
        }
    }
 
-   /* Not there, so build a new one */
-   MemoryContextSwitchTo(hashtable->tablecxt);
+   /* Not there, so build a new one if requested */
+   if (isnew)
+   {
+       MemoryContextSwitchTo(hashtable->tablecxt);
 
-   entry = (TupleHashEntry) palloc0(hashtable->entrysize);
+       entry = (TupleHashEntry) palloc0(hashtable->entrysize);
 
-   entry->hashkey = hashkey;
-   entry->firstTuple = heap_copytuple(tuple);
+       entry->hashkey = hashkey;
+       entry->firstTuple = heap_copytuple(tuple);
 
-   entry->next = hashtable->buckets[bucketno];
-   hashtable->buckets[bucketno] = entry;
+       entry->next = hashtable->buckets[bucketno];
+       hashtable->buckets[bucketno] = entry;
 
-   MemoryContextSwitchTo(oldContext);
+       *isnew = true;
+   }
 
-   *isnew = true;
+   MemoryContextSwitchTo(oldContext);
 
    return entry;
 }
index 49986de2748f2952a80f37bd4b8fbf2a499de913..c13e1e1e4d8c078db72b2868f68bbadf7d281583 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.122 2003/01/10 21:08:07 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.123 2003/01/12 04:03:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2324,8 +2324,13 @@ ExecCleanTargetListLength(List *targetlist)
 /* ----------------------------------------------------------------
  *     ExecTargetList
  *
- *     Evaluates a targetlist with respect to the current
- *     expression context and return a tuple.
+ *     Evaluates a targetlist with respect to the given
+ *     expression context and returns a tuple.
+ *
+ * The caller must pass workspace for the values and nulls arrays
+ * as well as the itemIsDone array.  This convention saves palloc'ing
+ * workspace on each call, and some callers may find it useful to examine
+ * the values array directly.
  *
  * As with ExecEvalExpr, the caller should pass isDone = NULL if not
  * prepared to deal with sets of result tuples.  Otherwise, a return
@@ -2335,21 +2340,15 @@ ExecCleanTargetListLength(List *targetlist)
  */
 static HeapTuple
 ExecTargetList(List *targetlist,
-              int nodomains,
               TupleDesc targettype,
-              Datum *values,
               ExprContext *econtext,
+              Datum *values,
+              char *nulls,
+              ExprDoneCond *itemIsDone,
               ExprDoneCond *isDone)
 {
    MemoryContext oldContext;
-
-#define NPREALLOCDOMAINS 64
-   char        nullsArray[NPREALLOCDOMAINS];
-   ExprDoneCond itemIsDoneArray[NPREALLOCDOMAINS];
-   char       *nulls;
-   ExprDoneCond *itemIsDone;
    List       *tl;
-   HeapTuple   newTuple;
    bool        isNull;
    bool        haveDoneSets;
    static struct tupleDesc NullTupleDesc;      /* we assume this inits to
@@ -2378,31 +2377,9 @@ ExecTargetList(List *targetlist,
    if (targettype == NULL)
        targettype = &NullTupleDesc;
 
-   /*
-    * allocate an array of char's to hold the "null" information only if
-    * we have a really large targetlist.  otherwise we use the stack.
-    *
-    * We also allocate another array that holds the isDone status for each
-    * targetlist item. The isDone status is needed so that we can iterate,
-    * generating multiple tuples, when one or more tlist items return
-    * sets.  (We expect the caller to call us again if we return
-    * isDone = ExprMultipleResult.)
-    */
-   if (nodomains > NPREALLOCDOMAINS)
-   {
-       nulls = (char *) palloc(nodomains * sizeof(char));
-       itemIsDone = (ExprDoneCond *) palloc(nodomains * sizeof(ExprDoneCond));
-   }
-   else
-   {
-       nulls = nullsArray;
-       itemIsDone = itemIsDoneArray;
-   }
-
    /*
     * evaluate all the expressions in the target list
     */
-
    if (isDone)
        *isDone = ExprSingleResult;     /* until proven otherwise */
 
@@ -2451,8 +2428,7 @@ ExecTargetList(List *targetlist,
             */
            *isDone = ExprEndResult;
            MemoryContextSwitchTo(oldContext);
-           newTuple = NULL;
-           goto exit;
+           return NULL;
        }
        else
        {
@@ -2511,8 +2487,7 @@ ExecTargetList(List *targetlist,
                }
 
                MemoryContextSwitchTo(oldContext);
-               newTuple = NULL;
-               goto exit;
+               return NULL;
            }
        }
    }
@@ -2522,20 +2497,7 @@ ExecTargetList(List *targetlist,
     */
    MemoryContextSwitchTo(oldContext);
 
-   newTuple = (HeapTuple) heap_formtuple(targettype, values, nulls);
-
-exit:
-
-   /*
-    * free the status arrays if we palloc'd them
-    */
-   if (nodomains > NPREALLOCDOMAINS)
-   {
-       pfree(nulls);
-       pfree(itemIsDone);
-   }
-
-   return newTuple;
+   return heap_formtuple(targettype, values, nulls);
 }
 
 /* ----------------------------------------------------------------
@@ -2555,11 +2517,7 @@ TupleTableSlot *
 ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone)
 {
    TupleTableSlot *slot;
-   List       *targetlist;
-   int         len;
    TupleDesc   tupType;
-   Datum      *tupValue;
-   ExprContext *econtext;
    HeapTuple   newTuple;
 
    /*
@@ -2572,21 +2530,17 @@ ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone)
     * get the projection info we want
     */
    slot = projInfo->pi_slot;
-   targetlist = projInfo->pi_targetlist;
-   len = projInfo->pi_len;
    tupType = slot->ttc_tupleDescriptor;
 
-   tupValue = projInfo->pi_tupValue;
-   econtext = projInfo->pi_exprContext;
-
    /*
     * form a new result tuple (if possible --- result can be NULL)
     */
-   newTuple = ExecTargetList(targetlist,
-                             len,
+   newTuple = ExecTargetList(projInfo->pi_targetlist,
                              tupType,
-                             tupValue,
-                             econtext,
+                             projInfo->pi_exprContext,
+                             projInfo->pi_tupValues,
+                             projInfo->pi_tupNulls,
+                             projInfo->pi_itemIsDone,
                              isDone);
 
    /*
index 054ec703866e7454edb26687a8482131ce4b9358..63eede2280234d613b4cbb1ef595fd4c65e17dea 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.94 2002/12/18 00:14:47 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.95 2003/01/12 04:03:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -476,28 +476,50 @@ ExecGetResultType(PlanState *planstate)
 }
 
 /* ----------------
- *     ExecAssignProjectionInfo
-         forms the projection information from the node's targetlist
+ *     ExecBuildProjectionInfo
+ *
+ * Build a ProjectionInfo node for evaluating the given tlist in the given
+ * econtext, and storing the result into the tuple slot.  (Caller must have
+ * ensured that tuple slot has a descriptor matching the tlist!)  Note that
+ * the given tlist should be a list of ExprState nodes, not Expr nodes.
  * ----------------
  */
-void
-ExecAssignProjectionInfo(PlanState *planstate)
+ProjectionInfo *
+ExecBuildProjectionInfo(List *targetList,
+                       ExprContext *econtext,
+                       TupleTableSlot *slot)
 {
-   ProjectionInfo *projInfo;
-   List       *targetList;
+   ProjectionInfo *projInfo = makeNode(ProjectionInfo);
    int         len;
 
-   targetList = planstate->targetlist;
    len = ExecTargetListLength(targetList);
 
-   projInfo = makeNode(ProjectionInfo);
    projInfo->pi_targetlist = targetList;
-   projInfo->pi_len = len;
-   projInfo->pi_tupValue = (len <= 0) ? NULL : (Datum *) palloc(sizeof(Datum) * len);
-   projInfo->pi_exprContext = planstate->ps_ExprContext;
-   projInfo->pi_slot = planstate->ps_ResultTupleSlot;
+   projInfo->pi_exprContext = econtext;
+   projInfo->pi_slot = slot;
+   if (len > 0)
+   {
+       projInfo->pi_tupValues = (Datum *) palloc(len * sizeof(Datum));
+       projInfo->pi_tupNulls = (char *) palloc(len * sizeof(char));
+       projInfo->pi_itemIsDone = (ExprDoneCond *) palloc(len * sizeof(ExprDoneCond));
+   }
+
+   return projInfo;
+}
 
-   planstate->ps_ProjInfo = projInfo;
+/* ----------------
+ *     ExecAssignProjectionInfo
+ *
+ * forms the projection information from the node's targetlist
+ * ----------------
+ */
+void
+ExecAssignProjectionInfo(PlanState *planstate)
+{
+   planstate->ps_ProjInfo =
+       ExecBuildProjectionInfo(planstate->targetlist,
+                               planstate->ps_ExprContext,
+                               planstate->ps_ResultTupleSlot);
 }
 
 
index 40eca6749ec94f94ad56e9593d5439de3c622826..d3f32913914f6fae8cc2689959be060725f935c4 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/nodeSubplan.c,v 1.42 2003/01/10 21:08:08 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/nodeSubplan.c,v 1.43 2003/01/12 04:03:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "access/heapam.h"
 #include "executor/executor.h"
 #include "executor/nodeSubplan.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_expr.h"
 #include "tcop/pquery.h"
 
 
+static Datum ExecHashSubPlan(SubPlanState *node,
+                            ExprContext *econtext,
+                            bool *isNull);
+static Datum ExecScanSubPlan(SubPlanState *node,
+                            ExprContext *econtext,
+                            bool *isNull);
+static void buildSubPlanHash(SubPlanState *node);
+static bool findPartialMatch(TupleHashTable hashtable, TupleTableSlot *slot);
+static bool tupleAllNulls(HeapTuple tuple);
+
+
 /* ----------------------------------------------------------------
- *     ExecSubPlan(node)
+ *     ExecSubPlan
  * ----------------------------------------------------------------
  */
 Datum
@@ -35,6 +48,155 @@ ExecSubPlan(SubPlanState *node,
            bool *isNull)
 {
    SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
+
+   if (subplan->setParam != NIL)
+       elog(ERROR, "ExecSubPlan: can't set parent params from subquery");
+
+   if (subplan->useHashTable)
+       return ExecHashSubPlan(node, econtext, isNull);
+   else
+       return ExecScanSubPlan(node, econtext, isNull);
+}
+
+/*
+ * ExecHashSubPlan: store subselect result in an in-memory hash table
+ */
+static Datum
+ExecHashSubPlan(SubPlanState *node,
+               ExprContext *econtext,
+               bool *isNull)
+{
+   SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
+   PlanState  *planstate = node->planstate;
+   ExprContext *innerecontext = node->innerecontext;
+   TupleTableSlot *slot;
+   HeapTuple   tup;
+
+   /* Shouldn't have any direct correlation Vars */
+   if (subplan->parParam != NIL || node->args != NIL)
+       elog(ERROR, "ExecHashSubPlan: direct correlation not supported");
+
+   /*
+    * If first time through or we need to rescan the subplan, build
+    * the hash table.
+    */
+   if (node->hashtable == NULL || planstate->chgParam != NIL)
+       buildSubPlanHash(node);
+
+   /*
+    * The result for an empty subplan is always FALSE; no need to
+    * evaluate lefthand side.
+    */
+   *isNull = false;
+   if (!node->havehashrows && !node->havenullrows)
+       return BoolGetDatum(false);
+
+   /*
+    * Evaluate lefthand expressions and form a projection tuple.
+    * First we have to set the econtext to use (hack alert!).
+    */
+   node->projLeft->pi_exprContext = econtext;
+   slot = ExecProject(node->projLeft, NULL);
+   tup = slot->val;
+
+   /*
+    * 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.
+    */
+   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.
+    *
+    * 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.
+    */
+   if (HeapTupleNoNulls(tup))
+   {
+       if (node->havehashrows &&
+           LookupTupleHashEntry(node->hashtable, slot, NULL) != NULL)
+       {
+           ExecClearTuple(slot);
+           return BoolGetDatum(true);
+       }
+       if (node->havenullrows &&
+           findPartialMatch(node->hashnulls, slot))
+       {
+           ExecClearTuple(slot);
+           *isNull = true;
+           return BoolGetDatum(false);
+       }
+       ExecClearTuple(slot);
+       return BoolGetDatum(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)
+   {
+       ExecClearTuple(slot);
+       return BoolGetDatum(false);
+   }
+   if (tupleAllNulls(tup))
+   {
+       ExecClearTuple(slot);
+       *isNull = true;
+       return BoolGetDatum(false);
+   }
+   /* Scan partly-null table first, since more likely to get a match */
+   if (node->havenullrows &&
+       findPartialMatch(node->hashnulls, slot))
+   {
+       ExecClearTuple(slot);
+       *isNull = true;
+       return BoolGetDatum(false);
+   }
+   if (node->havehashrows &&
+       findPartialMatch(node->hashtable, slot))
+   {
+       ExecClearTuple(slot);
+       *isNull = true;
+       return BoolGetDatum(false);
+   }
+   ExecClearTuple(slot);
+   return BoolGetDatum(false);
+}
+
+/*
+ * ExecScanSubPlan: default case where we have to rescan subplan each time
+ */
+static Datum
+ExecScanSubPlan(SubPlanState *node,
+               ExprContext *econtext,
+               bool *isNull)
+{
+   SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
    PlanState  *planstate = node->planstate;
    SubLinkType subLinkType = subplan->subLinkType;
    bool        useOr = subplan->useOr;
@@ -52,9 +214,6 @@ ExecSubPlan(SubPlanState *node,
     */
    oldcontext = MemoryContextSwitchTo(node->sub_estate->es_query_cxt);
 
-   if (subplan->setParam != NIL)
-       elog(ERROR, "ExecSubPlan: can't set parent params from subquery");
-
    /*
     * Set Params of this plan from parent plan correlation Vars
     */
@@ -267,6 +426,203 @@ ExecSubPlan(SubPlanState *node,
    return result;
 }
 
+/*
+ * buildSubPlanHash: load hash table by scanning subplan output.
+ */
+static void
+buildSubPlanHash(SubPlanState *node)
+{
+   SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
+   PlanState  *planstate = node->planstate;
+   int         ncols = length(node->exprs);
+   ExprContext *innerecontext = node->innerecontext;
+   MemoryContext tempcxt = innerecontext->ecxt_per_tuple_memory;
+   MemoryContext oldcontext;
+   int         nbuckets;
+   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 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.)
+    *
+    * If it's not necessary to distinguish FALSE and UNKNOWN, then we
+    * don't need to store subplan output rows that contain NULL.
+    */
+   MemoryContextReset(node->tablecxt);
+   node->hashtable = NULL;
+   node->hashnulls = NULL;
+   node->havehashrows = false;
+   node->havenullrows = false;
+
+   nbuckets = (int) ceil(planstate->plan->plan_rows);
+   if (nbuckets < 1)
+       nbuckets = 1;
+
+   node->hashtable = BuildTupleHashTable(ncols,
+                                         node->keyColIdx,
+                                         node->eqfunctions,
+                                         nbuckets,
+                                         sizeof(TupleHashEntryData),
+                                         node->tablecxt,
+                                         tempcxt);
+
+   if (!subplan->unknownEqFalse)
+   {
+       if (ncols == 1)
+           nbuckets = 1;       /* there can only be one entry */
+       else
+       {
+           nbuckets /= 16;
+           if (nbuckets < 1)
+               nbuckets = 1;
+       }
+       node->hashnulls = BuildTupleHashTable(ncols,
+                                             node->keyColIdx,
+                                             node->eqfunctions,
+                                             nbuckets,
+                                             sizeof(TupleHashEntryData),
+                                             node->tablecxt,
+                                             tempcxt);
+   }
+
+   /*
+    * 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);
+
+   /*
+    * Reset subplan to start.
+    */
+   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.
+    */
+   for (slot = ExecProcNode(planstate);
+        !TupIsNull(slot);
+        slot = ExecProcNode(planstate))
+   {
+       HeapTuple   tup = slot->val;
+       TupleDesc   tdesc = slot->ttc_tupleDescriptor;
+       int         col = 1;
+       List       *plst;
+       bool        isnew;
+
+       /*
+        * Load up the Params representing the raw sub-select outputs,
+        * then form the projection tuple to store in the hashtable.
+        */
+       foreach(plst, subplan->paramIds)
+       {
+           int         paramid = lfirsti(plst);
+           ParamExecData *prmdata;
+
+           prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
+           Assert(prmdata->execPlan == NULL);
+           prmdata->value = heap_getattr(tup, col, tdesc,
+                                         &(prmdata->isnull));
+           col++;
+       }
+       slot = ExecProject(node->projRight, NULL);
+       tup = slot->val;
+
+       /*
+        * If result contains any nulls, store separately or not at all.
+        * (Since we know the projection tuple has no junk columns, we
+        * can just look at the overall hasnull info bit, instead of
+        * groveling through the columns.)
+        */
+       if (HeapTupleNoNulls(tup))
+       {
+           (void) LookupTupleHashEntry(node->hashtable, slot, &isnew);
+           node->havehashrows = true;
+       }
+       else if (node->hashnulls)
+       {
+           (void) LookupTupleHashEntry(node->hashnulls, slot, &isnew);
+           node->havenullrows = true;
+       }
+
+       /*
+        * 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.
+    */
+   ExecClearTuple(node->projRight->pi_slot);
+
+   MemoryContextSwitchTo(oldcontext);
+}
+
+/*
+ * findPartialMatch: does the hashtable contain an entry that is not
+ * provably distinct from the tuple?
+ *
+ * We have to scan the whole hashtable; we can't usefully use hashkeys
+ * to guide probing, since we might get partial matches on tuples with
+ * hashkeys quite unrelated to what we'd get from the given tuple.
+ */
+static bool
+findPartialMatch(TupleHashTable hashtable, TupleTableSlot *slot)
+{
+   int         numCols = hashtable->numCols;
+   AttrNumber *keyColIdx = hashtable->keyColIdx;
+   HeapTuple   tuple = slot->val;
+   TupleDesc   tupdesc = slot->ttc_tupleDescriptor;
+   TupleHashIterator hashiter;
+   TupleHashEntry  entry;
+
+   ResetTupleHashIterator(&hashiter);
+   while ((entry = ScanTupleHashTable(hashtable, &hashiter)) != NULL)
+   {
+       if (!execTuplesUnequal(entry->firstTuple,
+                              tuple,
+                              tupdesc,
+                              numCols, keyColIdx,
+                              hashtable->eqfunctions,
+                              hashtable->tempcxt))
+           return true;
+   }
+   return false;
+}
+
+/*
+ * tupleAllNulls: is the tuple completely NULL?
+ */
+static bool
+tupleAllNulls(HeapTuple tuple)
+{
+   int     ncols = tuple->t_data->t_natts;
+   int     i;
+
+   for (i = 1; i <= ncols; i++)
+   {
+       if (!heap_attisnull(tuple, i))
+           return false;
+   }
+   return true;
+}
+
 /* ----------------------------------------------------------------
  *     ExecInitSubPlan
  * ----------------------------------------------------------------
@@ -289,8 +645,14 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
     */
    node->needShutdown = false;
    node->curTuple = NULL;
+   node->projLeft = NULL;
+   node->projRight = NULL;
    node->hashtable = NULL;
    node->hashnulls = NULL;
+   node->tablecxt = NULL;
+   node->innerecontext = NULL;
+   node->keyColIdx = NULL;
+   node->eqfunctions = NULL;
 
    /*
     * create an EState for the subplan
@@ -343,6 +705,137 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
         * it, for others - it doesn't matter...
         */
    }
+
+   /*
+    * If we are going to hash the subquery output, initialize relevant
+    * stuff.  (We don't create the hashtable until needed, though.)
+    */
+   if (subplan->useHashTable)
+   {
+       int     ncols,
+               i;
+       TupleDesc   tupDesc;
+       TupleTable  tupTable;
+       TupleTableSlot *slot;
+       List       *lefttlist,
+                  *righttlist,
+                  *leftptlist,
+                  *rightptlist,
+                  *lexpr;
+
+       /* We need a memory context to hold the hash table(s) */
+       node->tablecxt =
+           AllocSetContextCreate(CurrentMemoryContext,
+                                 "Subplan HashTable Context",
+                                 ALLOCSET_DEFAULT_MINSIZE,
+                                 ALLOCSET_DEFAULT_INITSIZE,
+                                 ALLOCSET_DEFAULT_MAXSIZE);
+       /* and a short-lived exprcontext for function evaluation */
+       node->innerecontext = CreateExprContext(estate);
+       /* Silly little array of column numbers 1..n */
+       ncols = length(node->exprs);
+       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).
+        *
+        * We also extract the combining operators themselves to initialize
+        * the equality functions for the hash tables.
+        */
+       lefttlist = righttlist = NIL;
+       leftptlist = rightptlist = NIL;
+       node->eqfunctions = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo));
+       i = 1;
+       foreach(lexpr, node->exprs)
+       {
+           FuncExprState  *fstate = (FuncExprState *) lfirst(lexpr);
+           OpExpr     *opexpr = (OpExpr *) fstate->xprstate.expr;
+           ExprState  *exstate;
+           Expr       *expr;
+           TargetEntry *tle;
+           GenericExprState *tlestate;
+
+           Assert(IsA(fstate, FuncExprState));
+           Assert(IsA(opexpr, OpExpr));
+           Assert(length(fstate->args) == 2);
+
+           /* Process lefthand argument */
+           exstate = (ExprState *) lfirst(fstate->args);
+           expr = exstate->expr;
+           tle = makeTargetEntry(makeResdom(i,
+                                            exprType((Node *) expr),
+                                            exprTypmod((Node *) expr),
+                                            NULL,
+                                            false),
+                                 expr);
+           tlestate = makeNode(GenericExprState);
+           tlestate->xprstate.expr = (Expr *) tle;
+           tlestate->arg = exstate;
+           lefttlist = lappend(lefttlist, tlestate);
+           leftptlist = lappend(leftptlist, tle);
+
+           /* Process righthand argument */
+           exstate = (ExprState *) lsecond(fstate->args);
+           expr = exstate->expr;
+           tle = makeTargetEntry(makeResdom(i,
+                                            exprType((Node *) expr),
+                                            exprTypmod((Node *) expr),
+                                            NULL,
+                                            false),
+                                 expr);
+           tlestate = makeNode(GenericExprState);
+           tlestate->xprstate.expr = (Expr *) tle;
+           tlestate->arg = exstate;
+           righttlist = lappend(righttlist, tlestate);
+           rightptlist = lappend(rightptlist, tle);
+
+           /* Lookup the combining function */
+           fmgr_info(opexpr->opfuncid, &node->eqfunctions[i-1]);
+
+           i++;
+       }
+
+       /*
+        * 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.
+        */
+       tupDesc = ExecTypeFromTL(leftptlist, false);
+       slot = ExecAllocTableSlot(tupTable);
+       ExecSetSlotDescriptor(slot, tupDesc, true);
+       node->projLeft = ExecBuildProjectionInfo(lefttlist,
+                                                NULL,
+                                                slot);
+
+       tupDesc = ExecTypeFromTL(rightptlist, false);
+       slot = ExecAllocTableSlot(tupTable);
+       ExecSetSlotDescriptor(slot, tupDesc, true);
+       node->projRight = ExecBuildProjectionInfo(righttlist,
+                                                 node->innerecontext,
+                                                 slot);
+   }
 }
 
 /* ----------------------------------------------------------------
@@ -476,11 +969,6 @@ ExecEndSubPlan(SubPlanState *node)
        node->planstate = NULL;
        node->needShutdown = false;
    }
-   if (node->curTuple)
-   {
-       heap_freetuple(node->curTuple);
-       node->curTuple = NULL;
-   }
 }
 
 void
index 460d5c388352a10169c9fef8203f4ea0785a91c2..2feaff11f75ea183d75fcb605446ee5c4da266f9 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.63 2003/01/10 21:08:11 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.64 2003/01/12 04:03:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -197,9 +197,9 @@ make_subplan(SubLink *slink, List *lefthand)
     * NOTE: if you change these numbers, also change cost_qual_eval_walker()
     * in path/costsize.c.
     *
-    * XXX If an ALL/ANY subplan is uncorrelated, we may decide to
-    * materialize its result below.  In that case it would've been better
-    * to specify full retrieval.  At present, however, we can only detect
+    * XXX If an ALL/ANY subplan is uncorrelated, we may decide to hash or
+    * materialize its result below.  In that case it would've been better to
+    * specify full retrieval.  At present, however, we can only detect
     * correlation or lack of it after we've made the subplan :-(. Perhaps
     * detection of correlation should be done as a separate step.
     * Meanwhile, we don't want to be too optimistic about the percentage
@@ -525,10 +525,17 @@ subplan_is_hashable(SubLink *slink, SubPlan *node)
    if (subquery_size > SortMem * 1024L)
        return false;
    /*
-    * The combining operators must be hashable and strict.  (Without
-    * strictness, behavior in the presence of nulls is too unpredictable.
-    * We actually must assume even more than plain strictness, see
-    * nodeSubplan.c for details.)
+    * The combining operators must be hashable, strict, and self-commutative.
+    * The need for hashability is obvious, since we want to use hashing.
+    * Without strictness, behavior in the presence of nulls is too
+    * unpredictable.  (We actually must assume even more than plain
+    * strictness, see nodeSubplan.c for details.)  And commutativity ensures
+    * that the left and right datatypes are the same; this allows us to
+    * assume that the combining operators are equality for the righthand
+    * datatype, so that they can be used to compare righthand tuples as
+    * well as comparing lefthand to righthand tuples.  (This last restriction
+    * could be relaxed by using two different sets of operators with the
+    * hash table, but there is no obvious usefulness to that at present.)
     */
    foreach(opids, slink->operOids)
    {
@@ -542,7 +549,8 @@ subplan_is_hashable(SubLink *slink, SubPlan *node)
        if (!HeapTupleIsValid(tup))
            elog(ERROR, "cache lookup failed for operator %u", opid);
        optup = (Form_pg_operator) GETSTRUCT(tup);
-       if (!optup->oprcanhash || !func_strict(optup->oprcode))
+       if (!optup->oprcanhash || optup->oprcom != opid ||
+           !func_strict(optup->oprcode))
        {
            ReleaseSysCache(tup);
            return false;
index fb300fc044339990234ecccb70e8ccc57dcda5d5..cd462ac27a03eefd6e3b5ce84a5b3ede0ad95324 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: executor.h,v 1.86 2003/01/10 23:54:24 tgl Exp $
+ * $Id: executor.h,v 1.87 2003/01/12 04:03:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -46,6 +46,13 @@ extern bool execTuplesMatch(HeapTuple tuple1,
                AttrNumber *matchColIdx,
                FmgrInfo *eqfunctions,
                MemoryContext evalContext);
+extern bool execTuplesUnequal(HeapTuple tuple1,
+               HeapTuple tuple2,
+               TupleDesc tupdesc,
+               int numCols,
+               AttrNumber *matchColIdx,
+               FmgrInfo *eqfunctions,
+               MemoryContext evalContext);
 extern FmgrInfo *execTuplesMatchPrepare(TupleDesc tupdesc,
                       int numCols,
                       AttrNumber *matchColIdx);
@@ -214,6 +221,9 @@ extern void ExecAssignResultType(PlanState *planstate,
 extern void ExecAssignResultTypeFromOuterPlan(PlanState *planstate);
 extern void ExecAssignResultTypeFromTL(PlanState *planstate);
 extern TupleDesc ExecGetResultType(PlanState *planstate);
+extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
+                                              ExprContext *econtext,
+                                              TupleTableSlot *slot);
 extern void ExecAssignProjectionInfo(PlanState *planstate);
 extern void ExecFreeExprContext(PlanState *planstate);
 extern TupleDesc ExecGetScanType(ScanState *scanstate);
index 9c43660c610ede090e2e6d3e9d92b866ff509101..2aa672b65ea345f22ce67ecac7afecb292945b85 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execnodes.h,v 1.90 2003/01/10 23:54:24 tgl Exp $
+ * $Id: execnodes.h,v 1.91 2003/01/12 04:03:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -170,27 +170,34 @@ typedef struct ReturnSetInfo
 /* ----------------
  *     ProjectionInfo node information
  *
- *     This is all the information needed to perform projections
- *     on a tuple.  Nodes which need to do projections create one
- *     of these.  In theory, when a node wants to perform a projection
+ *     This is all the information needed to perform projections ---
+ *     that is, form new tuples by evaluation of targetlist expressions.
+ *     Nodes which need to do projections create one of these.
+ *     In theory, when a node wants to perform a projection
  *     it should just update this information as necessary and then
  *     call ExecProject().  -cim 6/3/91
  *
+ *     ExecProject() evaluates the tlist, forms a tuple, and stores it
+ *     in the given slot.  As a side-effect, the actual datum values and
+ *     null indicators are placed in the work arrays tupValues/tupNulls.
+ *
  *     targetlist      target list for projection
- *     len             length of target list
- *     tupValue        array of pointers to projection results
- *     exprContext     expression context for ExecTargetList
+ *     exprContext     expression context in which to evaluate targetlist
  *     slot            slot to place projection result in
+ *     tupValues       array of computed values
+ *     tupNull         array of null indicators
+ *     itemIsDone      workspace for ExecProject
  * ----------------
  */
 typedef struct ProjectionInfo
 {
    NodeTag     type;
    List       *pi_targetlist;
-   int         pi_len;
-   Datum      *pi_tupValue;
    ExprContext *pi_exprContext;
    TupleTableSlot *pi_slot;
+   Datum      *pi_tupValues;
+   char       *pi_tupNulls;
+   ExprDoneCond *pi_itemIsDone;
 } ProjectionInfo;
 
 /* ----------------
@@ -495,8 +502,16 @@ typedef struct SubPlanState
    bool        needShutdown;   /* TRUE = need to shutdown subplan */
    HeapTuple   curTuple;       /* copy of most recent tuple from subplan */
    /* these are used when hashing the subselect's output: */
+   ProjectionInfo *projLeft;   /* for projecting lefthand exprs */
+   ProjectionInfo *projRight;  /* for projecting subselect output */
    TupleHashTable hashtable;   /* hash table for no-nulls subselect rows */
    TupleHashTable hashnulls;   /* hash table for rows with null(s) */
+   bool        havehashrows;   /* TRUE if hashtable is not empty */
+   bool        havenullrows;   /* TRUE if hashnulls is not empty */
+   MemoryContext tablecxt;     /* memory context containing tables */
+   ExprContext *innerecontext; /* working context for comparisons */
+   AttrNumber *keyColIdx;      /* control data for hash tables */
+   FmgrInfo   *eqfunctions;    /* comparison functions for hash tables */
 } SubPlanState;
 
 /* ----------------