]> granicus.if.org Git - postgresql/blob - src/backend/executor/nodeHashjoin.c
Message editing: remove gratuitous variations in message wording, standardize
[postgresql] / src / backend / executor / nodeHashjoin.c
1 /*-------------------------------------------------------------------------
2  *
3  * nodeHashjoin.c
4  *        Routines to handle hash join nodes
5  *
6  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.57 2003/09/25 06:57:59 petere Exp $
12  *
13  *-------------------------------------------------------------------------
14  */
15
16 #include "postgres.h"
17
18 #include "executor/executor.h"
19 #include "executor/nodeHash.h"
20 #include "executor/nodeHashjoin.h"
21 #include "optimizer/clauses.h"
22 #include "utils/memutils.h"
23
24
25 static TupleTableSlot *ExecHashJoinOuterGetTuple(PlanState *node,
26                                                   HashJoinState *hjstate);
27 static TupleTableSlot *ExecHashJoinGetSavedTuple(HashJoinState *hjstate,
28                                                   BufFile *file,
29                                                   TupleTableSlot *tupleSlot);
30 static int      ExecHashJoinNewBatch(HashJoinState *hjstate);
31
32
33 /* ----------------------------------------------------------------
34  *              ExecHashJoin
35  *
36  *              This function implements the Hybrid Hashjoin algorithm.
37  *              recursive partitioning remains to be added.
38  *              Note: the relation we build hash table on is the inner
39  *                        the other one is outer.
40  * ----------------------------------------------------------------
41  */
42 TupleTableSlot *                                /* return: a tuple or NULL */
43 ExecHashJoin(HashJoinState *node)
44 {
45         EState     *estate;
46         PlanState  *outerNode;
47         HashState  *hashNode;
48         List       *hjclauses;
49         List       *outerkeys;
50         List       *joinqual;
51         List       *otherqual;
52         ScanDirection dir;
53         TupleTableSlot *inntuple;
54         ExprContext *econtext;
55         ExprDoneCond isDone;
56         HashJoinTable hashtable;
57         HeapTuple       curtuple;
58         TupleTableSlot *outerTupleSlot;
59         int                     i;
60
61         /*
62          * get information from HashJoin node
63          */
64         hjclauses = node->hashclauses;
65         estate = node->js.ps.state;
66         joinqual = node->js.joinqual;
67         otherqual = node->js.ps.qual;
68         hashNode = (HashState *) innerPlanState(node);
69         outerNode = outerPlanState(node);
70         dir = estate->es_direction;
71
72         /*
73          * get information from HashJoin state
74          */
75         hashtable = node->hj_HashTable;
76         outerkeys = node->hj_OuterHashKeys;
77         econtext = node->js.ps.ps_ExprContext;
78
79         /*
80          * Check to see if we're still projecting out tuples from a previous
81          * join tuple (because there is a function-returning-set in the
82          * projection expressions).  If so, try to project another one.
83          */
84         if (node->js.ps.ps_TupFromTlist)
85         {
86                 TupleTableSlot *result;
87
88                 result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
89                 if (isDone == ExprMultipleResult)
90                         return result;
91                 /* Done with that source tuple... */
92                 node->js.ps.ps_TupFromTlist = false;
93         }
94
95         /*
96          * If we're doing an IN join, we want to return at most one row per
97          * outer tuple; so we can stop scanning the inner scan if we matched
98          * on the previous try.
99          */
100         if (node->js.jointype == JOIN_IN &&
101                 node->hj_MatchedOuter)
102                 node->hj_NeedNewOuter = true;
103
104         /*
105          * Reset per-tuple memory context to free any expression evaluation
106          * storage allocated in the previous tuple cycle.  Note this can't
107          * happen until we're done projecting out tuples from a join tuple.
108          */
109         ResetExprContext(econtext);
110
111         /*
112          * if this is the first call, build the hash table for inner relation
113          */
114         if (!node->hj_hashdone)
115         {
116                 /*
117                  * create the hash table
118                  */
119                 Assert(hashtable == NULL);
120                 hashtable = ExecHashTableCreate((Hash *) hashNode->ps.plan,
121                                                                                 node->hj_HashOperators);
122                 node->hj_HashTable = hashtable;
123
124                 /*
125                  * execute the Hash node, to build the hash table
126                  */
127                 hashNode->hashtable = hashtable;
128                 (void) ExecProcNode((PlanState *) hashNode);
129
130                 /*
131                  * Open temp files for outer batches, if needed. Note that file
132                  * buffers are palloc'd in regular executor context.
133                  */
134                 for (i = 0; i < hashtable->nbatch; i++)
135                         hashtable->outerBatchFile[i] = BufFileCreateTemp(false);
136
137                 node->hj_hashdone = true;
138         }
139
140         /*
141          * Now get an outer tuple and probe into the hash table for matches
142          */
143         outerTupleSlot = node->js.ps.ps_OuterTupleSlot;
144
145         for (;;)
146         {
147                 /*
148                  * If we don't have an outer tuple, get the next one
149                  */
150                 if (node->hj_NeedNewOuter)
151                 {
152                         outerTupleSlot = ExecHashJoinOuterGetTuple(outerNode,
153                                                                                                            node);
154                         if (TupIsNull(outerTupleSlot))
155                         {
156                                 /* end of join */
157                                 return NULL;
158                         }
159
160                         node->js.ps.ps_OuterTupleSlot = outerTupleSlot;
161                         econtext->ecxt_outertuple = outerTupleSlot;
162                         node->hj_NeedNewOuter = false;
163                         node->hj_MatchedOuter = false;
164
165                         /*
166                          * now we have an outer tuple, find the corresponding bucket
167                          * for this tuple from the hash table
168                          */
169                         node->hj_CurBucketNo = ExecHashGetBucket(hashtable, econtext,
170                                                                                                          outerkeys);
171                         node->hj_CurTuple = NULL;
172
173                         /*
174                          * Now we've got an outer tuple and the corresponding hash
175                          * bucket, but this tuple may not belong to the current batch.
176                          * This need only be checked in the first pass.
177                          */
178                         if (hashtable->curbatch == 0)
179                         {
180                                 int                     batchno = ExecHashGetBatch(node->hj_CurBucketNo,
181                                                                                                            hashtable);
182
183                                 if (batchno >= 0)
184                                 {
185                                         /*
186                                          * Need to postpone this outer tuple to a later batch.
187                                          * Save it in the corresponding outer-batch file.
188                                          */
189                                         hashtable->outerBatchSize[batchno]++;
190                                         ExecHashJoinSaveTuple(outerTupleSlot->val,
191                                                                          hashtable->outerBatchFile[batchno]);
192                                         node->hj_NeedNewOuter = true;
193                                         continue;       /* loop around for a new outer tuple */
194                                 }
195                         }
196                 }
197
198                 /*
199                  * OK, scan the selected hash bucket for matches
200                  */
201                 for (;;)
202                 {
203                         curtuple = ExecScanHashBucket(node,
204                                                                                   hjclauses,
205                                                                                   econtext);
206                         if (curtuple == NULL)
207                                 break;                  /* out of matches */
208
209                         /*
210                          * we've got a match, but still need to test non-hashed quals
211                          */
212                         inntuple = ExecStoreTuple(curtuple,
213                                                                           node->hj_HashTupleSlot,
214                                                                           InvalidBuffer,
215                                                                           false);       /* don't pfree this tuple */
216                         econtext->ecxt_innertuple = inntuple;
217
218                         /* reset temp memory each time to avoid leaks from qual expr */
219                         ResetExprContext(econtext);
220
221                         /*
222                          * if we pass the qual, then save state for next call and have
223                          * ExecProject form the projection, store it in the tuple
224                          * table, and return the slot.
225                          *
226                          * Only the joinquals determine MatchedOuter status, but all
227                          * quals must pass to actually return the tuple.
228                          */
229                         if (ExecQual(joinqual, econtext, false))
230                         {
231                                 node->hj_MatchedOuter = true;
232
233                                 if (otherqual == NIL || ExecQual(otherqual, econtext, false))
234                                 {
235                                         TupleTableSlot *result;
236
237                                         result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
238
239                                         if (isDone != ExprEndResult)
240                                         {
241                                                 node->js.ps.ps_TupFromTlist =
242                                                         (isDone == ExprMultipleResult);
243                                                 return result;
244                                         }
245                                 }
246
247                                 /*
248                                  * If we didn't return a tuple, may need to set
249                                  * NeedNewOuter
250                                  */
251                                 if (node->js.jointype == JOIN_IN)
252                                 {
253                                         node->hj_NeedNewOuter = true;
254                                         break;          /* out of loop over hash bucket */
255                                 }
256                         }
257                 }
258
259                 /*
260                  * Now the current outer tuple has run out of matches, so check
261                  * whether to emit a dummy outer-join tuple. If not, loop around
262                  * to get a new outer tuple.
263                  */
264                 node->hj_NeedNewOuter = true;
265
266                 if (!node->hj_MatchedOuter &&
267                         node->js.jointype == JOIN_LEFT)
268                 {
269                         /*
270                          * We are doing an outer join and there were no join matches
271                          * for this outer tuple.  Generate a fake join tuple with
272                          * nulls for the inner tuple, and return it if it passes the
273                          * non-join quals.
274                          */
275                         econtext->ecxt_innertuple = node->hj_NullInnerTupleSlot;
276
277                         if (ExecQual(otherqual, econtext, false))
278                         {
279                                 /*
280                                  * qualification was satisfied so we project and return
281                                  * the slot containing the result tuple using
282                                  * ExecProject().
283                                  */
284                                 TupleTableSlot *result;
285
286                                 result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
287
288                                 if (isDone != ExprEndResult)
289                                 {
290                                         node->js.ps.ps_TupFromTlist =
291                                                 (isDone == ExprMultipleResult);
292                                         return result;
293                                 }
294                         }
295                 }
296         }
297 }
298
299 /* ----------------------------------------------------------------
300  *              ExecInitHashJoin
301  *
302  *              Init routine for HashJoin node.
303  * ----------------------------------------------------------------
304  */
305 HashJoinState *
306 ExecInitHashJoin(HashJoin *node, EState *estate)
307 {
308         HashJoinState *hjstate;
309         Plan       *outerNode;
310         Hash       *hashNode;
311         List       *hclauses;
312         List       *hoperators;
313         List       *hcl;
314
315         /*
316          * create state structure
317          */
318         hjstate = makeNode(HashJoinState);
319         hjstate->js.ps.plan = (Plan *) node;
320         hjstate->js.ps.state = estate;
321
322         /*
323          * Miscellaneous initialization
324          *
325          * create expression context for node
326          */
327         ExecAssignExprContext(estate, &hjstate->js.ps);
328
329         /*
330          * initialize child expressions
331          */
332         hjstate->js.ps.targetlist = (List *)
333                 ExecInitExpr((Expr *) node->join.plan.targetlist,
334                                          (PlanState *) hjstate);
335         hjstate->js.ps.qual = (List *)
336                 ExecInitExpr((Expr *) node->join.plan.qual,
337                                          (PlanState *) hjstate);
338         hjstate->js.jointype = node->join.jointype;
339         hjstate->js.joinqual = (List *)
340                 ExecInitExpr((Expr *) node->join.joinqual,
341                                          (PlanState *) hjstate);
342         hjstate->hashclauses = (List *)
343                 ExecInitExpr((Expr *) node->hashclauses,
344                                          (PlanState *) hjstate);
345
346         /*
347          * initialize child nodes
348          */
349         outerNode = outerPlan(node);
350         hashNode = (Hash *) innerPlan(node);
351
352         outerPlanState(hjstate) = ExecInitNode(outerNode, estate);
353         innerPlanState(hjstate) = ExecInitNode((Plan *) hashNode, estate);
354
355 #define HASHJOIN_NSLOTS 3
356
357         /*
358          * tuple table initialization
359          */
360         ExecInitResultTupleSlot(estate, &hjstate->js.ps);
361         hjstate->hj_OuterTupleSlot = ExecInitExtraTupleSlot(estate);
362
363         switch (node->join.jointype)
364         {
365                 case JOIN_INNER:
366                 case JOIN_IN:
367                         break;
368                 case JOIN_LEFT:
369                         hjstate->hj_NullInnerTupleSlot =
370                                 ExecInitNullTupleSlot(estate,
371                                                          ExecGetResultType(innerPlanState(hjstate)));
372                         break;
373                 default:
374                         elog(ERROR, "unrecognized join type: %d",
375                                  (int) node->join.jointype);
376         }
377
378         /*
379          * now for some voodoo.  our temporary tuple slot is actually the
380          * result tuple slot of the Hash node (which is our inner plan).  we
381          * do this because Hash nodes don't return tuples via ExecProcNode()
382          * -- instead the hash join node uses ExecScanHashBucket() to get at
383          * the contents of the hash table.      -cim 6/9/91
384          */
385         {
386                 HashState  *hashstate = (HashState *) innerPlanState(hjstate);
387                 TupleTableSlot *slot = hashstate->ps.ps_ResultTupleSlot;
388
389                 hjstate->hj_HashTupleSlot = slot;
390         }
391
392         /*
393          * initialize tuple type and projection info
394          */
395         ExecAssignResultTypeFromTL(&hjstate->js.ps);
396         ExecAssignProjectionInfo(&hjstate->js.ps);
397
398         ExecSetSlotDescriptor(hjstate->hj_OuterTupleSlot,
399                                                   ExecGetResultType(outerPlanState(hjstate)),
400                                                   false);
401
402         /*
403          * initialize hash-specific info
404          */
405
406         hjstate->hj_hashdone = false;
407         hjstate->hj_HashTable = (HashJoinTable) NULL;
408
409         hjstate->hj_CurBucketNo = 0;
410         hjstate->hj_CurTuple = (HashJoinTuple) NULL;
411
412         /*
413          * The planner already made a list of the inner hashkeys for us, but
414          * we also need a list of the outer hashkeys, as well as a list of the
415          * hash operator OIDs.  Both lists of exprs must then be prepared for
416          * execution.
417          */
418         hjstate->hj_InnerHashKeys = (List *)
419                 ExecInitExpr((Expr *) hashNode->hashkeys,
420                                          innerPlanState(hjstate));
421         ((HashState *) innerPlanState(hjstate))->hashkeys =
422                 hjstate->hj_InnerHashKeys;
423
424         hclauses = NIL;
425         hoperators = NIL;
426         foreach(hcl, node->hashclauses)
427         {
428                 OpExpr     *hclause = (OpExpr *) lfirst(hcl);
429
430                 Assert(IsA(hclause, OpExpr));
431                 hclauses = lappend(hclauses, get_leftop((Expr *) hclause));
432                 hoperators = lappendo(hoperators, hclause->opno);
433         }
434         hjstate->hj_OuterHashKeys = (List *)
435                 ExecInitExpr((Expr *) hclauses,
436                                          (PlanState *) hjstate);
437         hjstate->hj_HashOperators = hoperators;
438
439         hjstate->js.ps.ps_OuterTupleSlot = NULL;
440         hjstate->js.ps.ps_TupFromTlist = false;
441         hjstate->hj_NeedNewOuter = true;
442         hjstate->hj_MatchedOuter = false;
443
444         return hjstate;
445 }
446
447 int
448 ExecCountSlotsHashJoin(HashJoin *node)
449 {
450         return ExecCountSlotsNode(outerPlan(node)) +
451                 ExecCountSlotsNode(innerPlan(node)) +
452                 HASHJOIN_NSLOTS;
453 }
454
455 /* ----------------------------------------------------------------
456  *              ExecEndHashJoin
457  *
458  *              clean up routine for HashJoin node
459  * ----------------------------------------------------------------
460  */
461 void
462 ExecEndHashJoin(HashJoinState *node)
463 {
464         /*
465          * Free hash table
466          */
467         if (node->hj_HashTable)
468         {
469                 ExecHashTableDestroy(node->hj_HashTable);
470                 node->hj_HashTable = NULL;
471         }
472
473         /*
474          * Free the exprcontext
475          */
476         ExecFreeExprContext(&node->js.ps);
477
478         /*
479          * clean out the tuple table
480          */
481         ExecClearTuple(node->js.ps.ps_ResultTupleSlot);
482         ExecClearTuple(node->hj_OuterTupleSlot);
483         ExecClearTuple(node->hj_HashTupleSlot);
484
485         /*
486          * clean up subtrees
487          */
488         ExecEndNode(outerPlanState(node));
489         ExecEndNode(innerPlanState(node));
490 }
491
492 /* ----------------------------------------------------------------
493  *              ExecHashJoinOuterGetTuple
494  *
495  *              get the next outer tuple for hashjoin: either by
496  *              executing a plan node as in the first pass, or from
497  *              the tmp files for the hashjoin batches.
498  * ----------------------------------------------------------------
499  */
500
501 static TupleTableSlot *
502 ExecHashJoinOuterGetTuple(PlanState *node, HashJoinState *hjstate)
503 {
504         HashJoinTable hashtable = hjstate->hj_HashTable;
505         int                     curbatch = hashtable->curbatch;
506         TupleTableSlot *slot;
507
508         if (curbatch == 0)
509         {                                                       /* if it is the first pass */
510                 slot = ExecProcNode(node);
511                 if (!TupIsNull(slot))
512                         return slot;
513
514                 /*
515                  * We have just reached the end of the first pass. Try to switch
516                  * to a saved batch.
517                  */
518                 curbatch = ExecHashJoinNewBatch(hjstate);
519         }
520
521         /*
522          * Try to read from a temp file. Loop allows us to advance to new
523          * batch as needed.
524          */
525         while (curbatch <= hashtable->nbatch)
526         {
527                 slot = ExecHashJoinGetSavedTuple(hjstate,
528                                                                  hashtable->outerBatchFile[curbatch - 1],
529                                                                                  hjstate->hj_OuterTupleSlot);
530                 if (!TupIsNull(slot))
531                         return slot;
532                 curbatch = ExecHashJoinNewBatch(hjstate);
533         }
534
535         /* Out of batches... */
536         return NULL;
537 }
538
539 /* ----------------------------------------------------------------
540  *              ExecHashJoinGetSavedTuple
541  *
542  *              read the next tuple from a tmp file
543  * ----------------------------------------------------------------
544  */
545
546 static TupleTableSlot *
547 ExecHashJoinGetSavedTuple(HashJoinState *hjstate,
548                                                   BufFile *file,
549                                                   TupleTableSlot *tupleSlot)
550 {
551         HeapTupleData htup;
552         size_t          nread;
553         HeapTuple       heapTuple;
554
555         nread = BufFileRead(file, (void *) &htup, sizeof(HeapTupleData));
556         if (nread == 0)
557                 return NULL;                    /* end of file */
558         if (nread != sizeof(HeapTupleData))
559                 ereport(ERROR,
560                                 (errcode_for_file_access(),
561                                  errmsg("could not read from hash-join temporary file: %m")));
562         heapTuple = palloc(HEAPTUPLESIZE + htup.t_len);
563         memcpy((char *) heapTuple, (char *) &htup, sizeof(HeapTupleData));
564         heapTuple->t_datamcxt = CurrentMemoryContext;
565         heapTuple->t_data = (HeapTupleHeader)
566                 ((char *) heapTuple + HEAPTUPLESIZE);
567         nread = BufFileRead(file, (void *) heapTuple->t_data, htup.t_len);
568         if (nread != (size_t) htup.t_len)
569                 ereport(ERROR,
570                                 (errcode_for_file_access(),
571                                  errmsg("could not read from hash-join temporary file: %m")));
572         return ExecStoreTuple(heapTuple, tupleSlot, InvalidBuffer, true);
573 }
574
575 /* ----------------------------------------------------------------
576  *              ExecHashJoinNewBatch
577  *
578  *              switch to a new hashjoin batch
579  * ----------------------------------------------------------------
580  */
581 static int
582 ExecHashJoinNewBatch(HashJoinState *hjstate)
583 {
584         HashJoinTable hashtable = hjstate->hj_HashTable;
585         int                     nbatch = hashtable->nbatch;
586         int                     newbatch = hashtable->curbatch + 1;
587         long       *innerBatchSize = hashtable->innerBatchSize;
588         long       *outerBatchSize = hashtable->outerBatchSize;
589         BufFile    *innerFile;
590         TupleTableSlot *slot;
591         ExprContext *econtext;
592         List       *innerhashkeys;
593
594         if (newbatch > 1)
595         {
596                 /*
597                  * We no longer need the previous outer batch file; close it right
598                  * away to free disk space.
599                  */
600                 BufFileClose(hashtable->outerBatchFile[newbatch - 2]);
601                 hashtable->outerBatchFile[newbatch - 2] = NULL;
602         }
603
604         /*
605          * We can skip over any batches that are empty on either side. Release
606          * associated temp files right away.
607          */
608         while (newbatch <= nbatch &&
609                    (innerBatchSize[newbatch - 1] == 0L ||
610                         outerBatchSize[newbatch - 1] == 0L))
611         {
612                 BufFileClose(hashtable->innerBatchFile[newbatch - 1]);
613                 hashtable->innerBatchFile[newbatch - 1] = NULL;
614                 BufFileClose(hashtable->outerBatchFile[newbatch - 1]);
615                 hashtable->outerBatchFile[newbatch - 1] = NULL;
616                 newbatch++;
617         }
618
619         if (newbatch > nbatch)
620                 return newbatch;                /* no more batches */
621
622         /*
623          * Rewind inner and outer batch files for this batch, so that we can
624          * start reading them.
625          */
626         if (BufFileSeek(hashtable->outerBatchFile[newbatch - 1], 0, 0L, SEEK_SET))
627                 ereport(ERROR,
628                                 (errcode_for_file_access(),
629                                  errmsg("could not rewind hash-join temporary file: %m")));
630
631         innerFile = hashtable->innerBatchFile[newbatch - 1];
632
633         if (BufFileSeek(innerFile, 0, 0L, SEEK_SET))
634                 ereport(ERROR,
635                                 (errcode_for_file_access(),
636                                  errmsg("could not rewind hash-join temporary file: %m")));
637
638         /*
639          * Reload the hash table with the new inner batch
640          */
641         ExecHashTableReset(hashtable, innerBatchSize[newbatch - 1]);
642
643         econtext = hjstate->js.ps.ps_ExprContext;
644         innerhashkeys = hjstate->hj_InnerHashKeys;
645
646         while ((slot = ExecHashJoinGetSavedTuple(hjstate,
647                                                                                          innerFile,
648                                                                                          hjstate->hj_HashTupleSlot))
649                    && !TupIsNull(slot))
650         {
651                 econtext->ecxt_innertuple = slot;
652                 ExecHashTableInsert(hashtable, econtext, innerhashkeys);
653         }
654
655         /*
656          * after we build the hash table, the inner batch file is no longer
657          * needed
658          */
659         BufFileClose(innerFile);
660         hashtable->innerBatchFile[newbatch - 1] = NULL;
661
662         hashtable->curbatch = newbatch;
663         return newbatch;
664 }
665
666 /* ----------------------------------------------------------------
667  *              ExecHashJoinSaveTuple
668  *
669  *              save a tuple to a tmp file.
670  *
671  * The data recorded in the file for each tuple is an image of its
672  * HeapTupleData (with meaningless t_data pointer) followed by the
673  * HeapTupleHeader and tuple data.
674  * ----------------------------------------------------------------
675  */
676
677 void
678 ExecHashJoinSaveTuple(HeapTuple heapTuple,
679                                           BufFile *file)
680 {
681         size_t          written;
682
683         written = BufFileWrite(file, (void *) heapTuple, sizeof(HeapTupleData));
684         if (written != sizeof(HeapTupleData))
685                 ereport(ERROR,
686                                 (errcode_for_file_access(),
687                                  errmsg("could not write to hash-join temporary file: %m")));
688         written = BufFileWrite(file, (void *) heapTuple->t_data, heapTuple->t_len);
689         if (written != (size_t) heapTuple->t_len)
690                 ereport(ERROR,
691                                 (errcode_for_file_access(),
692                                  errmsg("could not write to hash-join temporary file: %m")));
693 }
694
695 void
696 ExecReScanHashJoin(HashJoinState *node, ExprContext *exprCtxt)
697 {
698         /*
699          * If we haven't yet built the hash table then we can just return;
700          * nothing done yet, so nothing to undo.
701          */
702         if (!node->hj_hashdone)
703                 return;
704         Assert(node->hj_HashTable != NULL);
705
706         /*
707          * In a multi-batch join, we currently have to do rescans the hard
708          * way, primarily because batch temp files may have already been
709          * released. But if it's a single-batch join, and there is no
710          * parameter change for the inner subnode, then we can just re-use the
711          * existing hash table without rebuilding it.
712          */
713         if (node->hj_HashTable->nbatch == 0 &&
714                 ((PlanState *) node)->righttree->chgParam == NULL)
715         {
716                 /* okay to reuse the hash table; needn't rescan inner, either */
717         }
718         else
719         {
720                 /* must destroy and rebuild hash table */
721                 node->hj_hashdone = false;
722                 ExecHashTableDestroy(node->hj_HashTable);
723                 node->hj_HashTable = NULL;
724
725                 /*
726                  * if chgParam of subnode is not null then plan will be re-scanned
727                  * by first ExecProcNode.
728                  */
729                 if (((PlanState *) node)->righttree->chgParam == NULL)
730                         ExecReScan(((PlanState *) node)->righttree, exprCtxt);
731         }
732
733         /* Always reset intra-tuple state */
734         node->hj_CurBucketNo = 0;
735         node->hj_CurTuple = (HashJoinTuple) NULL;
736
737         node->js.ps.ps_OuterTupleSlot = (TupleTableSlot *) NULL;
738         node->js.ps.ps_TupFromTlist = false;
739         node->hj_NeedNewOuter = true;
740         node->hj_MatchedOuter = false;
741
742         /*
743          * if chgParam of subnode is not null then plan will be re-scanned by
744          * first ExecProcNode.
745          */
746         if (((PlanState *) node)->lefttree->chgParam == NULL)
747                 ExecReScan(((PlanState *) node)->lefttree, exprCtxt);
748 }