diff options
Diffstat (limited to 'src/backend/executor/nodeHashjoin.c')
-rw-r--r-- | src/backend/executor/nodeHashjoin.c | 576 |
1 files changed, 332 insertions, 244 deletions
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index dfca8cac522..cae31ba43a9 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -22,8 +22,20 @@ #include "utils/memutils.h" -/* Returns true for JOIN_LEFT and JOIN_ANTI jointypes */ -#define HASHJOIN_IS_OUTER(hjstate) ((hjstate)->hj_NullInnerTupleSlot != NULL) +/* + * States of the ExecHashJoin state machine + */ +#define HJ_BUILD_HASHTABLE 1 +#define HJ_NEED_NEW_OUTER 2 +#define HJ_SCAN_BUCKET 3 +#define HJ_FILL_OUTER_TUPLE 4 +#define HJ_FILL_INNER_TUPLES 5 +#define HJ_NEED_NEW_BATCH 6 + +/* Returns true if doing null-fill on outer relation */ +#define HJ_FILL_OUTER(hjstate) ((hjstate)->hj_NullInnerTupleSlot != NULL) +/* Returns true if doing null-fill on inner relation */ +#define HJ_FILL_INNER(hjstate) ((hjstate)->hj_NullOuterTupleSlot != NULL) static TupleTableSlot *ExecHashJoinOuterGetTuple(PlanState *outerNode, HashJoinState *hjstate, @@ -32,7 +44,7 @@ static TupleTableSlot *ExecHashJoinGetSavedTuple(HashJoinState *hjstate, BufFile *file, uint32 *hashvalue, TupleTableSlot *tupleSlot); -static int ExecHashJoinNewBatch(HashJoinState *hjstate); +static bool ExecHashJoinNewBatch(HashJoinState *hjstate); /* ---------------------------------------------------------------- @@ -52,11 +64,9 @@ ExecHashJoin(HashJoinState *node) HashState *hashNode; List *joinqual; List *otherqual; - TupleTableSlot *inntuple; ExprContext *econtext; ExprDoneCond isDone; HashJoinTable hashtable; - HashJoinTuple curtuple; TupleTableSlot *outerTupleSlot; uint32 hashvalue; int batchno; @@ -69,10 +79,6 @@ ExecHashJoin(HashJoinState *node) otherqual = node->js.ps.qual; hashNode = (HashState *) innerPlanState(node); outerNode = outerPlanState(node); - - /* - * get information from HashJoin state - */ hashtable = node->hj_HashTable; econtext = node->js.ps.ps_ExprContext; @@ -100,238 +106,300 @@ ExecHashJoin(HashJoinState *node) ResetExprContext(econtext); /* - * if this is the first call, build the hash table for inner relation + * run the hash join state machine */ - if (hashtable == NULL) + for (;;) { - /* - * If the outer relation is completely empty, we can quit without - * building the hash table. However, for an inner join it is only a - * win to check this when the outer relation's startup cost is less - * than the projected cost of building the hash table. Otherwise it's - * best to build the hash table first and see if the inner relation is - * empty. (When it's an outer join, we should always make this check, - * since we aren't going to be able to skip the join on the strength - * of an empty inner relation anyway.) - * - * If we are rescanning the join, we make use of information gained on - * the previous scan: don't bother to try the prefetch if the previous - * scan found the outer relation nonempty. This is not 100% reliable - * since with new parameters the outer relation might yield different - * results, but it's a good heuristic. - * - * The only way to make the check is to try to fetch a tuple from the - * outer plan node. If we succeed, we have to stash it away for later - * consumption by ExecHashJoinOuterGetTuple. - */ - if (HASHJOIN_IS_OUTER(node) || - (outerNode->plan->startup_cost < hashNode->ps.plan->total_cost && - !node->hj_OuterNotEmpty)) + switch (node->hj_JoinState) { - node->hj_FirstOuterTupleSlot = ExecProcNode(outerNode); - if (TupIsNull(node->hj_FirstOuterTupleSlot)) - { - node->hj_OuterNotEmpty = false; - return NULL; - } - else - node->hj_OuterNotEmpty = true; - } - else - node->hj_FirstOuterTupleSlot = NULL; + case HJ_BUILD_HASHTABLE: + /* + * First time through: build hash table for inner relation. + */ + Assert(hashtable == NULL); - /* - * create the hash table - */ - hashtable = ExecHashTableCreate((Hash *) hashNode->ps.plan, - node->hj_HashOperators); - node->hj_HashTable = hashtable; + /* + * If the outer relation is completely empty, and it's not + * right/full join, we can quit without building the hash + * table. However, for an inner join it is only a win to + * check this when the outer relation's startup cost is less + * than the projected cost of building the hash + * table. Otherwise it's best to build the hash table first + * and see if the inner relation is empty. (When it's a left + * join, we should always make this check, since we aren't + * going to be able to skip the join on the strength of an + * empty inner relation anyway.) + * + * If we are rescanning the join, we make use of information + * gained on the previous scan: don't bother to try the + * prefetch if the previous scan found the outer relation + * nonempty. This is not 100% reliable since with new + * parameters the outer relation might yield different + * results, but it's a good heuristic. + * + * The only way to make the check is to try to fetch a tuple + * from the outer plan node. If we succeed, we have to stash + * it away for later consumption by ExecHashJoinOuterGetTuple. + */ + if (HJ_FILL_INNER(node)) + { + /* no chance to not build the hash table */ + node->hj_FirstOuterTupleSlot = NULL; + } + else if (HJ_FILL_OUTER(node) || + (outerNode->plan->startup_cost < hashNode->ps.plan->total_cost && + !node->hj_OuterNotEmpty)) + { + node->hj_FirstOuterTupleSlot = ExecProcNode(outerNode); + if (TupIsNull(node->hj_FirstOuterTupleSlot)) + { + node->hj_OuterNotEmpty = false; + return NULL; + } + else + node->hj_OuterNotEmpty = true; + } + else + node->hj_FirstOuterTupleSlot = NULL; - /* - * execute the Hash node, to build the hash table - */ - hashNode->hashtable = hashtable; - (void) MultiExecProcNode((PlanState *) hashNode); + /* + * create the hash table + */ + hashtable = ExecHashTableCreate((Hash *) hashNode->ps.plan, + node->hj_HashOperators, + HJ_FILL_INNER(node)); + node->hj_HashTable = hashtable; - /* - * If the inner relation is completely empty, and we're not doing an - * outer join, we can quit without scanning the outer relation. - */ - if (hashtable->totalTuples == 0 && !HASHJOIN_IS_OUTER(node)) - return NULL; + /* + * execute the Hash node, to build the hash table + */ + hashNode->hashtable = hashtable; + (void) MultiExecProcNode((PlanState *) hashNode); - /* - * need to remember whether nbatch has increased since we began - * scanning the outer relation - */ - hashtable->nbatch_outstart = hashtable->nbatch; + /* + * If the inner relation is completely empty, and we're not + * doing a left outer join, we can quit without scanning the + * outer relation. + */ + if (hashtable->totalTuples == 0 && !HJ_FILL_OUTER(node)) + return NULL; - /* - * Reset OuterNotEmpty for scan. (It's OK if we fetched a tuple - * above, because ExecHashJoinOuterGetTuple will immediately set it - * again.) - */ - node->hj_OuterNotEmpty = false; - } + /* + * need to remember whether nbatch has increased since we began + * scanning the outer relation + */ + hashtable->nbatch_outstart = hashtable->nbatch; - /* - * run the hash join process - */ - for (;;) - { - /* - * If we don't have an outer tuple, get the next one - */ - if (node->hj_NeedNewOuter) - { - outerTupleSlot = ExecHashJoinOuterGetTuple(outerNode, - node, - &hashvalue); - if (TupIsNull(outerTupleSlot)) - { - /* end of join */ - return NULL; - } + /* + * Reset OuterNotEmpty for scan. (It's OK if we fetched a + * tuple above, because ExecHashJoinOuterGetTuple will + * immediately set it again.) + */ + node->hj_OuterNotEmpty = false; - econtext->ecxt_outertuple = outerTupleSlot; - node->hj_NeedNewOuter = false; - node->hj_MatchedOuter = false; + node->hj_JoinState = HJ_NEED_NEW_OUTER; - /* - * Now we have an outer tuple; find the corresponding bucket for - * this tuple in the main hash table or skew hash table. - */ - node->hj_CurHashValue = hashvalue; - ExecHashGetBucketAndBatch(hashtable, hashvalue, - &node->hj_CurBucketNo, &batchno); - node->hj_CurSkewBucketNo = ExecHashGetSkewBucket(hashtable, - hashvalue); - node->hj_CurTuple = NULL; + /* FALL THRU */ - /* - * Now we've got an outer tuple and the corresponding hash bucket, - * but it might not belong to the current batch, or it might match - * a skew bucket. - */ - if (batchno != hashtable->curbatch && - node->hj_CurSkewBucketNo == INVALID_SKEW_BUCKET_NO) - { + case HJ_NEED_NEW_OUTER: /* - * Need to postpone this outer tuple to a later batch. Save it - * in the corresponding outer-batch file. + * We don't have an outer tuple, try to get the next one */ - Assert(batchno > hashtable->curbatch); - ExecHashJoinSaveTuple(ExecFetchSlotMinimalTuple(outerTupleSlot), - hashvalue, - &hashtable->outerBatchFile[batchno]); - node->hj_NeedNewOuter = true; - continue; /* loop around for a new outer tuple */ - } - } + outerTupleSlot = ExecHashJoinOuterGetTuple(outerNode, + node, + &hashvalue); + if (TupIsNull(outerTupleSlot)) + { + /* end of batch, or maybe whole join */ + if (HJ_FILL_INNER(node)) + { + /* set up to scan for unmatched inner tuples */ + ExecPrepHashTableForUnmatched(node); + node->hj_JoinState = HJ_FILL_INNER_TUPLES; + } + else + node->hj_JoinState = HJ_NEED_NEW_BATCH; + continue; + } - /* - * OK, scan the selected hash bucket for matches - */ - for (;;) - { - curtuple = ExecScanHashBucket(node, econtext); - if (curtuple == NULL) - break; /* out of matches */ + econtext->ecxt_outertuple = outerTupleSlot; + node->hj_MatchedOuter = false; - /* - * we've got a match, but still need to test non-hashed quals - */ - inntuple = ExecStoreMinimalTuple(HJTUPLE_MINTUPLE(curtuple), - node->hj_HashTupleSlot, - false); /* don't pfree */ - econtext->ecxt_innertuple = inntuple; + /* + * Find the corresponding bucket for this tuple in the main + * hash table or skew hash table. + */ + node->hj_CurHashValue = hashvalue; + ExecHashGetBucketAndBatch(hashtable, hashvalue, + &node->hj_CurBucketNo, &batchno); + node->hj_CurSkewBucketNo = ExecHashGetSkewBucket(hashtable, + hashvalue); + node->hj_CurTuple = NULL; - /* reset temp memory each time to avoid leaks from qual expr */ - ResetExprContext(econtext); + /* + * The tuple might not belong to the current batch (where + * "current batch" includes the skew buckets if any). + */ + if (batchno != hashtable->curbatch && + node->hj_CurSkewBucketNo == INVALID_SKEW_BUCKET_NO) + { + /* + * Need to postpone this outer tuple to a later batch. + * Save it in the corresponding outer-batch file. + */ + Assert(batchno > hashtable->curbatch); + ExecHashJoinSaveTuple(ExecFetchSlotMinimalTuple(outerTupleSlot), + hashvalue, + &hashtable->outerBatchFile[batchno]); + /* Loop around, staying in HJ_NEED_NEW_OUTER state */ + continue; + } - /* - * if we pass the qual, then save state for next call and have - * ExecProject form the projection, store it in the tuple table, - * and return the slot. - * - * Only the joinquals determine MatchedOuter status, but all quals - * must pass to actually return the tuple. - */ - if (joinqual == NIL || ExecQual(joinqual, econtext, false)) - { - node->hj_MatchedOuter = true; + /* OK, let's scan the bucket for matches */ + node->hj_JoinState = HJ_SCAN_BUCKET; + + /* FALL THRU */ - /* In an antijoin, we never return a matched tuple */ - if (node->js.jointype == JOIN_ANTI) + case HJ_SCAN_BUCKET: + /* + * Scan the selected hash bucket for matches to current outer + */ + if (!ExecScanHashBucket(node, econtext)) { - node->hj_NeedNewOuter = true; - break; /* out of loop over hash bucket */ + /* out of matches; check for possible outer-join fill */ + node->hj_JoinState = HJ_FILL_OUTER_TUPLE; + continue; } /* - * In a semijoin, we'll consider returning the first match, - * but after that we're done with this outer tuple. + * We've got a match, but still need to test non-hashed quals. + * ExecScanHashBucket already set up all the state needed to + * call ExecQual. + * + * If we pass the qual, then save state for next call and have + * ExecProject form the projection, store it in the tuple + * table, and return the slot. + * + * Only the joinquals determine tuple match status, but all + * quals must pass to actually return the tuple. */ - if (node->js.jointype == JOIN_SEMI) - node->hj_NeedNewOuter = true; - - if (otherqual == NIL || ExecQual(otherqual, econtext, false)) + if (joinqual == NIL || ExecQual(joinqual, econtext, false)) { - TupleTableSlot *result; + node->hj_MatchedOuter = true; + HeapTupleHeaderSetMatch(HJTUPLE_MINTUPLE(node->hj_CurTuple)); - result = ExecProject(node->js.ps.ps_ProjInfo, &isDone); + /* In an antijoin, we never return a matched tuple */ + if (node->js.jointype == JOIN_ANTI) + { + node->hj_JoinState = HJ_NEED_NEW_OUTER; + continue; + } - if (isDone != ExprEndResult) + /* + * In a semijoin, we'll consider returning the first match, + * but after that we're done with this outer tuple. + */ + if (node->js.jointype == JOIN_SEMI) + node->hj_JoinState = HJ_NEED_NEW_OUTER; + + if (otherqual == NIL || + ExecQual(otherqual, econtext, false)) { - node->js.ps.ps_TupFromTlist = - (isDone == ExprMultipleResult); - return result; + TupleTableSlot *result; + + result = ExecProject(node->js.ps.ps_ProjInfo, &isDone); + + if (isDone != ExprEndResult) + { + node->js.ps.ps_TupFromTlist = + (isDone == ExprMultipleResult); + return result; + } } } + break; + case HJ_FILL_OUTER_TUPLE: /* - * If semijoin and we didn't return the tuple, we're still - * done with this outer tuple. + * The current outer tuple has run out of matches, so check + * whether to emit a dummy outer-join tuple. Whether we + * emit one or not, the next state is NEED_NEW_OUTER. */ - if (node->js.jointype == JOIN_SEMI) - break; /* out of loop over hash bucket */ - } - } + node->hj_JoinState = HJ_NEED_NEW_OUTER; - /* - * Now the current outer tuple has run out of matches, so check - * whether to emit a dummy outer-join tuple. If not, loop around to - * get a new outer tuple. - */ - node->hj_NeedNewOuter = true; + if (!node->hj_MatchedOuter && + HJ_FILL_OUTER(node)) + { + /* + * Generate a fake join tuple with nulls for the inner + * tuple, and return it if it passes the non-join quals. + */ + econtext->ecxt_innertuple = node->hj_NullInnerTupleSlot; + + if (otherqual == NIL || + ExecQual(otherqual, econtext, false)) + { + TupleTableSlot *result; - if (!node->hj_MatchedOuter && - HASHJOIN_IS_OUTER(node)) - { - /* - * We are doing an outer join and there were no join matches for - * this outer tuple. Generate a fake join tuple with nulls for - * the inner tuple, and return it if it passes the non-join quals. - */ - econtext->ecxt_innertuple = node->hj_NullInnerTupleSlot; + result = ExecProject(node->js.ps.ps_ProjInfo, &isDone); - if (otherqual == NIL || ExecQual(otherqual, econtext, false)) - { + if (isDone != ExprEndResult) + { + node->js.ps.ps_TupFromTlist = + (isDone == ExprMultipleResult); + return result; + } + } + } + break; + + case HJ_FILL_INNER_TUPLES: /* - * qualification was satisfied so we project and return the - * slot containing the result tuple using ExecProject(). + * We have finished a batch, but we are doing right/full join, + * so any unmatched inner tuples in the hashtable have to be + * emitted before we continue to the next batch. */ - TupleTableSlot *result; + if (!ExecScanHashTableForUnmatched(node, econtext)) + { + /* no more unmatched tuples */ + node->hj_JoinState = HJ_NEED_NEW_BATCH; + continue; + } - result = ExecProject(node->js.ps.ps_ProjInfo, &isDone); + /* + * Generate a fake join tuple with nulls for the outer tuple, + * and return it if it passes the non-join quals. + */ + econtext->ecxt_outertuple = node->hj_NullOuterTupleSlot; - if (isDone != ExprEndResult) + if (otherqual == NIL || + ExecQual(otherqual, econtext, false)) { - node->js.ps.ps_TupFromTlist = - (isDone == ExprMultipleResult); - return result; + TupleTableSlot *result; + + result = ExecProject(node->js.ps.ps_ProjInfo, &isDone); + + if (isDone != ExprEndResult) + { + node->js.ps.ps_TupFromTlist = + (isDone == ExprMultipleResult); + return result; + } } - } + break; + + case HJ_NEED_NEW_BATCH: + /* + * Try to advance to next batch. Done if there are no more. + */ + if (!ExecHashJoinNewBatch(node)) + return NULL; /* end of join */ + node->hj_JoinState = HJ_NEED_NEW_OUTER; + break; + + default: + elog(ERROR, "unrecognized hashjoin state: %d", + (int) node->hj_JoinState); } } } @@ -406,7 +474,7 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags) ExecInitResultTupleSlot(estate, &hjstate->js.ps); hjstate->hj_OuterTupleSlot = ExecInitExtraTupleSlot(estate); - /* note: HASHJOIN_IS_OUTER macro depends on this initialization */ + /* set up null tuples for outer joins, if needed */ switch (node->join.jointype) { case JOIN_INNER: @@ -418,6 +486,19 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags) ExecInitNullTupleSlot(estate, ExecGetResultType(innerPlanState(hjstate))); break; + case JOIN_RIGHT: + hjstate->hj_NullOuterTupleSlot = + ExecInitNullTupleSlot(estate, + ExecGetResultType(outerPlanState(hjstate))); + break; + case JOIN_FULL: + hjstate->hj_NullOuterTupleSlot = + ExecInitNullTupleSlot(estate, + ExecGetResultType(outerPlanState(hjstate))); + hjstate->hj_NullInnerTupleSlot = + ExecInitNullTupleSlot(estate, + ExecGetResultType(innerPlanState(hjstate))); + break; default: elog(ERROR, "unrecognized join type: %d", (int) node->join.jointype); @@ -425,7 +506,7 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags) /* * now for some voodoo. our temporary tuple slot is actually the result - * tuple slot of the Hash node (which is our inner plan). we do this + * tuple slot of the Hash node (which is our inner plan). we can do this * because Hash nodes don't return tuples via ExecProcNode() -- instead * the hash join node uses ExecScanHashBucket() to get at the contents of * the hash table. -cim 6/9/91 @@ -485,7 +566,7 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags) ((HashState *) innerPlanState(hjstate))->hashkeys = rclauses; hjstate->js.ps.ps_TupFromTlist = false; - hjstate->hj_NeedNewOuter = true; + hjstate->hj_JoinState = HJ_BUILD_HASHTABLE; hjstate->hj_MatchedOuter = false; hjstate->hj_OuterNotEmpty = false; @@ -533,12 +614,13 @@ ExecEndHashJoin(HashJoinState *node) * ExecHashJoinOuterGetTuple * * get the next outer tuple for hashjoin: either by - * executing a plan node in the first pass, or from + * executing the outer plan node in the first pass, or from * the temp files for the hashjoin batches. * - * Returns a null slot if no more outer tuples. On success, the tuple's - * hash value is stored at *hashvalue --- this is either originally computed, - * or re-read from the temp file. + * Returns a null slot if no more outer tuples (within the current batch). + * + * On success, the tuple's hash value is stored at *hashvalue --- this is + * either originally computed, or re-read from the temp file. */ static TupleTableSlot * ExecHashJoinOuterGetTuple(PlanState *outerNode, @@ -572,7 +654,7 @@ ExecHashJoinOuterGetTuple(PlanState *outerNode, if (ExecHashGetHashValue(hashtable, econtext, hjstate->hj_OuterHashKeys, true, /* outer tuple */ - HASHJOIN_IS_OUTER(hjstate), + HJ_FILL_OUTER(hjstate), hashvalue)) { /* remember outer relation is not empty for possible rescan */ @@ -587,31 +669,27 @@ ExecHashJoinOuterGetTuple(PlanState *outerNode, */ slot = ExecProcNode(outerNode); } + } + else if (curbatch < hashtable->nbatch) + { + BufFile *file = hashtable->outerBatchFile[curbatch]; /* - * We have just reached the end of the first pass. Try to switch to a - * saved batch. + * In outer-join cases, we could get here even though the batch file + * is empty. */ - curbatch = ExecHashJoinNewBatch(hjstate); - } + if (file == NULL) + return NULL; - /* - * Try to read from a temp file. Loop allows us to advance to new batches - * as needed. NOTE: nbatch could increase inside ExecHashJoinNewBatch, so - * don't try to optimize this loop. - */ - while (curbatch < hashtable->nbatch) - { slot = ExecHashJoinGetSavedTuple(hjstate, - hashtable->outerBatchFile[curbatch], + file, hashvalue, hjstate->hj_OuterTupleSlot); if (!TupIsNull(slot)) return slot; - curbatch = ExecHashJoinNewBatch(hjstate); } - /* Out of batches... */ + /* End of this batch */ return NULL; } @@ -619,10 +697,9 @@ ExecHashJoinOuterGetTuple(PlanState *outerNode, * ExecHashJoinNewBatch * switch to a new hashjoin batch * - * Returns the number of the new batch (1..nbatch-1), or nbatch if no more. - * We will never return a batch number that has an empty outer batch file. + * Returns true if successful, false if there are no more batches. */ -static int +static bool ExecHashJoinNewBatch(HashJoinState *hjstate) { HashJoinTable hashtable = hjstate->hj_HashTable; @@ -632,7 +709,6 @@ ExecHashJoinNewBatch(HashJoinState *hjstate) TupleTableSlot *slot; uint32 hashvalue; -start_over: nbatch = hashtable->nbatch; curbatch = hashtable->curbatch; @@ -657,6 +733,7 @@ start_over: hashtable->skewEnabled = false; hashtable->skewBucket = NULL; hashtable->skewBucketNums = NULL; + hashtable->nSkewBuckets = 0; hashtable->spaceUsedSkew = 0; } @@ -665,8 +742,9 @@ start_over: * sides. We can sometimes skip over batches that are empty on only one * side, but there are exceptions: * - * 1. In an outer join, we have to process outer batches even if the inner - * batch is empty. + * 1. In a left/full outer join, we have to process outer batches even if + * the inner batch is empty. Similarly, in a right/full outer join, we + * have to process inner batches even if the outer batch is empty. * * 2. If we have increased nbatch since the initial estimate, we have to * scan inner batches since they might contain tuples that need to be @@ -682,7 +760,10 @@ start_over: hashtable->innerBatchFile[curbatch] == NULL)) { if (hashtable->outerBatchFile[curbatch] && - HASHJOIN_IS_OUTER(hjstate)) + HJ_FILL_OUTER(hjstate)) + break; /* must process due to rule 1 */ + if (hashtable->innerBatchFile[curbatch] && + HJ_FILL_INNER(hjstate)) break; /* must process due to rule 1 */ if (hashtable->innerBatchFile[curbatch] && nbatch != hashtable->nbatch_original) @@ -702,7 +783,7 @@ start_over: } if (curbatch >= nbatch) - return curbatch; /* no more batches */ + return false; /* no more batches */ hashtable->curbatch = curbatch; @@ -741,20 +822,17 @@ start_over: } /* - * If there's no outer batch file, advance to next batch. + * Rewind outer batch file (if present), so that we can start reading it. */ - if (hashtable->outerBatchFile[curbatch] == NULL) - goto start_over; - - /* - * Rewind outer batch file, so that we can start reading it. - */ - if (BufFileSeek(hashtable->outerBatchFile[curbatch], 0, 0L, SEEK_SET)) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not rewind hash-join temporary file: %m"))); + if (hashtable->outerBatchFile[curbatch] != NULL) + { + if (BufFileSeek(hashtable->outerBatchFile[curbatch], 0, 0L, SEEK_SET)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not rewind hash-join temporary file: %m"))); + } - return curbatch; + return true; } /* @@ -857,9 +935,16 @@ ExecReScanHashJoin(HashJoinState *node) node->js.ps.righttree->chgParam == NULL) { /* - * okay to reuse the hash table; needn't rescan inner, either. + * Okay to reuse the hash table; needn't rescan inner, either. * - * What we do need to do is reset our state about the emptiness of + * However, if it's a right/full join, we'd better reset the + * inner-tuple match flags contained in the table. + */ + if (HJ_FILL_INNER(node)) + ExecHashTableResetMatchFlags(node->hj_HashTable); + + /* + * Also, we need to reset our state about the emptiness of * the outer relation, so that the new scan of the outer will * update it correctly if it turns out to be empty this time. * (There's no harm in clearing it now because ExecHashJoin won't @@ -869,12 +954,16 @@ ExecReScanHashJoin(HashJoinState *node) * through.) */ node->hj_OuterNotEmpty = false; + + /* ExecHashJoin can skip the BUILD_HASHTABLE step */ + node->hj_JoinState = HJ_NEED_NEW_OUTER; } else { /* must destroy and rebuild hash table */ ExecHashTableDestroy(node->hj_HashTable); node->hj_HashTable = NULL; + node->hj_JoinState = HJ_BUILD_HASHTABLE; /* * if chgParam of subnode is not null then plan will be re-scanned @@ -892,7 +981,6 @@ ExecReScanHashJoin(HashJoinState *node) node->hj_CurTuple = NULL; node->js.ps.ps_TupFromTlist = false; - node->hj_NeedNewOuter = true; node->hj_MatchedOuter = false; node->hj_FirstOuterTupleSlot = NULL; |