New rangejoin patch attached. I had previously attempted to make this work well for multiple range join keys, but this patch implements single rangejoin keys only, and the rest of the rangejoin clauses are effectively just rechecks. I believe it can be made effective for multiple rangejoin keys, but at the cost of additional complexity which is neither justified nor implemented at this point.
Regards, Jeff Davis
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml index 3a034d9..e27150d 100644 --- a/doc/src/sgml/rangetypes.sgml +++ b/doc/src/sgml/rangetypes.sgml @@ -522,4 +522,74 @@ INSERT 0 1 </programlisting> </para> </sect2> + <sect2 id="rangetypes-join"> + <title>Range Join</title> + + <indexterm> + <primary>range type</primary> + <secondary>join</secondary> + </indexterm> + + <para> + A Range Join is a join where one of the join conditions is the range + overlaps operator (see <xref linkend="range-operators-table">). In other + words, rather than returning rows where corresponding fields are equal, it + returns rows where the corresponding fields overlap. + </para> + <para> + For instance, to find users who were active on a website at the same time + as they were using a mobile app, you might issue a query like: +<programlisting> +CREATE TABLE web_session( + web_session_id text primary key, + username text, + during tstzrange); +CREATE TABLE app_session( + app_session_id text primary key, + username text, + during tstzrange); +INSERT INTO web_session VALUES + ('0cc175b9c0f1b6a8', 'user1', '[2017-02-01 09:30, 2017-02-01 10:45)'), + ('92eb5ffee6ae2fec', 'user2', '[2017-02-01 09:30, 2017-02-01 10:45)'), + ('4a8a08f09d37b737', 'user3', '[2017-02-01 09:30, 2017-02-01 10:45)'); +INSERT INTO app_session VALUES + ('8277e0910d750195', 'user1', '[2017-02-01 10:30, 2017-02-01 11:45)'), + ('b448797616e091ad', 'user3', '[2017-02-01 09:00, 2017-02-01 11:00)'), + ('95649038408b5f33', 'user4', '[2017-02-01 09:30, 2017-02-01 10:45)'); + +SELECT w.username, + w.during * a.during AS both_during + FROM web_session w, app_session a + WHERE w.username = a.username + AND w.during && a.during; + username | both_during +----------+----------------------------------------------------- + user1 | ["2017-02-01 10:30:00-08","2017-02-01 10:45:00-08") + user3 | ["2017-02-01 09:30:00-08","2017-02-01 10:45:00-08") +(2 rows) +</programlisting> + </para> + <para> + In addition to the general execution strategies available, Postgres also + has special support for a variant of Merge Join called Range Merge Join: +<programlisting> + EXPLAIN (costs off) SELECT w.username, + w.during * a.during AS both_during + FROM web_session w, app_session a + WHERE w.username = a.username + AND w.during && a.during; + QUERY PLAN +---------------------------------------------------------------------- + Range Merge Join + Merge Cond: ((w.during && a.during) AND (w.username = a.username)) + -> Sort + Sort Key: w.during, w.username + -> Seq Scan on web_session w + -> Sort + Sort Key: a.during, a.username + -> Seq Scan on app_session a +(8 rows) +</programlisting> + </para> + </sect2> </sect1> diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 7e4fbaf..1d9a2ad 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -917,7 +917,11 @@ ExplainNode(PlanState *planstate, List *ancestors, pname = sname = "Nested Loop"; break; case T_MergeJoin: - pname = "Merge"; /* "Join" gets added by jointype switch */ + if (((MergeJoin*)plan)->mergeRangeJoin) + pname = "Range Merge"; /* "Join" gets added by jointype switch */ + else + pname = "Merge"; /* "Join" gets added by jointype switch */ + sname = "Merge Join"; break; case T_HashJoin: diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c index ef9e1ee..e18a294 100644 --- a/src/backend/executor/nodeMergejoin.c +++ b/src/backend/executor/nodeMergejoin.c @@ -89,15 +89,67 @@ * proceed to another state. This state is stored in the node's * execution state information and is preserved across calls to * ExecMergeJoin. -cim 10/31/89 + * + * RANGE MERGE JOIN + * + * Range Merge Join is a generalization of merge join. When a join + * predicate involves the range overlaps operator (&&), a merge join + * becomes a range join. + * + * The input ranges must be ordered by (lower-bound, upper-bound), which + * is the ordinary range_ops operator class. MJCompare must not simply + * use the operator class's comparison semantics though; instead it + * returns: + * + * - MJCMP_MATCHES if outer range overlaps inner range + * - MJCMP_LEFTOF if outer range is strictly left-of inner range + * - MJCMP_RIGHTOF if outer range is strictly right-of inner range + * + * NB: the above is a simplification considering only a single merge + * clause. When there are multiple merge clauses, it's possible that one + * tuple is neither right-of, nor left-of, nor matching. For instance, if + * an earlier range merge clause matches (overlaps), but a later clause + * fails. In that case, MJCompare returns MJCMP_NOSKIP. + * + * If MJCompare returns MJCMP_RIGHTOF, later or earlier tuples on the + * inner side may match. For example: + * + * OUTER INNER + * ... [1,9] matches current outer + * [4,5] [2,3] no match + * ... [3,5] matches current outer + * ... [7,9] no match and no later matches for current outer + * + * Outer tuple [4,5] does not match [2,3], but it matches (overlaps with) + * the earlier tuple [1,9] and the later tuple [3,5]. But once we + * encounter [7,9], we know that no later inner tuple can possibly match + * the outer. + * + * When doing a range join, we lose two merge join optimizations: + * + * 1. Ordinary merge join only restores to the mark if it's equal to the + * new outer. For range join, we must always restore to the mark + * because there may be matches after the mark and before the current + * inner tuple. + * 2. After restoring to the mark, ordinary merge join immediately moves + * to JOINTUPLES. Range join must move to SKIP_TEST first. + * + * Range merge join is unable to implement RIGHT/FULL joins. It's also + * unable to cope with reverse sort order, because there could always be + * some later inner range that matches the outer tuple. */ #include "postgres.h" #include "access/nbtree.h" +#include "catalog/pg_operator.h" #include "executor/execdebug.h" #include "executor/nodeMergejoin.h" #include "miscadmin.h" +#include "nodes/nodeFuncs.h" #include "utils/lsyscache.h" #include "utils/memutils.h" +#include "utils/rangetypes.h" +#include "utils/typcache.h" /* @@ -138,6 +190,10 @@ typedef struct MergeJoinClauseData * stored here. */ SortSupportData ssup; + + /* needed for Range Join */ + bool isrange; + TypeCacheEntry *range_typcache; } MergeJoinClauseData; /* Result type for MJEvalOuterValues and MJEvalInnerValues */ @@ -148,6 +204,13 @@ typedef enum MJEVAL_ENDOFJOIN /* end of input (physical or effective) */ } MJEvalResult; +typedef enum +{ + MJCMP_LEFTOF, + MJCMP_RIGHTOF, + MJCMP_MATCHES, + MJCMP_NOSKIP +} MJCompareResult; #define MarkInnerTuple(innerTupleSlot, mergestate) \ ExecCopySlot((mergestate)->mj_MarkedTupleSlot, (innerTupleSlot)) @@ -178,6 +241,7 @@ MJExamineQuals(List *mergeclauses, Oid *mergecollations, int *mergestrategies, bool *mergenullsfirst, + bool *israngejoin, PlanState *parent) { MergeJoinClause clauses; @@ -185,6 +249,8 @@ MJExamineQuals(List *mergeclauses, int iClause; ListCell *cl; + *israngejoin = false; + clauses = (MergeJoinClause) palloc0(nClauses * sizeof(MergeJoinClauseData)); iClause = 0; @@ -196,6 +262,7 @@ MJExamineQuals(List *mergeclauses, Oid collation = mergecollations[iClause]; StrategyNumber opstrategy = mergestrategies[iClause]; bool nulls_first = mergenullsfirst[iClause]; + Oid eq_opno; int op_strategy; Oid op_lefttype; Oid op_righttype; @@ -221,8 +288,32 @@ MJExamineQuals(List *mergeclauses, elog(ERROR, "unsupported mergejoin strategy %d", opstrategy); clause->ssup.ssup_nulls_first = nulls_first; + /* + * If a range join, the original operator must be "&&" rather than + * "=". Set eq_opno to the range equality operator for the purposes of + * setting up the merge clause, but mark it as a range. + */ + if (qual->opno == OID_RANGE_OVERLAP_OP) + { + Oid rngtypid = exprType((Node*)clause->lexpr->expr); + Assert(exprType((Node*)clause->lexpr->expr) == + exprType((Node*)clause->rexpr->expr)); + eq_opno = OID_RANGE_EQ_OP; + clause->isrange = true; + clause->range_typcache = lookup_type_cache(rngtypid, + TYPECACHE_RANGE_INFO); + *israngejoin = true; + } + else + { + eq_opno = qual->opno; + clause->isrange = false; + clause->range_typcache = NULL; + } + + /* Extract the operator's declared left/right datatypes */ - get_op_opfamily_properties(qual->opno, opfamily, false, + get_op_opfamily_properties(eq_opno, opfamily, false, &op_strategy, &op_lefttype, &op_righttype); @@ -324,6 +415,11 @@ MJEvalOuterValues(MergeJoinState *mergestate) else if (result == MJEVAL_MATCHABLE) result = MJEVAL_NONMATCHABLE; } + else if (clause->isrange) + { + if (RangeIsEmpty(DatumGetRangeTypeP(clause->ldatum))) + result = MJEVAL_NONMATCHABLE; + } } MemoryContextSwitchTo(oldContext); @@ -371,6 +467,11 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot) else if (result == MJEVAL_MATCHABLE) result = MJEVAL_NONMATCHABLE; } + else if (clause->isrange) + { + if (RangeIsEmpty(DatumGetRangeTypeP(clause->rdatum))) + result = MJEVAL_NONMATCHABLE; + } } MemoryContextSwitchTo(oldContext); @@ -379,6 +480,34 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot) } /* + * Return 0 if ranges overlap, <0 if the first range is left-of the second, or + * >0 if the first range is right-of the second. See comment at the top of the + * file for explanation. + */ +static int +ApplyRangeMatch(Datum ldatum, bool lisnull, Datum rdatum, bool risnull, + SortSupport ssup, TypeCacheEntry *typcache) +{ + /* can't handle reverse sort order; should be prevented by optimizer */ + Assert(!ssup->ssup_reverse); + Assert(!lisnull || !risnull); + + if (lisnull) + return ssup->ssup_nulls_first ? -1 : 1; + if (risnull) + return ssup->ssup_nulls_first ? 1 : -1; + + if (range_before_internal(typcache, DatumGetRangeTypeP(ldatum), + DatumGetRangeTypeP(rdatum))) + return -1; + else if (range_overlaps_internal(typcache, DatumGetRangeTypeP(ldatum), + DatumGetRangeTypeP(rdatum))) + return 0; + else + return 1; +} + +/* * MJCompare * * Compare the mergejoinable values of the current two input tuples @@ -388,11 +517,12 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot) * MJEvalOuterValues and MJEvalInnerValues must already have been called * for the current outer and inner tuples, respectively. */ -static int +static MJCompareResult MJCompare(MergeJoinState *mergestate) { int result = 0; bool nulleqnull = false; + bool range_overlaps = false; ExprContext *econtext = mergestate->js.ps.ps_ExprContext; int i; MemoryContext oldContext; @@ -418,14 +548,25 @@ MJCompare(MergeJoinState *mergestate) continue; } - result = ApplySortComparator(clause->ldatum, clause->lisnull, + if (clause->isrange) + { + result = ApplyRangeMatch(clause->ldatum, clause->lisnull, clause->rdatum, clause->risnull, - &clause->ssup); + &clause->ssup, clause->range_typcache); + if (result == 0) + range_overlaps = true; + } + else + result = ApplySortComparator(clause->ldatum, clause->lisnull, + clause->rdatum, clause->risnull, + &clause->ssup); if (result != 0) break; } + MemoryContextSwitchTo(oldContext); + /* * 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 @@ -437,11 +578,22 @@ MJCompare(MergeJoinState *mergestate) */ if (result == 0 && (nulleqnull || mergestate->mj_ConstFalseJoin)) - result = 1; + return MJCMP_RIGHTOF; - MemoryContextSwitchTo(oldContext); + /* + * If a range predicate succeeds (overlaps) but a later predicate fails, + * it's neither strictly right-of, nor strictly left-of, nor matching. So + * we return MJCMP_NOSKIP. + */ + if (result != 0 && range_overlaps) + return MJCMP_NOSKIP; - return result; + if (result == 0) + return MJCMP_MATCHES; + else if (result < 0) + return MJCMP_LEFTOF; + else + return MJCMP_RIGHTOF; } @@ -533,7 +685,6 @@ check_constant_qual(List *qual, bool *is_const_false) return true; } - /* ---------------------------------------------------------------- * ExecMergeTupleDump * @@ -891,12 +1042,21 @@ ExecMergeJoin(PlanState *pstate) compareResult = MJCompare(node); MJ_DEBUG_COMPARE(compareResult); - if (compareResult == 0) + if (compareResult == MJCMP_MATCHES) node->mj_JoinState = EXEC_MJ_JOINTUPLES; + else if (compareResult == MJCMP_LEFTOF) + node->mj_JoinState = EXEC_MJ_NEXTOUTER; else { - Assert(compareResult < 0); - node->mj_JoinState = EXEC_MJ_NEXTOUTER; + /* + * This state is only reached during a range join, + * where earlier tuples may match, the current + * tuple may not match, but a later tuple in the + * inner may still match. See comment at the top + * of the file. + */ + Assert(((MergeJoin*)node->js.ps.plan)->mergeRangeJoin); + node->mj_JoinState = EXEC_MJ_NEXTINNER; } break; case MJEVAL_NONMATCHABLE: @@ -1038,17 +1198,33 @@ ExecMergeJoin(PlanState *pstate) MJ_printf("ExecMergeJoin: EXEC_MJ_TESTOUTER\n"); /* - * 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.) + * We can ignore the result of MJEvalInnerValues, since the + * marked inner tuple is certainly matchable. */ innerTupleSlot = node->mj_MarkedTupleSlot; (void) MJEvalInnerValues(node, innerTupleSlot); + if (((MergeJoin*)node->js.ps.plan)->mergeRangeJoin) + { + /* + * For range join, we must always restore to the mark, and + * then move to SKIP_TEST. + */ + ExecRestrPos(innerPlan); + node->mj_InnerTupleSlot = node->mj_MarkedTupleSlot; + node->mj_JoinState = EXEC_MJ_SKIP_TEST; + break; + } + + /* + * Here we must compare the outer tuple with the marked inner + * tuple. + */ compareResult = MJCompare(node); + Assert(compareResult != MJCMP_NOSKIP); MJ_DEBUG_COMPARE(compareResult); - if (compareResult == 0) + if (compareResult == MJCMP_MATCHES) { /* * the merge clause matched so now we restore the inner @@ -1106,7 +1282,7 @@ ExecMergeJoin(PlanState *pstate) * no more inners, no more matches are possible. * ---------------- */ - Assert(compareResult > 0); + Assert(compareResult == MJCMP_RIGHTOF); innerTupleSlot = node->mj_InnerTupleSlot; /* reload comparison data for current inner */ @@ -1143,7 +1319,7 @@ ExecMergeJoin(PlanState *pstate) break; /*---------------------------------------------------------- - * EXEC_MJ_SKIP means compare tuples and if they do not + * EXEC_MJ_SKIP_TEST means compare tuples and if they do not * match, skip whichever is lesser. * * For example: @@ -1182,19 +1358,23 @@ ExecMergeJoin(PlanState *pstate) compareResult = MJCompare(node); MJ_DEBUG_COMPARE(compareResult); - if (compareResult == 0) + if (compareResult == MJCMP_MATCHES || + compareResult == MJCMP_NOSKIP) { if (!node->mj_SkipMarkRestore) ExecMarkPos(innerPlan); MarkInnerTuple(node->mj_InnerTupleSlot, node); - node->mj_JoinState = EXEC_MJ_JOINTUPLES; + if (compareResult == MJCMP_NOSKIP) + node->mj_JoinState = EXEC_MJ_NEXTINNER; + else + node->mj_JoinState = EXEC_MJ_JOINTUPLES; } - else if (compareResult < 0) + else if (compareResult == MJCMP_LEFTOF) node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE; else - /* compareResult > 0 */ + /* compareResult == MJCMP_RIGHTOF */ node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE; break; @@ -1598,6 +1778,7 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags) node->mergeCollations, node->mergeStrategies, node->mergeNullsFirst, + &node->mergeRangeJoin, (PlanState *) mergestate); /* diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index e774130..e0e9d2d 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -567,6 +567,19 @@ try_mergejoin_path(PlannerInfo *root, Relids required_outer; JoinCostWorkspace workspace; + /* RIGHT/FULL joins don't support range join */ + if (jointype == JOIN_RIGHT || jointype == JOIN_FULL) + { + ListCell *lc; + + foreach(lc, mergeclauses) + { + RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc); + if (restrictinfo->rangejoin) + return; + } + } + if (is_partial) { try_partial_mergejoin_path(root, diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index c6870d3..fa2eec1 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -1125,6 +1125,7 @@ select_outer_pathkeys_for_merge(PlannerInfo *root, int nClauses = list_length(mergeclauses); EquivalenceClass **ecs; int *scores; + bool *range_ecs; int necs; ListCell *lc; int j; @@ -1139,6 +1140,7 @@ select_outer_pathkeys_for_merge(PlannerInfo *root, */ ecs = (EquivalenceClass **) palloc(nClauses * sizeof(EquivalenceClass *)); scores = (int *) palloc(nClauses * sizeof(int)); + range_ecs = palloc(nClauses * sizeof(bool)); necs = 0; foreach(lc, mergeclauses) @@ -1179,6 +1181,7 @@ select_outer_pathkeys_for_merge(PlannerInfo *root, ecs[necs] = oeclass; scores[necs] = score; + range_ecs[necs] = rinfo->rangejoin; necs++; } @@ -1196,6 +1199,11 @@ select_outer_pathkeys_for_merge(PlannerInfo *root, for (j = 0; j < necs; j++) { + /* for range join, the input order must be ascending */ + if (range_ecs[j] && + query_pathkey->pk_strategy != BTLessStrategyNumber) + continue; + if (ecs[j] == query_ec) break; /* found match */ } diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 5783f90..0f938a0 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -1101,8 +1101,9 @@ is_innerrel_unique_for(PlannerInfo *root, if (restrictinfo->is_pushed_down && IS_OUTER_JOIN(jointype)) continue; - /* Ignore if it's not a mergejoinable clause */ + /* Ignore if it's not a mergejoinable clause or is a range join */ if (!restrictinfo->can_join || + restrictinfo->rangejoin || restrictinfo->mergeopfamilies == NIL) continue; /* not mergejoinable */ diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 448cb73..91ab123 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -14,6 +14,7 @@ */ #include "postgres.h" +#include "catalog/pg_operator.h" #include "catalog/pg_type.h" #include "catalog/pg_class.h" #include "nodes/nodeFuncs.h" @@ -1961,7 +1962,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, */ if (restrictinfo->mergeopfamilies) { - if (maybe_equivalence) + if (maybe_equivalence && !restrictinfo->rangejoin) { if (check_equivalence_delay(root, restrictinfo) && process_equivalence(root, &restrictinfo, below_outer_join)) @@ -2616,6 +2617,12 @@ check_mergejoinable(RestrictInfo *restrictinfo) opno = ((OpExpr *) clause)->opno; leftarg = linitial(((OpExpr *) clause)->args); + if (opno == OID_RANGE_OVERLAP_OP) + { + restrictinfo->rangejoin = true; + opno = OID_RANGE_EQ_OP; + } + if (op_mergejoinable(opno, exprType(leftarg)) && !contain_volatile_functions((Node *) clause)) restrictinfo->mergeopfamilies = get_mergejoin_opfamilies(opno); diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index 39b52ae..ba0dd7b 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -186,6 +186,7 @@ make_restrictinfo_internal(Expr *clause, restrictinfo->outer_selec = -1; restrictinfo->mergeopfamilies = NIL; + restrictinfo->rangejoin = false; restrictinfo->left_ec = NULL; restrictinfo->right_ec = NULL; diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index ea95b80..1fa5835 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -2980,7 +2980,6 @@ mergejoinscansel(PlannerInfo *root, Node *clause, *right; VariableStatData leftvar, rightvar; - int op_strategy; Oid op_lefttype; Oid op_righttype; Oid opno, @@ -3017,12 +3016,20 @@ mergejoinscansel(PlannerInfo *root, Node *clause, examine_variable(root, left, 0, &leftvar); examine_variable(root, right, 0, &rightvar); - /* Extract the operator's declared left/right datatypes */ - get_op_opfamily_properties(opno, opfamily, false, - &op_strategy, - &op_lefttype, - &op_righttype); - Assert(op_strategy == BTEqualStrategyNumber); + if (opno == OID_RANGE_OVERLAP_OP) + { + op_lefttype = op_righttype = ANYRANGEOID; + } + else + { + int op_strategy; + /* Extract the operator's declared left/right datatypes */ + get_op_opfamily_properties(opno, opfamily, false, + &op_strategy, + &op_lefttype, + &op_righttype); + Assert(op_strategy == BTEqualStrategyNumber); + } /* * Look up the various operators we need. If we don't find them all, it diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index ff9b470..52ce99c 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -1748,6 +1748,7 @@ DESCR("greater than or equal"); /* generic range type operators */ DATA(insert OID = 3882 ( "=" PGNSP PGUID b t t 3831 3831 16 3882 3883 range_eq eqsel eqjoinsel )); DESCR("equal"); +#define OID_RANGE_EQ_OP 3882 DATA(insert OID = 3883 ( "<>" PGNSP PGUID b f f 3831 3831 16 3883 3882 range_ne neqsel neqjoinsel )); DESCR("not equal"); DATA(insert OID = 3884 ( "<" PGNSP PGUID b f f 3831 3831 16 3887 3886 range_lt rangesel scalarltjoinsel )); diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index d763da6..f9308df 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -715,6 +715,7 @@ typedef struct MergeJoin Oid *mergeCollations; /* per-clause OIDs of collations */ int *mergeStrategies; /* per-clause ordering (ASC or DESC) */ bool *mergeNullsFirst; /* per-clause nulls ordering */ + bool mergeRangeJoin; /* is this a range merge join? */ } MergeJoin; /* ---------------- diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 3b9d303..6f82803 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -1886,6 +1886,9 @@ typedef struct RestrictInfo /* valid if clause is mergejoinable, else NIL */ List *mergeopfamilies; /* opfamilies containing clause operator */ + /* is a rangejoin clause? */ + bool rangejoin; + /* cache space for mergeclause processing; NULL if not yet set */ EquivalenceClass *left_ec; /* EquivalenceClass containing lefthand */ EquivalenceClass *right_ec; /* EquivalenceClass containing righthand */ diff --git a/src/test/regress/expected/rangejoin.out b/src/test/regress/expected/rangejoin.out new file mode 100644 index 0000000..bda7c23 --- /dev/null +++ b/src/test/regress/expected/rangejoin.out @@ -0,0 +1,518 @@ +-- test with unique to exercise more of the planner +create table rangejoin_left(i1 int, ir1 int4range unique); +create table rangejoin_right(i2 int, ir2 int4range unique); +insert into rangejoin_left values + (1001, int4range(10, 80)), + (1002, int4range(20, 30)), + (1003, int4range(21, 25)), + (1004, int4range(22, 35)), + (1005, int4range(40, 60)), + (1006, int4range(50, 60)); +insert into rangejoin_right values + (1000, 'empty'::int4range), + (1001, int4range(NULL, 4)), + (1002, int4range(10, 12)), + (1003, int4range(11, 14)), + (1004, int4range(20, 45)), + (1005, int4range(24, 28)), + (1006, int4range(85, 90)); +-- simple inner join +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left, rangejoin_right + where ir1 && ir2; + QUERY PLAN +------------------------------------------------------------------------- + Range Merge Join + Merge Cond: (rangejoin_left.ir1 && rangejoin_right.ir2) + -> Index Scan using rangejoin_left_ir1_key on rangejoin_left + -> Materialize + -> Index Scan using rangejoin_right_ir2_key on rangejoin_right +(5 rows) + +select i1, ir1, i2, ir2 + from rangejoin_left, rangejoin_right + where ir1 && ir2; + i1 | ir1 | i2 | ir2 +------+---------+------+--------- + 1001 | [10,80) | 1002 | [10,12) + 1001 | [10,80) | 1003 | [11,14) + 1001 | [10,80) | 1004 | [20,45) + 1001 | [10,80) | 1005 | [24,28) + 1002 | [20,30) | 1004 | [20,45) + 1002 | [20,30) | 1005 | [24,28) + 1003 | [21,25) | 1004 | [20,45) + 1003 | [21,25) | 1005 | [24,28) + 1004 | [22,35) | 1004 | [20,45) + 1004 | [22,35) | 1005 | [24,28) + 1005 | [40,60) | 1004 | [20,45) +(11 rows) + +-- two predicates +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left inner join rangejoin_right + on (i1 = i2 and ir1 && ir2); + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Range Merge Join + Merge Cond: ((rangejoin_left.ir1 && rangejoin_right.ir2) AND (rangejoin_left.i1 = rangejoin_right.i2)) + -> Sort + Sort Key: rangejoin_left.ir1, rangejoin_left.i1 + -> Seq Scan on rangejoin_left + -> Sort + Sort Key: rangejoin_right.ir2, rangejoin_right.i2 + -> Seq Scan on rangejoin_right +(8 rows) + +select i1, ir1, i2, ir2 + from rangejoin_left inner join rangejoin_right + on (i1 = i2 and ir1 && ir2); + i1 | ir1 | i2 | ir2 +------+---------+------+--------- + 1004 | [22,35) | 1004 | [20,45) +(1 row) + +-- left join +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left left join rangejoin_right + on (ir1 && ir2); + QUERY PLAN +------------------------------------------------------------------------- + Range Merge Left Join + Merge Cond: (rangejoin_left.ir1 && rangejoin_right.ir2) + -> Index Scan using rangejoin_left_ir1_key on rangejoin_left + -> Materialize + -> Index Scan using rangejoin_right_ir2_key on rangejoin_right +(5 rows) + +select i1, ir1, i2, ir2 + from rangejoin_left left join rangejoin_right + on (ir1 && ir2); + i1 | ir1 | i2 | ir2 +------+---------+------+--------- + 1001 | [10,80) | 1002 | [10,12) + 1001 | [10,80) | 1003 | [11,14) + 1001 | [10,80) | 1004 | [20,45) + 1001 | [10,80) | 1005 | [24,28) + 1002 | [20,30) | 1004 | [20,45) + 1002 | [20,30) | 1005 | [24,28) + 1003 | [21,25) | 1004 | [20,45) + 1003 | [21,25) | 1005 | [24,28) + 1004 | [22,35) | 1004 | [20,45) + 1004 | [22,35) | 1005 | [24,28) + 1005 | [40,60) | 1004 | [20,45) + 1006 | [50,60) | | +(12 rows) + +-- right join should be implemented as left join +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left right join rangejoin_right + on (ir1 && ir2); + QUERY PLAN +----------------------------------------------------------------------- + Range Merge Left Join + Merge Cond: (rangejoin_right.ir2 && rangejoin_left.ir1) + -> Index Scan using rangejoin_right_ir2_key on rangejoin_right + -> Materialize + -> Index Scan using rangejoin_left_ir1_key on rangejoin_left +(5 rows) + +-- full join doesn't support range join +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left full join rangejoin_right + on (ir1 && ir2); +ERROR: FULL JOIN is only supported with merge-joinable or hash-joinable join conditions +-- range input to range join must be ascending +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left inner join rangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 desc, i1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------- + Sort + Sort Key: rangejoin_left.ir1 DESC, rangejoin_left.i1 + -> Range Merge Join + Merge Cond: ((rangejoin_left.ir1 && rangejoin_right.ir2) AND (rangejoin_left.i1 = rangejoin_right.i2)) + -> Sort + Sort Key: rangejoin_left.ir1, rangejoin_left.i1 + -> Seq Scan on rangejoin_left + -> Sort + Sort Key: rangejoin_right.ir2, rangejoin_right.i2 + -> Seq Scan on rangejoin_right +(10 rows) + +-- but it's OK for non-range inputs to be descending +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left inner join rangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 nulls first, i1 desc; + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Range Merge Join + Merge Cond: ((rangejoin_left.ir1 && rangejoin_right.ir2) AND (rangejoin_left.i1 = rangejoin_right.i2)) + -> Sort + Sort Key: rangejoin_left.ir1 NULLS FIRST, rangejoin_left.i1 DESC + -> Seq Scan on rangejoin_left + -> Sort + Sort Key: rangejoin_right.ir2 NULLS FIRST, rangejoin_right.i2 DESC + -> Seq Scan on rangejoin_right +(8 rows) + +select i1, ir1, i2, ir2 + from rangejoin_left inner join rangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 nulls first, i1 desc; + i1 | ir1 | i2 | ir2 +------+---------+------+--------- + 1004 | [22,35) | 1004 | [20,45) +(1 row) + +drop table rangejoin_left; +drop table rangejoin_right; +create table multirangejoin_left (ir1 int4range, ir2 int4range); +create table multirangejoin_right (ir3 int4range, ir4 int4range); +insert into multirangejoin_left values + (int4range(30,99), int4range(20,30)), + (int4range(2,40), int4range(15,27)), + (int4range(61,99), int4range(20,45)), + (int4range(22,32), int4range(21,66)), + (int4range(36,71), int4range(45,49)), + (int4range(9,80), int4range(2,4)); +insert into multirangejoin_right values + (int4range(9,70), int4range(10,78)), + (int4range(21,37), int4range(89,99)), + (int4range(5,98), int4range(35,97)), + (int4range(12,17), int4range(81,92)), + (int4range(15,19), int4range(5,55)), + (int4range(57,58), int4range(42,80)); +explain (costs off) select * + from multirangejoin_left inner join multirangejoin_right + on (ir1 && ir3 and ir2 && ir4) order by ir1, ir2, ir3, ir4; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------- + Sort + Sort Key: multirangejoin_left.ir1, multirangejoin_left.ir2, multirangejoin_right.ir3, multirangejoin_right.ir4 + -> Range Merge Join + Merge Cond: ((multirangejoin_left.ir1 && multirangejoin_right.ir3) AND (multirangejoin_left.ir2 && multirangejoin_right.ir4)) + -> Sort + Sort Key: multirangejoin_left.ir1, multirangejoin_left.ir2 + -> Seq Scan on multirangejoin_left + -> Sort + Sort Key: multirangejoin_right.ir3, multirangejoin_right.ir4 + -> Seq Scan on multirangejoin_right +(10 rows) + +select * + from multirangejoin_left inner join multirangejoin_right + on (ir1 && ir3 and ir2 && ir4) order by ir1, ir2, ir3, ir4; + ir1 | ir2 | ir3 | ir4 +---------+---------+---------+--------- + [2,40) | [15,27) | [9,70) | [10,78) + [2,40) | [15,27) | [15,19) | [5,55) + [22,32) | [21,66) | [5,98) | [35,97) + [22,32) | [21,66) | [9,70) | [10,78) + [30,99) | [20,30) | [9,70) | [10,78) + [36,71) | [45,49) | [5,98) | [35,97) + [36,71) | [45,49) | [9,70) | [10,78) + [36,71) | [45,49) | [57,58) | [42,80) + [61,99) | [20,45) | [5,98) | [35,97) + [61,99) | [20,45) | [9,70) | [10,78) +(10 rows) + +set enable_mergejoin=false; +explain (costs off) select * + from multirangejoin_left inner join multirangejoin_right + on (ir1 && ir3 and ir2 && ir4) order by ir1, ir2, ir3, ir4; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------- + Sort + Sort Key: multirangejoin_left.ir1, multirangejoin_left.ir2, multirangejoin_right.ir3, multirangejoin_right.ir4 + -> Nested Loop + Join Filter: ((multirangejoin_left.ir1 && multirangejoin_right.ir3) AND (multirangejoin_left.ir2 && multirangejoin_right.ir4)) + -> Seq Scan on multirangejoin_left + -> Materialize + -> Seq Scan on multirangejoin_right +(7 rows) + +select * + from multirangejoin_left inner join multirangejoin_right + on (ir1 && ir3 and ir2 && ir4) order by ir1, ir2, ir3, ir4; + ir1 | ir2 | ir3 | ir4 +---------+---------+---------+--------- + [2,40) | [15,27) | [9,70) | [10,78) + [2,40) | [15,27) | [15,19) | [5,55) + [22,32) | [21,66) | [5,98) | [35,97) + [22,32) | [21,66) | [9,70) | [10,78) + [30,99) | [20,30) | [9,70) | [10,78) + [36,71) | [45,49) | [5,98) | [35,97) + [36,71) | [45,49) | [9,70) | [10,78) + [36,71) | [45,49) | [57,58) | [42,80) + [61,99) | [20,45) | [5,98) | [35,97) + [61,99) | [20,45) | [9,70) | [10,78) +(10 rows) + +set enable_mergejoin=true; +explain (costs off) select * + from multirangejoin_left left join multirangejoin_right + on (ir1 && ir4 and ir2 && ir3) order by ir4, ir3, ir2, ir1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------- + Sort + Sort Key: multirangejoin_right.ir4, multirangejoin_right.ir3, multirangejoin_left.ir2, multirangejoin_left.ir1 + -> Range Merge Left Join + Merge Cond: ((multirangejoin_left.ir1 && multirangejoin_right.ir4) AND (multirangejoin_left.ir2 && multirangejoin_right.ir3)) + -> Sort + Sort Key: multirangejoin_left.ir1, multirangejoin_left.ir2 + -> Seq Scan on multirangejoin_left + -> Sort + Sort Key: multirangejoin_right.ir4, multirangejoin_right.ir3 + -> Seq Scan on multirangejoin_right +(10 rows) + +select * + from multirangejoin_left left join multirangejoin_right + on (ir1 && ir4 and ir2 && ir3) order by ir4, ir3, ir2, ir1; + ir1 | ir2 | ir3 | ir4 +---------+---------+---------+--------- + [2,40) | [15,27) | [15,19) | [5,55) + [2,40) | [15,27) | [9,70) | [10,78) + [30,99) | [20,30) | [9,70) | [10,78) + [61,99) | [20,45) | [9,70) | [10,78) + [22,32) | [21,66) | [9,70) | [10,78) + [36,71) | [45,49) | [9,70) | [10,78) + [2,40) | [15,27) | [5,98) | [35,97) + [30,99) | [20,30) | [5,98) | [35,97) + [61,99) | [20,45) | [5,98) | [35,97) + [36,71) | [45,49) | [5,98) | [35,97) + [30,99) | [20,30) | [21,37) | [89,99) + [61,99) | [20,45) | [21,37) | [89,99) + [9,80) | [2,4) | | +(13 rows) + +set enable_mergejoin=false; +explain (costs off) select * + from multirangejoin_left left join multirangejoin_right + on (ir1 && ir4 and ir2 && ir3) order by ir4, ir3, ir2, ir1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------- + Sort + Sort Key: multirangejoin_right.ir4, multirangejoin_right.ir3, multirangejoin_left.ir2, multirangejoin_left.ir1 + -> Nested Loop Left Join + Join Filter: ((multirangejoin_left.ir1 && multirangejoin_right.ir4) AND (multirangejoin_left.ir2 && multirangejoin_right.ir3)) + -> Seq Scan on multirangejoin_left + -> Materialize + -> Seq Scan on multirangejoin_right +(7 rows) + +select * + from multirangejoin_left left join multirangejoin_right + on (ir1 && ir4 and ir2 && ir3) order by ir4, ir3, ir2, ir1; + ir1 | ir2 | ir3 | ir4 +---------+---------+---------+--------- + [2,40) | [15,27) | [15,19) | [5,55) + [2,40) | [15,27) | [9,70) | [10,78) + [30,99) | [20,30) | [9,70) | [10,78) + [61,99) | [20,45) | [9,70) | [10,78) + [22,32) | [21,66) | [9,70) | [10,78) + [36,71) | [45,49) | [9,70) | [10,78) + [2,40) | [15,27) | [5,98) | [35,97) + [30,99) | [20,30) | [5,98) | [35,97) + [61,99) | [20,45) | [5,98) | [35,97) + [36,71) | [45,49) | [5,98) | [35,97) + [30,99) | [20,30) | [21,37) | [89,99) + [61,99) | [20,45) | [21,37) | [89,99) + [9,80) | [2,4) | | +(13 rows) + +set enable_mergejoin=true; +drop table multirangejoin_left; +drop table multirangejoin_right; +create table bigrangejoin_left (i1 int, ir1 int4range); +create table bigrangejoin_right (i2 int, ir2 int4range); +-- 100 small ranges +insert into bigrangejoin_left + select g/4, + int4range(g, + g + case when (g%2=0) then g%7 else 12-(g%11) end) + from generate_series(1,100) g; +insert into bigrangejoin_right + select g/4, + int4range(g-7+(g%19), + g-7+(g%19) + case when (g%3=0) then g%11 else 17-(g%15) end) + from generate_series(1,100) g; +-- 10 medium ranges +insert into bigrangejoin_left + select g/4*10, + int4range(g*10, + g*10 + case when (g%2=0) then g%7 else 12-(g%11) end) + from generate_series(1,10) g; +insert into bigrangejoin_right + select g/4*10, + int4range(g*10-57+(g%173), + g*10-57+(g%173) + case when (g%3=0) then g%123 else 97-(g%83) end) + from generate_series(1,10) g; +insert into bigrangejoin_left select g*11-21, 'empty'::int4range + from generate_series(1,9) g; +insert into bigrangejoin_right select g*13-29, 'empty'::int4range + from generate_series(1,8) g; +insert into bigrangejoin_left values + (4, int4range(NULL,5)), + (93, int4range(95, NULL)); +insert into bigrangejoin_right values + (7, int4range(NULL,3)), + (92, int4range(99, NULL)); +create temp table rangejoin_result1 + (i1 int, ir1 int4range, i2 int, ir2 int4range); +create temp table rangejoin_result2 + (i1 int, ir1 int4range, i2 int, ir2 int4range); +set enable_hashjoin=false; +explain (costs off) insert into rangejoin_result1 + select i1, ir1, i2, ir2 + from bigrangejoin_left left join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 nulls first, i1 desc; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Insert on rangejoin_result1 + -> Range Merge Left Join + Merge Cond: ((bigrangejoin_left.ir1 && bigrangejoin_right.ir2) AND (bigrangejoin_left.i1 = bigrangejoin_right.i2)) + -> Sort + Sort Key: bigrangejoin_left.ir1 NULLS FIRST, bigrangejoin_left.i1 DESC + -> Seq Scan on bigrangejoin_left + -> Sort + Sort Key: bigrangejoin_right.ir2 NULLS FIRST, bigrangejoin_right.i2 DESC + -> Seq Scan on bigrangejoin_right +(9 rows) + +insert into rangejoin_result1 + select i1, ir1, i2, ir2 + from bigrangejoin_left left join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 nulls first, i1 desc; +set enable_hashjoin=true; +set enable_mergejoin=false; +explain (costs off) insert into rangejoin_result2 + select i1, ir1, i2, ir2 + from bigrangejoin_left left join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 nulls first, i1 desc; + QUERY PLAN +-------------------------------------------------------------------------------- + Insert on rangejoin_result2 + -> Sort + Sort Key: bigrangejoin_left.ir1 NULLS FIRST, bigrangejoin_left.i1 DESC + -> Hash Left Join + Hash Cond: (bigrangejoin_left.i1 = bigrangejoin_right.i2) + Join Filter: (bigrangejoin_left.ir1 && bigrangejoin_right.ir2) + -> Seq Scan on bigrangejoin_left + -> Hash + -> Seq Scan on bigrangejoin_right +(9 rows) + +insert into rangejoin_result2 + select i1, ir1, i2, ir2 + from bigrangejoin_left left join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 nulls first, i1 desc; +set enable_mergejoin=true; +select count(*) from rangejoin_result1; + count +------- + 292 +(1 row) + +select count(*) from rangejoin_result2; + count +------- + 292 +(1 row) + +select * from rangejoin_result1 except select * from rangejoin_result2; + i1 | ir1 | i2 | ir2 +----+-----+----+----- +(0 rows) + +select * from rangejoin_result2 except select * from rangejoin_result1; + i1 | ir1 | i2 | ir2 +----+-----+----+----- +(0 rows) + +drop table rangejoin_result1; +drop table rangejoin_result2; +create temp table rangejoin_result3 + (i1 int, ir1 int4range, i2 int, ir2 int4range); +create temp table rangejoin_result4 + (i1 int, ir1 int4range, i2 int, ir2 int4range); +explain (costs off) insert into rangejoin_result3 + select i1, ir1, i2, ir2 + from bigrangejoin_left inner join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by i1, ir1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Insert on rangejoin_result3 + -> Range Merge Join + Merge Cond: ((bigrangejoin_left.i1 = bigrangejoin_right.i2) AND (bigrangejoin_left.ir1 && bigrangejoin_right.ir2)) + -> Sort + Sort Key: bigrangejoin_left.i1, bigrangejoin_left.ir1 + -> Seq Scan on bigrangejoin_left + -> Sort + Sort Key: bigrangejoin_right.i2, bigrangejoin_right.ir2 + -> Seq Scan on bigrangejoin_right +(9 rows) + +insert into rangejoin_result3 + select i1, ir1, i2, ir2 + from bigrangejoin_left inner join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by i1, ir1; +set enable_mergejoin=false; +explain (costs off) insert into rangejoin_result4 + select i1, ir1, i2, ir2 + from bigrangejoin_left inner join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by i1, ir1; + QUERY PLAN +------------------------------------------------------------------------------ + Insert on rangejoin_result4 + -> Sort + Sort Key: bigrangejoin_left.i1, bigrangejoin_left.ir1 + -> Hash Join + Hash Cond: (bigrangejoin_left.i1 = bigrangejoin_right.i2) + Join Filter: (bigrangejoin_left.ir1 && bigrangejoin_right.ir2) + -> Seq Scan on bigrangejoin_left + -> Hash + -> Seq Scan on bigrangejoin_right +(9 rows) + +insert into rangejoin_result4 + select i1, ir1, i2, ir2 + from bigrangejoin_left inner join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by i1, ir1; +set enable_mergejoin=true; +select count(*) from rangejoin_result3; + count +------- + 259 +(1 row) + +select count(*) from rangejoin_result4; + count +------- + 259 +(1 row) + +select * from rangejoin_result3 except select * from rangejoin_result4; + i1 | ir1 | i2 | ir2 +----+-----+----+----- +(0 rows) + +select * from rangejoin_result4 except select * from rangejoin_result3; + i1 | ir1 | i2 | ir2 +----+-----+----+----- +(0 rows) + +drop table rangejoin_result3; +drop table rangejoin_result4; +drop table bigrangejoin_left; +drop table bigrangejoin_right; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index e224977..88bc5bd 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -111,7 +111,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo # NB: temp.sql does a reconnect which transiently uses 2 connections, # so keep this parallel group to at most 19 tests # ---------- -test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml +test: plancache limit plpgsql copy2 temp domain rangefuncs rangejoin prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 9fc5f1a..5dd542a 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -167,6 +167,7 @@ test: copy2 test: temp test: domain test: rangefuncs +test: rangejoin test: prepare test: without_oid test: conversion diff --git a/src/test/regress/sql/rangejoin.sql b/src/test/regress/sql/rangejoin.sql new file mode 100644 index 0000000..ad859b5 --- /dev/null +++ b/src/test/regress/sql/rangejoin.sql @@ -0,0 +1,266 @@ + +-- test with unique to exercise more of the planner +create table rangejoin_left(i1 int, ir1 int4range unique); +create table rangejoin_right(i2 int, ir2 int4range unique); + +insert into rangejoin_left values + (1001, int4range(10, 80)), + (1002, int4range(20, 30)), + (1003, int4range(21, 25)), + (1004, int4range(22, 35)), + (1005, int4range(40, 60)), + (1006, int4range(50, 60)); + +insert into rangejoin_right values + (1000, 'empty'::int4range), + (1001, int4range(NULL, 4)), + (1002, int4range(10, 12)), + (1003, int4range(11, 14)), + (1004, int4range(20, 45)), + (1005, int4range(24, 28)), + (1006, int4range(85, 90)); + +-- simple inner join +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left, rangejoin_right + where ir1 && ir2; + +select i1, ir1, i2, ir2 + from rangejoin_left, rangejoin_right + where ir1 && ir2; + +-- two predicates +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left inner join rangejoin_right + on (i1 = i2 and ir1 && ir2); + +select i1, ir1, i2, ir2 + from rangejoin_left inner join rangejoin_right + on (i1 = i2 and ir1 && ir2); + +-- left join +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left left join rangejoin_right + on (ir1 && ir2); + +select i1, ir1, i2, ir2 + from rangejoin_left left join rangejoin_right + on (ir1 && ir2); + +-- right join should be implemented as left join +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left right join rangejoin_right + on (ir1 && ir2); + +-- full join doesn't support range join +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left full join rangejoin_right + on (ir1 && ir2); + +-- range input to range join must be ascending +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left inner join rangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 desc, i1; + +-- but it's OK for non-range inputs to be descending +explain (costs off) select i1, ir1, i2, ir2 + from rangejoin_left inner join rangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 nulls first, i1 desc; + +select i1, ir1, i2, ir2 + from rangejoin_left inner join rangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 nulls first, i1 desc; + +drop table rangejoin_left; +drop table rangejoin_right; + +create table multirangejoin_left (ir1 int4range, ir2 int4range); +create table multirangejoin_right (ir3 int4range, ir4 int4range); + +insert into multirangejoin_left values + (int4range(30,99), int4range(20,30)), + (int4range(2,40), int4range(15,27)), + (int4range(61,99), int4range(20,45)), + (int4range(22,32), int4range(21,66)), + (int4range(36,71), int4range(45,49)), + (int4range(9,80), int4range(2,4)); + + +insert into multirangejoin_right values + (int4range(9,70), int4range(10,78)), + (int4range(21,37), int4range(89,99)), + (int4range(5,98), int4range(35,97)), + (int4range(12,17), int4range(81,92)), + (int4range(15,19), int4range(5,55)), + (int4range(57,58), int4range(42,80)); + +explain (costs off) select * + from multirangejoin_left inner join multirangejoin_right + on (ir1 && ir3 and ir2 && ir4) order by ir1, ir2, ir3, ir4; + +select * + from multirangejoin_left inner join multirangejoin_right + on (ir1 && ir3 and ir2 && ir4) order by ir1, ir2, ir3, ir4; + +set enable_mergejoin=false; +explain (costs off) select * + from multirangejoin_left inner join multirangejoin_right + on (ir1 && ir3 and ir2 && ir4) order by ir1, ir2, ir3, ir4; + +select * + from multirangejoin_left inner join multirangejoin_right + on (ir1 && ir3 and ir2 && ir4) order by ir1, ir2, ir3, ir4; +set enable_mergejoin=true; + +explain (costs off) select * + from multirangejoin_left left join multirangejoin_right + on (ir1 && ir4 and ir2 && ir3) order by ir4, ir3, ir2, ir1; + +select * + from multirangejoin_left left join multirangejoin_right + on (ir1 && ir4 and ir2 && ir3) order by ir4, ir3, ir2, ir1; + +set enable_mergejoin=false; +explain (costs off) select * + from multirangejoin_left left join multirangejoin_right + on (ir1 && ir4 and ir2 && ir3) order by ir4, ir3, ir2, ir1; + +select * + from multirangejoin_left left join multirangejoin_right + on (ir1 && ir4 and ir2 && ir3) order by ir4, ir3, ir2, ir1; +set enable_mergejoin=true; + +drop table multirangejoin_left; +drop table multirangejoin_right; + +create table bigrangejoin_left (i1 int, ir1 int4range); +create table bigrangejoin_right (i2 int, ir2 int4range); + +-- 100 small ranges +insert into bigrangejoin_left + select g/4, + int4range(g, + g + case when (g%2=0) then g%7 else 12-(g%11) end) + from generate_series(1,100) g; +insert into bigrangejoin_right + select g/4, + int4range(g-7+(g%19), + g-7+(g%19) + case when (g%3=0) then g%11 else 17-(g%15) end) + from generate_series(1,100) g; + +-- 10 medium ranges +insert into bigrangejoin_left + select g/4*10, + int4range(g*10, + g*10 + case when (g%2=0) then g%7 else 12-(g%11) end) + from generate_series(1,10) g; +insert into bigrangejoin_right + select g/4*10, + int4range(g*10-57+(g%173), + g*10-57+(g%173) + case when (g%3=0) then g%123 else 97-(g%83) end) + from generate_series(1,10) g; + +insert into bigrangejoin_left select g*11-21, 'empty'::int4range + from generate_series(1,9) g; + +insert into bigrangejoin_right select g*13-29, 'empty'::int4range + from generate_series(1,8) g; + +insert into bigrangejoin_left values + (4, int4range(NULL,5)), + (93, int4range(95, NULL)); + +insert into bigrangejoin_right values + (7, int4range(NULL,3)), + (92, int4range(99, NULL)); + +create temp table rangejoin_result1 + (i1 int, ir1 int4range, i2 int, ir2 int4range); +create temp table rangejoin_result2 + (i1 int, ir1 int4range, i2 int, ir2 int4range); + +set enable_hashjoin=false; +explain (costs off) insert into rangejoin_result1 + select i1, ir1, i2, ir2 + from bigrangejoin_left left join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 nulls first, i1 desc; + +insert into rangejoin_result1 + select i1, ir1, i2, ir2 + from bigrangejoin_left left join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 nulls first, i1 desc; +set enable_hashjoin=true; + +set enable_mergejoin=false; +explain (costs off) insert into rangejoin_result2 + select i1, ir1, i2, ir2 + from bigrangejoin_left left join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 nulls first, i1 desc; + +insert into rangejoin_result2 + select i1, ir1, i2, ir2 + from bigrangejoin_left left join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by ir1 nulls first, i1 desc; +set enable_mergejoin=true; + +select count(*) from rangejoin_result1; +select count(*) from rangejoin_result2; + +select * from rangejoin_result1 except select * from rangejoin_result2; + +select * from rangejoin_result2 except select * from rangejoin_result1; + +drop table rangejoin_result1; +drop table rangejoin_result2; + +create temp table rangejoin_result3 + (i1 int, ir1 int4range, i2 int, ir2 int4range); +create temp table rangejoin_result4 + (i1 int, ir1 int4range, i2 int, ir2 int4range); + + +explain (costs off) insert into rangejoin_result3 + select i1, ir1, i2, ir2 + from bigrangejoin_left inner join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by i1, ir1; + +insert into rangejoin_result3 + select i1, ir1, i2, ir2 + from bigrangejoin_left inner join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by i1, ir1; + +set enable_mergejoin=false; +explain (costs off) insert into rangejoin_result4 + select i1, ir1, i2, ir2 + from bigrangejoin_left inner join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by i1, ir1; + +insert into rangejoin_result4 + select i1, ir1, i2, ir2 + from bigrangejoin_left inner join bigrangejoin_right + on (i1 = i2 and ir1 && ir2) + order by i1, ir1; +set enable_mergejoin=true; + +select count(*) from rangejoin_result3; +select count(*) from rangejoin_result4; + +select * from rangejoin_result3 except select * from rangejoin_result4; + +select * from rangejoin_result4 except select * from rangejoin_result3; + +drop table rangejoin_result3; +drop table rangejoin_result4; + +drop table bigrangejoin_left; +drop table bigrangejoin_right;