* nodeMergejoin.c
* routines supporting merge joins
*
- * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/nodeMergejoin.c,v 1.96 2009/04/02 20:59:10 momjian Exp $
+ * src/backend/executor/nodeMergejoin.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/nbtree.h"
-#include "catalog/pg_amop.h"
#include "executor/execdebug.h"
-#include "executor/execdefs.h"
#include "executor/nodeMergejoin.h"
-#include "miscadmin.h"
-#include "utils/acl.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
-#include "utils/syscache.h"
+/*
+ * States of the ExecMergeJoin state machine
+ */
+#define EXEC_MJ_INITIALIZE_OUTER 1
+#define EXEC_MJ_INITIALIZE_INNER 2
+#define EXEC_MJ_JOINTUPLES 3
+#define EXEC_MJ_NEXTOUTER 4
+#define EXEC_MJ_TESTOUTER 5
+#define EXEC_MJ_NEXTINNER 6
+#define EXEC_MJ_SKIP_TEST 7
+#define EXEC_MJ_SKIPOUTER_ADVANCE 8
+#define EXEC_MJ_SKIPINNER_ADVANCE 9
+#define EXEC_MJ_ENDOUTER 10
+#define EXEC_MJ_ENDINNER 11
+
/*
* Runtime data for each mergejoin clause
*/
bool risnull;
/*
- * The comparison strategy in use, and the lookup info to let us call the
- * btree comparison support function.
+ * Everything we need to know to compare the left and right values is
+ * stored here.
*/
- bool reverse; /* if true, negate the cmpfn's output */
- bool nulls_first; /* if true, nulls sort low */
- FmgrInfo cmpfinfo;
-} MergeJoinClauseData;
+ SortSupportData ssup;
+} MergeJoinClauseData;
+
+/* Result type for MJEvalOuterValues and MJEvalInnerValues */
+typedef enum
+{
+ MJEVAL_MATCHABLE, /* normal, potentially matchable tuple */
+ MJEVAL_NONMATCHABLE, /* tuple cannot join because it has a null */
+ MJEVAL_ENDOFJOIN /* end of input (physical or effective) */
+} MJEvalResult;
#define MarkInnerTuple(innerTupleSlot, mergestate) \
* the two expressions from the original clause.
*
* In addition to the expressions themselves, the planner passes the btree
- * opfamily OID, btree strategy number (BTLessStrategyNumber or
+ * opfamily OID, collation OID, btree strategy number (BTLessStrategyNumber or
* BTGreaterStrategyNumber), and nulls-first flag that identify the intended
* sort ordering for each merge key. The mergejoinable operator is an
- * equality operator in this opfamily, and the two inputs are guaranteed to be
+ * equality operator in the opfamily, and the two inputs are guaranteed to be
* ordered in either increasing or decreasing (respectively) order according
- * to this opfamily, with nulls at the indicated end of the range. This
- * allows us to obtain the needed comparison function from the opfamily.
+ * to the opfamily and collation, with nulls at the indicated end of the range.
+ * This allows us to obtain the needed comparison function from the opfamily.
*/
static MergeJoinClause
MJExamineQuals(List *mergeclauses,
Oid *mergefamilies,
+ Oid *mergecollations,
int *mergestrategies,
bool *mergenullsfirst,
PlanState *parent)
OpExpr *qual = (OpExpr *) lfirst(cl);
MergeJoinClause clause = &clauses[iClause];
Oid opfamily = mergefamilies[iClause];
+ Oid collation = mergecollations[iClause];
StrategyNumber opstrategy = mergestrategies[iClause];
bool nulls_first = mergenullsfirst[iClause];
int op_strategy;
Oid op_lefttype;
Oid op_righttype;
- RegProcedure cmpproc;
- AclResult aclresult;
+ Oid sortfunc;
if (!IsA(qual, OpExpr))
elog(ERROR, "mergejoin clause is not an OpExpr");
clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent);
clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent);
+ /* Set up sort support data */
+ clause->ssup.ssup_cxt = CurrentMemoryContext;
+ clause->ssup.ssup_collation = collation;
+ if (opstrategy == BTLessStrategyNumber)
+ clause->ssup.ssup_reverse = false;
+ else if (opstrategy == BTGreaterStrategyNumber)
+ clause->ssup.ssup_reverse = true;
+ else /* planner screwed up */
+ elog(ERROR, "unsupported mergejoin strategy %d", opstrategy);
+ clause->ssup.ssup_nulls_first = nulls_first;
+
/* Extract the operator's declared left/right datatypes */
- get_op_opfamily_properties(qual->opno, opfamily,
+ get_op_opfamily_properties(qual->opno, opfamily, false,
&op_strategy,
&op_lefttype,
&op_righttype);
elog(ERROR, "cannot merge using non-equality operator %u",
qual->opno);
- /* And get the matching support procedure (comparison function) */
- cmpproc = get_opfamily_proc(opfamily,
- op_lefttype,
- op_righttype,
- BTORDER_PROC);
- if (!RegProcedureIsValid(cmpproc)) /* should not happen */
- elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
- BTORDER_PROC, op_lefttype, op_righttype, opfamily);
-
- /* Check permission to call cmp function */
- aclresult = pg_proc_aclcheck(cmpproc, GetUserId(), ACL_EXECUTE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_PROC,
- get_func_name(cmpproc));
-
- /* Set up the fmgr lookup information */
- fmgr_info(cmpproc, &(clause->cmpfinfo));
-
- /* Fill the additional comparison-strategy flags */
- if (opstrategy == BTLessStrategyNumber)
- clause->reverse = false;
- else if (opstrategy == BTGreaterStrategyNumber)
- clause->reverse = true;
- else /* planner screwed up */
- elog(ERROR, "unsupported mergejoin strategy %d", opstrategy);
-
- clause->nulls_first = nulls_first;
+ /* And get the matching support or comparison function */
+ sortfunc = get_opfamily_proc(opfamily,
+ op_lefttype,
+ op_righttype,
+ BTSORTSUPPORT_PROC);
+ if (OidIsValid(sortfunc))
+ {
+ /* The sort support function should provide a comparator */
+ OidFunctionCall1(sortfunc, PointerGetDatum(&clause->ssup));
+ Assert(clause->ssup.comparator != NULL);
+ }
+ else
+ {
+ /* opfamily doesn't provide sort support, get comparison func */
+ sortfunc = get_opfamily_proc(opfamily,
+ op_lefttype,
+ op_righttype,
+ BTORDER_PROC);
+ if (!OidIsValid(sortfunc)) /* should not happen */
+ elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
+ BTORDER_PROC, op_lefttype, op_righttype, opfamily);
+ /* We'll use a shim to call the old-style btree comparator */
+ PrepareSortSupportComparisonShim(sortfunc, &clause->ssup);
+ }
iClause++;
}
* Compute the values of the mergejoined expressions for the current
* outer tuple. We also detect whether it's impossible for the current
* outer tuple to match anything --- this is true if it yields a NULL
- * input, since we assume mergejoin operators are strict.
+ * input, since we assume mergejoin operators are strict. If the NULL
+ * is in the first join column, and that column sorts nulls last, then
+ * we can further conclude that no following tuple can match anything
+ * either, since they must all have nulls in the first column. However,
+ * that case is only interesting if we're not in FillOuter mode, else
+ * we have to visit all the tuples anyway.
+ *
+ * For the convenience of callers, we also make this routine responsible
+ * for testing for end-of-input (null outer tuple), and returning
+ * MJEVAL_ENDOFJOIN when that's seen. This allows the same code to be used
+ * for both real end-of-input and the effective end-of-input represented by
+ * a first-column NULL.
*
* We evaluate the values in OuterEContext, which can be reset each
* time we move to a new tuple.
*/
-static bool
+static MJEvalResult
MJEvalOuterValues(MergeJoinState *mergestate)
{
ExprContext *econtext = mergestate->mj_OuterEContext;
- bool canmatch = true;
+ MJEvalResult result = MJEVAL_MATCHABLE;
int i;
MemoryContext oldContext;
+ /* Check for end of outer subplan */
+ if (TupIsNull(mergestate->mj_OuterTupleSlot))
+ return MJEVAL_ENDOFJOIN;
+
ResetExprContext(econtext);
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
clause->ldatum = ExecEvalExpr(clause->lexpr, econtext,
&clause->lisnull, NULL);
if (clause->lisnull)
- canmatch = false;
+ {
+ /* match is impossible; can we end the join early? */
+ if (i == 0 && !clause->ssup.ssup_nulls_first &&
+ !mergestate->mj_FillOuter)
+ result = MJEVAL_ENDOFJOIN;
+ else if (result == MJEVAL_MATCHABLE)
+ result = MJEVAL_NONMATCHABLE;
+ }
}
MemoryContextSwitchTo(oldContext);
- return canmatch;
+ return result;
}
/*
* to load data from either the true current inner, or the marked inner,
* so caller must tell us which slot to load from.
*/
-static bool
+static MJEvalResult
MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
{
ExprContext *econtext = mergestate->mj_InnerEContext;
- bool canmatch = true;
+ MJEvalResult result = MJEVAL_MATCHABLE;
int i;
MemoryContext oldContext;
+ /* Check for end of inner subplan */
+ if (TupIsNull(innerslot))
+ return MJEVAL_ENDOFJOIN;
+
ResetExprContext(econtext);
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
clause->rdatum = ExecEvalExpr(clause->rexpr, econtext,
&clause->risnull, NULL);
if (clause->risnull)
- canmatch = false;
+ {
+ /* match is impossible; can we end the join early? */
+ if (i == 0 && !clause->ssup.ssup_nulls_first &&
+ !mergestate->mj_FillInner)
+ result = MJEVAL_ENDOFJOIN;
+ else if (result == MJEVAL_MATCHABLE)
+ result = MJEVAL_NONMATCHABLE;
+ }
}
MemoryContextSwitchTo(oldContext);
- return canmatch;
+ return result;
}
/*
*
* Compare the mergejoinable values of the current two input tuples
* and return 0 if they are equal (ie, the mergejoin equalities all
- * succeed), +1 if outer > inner, -1 if outer < inner.
+ * succeed), >0 if outer > inner, <0 if outer < inner.
*
* MJEvalOuterValues and MJEvalInnerValues must already have been called
* for the current outer and inner tuples, respectively.
*/
-static int32
+static int
MJCompare(MergeJoinState *mergestate)
{
- int32 result = 0;
+ int result = 0;
bool nulleqnull = false;
ExprContext *econtext = mergestate->js.ps.ps_ExprContext;
int i;
MemoryContext oldContext;
- FunctionCallInfoData fcinfo;
/*
* Call the comparison functions in short-lived context, in case they leak
for (i = 0; i < mergestate->mj_NumClauses; i++)
{
MergeJoinClause clause = &mergestate->mj_Clauses[i];
- Datum fresult;
/*
- * Deal with null inputs.
+ * Special case for NULL-vs-NULL, else use standard comparison.
*/
- if (clause->lisnull)
- {
- if (clause->risnull)
- {
- nulleqnull = true; /* NULL "=" NULL */
- continue;
- }
- if (clause->nulls_first)
- result = -1; /* NULL "<" NOT_NULL */
- else
- result = 1; /* NULL ">" NOT_NULL */
- break;
- }
- if (clause->risnull)
+ if (clause->lisnull && clause->risnull)
{
- if (clause->nulls_first)
- result = 1; /* NOT_NULL ">" NULL */
- else
- result = -1; /* NOT_NULL "<" NULL */
- break;
- }
-
- /*
- * OK to call the comparison function.
- */
- InitFunctionCallInfoData(fcinfo, &(clause->cmpfinfo), 2,
- NULL, NULL);
- fcinfo.arg[0] = clause->ldatum;
- fcinfo.arg[1] = clause->rdatum;
- fcinfo.argnull[0] = false;
- fcinfo.argnull[1] = false;
- fresult = FunctionCallInvoke(&fcinfo);
- if (fcinfo.isnull)
- {
- nulleqnull = true; /* treat like NULL = NULL */
+ nulleqnull = true; /* NULL "=" NULL */
continue;
}
- result = DatumGetInt32(fresult);
- if (clause->reverse)
- result = -result;
+ result = ApplySortComparator(clause->ldatum, clause->lisnull,
+ clause->rdatum, clause->risnull,
+ &clause->ssup);
if (result != 0)
break;
}
/*
- * If we had any null comparison results or NULL-vs-NULL inputs, we do not
- * want to report that the tuples are equal. Instead, if result is still
- * 0, change it to +1. This will result in advancing the inner side of
- * the join.
+ * If we had any NULL-vs-NULL inputs, we do not want to report that the
+ * tuples are equal. Instead, if result is still 0, change it to +1.
+ * This will result in advancing the inner side of the join.
+ *
+ * Likewise, if there was a constant-false joinqual, do not report
+ * equality. We have to check this as part of the mergequals, else the
+ * rescan logic will do the wrong thing.
*/
- if (nulleqnull && result == 0)
+ if (result == 0 &&
+ (nulleqnull || mergestate->mj_ConstFalseJoin))
result = 1;
MemoryContextSwitchTo(oldContext);
return result;
}
}
+ else
+ InstrCountFiltered2(node, 1);
return NULL;
}
return result;
}
}
+ else
+ InstrCountFiltered2(node, 1);
return NULL;
}
+/*
+ * Check that a qual condition is constant true or constant false.
+ * If it is constant false (or null), set *is_const_false to TRUE.
+ *
+ * Constant true would normally be represented by a NIL list, but we allow an
+ * actual bool Const as well. We do expect that the planner will have thrown
+ * away any non-constant terms that have been ANDed with a constant false.
+ */
+static bool
+check_constant_qual(List *qual, bool *is_const_false)
+{
+ ListCell *lc;
+
+ foreach(lc, qual)
+ {
+ Const *con = (Const *) lfirst(lc);
+
+ if (!con || !IsA(con, Const))
+ return false;
+ if (con->constisnull || !DatumGetBool(con->constvalue))
+ *is_const_false = true;
+ }
+ return true;
+}
+
+
/* ----------------------------------------------------------------
* ExecMergeTupleDump
*
TupleTableSlot *
ExecMergeJoin(MergeJoinState *node)
{
- EState *estate;
List *joinqual;
List *otherqual;
bool qualResult;
- int32 compareResult;
+ int compareResult;
PlanState *innerPlan;
TupleTableSlot *innerTupleSlot;
PlanState *outerPlan;
/*
* get information from node
*/
- estate = node->js.ps.state;
innerPlan = innerPlanState(node);
outerPlan = outerPlanState(node);
econtext = node->js.ps.ps_ExprContext;
outerTupleSlot = ExecProcNode(outerPlan);
node->mj_OuterTupleSlot = outerTupleSlot;
- if (TupIsNull(outerTupleSlot))
- {
- MJ_printf("ExecMergeJoin: nothing in outer subplan\n");
- if (doFillInner)
- {
- /*
- * Need to emit right-join tuples for remaining inner
- * tuples. We set MatchedInner = true to force the
- * ENDOUTER state to advance inner.
- */
- node->mj_JoinState = EXEC_MJ_ENDOUTER;
- node->mj_MatchedInner = true;
- break;
- }
- /* Otherwise we're done. */
- return NULL;
- }
/* Compute join values and check for unmatchability */
- if (MJEvalOuterValues(node))
+ switch (MJEvalOuterValues(node))
{
- /* OK to go get the first inner tuple */
- node->mj_JoinState = EXEC_MJ_INITIALIZE_INNER;
- }
- else
- {
- /* Stay in same state to fetch next outer tuple */
- if (doFillOuter)
- {
- /*
- * Generate a fake join tuple with nulls for the inner
- * tuple, and return it if it passes the non-join
- * quals.
- */
- TupleTableSlot *result;
+ case MJEVAL_MATCHABLE:
+ /* OK to go get the first inner tuple */
+ node->mj_JoinState = EXEC_MJ_INITIALIZE_INNER;
+ break;
+ case MJEVAL_NONMATCHABLE:
+ /* Stay in same state to fetch next outer tuple */
+ if (doFillOuter)
+ {
+ /*
+ * Generate a fake join tuple with nulls for the
+ * inner tuple, and return it if it passes the
+ * non-join quals.
+ */
+ TupleTableSlot *result;
- result = MJFillOuter(node);
- if (result)
- return result;
- }
+ result = MJFillOuter(node);
+ if (result)
+ return result;
+ }
+ break;
+ case MJEVAL_ENDOFJOIN:
+ /* No more outer tuples */
+ MJ_printf("ExecMergeJoin: nothing in outer subplan\n");
+ if (doFillInner)
+ {
+ /*
+ * Need to emit right-join tuples for remaining
+ * inner tuples. We set MatchedInner = true to
+ * force the ENDOUTER state to advance inner.
+ */
+ node->mj_JoinState = EXEC_MJ_ENDOUTER;
+ node->mj_MatchedInner = true;
+ break;
+ }
+ /* Otherwise we're done. */
+ return NULL;
}
break;
innerTupleSlot = ExecProcNode(innerPlan);
node->mj_InnerTupleSlot = innerTupleSlot;
- if (TupIsNull(innerTupleSlot))
- {
- MJ_printf("ExecMergeJoin: nothing in inner subplan\n");
- if (doFillOuter)
- {
- /*
- * Need to emit left-join tuples for all outer tuples,
- * including the one we just fetched. We set
- * MatchedOuter = false to force the ENDINNER state to
- * emit first tuple before advancing outer.
- */
- node->mj_JoinState = EXEC_MJ_ENDINNER;
- node->mj_MatchedOuter = false;
- break;
- }
- /* Otherwise we're done. */
- return NULL;
- }
/* Compute join values and check for unmatchability */
- if (MJEvalInnerValues(node, innerTupleSlot))
- {
- /*
- * OK, we have the initial tuples. Begin by skipping
- * non-matching tuples.
- */
- node->mj_JoinState = EXEC_MJ_SKIP_TEST;
- }
- else
+ switch (MJEvalInnerValues(node, innerTupleSlot))
{
- /* Mark before advancing, if wanted */
- if (node->mj_ExtraMarks)
- ExecMarkPos(innerPlan);
- /* Stay in same state to fetch next inner tuple */
- if (doFillInner)
- {
+ case MJEVAL_MATCHABLE:
+
/*
- * Generate a fake join tuple with nulls for the outer
- * tuple, and return it if it passes the non-join
- * quals.
+ * OK, we have the initial tuples. Begin by skipping
+ * non-matching tuples.
*/
- TupleTableSlot *result;
+ node->mj_JoinState = EXEC_MJ_SKIP_TEST;
+ break;
+ case MJEVAL_NONMATCHABLE:
+ /* Mark before advancing, if wanted */
+ if (node->mj_ExtraMarks)
+ ExecMarkPos(innerPlan);
+ /* Stay in same state to fetch next inner tuple */
+ if (doFillInner)
+ {
+ /*
+ * Generate a fake join tuple with nulls for the
+ * outer tuple, and return it if it passes the
+ * non-join quals.
+ */
+ TupleTableSlot *result;
- result = MJFillInner(node);
- if (result)
- return result;
- }
+ result = MJFillInner(node);
+ if (result)
+ return result;
+ }
+ break;
+ case MJEVAL_ENDOFJOIN:
+ /* No more inner tuples */
+ MJ_printf("ExecMergeJoin: nothing in inner subplan\n");
+ if (doFillOuter)
+ {
+ /*
+ * Need to emit left-join tuples for all outer
+ * tuples, including the one we just fetched. We
+ * set MatchedOuter = false to force the ENDINNER
+ * state to emit first tuple before advancing
+ * outer.
+ */
+ node->mj_JoinState = EXEC_MJ_ENDINNER;
+ node->mj_MatchedOuter = false;
+ break;
+ }
+ /* Otherwise we're done. */
+ return NULL;
}
break;
}
/*
- * In a semijoin, we'll consider returning the first match,
- * but after that we're done with this outer tuple.
+ * 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->mj_JoinState = EXEC_MJ_NEXTOUTER;
return result;
}
}
+ else
+ InstrCountFiltered2(node, 1);
}
+ else
+ InstrCountFiltered1(node, 1);
break;
/*
MJ_DEBUG_PROC_NODE(innerTupleSlot);
node->mj_MatchedInner = false;
- if (TupIsNull(innerTupleSlot))
+ /* Compute join values and check for unmatchability */
+ switch (MJEvalInnerValues(node, innerTupleSlot))
{
- node->mj_JoinState = EXEC_MJ_NEXTOUTER;
- break;
- }
+ case MJEVAL_MATCHABLE:
- /*
- * Load up the new inner tuple's comparison values. If we see
- * that it contains a NULL and hence can't match any outer
- * tuple, we can skip the comparison and assume the new tuple
- * is greater than current outer.
- */
- if (!MJEvalInnerValues(node, innerTupleSlot))
- {
- node->mj_JoinState = EXEC_MJ_NEXTOUTER;
- break;
- }
+ /*
+ * Test the new inner tuple to see if it matches
+ * outer.
+ *
+ * If they do match, then we join them and move on to
+ * the next inner tuple (EXEC_MJ_JOINTUPLES).
+ *
+ * If they do not match then advance to next outer
+ * tuple.
+ */
+ compareResult = MJCompare(node);
+ MJ_DEBUG_COMPARE(compareResult);
- /*
- * Test the new inner tuple to see if it matches outer.
- *
- * If they do match, then we join them and move on to the next
- * inner tuple (EXEC_MJ_JOINTUPLES).
- *
- * If they do not match then advance to next outer tuple.
- */
- compareResult = MJCompare(node);
- MJ_DEBUG_COMPARE(compareResult);
+ if (compareResult == 0)
+ node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+ else
+ {
+ Assert(compareResult < 0);
+ node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+ }
+ break;
+ case MJEVAL_NONMATCHABLE:
- if (compareResult == 0)
- node->mj_JoinState = EXEC_MJ_JOINTUPLES;
- else
- {
- Assert(compareResult < 0);
- node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+ /*
+ * It contains a NULL and hence can't match any outer
+ * tuple, so we can skip the comparison and assume the
+ * new tuple is greater than current outer.
+ */
+ node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+ break;
+ case MJEVAL_ENDOFJOIN:
+
+ /*
+ * No more inner tuples. However, this might be only
+ * effective and not physical end of inner plan, so
+ * force mj_InnerTupleSlot to null to make sure we
+ * don't fetch more inner tuples. (We need this hack
+ * because we are not transiting to a state where the
+ * inner plan is assumed to be exhausted.)
+ */
+ node->mj_InnerTupleSlot = NULL;
+ node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+ break;
}
break;
MJ_DEBUG_PROC_NODE(outerTupleSlot);
node->mj_MatchedOuter = false;
- /*
- * if the outer tuple is null then we are done with the join,
- * unless we have inner tuples we need to null-fill.
- */
- if (TupIsNull(outerTupleSlot))
- {
- MJ_printf("ExecMergeJoin: end of outer subplan\n");
- innerTupleSlot = node->mj_InnerTupleSlot;
- if (doFillInner && !TupIsNull(innerTupleSlot))
- {
- /*
- * Need to emit right-join tuples for remaining inner
- * tuples.
- */
- node->mj_JoinState = EXEC_MJ_ENDOUTER;
- break;
- }
- /* Otherwise we're done. */
- return NULL;
- }
-
/* Compute join values and check for unmatchability */
- if (MJEvalOuterValues(node))
- {
- /* Go test the new tuple against the marked tuple */
- node->mj_JoinState = EXEC_MJ_TESTOUTER;
- }
- else
+ switch (MJEvalOuterValues(node))
{
- /* Can't match, so fetch next outer tuple */
- node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+ case MJEVAL_MATCHABLE:
+ /* Go test the new tuple against the marked tuple */
+ node->mj_JoinState = EXEC_MJ_TESTOUTER;
+ break;
+ case MJEVAL_NONMATCHABLE:
+ /* Can't match, so fetch next outer tuple */
+ node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+ break;
+ case MJEVAL_ENDOFJOIN:
+ /* No more outer tuples */
+ MJ_printf("ExecMergeJoin: end of outer subplan\n");
+ innerTupleSlot = node->mj_InnerTupleSlot;
+ if (doFillInner && !TupIsNull(innerTupleSlot))
+ {
+ /*
+ * Need to emit right-join tuples for remaining
+ * inner tuples.
+ */
+ node->mj_JoinState = EXEC_MJ_ENDOUTER;
+ break;
+ }
+ /* Otherwise we're done. */
+ return NULL;
}
break;
* state for the rescanned inner tuples. We know all of
* them will match this new outer tuple and therefore
* won't be emitted as fill tuples. This works *only*
- * because we require the extra joinquals to be nil when
- * doing a right or full join --- otherwise some of the
- * rescanned tuples might fail the extra joinquals.
+ * because we require the extra joinquals to be constant
+ * when doing a right or full join --- otherwise some of
+ * the rescanned tuples might fail the extra joinquals.
+ * This obviously won't happen for a constant-true extra
+ * joinqual, while the constant-false case is handled by
+ * forcing the merge clause to never match, so we never
+ * get here.
*/
ExecRestrPos(innerPlan);
* larger than our marked inner tuples. So we need not
* revisit any of the marked tuples but can proceed to
* look for a match to the current inner. If there's
- * no more inners, we are done.
+ * no more inners, no more matches are possible.
* ----------------
*/
Assert(compareResult > 0);
innerTupleSlot = node->mj_InnerTupleSlot;
- if (TupIsNull(innerTupleSlot))
+
+ /* reload comparison data for current inner */
+ switch (MJEvalInnerValues(node, innerTupleSlot))
{
- if (doFillOuter)
- {
+ case MJEVAL_MATCHABLE:
+ /* proceed to compare it to the current outer */
+ node->mj_JoinState = EXEC_MJ_SKIP_TEST;
+ break;
+ case MJEVAL_NONMATCHABLE:
+
/*
- * Need to emit left-join tuples for remaining
- * outer tuples.
+ * current inner can't possibly match any outer;
+ * better to advance the inner scan than the
+ * outer.
*/
- node->mj_JoinState = EXEC_MJ_ENDINNER;
+ node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
break;
- }
- /* Otherwise we're done. */
- return NULL;
- }
-
- /* reload comparison data for current inner */
- if (MJEvalInnerValues(node, innerTupleSlot))
- {
- /* proceed to compare it to the current outer */
- node->mj_JoinState = EXEC_MJ_SKIP_TEST;
- }
- else
- {
- /*
- * current inner can't possibly match any outer;
- * better to advance the inner scan than the outer.
- */
- node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+ case MJEVAL_ENDOFJOIN:
+ /* No more inner tuples */
+ if (doFillOuter)
+ {
+ /*
+ * Need to emit left-join tuples for remaining
+ * outer tuples.
+ */
+ node->mj_JoinState = EXEC_MJ_ENDINNER;
+ break;
+ }
+ /* Otherwise we're done. */
+ return NULL;
}
}
break;
MJ_DEBUG_PROC_NODE(outerTupleSlot);
node->mj_MatchedOuter = false;
- /*
- * if the outer tuple is null then we are done with the join,
- * unless we have inner tuples we need to null-fill.
- */
- if (TupIsNull(outerTupleSlot))
- {
- MJ_printf("ExecMergeJoin: end of outer subplan\n");
- innerTupleSlot = node->mj_InnerTupleSlot;
- if (doFillInner && !TupIsNull(innerTupleSlot))
- {
- /*
- * Need to emit right-join tuples for remaining inner
- * tuples.
- */
- node->mj_JoinState = EXEC_MJ_ENDOUTER;
- break;
- }
- /* Otherwise we're done. */
- return NULL;
- }
-
/* Compute join values and check for unmatchability */
- if (MJEvalOuterValues(node))
+ switch (MJEvalOuterValues(node))
{
- /* Go test the new tuple against the current inner */
- node->mj_JoinState = EXEC_MJ_SKIP_TEST;
- }
- else
- {
- /* Can't match, so fetch next outer tuple */
- node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+ case MJEVAL_MATCHABLE:
+ /* Go test the new tuple against the current inner */
+ node->mj_JoinState = EXEC_MJ_SKIP_TEST;
+ break;
+ case MJEVAL_NONMATCHABLE:
+ /* Can't match, so fetch next outer tuple */
+ node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+ break;
+ case MJEVAL_ENDOFJOIN:
+ /* No more outer tuples */
+ MJ_printf("ExecMergeJoin: end of outer subplan\n");
+ innerTupleSlot = node->mj_InnerTupleSlot;
+ if (doFillInner && !TupIsNull(innerTupleSlot))
+ {
+ /*
+ * Need to emit right-join tuples for remaining
+ * inner tuples.
+ */
+ node->mj_JoinState = EXEC_MJ_ENDOUTER;
+ break;
+ }
+ /* Otherwise we're done. */
+ return NULL;
}
break;
MJ_DEBUG_PROC_NODE(innerTupleSlot);
node->mj_MatchedInner = false;
- /*
- * if the inner tuple is null then we are done with the join,
- * unless we have outer tuples we need to null-fill.
- */
- if (TupIsNull(innerTupleSlot))
+ /* Compute join values and check for unmatchability */
+ switch (MJEvalInnerValues(node, innerTupleSlot))
{
- MJ_printf("ExecMergeJoin: end of inner subplan\n");
- outerTupleSlot = node->mj_OuterTupleSlot;
- if (doFillOuter && !TupIsNull(outerTupleSlot))
- {
+ case MJEVAL_MATCHABLE:
+ /* proceed to compare it to the current outer */
+ node->mj_JoinState = EXEC_MJ_SKIP_TEST;
+ break;
+ case MJEVAL_NONMATCHABLE:
+
/*
- * Need to emit left-join tuples for remaining outer
- * tuples.
+ * current inner can't possibly match any outer;
+ * better to advance the inner scan than the outer.
*/
- node->mj_JoinState = EXEC_MJ_ENDINNER;
+ node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
break;
- }
- /* Otherwise we're done. */
- return NULL;
- }
-
- /* Compute join values and check for unmatchability */
- if (MJEvalInnerValues(node, innerTupleSlot))
- {
- /* proceed to compare it to the current outer */
- node->mj_JoinState = EXEC_MJ_SKIP_TEST;
- }
- else
- {
- /*
- * current inner can't possibly match any outer; better to
- * advance the inner scan than the outer.
- */
- node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+ case MJEVAL_ENDOFJOIN:
+ /* No more inner tuples */
+ MJ_printf("ExecMergeJoin: end of inner subplan\n");
+ outerTupleSlot = node->mj_OuterTupleSlot;
+ if (doFillOuter && !TupIsNull(outerTupleSlot))
+ {
+ /*
+ * Need to emit left-join tuples for remaining
+ * outer tuples.
+ */
+ node->mj_JoinState = EXEC_MJ_ENDINNER;
+ break;
+ }
+ /* Otherwise we're done. */
+ return NULL;
}
break;
mergestate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
(PlanState *) mergestate);
+ mergestate->mj_ConstFalseJoin = false;
/* mergeclauses are handled below */
/*
else
mergestate->mj_ExtraMarks = false;
-#define MERGEJOIN_NSLOTS 4
-
/*
* tuple table initialization
*/
ExecGetResultType(outerPlanState(mergestate)));
/*
- * Can't handle right or full join with non-nil extra joinclauses.
- * This should have been caught by planner.
+ * Can't handle right or full join with non-constant extra
+ * joinclauses. This should have been caught by planner.
*/
- if (node->join.joinqual != NIL)
+ if (!check_constant_qual(node->join.joinqual,
+ &mergestate->mj_ConstFalseJoin))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("RIGHT JOIN is only supported with merge-joinable join conditions")));
ExecGetResultType(innerPlanState(mergestate)));
/*
- * Can't handle right or full join with non-nil extra joinclauses.
+ * Can't handle right or full join with non-constant extra
+ * joinclauses. This should have been caught by planner.
*/
- if (node->join.joinqual != NIL)
+ if (!check_constant_qual(node->join.joinqual,
+ &mergestate->mj_ConstFalseJoin))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("FULL JOIN is only supported with merge-joinable join conditions")));
mergestate->mj_NumClauses = list_length(node->mergeclauses);
mergestate->mj_Clauses = MJExamineQuals(node->mergeclauses,
node->mergeFamilies,
+ node->mergeCollations,
node->mergeStrategies,
node->mergeNullsFirst,
(PlanState *) mergestate);
return mergestate;
}
-int
-ExecCountSlotsMergeJoin(MergeJoin *node)
-{
- return ExecCountSlotsNode(outerPlan((Plan *) node)) +
- ExecCountSlotsNode(innerPlan((Plan *) node)) +
- MERGEJOIN_NSLOTS;
-}
-
/* ----------------------------------------------------------------
* ExecEndMergeJoin
*
}
void
-ExecReScanMergeJoin(MergeJoinState *node, ExprContext *exprCtxt)
+ExecReScanMergeJoin(MergeJoinState *node)
{
ExecClearTuple(node->mj_MarkedTupleSlot);
* if chgParam of subnodes is not null then plans will be re-scanned by
* first ExecProcNode.
*/
- if (((PlanState *) node)->lefttree->chgParam == NULL)
- ExecReScan(((PlanState *) node)->lefttree, exprCtxt);
- if (((PlanState *) node)->righttree->chgParam == NULL)
- ExecReScan(((PlanState *) node)->righttree, exprCtxt);
+ if (node->js.ps.lefttree->chgParam == NULL)
+ ExecReScan(node->js.ps.lefttree);
+ if (node->js.ps.righttree->chgParam == NULL)
+ ExecReScan(node->js.ps.righttree);
}