1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-11 20:28:21 +03:00

Support RIGHT and FULL OUTER JOIN in hash joins.

This is advantageous first because it allows us to hash the smaller table
regardless of the outer-join type, and second because hash join can be more
flexible than merge join in dealing with arbitrary join quals in a FULL
join.  For merge join all the join quals have to be mergejoinable, but hash
join will work so long as there's at least one hashjoinable qual --- the
others can be any condition.  (This is true essentially because we don't
keep per-inner-tuple match flags in merge join, while hash join can do so.)

To do this, we need a has-it-been-matched flag for each tuple in the
hashtable, not just one for the current outer tuple.  The key idea that
makes this practical is that we can store the match flag in the tuple's
infomask, since there are lots of bits there that are of no interest for a
MinimalTuple.  So we aren't increasing the size of the hashtable at all for
the feature.

To write this without turning the hash code into even more of a pile of
spaghetti than it already was, I rewrote ExecHashJoin in a state-machine
style, similar to ExecMergeJoin.  Other than that decision, it was pretty
straightforward.
This commit is contained in:
Tom Lane
2010-12-30 20:24:55 -05:00
parent 17cb9e8c98
commit f4e4b32743
10 changed files with 609 additions and 376 deletions

View File

@ -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,182 +106,274 @@ ExecHashJoin(HashJoinState *node)
ResetExprContext(econtext);
/*
* if this is the first call, build the hash table for inner relation
*/
if (hashtable == NULL)
{
/*
* 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))
{
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;
/*
* create the hash table
*/
hashtable = ExecHashTableCreate((Hash *) hashNode->ps.plan,
node->hj_HashOperators);
node->hj_HashTable = hashtable;
/*
* execute the Hash node, to build the hash table
*/
hashNode->hashtable = hashtable;
(void) MultiExecProcNode((PlanState *) hashNode);
/*
* 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;
/*
* need to remember whether nbatch has increased since we began
* scanning the outer relation
*/
hashtable->nbatch_outstart = hashtable->nbatch;
/*
* Reset OuterNotEmpty for scan. (It's OK if we fetched a tuple
* above, because ExecHashJoinOuterGetTuple will immediately set it
* again.)
*/
node->hj_OuterNotEmpty = false;
}
/*
* run the hash join process
* run the hash join state machine
*/
for (;;)
{
/*
* If we don't have an outer tuple, get the next one
*/
if (node->hj_NeedNewOuter)
switch (node->hj_JoinState)
{
outerTupleSlot = ExecHashJoinOuterGetTuple(outerNode,
node,
&hashvalue);
if (TupIsNull(outerTupleSlot))
{
/* end of join */
return NULL;
}
econtext->ecxt_outertuple = outerTupleSlot;
node->hj_NeedNewOuter = false;
node->hj_MatchedOuter = false;
/*
* 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;
/*
* 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_BUILD_HASHTABLE:
/*
* Need to postpone this outer tuple to a later batch. Save it
* in the corresponding outer-batch file.
* First time through: build hash table for inner relation.
*/
Assert(batchno > hashtable->curbatch);
ExecHashJoinSaveTuple(ExecFetchSlotMinimalTuple(outerTupleSlot),
hashvalue,
&hashtable->outerBatchFile[batchno]);
node->hj_NeedNewOuter = true;
continue; /* loop around for a new outer tuple */
}
}
Assert(hashtable == NULL);
/*
* OK, scan the selected hash bucket for matches
*/
for (;;)
{
curtuple = ExecScanHashBucket(node, econtext);
if (curtuple == NULL)
break; /* out of matches */
/*
* 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;
/* reset temp memory each time to avoid leaks from qual expr */
ResetExprContext(econtext);
/*
* 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;
/* In an antijoin, we never return a matched tuple */
if (node->js.jointype == JOIN_ANTI)
/*
* 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))
{
node->hj_NeedNewOuter = true;
break; /* out of loop over hash bucket */
/* 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;
/*
* create the hash table
*/
hashtable = ExecHashTableCreate((Hash *) hashNode->ps.plan,
node->hj_HashOperators,
HJ_FILL_INNER(node));
node->hj_HashTable = hashtable;
/*
* execute the Hash node, to build the hash table
*/
hashNode->hashtable = hashtable;
(void) MultiExecProcNode((PlanState *) hashNode);
/*
* 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;
/*
* need to remember whether nbatch has increased since we began
* scanning the outer relation
*/
hashtable->nbatch_outstart = hashtable->nbatch;
/*
* Reset OuterNotEmpty for scan. (It's OK if we fetched a
* tuple above, because ExecHashJoinOuterGetTuple will
* immediately set it again.)
*/
node->hj_OuterNotEmpty = false;
node->hj_JoinState = HJ_NEED_NEW_OUTER;
/* FALL THRU */
case HJ_NEED_NEW_OUTER:
/*
* We don't have an outer tuple, try to get the next one
*/
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;
}
econtext->ecxt_outertuple = outerTupleSlot;
node->hj_MatchedOuter = false;
/*
* 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;
/*
* 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;
}
/* OK, let's scan the bucket for matches */
node->hj_JoinState = HJ_SCAN_BUCKET;
/* FALL THRU */
case HJ_SCAN_BUCKET:
/*
* Scan the selected hash bucket for matches to current outer
*/
if (!ExecScanHashBucket(node, econtext))
{
/* 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 (joinqual == NIL || ExecQual(joinqual, econtext, false))
{
node->hj_MatchedOuter = true;
HeapTupleHeaderSetMatch(HJTUPLE_MINTUPLE(node->hj_CurTuple));
if (otherqual == NIL || ExecQual(otherqual, econtext, false))
/* In an antijoin, we never return a matched tuple */
if (node->js.jointype == JOIN_ANTI)
{
node->hj_JoinState = HJ_NEED_NEW_OUTER;
continue;
}
/*
* 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))
{
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:
/*
* 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.
*/
node->hj_JoinState = HJ_NEED_NEW_OUTER;
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;
result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
if (isDone != ExprEndResult)
{
node->js.ps.ps_TupFromTlist =
(isDone == ExprMultipleResult);
return result;
}
}
}
break;
case HJ_FILL_INNER_TUPLES:
/*
* 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.
*/
if (!ExecScanHashTableForUnmatched(node, econtext))
{
/* no more unmatched tuples */
node->hj_JoinState = HJ_NEED_NEW_BATCH;
continue;
}
/*
* 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 (otherqual == NIL ||
ExecQual(otherqual, econtext, false))
{
TupleTableSlot *result;
@ -288,50 +386,20 @@ ExecHashJoin(HashJoinState *node)
return result;
}
}
break;
case HJ_NEED_NEW_BATCH:
/*
* If semijoin and we didn't return the tuple, we're still
* done with this outer tuple.
* Try to advance to next batch. Done if there are no more.
*/
if (node->js.jointype == JOIN_SEMI)
break; /* out of loop over hash bucket */
}
}
if (!ExecHashJoinNewBatch(node))
return NULL; /* end of join */
node->hj_JoinState = HJ_NEED_NEW_OUTER;
break;
/*
* 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 &&
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;
if (otherqual == NIL || ExecQual(otherqual, econtext, false))
{
/*
* qualification was satisfied so we project and return the
* slot containing the result tuple using ExecProject().
*/
TupleTableSlot *result;
result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
if (isDone != ExprEndResult)
{
node->js.ps.ps_TupFromTlist =
(isDone == ExprMultipleResult);
return result;
}
}
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;
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")));
}
/*
* 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")));
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;