Upgrade planner and executor to allow multiple hash keys for a hash join,
authorTom Lane
Sat, 30 Nov 2002 00:08:22 +0000 (00:08 +0000)
committerTom Lane
Sat, 30 Nov 2002 00:08:22 +0000 (00:08 +0000)
instead of only one.  This should speed up planning (only one hash path
to consider for a given pair of relations) as well as allow more effective
hashing, when there are multiple hashable joinclauses.

14 files changed:
src/backend/executor/nodeHash.c
src/backend/executor/nodeHashjoin.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/outfuncs.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/path/joinpath.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/subselect.c
src/backend/optimizer/util/pathnode.c
src/include/executor/hashjoin.h
src/include/executor/nodeHash.h
src/include/nodes/execnodes.h
src/include/nodes/plannodes.h
src/include/nodes/relation.h

index 57faf0622cbd5e122966c10f78ced32e24f29b19..c2c3ab66644af41e00d74c3479579edc82352988 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/nodeHash.c,v 1.67 2002/11/06 22:31:23 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/nodeHash.c,v 1.68 2002/11/30 00:08:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -45,7 +45,7 @@ ExecHash(Hash *node)
    EState     *estate;
    HashState  *hashstate;
    Plan       *outerNode;
-   Node       *hashkey;
+   List       *hashkeys;
    HashJoinTable hashtable;
    TupleTableSlot *slot;
    ExprContext *econtext;
@@ -79,7 +79,7 @@ ExecHash(Hash *node)
    /*
     * set expression context
     */
-   hashkey = node->hashkey;
+   hashkeys = node->hashkeys;
    econtext = hashstate->cstate.cs_ExprContext;
 
    /*
@@ -91,7 +91,7 @@ ExecHash(Hash *node)
        if (TupIsNull(slot))
            break;
        econtext->ecxt_innertuple = slot;
-       ExecHashTableInsert(hashtable, econtext, hashkey);
+       ExecHashTableInsert(hashtable, econtext, hashkeys);
        ExecClearTuple(slot);
    }
 
@@ -212,7 +212,9 @@ ExecHashTableCreate(Hash *node)
    int         totalbuckets;
    int         nbuckets;
    int         nbatch;
+   int         nkeys;
    int         i;
+   List       *hk;
    MemoryContext oldcxt;
 
    /*
@@ -248,11 +250,19 @@ ExecHashTableCreate(Hash *node)
    hashtable->outerBatchSize = NULL;
 
    /*
-    * Get info about the datatype of the hash key.
+    * Get info about the datatypes of the hash keys.
     */
-   get_typlenbyval(exprType(node->hashkey),
-                   &hashtable->typLen,
-                   &hashtable->typByVal);
+   nkeys = length(node->hashkeys);
+   hashtable->typLens = (int16 *) palloc(nkeys * sizeof(int16));
+   hashtable->typByVals = (bool *) palloc(nkeys * sizeof(bool));
+   i = 0;
+   foreach(hk, node->hashkeys)
+   {
+       get_typlenbyval(exprType(lfirst(hk)),
+                       &hashtable->typLens[i],
+                       &hashtable->typByVals[i]);
+       i++;
+   }
 
    /*
     * Create temporary memory contexts in which to keep the hashtable
@@ -465,9 +475,9 @@ ExecHashTableDestroy(HashJoinTable hashtable)
 void
 ExecHashTableInsert(HashJoinTable hashtable,
                    ExprContext *econtext,
-                   Node *hashkey)
+                   List *hashkeys)
 {
-   int         bucketno = ExecHashGetBucket(hashtable, econtext, hashkey);
+   int         bucketno = ExecHashGetBucket(hashtable, econtext, hashkeys);
    TupleTableSlot *slot = econtext->ecxt_innertuple;
    HeapTuple   heapTuple = slot->val;
 
@@ -522,44 +532,55 @@ ExecHashTableInsert(HashJoinTable hashtable,
 int
 ExecHashGetBucket(HashJoinTable hashtable,
                  ExprContext *econtext,
-                 Node *hashkey)
+                 List *hashkeys)
 {
+   uint32      hashkey = 0;
    int         bucketno;
-   Datum       keyval;
-   bool        isNull;
+   List       *hk;
+   int         i = 0;
    MemoryContext oldContext;
 
    /*
     * We reset the eval context each time to reclaim any memory leaked in
-    * the hashkey expression or ComputeHashFunc itself.
+    * the hashkey expressions or ComputeHashFunc itself.
     */
    ResetExprContext(econtext);
 
    oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
 
-   /*
-    * Get the join attribute value of the tuple
-    */
-   keyval = ExecEvalExpr(hashkey, econtext, &isNull, NULL);
-
-   /*
-    * Compute the hash function
-    */
-   if (isNull)
-       bucketno = 0;
-   else
+   foreach(hk, hashkeys)
    {
-       bucketno = ComputeHashFunc(keyval,
-                                  (int) hashtable->typLen,
-                                  hashtable->typByVal)
-           % (uint32) hashtable->totalbuckets;
+       Datum       keyval;
+       bool        isNull;
+
+       /* rotate hashkey left 1 bit at each step */
+       hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0);
+
+       /*
+        * Get the join attribute value of the tuple
+        */
+       keyval = ExecEvalExpr(lfirst(hk), econtext, &isNull, NULL);
+
+       /*
+        * Compute the hash function
+        */
+       if (!isNull)            /* treat nulls as having hash key 0 */
+       {
+           hashkey ^= ComputeHashFunc(keyval,
+                                      (int) hashtable->typLens[i],
+                                      hashtable->typByVals[i]);
+       }
+
+       i++;
    }
 
+   bucketno = hashkey % (uint32) hashtable->totalbuckets;
+
 #ifdef HJDEBUG
    if (bucketno >= hashtable->nbuckets)
-       printf("hash(%ld) = %d SAVED\n", (long) keyval, bucketno);
+       printf("hash(%u) = %d SAVED\n", hashkey, bucketno);
    else
-       printf("hash(%ld) = %d\n", (long) keyval, bucketno);
+       printf("hash(%u) = %d\n", hashkey, bucketno);
 #endif
 
    MemoryContextSwitchTo(oldContext);
index f1484c4a0545769f2f46b80fff147e2b01298168..8f0e700ac35bfe345cd61c3a239bc56a8e381dc7 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.41 2002/09/02 02:47:02 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.42 2002/11/30 00:08:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -48,12 +48,11 @@ ExecHashJoin(HashJoin *node)
    Plan       *outerNode;
    Hash       *hashNode;
    List       *hjclauses;
-   Expr       *clause;
+   List       *outerkeys;
    List       *joinqual;
    List       *otherqual;
    ScanDirection dir;
    TupleTableSlot *inntuple;
-   Node       *outerVar;
    ExprContext *econtext;
    ExprDoneCond isDone;
    HashJoinTable hashtable;
@@ -68,7 +67,6 @@ ExecHashJoin(HashJoin *node)
     */
    hjstate = node->hashjoinstate;
    hjclauses = node->hashclauses;
-   clause = lfirst(hjclauses);
    estate = node->join.plan.state;
    joinqual = node->join.joinqual;
    otherqual = node->join.plan.qual;
@@ -81,6 +79,7 @@ ExecHashJoin(HashJoin *node)
     * get information from HashJoin state
     */
    hashtable = hjstate->hj_HashTable;
+   outerkeys = hjstate->hj_OuterHashKeys;
    econtext = hjstate->jstate.cs_ExprContext;
 
    /*
@@ -119,7 +118,6 @@ ExecHashJoin(HashJoin *node)
             */
            hashtable = ExecHashTableCreate(hashNode);
            hjstate->hj_HashTable = hashtable;
-           hjstate->hj_InnerHashKey = hashNode->hashkey;
 
            /*
             * execute the Hash node, to build the hash table
@@ -143,7 +141,6 @@ ExecHashJoin(HashJoin *node)
     * Now get an outer tuple and probe into the hash table for matches
     */
    outerTupleSlot = hjstate->jstate.cs_OuterTupleSlot;
-   outerVar = (Node *) get_leftop(clause);
 
    for (;;)
    {
@@ -175,7 +172,7 @@ ExecHashJoin(HashJoin *node)
             * for this tuple from the hash table
             */
            hjstate->hj_CurBucketNo = ExecHashGetBucket(hashtable, econtext,
-                                                       outerVar);
+                                                       outerkeys);
            hjstate->hj_CurTuple = NULL;
 
            /*
@@ -308,6 +305,7 @@ ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent)
    HashJoinState *hjstate;
    Plan       *outerNode;
    Hash       *hashNode;
+   List       *hcl;
 
    /*
     * assign the node's execution state
@@ -391,7 +389,18 @@ ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent)
    hjstate->hj_HashTable = (HashJoinTable) NULL;
    hjstate->hj_CurBucketNo = 0;
    hjstate->hj_CurTuple = (HashJoinTuple) NULL;
-   hjstate->hj_InnerHashKey = (Node *) NULL;
+
+   /*
+    * The planner already made a list of the inner hashkeys for us,
+    * but we also need a list of the outer hashkeys.
+    */
+   hjstate->hj_InnerHashKeys = hashNode->hashkeys;
+   hjstate->hj_OuterHashKeys = NIL;
+   foreach(hcl, node->hashclauses)
+   {
+       hjstate->hj_OuterHashKeys = lappend(hjstate->hj_OuterHashKeys,
+                                           get_leftop(lfirst(hcl)));
+   }
 
    hjstate->jstate.cs_OuterTupleSlot = NULL;
    hjstate->jstate.cs_TupFromTlist = false;
@@ -555,7 +564,7 @@ ExecHashJoinNewBatch(HashJoinState *hjstate)
    BufFile    *innerFile;
    TupleTableSlot *slot;
    ExprContext *econtext;
-   Node       *innerhashkey;
+   List       *innerhashkeys;
 
    if (newbatch > 1)
    {
@@ -603,7 +612,7 @@ ExecHashJoinNewBatch(HashJoinState *hjstate)
    ExecHashTableReset(hashtable, innerBatchSize[newbatch - 1]);
 
    econtext = hjstate->jstate.cs_ExprContext;
-   innerhashkey = hjstate->hj_InnerHashKey;
+   innerhashkeys = hjstate->hj_InnerHashKeys;
 
    while ((slot = ExecHashJoinGetSavedTuple(hjstate,
                                             innerFile,
@@ -611,7 +620,7 @@ ExecHashJoinNewBatch(HashJoinState *hjstate)
           && !TupIsNull(slot))
    {
        econtext->ecxt_innertuple = slot;
-       ExecHashTableInsert(hashtable, econtext, innerhashkey);
+       ExecHashTableInsert(hashtable, econtext, innerhashkeys);
    }
 
    /*
@@ -694,7 +703,6 @@ ExecReScanHashJoin(HashJoin *node, ExprContext *exprCtxt, Plan *parent)
 
    hjstate->hj_CurBucketNo = 0;
    hjstate->hj_CurTuple = (HashJoinTuple) NULL;
-   hjstate->hj_InnerHashKey = (Node *) NULL;
 
    hjstate->jstate.cs_OuterTupleSlot = (TupleTableSlot *) NULL;
    hjstate->jstate.cs_TupFromTlist = false;
index a678d6326b1942da3ceb3c3117301152a505ed66..7798913cde517ff0f1d351db1d32922239c814d2 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.223 2002/11/25 21:29:36 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.224 2002/11/30 00:08:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -429,7 +429,6 @@ _copyHashJoin(HashJoin *from)
     * copy remainder of node
     */
    COPY_NODE_FIELD(hashclauses);
-   COPY_SCALAR_FIELD(hashjoinop);
 
    /* subPlan list must point to subplans in the new subtree, not the old */
    FIX_SUBPLAN_LINKS(join.plan.subPlan, hashclauses);
@@ -593,9 +592,9 @@ _copyHash(Hash *from)
    /*
     * copy remainder of node
     */
-   COPY_NODE_FIELD(hashkey);
+   COPY_NODE_FIELD(hashkeys);
 
-   /* XXX could the hashkey contain subplans?  Not at present... */
+   /* XXX could the hashkeys contain subplans?  Not at present... */
 
    return newnode;
 }
index 11572a4ebad0afe0527e5fd509d5d81174bdcd68..528148f02f0dce56e6298e521d0ce2f296e81212 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.183 2002/11/25 21:29:36 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.184 2002/11/30 00:08:16 tgl Exp $
  *
  * NOTES
  *   Every node type that can appear in stored rules' parsetrees *must*
@@ -538,7 +538,6 @@ _outHashJoin(StringInfo str, HashJoin *node)
    _outJoinPlanInfo(str, (Join *) node);
 
    WRITE_NODE_FIELD(hashclauses);
-   WRITE_OID_FIELD(hashjoinop);
 }
 
 static void
@@ -634,7 +633,7 @@ _outHash(StringInfo str, Hash *node)
 
    _outPlanInfo(str, (Plan *) node);
 
-   WRITE_NODE_FIELD(hashkey);
+   WRITE_NODE_FIELD(hashkeys);
 }
 
 static void
index 6cf8b2af4b5f3db0dd1bc90c88d1651c42da3b1a..fbdeea414c274c442240fdd8a4a67374db954cfb 100644 (file)
@@ -42,7 +42,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.91 2002/11/21 00:42:19 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.92 2002/11/30 00:08:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -819,7 +819,7 @@ cost_mergejoin(Path *path, Query *root,
  * 'outer_path' is the path for the outer relation
  * 'inner_path' is the path for the inner relation
  * 'restrictlist' are the RestrictInfo nodes to be applied at the join
- * 'hashclauses' is a list of the hash join clause (always a 1-element list)
+ * 'hashclauses' are the RestrictInfo nodes to use as hash clauses
  *     (this should be a subset of the restrictlist)
  */
 void
@@ -838,10 +838,8 @@ cost_hashjoin(Path *path, Query *root,
    double      innerbytes = relation_byte_size(inner_path->parent->rows,
                                              inner_path->parent->width);
    long        hashtablebytes = SortMem * 1024L;
-   RestrictInfo *restrictinfo;
-   Var        *left,
-              *right;
    Selectivity innerbucketsize;
+   List       *hcl;
 
    if (!enable_hashjoin)
        startup_cost += disable_cost;
@@ -856,43 +854,57 @@ cost_hashjoin(Path *path, Query *root,
    run_cost += cpu_operator_cost * outer_path->parent->rows;
 
    /*
-    * Determine bucketsize fraction for inner relation.  First we have to
-    * figure out which side of the hashjoin clause is the inner side.
+    * Determine bucketsize fraction for inner relation.  We use the
+    * smallest bucketsize estimated for any individual hashclause;
+    * this is undoubtedly conservative.
     */
-   Assert(length(hashclauses) == 1);
-   Assert(IsA(lfirst(hashclauses), RestrictInfo));
-   restrictinfo = (RestrictInfo *) lfirst(hashclauses);
-   /* these must be OK, since check_hashjoinable accepted the clause */
-   left = get_leftop(restrictinfo->clause);
-   right = get_rightop(restrictinfo->clause);
-
-   /*
-    * Since we tend to visit the same clauses over and over when planning
-    * a large query, we cache the bucketsize estimate in the RestrictInfo
-    * node to avoid repeated lookups of statistics.
-    */
-   if (VARISRELMEMBER(right->varno, inner_path->parent))
+   innerbucketsize = 1.0;
+   foreach(hcl, hashclauses)
    {
-       /* righthand side is inner */
-       innerbucketsize = restrictinfo->right_bucketsize;
-       if (innerbucketsize < 0)
+       RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(hcl);
+       Var        *left,
+                  *right;
+       Selectivity thisbucketsize;
+
+       Assert(IsA(restrictinfo, RestrictInfo));
+       /* these must be OK, since check_hashjoinable accepted the clause */
+       left = get_leftop(restrictinfo->clause);
+       right = get_rightop(restrictinfo->clause);
+
+       /*
+        * First we have to figure out which side of the hashjoin clause is the
+        * inner side.
+        *
+        * Since we tend to visit the same clauses over and over when planning
+        * a large query, we cache the bucketsize estimate in the RestrictInfo
+        * node to avoid repeated lookups of statistics.
+        */
+       if (VARISRELMEMBER(right->varno, inner_path->parent))
        {
-           /* not cached yet */
-           innerbucketsize = estimate_hash_bucketsize(root, right);
-           restrictinfo->right_bucketsize = innerbucketsize;
+           /* righthand side is inner */
+           thisbucketsize = restrictinfo->right_bucketsize;
+           if (thisbucketsize < 0)
+           {
+               /* not cached yet */
+               thisbucketsize = estimate_hash_bucketsize(root, right);
+               restrictinfo->right_bucketsize = thisbucketsize;
+           }
        }
-   }
-   else
-   {
-       Assert(VARISRELMEMBER(left->varno, inner_path->parent));
-       /* lefthand side is inner */
-       innerbucketsize = restrictinfo->left_bucketsize;
-       if (innerbucketsize < 0)
+       else
        {
-           /* not cached yet */
-           innerbucketsize = estimate_hash_bucketsize(root, left);
-           restrictinfo->left_bucketsize = innerbucketsize;
+           Assert(VARISRELMEMBER(left->varno, inner_path->parent));
+           /* lefthand side is inner */
+           thisbucketsize = restrictinfo->left_bucketsize;
+           if (thisbucketsize < 0)
+           {
+               /* not cached yet */
+               thisbucketsize = estimate_hash_bucketsize(root, left);
+               restrictinfo->left_bucketsize = thisbucketsize;
+           }
        }
+
+       if (innerbucketsize > thisbucketsize)
+           innerbucketsize = thisbucketsize;
    }
 
    /*
index ac5d4a72d456330226918b161e401300b7a35c11..6069a34d879e7d3ad4579ee50c9ea2318430d017 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.72 2002/11/24 21:52:14 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.73 2002/11/30 00:08:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -701,7 +701,7 @@ match_unsorted_inner(Query *root,
 /*
  * hash_inner_and_outer
  *   Create hashjoin join paths by explicitly hashing both the outer and
- *   inner join relations of each available hash clause.
+ *   inner keys of each available hash clause.
  *
  * 'joinrel' is the join relation
  * 'outerrel' is the outer join relation
@@ -719,6 +719,7 @@ hash_inner_and_outer(Query *root,
                     JoinType jointype)
 {
    bool        isouterjoin;
+   List       *hashclauses;
    List       *i;
 
    /*
@@ -737,20 +738,18 @@ hash_inner_and_outer(Query *root,
    }
 
    /*
+    * We need to build only one hashpath for any given pair of outer and
+    * inner relations; all of the hashable clauses will be used as keys.
+    *
     * Scan the join's restrictinfo list to find hashjoinable clauses that
-    * are usable with this pair of sub-relations.  Since we currently
-    * accept only var-op-var clauses as hashjoinable, we need only check
-    * the membership of the vars to determine whether a particular clause
-    * can be used with this pair of sub-relations.  This code would need
-    * to be upgraded if we wanted to allow more-complex expressions in
-    * hash joins.
+    * are usable with this pair of sub-relations.
     */
+   hashclauses = NIL;
    foreach(i, restrictlist)
    {
        RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(i);
        Var        *left,
                   *right;
-       List       *hashclauses;
 
        if (restrictinfo->hashjoinoperator == InvalidOid)
            continue;           /* not hashjoinable */
@@ -768,6 +767,12 @@ hash_inner_and_outer(Query *root,
 
        /*
         * Check if clause is usable with these input rels.
+        *
+        * Since we currently accept only var-op-var clauses as hashjoinable,
+        * we need only check the membership of the vars to determine whether
+        * a particular clause can be used with this pair of sub-relations.
+        * This code would need to be upgraded if we wanted to allow
+        * more-complex expressions in hash joins.
         */
        if (VARISRELMEMBER(left->varno, outerrel) &&
            VARISRELMEMBER(right->varno, innerrel))
@@ -782,9 +787,12 @@ hash_inner_and_outer(Query *root,
        else
            continue;           /* no good for these input relations */
 
-       /* always a one-element list of hash clauses */
-       hashclauses = makeList1(restrictinfo);
+       hashclauses = lappend(hashclauses, restrictinfo);
+   }
 
+   /* If we found any usable hashclauses, make a path */
+   if (hashclauses)
+   {
        /*
         * We consider both the cheapest-total-cost and
         * cheapest-startup-cost outer paths.  There's no need to consider
index b393252542fc81316f940fecf12bb355f6d744b6..d43e3271fbf604d4c4d11883d5e263a4cf4ab271 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.124 2002/11/21 00:42:19 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.125 2002/11/30 00:08:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -91,7 +91,7 @@ static HashJoin *make_hashjoin(List *tlist,
              List *hashclauses,
              Plan *lefttree, Plan *righttree,
              JoinType jointype);
-static Hash *make_hash(List *tlist, Node *hashkey, Plan *lefttree);
+static Hash *make_hash(List *tlist, List *hashkeys, Plan *lefttree);
 static MergeJoin *make_mergejoin(List *tlist,
               List *joinclauses, List *otherclauses,
               List *mergeclauses,
@@ -910,14 +910,9 @@ create_hashjoin_plan(Query *root,
    List       *hashclauses;
    HashJoin   *join_plan;
    Hash       *hash_plan;
-   Node       *innerhashkey;
+   List       *innerhashkeys;
+   List       *hcl;
 
-   /*
-    * NOTE: there will always be exactly one hashclause in the list
-    * best_path->path_hashclauses (cf. hash_inner_and_outer()). We
-    * represent it as a list anyway, for convenience with routines that
-    * want to work on lists of clauses.
-    */
    hashclauses = get_actual_clauses(best_path->path_hashclauses);
 
    /*
@@ -950,13 +945,20 @@ create_hashjoin_plan(Query *root,
                                               inner_tlist,
                                               (Index) 0));
 
-   /* Now the righthand op of the sole hashclause is the inner hash key. */
-   innerhashkey = (Node *) get_rightop(lfirst(hashclauses));
+   /*
+    * Extract the inner hash keys (right-hand operands of the hashclauses)
+    * to put in the Hash node.
+    */
+   innerhashkeys = NIL;
+   foreach(hcl, hashclauses)
+   {
+       innerhashkeys = lappend(innerhashkeys, get_rightop(lfirst(hcl)));
+   }
 
    /*
     * Build the hash node and hash join node.
     */
-   hash_plan = make_hash(inner_tlist, innerhashkey, inner_plan);
+   hash_plan = make_hash(inner_tlist, innerhashkeys, inner_plan);
    join_plan = make_hashjoin(tlist,
                              joinclauses,
                              otherclauses,
@@ -1511,7 +1513,7 @@ make_hashjoin(List *tlist,
 }
 
 static Hash *
-make_hash(List *tlist, Node *hashkey, Plan *lefttree)
+make_hash(List *tlist, List *hashkeys, Plan *lefttree)
 {
    Hash       *node = makeNode(Hash);
    Plan       *plan = &node->plan;
@@ -1528,7 +1530,7 @@ make_hash(List *tlist, Node *hashkey, Plan *lefttree)
    plan->qual = NULL;
    plan->lefttree = lefttree;
    plan->righttree = NULL;
-   node->hashkey = hashkey;
+   node->hashkeys = hashkeys;
 
    return node;
 }
index 5b171fb819a20babcc31e48250bdf0364a59a686..61476a656041f70c7d7167a829f8165075cf86b3 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.56 2002/11/26 03:01:58 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.57 2002/11/30 00:08:18 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -677,7 +677,7 @@ SS_finalize_plan(Plan *plan, List *rtable)
            break;
 
        case T_Hash:
-           finalize_primnode(((Hash *) plan)->hashkey,
+           finalize_primnode((Node *) ((Hash *) plan)->hashkeys,
                              &results);
            break;
 
index e99435a6edf91c48e5dda41ecd375906cfc4580b..98227355605ad980ca62cb5a12484d6b0155cc86 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.80 2002/11/24 21:52:14 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.81 2002/11/30 00:08:20 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -616,7 +616,7 @@ create_mergejoin_path(Query *root,
  * 'outer_path' is the cheapest outer path
  * 'inner_path' is the cheapest inner path
  * 'restrict_clauses' are the RestrictInfo nodes to apply at the join
- * 'hashclauses' is a list of the hash join clause (always a 1-element list)
+ * 'hashclauses' are the RestrictInfo nodes to use as hash clauses
  *     (this should be a subset of the restrict_clauses list)
  */
 HashPath *
index 1869feae08b79c1f34339ee4ed336e48b1579c93..a2d5f633fcd198559586672ea166a4805ee36943 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: hashjoin.h,v 1.26 2002/06/20 20:29:49 momjian Exp $
+ * $Id: hashjoin.h,v 1.27 2002/11/30 00:08:20 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -69,12 +69,13 @@ typedef struct HashTableData
                                 * file */
 
    /*
-    * Info about the datatype being hashed.  We assume that the inner and
-    * outer sides of the hash are the same type, or at least
-    * binary-compatible types.
+    * Info about the datatypes being hashed.  We assume that the inner and
+    * outer sides of each hashclause are the same type, or at least
+    * binary-compatible types.  Each of these fields points to an array
+    * of the same length as the number of hash keys.
     */
-   int16       typLen;
-   bool        typByVal;
+   int16      *typLens;
+   bool       *typByVals;
 
    /*
     * During 1st scan of inner relation, we get tuples from executor. If
index 8bea51e8af05ee635c97103d81af14a94614dab0..654906cd3c27140b635b80fe91813f2b11fad39a 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: nodeHash.h,v 1.25 2002/11/06 22:31:24 tgl Exp $
+ * $Id: nodeHash.h,v 1.26 2002/11/30 00:08:20 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,10 +24,10 @@ extern HashJoinTable ExecHashTableCreate(Hash *node);
 extern void ExecHashTableDestroy(HashJoinTable hashtable);
 extern void ExecHashTableInsert(HashJoinTable hashtable,
                    ExprContext *econtext,
-                   Node *hashkey);
+                   List *hashkeys);
 extern int ExecHashGetBucket(HashJoinTable hashtable,
                  ExprContext *econtext,
-                 Node *hashkey);
+                 List *hashkeys);
 extern HeapTuple ExecScanHashBucket(HashJoinState *hjstate, List *hjclauses,
                   ExprContext *econtext);
 extern void ExecHashTableReset(HashJoinTable hashtable, long ntuples);
index 6ee39b98182174ed2773c0cd1e52732699f5a7d6..544510746d653649e35e8206bde5a97286948c5e 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.80 2002/11/25 21:29:42 tgl Exp $
+ * $Id: execnodes.h,v 1.81 2002/11/30 00:08:20 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -618,7 +618,8 @@ typedef struct MergeJoinState
  *                             tuple, or NULL if starting search
  *                             (CurBucketNo and CurTuple are meaningless
  *                              unless OuterTupleSlot is nonempty!)
- *     hj_InnerHashKey         the inner hash key in the hashjoin condition
+ *     hj_OuterHashKeys        the outer hash keys in the hashjoin condition
+ *     hj_InnerHashKeys        the inner hash keys in the hashjoin condition
  *     hj_OuterTupleSlot       tuple slot for outer tuples
  *     hj_HashTupleSlot        tuple slot for hashed tuples
  *     hj_NullInnerTupleSlot   prepared null tuple for left outer joins
@@ -633,7 +634,8 @@ typedef struct HashJoinState
    HashJoinTable hj_HashTable;
    int         hj_CurBucketNo;
    HashJoinTuple hj_CurTuple;
-   Node       *hj_InnerHashKey;
+   List       *hj_OuterHashKeys;
+   List       *hj_InnerHashKeys;
    TupleTableSlot *hj_OuterTupleSlot;
    TupleTableSlot *hj_HashTupleSlot;
    TupleTableSlot *hj_NullInnerTupleSlot;
index 0cf9d0bac913defb16e190a76e59d0d4e4003b76..6a6ac415f9f79b3c05840803c391c9b4215f53d6 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: plannodes.h,v 1.60 2002/11/06 22:31:24 tgl Exp $
+ * $Id: plannodes.h,v 1.61 2002/11/30 00:08:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -318,7 +318,6 @@ typedef struct HashJoin
 {
    Join        join;
    List       *hashclauses;
-   Oid         hashjoinop;
    HashJoinState *hashjoinstate;
 } HashJoin;
 
@@ -443,7 +442,7 @@ typedef struct Limit
 typedef struct Hash
 {
    Plan        plan;
-   Node       *hashkey;
+   List       *hashkeys;
    HashState  *hashstate;
 } Hash;
 
index a3f0e36c76698e4faaecb6c616f82b043205ce28..4c06224eccec2465fbf9ca1a6d68605cdac21744 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: relation.h,v 1.70 2002/11/27 20:52:04 tgl Exp $
+ * $Id: relation.h,v 1.71 2002/11/30 00:08:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -464,8 +464,6 @@ typedef struct MergePath
  * A hashjoin path has these fields.
  *
  * The remarks above for mergeclauses apply for hashclauses as well.
- * (But note that path_hashclauses will always be a one-element list,
- * since we only hash on one hashable clause.)
  *
  * Hashjoin does not care what order its inputs appear in, so we have
  * no need for sortkeys.