@ -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 )
{
/*
* o kay to reuse the hash table ; needn ' t rescan inner , either .
* O kay 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 ;