* nodeMergejoin.c
* routines supporting merge joins
*
- * Portions Copyright (c) 1996-2005, 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.72 2005/05/13 21:20:16 tgl Exp $
+ * src/backend/executor/nodeMergejoin.c
*
*-------------------------------------------------------------------------
*/
* therefore it should scan the outer relation first to find a
* matching tuple and so on.
*
- * Therefore, when initializing the merge-join node, we look up the
- * associated sort operators. We assume the planner has seen to it
- * that the inputs are correctly sorted by these operators. Rather
- * than directly executing the merge join clauses, we evaluate the
- * left and right key expressions separately and then compare the
- * columns one at a time (see MJCompare).
+ * Therefore, rather than directly executing the merge join clauses,
+ * we evaluate the left and right key expressions separately and then
+ * compare the columns one at a time (see MJCompare). The planner
+ * passes us enough information about the sort ordering of the inputs
+ * to allow us to determine how to make the comparison. We may use the
+ * appropriate btree comparison function, since Postgres' only notion
+ * of ordering is specified by btree opfamilies.
*
*
* Consider the above relations and suppose that the executor has
*/
#include "postgres.h"
-#include "access/heapam.h"
#include "access/nbtree.h"
-#include "access/printtup.h"
-#include "catalog/pg_amop.h"
-#include "catalog/pg_operator.h"
#include "executor/execdebug.h"
-#include "executor/execdefs.h"
#include "executor/nodeMergejoin.h"
-#include "miscadmin.h"
-#include "utils/acl.h"
-#include "utils/catcache.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
-#include "utils/syscache.h"
/*
- * Comparison strategies supported by MJCompare
- *
- * XXX eventually should extend these to support descending-order sorts.
- * There are some tricky issues however about being sure we are on the same
- * page as the underlying sort or index as to which end NULLs sort to.
+ * States of the ExecMergeJoin state machine
*/
-typedef enum
-{
- MERGEFUNC_LT, /* raw "<" operator */
- MERGEFUNC_CMP /* -1 / 0 / 1 three-way comparator */
-} MergeFunctionKind;
+#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 */
+/*
+ * Runtime data for each mergejoin clause
+ */
typedef struct MergeJoinClauseData
{
/* Executable expression trees */
- ExprState *lexpr; /* left-hand (outer) input expression */
- ExprState *rexpr; /* right-hand (inner) input expression */
+ ExprState *lexpr; /* left-hand (outer) input expression */
+ ExprState *rexpr; /* right-hand (inner) input expression */
+
/*
* If we have a current left or right input tuple, the values of the
* expressions are loaded into these fields:
*/
- Datum ldatum; /* current left-hand value */
- Datum rdatum; /* current right-hand value */
- bool lisnull; /* and their isnull flags */
- bool risnull;
- /*
- * Remember whether mergejoin operator is strict (usually it will be).
- * NOTE: if it's not strict, we still assume it cannot return true for
- * one null and one non-null input.
- */
- bool mergestrict;
+ Datum ldatum; /* current left-hand value */
+ Datum rdatum; /* current right-hand value */
+ bool lisnull; /* and their isnull flags */
+ bool risnull;
+
/*
- * The comparison strategy in use, and the lookup info to let us call
- * the needed comparison routines. eqfinfo is the "=" operator itself.
- * cmpfinfo is either the btree comparator or the "<" operator.
+ * Everything we need to know to compare the left and right values is
+ * stored here.
*/
- MergeFunctionKind cmpstrategy;
- FmgrInfo eqfinfo;
- 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) \
* we will need at runtime. Each struct essentially tells us how to compare
* the two expressions from the original clause.
*
- * The best, most efficient way to compare two expressions is to use a btree
- * comparison support routine, since that requires only one function call
- * per comparison. Hence we try to find a btree opclass that matches the
- * mergejoinable operator. If we cannot find one, we'll have to call both
- * the "=" and (often) the "<" operator for each comparison.
+ * In addition to the expressions themselves, the planner passes the btree
+ * 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 the opfamily, and the two inputs are guaranteed to be
+ * ordered in either increasing or decreasing (respectively) order according
+ * 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 *qualList, PlanState *parent)
+MJExamineQuals(List *mergeclauses,
+ Oid *mergefamilies,
+ Oid *mergecollations,
+ int *mergestrategies,
+ bool *mergenullsfirst,
+ PlanState *parent)
{
MergeJoinClause clauses;
- int nClauses = list_length(qualList);
+ int nClauses = list_length(mergeclauses);
int iClause;
- ListCell *l;
+ ListCell *cl;
clauses = (MergeJoinClause) palloc0(nClauses * sizeof(MergeJoinClauseData));
iClause = 0;
- foreach(l, qualList)
+ foreach(cl, mergeclauses)
{
- OpExpr *qual = (OpExpr *) lfirst(l);
+ OpExpr *qual = (OpExpr *) lfirst(cl);
MergeJoinClause clause = &clauses[iClause];
- Oid ltop;
- Oid gtop;
- RegProcedure ltproc;
- RegProcedure gtproc;
- AclResult aclresult;
- CatCList *catlist;
- int i;
+ 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;
+ 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);
- /*
- * Check permission to call the mergejoinable operator.
- * For predictability, we check this even if we end up not using it.
- */
- aclresult = pg_proc_aclcheck(qual->opfuncid, GetUserId(), ACL_EXECUTE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_PROC,
- get_func_name(qual->opfuncid));
-
- /* Set up the fmgr lookup information */
- fmgr_info(qual->opfuncid, &(clause->eqfinfo));
-
- /* And remember strictness */
- clause->mergestrict = clause->eqfinfo.fn_strict;
-
- /*
- * Lookup the comparison operators that go with the mergejoinable
- * top-level operator. (This will elog if the operator isn't
- * mergejoinable, which would be the planner's mistake.)
- */
- op_mergejoin_crossops(qual->opno,
- <op,
- >op,
- <proc,
- >proc);
-
- clause->cmpstrategy = MERGEFUNC_LT;
-
- /*
- * Look for a btree opclass including all three operators.
- * This is much like SelectSortFunction except we insist on
- * matching all the operators provided, and it can be a cross-type
- * opclass.
- *
- * XXX for now, insist on forward sort so that NULLs can be counted
- * on to be high.
- */
- catlist = SearchSysCacheList(AMOPOPID, 1,
- ObjectIdGetDatum(qual->opno),
- 0, 0, 0);
-
- for (i = 0; i < catlist->n_members; i++)
+ /* 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, false,
+ &op_strategy,
+ &op_lefttype,
+ &op_righttype);
+ if (op_strategy != BTEqualStrategyNumber) /* should not happen */
+ elog(ERROR, "cannot merge using non-equality operator %u",
+ qual->opno);
+
+ /* And get the matching support or comparison function */
+ sortfunc = get_opfamily_proc(opfamily,
+ op_lefttype,
+ op_righttype,
+ BTSORTSUPPORT_PROC);
+ if (OidIsValid(sortfunc))
{
- HeapTuple tuple = &catlist->members[i]->tuple;
- Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
- Oid opcid = aform->amopclaid;
-
- if (aform->amopstrategy != BTEqualStrategyNumber)
- continue;
- if (!opclass_is_btree(opcid))
- continue;
- if (get_op_opclass_strategy(ltop, opcid) == BTLessStrategyNumber &&
- get_op_opclass_strategy(gtop, opcid) == BTGreaterStrategyNumber)
- {
- clause->cmpstrategy = MERGEFUNC_CMP;
- ltproc = get_opclass_proc(opcid, aform->amopsubtype,
- BTORDER_PROC);
- Assert(RegProcedureIsValid(ltproc));
- break; /* done looking */
- }
+ /* 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);
}
-
- ReleaseSysCacheList(catlist);
-
- /* Check permission to call "<" operator or cmp function */
- aclresult = pg_proc_aclcheck(ltproc, GetUserId(), ACL_EXECUTE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_PROC,
- get_func_name(ltproc));
-
- /* Set up the fmgr lookup information */
- fmgr_info(ltproc, &(clause->cmpfinfo));
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 for any strict mergejoin operator.
+ * 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 && clause->mergestrict)
- canmatch = false;
+ if (clause->lisnull)
+ {
+ /* 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;
}
/*
* MJEvalInnerValues
*
- * Same as above, but for the inner tuple. Here, we have to be prepared
+ * Same as above, but for the inner tuple. Here, we have to be prepared
* 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 && clause->mergestrict)
- canmatch = false;
+ if (clause->risnull)
+ {
+ /* 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.
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 memory.
+ * Call the comparison functions in short-lived context, in case they leak
+ * memory.
*/
ResetExprContext(econtext);
for (i = 0; i < mergestate->mj_NumClauses; i++)
{
MergeJoinClause clause = &mergestate->mj_Clauses[i];
- Datum fresult;
/*
- * Deal with null inputs. We treat NULL as sorting after non-NULL.
- *
- * If both inputs are NULL, and the comparison function isn't
- * strict, then we call it and check for a true result (this allows
- * operators that behave like IS NOT DISTINCT to be mergejoinable).
- * If the function is strict or returns false, we temporarily
- * pretend NULL == NULL and contine checking remaining columns.
+ * Special case for NULL-vs-NULL, else use standard comparison.
*/
- if (clause->lisnull)
- {
- if (clause->risnull)
- {
- if (!clause->eqfinfo.fn_strict)
- {
- InitFunctionCallInfoData(fcinfo, &(clause->eqfinfo), 2,
- NULL, NULL);
- fcinfo.arg[0] = clause->ldatum;
- fcinfo.arg[1] = clause->rdatum;
- fcinfo.argnull[0] = true;
- fcinfo.argnull[1] = true;
- fresult = FunctionCallInvoke(&fcinfo);
- if (!fcinfo.isnull && DatumGetBool(fresult))
- {
- /* treat nulls as really equal */
- continue;
- }
- }
- nulleqnull = true;
- continue;
- }
- /* NULL > non-NULL */
- result = 1;
- break;
- }
- if (clause->risnull)
+ if (clause->lisnull && clause->risnull)
{
- /* non-NULL < NULL */
- result = -1;
- break;
+ nulleqnull = true; /* NULL "=" NULL */
+ continue;
}
- if (clause->cmpstrategy == MERGEFUNC_LT)
- {
- InitFunctionCallInfoData(fcinfo, &(clause->eqfinfo), 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;
- continue;
- }
- else if (DatumGetBool(fresult))
- {
- /* equal */
- continue;
- }
- 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;
- continue;
- }
- else if (DatumGetBool(fresult))
- {
- /* less than */
- result = -1;
- break;
- }
- else
- {
- /* greater than */
- result = 1;
- break;
- }
- }
- else /* must be MERGEFUNC_CMP */
- {
- 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;
- continue;
- }
- else if (DatumGetInt32(fresult) == 0)
- {
- /* equal */
- continue;
- }
- else if (DatumGetInt32(fresult) < 0)
- {
- /* less than */
- result = -1;
- break;
- }
- else
- {
- /* greater than */
- result = 1;
- break;
- }
- }
+ 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;
}
+
+/*
+ * Generate a fake join tuple with nulls for the inner tuple,
+ * and return it if it passes the non-join quals.
+ */
+static TupleTableSlot *
+MJFillOuter(MergeJoinState *node)
+{
+ ExprContext *econtext = node->js.ps.ps_ExprContext;
+ List *otherqual = node->js.ps.qual;
+
+ ResetExprContext(econtext);
+
+ econtext->ecxt_outertuple = node->mj_OuterTupleSlot;
+ econtext->ecxt_innertuple = node->mj_NullInnerTupleSlot;
+
+ if (ExecQual(otherqual, econtext, false))
+ {
+ /*
+ * qualification succeeded. now form the desired projection tuple and
+ * return the slot containing it.
+ */
+ TupleTableSlot *result;
+ ExprDoneCond isDone;
+
+ MJ_printf("ExecMergeJoin: returning outer fill tuple\n");
+
+ result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ node->js.ps.ps_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ return result;
+ }
+ }
+ else
+ InstrCountFiltered2(node, 1);
+
+ return NULL;
+}
+
+/*
+ * Generate a fake join tuple with nulls for the outer tuple,
+ * and return it if it passes the non-join quals.
+ */
+static TupleTableSlot *
+MJFillInner(MergeJoinState *node)
+{
+ ExprContext *econtext = node->js.ps.ps_ExprContext;
+ List *otherqual = node->js.ps.qual;
+
+ ResetExprContext(econtext);
+
+ econtext->ecxt_outertuple = node->mj_NullOuterTupleSlot;
+ econtext->ecxt_innertuple = node->mj_InnerTupleSlot;
+
+ if (ExecQual(otherqual, econtext, false))
+ {
+ /*
+ * qualification succeeded. now form the desired projection tuple and
+ * return the slot containing it.
+ */
+ TupleTableSlot *result;
+ ExprDoneCond isDone;
+
+ MJ_printf("ExecMergeJoin: returning inner fill tuple\n");
+
+ result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
+
+ if (isDone != ExprEndResult)
+ {
+ node->js.ps.ps_TupFromTlist =
+ (isDone == ExprMultipleResult);
+ 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;
/*
* get information from node
*/
- estate = node->js.ps.state;
innerPlan = innerPlanState(node);
outerPlan = outerPlanState(node);
econtext = node->js.ps.ps_ExprContext;
joinqual = node->js.joinqual;
otherqual = node->js.ps.qual;
-
- switch (node->js.jointype)
- {
- case JOIN_INNER:
- case JOIN_IN:
- doFillOuter = false;
- doFillInner = false;
- break;
- case JOIN_LEFT:
- doFillOuter = true;
- doFillInner = false;
- break;
- case JOIN_FULL:
- doFillOuter = true;
- doFillInner = true;
- break;
- case JOIN_RIGHT:
- doFillOuter = false;
- doFillInner = true;
- break;
- default:
- elog(ERROR, "unrecognized join type: %d",
- (int) node->js.jointype);
- doFillOuter = false; /* keep compiler quiet */
- doFillInner = false;
- break;
- }
+ doFillOuter = node->mj_FillOuter;
+ doFillInner = node->mj_FillInner;
/*
- * Check to see if we're still projecting out tuples from a previous
- * join tuple (because there is a function-returning-set in the
- * projection expressions). If so, try to project another one.
+ * Check to see if we're still projecting out tuples from a previous join
+ * tuple (because there is a function-returning-set in the projection
+ * expressions). If so, try to project another one.
*/
if (node->js.ps.ps_TupFromTlist)
{
/*
* Reset per-tuple memory context to free any expression evaluation
- * storage allocated in the previous tuple cycle. Note this can't
- * happen until we're done projecting out tuples from a join tuple.
+ * storage allocated in the previous tuple cycle. Note this can't happen
+ * until we're done projecting out tuples from a join tuple.
*/
ResetExprContext(econtext);
{
/*
* EXEC_MJ_INITIALIZE_OUTER means that this is the first time
- * ExecMergeJoin() has been called and so we have to fetch
- * the first matchable tuple for both outer and inner subplans.
- * We do the outer side in INITIALIZE_OUTER state, then
- * advance to INITIALIZE_INNER state for the inner subplan.
+ * ExecMergeJoin() has been called and so we have to fetch the
+ * first matchable tuple for both outer and inner subplans. We
+ * do the outer side in INITIALIZE_OUTER state, then advance
+ * to INITIALIZE_INNER state for the inner subplan.
*/
case EXEC_MJ_INITIALIZE_OUTER:
MJ_printf("ExecMergeJoin: EXEC_MJ_INITIALIZE_OUTER\n");
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) && !doFillOuter)
+ switch (MJEvalOuterValues(node))
{
- /* Stay in same state to fetch next outer tuple */
- node->mj_JoinState = EXEC_MJ_INITIALIZE_OUTER;
- }
- else
- {
- /* OK to go get the first inner tuple */
- node->mj_JoinState = EXEC_MJ_INITIALIZE_INNER;
+ 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;
+ }
+ 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))
+
+ /* Compute join values and check for unmatchability */
+ switch (MJEvalInnerValues(node, innerTupleSlot))
{
- MJ_printf("ExecMergeJoin: nothing in inner subplan\n");
- if (doFillOuter)
- {
+ case MJEVAL_MATCHABLE:
+
/*
- * 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.
+ * OK, we have the initial tuples. Begin by skipping
+ * non-matching tuples.
*/
- node->mj_JoinState = EXEC_MJ_ENDINNER;
- node->mj_MatchedOuter = false;
+ node->mj_JoinState = EXEC_MJ_SKIP_TEST;
break;
- }
- /* Otherwise we're done. */
- return NULL;
- }
+ 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;
- /* Compute join values and check for unmatchability */
- if (!MJEvalInnerValues(node, innerTupleSlot) && !doFillInner)
- {
- /* Stay in same state to fetch next inner tuple */
- node->mj_JoinState = EXEC_MJ_INITIALIZE_INNER;
- }
- else
- {
- /*
- * OK, we have the initial tuples. Begin by skipping
- * non-matching tuples.
- */
- node->mj_JoinState = EXEC_MJ_SKIP_TEST;
+ 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;
/*
- * EXEC_MJ_JOINTUPLES means we have two tuples which
- * satisfied the merge clause so we join them and then
- * proceed to get the next inner tuple (EXEC_MJ_NEXTINNER).
+ * EXEC_MJ_JOINTUPLES means we have two tuples which satisfied
+ * the merge clause so we join them and then proceed to get
+ * the next inner tuple (EXEC_MJ_NEXTINNER).
*/
case EXEC_MJ_JOINTUPLES:
MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTUPLES\n");
node->mj_JoinState = EXEC_MJ_NEXTINNER;
/*
- * Check the extra qual conditions to see if we actually
- * want to return this join tuple. If not, can proceed
- * with merge. We must distinguish the additional
- * joinquals (which must pass to consider the tuples
- * "matched" for outer-join logic) from the otherquals
- * (which must pass before we actually return the tuple).
+ * Check the extra qual conditions to see if we actually want
+ * to return this join tuple. If not, can proceed with merge.
+ * We must distinguish the additional joinquals (which must
+ * pass to consider the tuples "matched" for outer-join logic)
+ * from the otherquals (which must pass before we actually
+ * return the tuple).
*
* We don't bother with a ResetExprContext here, on the
- * assumption that we just did one while checking the
- * merge qual. One per tuple should be sufficient. We
- * do have to set up the econtext links to the tuples
- * for ExecQual to use.
+ * assumption that we just did one while checking the merge
+ * qual. One per tuple should be sufficient. We do have to
+ * set up the econtext links to the tuples for ExecQual to
+ * use.
*/
outerTupleSlot = node->mj_OuterTupleSlot;
econtext->ecxt_outertuple = outerTupleSlot;
innerTupleSlot = node->mj_InnerTupleSlot;
econtext->ecxt_innertuple = innerTupleSlot;
- if (node->js.jointype == JOIN_IN &&
- node->mj_MatchedOuter)
- qualResult = false;
- else
- {
- qualResult = (joinqual == NIL ||
- ExecQual(joinqual, econtext, false));
- MJ_DEBUG_QUAL(joinqual, qualResult);
- }
+ qualResult = (joinqual == NIL ||
+ ExecQual(joinqual, econtext, false));
+ MJ_DEBUG_QUAL(joinqual, qualResult);
if (qualResult)
{
node->mj_MatchedOuter = true;
node->mj_MatchedInner = true;
+ /* In an antijoin, we never return a matched tuple */
+ if (node->js.jointype == JOIN_ANTI)
+ {
+ node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+ break;
+ }
+
+ /*
+ * 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;
+
qualResult = (otherqual == NIL ||
ExecQual(otherqual, econtext, false));
MJ_DEBUG_QUAL(otherqual, qualResult);
{
/*
* qualification succeeded. now form the desired
- * projection tuple and return the slot containing
- * it.
+ * projection tuple and return the slot containing it.
*/
TupleTableSlot *result;
ExprDoneCond isDone;
return result;
}
}
+ else
+ InstrCountFiltered2(node, 1);
}
+ else
+ InstrCountFiltered1(node, 1);
break;
/*
- * EXEC_MJ_NEXTINNER means advance the inner scan to the
- * next tuple. If the tuple is not nil, we then proceed to
- * test it against the join qualification.
+ * EXEC_MJ_NEXTINNER means advance the inner scan to the next
+ * tuple. If the tuple is not nil, we then proceed to test it
+ * against the join qualification.
*
* Before advancing, we check to see if we must emit an
* outer-join fill tuple for this inner tuple.
{
/*
* Generate a fake join tuple with nulls for the outer
- * tuple, and return it if it passes the non-join
- * quals.
+ * tuple, and return it if it passes the non-join quals.
*/
- node->mj_MatchedInner = true; /* do it only once */
+ TupleTableSlot *result;
- ResetExprContext(econtext);
-
- outerTupleSlot = node->mj_NullOuterTupleSlot;
- econtext->ecxt_outertuple = outerTupleSlot;
- innerTupleSlot = node->mj_InnerTupleSlot;
- econtext->ecxt_innertuple = innerTupleSlot;
-
- if (ExecQual(otherqual, econtext, false))
- {
- /*
- * qualification succeeded. now form the desired
- * projection tuple and return the slot containing
- * it.
- */
- TupleTableSlot *result;
- ExprDoneCond isDone;
-
- MJ_printf("ExecMergeJoin: returning fill tuple\n");
-
- result = ExecProject(node->js.ps.ps_ProjInfo,
- &isDone);
+ node->mj_MatchedInner = true; /* do it only once */
- if (isDone != ExprEndResult)
- {
- node->js.ps.ps_TupFromTlist =
- (isDone == ExprMultipleResult);
- return result;
- }
- }
+ result = MJFillInner(node);
+ if (result)
+ return result;
}
/*
- * now we get the next inner tuple, if any. If there's
- * none, advance to next outer tuple (which may be able
- * to join to previously marked tuples).
+ * now we get the next inner tuple, if any. If there's none,
+ * advance to next outer tuple (which may be able to join to
+ * previously marked tuples).
*
- * If we find one but it cannot join to anything, stay
- * in NEXTINNER state to fetch the next one.
+ * NB: must NOT do "extraMarks" here, since we may need to
+ * return to previously marked tuples.
*/
innerTupleSlot = ExecProcNode(innerPlan);
node->mj_InnerTupleSlot = innerTupleSlot;
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:
- if (!MJEvalInnerValues(node, innerTupleSlot))
- break; /* stay in NEXTINNER state */
+ /*
+ * 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;
{
/*
* Generate a fake join tuple with nulls for the inner
- * tuple, and return it if it passes the non-join
- * quals.
+ * tuple, and return it if it passes the non-join quals.
*/
- node->mj_MatchedOuter = true; /* do it only once */
+ TupleTableSlot *result;
- ResetExprContext(econtext);
-
- outerTupleSlot = node->mj_OuterTupleSlot;
- econtext->ecxt_outertuple = outerTupleSlot;
- innerTupleSlot = node->mj_NullInnerTupleSlot;
- econtext->ecxt_innertuple = innerTupleSlot;
-
- if (ExecQual(otherqual, econtext, false))
- {
- /*
- * qualification succeeded. now form the desired
- * projection tuple and return the slot containing
- * it.
- */
- TupleTableSlot *result;
- ExprDoneCond isDone;
-
- MJ_printf("ExecMergeJoin: returning fill tuple\n");
-
- result = ExecProject(node->js.ps.ps_ProjInfo,
- &isDone);
+ node->mj_MatchedOuter = true; /* do it only once */
- if (isDone != ExprEndResult)
- {
- node->js.ps.ps_TupFromTlist =
- (isDone == ExprMultipleResult);
- return result;
- }
- }
+ result = MJFillOuter(node);
+ if (result)
+ return result;
}
/*
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))
{
- /* Stay in same state to fetch next outer tuple */
- node->mj_JoinState = EXEC_MJ_NEXTOUTER;
- }
- else
- {
- /* Go test the tuple */
- node->mj_JoinState = EXEC_MJ_TESTOUTER;
+ 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;
* tuple satisfy the merge clause then we know we have
* duplicates in the outer scan so we have to restore the
* inner scan to the marked tuple and proceed to join the
- * new outer tuples with the inner tuples.
+ * new outer tuple with the inner tuples.
*
* This is the case when
* outer inner
MJ_printf("ExecMergeJoin: EXEC_MJ_TESTOUTER\n");
/*
- * here we must compare the outer tuple with the marked inner
- * tuple
+ * Here we must compare the outer tuple with the marked inner
+ * tuple. (We can ignore the result of MJEvalInnerValues,
+ * since the marked inner tuple is certainly matchable.)
*/
innerTupleSlot = node->mj_MarkedTupleSlot;
(void) MJEvalInnerValues(node, innerTupleSlot);
if (compareResult == 0)
{
/*
- * the merge clause matched so now we restore the
- * inner scan position to the first mark, and go join
- * that tuple (and any following ones) to the new outer.
+ * the merge clause matched so now we restore the inner
+ * scan position to the first mark, and go join that tuple
+ * (and any following ones) to the new outer.
*
* NOTE: we do not need to worry about the MatchedInner
- * 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.
+ * 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 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);
/*
- * ExecRestrPos really should give us back a new Slot,
- * but since it doesn't, use the marked slot.
+ * ExecRestrPos probably should give us back a new Slot,
+ * but since it doesn't, use the marked slot. (The
+ * previously returned mj_InnerTupleSlot cannot be assumed
+ * to hold the required tuple.)
*/
node->mj_InnerTupleSlot = innerTupleSlot;
/* we need not do MJEvalInnerValues again */
* which means that all subsequent outer tuples will be
* 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.
+ * look for a match to the current inner. If there's
+ * 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;
+ 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;
}
-
- /* reload comparison data for current inner */
- (void) MJEvalInnerValues(node, innerTupleSlot);
-
- /* continue on to skip outer tuples */
- node->mj_JoinState = EXEC_MJ_SKIP_TEST;
}
break;
/*
* before we advance, make sure the current tuples do not
- * satisfy the mergeclauses. If they do, then we update
- * the marked tuple position and go join them.
+ * satisfy the mergeclauses. If they do, then we update the
+ * marked tuple position and go join them.
*/
compareResult = MJCompare(node);
MJ_DEBUG_COMPARE(compareResult);
}
else if (compareResult < 0)
node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
- else /* compareResult > 0 */
+ else
+ /* compareResult > 0 */
node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
break;
/*
+ * SKIPOUTER_ADVANCE: advance over an outer tuple that is
+ * known not to join to any inner tuple.
+ *
* Before advancing, we check to see if we must emit an
* outer-join fill tuple 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.
+ * tuple, and return it if it passes the non-join quals.
*/
- node->mj_MatchedOuter = true; /* do it only once */
+ TupleTableSlot *result;
- ResetExprContext(econtext);
-
- outerTupleSlot = node->mj_OuterTupleSlot;
- econtext->ecxt_outertuple = outerTupleSlot;
- innerTupleSlot = node->mj_NullInnerTupleSlot;
- econtext->ecxt_innertuple = innerTupleSlot;
-
- if (ExecQual(otherqual, econtext, false))
- {
- /*
- * qualification succeeded. now form the desired
- * projection tuple and return the slot containing
- * it.
- */
- TupleTableSlot *result;
- ExprDoneCond isDone;
-
- MJ_printf("ExecMergeJoin: returning fill tuple\n");
-
- result = ExecProject(node->js.ps.ps_ProjInfo,
- &isDone);
+ node->mj_MatchedOuter = true; /* do it only once */
- if (isDone != ExprEndResult)
- {
- node->js.ps.ps_TupFromTlist =
- (isDone == ExprMultipleResult);
- return result;
- }
- }
+ result = MJFillOuter(node);
+ if (result)
+ return result;
}
/*
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))
{
- /* Stay in same state to fetch next outer tuple */
- node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
- break;
+ 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;
}
-
- /* Test the new tuple against the current inner */
- node->mj_JoinState = EXEC_MJ_SKIP_TEST;
break;
/*
+ * SKIPINNER_ADVANCE: advance over an inner tuple that is
+ * known not to join to any outer tuple.
+ *
* Before advancing, we check to see if we must emit an
* outer-join fill tuple for this inner tuple.
*/
{
/*
* Generate a fake join tuple with nulls for the outer
- * tuple, and return it if it passes the non-join
- * quals.
+ * tuple, and return it if it passes the non-join quals.
*/
- node->mj_MatchedInner = true; /* do it only once */
-
- ResetExprContext(econtext);
-
- outerTupleSlot = node->mj_NullOuterTupleSlot;
- econtext->ecxt_outertuple = outerTupleSlot;
- innerTupleSlot = node->mj_InnerTupleSlot;
- econtext->ecxt_innertuple = innerTupleSlot;
-
- if (ExecQual(otherqual, econtext, false))
- {
- /*
- * qualification succeeded. now form the desired
- * projection tuple and return the slot containing
- * it.
- */
- TupleTableSlot *result;
- ExprDoneCond isDone;
+ TupleTableSlot *result;
- MJ_printf("ExecMergeJoin: returning fill tuple\n");
-
- result = ExecProject(node->js.ps.ps_ProjInfo,
- &isDone);
+ node->mj_MatchedInner = true; /* do it only once */
- if (isDone != ExprEndResult)
- {
- node->js.ps.ps_TupFromTlist =
- (isDone == ExprMultipleResult);
- return result;
- }
- }
+ result = MJFillInner(node);
+ if (result)
+ return result;
}
+ /* Mark before advancing, if wanted */
+ if (node->mj_ExtraMarks)
+ ExecMarkPos(innerPlan);
+
/*
* now we get the next inner tuple, if any
*/
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))
- {
- /* Stay in same state to fetch next inner tuple */
- node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
- break;
+ 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;
}
-
- /* Test the new tuple against the current outer */
- node->mj_JoinState = EXEC_MJ_SKIP_TEST;
break;
/*
- * EXEC_MJ_ENDOUTER means we have run out of outer tuples,
- * but are doing a right/full join and therefore must
- * null-fill any remaing unmatched inner tuples.
+ * EXEC_MJ_ENDOUTER means we have run out of outer tuples, but
+ * are doing a right/full join and therefore must null-fill
+ * any remaing unmatched inner tuples.
*/
case EXEC_MJ_ENDOUTER:
MJ_printf("ExecMergeJoin: EXEC_MJ_ENDOUTER\n");
{
/*
* Generate a fake join tuple with nulls for the outer
- * tuple, and return it if it passes the non-join
- * quals.
+ * tuple, and return it if it passes the non-join quals.
*/
- node->mj_MatchedInner = true; /* do it only once */
-
- ResetExprContext(econtext);
-
- outerTupleSlot = node->mj_NullOuterTupleSlot;
- econtext->ecxt_outertuple = outerTupleSlot;
- innerTupleSlot = node->mj_InnerTupleSlot;
- econtext->ecxt_innertuple = innerTupleSlot;
+ TupleTableSlot *result;
- if (ExecQual(otherqual, econtext, false))
- {
- /*
- * qualification succeeded. now form the desired
- * projection tuple and return the slot containing
- * it.
- */
- TupleTableSlot *result;
- ExprDoneCond isDone;
-
- MJ_printf("ExecMergeJoin: returning fill tuple\n");
-
- result = ExecProject(node->js.ps.ps_ProjInfo,
- &isDone);
+ node->mj_MatchedInner = true; /* do it only once */
- if (isDone != ExprEndResult)
- {
- node->js.ps.ps_TupFromTlist =
- (isDone == ExprMultipleResult);
- return result;
- }
- }
+ result = MJFillInner(node);
+ if (result)
+ return result;
}
+ /* Mark before advancing, if wanted */
+ if (node->mj_ExtraMarks)
+ ExecMarkPos(innerPlan);
+
/*
* now we get the next inner tuple, if any
*/
break;
/*
- * EXEC_MJ_ENDINNER means we have run out of inner tuples,
- * but are doing a left/full join and therefore must null-
- * fill any remaing unmatched outer tuples.
+ * EXEC_MJ_ENDINNER means we have run out of inner tuples, but
+ * are doing a left/full join and therefore must null- fill
+ * any remaing unmatched outer tuples.
*/
case EXEC_MJ_ENDINNER:
MJ_printf("ExecMergeJoin: EXEC_MJ_ENDINNER\n");
{
/*
* Generate a fake join tuple with nulls for the inner
- * tuple, and return it if it passes the non-join
- * quals.
+ * tuple, and return it if it passes the non-join quals.
*/
- node->mj_MatchedOuter = true; /* do it only once */
-
- ResetExprContext(econtext);
+ TupleTableSlot *result;
- outerTupleSlot = node->mj_OuterTupleSlot;
- econtext->ecxt_outertuple = outerTupleSlot;
- innerTupleSlot = node->mj_NullInnerTupleSlot;
- econtext->ecxt_innertuple = innerTupleSlot;
-
- if (ExecQual(otherqual, econtext, false))
- {
- /*
- * qualification succeeded. now form the desired
- * projection tuple and return the slot containing
- * it.
- */
- TupleTableSlot *result;
- ExprDoneCond isDone;
-
- MJ_printf("ExecMergeJoin: returning fill tuple\n");
-
- result = ExecProject(node->js.ps.ps_ProjInfo,
- &isDone);
+ node->mj_MatchedOuter = true; /* do it only once */
- if (isDone != ExprEndResult)
- {
- node->js.ps.ps_TupFromTlist =
- (isDone == ExprMultipleResult);
- return result;
- }
- }
+ result = MJFillOuter(node);
+ if (result)
+ return result;
}
/*
* ----------------------------------------------------------------
*/
MergeJoinState *
-ExecInitMergeJoin(MergeJoin *node, EState *estate)
+ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
{
MergeJoinState *mergestate;
+ /* check for unsupported flags */
+ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
+
MJ1_printf("ExecInitMergeJoin: %s\n",
"initializing node");
ExecAssignExprContext(estate, &mergestate->js.ps);
/*
- * we need two additional econtexts in which we can compute the
- * join expressions from the left and right input tuples. The
- * node's regular econtext won't do because it gets reset too
- * often.
+ * we need two additional econtexts in which we can compute the join
+ * expressions from the left and right input tuples. The node's regular
+ * econtext won't do because it gets reset too often.
*/
mergestate->mj_OuterEContext = CreateExprContext(estate);
mergestate->mj_InnerEContext = CreateExprContext(estate);
mergestate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
(PlanState *) mergestate);
+ mergestate->mj_ConstFalseJoin = false;
/* mergeclauses are handled below */
/*
* initialize child nodes
+ *
+ * inner child must support MARK/RESTORE.
*/
- outerPlanState(mergestate) = ExecInitNode(outerPlan(node), estate);
- innerPlanState(mergestate) = ExecInitNode(innerPlan(node), estate);
+ outerPlanState(mergestate) = ExecInitNode(outerPlan(node), estate, eflags);
+ innerPlanState(mergestate) = ExecInitNode(innerPlan(node), estate,
+ eflags | EXEC_FLAG_MARK);
-#define MERGEJOIN_NSLOTS 4
+ /*
+ * For certain types of inner child nodes, it is advantageous to issue
+ * MARK every time we advance past an inner tuple we will never return to.
+ * For other types, MARK on a tuple we cannot return to is a waste of
+ * cycles. Detect which case applies and set mj_ExtraMarks if we want to
+ * issue "unnecessary" MARK calls.
+ *
+ * Currently, only Material wants the extra MARKs, and it will be helpful
+ * only if eflags doesn't specify REWIND.
+ */
+ if (IsA(innerPlan(node), Material) &&
+ (eflags & EXEC_FLAG_REWIND) == 0)
+ mergestate->mj_ExtraMarks = true;
+ else
+ mergestate->mj_ExtraMarks = false;
/*
* tuple table initialization
mergestate->mj_MarkedTupleSlot = ExecInitExtraTupleSlot(estate);
ExecSetSlotDescriptor(mergestate->mj_MarkedTupleSlot,
- ExecGetResultType(innerPlanState(mergestate)),
- false);
+ ExecGetResultType(innerPlanState(mergestate)));
switch (node->join.jointype)
{
case JOIN_INNER:
- case JOIN_IN:
+ case JOIN_SEMI:
+ mergestate->mj_FillOuter = false;
+ mergestate->mj_FillInner = false;
break;
case JOIN_LEFT:
+ case JOIN_ANTI:
+ mergestate->mj_FillOuter = true;
+ mergestate->mj_FillInner = false;
mergestate->mj_NullInnerTupleSlot =
ExecInitNullTupleSlot(estate,
- ExecGetResultType(innerPlanState(mergestate)));
+ ExecGetResultType(innerPlanState(mergestate)));
break;
case JOIN_RIGHT:
+ mergestate->mj_FillOuter = false;
+ mergestate->mj_FillInner = true;
mergestate->mj_NullOuterTupleSlot =
ExecInitNullTupleSlot(estate,
- ExecGetResultType(outerPlanState(mergestate)));
+ ExecGetResultType(outerPlanState(mergestate)));
/*
- * Can't handle right or full join with non-nil extra
+ * 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")));
break;
case JOIN_FULL:
+ mergestate->mj_FillOuter = true;
+ mergestate->mj_FillInner = true;
mergestate->mj_NullOuterTupleSlot =
ExecInitNullTupleSlot(estate,
- ExecGetResultType(outerPlanState(mergestate)));
+ ExecGetResultType(outerPlanState(mergestate)));
mergestate->mj_NullInnerTupleSlot =
ExecInitNullTupleSlot(estate,
- ExecGetResultType(innerPlanState(mergestate)));
+ 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")));
* initialize tuple type and projection info
*/
ExecAssignResultTypeFromTL(&mergestate->js.ps);
- ExecAssignProjectionInfo(&mergestate->js.ps);
+ ExecAssignProjectionInfo(&mergestate->js.ps, NULL);
/*
* preprocess the merge clauses
*/
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);
node->mj_InnerTupleSlot = NULL;
/*
- * if chgParam of subnodes is not null then plans will be re-scanned
- * by first ExecProcNode.
+ * 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);
}