]> granicus.if.org Git - postgresql/blob - src/backend/executor/nodeHashjoin.c
Get rid of hashkeys field of Hash plan node, since it's redundant with
[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.58 2003/11/25 21:00:52 tgl 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       *lclauses;
312         List       *rclauses;
313         List       *hoperators;
314         List       *hcl;
315
316         /*
317          * create state structure
318          */
319         hjstate = makeNode(HashJoinState);
320         hjstate->js.ps.plan = (Plan *) node;
321         hjstate->js.ps.state = estate;
322
323         /*
324          * Miscellaneous initialization
325          *
326          * create expression context for node
327          */
328         ExecAssignExprContext(estate, &hjstate->js.ps);
329
330         /*
331          * initialize child expressions
332          */
333         hjstate->js.ps.targetlist = (List *)
334                 ExecInitExpr((Expr *) node->join.plan.targetlist,
335                                          (PlanState *) hjstate);
336         hjstate->js.ps.qual = (List *)
337                 ExecInitExpr((Expr *) node->join.plan.qual,
338                                          (PlanState *) hjstate);
339         hjstate->js.jointype = node->join.jointype;
340         hjstate->js.joinqual = (List *)
341                 ExecInitExpr((Expr *) node->join.joinqual,
342                                          (PlanState *) hjstate);
343         hjstate->hashclauses = (List *)
344                 ExecInitExpr((Expr *) node->hashclauses,
345                                          (PlanState *) hjstate);
346
347         /*
348          * initialize child nodes
349          */
350         outerNode = outerPlan(node);
351         hashNode = (Hash *) innerPlan(node);
352
353         outerPlanState(hjstate) = ExecInitNode(outerNode, estate);
354         innerPlanState(hjstate) = ExecInitNode((Plan *) hashNode, estate);
355
356 #define HASHJOIN_NSLOTS 3
357
358         /*
359          * tuple table initialization
360          */
361         ExecInitResultTupleSlot(estate, &hjstate->js.ps);
362         hjstate->hj_OuterTupleSlot = ExecInitExtraTupleSlot(estate);
363
364         switch (node->join.jointype)
365         {
366                 case JOIN_INNER:
367                 case JOIN_IN:
368                         break;
369                 case JOIN_LEFT:
370                         hjstate->hj_NullInnerTupleSlot =
371                                 ExecInitNullTupleSlot(estate,
372                                                          ExecGetResultType(innerPlanState(hjstate)));
373                         break;
374                 default:
375                         elog(ERROR, "unrecognized join type: %d",
376                                  (int) node->join.jointype);
377         }
378
379         /*
380          * now for some voodoo.  our temporary tuple slot is actually the
381          * result tuple slot of the Hash node (which is our inner plan).  we
382          * do this because Hash nodes don't return tuples via ExecProcNode()
383          * -- instead the hash join node uses ExecScanHashBucket() to get at
384          * the contents of the hash table.      -cim 6/9/91
385          */
386         {
387                 HashState  *hashstate = (HashState *) innerPlanState(hjstate);
388                 TupleTableSlot *slot = hashstate->ps.ps_ResultTupleSlot;
389
390                 hjstate->hj_HashTupleSlot = slot;
391         }
392
393         /*
394          * initialize tuple type and projection info
395          */
396         ExecAssignResultTypeFromTL(&hjstate->js.ps);
397         ExecAssignProjectionInfo(&hjstate->js.ps);
398
399         ExecSetSlotDescriptor(hjstate->hj_OuterTupleSlot,
400                                                   ExecGetResultType(outerPlanState(hjstate)),
401                                                   false);
402
403         /*
404          * initialize hash-specific info
405          */
406
407         hjstate->hj_hashdone = false;
408         hjstate->hj_HashTable = (HashJoinTable) NULL;
409
410         hjstate->hj_CurBucketNo = 0;
411         hjstate->hj_CurTuple = (HashJoinTuple) NULL;
412
413         /*
414          * Deconstruct the hash clauses into outer and inner argument values,
415          * so that we can evaluate those subexpressions separately.  Also make
416          * a list of the hash operator OIDs, in preparation for looking up the
417          * hash functions to use.
418          */
419         lclauses = NIL;
420         rclauses = NIL;
421         hoperators = NIL;
422         foreach(hcl, hjstate->hashclauses)
423         {
424                 FuncExprState *fstate = (FuncExprState *) lfirst(hcl);
425                 OpExpr     *hclause;
426
427                 Assert(IsA(fstate, FuncExprState));
428                 hclause = (OpExpr *) fstate->xprstate.expr;
429                 Assert(IsA(hclause, OpExpr));
430                 lclauses = lappend(lclauses, lfirst(fstate->args));
431                 rclauses = lappend(rclauses, lsecond(fstate->args));
432                 hoperators = lappendo(hoperators, hclause->opno);
433         }
434         hjstate->hj_OuterHashKeys = lclauses;
435         hjstate->hj_InnerHashKeys = rclauses;
436         hjstate->hj_HashOperators = hoperators;
437         /* child Hash node needs to evaluate inner hash keys, too */
438         ((HashState *) innerPlanState(hjstate))->hashkeys = rclauses;
439
440         hjstate->js.ps.ps_OuterTupleSlot = NULL;
441         hjstate->js.ps.ps_TupFromTlist = false;
442         hjstate->hj_NeedNewOuter = true;
443         hjstate->hj_MatchedOuter = false;
444
445         return hjstate;
446 }
447
448 int
449 ExecCountSlotsHashJoin(HashJoin *node)
450 {
451         return ExecCountSlotsNode(outerPlan(node)) +
452                 ExecCountSlotsNode(innerPlan(node)) +
453                 HASHJOIN_NSLOTS;
454 }
455
456 /* ----------------------------------------------------------------
457  *              ExecEndHashJoin
458  *
459  *              clean up routine for HashJoin node
460  * ----------------------------------------------------------------
461  */
462 void
463 ExecEndHashJoin(HashJoinState *node)
464 {
465         /*
466          * Free hash table
467          */
468         if (node->hj_HashTable)
469         {
470                 ExecHashTableDestroy(node->hj_HashTable);
471                 node->hj_HashTable = NULL;
472         }
473
474         /*
475          * Free the exprcontext
476          */
477         ExecFreeExprContext(&node->js.ps);
478
479         /*
480          * clean out the tuple table
481          */
482         ExecClearTuple(node->js.ps.ps_ResultTupleSlot);
483         ExecClearTuple(node->hj_OuterTupleSlot);
484         ExecClearTuple(node->hj_HashTupleSlot);
485
486         /*
487          * clean up subtrees
488          */
489         ExecEndNode(outerPlanState(node));
490         ExecEndNode(innerPlanState(node));
491 }
492
493 /* ----------------------------------------------------------------
494  *              ExecHashJoinOuterGetTuple
495  *
496  *              get the next outer tuple for hashjoin: either by
497  *              executing a plan node as in the first pass, or from
498  *              the tmp files for the hashjoin batches.
499  * ----------------------------------------------------------------
500  */
501
502 static TupleTableSlot *
503 ExecHashJoinOuterGetTuple(PlanState *node, HashJoinState *hjstate)
504 {
505         HashJoinTable hashtable = hjstate->hj_HashTable;
506         int                     curbatch = hashtable->curbatch;
507         TupleTableSlot *slot;
508
509         if (curbatch == 0)
510         {                                                       /* if it is the first pass */
511                 slot = ExecProcNode(node);
512                 if (!TupIsNull(slot))
513                         return slot;
514
515                 /*
516                  * We have just reached the end of the first pass. Try to switch
517                  * to a saved batch.
518                  */
519                 curbatch = ExecHashJoinNewBatch(hjstate);
520         }
521
522         /*
523          * Try to read from a temp file. Loop allows us to advance to new
524          * batch as needed.
525          */
526         while (curbatch <= hashtable->nbatch)
527         {
528                 slot = ExecHashJoinGetSavedTuple(hjstate,
529                                                                  hashtable->outerBatchFile[curbatch - 1],
530                                                                                  hjstate->hj_OuterTupleSlot);
531                 if (!TupIsNull(slot))
532                         return slot;
533                 curbatch = ExecHashJoinNewBatch(hjstate);
534         }
535
536         /* Out of batches... */
537         return NULL;
538 }
539
540 /* ----------------------------------------------------------------
541  *              ExecHashJoinGetSavedTuple
542  *
543  *              read the next tuple from a tmp file
544  * ----------------------------------------------------------------
545  */
546
547 static TupleTableSlot *
548 ExecHashJoinGetSavedTuple(HashJoinState *hjstate,
549                                                   BufFile *file,
550                                                   TupleTableSlot *tupleSlot)
551 {
552         HeapTupleData htup;
553         size_t          nread;
554         HeapTuple       heapTuple;
555
556         nread = BufFileRead(file, (void *) &htup, sizeof(HeapTupleData));
557         if (nread == 0)
558                 return NULL;                    /* end of file */
559         if (nread != sizeof(HeapTupleData))
560                 ereport(ERROR,
561                                 (errcode_for_file_access(),
562                                  errmsg("could not read from hash-join temporary file: %m")));
563         heapTuple = palloc(HEAPTUPLESIZE + htup.t_len);
564         memcpy((char *) heapTuple, (char *) &htup, sizeof(HeapTupleData));
565         heapTuple->t_datamcxt = CurrentMemoryContext;
566         heapTuple->t_data = (HeapTupleHeader)
567                 ((char *) heapTuple + HEAPTUPLESIZE);
568         nread = BufFileRead(file, (void *) heapTuple->t_data, htup.t_len);
569         if (nread != (size_t) htup.t_len)
570                 ereport(ERROR,
571                                 (errcode_for_file_access(),
572                                  errmsg("could not read from hash-join temporary file: %m")));
573         return ExecStoreTuple(heapTuple, tupleSlot, InvalidBuffer, true);
574 }
575
576 /* ----------------------------------------------------------------
577  *              ExecHashJoinNewBatch
578  *
579  *              switch to a new hashjoin batch
580  * ----------------------------------------------------------------
581  */
582 static int
583 ExecHashJoinNewBatch(HashJoinState *hjstate)
584 {
585         HashJoinTable hashtable = hjstate->hj_HashTable;
586         int                     nbatch = hashtable->nbatch;
587         int                     newbatch = hashtable->curbatch + 1;
588         long       *innerBatchSize = hashtable->innerBatchSize;
589         long       *outerBatchSize = hashtable->outerBatchSize;
590         BufFile    *innerFile;
591         TupleTableSlot *slot;
592         ExprContext *econtext;
593         List       *innerhashkeys;
594
595         if (newbatch > 1)
596         {
597                 /*
598                  * We no longer need the previous outer batch file; close it right
599                  * away to free disk space.
600                  */
601                 BufFileClose(hashtable->outerBatchFile[newbatch - 2]);
602                 hashtable->outerBatchFile[newbatch - 2] = NULL;
603         }
604
605         /*
606          * We can skip over any batches that are empty on either side. Release
607          * associated temp files right away.
608          */
609         while (newbatch <= nbatch &&
610                    (innerBatchSize[newbatch - 1] == 0L ||
611                         outerBatchSize[newbatch - 1] == 0L))
612         {
613                 BufFileClose(hashtable->innerBatchFile[newbatch - 1]);
614                 hashtable->innerBatchFile[newbatch - 1] = NULL;
615                 BufFileClose(hashtable->outerBatchFile[newbatch - 1]);
616                 hashtable->outerBatchFile[newbatch - 1] = NULL;
617                 newbatch++;
618         }
619
620         if (newbatch > nbatch)
621                 return newbatch;                /* no more batches */
622
623         /*
624          * Rewind inner and outer batch files for this batch, so that we can
625          * start reading them.
626          */
627         if (BufFileSeek(hashtable->outerBatchFile[newbatch - 1], 0, 0L, SEEK_SET))
628                 ereport(ERROR,
629                                 (errcode_for_file_access(),
630                                  errmsg("could not rewind hash-join temporary file: %m")));
631
632         innerFile = hashtable->innerBatchFile[newbatch - 1];
633
634         if (BufFileSeek(innerFile, 0, 0L, SEEK_SET))
635                 ereport(ERROR,
636                                 (errcode_for_file_access(),
637                                  errmsg("could not rewind hash-join temporary file: %m")));
638
639         /*
640          * Reload the hash table with the new inner batch
641          */
642         ExecHashTableReset(hashtable, innerBatchSize[newbatch - 1]);
643
644         econtext = hjstate->js.ps.ps_ExprContext;
645         innerhashkeys = hjstate->hj_InnerHashKeys;
646
647         while ((slot = ExecHashJoinGetSavedTuple(hjstate,
648                                                                                          innerFile,
649                                                                                          hjstate->hj_HashTupleSlot))
650                    && !TupIsNull(slot))
651         {
652                 econtext->ecxt_innertuple = slot;
653                 ExecHashTableInsert(hashtable, econtext, innerhashkeys);
654         }
655
656         /*
657          * after we build the hash table, the inner batch file is no longer
658          * needed
659          */
660         BufFileClose(innerFile);
661         hashtable->innerBatchFile[newbatch - 1] = NULL;
662
663         hashtable->curbatch = newbatch;
664         return newbatch;
665 }
666
667 /* ----------------------------------------------------------------
668  *              ExecHashJoinSaveTuple
669  *
670  *              save a tuple to a tmp file.
671  *
672  * The data recorded in the file for each tuple is an image of its
673  * HeapTupleData (with meaningless t_data pointer) followed by the
674  * HeapTupleHeader and tuple data.
675  * ----------------------------------------------------------------
676  */
677
678 void
679 ExecHashJoinSaveTuple(HeapTuple heapTuple,
680                                           BufFile *file)
681 {
682         size_t          written;
683
684         written = BufFileWrite(file, (void *) heapTuple, sizeof(HeapTupleData));
685         if (written != sizeof(HeapTupleData))
686                 ereport(ERROR,
687                                 (errcode_for_file_access(),
688                                  errmsg("could not write to hash-join temporary file: %m")));
689         written = BufFileWrite(file, (void *) heapTuple->t_data, heapTuple->t_len);
690         if (written != (size_t) heapTuple->t_len)
691                 ereport(ERROR,
692                                 (errcode_for_file_access(),
693                                  errmsg("could not write to hash-join temporary file: %m")));
694 }
695
696 void
697 ExecReScanHashJoin(HashJoinState *node, ExprContext *exprCtxt)
698 {
699         /*
700          * If we haven't yet built the hash table then we can just return;
701          * nothing done yet, so nothing to undo.
702          */
703         if (!node->hj_hashdone)
704                 return;
705         Assert(node->hj_HashTable != NULL);
706
707         /*
708          * In a multi-batch join, we currently have to do rescans the hard
709          * way, primarily because batch temp files may have already been
710          * released. But if it's a single-batch join, and there is no
711          * parameter change for the inner subnode, then we can just re-use the
712          * existing hash table without rebuilding it.
713          */
714         if (node->hj_HashTable->nbatch == 0 &&
715                 ((PlanState *) node)->righttree->chgParam == NULL)
716         {
717                 /* okay to reuse the hash table; needn't rescan inner, either */
718         }
719         else
720         {
721                 /* must destroy and rebuild hash table */
722                 node->hj_hashdone = false;
723                 ExecHashTableDestroy(node->hj_HashTable);
724                 node->hj_HashTable = NULL;
725
726                 /*
727                  * if chgParam of subnode is not null then plan will be re-scanned
728                  * by first ExecProcNode.
729                  */
730                 if (((PlanState *) node)->righttree->chgParam == NULL)
731                         ExecReScan(((PlanState *) node)->righttree, exprCtxt);
732         }
733
734         /* Always reset intra-tuple state */
735         node->hj_CurBucketNo = 0;
736         node->hj_CurTuple = (HashJoinTuple) NULL;
737
738         node->js.ps.ps_OuterTupleSlot = (TupleTableSlot *) NULL;
739         node->js.ps.ps_TupFromTlist = false;
740         node->hj_NeedNewOuter = true;
741         node->hj_MatchedOuter = false;
742
743         /*
744          * if chgParam of subnode is not null then plan will be re-scanned by
745          * first ExecProcNode.
746          */
747         if (((PlanState *) node)->lefttree->chgParam == NULL)
748                 ExecReScan(((PlanState *) node)->lefttree, exprCtxt);
749 }