aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/nodeHashjoin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/executor/nodeHashjoin.c')
-rw-r--r--src/backend/executor/nodeHashjoin.c576
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;