Patch: Range Merge Join

Started by Thomasover 4 years ago11 messages
#1Thomas
thomasmannhart97@gmail.com
2 attachment(s)

Hi Hackers,

More than a year ago we submitted a patch that offered two primitives
(ALIGN and NORMALIZE) to support the processing of temporal data with range
types. During the ensuing discussion we decided to withdraw the original
patch
and to split it into smaller parts.

In the context of my BSc thesis, we started working and implementing a
Range Merge Join (RMJ), which is key for most temporal operations. The RMJ
is a useful operator in its own right and it greatly benefits any possible
temporal extension.

We have implemented the Range Merge Join algorithm by extending the
existing Merge Join to also support range conditions, i.e., BETWEEN-AND
or @> (containment for range types). Range joins contain a containment
condition and may have (optional) equality conditions. For example the
following query joins employees with a department and work period with
events on a specific day for that department:

SELECT emps.name, emps.dept, events.event, events.day
FROM emps JOIN events ON emps.dept = events.dept
AND events.day <@ emps.eperiod;

The resulting query plan is as follows:

QUERY PLAN

----------------------------------------------------------------------------------------------
Range Merge Join (cost=106.73..118.01 rows=3 width=100) (actual rows=6
loops=1)
Merge Cond: (emps.dept = events.dept)
Range Cond: (events.day <@ emps.eperiod)
-> Sort (cost=46.87..48.49 rows=650 width=96) (actual rows=5 loops=1)
Sort Key: emps.dept, emps.eperiod
Sort Method: quicksort Memory: 25kB
-> Seq Scan on emps (cost=0.00..16.50 rows=650 width=96) (actual
rows=5 loops=1)
-> Sort (cost=59.86..61.98 rows=850 width=68) (actual rows=6 loops=1)
Sort Key: events.dept, events.day
Sort Method: quicksort Memory: 25kB
-> Seq Scan on events (cost=0.00..18.50 rows=850 width=68)
(actual rows=5 loops=1)
Planning Time: 0.077 ms
Execution Time: 0.092 ms
(13 rows)

Example queries and instances of tables can be found at the end of the mail.

The range merge join works with range types using <@ and also scalar data
types
using "a.ts BETWEEN b.ts AND b.te" or "b.ts <= a.ts AND a.ts <= b.te".
Currently, PostgreSQL does not provide specialized join algorithms for range
conditions (besides index nested loops), or Hash Join and Merge Joins that
evaluate an equality condition only.

Our idea is to have a separate range_cond besides the merge_cond for the
Merge Join that stores the potential range conditions of a query. The state
diagram of the Merge Join is then extended to also take into consideration
the range_cond. See the simplified state diagram of the Range Merge Join as
an extension of the Merge Join in the attachment. These additions besides a
boolean check have no effect on the Marge Join when no range condition is
present.

We provide extensive testing results and further information, including the
full BSc Thesis (technical report), describing the implementation and tests
in detail on http://tpg.inf.unibz.it/project-rmj and
http://tpg.inf.unibz.it/downloads/rmj-report.pdf.

We performed several experiments and show that depending on the selectivity
of
the range condition the range merge join outperforms existing execution
algorithms up to an order of magnitude. We found that the range merge join
that
needs to find range_cond from inequalities, incurs only a very small
overhead
in planning time in some TPCH queries (see Table 5.3 in the technical
report)
and in general only a very small overhead for a large number of joins or
many
inequality conditions (see Figure 5.1). To check the overhead of our
extension
for the traditional merge join execution time, we executed the TPCH queries
using the merge join (hash join disabled) and found no statistically
significant difference (see Table 5.4).

We are looking forward to your feedback and any suggestions to improve the
patch.

Best Regards,

Thomas Mannhart

Attachments: State Diagram and Patch

OPEN POINTS AND TODOs:

- Currently we do not consider parallelization
- Not all cases for input sort orders are considered yet

EXAMPLE QUERIES:

The first query uses a range condition using BETWEEN AND only and no
equality condition.

----------------------------------------------------------------------------------------------

DROP TABLE IF EXISTS marks;
DROP TABLE IF EXISTS grades;

CREATE TABLE marks (name text, snumber numeric, mark numeric);
CREATE TABLE grades (mmin numeric, mmax numeric, grade numeric);

INSERT INTO marks (name, snumber, mark) VALUES
('Anton', 1232, 23.5),
('Thomas', 4356, 95),
('Michael', 1125, 72),
('Hans', 3425, 90);

INSERT INTO grades (mmin, mmax, grade) VALUES
(0.0, 18, 1),
(18.5, 36, 2),
(36.5, 54, 3),
(54.5, 72, 4),
(72.5, 90, 5),
(90.5, 100, 6);

EXPLAIN(ANALYZE, TIMING FALSE)
SELECT marks.name, marks.snumber, grades.grade
FROM marks JOIN grades ON marks.mark BETWEEN grades.mmin AND grades.mmax;

QUERY PLAN

-----------------------------------------------------------------------------------------------
Range Merge Join (cost=93.74..920.13 rows=46944 width=96) (actual rows=16
loops=1)
Range Cond: ((marks.mark >= grades.mmin) AND (marks.mark <= grades.mmax))
-> Sort (cost=46.87..48.49 rows=650 width=96) (actual rows=12 loops=1)
Sort Key: grades.mmin
Sort Method: quicksort Memory: 25kB
-> Seq Scan on grades (cost=0.00..16.50 rows=650 width=96)
(actual rows=12 loops=1)
-> Sort (cost=46.87..48.49 rows=650 width=96) (actual rows=21 loops=1)
Sort Key: marks.mark
Sort Method: quicksort Memory: 25kB
-> Seq Scan on marks (cost=0.00..16.50 rows=650 width=96)
(actual rows=8 loops=1)
Planning Time: 0.078 ms
Execution Time: 0.068 ms
(12 rows)

----------------------------------------------------------------------------------------------

The second query uses a range and an equality condition and joins the
relations using contained in (<@).

----------------------------------------------------------------------------------------------

DROP TABLE IF EXISTS emps;
DROP TABLE IF EXISTS events;

CREATE TABLE emps (name text, dept text, eperiod daterange);
CREATE TABLE events (event text, dept text, day date);

INSERT INTO emps (name, dept, eperiod) VALUES
('Anton', 'Sales', '(2020-01-01, 2020-03-31)'),
('Thomas', 'Marketing', '(2020-01-01, 2020-06-30)'),
('Michael', 'Marketing', '(2020-03-01, 2020-12-31)'),
('Hans', 'Sales', '(2020-01-01, 2020-12-31)'),
('Thomas', 'Accounting', '(2020-07-01, 2020-12-31)');

INSERT INTO events (event, dept, day) VALUES
('Fair CH', 'Marketing', '2020-03-05'),
('Presentation', 'Sales', '2020-06-15'),
('Fair IT', 'Marketing', '2020-08-03'),
('Balance Report', 'Accounting', '2020-08-03'),
('Product launch', 'Marketing', '2020-10-15');

EXPLAIN(ANALYZE, TIMING FALSE)
SELECT emps.name, emps.dept, events.event, events.day
FROM emps JOIN events ON emps.dept = events.dept
AND events.day <@ emps.eperiod;

QUERY PLAN

----------------------------------------------------------------------------------------------
Range Merge Join (cost=106.73..118.01 rows=3 width=100) (actual rows=6
loops=1)
Merge Cond: (emps.dept = events.dept)
Range Cond: (events.day <@ emps.eperiod)
-> Sort (cost=46.87..48.49 rows=650 width=96) (actual rows=5 loops=1)
Sort Key: emps.dept, emps.eperiod
Sort Method: quicksort Memory: 25kB
-> Seq Scan on emps (cost=0.00..16.50 rows=650 width=96) (actual
rows=5 loops=1)
-> Sort (cost=59.86..61.98 rows=850 width=68) (actual rows=6 loops=1)
Sort Key: events.dept, events.day
Sort Method: quicksort Memory: 25kB
-> Seq Scan on events (cost=0.00..18.50 rows=850 width=68)
(actual rows=5 loops=1)
Planning Time: 0.077 ms
Execution Time: 0.092 ms
(13 rows)

----------------------------------------------------------------------------------------------

Attachments:

postgres-rmj.patchapplication/octet-stream; name=postgres-rmj.patchDownload
diff --git a/.gitignore b/.gitignore
index 794e35b73c..5dca1a39ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,14 @@ lib*.pc
 /Debug/
 /Release/
 /tmp_install/
+
+# Thomas
+/data/
+/server/
+.idea/
+.settings/
+.cproject
+.project
+logfile
+src/backend/catalog/postgres.*
+*.patch
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 9a60865d19..bb517d2909 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1210,8 +1210,16 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			pname = sname = "Nested Loop";
 			break;
 		case T_MergeJoin:
-			pname = "Merge";	/* "Join" gets added by jointype switch */
-			sname = "Merge Join";
+			if(((MergeJoin *) plan)->rangeclause) /* Thomas */
+			{
+				pname = "Range Merge";	/* "Join" gets added by jointype switch */
+				sname = "Range Merge Join";
+			}
+			else
+			{
+				pname = "Merge";	/* "Join" gets added by jointype switch */
+				sname = "Merge Join";
+			}
 			break;
 		case T_HashJoin:
 			pname = "Hash";		/* "Join" gets added by jointype switch */
@@ -1952,6 +1960,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_MergeJoin:
 			show_upper_qual(((MergeJoin *) plan)->mergeclauses,
 							"Merge Cond", planstate, ancestors, es);
+			show_upper_qual(((MergeJoin *) plan)->rangeclause,
+							"Range Cond", planstate, ancestors, es); /* Thomas */
 			show_upper_qual(((MergeJoin *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
 			if (((MergeJoin *) plan)->join.joinqual)
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index b41454ab6d..aefb23a2e1 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -93,11 +93,15 @@
 #include "postgres.h"
 
 #include "access/nbtree.h"
+#include "catalog/pg_operator.h"	/* Thomas */
 #include "executor/execdebug.h"
 #include "executor/nodeMergejoin.h"
 #include "miscadmin.h"
+#include "nodes/nodeFuncs.h"	/* Thomas */
+#include "utils/rangetypes.h"	/* Thomas */
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/typcache.h" /* Thomas */
 
 
 /*
@@ -140,6 +144,20 @@ typedef struct MergeJoinClauseData
 	SortSupportData ssup;
 }			MergeJoinClauseData;
 
+/*
+ * Thomas
+ *
+ * RangeJoin data
+ */
+typedef struct RangeJoinData
+{
+	ExprState *startClause;
+	ExprState *endClause;
+	ExprState *rangeExpr;
+	ExprState *elemExpr;
+
+}			RangeJoinData;
+
 /* Result type for MJEvalOuterValues and MJEvalInnerValues */
 typedef enum
 {
@@ -269,6 +287,57 @@ MJExamineQuals(List *mergeclauses,
 	return clauses;
 }
 
+/*
+ * Thomas
+ */
+static RangeData
+MJCreateRangeData(List *rangeclause,
+				   PlanState *parent)
+{
+	RangeData data;
+
+	Assert(list_legth(node->rangeclause) < 3);
+
+	data = (RangeData) palloc0(sizeof(RangeJoinData));
+
+	data->startClause = NULL;
+	data->endClause = NULL;
+	data->rangeExpr = NULL;
+	data->elemExpr = NULL;
+
+	if(list_length(rangeclause) == 2)
+	{
+		data->startClause = ExecInitExpr(linitial(rangeclause), parent);
+		data->endClause = ExecInitExpr(lsecond(rangeclause), parent);
+	}
+	else
+	{
+		OpExpr		*qual = (OpExpr *) linitial(rangeclause);
+		ExprState	*lexpr;
+		ExprState	*rexpr;
+
+		/*
+		 * Prepare the input expressions for execution.
+		 */
+		lexpr = ExecInitExpr((Expr *) get_leftop(qual), parent);
+		rexpr = ExecInitExpr((Expr *) get_rightop(qual), parent);
+
+		if(qual->opno == OID_RANGE_CONTAINS_ELEM_OP)
+		{
+			data->rangeExpr = lexpr;
+			data->elemExpr = rexpr;
+		}
+		else
+		{
+			Assert(qual->opno == OID_RANGE_ELEM_CONTAINED_OP);
+			data->rangeExpr = rexpr;
+			data->elemExpr = lexpr;
+		}
+	}
+
+	return data;
+}
+
 /*
  * MJEvalOuterValues
  *
@@ -444,6 +513,70 @@ MJCompare(MergeJoinState *mergestate)
 	return result;
 }
 
+/*
+ * MJCompareRange
+ *
+ * Compare the rangejoinable values of the current two input tuples
+ * and return 0 if they are equal (ie, the outer interval contains the inner), >0 if outer > inner, <0 if outer < inner.
+ *
+ */
+static int
+MJCompareRange(MergeJoinState *mergestate)
+{
+	int 		  result = 0;
+	bool 		  isNull;
+	MemoryContext oldContext;
+	RangeData	  rangeData = mergestate->mj_RangeData;
+	ExprContext	 *econtext = mergestate->js.ps.ps_ExprContext;
+	ExprState	 *endClause = rangeData->endClause,
+				 *startClause = rangeData->startClause,
+				 *rangeExpr = rangeData->rangeExpr,
+				 *elemExpr = rangeData->elemExpr;
+
+	/*
+	 * Call the comparison functions in short-lived context, in case they leak
+	 * memory.
+	 */
+	ResetExprContext(econtext);
+
+	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+	econtext->ecxt_outertuple = mergestate->mj_OuterTupleSlot;
+	econtext->ecxt_innertuple = mergestate->mj_InnerTupleSlot;
+
+	if (endClause != NULL)
+	{
+		Assert(startClause != NULL);
+
+		if (!ExecEvalExprSwitchContext(endClause, econtext, &isNull))
+			result = -1;
+		else if (!ExecEvalExprSwitchContext(startClause, econtext, &isNull))
+			result = 1;
+	}
+	else
+	{
+		Datum			rangeDatum,
+    					elemDatum;
+		Oid				rangeType;
+		TypeCacheEntry *typecache;
+
+		Assert(rangeExpr != NULL && elemExpr != NULL);
+
+		rangeDatum = ExecEvalExprSwitchContext(rangeExpr, econtext, &isNull);
+		elemDatum = ExecEvalExprSwitchContext(elemExpr, econtext, &isNull);
+
+		rangeType = exprType((Node *) rangeExpr->expr);
+		typecache = lookup_type_cache(rangeType, TYPECACHE_RANGE_INFO);
+
+		if (elem_after_range_internal(typecache, elemDatum, DatumGetRangeTypeP(rangeDatum)))
+			result = -1;
+		else if (elem_before_range_internal(typecache, elemDatum, DatumGetRangeTypeP(rangeDatum)))
+			result = 1;
+	}
+
+	MemoryContextSwitchTo(oldContext);
+	return result;
+}
 
 /*
  * Generate a fake join tuple with nulls for the inner tuple,
@@ -604,6 +737,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprState  *otherqual;
 	bool		qualResult;
 	int			compareResult;
+	int			compareRangeResult;	/* Thomas */
 	PlanState  *innerPlan;
 	TupleTableSlot *innerTupleSlot;
 	PlanState  *outerPlan;
@@ -611,6 +745,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprContext *econtext;
 	bool		doFillOuter;
 	bool		doFillInner;
+	bool		isRangeJoin;	/* Thomas */
 
 	CHECK_FOR_INTERRUPTS();
 
@@ -624,6 +759,7 @@ ExecMergeJoin(PlanState *pstate)
 	otherqual = node->js.ps.qual;
 	doFillOuter = node->mj_FillOuter;
 	doFillInner = node->mj_FillInner;
+	isRangeJoin = node->mj_RangeJoin;
 
 	/*
 	 * Reset per-tuple memory context to free any expression evaluation
@@ -891,8 +1027,23 @@ ExecMergeJoin(PlanState *pstate)
 						compareResult = MJCompare(node);
 						MJ_DEBUG_COMPARE(compareResult);
 
-						if (compareResult == 0)
-							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						if(compareResult == 0)
+						{
+							if(isRangeJoin)
+							{
+								compareRangeResult = MJCompareRange(node);
+
+								if (compareRangeResult == 0)
+									node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+								else
+								{
+									Assert(compareRangeResult < 0);
+									node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+								}
+							}
+							else
+								node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						}
 						else
 						{
 							Assert(compareResult < 0);
@@ -1085,7 +1236,19 @@ ExecMergeJoin(PlanState *pstate)
 						/* we need not do MJEvalInnerValues again */
 					}
 
-					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					if(isRangeJoin)
+					{
+						compareRangeResult = MJCompareRange(node);
+
+						if(compareRangeResult == 0)
+							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						else if(compareRangeResult < 0)
+							node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+						else /* compareRangeResult > 0 */
+							node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+					}
+					else
+						node->mj_JoinState = EXEC_MJ_JOINTUPLES;
 				}
 				else
 				{
@@ -1184,12 +1347,28 @@ ExecMergeJoin(PlanState *pstate)
 
 				if (compareResult == 0)
 				{
-					if (!node->mj_SkipMarkRestore)
-						ExecMarkPos(innerPlan);
-
-					MarkInnerTuple(node->mj_InnerTupleSlot, node);
-
-					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					if(isRangeJoin)
+					{
+						compareRangeResult = MJCompareRange(node);
+						if(compareRangeResult == 0)
+						{
+							if (!node->mj_SkipMarkRestore)
+								ExecMarkPos(innerPlan);
+							MarkInnerTuple(node->mj_InnerTupleSlot, node);
+							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						}
+						else if(compareRangeResult < 0)
+							node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+						else /* compareRangeResult > 0 */
+							node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+					}
+					else
+					{
+						if (!node->mj_SkipMarkRestore)
+							ExecMarkPos(innerPlan);
+						MarkInnerTuple(node->mj_InnerTupleSlot, node);
+						node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					}
 				}
 				else if (compareResult < 0)
 					node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
@@ -1532,6 +1711,17 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 		ExecInitQual(node->join.joinqual, (PlanState *) mergestate);
 	/* mergeclauses are handled below */
 
+	/*
+	 * Thomas
+	 */
+	if(node->rangeclause)
+	{
+		mergestate->mj_RangeData = MJCreateRangeData(node->rangeclause, (PlanState *) mergestate);
+		mergestate->mj_RangeJoin = true;
+	}
+	else
+		mergestate->mj_RangeJoin = false;
+
 	/*
 	 * detect whether we need only consider the first matching inner tuple
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 621f7ce068..ed81a185cc 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2356,6 +2356,9 @@ _copyRestrictInfo(const RestrictInfo *from)
 	COPY_SCALAR_FIELD(norm_selec);
 	COPY_SCALAR_FIELD(outer_selec);
 	COPY_NODE_FIELD(mergeopfamilies);
+	COPY_NODE_FIELD(rangeleftopfamilies); /* Thomas */
+	COPY_NODE_FIELD(rangerightopfamilies); /* Thomas */
+
 	/* EquivalenceClasses are never copied, so shallow-copy the pointers */
 	COPY_SCALAR_FIELD(left_ec);
 	COPY_SCALAR_FIELD(right_ec);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e32b92e299..9612079b07 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -765,6 +765,8 @@ _outMergeJoin(StringInfo str, const MergeJoin *node)
 	WRITE_OID_ARRAY(mergeCollations, numCols);
 	WRITE_INT_ARRAY(mergeStrategies, numCols);
 	WRITE_BOOL_ARRAY(mergeNullsFirst, numCols);
+
+	WRITE_NODE_FIELD(rangeclause); /* Thomas */
 }
 
 static void
@@ -2235,6 +2237,7 @@ _outMergePath(StringInfo str, const MergePath *node)
 	_outJoinPathInfo(str, (const JoinPath *) node);
 
 	WRITE_NODE_FIELD(path_mergeclauses);
+	WRITE_NODE_FIELD(path_rangeclause); /* Thomas */
 	WRITE_NODE_FIELD(outersortkeys);
 	WRITE_NODE_FIELD(innersortkeys);
 	WRITE_BOOL_FIELD(skip_mark_restore);
@@ -2557,6 +2560,9 @@ _outRestrictInfo(StringInfo str, const RestrictInfo *node)
 	WRITE_FLOAT_FIELD(norm_selec, "%.4f");
 	WRITE_FLOAT_FIELD(outer_selec, "%.4f");
 	WRITE_NODE_FIELD(mergeopfamilies);
+	WRITE_NODE_FIELD(rangeleftopfamilies); /* Thomas */
+	WRITE_NODE_FIELD(rangerightopfamilies); /* Thomas */
+
 	/* don't write left_ec, leads to infinite recursion in plan tree dump */
 	/* don't write right_ec, leads to infinite recursion in plan tree dump */
 	WRITE_NODE_FIELD(left_em);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index f0b34ecfac..20c8d4883a 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2180,6 +2180,8 @@ _readMergeJoin(void)
 	READ_INT_ARRAY(mergeStrategies, numCols);
 	READ_BOOL_ARRAY(mergeNullsFirst, numCols);
 
+	READ_NODE_FIELD(rangeclause); /* Thomas */
+
 	READ_DONE();
 }
 
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 353454b183..58845d78db 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -3017,6 +3017,7 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
 		 * level, and build paths for making each one from every available
 		 * pair of lower-level relations.
 		 */
+
 		join_search_one_level(root, lev);
 
 		/*
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 8577c7b138..ea45f961ec 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3419,6 +3419,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	double		inner_path_rows = inner_path->rows;
 	List	   *mergeclauses = path->path_mergeclauses;
 	List	   *innersortkeys = path->innersortkeys;
+	List	   *allclauses; /* Thomas */
 	Cost		startup_cost = workspace->startup_cost;
 	Cost		run_cost = workspace->run_cost;
 	Cost		inner_run_cost = workspace->inner_run_cost;
@@ -3435,9 +3436,12 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 				rescannedtuples;
 	double		rescanratio;
 
-	/* Protect some assumptions below that rowcounts aren't zero */
-	if (inner_path_rows <= 0)
-		inner_path_rows = 1;
+	allclauses = list_concat(list_copy(mergeclauses), path->path_rangeclause); /* Thomas */
+
+
+    /* Protect some assumptions below that rowcounts aren't zero */
+    if (inner_path_rows <= 0)
+        inner_path_rows = 1;
 
 	/* Mark the path with the correct row estimate */
 	if (path->jpath.path.param_info)
@@ -3466,7 +3470,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * Compute cost of the mergequals and qpquals (other restriction clauses)
 	 * separately.
 	 */
-	cost_qual_eval(&merge_qual_cost, mergeclauses, root);
+	cost_qual_eval(&merge_qual_cost, allclauses, root); /* Thomas merge -> all */
 	cost_qual_eval(&qp_qual_cost, path->jpath.joinrestrictinfo, root);
 	qp_qual_cost.startup -= merge_qual_cost.startup;
 	qp_qual_cost.per_tuple -= merge_qual_cost.per_tuple;
@@ -3490,7 +3494,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * Get approx # tuples passing the mergequals.  We use approx_tuple_count
 	 * here because we need an estimate done with JOIN_INNER semantics.
 	 */
-	mergejointuples = approx_tuple_count(root, &path->jpath, mergeclauses);
+	mergejointuples = approx_tuple_count(root, &path->jpath, allclauses); /* Thomas merge -> all */
 
 	/*
 	 * When there are equal merge keys in the outer relation, the mergejoin
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index b67b517770..ab9e6a3c78 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -16,6 +16,7 @@
 
 #include <math.h>
 
+#include "catalog/pg_operator.h"	/* Thomas */
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
 #include "nodes/nodeFuncs.h"
@@ -24,6 +25,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "utils/lsyscache.h"	/* Thomas */
 #include "utils/typcache.h"
 
 /* Hook for plugins to get control in add_paths_to_joinrel() */
@@ -84,6 +86,9 @@ static List *select_mergejoin_clauses(PlannerInfo *root,
 									  List *restrictlist,
 									  JoinType jointype,
 									  bool *mergejoin_allowed);
+static List *select_rangejoin_clauses(RelOptInfo *outerrel,
+									  RelOptInfo *innerrel,
+									  List *restrictlist);
 static void generate_mergejoin_paths(PlannerInfo *root,
 									 RelOptInfo *joinrel,
 									 RelOptInfo *innerrel,
@@ -147,6 +152,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 
 	extra.restrictlist = restrictlist;
 	extra.mergeclause_list = NIL;
+	extra.rangeclause_list = NIL; /* Thomas */
 	extra.sjinfo = sjinfo;
 	extra.param_source_rels = NULL;
 
@@ -207,6 +213,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 * it's a full join.
 	 */
 	if (enable_mergejoin || jointype == JOIN_FULL)
+	{
 		extra.mergeclause_list = select_mergejoin_clauses(root,
 														  joinrel,
 														  outerrel,
@@ -215,6 +222,12 @@ add_paths_to_joinrel(PlannerInfo *root,
 														  jointype,
 														  &mergejoin_allowed);
 
+		if (jointype == JOIN_INNER || jointype == JOIN_LEFT)
+			extra.rangeclause_list = select_rangejoin_clauses(outerrel,
+															  innerrel,
+															  restrictlist);
+	}
+
 	/*
 	 * If it's SEMI, ANTI, or inner_unique join, compute correction factors
 	 * for cost estimation.  These will be the same for all paths.
@@ -777,6 +790,7 @@ try_mergejoin_path(PlannerInfo *root,
 				   Path *inner_path,
 				   List *pathkeys,
 				   List *mergeclauses,
+				   List *rangeclause,	/* Thomas */
 				   List *outersortkeys,
 				   List *innersortkeys,
 				   JoinType jointype,
@@ -850,6 +864,7 @@ try_mergejoin_path(PlannerInfo *root,
 									   pathkeys,
 									   required_outer,
 									   mergeclauses,
+									   rangeclause,
 									   outersortkeys,
 									   innersortkeys));
 	}
@@ -926,6 +941,7 @@ try_partial_mergejoin_path(PlannerInfo *root,
 										   pathkeys,
 										   NULL,
 										   mergeclauses,
+										   NIL,
 										   outersortkeys,
 										   innersortkeys));
 }
@@ -1107,7 +1123,8 @@ sort_inner_and_outer(PlannerInfo *root,
 	Path	   *inner_path;
 	Path	   *cheapest_partial_outer = NULL;
 	Path	   *cheapest_safe_inner = NULL;
-	List	   *all_pathkeys;
+	List	   *merge_pathkeys;
+	List	   *range_pathkeys; /* Thomas */
 	ListCell   *l;
 
 	/*
@@ -1206,26 +1223,84 @@ sort_inner_and_outer(PlannerInfo *root,
 	 * The pathkey order returned by select_outer_pathkeys_for_merge() has
 	 * some heuristics behind it (see that function), so be sure to try it
 	 * exactly as-is as well as making variants.
+	 *
+	 * Thomas: range_pathkeys
 	 */
-	all_pathkeys = select_outer_pathkeys_for_merge(root,
-												   extra->mergeclause_list,
-												   joinrel);
 
-	foreach(l, all_pathkeys)
+	range_pathkeys = select_outer_pathkeys_for_range(root,
+													 extra->rangeclause_list);
+
+	merge_pathkeys = select_outer_pathkeys_for_merge(root,
+													 extra->mergeclause_list,
+													 joinrel);
+
+	/* Thomas */
+	if(merge_pathkeys == NIL && enable_mergejoin)
+	{
+		foreach(l, range_pathkeys)
+		{
+			PathKey		*range_pathkey = (PathKey *) lfirst(l);
+			List		*outerkeys = list_make1(range_pathkey);
+			List		*innerkeys = NIL;
+			List		*rangeclauses;
+			ListCell	*lc;
+
+			rangeclauses = find_rangeclauses_for_outer_pathkeys(root,
+												outerkeys,
+												NIL,
+												extra->rangeclause_list);
+
+			foreach(lc, rangeclauses)
+			{
+				List	*rangeclause = (List *) lfirst(lc);
+				PathKey *range_inner_pathkey =
+						make_inner_pathkey_for_range(root,
+													 rangeclause);
+
+				innerkeys = lappend(innerkeys, range_inner_pathkey);
+
+				merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+															 outerkeys);
+
+				/*
+				 * And now we can make the path.
+				 *
+				 * Note: it's possible that the cheapest paths will already be sorted
+				 * properly.  try_mergejoin_path will detect that case and suppress an
+				 * explicit sort step, so we needn't do so here.
+				 */
+				try_mergejoin_path(root,
+								   joinrel,
+								   outer_path,
+								   inner_path,
+								   merge_pathkeys,
+								   NIL,
+								   rangeclause,
+								   outerkeys,
+								   innerkeys,
+								   jointype,
+								   extra,
+								   false);
+			}
+		}
+	}
+
+
+	foreach(l, merge_pathkeys)
 	{
 		List	   *front_pathkey = (List *) lfirst(l);
 		List	   *cur_mergeclauses;
 		List	   *outerkeys;
 		List	   *innerkeys;
-		List	   *merge_pathkeys;
+		List	   *mergejoin_pathkeys;
 
 		/* Make a pathkey list with this guy first */
-		if (l != list_head(all_pathkeys))
+		if (l != list_head(merge_pathkeys))
 			outerkeys = lcons(front_pathkey,
-							  list_delete_nth_cell(list_copy(all_pathkeys),
-												   foreach_current_index(l)));
+							  list_delete_ptr(list_copy(merge_pathkeys),
+											  front_pathkey));
 		else
-			outerkeys = all_pathkeys;	/* no work at first one... */
+			outerkeys = merge_pathkeys;	/* no work at first one... */
 
 		/* Sort the mergeclauses into the corresponding ordering */
 		cur_mergeclauses =
@@ -1242,7 +1317,7 @@ sort_inner_and_outer(PlannerInfo *root,
 												  outerkeys);
 
 		/* Build pathkeys representing output sort order */
-		merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+		mergejoin_pathkeys = build_join_pathkeys(root, joinrel, jointype,
 											 outerkeys);
 
 		/*
@@ -1256,8 +1331,9 @@ sort_inner_and_outer(PlannerInfo *root,
 						   joinrel,
 						   outer_path,
 						   inner_path,
-						   merge_pathkeys,
+						   mergejoin_pathkeys,
 						   cur_mergeclauses,
+						   NIL,
 						   outerkeys,
 						   innerkeys,
 						   jointype,
@@ -1273,12 +1349,88 @@ sort_inner_and_outer(PlannerInfo *root,
 									   joinrel,
 									   cheapest_partial_outer,
 									   cheapest_safe_inner,
-									   merge_pathkeys,
+									   mergejoin_pathkeys,
 									   cur_mergeclauses,
 									   outerkeys,
 									   innerkeys,
 									   jointype,
 									   extra);
+
+		foreach(l, range_pathkeys)
+		{
+			PathKey		*range_outer_pathkey = (PathKey *) lfirst(l);
+			List		*range_outerkeys = list_copy(outerkeys);
+			List		*range_innerkeys;
+			List		*rangeclauses;
+			ListCell	*lc;
+
+			if(list_member_ptr(range_outerkeys, range_outer_pathkey))
+			{
+				if(!equal(llast(range_outerkeys), range_outer_pathkey))
+					continue;
+			}
+			else
+				range_outerkeys = lappend(range_outerkeys, range_outer_pathkey);
+
+			/* Sort the mergeclauses into the corresponding ordering */
+			cur_mergeclauses =
+				find_mergeclauses_for_outer_pathkeys(root,
+													 range_outerkeys,
+													 extra->mergeclause_list);
+
+			/* Should have used them all... */
+			Assert(list_length(cur_mergeclauses) == list_length(extra->mergeclause_list));
+
+			/* Build sort pathkeys for the inner side */
+			range_innerkeys = make_inner_pathkeys_for_merge(root,
+													  	  	cur_mergeclauses,
+															range_outerkeys);
+
+			rangeclauses = find_rangeclauses_for_outer_pathkeys(root,
+												range_outerkeys,
+												cur_mergeclauses,
+												extra->rangeclause_list);
+
+			foreach(lc, rangeclauses)
+			{
+				List	*cur_rangeclause = (List *) lfirst(lc);
+				PathKey *range_inner_pathkey;
+
+				range_inner_pathkey = make_inner_pathkey_for_range(root, cur_rangeclause);
+
+				if(list_member_ptr(range_innerkeys, range_inner_pathkey))
+				{
+					if(!equal(llast(range_innerkeys), range_inner_pathkey))
+						continue;
+				}
+				else
+					range_innerkeys = lappend(range_innerkeys, range_inner_pathkey);
+
+
+				mergejoin_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+															 range_outerkeys);
+
+				/*
+				 * And now we can make the path.
+				 *
+				 * Note: it's possible that the cheapest paths will already be sorted
+				 * properly.  try_mergejoin_path will detect that case and suppress an
+				 * explicit sort step, so we needn't do so here.
+				 */
+				try_mergejoin_path(root,
+								   joinrel,
+								   outer_path,
+								   inner_path,
+								   mergejoin_pathkeys,
+								   cur_mergeclauses,
+								   cur_rangeclause,
+								   range_outerkeys,
+								   range_innerkeys,
+								   jointype,
+								   extra,
+								   false);
+			}
+		}
 	}
 }
 
@@ -1364,6 +1516,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 					   merge_pathkeys,
 					   mergeclauses,
 					   NIL,
+					   NIL,
 					   innersortkeys,
 					   jointype,
 					   extra,
@@ -1462,6 +1615,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 							   newclauses,
 							   NIL,
 							   NIL,
+							   NIL,
 							   jointype,
 							   extra,
 							   is_partial);
@@ -1506,6 +1660,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 								   newclauses,
 								   NIL,
 								   NIL,
+								   NIL,
 								   jointype,
 								   extra,
 								   is_partial);
@@ -2272,3 +2427,143 @@ select_mergejoin_clauses(PlannerInfo *root,
 
 	return result_list;
 }
+
+/*
+ *
+ * Thomas
+ *
+ */
+
+static int
+range_clause_order(RestrictInfo *first,
+				   RestrictInfo *second)
+{
+	/*
+	 * Extract details from first restrictinfo
+	 */
+	Node   *first_left = get_leftop(first->clause),
+		   *first_right = get_rightop(first->clause);
+	bool	first_outer_is_left = first->outer_is_left;
+	int		first_strategy = get_op_opfamily_strategy(((OpExpr *) first->clause)->opno,
+														(linitial_oid(first->rangeleftopfamilies)));
+	bool    first_less = (first_strategy == BTLessStrategyNumber ||
+							first_strategy == BTLessEqualStrategyNumber);
+
+	/*
+	 * Extract details from second restrictinfo
+	 */
+	Node   *second_left = get_leftop(second->clause),
+		   *second_right = get_rightop(second->clause);
+	bool	second_outer_is_left = second->outer_is_left;
+	int		second_strategy = get_op_opfamily_strategy(((OpExpr *) second->clause)->opno,
+														(linitial_oid(second->rangeleftopfamilies)));
+	bool    second_less = (second_strategy == BTLessStrategyNumber ||
+							second_strategy == BTLessEqualStrategyNumber);
+
+	/*
+	 * Check for rangeclause
+	 */
+	if (first_less && second_less)
+	{
+		if (!first_outer_is_left && second_outer_is_left &&
+				equal(first_left, second_right))
+			return 2;
+		else if (first_outer_is_left && !second_outer_is_left &&
+				equal(first_right, second_left))
+			return 1;
+	}
+	else if (!first_less && !second_less)
+	{
+		if (!first_outer_is_left && second_outer_is_left &&
+				equal(first_left, second_right))
+			return 1;
+		else if (first_outer_is_left && !second_outer_is_left &&
+				equal(first_right, second_left))
+			return 2;
+	}
+	else if (first_less && !second_less)
+	{
+		if (!first_outer_is_left && !second_outer_is_left &&
+				equal(first_left, second_left))
+			return 2;
+		else if (first_outer_is_left && second_outer_is_left &&
+				equal(first_right, second_right))
+			return 1;
+	}
+	else if (!first_less && second_less)
+	{
+		if (!first_outer_is_left && !second_outer_is_left &&
+				equal(first_left, second_left))
+			return 1;
+		else if (first_outer_is_left && second_outer_is_left &&
+				equal(first_right, second_right))
+			return 2;
+	}
+
+	return 0;
+}
+
+/*
+ * Thomas
+ *
+ * select_rangejoin_clauses
+ */
+static List *
+select_rangejoin_clauses(RelOptInfo *outerrel,
+						 RelOptInfo *innerrel,
+						 List *restrictlist)
+{
+	List	   *result_list = NIL;
+	ListCell   *l;
+	List	   *range_candidates = NIL; /* Thomas */
+
+	foreach(l, restrictlist)
+	{
+		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
+		OpExpr		 *clause = (OpExpr *) restrictinfo->clause;
+
+		/* Check that clause is a rangejoinable operator clause */
+		if (restrictinfo->rangeleftopfamilies == NIL)
+			continue;			/* not rangejoinable */
+
+		/*
+		 * Check if clause has the form "outer op inner" or "inner op outer".
+		 */
+		if (!clause_sides_match_join(restrictinfo, outerrel, innerrel))
+			continue;			/* no good for these input relations */
+
+		if (restrictinfo->rangeleftopfamilies == restrictinfo->rangerightopfamilies)
+		{
+			ListCell *lc;
+			List	 *range_clause;
+
+			foreach(lc, range_candidates)
+			{
+				RestrictInfo *candidate = (RestrictInfo *) lfirst(lc);
+
+				switch (range_clause_order(restrictinfo, candidate))
+				{
+				case 1:
+					range_clause = list_make2(restrictinfo, candidate);
+					result_list = lappend(result_list, range_clause);
+					break;
+
+				case 2:
+					range_clause = list_make2(candidate, restrictinfo);
+					result_list = lappend(result_list, range_clause);
+					break;
+				default:
+					break;
+				}
+			}
+			range_candidates = lappend(range_candidates, restrictinfo);
+		}
+		else if ((clause->opno == OID_RANGE_CONTAINS_ELEM_OP && restrictinfo->outer_is_left) ||
+				(clause->opno == OID_RANGE_ELEM_CONTAINED_OP && !restrictinfo->outer_is_left))
+		{
+			result_list = lappend(result_list, list_make1(restrictinfo));
+		}
+	}
+	list_free(range_candidates);
+	return result_list;
+}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..d2bf0ff9b4 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -19,6 +19,7 @@
 
 #include "access/stratnum.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_operator.h" /* Thomas */
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/plannodes.h"
@@ -1214,6 +1215,60 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo)
 								 true);
 }
 
+/*
+ * Thomas
+ */
+void
+initialize_rangeclause_eclasses(PlannerInfo *root,
+								RestrictInfo *restrictinfo)
+{
+	Expr   *clause = restrictinfo->clause;
+	Oid		lefttype,
+			righttype,
+			opno;
+
+	/* Should be a rangeclause ... */
+	Assert(restrictinfo->rangeleftopfamilies != NIL &&
+			restrictinfo->rangerightopfamilies != NIL);
+	/* ... with links not yet set */
+	Assert(restrictinfo->left_ec == NULL);
+	Assert(restrictinfo->right_ec == NULL);
+
+	opno = ((OpExpr *) clause)->opno;
+
+	/* Need the declared input types of the operator */
+	op_input_types(opno, &lefttype, &righttype);
+
+	if(opno == OID_RANGE_CONTAINS_ELEM_OP)
+		righttype = exprType(get_rightop(clause));
+
+	else if(opno == OID_RANGE_ELEM_CONTAINED_OP)
+		lefttype = exprType(get_leftop(clause));
+
+
+	/* Find or create a matching EquivalenceClass for each side */
+	restrictinfo->left_ec =
+		get_eclass_for_sort_expr(root,
+				 	 	 	 	 (Expr *) get_leftop(clause),
+								 restrictinfo->nullable_relids,
+								 restrictinfo->rangeleftopfamilies,
+								 lefttype,
+								 ((OpExpr *) clause)->inputcollid,
+								 0,
+								 NULL,
+								 true);
+	restrictinfo->right_ec =
+		get_eclass_for_sort_expr(root,
+								 (Expr *) get_rightop(clause),
+								 restrictinfo->nullable_relids,
+								 restrictinfo->rangerightopfamilies,
+								 righttype,
+								 ((OpExpr *) clause)->inputcollid,
+								 0,
+								 NULL,
+								 true);
+}
+
 /*
  * update_mergeclause_eclasses
  *		Make the cached EquivalenceClass links valid in a mergeclause
@@ -1347,6 +1402,62 @@ find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 	return mergeclauses;
 }
 
+/* Thomas */
+List *
+find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
+									List *pathkeys,
+									List *mergeclauses,
+									List *rangeclauses)
+{
+	ListCell   *i;
+	RestrictInfo *mergeclause = NULL;
+	List *result_list = NIL;
+
+	if(mergeclauses)
+		mergeclause = llast(mergeclauses);
+
+	foreach(i, pathkeys)
+	{
+		PathKey    *pathkey = (PathKey *) lfirst(i);
+		EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
+		ListCell   *j;
+
+		if(mergeclause != NULL)
+		{
+			if(mergeclause->outer_is_left)
+			{
+				if(mergeclause->left_ec != pathkey_ec)
+					continue;
+			}
+			else if(mergeclause->right_ec != pathkey_ec)
+				continue;
+		}
+
+		foreach(j, rangeclauses)
+		{
+			List				*rangeclause = (List *) lfirst(j);
+			RestrictInfo 		*rinfo = (RestrictInfo *) linitial(rangeclause);
+			EquivalenceClass 	*clause_ec;
+
+			clause_ec = rinfo->outer_is_left ?
+				rinfo->left_ec : rinfo->right_ec;
+
+			if (clause_ec == pathkey_ec)
+				result_list = lappend(result_list, rangeclause);
+		}
+
+		/*
+		 * Was it the last possible pathkey?
+		 */
+		if(mergeclause == NULL)
+			break;
+		else
+			mergeclause = NULL;
+
+  }
+  return result_list;
+}
+
 /*
  * select_outer_pathkeys_for_merge
  *	  Builds a pathkey list representing a possible sort ordering
@@ -1441,6 +1552,8 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 	 * Find out if we have all the ECs mentioned in query_pathkeys; if so we
 	 * can generate a sort order that's also useful for final output. There is
 	 * no percentage in a partial match, though, so we have to have 'em all.
+	 *
+	 * TODO: check for possible pathkeys of rangeclauses.
 	 */
 	if (root->query_pathkeys)
 	{
@@ -1522,6 +1635,37 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 	return pathkeys;
 }
 
+/*
+ * Thomas
+ */
+List *
+select_outer_pathkeys_for_range(PlannerInfo *root,
+								List *rangeclauses)
+{
+	EquivalenceClass	*ec;
+	PathKey 			*pathkey;
+	List 				*pathkeys = NIL;
+	ListCell			*lc;
+
+	foreach(lc, rangeclauses){
+		List *rangeclause = lfirst(lc);
+		RestrictInfo *rinfo = linitial(rangeclause);
+
+		if (rinfo->outer_is_left)
+			ec = rinfo->left_ec;
+		else
+			ec = rinfo->right_ec;
+
+		pathkey = make_canonical_pathkey(root,
+										 ec,
+										 linitial_oid(ec->ec_opfamilies),
+										 BTLessStrategyNumber,
+										 false);
+		pathkeys = lappend(pathkeys, pathkey);
+	}
+	return pathkeys;
+}
+
 /*
  * make_inner_pathkeys_for_merge
  *	  Builds a pathkey list representing the explicit sort order that
@@ -1621,6 +1765,32 @@ make_inner_pathkeys_for_merge(PlannerInfo *root,
 	return pathkeys;
 }
 
+PathKey *
+make_inner_pathkey_for_range(PlannerInfo *root,
+							 List *rangeclause)
+{
+	EquivalenceClass *ec;
+	PathKey 	 *pathkey;
+	RestrictInfo *rinfo;
+
+	if(rangeclause == NIL)
+		return NULL;
+
+	rinfo = linitial(rangeclause);
+
+	if (rinfo->outer_is_left)
+		ec = rinfo->right_ec;
+	else
+		ec = rinfo->left_ec;
+
+	pathkey = make_canonical_pathkey(root,
+									 ec,
+									 linitial_oid(ec->ec_opfamilies),
+									 BTLessStrategyNumber,
+									 false);
+	return pathkey;
+}
+
 /*
  * trim_mergeclauses_for_inner_pathkeys
  *	  This routine trims a list of mergeclauses to include just those that
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 439e6b6426..83641a623d 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -247,6 +247,7 @@ static Hash *make_hash(Plan *lefttree,
 static MergeJoin *make_mergejoin(List *tlist,
 								 List *joinclauses, List *otherclauses,
 								 List *mergeclauses,
+								 List *rangeclause,
 								 Oid *mergefamilies,
 								 Oid *mergecollations,
 								 int *mergestrategies,
@@ -4304,6 +4305,7 @@ create_mergejoin_plan(PlannerInfo *root,
 	List	   *joinclauses;
 	List	   *otherclauses;
 	List	   *mergeclauses;
+	List	   *rangeclause;	/*Thomas */
 	List	   *outerpathkeys;
 	List	   *innerpathkeys;
 	int			nClauses;
@@ -4326,6 +4328,7 @@ create_mergejoin_plan(PlannerInfo *root,
 	 * best to request a small tlist so we aren't sorting more data than
 	 * necessary.
 	 */
+
 	outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath,
 									 (best_path->outersortkeys != NIL) ? CP_SMALL_TLIST : 0);
 
@@ -4358,6 +4361,10 @@ create_mergejoin_plan(PlannerInfo *root,
 	mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
 	joinclauses = list_difference(joinclauses, mergeclauses);
 
+	/* Thomas */
+	rangeclause = get_actual_clauses(best_path->path_rangeclause);
+	joinclauses = list_difference(joinclauses, rangeclause);
+
 	/*
 	 * Replace any outer-relation variables with nestloop params.  There
 	 * should not be any in the mergeclauses.
@@ -4368,6 +4375,8 @@ create_mergejoin_plan(PlannerInfo *root,
 			replace_nestloop_params(root, (Node *) joinclauses);
 		otherclauses = (List *)
 			replace_nestloop_params(root, (Node *) otherclauses);
+		rangeclause = (List *)
+			replace_nestloop_params(root, (Node *) rangeclause);
 	}
 
 	/*
@@ -4584,6 +4593,7 @@ create_mergejoin_plan(PlannerInfo *root,
 							   joinclauses,
 							   otherclauses,
 							   mergeclauses,
+							   rangeclause,
 							   mergefamilies,
 							   mergecollations,
 							   mergestrategies,
@@ -5885,6 +5895,7 @@ make_mergejoin(List *tlist,
 			   List *joinclauses,
 			   List *otherclauses,
 			   List *mergeclauses,
+			   List *rangeclause,	/* Thomas */
 			   Oid *mergefamilies,
 			   Oid *mergecollations,
 			   int *mergestrategies,
@@ -5911,6 +5922,7 @@ make_mergejoin(List *tlist,
 	node->join.jointype = jointype;
 	node->join.inner_unique = inner_unique;
 	node->join.joinqual = joinclauses;
+	node->rangeclause = rangeclause;
 
 	return node;
 }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 3ac853d9ef..acfca8db99 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -16,6 +16,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_operator.h" /* Thomas */
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -77,6 +78,7 @@ static bool check_equivalence_delay(PlannerInfo *root,
 									RestrictInfo *restrictinfo);
 static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
 static void check_mergejoinable(RestrictInfo *restrictinfo);
+static void check_rangejoinable(RestrictInfo *restrictinfo); /* Thomas */
 static void check_hashjoinable(RestrictInfo *restrictinfo);
 static void check_resultcacheable(RestrictInfo *restrictinfo);
 
@@ -1877,6 +1879,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	 */
 	check_mergejoinable(restrictinfo);
 
+	check_rangejoinable(restrictinfo);
+
 	/*
 	 * If it is a true equivalence clause, send it to the EquivalenceClass
 	 * machinery.  We do *not* attach it directly to any restriction or join
@@ -1962,6 +1966,15 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 		}
 	}
 
+	/* Thomas */
+	if(restrictinfo->rangeleftopfamilies)
+	{
+		Assert(restrictinfo->rangerightopfamilies);
+
+		initialize_rangeclause_eclasses(root,
+										restrictinfo);
+	}
+
 	/* No EC special case applies, so push it into the clause lists */
 	distribute_restrictinfo_to_rels(root, restrictinfo);
 }
@@ -2677,6 +2690,48 @@ check_mergejoinable(RestrictInfo *restrictinfo)
 	 */
 }
 
+/* Thomas */
+static void
+check_rangejoinable(RestrictInfo *restrictinfo)
+{
+	OpExpr	   *clause = (OpExpr *) restrictinfo->clause;
+	Oid			opno = clause->opno;
+
+	if (!restrictinfo->can_join)
+		return;
+	if (contain_volatile_functions((Node *) clause))
+		return;
+
+	if (opno == OID_RANGE_CONTAINS_ELEM_OP ||
+			opno == OID_RANGE_ELEM_CONTAINED_OP)
+	{
+		Node		   *leftarg = get_leftop(clause),
+					   *rightarg = get_rightop(clause);
+
+		Oid				lefttype = exprType(leftarg),
+						righttype = exprType(rightarg);
+
+		TypeCacheEntry *left_typecache = lookup_type_cache(lefttype, TYPECACHE_CMP_PROC),
+					   *right_typecache = lookup_type_cache(righttype, TYPECACHE_CMP_PROC);
+
+		restrictinfo->rangeleftopfamilies =
+				lappend_oid(restrictinfo->rangeleftopfamilies,
+							left_typecache->btree_opf);
+
+		restrictinfo->rangerightopfamilies =
+				lappend_oid(restrictinfo->rangerightopfamilies,
+							right_typecache->btree_opf);
+	}
+	else
+	{
+		List *opfamilies = get_rangejoin_opfamilies(opno);
+
+		restrictinfo->rangeleftopfamilies = opfamilies;
+		restrictinfo->rangerightopfamilies = opfamilies;
+
+	}
+}
+
 /*
  * check_hashjoinable
  *	  If the restrictinfo's clause is hashjoinable, set the hashjoin
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 61ccfd300b..75d35a1a8d 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2024,6 +2024,14 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
 										 (Index) 0,
 										 rtoffset,
 										 NUM_EXEC_QUAL((Plan *) join));
+
+		mj->rangeclause = fix_join_expr(root,
+										 mj->rangeclause,
+										 outer_itlist,
+										 inner_itlist,
+										 (Index) 0,
+										 rtoffset,
+										 NUM_EXEC_QUAL((Plan *) join));
 	}
 	else if (IsA(join, HashJoin))
 	{
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 9ce5f95e3b..6e79beb9ec 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -2505,6 +2505,7 @@ create_mergejoin_path(PlannerInfo *root,
 					  List *pathkeys,
 					  Relids required_outer,
 					  List *mergeclauses,
+					  List *rangeclause, /* Thomas */
 					  List *outersortkeys,
 					  List *innersortkeys)
 {
@@ -2533,6 +2534,7 @@ create_mergejoin_path(PlannerInfo *root,
 	pathnode->jpath.innerjoinpath = inner_path;
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
 	pathnode->path_mergeclauses = mergeclauses;
+	pathnode->path_rangeclause = rangeclause;	/* Thomas */
 	pathnode->outersortkeys = outersortkeys;
 	pathnode->innersortkeys = innersortkeys;
 	/* pathnode->skip_mark_restore will be set by final_cost_mergejoin */
@@ -4135,6 +4137,7 @@ do { \
 				REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
 				ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
 				ADJUST_CHILD_ATTRS(mpath->path_mergeclauses);
+				ADJUST_CHILD_ATTRS(mpath->path_rangeclause); /* Thomas */
 				new_path = (Path *) mpath;
 			}
 			break;
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index aa9fb3a9fa..8887341d74 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -201,6 +201,8 @@ make_restrictinfo_internal(PlannerInfo *root,
 	restrictinfo->outer_selec = -1;
 
 	restrictinfo->mergeopfamilies = NIL;
+	restrictinfo->rangeleftopfamilies = NIL;
+	restrictinfo->rangerightopfamilies = NIL;
 
 	restrictinfo->left_ec = NULL;
 	restrictinfo->right_ec = NULL;
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 815175a654..bcd7c328c3 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -547,7 +547,6 @@ elem_contained_by_range(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(range_contains_elem_internal(typcache, r, val));
 }
 
-
 /* range, range -> bool functions */
 
 /* equality (internal version) */
@@ -2507,6 +2506,66 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
 	return true;
 }
 
+/*
+ * Test whether range r is right of a specific element value.
+ */
+bool
+elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
+{
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	int32		cmp;
+
+	range_deserialize(typcache, r, &lower, &upper, &empty);
+
+	if (empty)
+		return true;
+
+	if (!lower.infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  lower.val, val));
+		if (cmp > 0)
+			return true;
+		if (cmp == 0 && !lower.inclusive)
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Test whether range r is left of a specific element value.
+ */
+bool
+elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
+{
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	int32		cmp;
+
+	range_deserialize(typcache, r, &lower, &upper, &empty);
+
+	if (empty)
+		return true;
+
+	if (!upper.infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  upper.val, val));
+		if (cmp < 0)
+			return true;
+		if (cmp == 0 && !upper.inclusive)
+			return true;
+	}
+
+	return false;
+}
+
 
 /*
  * datum_compute_size() and datum_write() are used to insert the bound
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 6bba5f8ec4..22b2da8df7 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -389,6 +389,40 @@ get_mergejoin_opfamilies(Oid opno)
 	return result;
 }
 
+/*
+ * Thomas
+ */
+List *
+get_rangejoin_opfamilies(Oid opno)
+{
+	List	   *result = NIL;
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * Search pg_amop to see if the target operator is registered as the "<",
+	 * "<=", ">" or ">=" operator of any btree opfamily.
+	 */
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == BTREE_AM_OID &&
+			(aform->amopstrategy == BTLessStrategyNumber ||
+				aform->amopstrategy == BTLessEqualStrategyNumber ||
+				aform->amopstrategy == BTGreaterStrategyNumber ||
+				aform->amopstrategy == BTGreaterEqualStrategyNumber))
+			result = lappend_oid(result, aform->amopfamily);
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_compatible_hash_operators
  *		Get the OID(s) of hash equality operator(s) compatible with the given
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 7795a69490..ea37a7fdf4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1947,6 +1947,7 @@ typedef struct NestLoopState
  */
 /* private in nodeMergejoin.c: */
 typedef struct MergeJoinClauseData *MergeJoinClause;
+typedef struct RangeJoinData *RangeData;
 
 typedef struct MergeJoinState
 {
@@ -1968,6 +1969,8 @@ typedef struct MergeJoinState
 	TupleTableSlot *mj_NullInnerTupleSlot;
 	ExprContext *mj_OuterEContext;
 	ExprContext *mj_InnerEContext;
+	RangeData	 mj_RangeData;	/* Thomas */
+	bool		 mj_RangeJoin;	/* Thomas */
 } MergeJoinState;
 
 /* ----------------
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index b7b2817a5d..5cdd8727aa 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1636,6 +1636,7 @@ typedef struct MergePath
 {
 	JoinPath	jpath;
 	List	   *path_mergeclauses;	/* join clauses to be used for merge */
+	List	   *path_rangeclause;	/* Thomas */
 	List	   *outersortkeys;	/* keys for explicit sort, if any */
 	List	   *innersortkeys;	/* keys for explicit sort, if any */
 	bool		skip_mark_restore;	/* can executor skip mark/restore? */
@@ -2091,6 +2092,8 @@ typedef struct RestrictInfo
 
 	/* valid if clause is mergejoinable, else NIL */
 	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List	   *rangeleftopfamilies; /* Thomas */
+	List	   *rangerightopfamilies; /* Thomas */
 
 	/* cache space for mergeclause processing; NULL if not yet set */
 	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
@@ -2517,6 +2520,7 @@ typedef struct JoinPathExtraData
 {
 	List	   *restrictlist;
 	List	   *mergeclause_list;
+	List	   *rangeclause_list; /* Thomas */
 	bool		inner_unique;
 	SpecialJoinInfo *sjinfo;
 	SemiAntiJoinFactors semifactors;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aaa3b65d04..b9e72f95b5 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -751,6 +751,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 */
+	List	   *rangeclause;	/* Thomas */
 } MergeJoin;
 
 /* ----------------
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 53261ee91f..01cba9d360 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -167,6 +167,7 @@ extern MergePath *create_mergejoin_path(PlannerInfo *root,
 										List *pathkeys,
 										Relids required_outer,
 										List *mergeclauses,
+										List *rangeclause,
 										List *outersortkeys,
 										List *innersortkeys);
 
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index f1d111063c..0acfd297eb 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -231,17 +231,27 @@ extern List *make_pathkeys_for_sortclauses(PlannerInfo *root,
 										   List *tlist);
 extern void initialize_mergeclause_eclasses(PlannerInfo *root,
 											RestrictInfo *restrictinfo);
+extern void initialize_rangeclause_eclasses(PlannerInfo *root,
+											RestrictInfo *restrictinfo);
 extern void update_mergeclause_eclasses(PlannerInfo *root,
 										RestrictInfo *restrictinfo);
 extern List *find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 												  List *pathkeys,
 												  List *restrictinfos);
+extern List *find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
+												  List *pathkeys,
+												  List *mergeclauses,
+												  List *rangeclauses);
 extern List *select_outer_pathkeys_for_merge(PlannerInfo *root,
 											 List *mergeclauses,
 											 RelOptInfo *joinrel);
+extern List *select_outer_pathkeys_for_range(PlannerInfo *root,
+											 List *rangeclauses);
 extern List *make_inner_pathkeys_for_merge(PlannerInfo *root,
 										   List *mergeclauses,
 										   List *outer_pathkeys);
+extern PathKey *make_inner_pathkey_for_range(PlannerInfo *root,
+											 List *rangeclause);
 extern List *trim_mergeclauses_for_inner_pathkeys(PlannerInfo *root,
 												  List *mergeclauses,
 												  List *pathkeys);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 77871aaefc..3cabd336c9 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -79,6 +79,7 @@ extern bool get_ordering_op_properties(Oid opno,
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
 extern List *get_mergejoin_opfamilies(Oid opno);
+extern List *get_rangejoin_opfamilies(Oid opno);		/* Thomas */
 extern bool get_compatible_hash_operators(Oid opno,
 										  Oid *lhs_opno, Oid *rhs_opno);
 extern bool get_op_hash_functions(Oid opno,
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 04c302c619..f7b7bbad08 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -96,6 +96,10 @@ typedef struct
 
 extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 
+extern bool elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r);
+
+extern bool elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r);
+
 /* internal versions of the above */
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
simple-RMJ-annotated.pdfapplication/pdf; name=simple-RMJ-annotated.pdfDownload
%PDF-1.3
%�����������
3 0 obj
<< /Filter /FlateDecode /Length 5699 >>
stream
x�\k���q��_��6l.��|�g��de{um�����HZ��
���������&��;sW�(���4�b?O��.���o�w.��w���|��q�w��g������?���z��w�o���8����a�G�����G�c�>�C���!�S?����������f���}��z��g�yi���i���S�����c��z��-�~�.��b��>�Se	�Z�����I�^i���X*-:�{�Ot_�7��t����s�E�#&�-?��#��an�0G~��
]����T�����4�&N����w#�G�(?�]��v��E~��������aj0��D�?aG����X/�����Xz��O�2t-{��!A�W8���Zw��'��q\`#������h�E����������{�������3�Zp��//�aJ�p'
�;�67�:�[:����������/���<�Q�H��b���+���n(��
���>�Ri������ii��`�)��-\zE��e��j�Pl��Q����q�0U���%����>~V(�O��.��q��|���B�aC�|V(f3oL������MAq��SQ�e�GP��	 Cr�������������~������{��0������v����1�{���d��<�g)���o����c��`}k�}�v�^Y�-�~����j�%%Q�_M��Wv���,Y�G�Z?b�HN�o�gc��P�����|vb�~�%�(�����~���m�����j��q�vbc����l��������}H���gzp��
���������l�Yp=���Y��0���D�,�B�lJ���c*�����)�8�AQ�TP�����'o�ZP�(�j��d�5�T;K�Y,)~����d��mxt4f�Z�P�*&)M�B�M:��� �&X��#��C#?��Mql��� l��Sn��G~(TP��&*Fj�=��D������u�0�>������x�B�8�4;��@���+��-uoc�y�w\�P���R)k3-`�J���Rd������r�����2}`�JK�_��Sa��uo�&������CC/��U��M�v����a�*�0vb��b�O���?�����t>K��f�n����)���P��Y64���������?$��lx�/+F�!�{4D3��Z���]Bc8kIk"�jm����R�S�����r���+t��YRF�������J:KP�o
U�<������D�����N������!�. Ti'AU��O��.���rJ)N��*��7�5,�>R!�*b;���@�E�f�����{�&Dp�x���g�
�)�(zU���F���(�����5\�
����j9KUo��
��W�^��bIq��k���zeg��-��E�W���	S�	��S��P��O��i��V�4Qk���&1��,P���i��n��c�z
���
+�x�j�+o4���A��=�7,HIb}��Eo�z%vQ���5�W�����va6&:R��@�! kM�F�8�4?e��s��������)��'��&%�&<������@5S�������.)������,������H�6N�g7��yi���N\B9OF�Zx�2F�y]O� U�=t@	F��a�0���9��6����#��U��F,R�:8��e����k��&,]��f���=���D��6O����	+�g�
i�����
^��S ����������,��!����-z�"��p,ov��1�����e�c�^��s{\�{��v�qIR	��hF�E[%������L��%�5x����$J�4�s��������[�W�^[��_�5)q��� ����5|��z��U`���������n��\�����]��\��Ih�f��<";b�%HF�����YZ����>H���}���y@��]"~z<2�K��v������q7�jvz�M��s�'��������b��=>�H,��@��\�^S6M�2�7F�,�u�*�{��v�;E��`�ngq��?�����$�G�D�����,p�Vl��A{!=�&{�5��l|�vz$��;?���w����4z��e���Av�@���M�~!��A.{o!;?����R��#��4���
��pW#���	����%�~A.�����{����&��?JvS����Gi��-:�:�O��}6�It�s������!�y����({v�/$`'H�
�\��'a^��q��+�����W�B����������=�x�,����9}0gV�������W��jgb^�v�/��n%���[��i3�/�1���s�|��u-��"(��|d2?"��]�,��Y��J�W�na����x�#�(j�}=&������E^�I��Loc�q%}}>J��O|�R����7%�F�'`@��C�D�!�!w���.^��4���)���5�K��t'��'�L"�
����l\/������}+� B�3@#�<�E~������F�V#U����]�#���X������c�9NR�W�bD��4aO��>T��~4��WQ ��jUo��	�@pGd��KBy����41��{�
Yh]�bQ�5���e��NU��{ti���l��1��������z�r������d�G�����,�q9�NKp��Bq�k.��F��O�QB�2�;�`Lk�H����������fc�/Fr���*������VG����*��.�	Z~&2�
|��T-9�����G���"*6"�Y����/	 8�H��4fF+X$�P��B�q��$C���?<
<�#L:R��`����d2�8-%��,������dYJ����i-xWr#|`b��m��r�Y��n�q��=N�w���i.W����"e�`b*4��U�x�j��/�yu���:�;���d���x$r����m���@.-���*��-d���[�%��$�(���K�L'�3�3��rH>�i�d�v���,�u��|�� ����!b:V���%��^�|��:���\'�C��N��\��5eE"
\D�bN4�	�����,��"����\'j(H�U����:T��:+{5g�\'kB����	����KVP{����_;* R`��0
@�[
��V-������_G�\�-r��Z�-F$��)��x���,~�,����:k
�b�I�B�
��)������"Q
�L��R�Y��3��B�*�U)�y��(�ip��"<��C�^�j��0���%�St�l�w:�R��1Rz4�?������DD)p��D�zvS����J���F��Q���z��YG�G���?���d��:P
q&*���O��,H� _�|d|X��Gp����U��1
���fO��y�Q�u�uC�\4�����%��K��8�B?"/���c�"��6b��@wQ��=���FY��(�����>�#7��I���$�fd;��&8���|;l��
����I�a�35����4���#�S��&�7��l����[�\��<H��Xd���N3�R����+����G��'����
uD�&���@����\�E��(����Wm�e�,����<�!L��*��&�����:�D���U}����w��%v��,��:�=c�����UFg?�]�cD�E���{O�����t�:�X4����g�V��&$����$�M�w� �r�*C�8F���5p0PJ�W=+J�,P��'3P�������l�Ut9��P^q=&�F�tg������l�;kI���2�V�����6�@��v!�O@r�@q���l2�St	��;�)�h(N��q��%�i(=���dhj�N�����g�:����%�Wp�G>HDR����$CIy�r?�x�v$q�a"��M�2�#jw*'��l26ic�P�Kv���2�����l��K��1r���������b��`LF�T��]v8)�lK�����s��U�k�F�K%#�)�*�mEU��S�e6����<3\�)�$@�]�.s�~��i� �+�����e<���&��M����6��?���Dy���2
1c�a�E%�E8�4��$Nv���N�,B=3�5Q�#�B��i4b��9������>vF������#KD�cqW�[�KD�J|��a!��M�/qB���*����2!��B�<F�	�����K���dY�%�,C?���I&�����H��(Z��C��VV��x�����	&�r*t(�l���lD������
Df�M@���&n���"��^�u��g��G���q+;D���<Uh�0<i���l�Kxp�������hs����@7e�>������&H�8x�$��lG�'��/�"�G��%�8���gG,7
�d���_Te��+<Xg��W�a������SNz�:�K���e�"�3e�w)��(�r���i��>?�`n��xE���/�_o=K��1u�h{��VB�|.)�+��[��W� ���o�1GIO�;���8����y��
�
�z@@6��+D�p��]�?��.U�N]��M{�r.>���dL~��V��%�/k5��6=B=�4u��v8z���`C���������
6��i�������/P���{bp�77D��Lx���8��{7
����(��)vi�!g�o�����������	uE���El������&�u;���\�UM��L�rD�5k	�3�b$j!{F��<��yR�J��g�Zf�3��,����������lFe�9%);�r5�����	�r�Io�E�V��G�aI�4����"����!>UO�-���L�l��{�>a��EqTnI6?�����=#�<��K>f���t�?#��V�o�J�2�7'������,��I���0Y���([�7A�r�0���&��B&w��-���H��Ug�~I#��%ap _z���/rF<k�������/�
����Wx���;�w9���SszH_�	���p�
��r*��|"� gl�_K��eu��mI���M����Ro�����/��6��^����6�����L��)p'�N�^$:<C��L7'�LVC��NYRE�yF^<���'����gX9�B^�L+�x�;J;�	��v��0�M��*O�fJg�����p�Ao���Z�Na��	|Po����<
���2��<���G���;���'������?��������e'��=4z��A�c
c5��7L��?�����f���Ng4'���\�sfA;��z��Z���6��/z�����F������W*���$���<�����J3z�� ����"��J��j��;�d��6�
]�v�)�=�#�F7-(�{�s���=����(wP�~�2�	{]@1#1�(�Cj��d�@�q!�aLMX�Nz��bJI�b���8n�7=�4.����~��t9�W�p��~2�f���T���I�����^��t���_{F��9~�����W�@�uD��Q������	L=��lF�h2���?m�u�������X�"S�~�%���_���N��'y�!M8Ef������I�k����!Bm(|q���z0��8��^����M�f�u�?�#�l��~���Z�A����2H/�5������`~$�����8*�{6��}+��\dl@q��'��$j]Y��c�[��n]�����
endstream
endobj
1 0 obj
<< /Type /Page /Parent 2 0 R /Resources 4 0 R /Contents 3 0 R /MediaBox [0 0 612 792]
>>
endobj
4 0 obj
<< /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 5 0 R >> /Font << /TT1 6 0 R
/TT3 8 0 R /TT2 7 0 R /TT4 9 0 R /TT5 10 0 R /TT6 11 0 R >> >>
endobj
12 0 obj
<< /N 3 /Alternate /DeviceRGB /Length 2612 /Filter /FlateDecode >>
stream
x��wTS����7��" %�z	 �;HQ�I�P��&vDF)VdT�G�"cE��b�	�P��QDE���k	��5�����Y������g�}��P���tX�4�X���\���X��ffG�D���=���H����.�d��,�P&s���"7C$
E�6<~&��S��2����)2�12�	��"���l���+����&��Y��4���P��%����\�%�g�|e�TI���(����L0�_��&�l�2E�����9�r��9h�x�g���Ib���i���f���S�b1+��M��xL����0��o�E%Ym�h�����Y��h����~S�=�z�U�&���A��Y�l��/��$Z����U�m@���O� ������l^���'���ls�k.+�7���o���9�����V;�?�#I3eE����KD����d�����9i���,������UQ��	��h��<�X�.d
���6'~�khu_}�9P�I�o=C#$n?z}�[1
���h���s�2z���\�n�LA"S���dr%�,���l��t�
4�.0,`
�3p� ��H�.Hi@�A>�
A1�v�jp��z�N�6p\W�
p�G@
��K0��i���A����B�ZyCAP8�C���@��&�*���CP=�#t�]���� 4�}���a
�����;G���Dx����J�>����,�_��@��FX�DB�X$!k�"��E�����H�q���a����Y��bVa�bJ0��c�VL�6f3����b���X'�?v	6��-�V`�`[����a�;���p~�\2n5��������
�&�x�*����s�b|!�
����'�	Zk�!� $l$T����4Q��Ot"�y�\b)���A�I&N�I�$R$)���TIj"]&=&�!��:dGrY@^O�$� _%�?P�(&OJEB�N9J�@y@yC�R
�n�X����ZO�D}J}/G�3���������k���{%O���w�_.�'_!J����Q�@�S���V�F���=�IE���b�b�b�b��5�Q%�����O�@���%�!B��y���M�:�e�0G7����������	e%e[�(�����R�0`�3R��������4������6�i^��)��*n*|�"�f����LUo����m�O�0j&jaj�j��.�����w���_4��������z��j���=����U�4�5�n������4��hZ�Z�Z��^0����Tf%��9�����-�>���=�c��Xg�N��]�.[7A�\�SwBOK/X/_�Q��>Q�����G�[��� �`�A�������a�a��c#����*�Z�;�8c�q��>�[&���I�I��MS���T`����k�h&4�5�����YY�F��9�<�|�y��+=�X���_,�,S-�,Y)YXm��������k]c}��j�c��������-�v��};�]���N����"�&�1=�x����tv(��}���������'{'��I���Y�)�
����-r�q��r�.d.�_xp��U���Z���M���v�m���=����+K�G�������^���W�W����b�j��>:>�>�>�v��}/�a��v���������O8�	�
�FV>2	u�����/�_$\�B�Cv�<	5]�s.,4�&�y�Ux~xw-bEDC��H����G��KwF�G�E�GME{E�EK�X,Y��F�Z� �={$vr����K����
��.3\����r�������_�Yq*������L��_�w���������+���]�e�������D��]�cI�II�OA��u�_��������)3����i�����B%a��+]3='�/�4�0C��i��U�@��L(sYf����L�H�$�%�Y�j��gGe��Q������n�����~5f5wug�v����5�k����\��Nw]�������m mH���F��e�n���Q�Q��`h����B�BQ��-�[l�ll��f��j��"^��b����O%����Y}W�����������w�vw�����X�bY^����]��������W��Va[q`i�d��2���J�jG�����������{���������m���>���Pk�Am�a����������g_D�H���G�G����u�;��7�7�6������q�o���C{��P3���8!9������<�y�}��'�����Z�Z�������6i{L{������-?��|�������gK�����9�w~�B������:Wt>�������������^��r�����U��g�9];}�}���������_�~i���m��p�������}��]�/���}�������.�{�^�=�}����^?�z8�h�c���'
O*��?�����f������`���g���C/����O����+F�F�G�G�����z�����������)�������~w��gb���k���?J���9���m�d���wi�������?�����c�����O�O���?w|	��x&mf������
endstream
endobj
5 0 obj
[ /ICCBased 12 0 R ]
endobj
2 0 obj
<< /Type /Pages /MediaBox [0 0 612 792] /Count 1 /Kids [ 1 0 R ] >>
endobj
13 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
6 0 obj
<< /Type /Font /Subtype /TrueType /BaseFont /AAAAAB+Helvetica /FontDescriptor
14 0 R /Encoding /MacRomanEncoding /FirstChar 32 /LastChar 117 /Widths [ 278
0 0 0 0 0 0 0 0 0 0 0 0 0 278 0 556 0 0 0 0 0 0 0 0 0 0 0 584 584 584 0 0
667 0 722 722 667 0 0 0 278 500 667 556 0 722 778 667 0 722 667 611 722 667
0 667 0 0 0 0 0 0 556 0 556 0 500 556 556 0 0 0 0 0 0 222 833 556 556 556
0 333 500 278 556 ] >>
endobj
14 0 obj
<< /Type /FontDescriptor /FontName /AAAAAB+Helvetica /Flags 32 /FontBBox [-951 -481 1445 1122]
/ItalicAngle 0 /Ascent 770 /Descent -230 /CapHeight 717 /StemV 98 /XHeight
523 /StemH 85 /AvgWidth 441 /MaxWidth 1500 /FontFile2 15 0 R >>
endobj
15 0 obj
<< /Length1 13880 /Length 8954 /Filter /FlateDecode >>
stream
x�{{|T�����>����f�������~�@���g"�	$@ A(c*|��@�T@�*h��,��������Jm�
����~h[`w�3wC�����������;��3g��3��c���(u!����h���r���YZ�p������������1��,�� �+&!�zt��e��'>������2;��n@^�1@����ci7\�|��������-hY��|�!����,h��|3$����!�(o��-i���b��0���P��^i�7!��
�����u�O^�6#��+�e|F����79�e�h��Wm�	���i��FRRch�Smh����BM��8��14#��O�a��"�Csk���T9��$r[
zE�@1J�-���r���ch$��u����F��)����!|�]���\B��!4�� =}�u�]�Q�j���ek�`J�v?�����x��1��d���J����?�����	l��[z��b���O�����C^��B)��C��]���<Z���)���:�\�p����!'���������"��u�b!���@����)�����S�bM{S�������M�~�����p��Xv�n=�Z���5;Wn�%D���J�}r@�**�\��K�lH��g:���r�J����t.�c�k49��!'�^�
��m��1��Pv�N-���?4*%���E�R����{S����5~?�'�,<$�%��t!E�	�`�^��Q-*EQBxo��?���
��C"/r!�S�dO�r���"+��P���T�
!������p��K|8�:p����
ZB��@�	����!�J��0W���Jk��S�,��L���e������
����`-D�7��o�c�q/4�V������P��ys�[=����V���#�m�`�L����E��d|�3g����5���Z���r����<�6wz��9���	�V�v:�=-U��fV.i��Yk�����<����>k�|��g5����YM�YM�Y33�gQ����+���tW��uS���'Lm�[�BxTV��wi��(��BV6��~p���;��q��6� �%S�z��T�����h�A<��4mE��y������]�DY�XB��k8}�A?�����"5���u=�F���L�P���J���$*�Q������C�:����}p�/��d�?�^B"�c>-oD�E{��e�
��S��\��!3*�����N�s�9~��E;���������o>�?az������F@)(
���6�ga����W��q��7�y�fWq�H��
�t$���h
H�:������ fF�t0������Th,pI9iE��[
����	��<��+�cx3~���;I��,%�1u�4f�{��������W����w�	��]h	Z	��A�Ut
30�{q��������cx'>F������?���/�u�51�t�A6�}��5��lf�`>b�b�q���]���o"3#k#���E?��l��$��JT�f��v���\�_��Yt�"�>�v���R@X��8�������v�>�S2-_�� :b"vROf����C����a�2=�{�y���\g96�5�#��h��}~��=l/�:W�
����\��[�������W���^���`�	�u0;����t�����@}�����L�fc'nA��]����"�mbV2#Ih�)�}��'�
����vF�g���@S���]h7[���0;���o~�`�w���.���J��)�;�;����������I����6��lJ4�:m�Z�T����(��S�������3jT&�=-P�rKE3�uw���>A7���n���s��zzb���gf��=����<��:��?��4��}r�V.?*���,Ip����V��fwu�������*3�������D#ZV�F#h����SU�x�m���evp����*�$5BTMl�gdf��N��z�g�#���LK-��LKc�4��t�A��*hZ~��
z�T���� ����v����p)�L��u���w��dUcC��'��8(������y�;��Tz���5�p���^k�*[� ��k	Xd$3��ye����9��e�ye,��b�o���y���!;q@�J�3��g���%4i-A��J@Np5b`��$�3�7�yG���o��V#�y^U��b��Te#�o������Z���+p�����o�i������m�=�+A�r��I���n3{���v�s
��\}K�T4���<��)�n�
�nf�
!�����o����r�=63c:4gPUk�����i��2�5���+�nw�����w(��shh�n�	�7���$xb��6Plml�d�q����#��r�*;�r2��2��
�]U�`��f������i���F��;@)PL��1�����4h�����.�����Y�������m�t���F�+�!D�P��p�x�2�d��@�H@V#�i��M��M��p��pg1P[$K��;�p�����o%��Jo�p9�\F%<�'�a�I���K80@79�
����$<��H��[I�z���$\4WS	���Ix�m��%<f�n r,P;F����H���F�u�J�wPz����wP	O��Ix�m����4@7y'P;I����H�S������(�M�S��F*��$���v�k��E��a�v�������0T����"o��D��mD>�[�|����|6�<���������"rN�*I)�d���`�#;�4�s��b���A���'!(o����~;_���6(����IP���4��&�}e������Z~/z�h}'���BJ�	�.�4CH)$B�yr7��_#W�KB�����X�8�^�{)����,��4���	�r=J@���l��������lQ�z9��i7��%!��^���g�AL~��'��kF��g^e���b_���.~	���"�g�w��$�ze�r��k�D���r��q3�z5j�@L��p��#b�bv��6���C�����3�+>D�e�'���849='7_'��������s'�������$�I���p��L&+��,,7[��
�PuWk�u��U��������|����7.��
�5���s[A�v�8`Z���X���8}	��\�r�th;��������
c��@������Sx9_�����O�6JZJ&�1'H,J{
����� L�z5!S�N+����
����HW@<IDg0%�3�������>Y�^���w-=����Q?��^>y-�����||�]��5�.�O���"�D~��U���ad������y��f.��a�[�0�����<�Y�u�9�gx� ��0�9%�0K8[EYq�4�g� ��_��u�R4��B�7�f�����t��tH�/77%`����p��y��B2�z8�xd�a�K��4�;v!S$��?`�n�+������ETL�*&�|T9����aP�TH�]x��M���������v�*P���aX�%a(K��9�1F�\b`S.yd1�1�V.M�&�r(�r��|;�
\�=p}!s�F5����p��H�+'��
�5@`
W�w	pl{���<�v1��0��
�Vp��@��V(h�	��,�XX���������U�����O�F������Y�F�Pd}���En1wN=��`���=��p����jnmW/2;t:#?����N�tZ�\R���Y��\����)��[=}u��k����K�A"������!�n�&�4^�O���Cqm�����01y�Q��y(^�h��0�!�Kk�������V<���Dl�daO�i��y�E�����+D[\$yX'.�����~���/>�g���ucO��(��������r�����"�G�E"��o�p��'��
���~/���`nf��������k�n����*g<L�Is��8��b��+u��iA�PE����'��F/p+08^���DHzUp�%/�4���r�B-�H:Fr��7$���;��Y�?�2����g����-�p�g��������X���o��;y�O'#�2`��>��z��@��dY��3��T�D5Q�	��I���aD/��iBXuH�|��r:�W/���Y-�W����$�����f�����x������N�T��hz��pa8�Cx# 2�WP�e���.�x����������+�lC�'���'��@����sU���	���
�'qY����F�W��!<���n���Hm0�����
�I��8�hH��R��g�����?��{����s����/����t�����(��a��d�,�9/�G	
�}�f+���?�]�.0�\������'�`�EN��M����8
BR��gY�q^=Tq,�SA�W�a�,��R������W��Z����Bu(�����
�[���5�QE9LA9XU�3���k����s�����v�����i�r�<7-Y��7�X���t��g�k8!�c�	�#>��������'�D�y����3�r	�0��r"c0�r���K��C�4E@](8`���<�rcL��z��P��xt��0�����x$nqaBBx[���yX"H�r)��"<�~��V� �^J���;���D���v���4���PdWg��^q����t���?��N~�?E�p'�Oew]�>w�.�����G����=��wD������;�A^_Q�#D��x�6DP������2V�9Weq8�dcK�� Y�����Q����G���y����������D�y4�� ������ZD-�A/1��w��l�8s��S��������������._�E�B���(��}<�����������������Fr�a�����A	��
����e��zv����fXn`���j�XO��R$�^�*�\����aL�7�#L��nhA,`>0]�	R���`�<���L�+*�!������E��3]K+��t�u��N������&��W�Y��	�@�RY�����^��Gq���k�f�rb�	x9cA6lTz$��Y�X���;]�������O�:�Ba�*���D����&2���6k��������w��1���i�W������1vJab�}�������M��#������.�hJ4�gec���aF�{v��9�y�g�����<��>��R������#e����?.uF�dg���C{�=K��MJ�1H	���z4���4G�a���������x���+�J#}3}�5�%+����4j�4{��fHRr��a���
����!����!J��KNr�����^�h��b�syu��7����C��������:��-���|V8�i��kS�t.$�ezy�3(ef��&�cG�+���tl1�L6)��t�U�,(��8�vhL�D^!Z��3�k������Z��e#S ��� 9?�5z���x#����0Xl��1v
��-��;v�3�1a�_�q">����'��]���	�"?�s�/��1�_\Q��=l���<ofF���/E>������f��s�d'��={��u���UQ1>
6g+��H ����]'��c��q��:�����<��� �M���:����Dt�Ft���k�4�X�:������itW��8?W�e��pC��D&1�����f�5XL������M#U��qD�/���g��Q��V���WG?YY�j4�����c����f��8�rc�k�����R���e�����;0	�I�$b�����l��e,)�K���6���� �E�
K��8���-�z�
�����pi)�Yd�P�Wa����^>
�1�bc��Lxn��B{VNh���/^� �%�X�w������#���5`���?��B/��"2�La��s��l'Y*���*���s%bD,{uND����W@��W��{���D�w��/J�Jbh������`p�!�>$Q~�������:����l���{pp`g(���4�M���d1_'y8_�v�%�H�?�}��/�@��G:����M���x2
�FQ8���������l�Q6l�u���Pk+����op���|9�m�$-+�*�����mW��
��^�`lyB���U;��IVj��2R����kN���L��Uu��#KE��r���nR��$[��R�c��%���^����9����M]��\
�����`a'JC
oV_V5�`�e��RTlLB���E�2;mJt$,%�b"!��$A�	�%`|c>��e�1�9�1�FL��`;^�������F�������WXPT��5K�f4n����������?X��2I�����';�5y�N]Z��)-QQ���7�<�x��S3F��`���8{�\<_�0gN��V���Q����'1�*5_�	����5���/��o��l�|��;��������{��Q���0�a��M��LZ�3~��:�~���=	��%+��2���������YD����!M����,�A�&�q�*1�q$
3��q��o�6j��Pb,j��$�OEG`?�/�w?5�h���?&g��E?��8�z��kX�����7�{�������9��?���p ���E��;�����kFj�hv���8�h �-!AI&����M���.��jq�VKK*oe?|�
�OW��i��lW(�f�f�Y�)m��?����*�
����Dw���-TX���z��;w-_�<���z����E�}�;<��{���^%����qm��Y
8����Xo��Y+|1a��oXX����u���4$�35�x�!��T+�:���/1�m��(�u���b��Q�����i"'%�?��H.�!Qr��#Q������gg���^;gQ�����sVr���X|���]M1�/����0����;�M����j��W�<pR��`��]>���	Z�����$[�������I�!�Q��q�U�*�z��$�	����6����h-n�*?����H�~
�W��P���J�!�8�nI�^{����8)�pP�#w���_��	��]��1���a;��;'�������F�m�o������TF|��<x��k�Q����`cg�$1p��W�S&y�;G��D����Q-�3��^v$|��*P&����7�&�)�/�A��,�UsUj�Wiux,J�����������^&A�^R�
_��^k*��1���M�!�?%���B�%����75������)�c��� 82�u�&�t��yt���F�x�M�\z�7P����.#�������N�];�����Esvf����<�"�fr��I���������
o '��}��0|BL(�L�����@�Q�<OX���
�|����`�:8`��RZ��SV;�2�Z��n���3Yc�����'�1D�=�-�P@?4���������������1%���x7�?}��S�	O ��,��XY��=�:3]���X5�Y����������{�]��C���{�e�
�!"�0j�^e,#��[m
��b���,m�J�m�|��d2�5�O����LP����4�}k!��2H���&��@��j��pF�������r�n��g������6r���w��][�?v�w�%�b����H8�S�~�]�������X���G �yq����n�N�;�B<�q�UI�7[��Y�,)5)��I^-����)f*d�@'��,�m���X�c\"$���!�$�$sDw����	��n����F�O����qp�:yi�����j/������]�?9�����9e����f���'f?y��]����S�#��2��9:�[�a���u����������,��D��)�~���N)Z0���O�jP���1�<���g��K��������A����.����?��3;�6����T�m#�)��,	o���2�s�__f����J[�-�'[�[���d��H��F&��ON����<��L�,n����������r��H`��r�lV���Lv�1����j�(��K�IqJ6=����� ���'�U��B�%����.s��o���)�\�����Y�����&��.�p�J���lH��=��&%�����g��N��$����1Dx�A���:��,aw|���$M��WJ��U(!���[{H�:�D����,v�+����^���&z�#�y����y��5�������j���C���h�������#�^�7��9��)u���l��w_��Q<~j��)wU'��LJ����Om�7�.P�fIpdgT?��� �]2E� 
n*X��?��R����x��R�k�:+�k��35�x�on$���
in��+�Tz���hc�_65���>m��|�o�b�Y�^�W{��=G���s������6pS#�l
W�$�0Y��K�m�c������`=�����!!���H
��!���yOD�i���Sr`������,e�Ze��TJ��7]J-5^T�a�cg��T!b��mQ��X��~���K�9?x��{x/���t�>��K'4���c����SC�&=1q-y�J��|AL�����H�]���p�e2�R?|���2�*B�p���
�]e1*�/_�P%�B5�~�{����D�z�Nx�95��cxK
�\<�5E��U�>�u~gkG������;����|p:�[M����r�q����`;�~�^��s�����P0`�|���l���k|���4P��=������C�����#�U���A88���3;7���1?�8��O��9�<���y��N5����5�=������j������
j_8_4_2�g�1����V+ 1
endstream
endobj
8 0 obj
<< /Type /Font /Subtype /TrueType /BaseFont /AAAAAD+Helvetica /FontDescriptor
16 0 R /ToUnicode 17 0 R /FirstChar 33 /LastChar 33 /Widths [ 278 ] >>
endobj
17 0 obj
<< /Length 222 /Filter /FlateDecode >>
stream
x]��n� �{�b�Kq_����Nr���`X[H�������s�Rl��|0���O=����0��3�qc�0�Ht�����4��$d��}���4E�Z�����w8=�8�C�^�#���y�2l)}���A	c��T�{���.����?��\�����J�Bt?�\��&��-�(�RF�nF ������tF�Q����u*Z�x��6�������}U)��`�o|WpA
endstream
endobj
16 0 obj
<< /Type /FontDescriptor /FontName /AAAAAD+Helvetica /Flags 4 /FontBBox [-951 -481 1445 1122]
/ItalicAngle 0 /Ascent 770 /Descent -230 /CapHeight 717 /StemV 98 /XHeight
523 /StemH 85 /AvgWidth 441 /MaxWidth 1500 /FontFile2 18 0 R >>
endobj
18 0 obj
<< /Length1 5620 /Length 2859 /Filter /FlateDecode >>
stream
x�X{pT���}�nH�	�$,�n��k!�aYvCBty�&���D2R�Bw�,H������
�l�	"�U�����>f���td������w�&+a���=g����;���s�}t�[��#�j�E���,�_)ih��'��O@s:;��,�.�hj_������Z�f�`�L��4G#�I;}	:����n���qGR����fm��}�
��-r����d��H[4�?�1���ko��Z��.:���D�m������@�����K�ej�e�c��b�5�/P�h�������u��E�,H���9if<nA?KQQ��|��)��A��K2��Uv���t#��F���f�#�e��4m��N��i�B7Sa��NaZ`j����\������N�D��W�Z�~��<:U��D�	���e�{�v�=�&R�N���=�&����z���d���
���z�%eqv�b����3����7���`9�\�XNb$���e�P#)�7�f��
����5J����C�+c�&)�X	�%�>�4Nb����J��t�%�3���8H�k�>���i�j�ZW�t�����5��q:��P~�����$��z\i+��4��������P������L��Tnr|�L(�mr��F).{I��pS���R�:v+�`�LC;���T��'�s�^��nwU���:�������������{�*�E5��������[��ZgY'Y=�Bk��i��f�F�2m��e�F�l6���L�T,'X�,]�6�M��SPJ'�aSy�i�dld������
����c�+�9n19��w'U����9�4d��a�]��.0�@sIc���z]�L��Q3��V���6-�W��;sh{����!GH���t�2�J;���y<��6tw��6��@��������v-V��G[��A���p}C3��������V�_=�i�������.�Qj
,m�F��Nog�����}�Vk[j�u�o�����c���.k7���V��V������X|���:����j��Z�
�������	�u�8���Dre�'�P�Q�4�"�M��8�_b|$?O��m�?�r�=�	�3�S�G��Y�	�����Y+n��t��`��z���������j����Agh��i����dnc#d/�z�b������N�TD�I���F7��h	�.��s	G�k���pF/D�-��b�Gp�����v�bn�-���T���G�=C����1���4���W�4��P7�c��xD��x����$
���i7�����1b�C��v�=�W�K8&m����p�������=�,��>g�
v1S��3n2�M�T�Y��D����;1���&����mb�`{�kB��D
?�>����
�k��RB�!?`I��`�0�7^�1x,�J�h3fw����&"�X�f���V���~��`=B-�cg�C�]�>��]d!C-x�a��%�^[�=�����i�,��-n�������_6��s�g8cm����h>��f��G�O0���G�j��s��Y�gc�<}��b�l��:�-`M��=�zQO���W�BiB�0F+�	�B�^bb�X,���GP_�/�%Y�V-���h��&�C}\zBJH���3���R9&o�w�
�+��������S�?p.�X�Zw`u^��}{��"���~�F
���i/V��P��������X!n��N�����hm���/�!�3v����o%9�_bu����E_��X�G�I�]����������)*�������T�L���cs���kGee��H�f�ZdI�\aU�kR��������Ka���V1�GSy�LC<��l�������<Y�:���������w�:[�0�>�+�j�M~���2����NtP�f�����*:��������x���~�x)��hvdN`��=Z���r\�a��H�V�0��9�!��Z��%-�������������,jb$�	a+���q��1?�-r��5�]��+4ox;��b�K����T�����:�����t�
w�U��\>Ws�5piQ0���5Og�j��o�)����7�;1���Y��8-w�7'��w'���qj���{���R0���
yjj�9��N����7LN(!�i� ����=#�5�]�bu�i4����[����\�)��?������K�_��<�:��PMd@cqg^ n���+����i������f�����Bv��( shx�Z6���A������fI�Ni�������������wlq�J�K�Vk�c|�%P;�]_�V`�
�W���j��j36��6)�xh�'Z����
��!�]�!B�@PS5�+8M,��V��
.j1�����
��}�A�;7�WY*Sd�_��9OB�e��������B��q�.�rj}�x^��oIYgt��;����p�u�E_�3�\����B���w^�����T��y3��l"<�*!<u8O���L� <9�s�o���1��WF���I�B�^a�UBx�p��@*�!W �Gx�w�p�������T�H���5��J�������T�C�E�8��;�
A���/N��$� ��&�K��?��a!Je:�e�9��5��7O�K���e�.]��y��W\���� �UH�	y�*A�����!����s�<������Q�������
�|�e�:S��D�	}�wx��"Yiv�G�m�N�-S':��e��;�A��"h�;��^DK=��$�N,�!��U���v�_�U>��l]�w?^�a#���o*���i`�"Nfn�?L����S]��hi��'i����Xc�pE�g�fA��0��
endstream
endobj
7 0 obj
<< /Type /Font /Subtype /TrueType /BaseFont /AAAAAC+Helvetica-Bold /FontDescriptor
19 0 R /Encoding /MacRomanEncoding /FirstChar 32 /LastChar 116 /Widths [ 278
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 556 0 0 611 0 0 0 0
0 0 0 0 0 611 611 0 0 389 0 333 ] >>
endobj
19 0 obj
<< /Type /FontDescriptor /FontName /AAAAAC+Helvetica-Bold /Flags 32 /FontBBox
[-1018 -481 1436 1159] /ItalicAngle 0 /Ascent 770 /Descent -230 /CapHeight
720 /StemV 149 /XHeight 532 /StemH 127 /AvgWidth 622 /MaxWidth 1500 /FontFile2
20 0 R >>
endobj
20 0 obj
<< /Length1 7464 /Length 4007 /Filter /FlateDecode >>
stream
x�YtTE���{o?�N���Gs�����I�$�r�C $��G7&�MhL����<	���,����;..�M��UY��p�3��*�8�B�YaGH��u;�	���9���S��������Wu���^��~��&��e��w9()k+���=��p;���t��5�������{��	����E���n���@8���.��&��.R��8����g-�R��.����#v}5��z����3�����#I)��.����ViBV.�)�k|#�H�	����B�K$R&���hE��T/nJ�'��K�i�/���X����������l��'�(e<f�>��`@,��������
��<��
�e|rA�H�2�*x��
���B)G;�a:�@6��t{
r�v;�o�=<irnXap�� �T���2�J~��V:�� �����1���?���4��(W���8���R-vn���W��P! ���"~�3bg������W�RT�U�X*��hQ����3�:�k��Kk��;�{�b�x���~���G�Mk@@)A|����\E�x��J��I�jIV<(�r���E�"��p�_|��������p��a��U	�zP��3G�k���:�#vX��vk���6�6������O��,�_�������Y���a�/�����[JM�����/|r�Xh�[�����7��[���xU��ebUXt�_�_�,��%`��l�#���/n!�o�!���/JI������J9��\ j�h�ij&k
4y��Y3A��I�&i���Xm�V�Uk-�mJ ���-�u"j�]����:]�
j9�5lK��1�$��U�;���Q�G�7��M���Fywc�K>dt��X%dt���5U_�nl]7����"����X�>*yg_�A���d���L2��Y�����'����%��4��������[-�X�\�X$��V��i�:��-���1�vDb58�%��
��b�(�n����-,V3���b�H-J�����:�	��^�"�0
u`}@�C&�m�+��!:4�ig�lV����7�����6���!��$�/4b>�� |��f'<IW'\�k��}���S���@��v��QE{�T�,�>�z�/P�p5����1��)����-

���������B���4~9\@N�B�
S��v�!x�h�|�+t�����BM�>�w��&B3��h��9X���,?�z��-�l��r<{a?V��
Y�T�A�g'<�����a>�����Y���N����'V���a/�#�k��Up^��O��"l�!x
^����.�|�(�8��?P�?r��n�0�w�/��
a�j����z/�a���N���>��a�?����=0�&���Fc"N�R���.����� �3x?��]2'rn"�G�6s;����;�
�:~-��?����	�B�p��9U�j�:K���|<x.T��G��F�
y��������frq��3�,�����p~G�>�+p�f,��'�Tl����{�'������|����� �sQ\w���Q�{�w~by3����|[������`��	��~a�p@����Z�Z��U�\uT���?UWTW�F�v�A��;�f�f�&�����68/�����R�
3q+��"�-��!xn�_��
���l����@�!���9�o��~��	��O�Q8)t��*������V������@<=��lJ�y�����m6��Y��}ZjJr�.1!>.6&:J�Q��C(tZ�=&9�#9���"��xI����&R����M���Lc<%�\~����"��h����B��b�O9,�.�����:,n�<�����.�Gu��:���.�IF��)��u�;=��B<&��"���H��a�w��0�y8���)�[�N6���.�[����L��]T(��N�R,urB�Hw��vH[��b��	?��]fY�3 �R�y�]2�u������z�C��������5����2g�����e���HgMky�Vc������]2n#p��=|�w����$GY�,]�+=�9���R���q�ehu���t�QTx����L�+�-�e��l���n	��I��� ���Yl�l�)�:)qAX���o*�wN%�(���r��)s��x��������F`x�#�V:�Q��K��M����i4���h1�	4����c5�����%0#������u���\m���2X����)SMm��9JAm�R+�_����jq
 >�`h[�c�������l��pP8j�"�L5BPO����0���g/�7���hI	6E�����H����X�2��;3R����h�b6u!�~7��rd��j�09M,l���iq�w�����p���O����~�n�*� %���ws)a.�'���(��g
���gc��,f�Df?{��m��|�BQ��0��������3��3�r3N'��z�o��3\�M=�	m��p�bx�x���"H�0\E��1���;���ax�����&���Q��C������b�A:�a'av0���;�g�a������&�s�l���;����0�4.��#H�0<�073�[�;���a����Ep���Max�bx�x^<.�]�cvfcxI�a)S�����[�]���]c(o�=��!�w���{����P��K#H�P�I��2��}����P������B�E��U(_q�(_9�0.��#H�P�C�����G�W����+^�}\%�������*����F��RN3�s������`����FQON��Kt��
XRS�(5:s�|��47�i.��&-G� @������fHf��H�{���C�J0�<�����Q����tR�|��i��9�ZuS�p5�TW�RZ2Yg����~�yJ��W3B��gYp�Bg���=O���Qi^WV�w�pz���'��Z��Tt���Q@��BNqNq>S�1��i��DM~tL���i�M�T<�,FNFuFSf���,����1������i��Al:��<u��D��!��*uI����Jd��������Tk��)i�'�W��+����X�5j����I�e�K�O@}����i��)�RQ^�����(�����Ok�?</{z�_����P���0yp�N���
��W5��}�?�X�O�\�.5*6Qoo�]����������'�jm�5k����)s����������y��%���.������:A�
a�Tx1	��K�AoM�������T>#YT��]�3���'�{�
�^+�W���*R�jt�:���: MO�g.�GK6�%��$b�N4(�p?�W�������������}3Z7���uB��&N�������/P���5G}}����a�NO���>
��(�0mX����5&%����4M��W����t]�=!�����e��I�ZW9DI�m���������u
B"�AO���l�������=V�<�����+���S����E�������t`���=9<��>�>��CO'zMR��Gm�C�%��8C��8^e5�hb��1��+�gdh�u���474�bvHW�,��!���g
/$�A+�MP6�]S�����n��}��8x9�)������S��y����������gp��/���?_�$�q6�"�!��_3s�����8ugI���8ul��+�$�O��3��{lzJj�
�[��Q�|�Nu�4u@MM
�}����/�I�"�QLrf~��V��cb]i�&�1x��zk�_�^���I�������.��9G��B�q���Nj��4��F���$��
Qv�;�f`���� �[}~��j5���&e3���YZ�\�����]��L���9e����10��8����9%|�[X|���3����4M������b'����_g<���s�V��+NI#{�����,�,h�u���������^�������G
�T�k�l��xh$Q"u�-m�-m6�h�U��W��^sK����J2��
endstream
endobj
9 0 obj
<< /Type /Font /Subtype /TrueType /BaseFont /AAAAAE+Helvetica-Oblique /FontDescriptor
21 0 R /Encoding /MacRomanEncoding /FirstChar 32 /LastChar 116 /Widths [ 278
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 556 0 0 556 556 0 0
0 222 0 500 0 833 556 556 0 0 333 500 278 ] >>
endobj
21 0 obj
<< /Type /FontDescriptor /FontName /AAAAAE+Helvetica-Oblique /Flags 96 /FontBBox
[-933 -481 1571 1138] /ItalicAngle -6 /Ascent 770 /Descent -230 /CapHeight
717 /StemV 98 /XHeight 523 /StemH 85 /AvgWidth -441 /MaxWidth 1500 /FontFile2
22 0 R >>
endobj
22 0 obj
<< /Length1 8748 /Length 4888 /Filter /FlateDecode >>
stream
x�Z	tTU�����R$���T��T��B�$ai(BU{ ,�H$	$$H �DP�#
�Y����D�H* K3�����=.����N=�����$5�}U*����p��������������U�\���"��D*��i�'���1����f9���ZVZ�e������������j"�i��5������7��,���u�
`���6�)
M+W��1�Ho[�lA�>���M5����([��4�!��o��n^�b�Z$��HG7/��gD�}�����$�����"�|�3�^�km��������(�Z�?�a5����1��]O$,	c�����S2z2�F�����h�u�]��/�g��$�.���N#[L�(�L���������y�F������E��Q�Jc�����U��>�&��L��;�qv�$����W,�����&fyi,h8(+k��Z���k�� ��Z�z$��^D��m�����l
%�I��yVl���`���)G�4_6|r�%@���
�cO��h!��sdg�P)���;3���Q�"5�ZA��d����a�S,��C�TJ��Q��y9�����0�M�JH~���#�|�����i����_u -��_4-1�H���;����}���U&t=jn��e^���O���;�#P?�1�\Pd57}j�M�j�9�)���?�S��,���2M;�#Q�lr���`�^�d{;������r;'f���{;K���^v���4}WFi�=c���Q�������
��5�4�4Y�tM���I��j��z�@m�V��j5^�R�X�r���*���S�he/{L�;�2�JZAK�X���#�|c����2G5�x�A1gt�%���
=���>��V�I�ay��2�06zL����=����3��/3yvM.��0�=�x�gr���MW�BU]qV���k:[����l�j��T����`���Z,�7�
�GL��]����:O����YlsZ����U�����0��fU�w�9;Z-.[���Y[����\�{�Z^��*��-�s�����U��k�\U|�*>W��V��/��X^�b%���j�l���{&����Xj�N/{L��3��N���*'R�dF���#�����s�w�M=��%u���z��c��C���B������^��`��z���lyi
��|�-_=��W�Y�I�)}�hj�q����Z�@��R����4�n���E_��A3i6�v��l��#������sj��)�C�h�F8+w���W���FA�'�)�G��/i=;�|-�K��|���q��#�#vH|@z��7_��	31k5��g1�!�g`�.v'[�v���!���x��GA�������&:������3zq�x^��/���U���Q�qo��N��������e�b�?�La�P!�-��
��y��O�
�C�*��(����]�����q;-�uX�Y�D_�w$2����b����l/���.V&�a�����v�}�d!\$d	+�B�pVxChw�����H��O�L�+���l�y�g���'����L1M��T��6�m�K�� �C��9:K��'dd_��@�X4Kd��T���tV����8�S�,��#�&D	��Q(j�&�Ux�$f���J����;���e)F$M�&J[�Iz���~`��4B#O�����fy��@|K~G^�lS:�k�(��)�e���#��,!��(���������]4��c5�
���6�f�NU�u�q�p�u/��qX�f�'���|�wa)K0h+�@���v�&ZOCaE��(�%6�^�c���e���D)���)Jsddf����Sl������C|�����(}D�]�V���(0�v�J�-��j��j+-��e[
5!�jx��S���������OKZ��k���t��dz�h��mq�,�?8m/��Q��CN����J�OU��V��[��`q����<%-
m�jgN6�r]N6�,��������4��pymN�'��<�D��f��lF���d���kf���n�@N����p��A��<W3��#��=B5+*�osz����p�����Tz{IM][��Q���b5/�lEir��
��1 �q1$����{�b�'�Vlkh[\
pifEG�#Q��*��Hp$����.��QV��+g\�8�����������O
��}�t��^G�6rz,�Il��?���mAp��fXf#��`3��#�'�xZ��b48��U/vv�%$�{T�����#�)���,m�`3��}�e_NM�����������(`7-|/�c�
[�o��S�mWe
������������a3{����*3���e��^r��p�����lnj�N��BN6�V��d[J0s	�K��m��6K���$��um�\ X^�hft��z�un�H����A4osc�������n4���VL-��Q�iu&yN7��=SV�9�u��*�WRH��~��A��L���G���C������6��L[[R�7���?�`x�7��{Yk�"�Y�TXmV������Z��?�pA���YiT��n�#n��7���^I� <2�����CxL���4��^�!�8H�P.�E����7���W�>�@fGx���pi�'�4��z����!�$�)���7����Bxz��}.���9�3~>�g�A�����+7��
ig���E���+n
aw��}���n����;�<�[��]���y^�8)��T,�����ZZ�.���*��Pi9���[�������W8^��FO���9*�����D�$�/���#Le�hd�_�T�R�{��G�'�p�iY�_Ub��PD.�'1����4/is�$��z/�%/���vt�k����9�a��+�+�
/���.b��,Tq�Sc�]$1/�2t����e�JK���?�O~7�+M���4���K�D����w\���f>����g��&���;���$��(�*M�-�
z����(�j���p�� �
�i8���Gx�����e��q�$Ta-2P:�T
�5�V�6����������*�����p&���x���44�>��`,�"����

q���d+H�����a����68-��'��cX!�:�s)�����u�UqQ�Y������&g&��e�gO��[~�D��������*c�<���v�6Y>��*{�������E�*�Xm�!g^������--����8X7�+�6G��W6��M`�e�+r�|Vg��C���bF�0�(2�3�B�q�Z�^2B}JP}
����@}
��@}
��@}
��@}
��@}�s��)P������	�8N1�W�_O����c��M��PX@�����ode�w�����5����{.<c�uzY��
�X�_�����^��K����������1;T����������+xe��/)�A(����Xc9#��x�L��-�\8w,0��C;��9�p-�C���9�p-�C���9�p-�Ct-�C�:G�R����D�
t�At�@�
t�@�
t�@�
t�@�
t�@�
t�@�
t��fh���i���a����`!&8��ipE#�o������E�Z4�v8�i��_�f^|�����x������
����%��y�|U��Q������o=+��g�����m�
x����Pl%�b�g[��/�X�����}|��>&e~��|����V��ss�?��Y 8���"QQ��J�W�8C��!+��jyRoN����0f5��{���{�'����d�A���vv�gYw�`l�Y���u+W`S��J��2����n0"�H!BGsFt��I����s�=��hR�U����R�b�<��a�� �����X!�z��W��11~��F[�
�	X
�X�a�1����q)��w���cH�}��������6{4>2������G��=�S~16�dN���u
_�Y��������a��|��"�+0����K��]���|�rD7�����!���d��d���^���� zx����7��Mz����kB�������n�T�h�tH��G�=<H����=��(?�n��;f>5�������F�����M��}��K�4�!�Z�
������zU��������`�0��%H-Aj	1@B�$�	1@B���%�I� �O�V:���J�h�C��!Z��t�V:D+���J�h�C�����JA�R�xT�.�:X(��$Z�b1������g;OX��j��|pn�H6��7?2v������OYd{Ow��,�:�fG�����8���D��v���iC������^;�A8� <h\3���p D�C� .^3��lI{��������O�o:��z�*�+��D����=jX�{W�"M�!t9T�1\��)�8#�k���p��
���$�H��$�H�������=�
ztt8nI�7/�`�xud�3��k2�����v#�n����&#��k2����&#��k2����E�WOL0�\��#S� eZE|����d�H�(�
$��_�Y0����EuF��>u������X�����m������$�|��k�%����s���J�3���x��W�U6�b��i����Sn����/]~�����k��r%�Q��������r���9��!J91���8~��R7/
�y2N
�"6&1�l���Zc+�g�u���w����H�lu���{�y�\����nWQ�&l���H8����}
���/���S$'^�i��?"��j���m���g��,�/WVi������jr��.i����������*@
���M n��A^�E�{�/@�����gx_�[��W^����/�c��;����e���;j�������+��WV�g�>0�v
endstream
endobj
10 0 obj
<< /Type /Font /Subtype /TrueType /BaseFont /AAAAAF+ArialMT /FontDescriptor
23 0 R /Encoding /MacRomanEncoding /FirstChar 32 /LastChar 213 /Widths [ 278
0 0 0 0 0 0 0 0 0 0 0 278 0 278 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
722 0 0 0 0 0 500 0 0 833 0 0 0 0 0 0 611 0 0 0 0 0 0 0 0 0 0 0 0 556 556
500 556 556 278 556 556 222 0 0 222 833 556 556 0 0 333 500 278 556 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 222 ] >>
endobj
23 0 obj
<< /Type /FontDescriptor /FontName /AAAAAF+ArialMT /Flags 32 /FontBBox [-665 -325 2000 1006]
/ItalicAngle 0 /Ascent 905 /Descent -212 /CapHeight 716 /StemV 0 /Leading
33 /XHeight 519 /AvgWidth 441 /MaxWidth 2000 /FontFile2 24 0 R >>
endobj
24 0 obj
<< /Length1 16728 /Length 11623 /Filter /FlateDecode >>
stream
x�|{`T����������f���f��y��@���`��$@�@�(��`|���[�7j-�i@�%>j�>(��~�U���6��EE��}fv��o�������9�����9��9gnX�f]1�N"u����D\�W	�7/^�6�*[<�h�]�����r�
B4_]���+Se'!���m�KRer����H�i	���+�nH��M���X�8����������'����ue[��8^^��cm��C��zM[�?m �����E(z��8��]�(���'D^*g���a������m����������9���z��=�W)DoF� ��p�n��2Y!g����"jx�������F��y�G^�F�c&���g�KyRV����'E�:2�lU���X(��U�=���,���� ���h	A�[C�U���c��%zBA�*O��^/�`��� 	�Hi!`&`!`;`'@+���U�M����-Q%w������6��]��H[S��fQ�{Yc*�>;�W_��6>�mLI���I�<� �;�E�x�^�����%�0I�)e��$HvI�$`�*jT��77V���$*1��%$���h��^TedIv�8H�}�R-l`��^���R��8��G�����lb�8��VvN��~G���}Hl�R�,����H�>��r����H�L�/Hm�=`�������=e�E�/L#�hq����U������HpT+
�zA�!I�����<=��}����xpW�h�6IF�6��6	fZ�Z`�{�tvvpRbo��CFT�,����k������`�������A�C�?E�{M�o����u��h����$U&��� W��]�^���&��� (DZ��,lh�A���$��C^ o@����|!�'��z�.���`�Ob�/�dghg���{@�'�;�������I�G���$�b=0���,������x�9�>���r��e3���*�T�T�T����Z�#��|�����b��������E�9�v>J;�h���s3����W��8���l�����8������U�|�v>G;;hg�vFig.��2���{.��!���*.t,�����>6E���0t�A��IQR�)������y����T���E����Wp�+X�W�Q��zl�
�
gCZ	X��$Z���<�����P	X�8�����PY��q�X!�J�L^b����_���,%�������e����lVF\.(f�]o���}�X���BUv'�N��;�������}������Lz���u���h�8�!��$���%$��E^����l=���j�w�~8�"���~x!�_�>�����g���|��O��c}�����?0.����f4<���g��7�����T�(�������T<�:�(�v������+��^���}��B<��c�#���l����>�T-���k������
ta]P�����z�^�[�f�Q��k��������<��������O��D�
C��AJ�3r)IdHu�n�$Z��_L����F��q���&2�&u�n����x]�.9'Q�K�f]��M����M�m}��k��I^���pLn�O(�o����[�hl$��JO�c�����_$-���:�����{�Y�{��6$��jLq$��X����PS�~�5=YS���7��K��5sx�4��������H��7��c���3�GB��T�S����ry�~��~Q�A��)����[S���}�!�!�t�C��y#�>Q$���$o�>o�:y��D��@]��������>�E��[t)Lw��\�[����hD��1�c�},���<B�{�mR<N�Nh\�T��i���Z��_�It.
��7��PB��,Z����m��H[ubq�:�=A��O�M�yB���4��k�nR��{&�j"���{��*)��]��{W����Y�a%�]S�}���2�<��������k�:E������'�'c�x�������q�KY=Q0����F�X+��)��0G&%,����FU�&�o����n��8!�?@w��T�#�H|���u�S��:������K�J���_^�R�P[��m]���[������[�CmKu#����L5}��T�E��;J�������������P
������^�f����QJd��cP��M����DG#&�A��c�i|'��iw��ui,M���\t���x�0I�������8T���|���W��?����|hY�s��s�%]_�M����s� y���]{�~�K�	TM~F6��������V2?
�J��^x&�`�|�B�����qQO���l������!UdYE�����H9*�D��4r
YM;�
�;�w''O���&�	��b�%���9�>�;�!���n��D�[:���d
yPj�i���� L��d2���,�������n�&�)�%�W�+@��R� 9@K��4%�'��O}���}���_���Ys2�x�$��r	��K~G�����C���TI������-9B#�%�Jc�iT���o'C�1��p���v#~������$b]���&�!�>ZHg��l$[���=�8�%d�}?��!�h3���c�������cI+V$F"?'/Qf�����1�����G�O���?�Z1�+�Jry�|Ct�M/�K�F���E����9�b������Tj�~-O�o��!���Es������W�~?�M�(y�
~����C������]���������
�zz=~7�;��t7}���-G�G�lI���3��L��0~�	ak`a�������;�NrK9R\*�*�FiF�U�����_e�|XN��E�{5;5�5�j^����u?������?"C�����M��db
�{����[�[��������A;���4Pf!]N��P�f� }B����EP���	���b��R6����
���a���z�;����L�M����)R��&�������[��G�i�,~I�(�9&��)�By��������I����Q�R{��O���j&�f�f��u�u�to�[����������.zL�,�H��;Y����;��B�D����l7��n��,W�A;�M�3�I9Z��v��l�4����d9�z��)?�B~��/bn���7h��FvBk&=���a#�F-��7�{�Q��!���M�S�,p������~F~)������������3�3��h�VJ��.*�>&7������x��.��"w�b��|F��T��\���f���2��e�^���1�r�K%���L���'��d9,���/0�����t��f]
	���B����u���*"��$*�v�(�a���U����A�@TI�Q��L_�CC<����28hd�2h���^�<�G��X)�"5o�!�O��W�k�w�Q�[���������[��'��J�����e�5��Q������{/\_P;J=�K�~����y�t��E������?��G@�>@�`=�Y~�7L��I������Vc�G���S� 5���d&y�<���V]k���|�'mlNr��6�t�*���:��[��������WL_>�����h����F��G���Es#9�P0;+��y=nW�3�aWlV��d4�uZ�,1J
j"�-�D�%!�"S����H+*Z��hI�PU{a�D�����z��y�?�TS=�s=�� �
B5�P�Pu$�G�n~Gu�1��t���x8�B5����m	�$j�/��i�U@�M����m�Q��hj�pGVwS�D*�������)&|����7�[�)Z��$1kvCM�?nU���G%�����,^��NN��kB�`�$�m���������%n^Y�����Z����=��V'�?:������&�z~�_���,��]][C�]�����Ohl�3p/���t����c���-�`[t^	�2*f��_����,%�I��]�[�4���s]���S�'�_M�k^C$���G[��N�5���^5���eTA�bO��jK#f��H��j�����9�(K�#��L��0���4�'m�H��qX\�w%�`E�%�[����S�	MT����A����_X����F������%h�0������Et����DQ.U���E"����i �@����� 8���>�,B!�9�!U�E����f-���%���t����%N���,�L�c���WF���	��7�m�����:������4������j������$?C��_�)y���sB���V0��>�\)jh�6��LM���p8-3�����'�]"����4��������pA�������yP9�}W���6�Zj���3p<�phr��C2���c�FB��2R$�������qq�UP���U	�v�t��%;EBJ�k?{������.�8}�����7�bK�x�#��#t��n�n���a?B�m�ze�[&5�ea��5��"��>�'&�������<��L��_����;���[P7y-��2�Ol��%�7��V^T�g���D�\�oB;�b�"��'9Z�y;Q�FT_����tA��/�u�.��>����_��qfb�b��b%6����N$;>�2�3�a��}y��31��d��[���!��yS��n��
��H�0M3�a���������}��
G�aK�N+P2l��HM#gCR�YUC�'!����3C��������������RcT�`�i1��C� �q��3a������ez�~O\9�|��2P�T�J�*���;���..-�tjuyc���;4�������C����{[/�{��[�^�DU���H=|�w�}�,���y�N=���C�0;�6D���
��O��P�N���!?v%�F��X�@�����
!EAj���zDM_���i6k�
�`��!�M�"}�����@x_ gz�f�|D�k�U����	M������\1X����Q�0f������_�8��NY���<Lk2�����t9].I���a��"��a�2���C�x<�f�\l�]n�#���,
�-;��$�����=�����3~t��-C����'��L�o����������h���O

=�Z���15_<��7���,���
:���������e���,��Z�P��&���Q�j��mC�h��L}�/{9Q��dHP	���&�V��.N/Ss(��2��Y-r�������a���&i�c_c��Js{���+���1�IsF��U\�����N�6���G�z�%�G�����_:Dwyvo��q���Yo��?�<~?��
�T��n�U�~�O�VM|e�V������W��QGp�����Y�!-�LV�70�I+���'~f�eR0�OU8�3��^�D���C"!������#�v��<3+'�n-���8��"�D*�T#R}_�k5�{03O%-'0�
V6��(R�P����XLC�!���&�Y"�j"z=eF>q�4������C�*l�j!�ED�HLL0>�s9Ux
C��W�&������T��n"��w2�^^o���� ���%6i��X�����
����i�������N������IV����^�����S:��������h��l�������sls�J��
F��b�Zq�n`-�Ns`��������}t�j4�!���DM0I+5���Q�j�A�V+T�c���h:5�����k���r=�\������p����f������|���V�E��7���"���u	�����~M�������|g��q�8�0�m��K��n������%���p�� \n�ZVn-*���P;
|���5�����676BYP�{l
�#v������|��[������{�4��������������T>�=��������YZ4,�!n���u�
�T�)���!�
�+�
�	�VM\0�>�@���RB��<;,�C�;	�~���P��B<��)�$�EhP�\�����ee�]C~�������\��\��c��Qj��b��Z|�#-��`��2���K��-���-��[FwYn���!������*�����I�3#�y_�����?d~0B_���|��s�p@h�5B�J�QU=/�AO� ��\./�D�Z0_��R�,�������;�wq{Y���Jan��(��,�j$(�VZ�[wZ�V�N��	�d5s�Y�u+lR�������jm6�����c�����t��S��������id��J�B�D��}�������se��P>��)!��y����>W���j����Uk�JbJ,������je����;�2�����H����r����������QONa�A�a-j+�Lk�3�bZH=|<Zh�T
�g���*|V�1��w�xs����8��86�����U1���������ayp��.��(�_9�7����i;2�;Gi	��+-�F�ke"+.ra���t������YP�8ec�\����tL-����hq��M�e%<��u�3��;���{�����V.[�h,����g���<�i��r��kF]���i��Nm���
'��r�8����2bz����g^|-8�pt��T$g�Q����)��h4��D��9���������v|F���7�5���o�4��]W���WX���q]���k~�����������8�X0��4��B�hM�M�L���\�y/����dZe-#��VG�����{�D�jj1u���-e<jV���B�����Oa���1!�h:���4���b"���b)����S����	z��AZ�8��cH0-��jg/*X��%�;8�P�-�����-tu�WS�R'�fO)��g(�x�s����� j�+���V��v�/�S(:��p6����l���HN��tsF��V����]��hO�:���_�������O�[����l������C����{���Coy����������.H�!j�u���LT��j���#`�y2b��:=��N�^�\���	?��k|����E����SfL�����1�����~�=$=hy\y�g�[���l��\�����iy���a��y�����c&Ys�V�6�$��Q�� �,��a�������e���A�1���Z����?��k�)�FJU�Q�*Vg�X�X�K���u4���1��w�y'�P��1��W��V%%��k��0���>��T|`��;��^^�4�?�l�X�F���M�%,������J��T��u���
}���[�{?���i��g�y��t��W�i5����{�_���?�����S�5;*l�,Z�>nd�%j)�T[4����el�q�sn�*�D�fX�l	����)��'�8O����DH�+���������.b���\�Y����Xj��.3��\e�D���=eUh�d5)6H�Ig'I��)�$j�E���*v��b��C49O����[�����+Y��s�],j�FWP�n�G�+!�@�U'����u����%u2_��:I�-XN�i]6��:!|�-�lb��y�Kf�'i����I�/�V�kV��9�6K�����2G��M��4���MZ����Z�-�;����O��~�#�<|�����R��*f=S�o���k���*_�:h�l�Y&��\�$�LV/5k�
��6�j�*C�I��wA1m ��ex��xWs�y�'�q���	T9����M�9�V�J_k`�vC�iv���8�fq�g�Z\��aa��C��0E����@\;���!
����'�Va��/�# �>�j )�H�j��/IX��Dio4V�s��o�At+�:57�dx��$`uR+��O	X�������W�|���>x|��������&�y�r�����{��\-�f����5�"���"bw���%�a���h�+|��������p�x�sc������f����u��t���^��7�C}���XJ��e��'�E2����?7���N�y����w��!��,O[�>�K������#|��,�E2�F�����pw:�3������s����8��+��Kv�U��xU��8��:���.8��S�!8\�'���+�O����3��D&�y�/�$LN�4�c��qP�B9�v�A37n+x�`���"J�T�v��zXH���'v����x���49YSl������/.�Z�Z-�83b���3�w��iM�qEs���������?7��,������IC��/!�$��R[L&���uN3�8��,oV�)�,����:/5�:��LKMg����^)����7-oG�����������p��y�y#����l)�,x/���W�yv�K����{G2tb'QBp+�>�I���}��H��59���Y-6F=�#n��Uw���-����@�p�B�	�R�5�Pknp4�G��)��{��Vk@���r�w���(�	����%mr�Vi���NH�
:���r��l��6��x=po�`m������!���)������n��i��6#9^���#lJn�dK�9w�p�"�<ew�S���7l�X���_N^��;^���m��_>��
w?��
�|��EK�%n��O���w�]���
�J���?��+���#[����8i�~��g�K$���:*��L��EU�����n�;%
%��F�Dx*jP���$
��@]�0�wA�!�6B�N. �X�9���3�x?�"��	i�H!��`���iDM��pqYt��-I�N��j�.W��t�.��
yU��$�CB��c�_����S �[Hi����W9-�gR� aB,9��9�x�����r��<�oWq�K����k>H�Uk�E�Z��Z��K�cs�	����SV���	w[���L��������w������I������lp!{d��s��a���6,�D���z�a,��L��.C��o8j8i�C����i���:fH�Al,��$�V���F+�����;�]rB�����~�������$�)[��I�Mt�4A��lhKi6 )�YDM�V��?So
���X��
W�����xB������W�����g�����|y�21���i��p��[6�D�%��|��T����4A|PrT#�DrR#5��Ij��,�Q��?	<��fBS�$�f+�&~��1�~&�\��A��~����p�"�\�iAf��<��]�CH=J���qS������Ljc���[�9%v|��R����t��$*�����$����t�������$)��fr����P�#Q�#�+��n���a�v�O����p����*�����/��q1���D��@&���I�]s�j�Dw��_<��q~�8��R��q~!u~a��v(%�~x��z�S�?���8�G�>�J|� a����!��D�z<���r�%����{Ju���D�3�����
{�|Y��i,�������:@������O�����<�v7�����s��~��d~���f���6$)��������'���/x�?�7�4q�O{�L�<^��3c���{�
����X8����c=6�z���w��"}
~q�����`��>�c������-d��Z��N��+G<�<I��;�N�:�j]��j��z���n��$�7����6	U`��N���&��Q�.��&��Q>�
o�$6M"�:�����5��������$<��#��L�����vD��w��8�z�rJ��b���f���P��.H!�,�\P���!Vy�~N���v�Qo��$F��[�S���^d~��
�.V9�Z��������Y��7���O�������^t�`����Uw�5�"�E����a-�K���	_L[�����q����W48tF�y�v�~��Q�v�^_��w�w�zj�:G�����i2�Q���9�����%�J�J���4���X.��i�/7���4m�f�; ��P�\�����
pD�e�E�	�0X�����@�:�� �jFn�d�����Es:��_�]��\b�r3�!h� j�K��Z��8&"*��#c|�E���[�8(�8<W�7h�>�j��� �j���kX�Yd��J���F9���~���������mG���l��g���=�l-���C<���ljy���~��7������V�N^W'fPE��D���0����Z�]o�,v��Hzj�148 �;�T�����c����s;����<{F+���gz;:�6iR�1"�Bf8��Kq��Pw�������Bh�r����*�!�������J�:,[�����+&N�4�
g�{�}�����T��|�k�J�A�A���[�^�q��7\j��������p����'3�-xY��>�{t]�;n��jL)�FO����dl25��,���
���M���-���<[^,7/w����F����k#ks;sb������3�q������_>�&�B�>�_�#�a$w}8��>}8"�p$�/����._������/��Me�x�+�[���Vzgzz�x{�6o���{�+�����k�r&�Bx�*�'��P
��e�*T���u�J���o��PzQS��,���Aw���0���!�+��\���E����r�j�����^(�T�s=�B�4�����]^1�
/���}�.7�>(?�O�}�����\N
���������W�sh)�/b�E�E��G+r�x'���JQ����#��"�kJ��-$\*npb�p��Ie���9J(����I�$�W�
�0���fF:���#6���.��T9�.���BDWd���/��������������H�K�O#t~��$��b�������i��y�6.�IP���A�G�~`��o����qG���O��QB�@.��7~Ar?��`r�����v��7�F��3����5��_/�'��6.w�
�7�o���n8�.�8p�����#�h�%�gL�nD0>���<s���EY�����Mv^�.����Y��|�����:+�m���N/<��H%�Rq�Q�B`����jqD�4���jZt�u��:�@���%t��#:-T�W�U��8���8
��FQ���4�pG-�s�&��������;���}��N�[�A	����C���0�����������n�ty<�^M�;y�)�i�V�|��������~d�2��Q��v�[1t���?�^��4�	���:s?��6�,��*��|R-v8K�4W��2��	��2�bW���F�OXTnaK�\i#��>bs[
�S)���T@9���Y��i��0���n����k��
(�I[���K��>�G��G~�!d8b8f��S-�#��#����J����� ,(�p�
3�8.�����vN�����!�_��X-6��>d��$������	w���
M �Xn�
���O5����������d�f��;'���w��������w��2{��m�N"%+�/�������Z5�G��:��{E������������l�M�3�7ID�����M������T�9|����#$�5��:�n����~�������i�E��������6��_^��?���?q���#����V���y�;�u�����`1��k���{�jRCj�2�r)��_�����l��N=����HC��r�//������6R���x��e�+�������{
endstream
endobj
11 0 obj
<< /Type /Font /Subtype /TrueType /BaseFont /AAAAAG+Times-Roman /FontDescriptor
25 0 R /Encoding /MacRomanEncoding /FirstChar 32 /LastChar 120 /Widths [ 250
0 0 0 0 0 0 0 0 0 0 0 250 333 250 278 500 500 500 0 500 0 0 0 0 0 278 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 389 0 0 889 0 0 0 0 667 0 0 0 0 0 0 0 0 0 0 0 0
0 0 444 0 0 500 444 333 500 500 278 0 0 278 778 500 500 500 0 333 389 278
0 0 0 500 ] >>
endobj
25 0 obj
<< /Type /FontDescriptor /FontName /AAAAAG+Times-Roman /Flags 32 /FontBBox
[-203 -428 1700 1272] /ItalicAngle 0 /Ascent 750 /Descent -250 /CapHeight
662 /StemV 102 /XHeight 454 /StemH 38 /AvgWidth 591 /MaxWidth 1721 /FontFile2
26 0 R >>
endobj
26 0 obj
<< /Length1 12788 /Length 9461 /Filter /FlateDecode >>
stream
x�{	t�����3���������7��d��-[��k�8���'v6�@��K�@�,!���eK)�(MJ���:@!B����]h���-����$�mb�}��	������;o����/��}o�r�j�E;���F6!�2� ����m�R])"�V��t��R]��������T/�A�"�v���R��<�Ju\y`��mW�����_}�����eJ��l�zz}t��+G6�.��y����[�M����M[VO��)^���0�
 V~� �&���/�-��Kt����;��t}�y���n�cRa��7u��U�����=����{�0\����C{�U1�jb(��_.������(~h!��t�O��GN9E��������x��g�\�����cC��E����D	�zJOB���}h�����������p�+�6B�Fh�-��E���+,���B��:W�%��
��\K�j	�%���(��Rz�g��O�/l����}x6Z���f������c��t�.9�I�{t�zto{twyt7xt��;P?t_!�X���~���������u��&�nF2Gt?��&"��Gt�Et�]u�C3���r�-�3S����Wf�����e�����53u����. ���n_������TP<�G�w��9T�p�M��=�r��2�	|���*��:������oDQ��v�(���C�r�'��j����2���k[�[�l�x�)��(.w�O��������m*����u*����-����������8��8�����(%�����C0�;-F'0wvM|�}.1�}6>�B�����?F'��q�;	�$����e����t�^\x��|�
��U�?�|����I�O��M|��H�v�w[���)y�{���Qh<���������[Rt��r�q}U�{%4���xU�{A�������J����=���c�M����m	�������N8��;K_w�������i������#���5n,��j������"��O�I>��
����&���+�J�R�T(Y%���<Q|O�Q6+�)@4`��e���$@Z�xK�im���Z����/I�������]��=}�\��ZZ(�����K��?��:��i6u/�s�|���98h������v����u#y�v~
yi�����(4uEi����y�d3���K�0��v
%�a	rJ�s:llW��s,
	��h�<d�C���'����$0J��Iy�Iu^��%O�J��H�jS0`,U+w'>��*u�/u����~�/u�/u���/��M�����CK�}�]��]�������]k��\�����m���3��+��|du������������{_�~�v/�w����= ��_*-��B�z�����u���6��%k�I'�L��U~�ku��[�Z]t�.������Z��]��o��k�UW-��u��!��!<v�^T�P�}x&��K�,�+���k�x�0}H�5�BHD;�}�Ov�wQ�@���[�~����f�K�q���x����,���Q�
ePz=olGal7���[O_��:�&�Y��.�Z��5t�8��>Bg���R��V�O(�vB/A�9��M��l+��Z`�����]l-� �C9��.Ck�t%��~��B��`��j��u��t�&���O�_�����k���b��zxo/��u�g�_����/���b����.�?���F��'A/�}
M�x	�g���bm��7�$��.�76���$����kp�V��^�7��x7~��3e,��)��T������
;���>����E�oy��6���f�����F�7�,>W�%`�Cz����6t5��n���EG������DS�����w��#��l?c62��'Y�>����p�+�*^V���Z����� `��X��C� Z��\�����h����F��)T�p�z��/��������;!D nX3Jn#�}�� o1��D�fs��8�Mn���7����UXYXU-\Wx��W�����1��vCq��z����B��t�����`?��Sp�]=��E'`oD����T2��$��n�}v�N�f�\�����������p?�8�Q�~
���)�	�'����I�H��%p���d-YG6�;�~�<I�	1-L73w�9���������nc�e�{�}�=��7{��$��[��+��Q�#�C�����h`�#8�g��h;���]Of��:�g�0����E��-~(SB5p���	`X	7���`���z�.��(���s��(�:�F�R�
�e�L���w{���<���T��F>�0��@S����=�����H	F���H�4R�8��UWy�����;��	|��(�����'��l��G.������.��NO{����kww
wVW��@O]]�������cd��AGt����|�����FV���
tu:�����<�X�_�G���!6�:}�vp��]]�4{��]�_u���V����yfd0O���X���������i�B��k����`�����yi�:�������G�l��28������&���NQRl��������_�{�0��wHY��Q��x�T.W�����o�P�V�U����k��������_������A�7�"\0]����{V���a�
4Y��v�l��5���������B#�#��g�p�����|�ed�E� �I�y�"���%���o���0�� ��&P��(hf���	������J�
*�P���t���)�<�=�{W��t{�����C����	8���u�.���A������F�'F��W`��A�a����M�)TU�GU{������Ng^�������Qp���B�c�}z�q�su%�'J��
�������`���������D�>�����	D�PR��;��]��^'m�{�^��`',����^�������ox3���!\������	��w�97���)���� ��s/#/��o���:I�������4�c��G��.d�#s'����#�^�xL0.OP�E^����Q��k���~X�F%�������{@��Z���%�
,�bhQ}�W����5r��b��b��
z���#2��j��d+�����@N��J�k:���y������r)y��5,���Pl��f�Z�V5�zA�#���u�N��B�4��	���lb��q��^N��$"��e��c{�8T
��r��co�|{,6�tn�Tn�p67[�����TN8
IM2e���x���s���������AX��N��`����R�3D�F�#�R���Xl6�C
������-��W/���=u������������T
M�,^�I�!V��[p�.��}�t]&Uk���{�
�����������d���+��������~�u2����I�����>�	��!�W�uO	�����d�����/��~�|������`�jPH*�*5�\YI����W}z�7\�d/l�������D��-i�Y{"�!���%	n-��E�#��k"�
�!���=�+�?>�X�|l��o�aS6{��xl��@$����U��5���������7Vs�+�~�?�~`f^B�F �������T������:��4A��<�]�	��y���'����u����]j@.m�4��A1$����N#d!i������)��.�D�T�1c���orBp��7����X&~���m���d<���!�P�I�Wz[h�����7�n����K7�Zzm1�l�������|����U����������=�{0yz����� ��h�T>j��LB���h�V(��1�X���7�1�+��V�!�&uZ��J���v�+a&p�)�VWn�?���?���4�O�9�
$9���y|&�I"8��b4[mo����S��`����7M��>�q,��Zn�S��5��+�K���.A������U�����	��W�ue�*r�������
��4�1������Q3r0�rc�]������\�Y��8$�uj�]�g������KL�8%�=
�u4��;��[')w�eMYZ^^�9���YA�u�e�Z��L���ZV�Q�
9!w����2k�x�K)o�	��f�m��_��7��(�������������95����h���U?���W�oQ}x��}�^/\��Hh��y�J��"��f0�S�*���[M:�uH��j�A�<���|��{�R3�Z����
=�J�������{�b��Q<C���j�j���A3tz(7u
����;�3fe�p���c,p���
"����)���7z�dQ��?n_��Mc�L�dn>�-��x�-��-���<2����H*�����)�3W�5��/����=���M
SE
eD��_x������d���z�MJ$���]kgU��5q>����h�Wu�0�?"gA�;��R-k�m'����W�T^�����J���&���_	K�mz��6�J-3=�u9T�S9a���}RY�
@7����V�r�m���2��F4���,R��#������7�|8K���=q��Ff}e���{�/"q�{	��I
�3�O	�X�j CiS�P|�	���uz1�Rib��&F5YM��X�L��
|<�FSer:E�J5UT�*�j�:���� ���1f�Ut��E)�^�%���G �VF�&p����tD�a�
W��Xf�0c��K��B"��'����'�4'�8@b�rS6����/�X!,��AA���D���������$�
=�������%|����-S/\�<uZ����l,�����2��E���YUH�����M��`P�us��-����|<���r���k�i�G�O�����$,���{�w��?�i��,>��R�8Qy����fj�����u�#~C0P)���5�\=�(@�������@>���2'7�6�������1�
2�7H^�� ������`����]��iY*5'�������z61t|j�q;�����;�eI����!��LV;������,���_�eEtcI��L�.L%���iJ���H�Cx`���0'��o����;G�6�����^>��i.)��tm�z���8:��/��������W��P�������?�U� �
�4���+�s;s`/�6����%t.D�p��h�����?P�@���Y��T�%^K�T�L�]��j2iX������n��l���g�5U5R���T���d4��fM��
�D[�T�=��|��TN$�;�ag����� L>��N���)P	C��S�S	�}��Kj�FC���TG��mji�G�6���.��BX����V K�V�����P��F�B#��kUV c�\Au��1�8��C�n�
�u�Ah�V��@}&XBm�F�E	��<R��o�`��4&}[���������Kq��wy*W-�����+��EmAO�������Y:��x��7/��3����.[�&��p&�Ye�U��������a��.\hn�d�2S^�{k��hT#������"�E^�z��t�m�_�#������O����<v"��������j�3�YQ�Wj�f���f���c�w�{��5�kc}��H'���5Uq]��ol	���0�%�R���;���_<�k���6tZ���I@�L&�@jG�V�t&����F��U�!��@����pC�1�dO�6`,�B�X���y���yN�h �:S�$Kx�c�O����3�5��W_��
0v��Kc�(����>�oS?�:|G��_��[����-�q�x-Z����u��M��V���$�T�6;�1���p���/��'n���{�<�V��D[��!���;ih�r�v��
���v�5��L;�;,��g
�E�5�a�q�e�����?��J��fqOTjF����BW������*��M�q���%�R���r3�(��������I`����Z��.����Y�w"�t��&�c��^�����	!�Q�2��5(d,�vV�_dN����4)�@��>�{��VOV�9u������i�G���g���0����4������}�����7��G_�������jzA�F@@	@�!���^���ru�bZ�z7�m���/*���w�S���.^��L�'�#�!%���M�	�Q� ��VU����� �0TR�N)36�������t�|j?R�N�8�W�0��d+;������������*]F}�7�i8����>������7������W9,��H�d����[U��fQ�����<	t��%�ui��������;89.���:����
�������xOU��ZC>'	��jUPg��U�jk�dq���}>�Dg�M-qx�y���/Y����'�?DBTk�T�3o�
[�&��$}���N��������V���
$NL
M1���7���d�G�����C�����d��-W��K`4�6h�p�?���4m6�4a��*+<�/Ly��������J����&]	�R��)���\����������k�������Y�i�0��?�_��Z�o���{~�X��]�E�6�	�F6���.S�&m]��'���k�2$�o��_u1�*��u[�W�p{jW��>{Ko���Ik2�#��U���W���PM�5/�>�W���`��]V�V�6�l��;I��"|�pNj�Q�Mz�V������	������c���ad�$+X(�b��	w�:�O��W���
�X,>�Nm�l���b���K5$�v
������B0d���0,�A[�l��-L����'{�-�����E��MN7���\
�����a�Z3���M��Y��Kj�.���nQa��i��9��fH@TV�S�z|�]=W������m��
_1e��r��P�'z����!)��Z�b��{<v��o�{E�[0�bPy7A"���E�������w��P�Z�@����B��ReI�g@��$M��u���/�����R��x�*�������AeHj,��'�D%�)8�LK��{��N_��i+��z�>������-��C0�PCW�]~�L�cALm���c���6jD���eX��6���r�hJ0,=�]>�|�~����~	_�[����*��r�]cn�{�o{�gr�
���.`�h/�T(����|n��;�4��c�_y�)���:1z��'s�ssDhf��0���b�9����$��D�*��c�8�4����Ul��{���������:z��RoO�T��8����Sf��j�����6?gv�)Q�S�i��D�����IO��!���L}��.�u��H���n�,���d���P9%����/(���l�|���X���PB~��3��X�`�
���3��z����m_����>�%$�Z�s�p����	�sY�g�e�����+��[����[�W�I�L��Bzy��{y�huz��u�e�%��g9�]����vIWw��/�����#3RJ���,�O������h�B���v����Wtd�������2�3?�_������r�l�G�����+��S	o����^��c�E��ok)
KTi�W�2��S&C��6��Z�G]�����:�)�~�1��	��XFq,��I�V|�������*k��}�[H�������p����
��iqt��[1���������YmY��L��\Z7gag����E�����f_F�*����*})P�#D�_[�V����S��b��G���n^�.����^��!"@�Au�?��>r����JU?U}�"��[��g���Y�m�z��5*��r��RJM���\��yv�i�4�WJ-4�c�86ee
�3U��j�!�6XB���R%=�����DP�B�9$����l9�dD��>���*I�m�}���?���W�����U��i���Ar/.+��������-�`��?�N���O���!�l���o)t\��2�{��>>>V��\xf�y,����:tb��@�C�}�o�eA
a
{��x4U�6j��p*[���'+��T���[�H�t����JF�:
��4�K�������?�:L�x���3.��$l���"�t���jo
����O�mY���H\`w�V)13�����Z7����R������;�q���,���_�j�J��������8�����:���e��������*�m����O�RxZoi(]W��@���9d��(�%�:^��}���� V;�X�x�V���
>w|�$�fW�g�{\Iq���6��Nv�����ip�I8xe���,&�Q �>ubr3��lI"^���y0-	K�i`,r��d���3[�-^���`��k�^m�M���)%�C���5�C�2�����r�z���2�ZxdpUs<���9Wh^�A@@Q[[�$�2c��bF���2l%Q]V�V�N�]�]�K��*�b�������!(���J_�],Cr�^�NO��j�}1�I��J_��S��;Uk��T�����u���l�E�0���kVI@�M���c���������i:5)�	 ����f;���#��>iv|bw��`��I(M"(|������I(��Z�D=^�^QTY�=F_�#\�
n9!)���R`	�\+;���^�*�Sw���n<��P4��z�-�}[����M�n��=}��$(����k�����o����O�5q�M[�7-h����o]�iOD�)��Hf���K{fl�7zO��e�m�^���A�/�*UF��3�V�������4���	H^�#	��.��`��8	���������P~
	j�}�a��z�&�Wj��g��&�u�,�6����P���#Q� :{w �sX��eF+m8Ne�j����}=S�f��|AkEw���j��Qpj�9;�>Vw���|�s/j��t��<�O� ���8J$��x|�Q�m�m����%��aS7�c�f��L���V+����T��V�%��@a�Z"�z?��d���z�%kRG��4�G������
��0�������Q���V(k���4����A
W��q�b��q��j�d4�6��MF��FrbgsM.���?�6Lb��i!�{��rJ�q��	�!�!��B� ;������_���x��^�vi$�)�T������ijWN{k4�r����2��4~;�N��&WMe����25#^X���C������K��+�6S��c�U3�{E\VQ6���iu�Qd��6��Q$�n*k��;�}��+�eZ^5������F*�8O��j��T�BJ��d�w�O�������\K���3�����'�{s���yh>|W�H��K��@=7,���>\�f����zk���F���X���
endstream
endobj
27 0 obj
<< /Title (simple-RMJ-annotated.xml) /Producer (macOS Version 10.16 \(Build 20D91\) Quartz PDFContext)
/Creator (Firefox) /CreationDate (D:20210412180047Z00'00') /ModDate (D:20210412180047Z00'00')
>>
endobj
xref
0 28
0000000000 65535 f 
0000005794 00000 n 
0000008801 00000 n 
0000000022 00000 n 
0000005898 00000 n 
0000008765 00000 n 
0000008934 00000 n 
0000022299 00000 n 
0000018644 00000 n 
0000027012 00000 n 
0000032621 00000 n 
0000045167 00000 n 
0000006052 00000 n 
0000008884 00000 n 
0000009351 00000 n 
0000009601 00000 n 
0000019103 00000 n 
0000018808 00000 n 
0000019352 00000 n 
0000022659 00000 n 
0000022917 00000 n 
0000027385 00000 n 
0000027645 00000 n 
0000033205 00000 n 
0000033454 00000 n 
0000045577 00000 n 
0000045830 00000 n 
0000055380 00000 n 
trailer
<< /Size 28 /Root 13 0 R /Info 27 0 R /ID [ <79b0abf9a8caa84572c1ee5186250bda>
<79b0abf9a8caa84572c1ee5186250bda> ] >>
startxref
55596
%%EOF
#2David Rowley
dgrowleyml@gmail.com
In reply to: Thomas (#1)
Re: Patch: Range Merge Join

On Thu, 10 Jun 2021 at 03:05, Thomas <thomasmannhart97@gmail.com> wrote:

We have implemented the Range Merge Join algorithm by extending the
existing Merge Join to also support range conditions, i.e., BETWEEN-AND
or @> (containment for range types).

It shouldn't be a blocker for you, but just so you're aware, there was
a previous proposal for this in [1]/messages/by-id/6227.1334559170@sss.pgh.pa.us and a patch in [2]/messages/by-id/CAMp0ubfwAFFW3O_NgKqpRPmm56M4weTEXjprb2gP_NrDaEC4Eg@mail.gmail.com. I've include
Jeff here just so he's aware of this. Jeff may wish to state his
intentions with his own patch. It's been a few years now.

I only just glanced over the patch. I'd suggest getting rid of the /*
Thomas */ comments. We use git, so if you need an audit trail about
changes then you'll find it in git blame. If you have those for an
internal audit trail then you should consider using git. No committer
would commit those to PostgreSQL, so they might as well disappear.

For further review, please add the patch to the July commitfest [3]https://commitfest.postgresql.org/33/.
We should be branching for pg15 sometime before the start of July.
There will be more focus on new patches around that time. Further
details in [4]https://wiki.postgresql.org/wiki/CommitFest.

Also, I see this if your first post to this list, so welcome, and
thank you for the contribution. Also, just to set expectations;
patches like this almost always take a while to get into shape for
PostgreSQL. Please expect a lot of requests to change things. That's
fairly standard procedure. The process often drags on for months and
in some less common cases, years.

David

[1]: /messages/by-id/6227.1334559170@sss.pgh.pa.us
[2]: /messages/by-id/CAMp0ubfwAFFW3O_NgKqpRPmm56M4weTEXjprb2gP_NrDaEC4Eg@mail.gmail.com
[3]: https://commitfest.postgresql.org/33/
[4]: https://wiki.postgresql.org/wiki/CommitFest

#3Thomas
thomasmannhart97@gmail.com
In reply to: David Rowley (#2)
1 attachment(s)
Re: Patch: Range Merge Join

Thank you for the feedback.
I removed the redundant comments from the patch and added this thread to
the July CF [1]https://commitfest.postgresql.org/33/3160/.

Best Regards,
Thomas Mannhart

[1]: https://commitfest.postgresql.org/33/3160/

Am Do., 10. Juni 2021 um 05:10 Uhr schrieb David Rowley <
dgrowleyml@gmail.com>:

Show quoted text

On Thu, 10 Jun 2021 at 03:05, Thomas <thomasmannhart97@gmail.com> wrote:

We have implemented the Range Merge Join algorithm by extending the
existing Merge Join to also support range conditions, i.e., BETWEEN-AND
or @> (containment for range types).

It shouldn't be a blocker for you, but just so you're aware, there was
a previous proposal for this in [1] and a patch in [2]. I've include
Jeff here just so he's aware of this. Jeff may wish to state his
intentions with his own patch. It's been a few years now.

I only just glanced over the patch. I'd suggest getting rid of the /*
Thomas */ comments. We use git, so if you need an audit trail about
changes then you'll find it in git blame. If you have those for an
internal audit trail then you should consider using git. No committer
would commit those to PostgreSQL, so they might as well disappear.

For further review, please add the patch to the July commitfest [3].
We should be branching for pg15 sometime before the start of July.
There will be more focus on new patches around that time. Further
details in [4].

Also, I see this if your first post to this list, so welcome, and
thank you for the contribution. Also, just to set expectations;
patches like this almost always take a while to get into shape for
PostgreSQL. Please expect a lot of requests to change things. That's
fairly standard procedure. The process often drags on for months and
in some less common cases, years.

David

[1]
/messages/by-id/6227.1334559170@sss.pgh.pa.us
[2]
/messages/by-id/CAMp0ubfwAFFW3O_NgKqpRPmm56M4weTEXjprb2gP_NrDaEC4Eg@mail.gmail.com
[3] https://commitfest.postgresql.org/33/
[4] https://wiki.postgresql.org/wiki/CommitFest

Attachments:

postgres-rmj.patchapplication/octet-stream; name=postgres-rmj.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 9a60865d19..20fa83050d 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1210,8 +1210,16 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			pname = sname = "Nested Loop";
 			break;
 		case T_MergeJoin:
-			pname = "Merge";	/* "Join" gets added by jointype switch */
-			sname = "Merge Join";
+			if(((MergeJoin *) plan)->rangeclause)
+			{
+				pname = "Range Merge";	/* "Join" gets added by jointype switch */
+				sname = "Range Merge Join";
+			}
+			else
+			{
+				pname = "Merge";	/* "Join" gets added by jointype switch */
+				sname = "Merge Join";
+			}
 			break;
 		case T_HashJoin:
 			pname = "Hash";		/* "Join" gets added by jointype switch */
@@ -1952,6 +1960,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_MergeJoin:
 			show_upper_qual(((MergeJoin *) plan)->mergeclauses,
 							"Merge Cond", planstate, ancestors, es);
+			show_upper_qual(((MergeJoin *) plan)->rangeclause,
+							"Range Cond", planstate, ancestors, es);
 			show_upper_qual(((MergeJoin *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
 			if (((MergeJoin *) plan)->join.joinqual)
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index b41454ab6d..2dccba24b3 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -93,11 +93,15 @@
 #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/rangetypes.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/typcache.h"
 
 
 /*
@@ -140,6 +144,18 @@ typedef struct MergeJoinClauseData
 	SortSupportData ssup;
 }			MergeJoinClauseData;
 
+/*
+ * Runtime data for the range clause
+ */
+typedef struct RangeJoinData
+{
+	ExprState *startClause;
+	ExprState *endClause;
+	ExprState *rangeExpr;
+	ExprState *elemExpr;
+
+}			RangeJoinData;
+
 /* Result type for MJEvalOuterValues and MJEvalInnerValues */
 typedef enum
 {
@@ -269,6 +285,57 @@ MJExamineQuals(List *mergeclauses,
 	return clauses;
 }
 
+/*
+ * MJCreateRangeData
+ */
+static RangeData
+MJCreateRangeData(List *rangeclause,
+				   PlanState *parent)
+{
+	RangeData data;
+
+	Assert(list_legth(node->rangeclause) < 3);
+
+	data = (RangeData) palloc0(sizeof(RangeJoinData));
+
+	data->startClause = NULL;
+	data->endClause = NULL;
+	data->rangeExpr = NULL;
+	data->elemExpr = NULL;
+
+	if(list_length(rangeclause) == 2)
+	{
+		data->startClause = ExecInitExpr(linitial(rangeclause), parent);
+		data->endClause = ExecInitExpr(lsecond(rangeclause), parent);
+	}
+	else
+	{
+		OpExpr		*qual = (OpExpr *) linitial(rangeclause);
+		ExprState	*lexpr;
+		ExprState	*rexpr;
+
+		/*
+		 * Prepare the input expressions for execution.
+		 */
+		lexpr = ExecInitExpr((Expr *) get_leftop(qual), parent);
+		rexpr = ExecInitExpr((Expr *) get_rightop(qual), parent);
+
+		if(qual->opno == OID_RANGE_CONTAINS_ELEM_OP)
+		{
+			data->rangeExpr = lexpr;
+			data->elemExpr = rexpr;
+		}
+		else
+		{
+			Assert(qual->opno == OID_RANGE_ELEM_CONTAINED_OP);
+			data->rangeExpr = rexpr;
+			data->elemExpr = lexpr;
+		}
+	}
+
+	return data;
+}
+
 /*
  * MJEvalOuterValues
  *
@@ -445,6 +512,73 @@ MJCompare(MergeJoinState *mergestate)
 }
 
 
+/*
+ * MJCompareRange
+ *
+ * Compare the rangejoinable values of the current two input tuples
+ * and return 0 if they are equal (ie, the outer interval contains the inner),
+ * >0 if outer > inner, <0 if outer < inner.
+ */
+static int
+MJCompareRange(MergeJoinState *mergestate)
+{
+	int 		  result = 0;
+	bool 		  isNull;
+	MemoryContext oldContext;
+	RangeData	  rangeData = mergestate->mj_RangeData;
+	ExprContext	 *econtext = mergestate->js.ps.ps_ExprContext;
+	ExprState	 *endClause = rangeData->endClause,
+				 *startClause = rangeData->startClause,
+				 *rangeExpr = rangeData->rangeExpr,
+				 *elemExpr = rangeData->elemExpr;
+
+	/*
+	 * Call the comparison functions in short-lived context, in case they leak
+	 * memory.
+	 */
+	ResetExprContext(econtext);
+
+	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+	econtext->ecxt_outertuple = mergestate->mj_OuterTupleSlot;
+	econtext->ecxt_innertuple = mergestate->mj_InnerTupleSlot;
+
+	if (endClause != NULL)
+	{
+		Assert(startClause != NULL);
+
+		if (!ExecEvalExprSwitchContext(endClause, econtext, &isNull))
+			result = -1;
+		else if (!ExecEvalExprSwitchContext(startClause, econtext, &isNull))
+			result = 1;
+	}
+	else
+	{
+		Datum			rangeDatum,
+    					elemDatum;
+		Oid				rangeType;
+		TypeCacheEntry *typecache;
+
+		Assert(rangeExpr != NULL && elemExpr != NULL);
+
+		rangeDatum = ExecEvalExprSwitchContext(rangeExpr, econtext, &isNull);
+		elemDatum = ExecEvalExprSwitchContext(elemExpr, econtext, &isNull);
+
+		rangeType = exprType((Node *) rangeExpr->expr);
+		typecache = lookup_type_cache(rangeType, TYPECACHE_RANGE_INFO);
+
+		if (elem_after_range_internal(typecache, elemDatum, DatumGetRangeTypeP(rangeDatum)))
+			result = -1;
+		else if (elem_before_range_internal(typecache, elemDatum, DatumGetRangeTypeP(rangeDatum)))
+			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.
@@ -604,6 +738,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprState  *otherqual;
 	bool		qualResult;
 	int			compareResult;
+	int			compareRangeResult;
 	PlanState  *innerPlan;
 	TupleTableSlot *innerTupleSlot;
 	PlanState  *outerPlan;
@@ -611,6 +746,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprContext *econtext;
 	bool		doFillOuter;
 	bool		doFillInner;
+	bool		isRangeJoin;
 
 	CHECK_FOR_INTERRUPTS();
 
@@ -624,6 +760,7 @@ ExecMergeJoin(PlanState *pstate)
 	otherqual = node->js.ps.qual;
 	doFillOuter = node->mj_FillOuter;
 	doFillInner = node->mj_FillInner;
+	isRangeJoin = node->mj_RangeJoin;
 
 	/*
 	 * Reset per-tuple memory context to free any expression evaluation
@@ -891,8 +1028,23 @@ ExecMergeJoin(PlanState *pstate)
 						compareResult = MJCompare(node);
 						MJ_DEBUG_COMPARE(compareResult);
 
-						if (compareResult == 0)
-							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						if(compareResult == 0)
+						{
+							if(isRangeJoin)
+							{
+								compareRangeResult = MJCompareRange(node);
+
+								if (compareRangeResult == 0)
+									node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+								else
+								{
+									Assert(compareRangeResult < 0);
+									node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+								}
+							}
+							else
+								node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						}
 						else
 						{
 							Assert(compareResult < 0);
@@ -1085,7 +1237,19 @@ ExecMergeJoin(PlanState *pstate)
 						/* we need not do MJEvalInnerValues again */
 					}
 
-					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					if(isRangeJoin)
+					{
+						compareRangeResult = MJCompareRange(node);
+
+						if(compareRangeResult == 0)
+							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						else if(compareRangeResult < 0)
+							node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+						else /* compareRangeResult > 0 */
+							node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+					}
+					else
+						node->mj_JoinState = EXEC_MJ_JOINTUPLES;
 				}
 				else
 				{
@@ -1184,12 +1348,28 @@ ExecMergeJoin(PlanState *pstate)
 
 				if (compareResult == 0)
 				{
-					if (!node->mj_SkipMarkRestore)
-						ExecMarkPos(innerPlan);
-
-					MarkInnerTuple(node->mj_InnerTupleSlot, node);
-
-					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					if(isRangeJoin)
+					{
+						compareRangeResult = MJCompareRange(node);
+						if(compareRangeResult == 0)
+						{
+							if (!node->mj_SkipMarkRestore)
+								ExecMarkPos(innerPlan);
+							MarkInnerTuple(node->mj_InnerTupleSlot, node);
+							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						}
+						else if(compareRangeResult < 0)
+							node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+						else /* compareRangeResult > 0 */
+							node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+					}
+					else
+					{
+						if (!node->mj_SkipMarkRestore)
+							ExecMarkPos(innerPlan);
+						MarkInnerTuple(node->mj_InnerTupleSlot, node);
+						node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					}
 				}
 				else if (compareResult < 0)
 					node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
@@ -1532,6 +1712,17 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 		ExecInitQual(node->join.joinqual, (PlanState *) mergestate);
 	/* mergeclauses are handled below */
 
+	/*
+	 * initialize range join
+	 */
+	if(node->rangeclause)
+	{
+		mergestate->mj_RangeData = MJCreateRangeData(node->rangeclause, (PlanState *) mergestate);
+		mergestate->mj_RangeJoin = true;
+	}
+	else
+		mergestate->mj_RangeJoin = false;
+
 	/*
 	 * detect whether we need only consider the first matching inner tuple
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 621f7ce068..8448fea43a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2356,6 +2356,8 @@ _copyRestrictInfo(const RestrictInfo *from)
 	COPY_SCALAR_FIELD(norm_selec);
 	COPY_SCALAR_FIELD(outer_selec);
 	COPY_NODE_FIELD(mergeopfamilies);
+	COPY_NODE_FIELD(rangeleftopfamilies);
+	COPY_NODE_FIELD(rangerightopfamilies);
 	/* EquivalenceClasses are never copied, so shallow-copy the pointers */
 	COPY_SCALAR_FIELD(left_ec);
 	COPY_SCALAR_FIELD(right_ec);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e32b92e299..ee624ef000 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -758,6 +758,7 @@ _outMergeJoin(StringInfo str, const MergeJoin *node)
 
 	WRITE_BOOL_FIELD(skip_mark_restore);
 	WRITE_NODE_FIELD(mergeclauses);
+    WRITE_NODE_FIELD(rangeclause);
 
 	numCols = list_length(node->mergeclauses);
 
@@ -2235,6 +2236,7 @@ _outMergePath(StringInfo str, const MergePath *node)
 	_outJoinPathInfo(str, (const JoinPath *) node);
 
 	WRITE_NODE_FIELD(path_mergeclauses);
+	WRITE_NODE_FIELD(path_rangeclause);
 	WRITE_NODE_FIELD(outersortkeys);
 	WRITE_NODE_FIELD(innersortkeys);
 	WRITE_BOOL_FIELD(skip_mark_restore);
@@ -2557,6 +2559,8 @@ _outRestrictInfo(StringInfo str, const RestrictInfo *node)
 	WRITE_FLOAT_FIELD(norm_selec, "%.4f");
 	WRITE_FLOAT_FIELD(outer_selec, "%.4f");
 	WRITE_NODE_FIELD(mergeopfamilies);
+	WRITE_NODE_FIELD(rangeleftopfamilies);
+	WRITE_NODE_FIELD(rangerightopfamilies);
 	/* don't write left_ec, leads to infinite recursion in plan tree dump */
 	/* don't write right_ec, leads to infinite recursion in plan tree dump */
 	WRITE_NODE_FIELD(left_em);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index f0b34ecfac..8f7160ffee 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2172,6 +2172,7 @@ _readMergeJoin(void)
 
 	READ_BOOL_FIELD(skip_mark_restore);
 	READ_NODE_FIELD(mergeclauses);
+    READ_NODE_FIELD(rangeclause);
 
 	numCols = list_length(local_node->mergeclauses);
 
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 8577c7b138..2f9efb1e3e 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3419,6 +3419,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	double		inner_path_rows = inner_path->rows;
 	List	   *mergeclauses = path->path_mergeclauses;
 	List	   *innersortkeys = path->innersortkeys;
+	List	   *allclauses;
 	Cost		startup_cost = workspace->startup_cost;
 	Cost		run_cost = workspace->run_cost;
 	Cost		inner_run_cost = workspace->inner_run_cost;
@@ -3435,9 +3436,12 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 				rescannedtuples;
 	double		rescanratio;
 
-	/* Protect some assumptions below that rowcounts aren't zero */
-	if (inner_path_rows <= 0)
-		inner_path_rows = 1;
+	allclauses = list_concat(list_copy(mergeclauses), path->path_rangeclause);
+
+
+    /* Protect some assumptions below that rowcounts aren't zero */
+    if (inner_path_rows <= 0)
+        inner_path_rows = 1;
 
 	/* Mark the path with the correct row estimate */
 	if (path->jpath.path.param_info)
@@ -3466,7 +3470,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * Compute cost of the mergequals and qpquals (other restriction clauses)
 	 * separately.
 	 */
-	cost_qual_eval(&merge_qual_cost, mergeclauses, root);
+	cost_qual_eval(&merge_qual_cost, allclauses, root);
 	cost_qual_eval(&qp_qual_cost, path->jpath.joinrestrictinfo, root);
 	qp_qual_cost.startup -= merge_qual_cost.startup;
 	qp_qual_cost.per_tuple -= merge_qual_cost.per_tuple;
@@ -3490,7 +3494,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * Get approx # tuples passing the mergequals.  We use approx_tuple_count
 	 * here because we need an estimate done with JOIN_INNER semantics.
 	 */
-	mergejointuples = approx_tuple_count(root, &path->jpath, mergeclauses);
+	mergejointuples = approx_tuple_count(root, &path->jpath, allclauses);
 
 	/*
 	 * When there are equal merge keys in the outer relation, the mergejoin
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index b67b517770..c866ed84d8 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -16,6 +16,7 @@
 
 #include <math.h>
 
+#include "catalog/pg_operator.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
 #include "nodes/nodeFuncs.h"
@@ -24,6 +25,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
 /* Hook for plugins to get control in add_paths_to_joinrel() */
@@ -84,6 +86,9 @@ static List *select_mergejoin_clauses(PlannerInfo *root,
 									  List *restrictlist,
 									  JoinType jointype,
 									  bool *mergejoin_allowed);
+static List *select_rangejoin_clauses(RelOptInfo *outerrel,
+									  RelOptInfo *innerrel,
+									  List *restrictlist);
 static void generate_mergejoin_paths(PlannerInfo *root,
 									 RelOptInfo *joinrel,
 									 RelOptInfo *innerrel,
@@ -147,6 +152,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 
 	extra.restrictlist = restrictlist;
 	extra.mergeclause_list = NIL;
+	extra.rangeclause_list = NIL;
 	extra.sjinfo = sjinfo;
 	extra.param_source_rels = NULL;
 
@@ -207,6 +213,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 * it's a full join.
 	 */
 	if (enable_mergejoin || jointype == JOIN_FULL)
+	{
 		extra.mergeclause_list = select_mergejoin_clauses(root,
 														  joinrel,
 														  outerrel,
@@ -215,6 +222,12 @@ add_paths_to_joinrel(PlannerInfo *root,
 														  jointype,
 														  &mergejoin_allowed);
 
+		if (jointype == JOIN_INNER || jointype == JOIN_LEFT)
+			extra.rangeclause_list = select_rangejoin_clauses(outerrel,
+															  innerrel,
+															  restrictlist);
+	}
+
 	/*
 	 * If it's SEMI, ANTI, or inner_unique join, compute correction factors
 	 * for cost estimation.  These will be the same for all paths.
@@ -777,6 +790,7 @@ try_mergejoin_path(PlannerInfo *root,
 				   Path *inner_path,
 				   List *pathkeys,
 				   List *mergeclauses,
+				   List *rangeclause,
 				   List *outersortkeys,
 				   List *innersortkeys,
 				   JoinType jointype,
@@ -850,6 +864,7 @@ try_mergejoin_path(PlannerInfo *root,
 									   pathkeys,
 									   required_outer,
 									   mergeclauses,
+									   rangeclause,
 									   outersortkeys,
 									   innersortkeys));
 	}
@@ -926,6 +941,7 @@ try_partial_mergejoin_path(PlannerInfo *root,
 										   pathkeys,
 										   NULL,
 										   mergeclauses,
+										   NIL,
 										   outersortkeys,
 										   innersortkeys));
 }
@@ -1107,7 +1123,8 @@ sort_inner_and_outer(PlannerInfo *root,
 	Path	   *inner_path;
 	Path	   *cheapest_partial_outer = NULL;
 	Path	   *cheapest_safe_inner = NULL;
-	List	   *all_pathkeys;
+	List	   *merge_pathkeys;
+	List	   *range_pathkeys;
 	ListCell   *l;
 
 	/*
@@ -1207,25 +1224,80 @@ sort_inner_and_outer(PlannerInfo *root,
 	 * some heuristics behind it (see that function), so be sure to try it
 	 * exactly as-is as well as making variants.
 	 */
-	all_pathkeys = select_outer_pathkeys_for_merge(root,
-												   extra->mergeclause_list,
-												   joinrel);
 
-	foreach(l, all_pathkeys)
+	range_pathkeys = select_outer_pathkeys_for_range(root,
+													 extra->rangeclause_list);
+
+	merge_pathkeys = select_outer_pathkeys_for_merge(root,
+													 extra->mergeclause_list,
+													 joinrel);
+
+	if(merge_pathkeys == NIL && enable_mergejoin)
+	{
+		foreach(l, range_pathkeys)
+		{
+			PathKey		*range_pathkey = (PathKey *) lfirst(l);
+			List		*outerkeys = list_make1(range_pathkey);
+			List		*innerkeys = NIL;
+			List		*rangeclauses;
+			ListCell	*lc;
+
+			rangeclauses = find_rangeclauses_for_outer_pathkeys(root,
+												outerkeys,
+												NIL,
+												extra->rangeclause_list);
+
+			foreach(lc, rangeclauses)
+			{
+				List	*rangeclause = (List *) lfirst(lc);
+				PathKey *range_inner_pathkey =
+						make_inner_pathkey_for_range(root,
+													 rangeclause);
+
+				innerkeys = lappend(innerkeys, range_inner_pathkey);
+
+				merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+															 outerkeys);
+
+				/*
+				 * And now we can make the path.
+				 *
+				 * Note: it's possible that the cheapest paths will already be sorted
+				 * properly.  try_mergejoin_path will detect that case and suppress an
+				 * explicit sort step, so we needn't do so here.
+				 */
+				try_mergejoin_path(root,
+								   joinrel,
+								   outer_path,
+								   inner_path,
+								   merge_pathkeys,
+								   NIL,
+								   rangeclause,
+								   outerkeys,
+								   innerkeys,
+								   jointype,
+								   extra,
+								   false);
+			}
+		}
+	}
+
+
+	foreach(l, merge_pathkeys)
 	{
 		List	   *front_pathkey = (List *) lfirst(l);
 		List	   *cur_mergeclauses;
 		List	   *outerkeys;
 		List	   *innerkeys;
-		List	   *merge_pathkeys;
+		List	   *mergejoin_pathkeys;
 
 		/* Make a pathkey list with this guy first */
-		if (l != list_head(all_pathkeys))
+		if (l != list_head(merge_pathkeys))
 			outerkeys = lcons(front_pathkey,
-							  list_delete_nth_cell(list_copy(all_pathkeys),
-												   foreach_current_index(l)));
+							  list_delete_ptr(list_copy(merge_pathkeys),
+											  front_pathkey));
 		else
-			outerkeys = all_pathkeys;	/* no work at first one... */
+			outerkeys = merge_pathkeys;	/* no work at first one... */
 
 		/* Sort the mergeclauses into the corresponding ordering */
 		cur_mergeclauses =
@@ -1242,7 +1314,7 @@ sort_inner_and_outer(PlannerInfo *root,
 												  outerkeys);
 
 		/* Build pathkeys representing output sort order */
-		merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+		mergejoin_pathkeys = build_join_pathkeys(root, joinrel, jointype,
 											 outerkeys);
 
 		/*
@@ -1256,8 +1328,9 @@ sort_inner_and_outer(PlannerInfo *root,
 						   joinrel,
 						   outer_path,
 						   inner_path,
-						   merge_pathkeys,
+						   mergejoin_pathkeys,
 						   cur_mergeclauses,
+						   NIL,
 						   outerkeys,
 						   innerkeys,
 						   jointype,
@@ -1273,12 +1346,88 @@ sort_inner_and_outer(PlannerInfo *root,
 									   joinrel,
 									   cheapest_partial_outer,
 									   cheapest_safe_inner,
-									   merge_pathkeys,
+									   mergejoin_pathkeys,
 									   cur_mergeclauses,
 									   outerkeys,
 									   innerkeys,
 									   jointype,
 									   extra);
+
+		foreach(l, range_pathkeys)
+		{
+			PathKey		*range_outer_pathkey = (PathKey *) lfirst(l);
+			List		*range_outerkeys = list_copy(outerkeys);
+			List		*range_innerkeys;
+			List		*rangeclauses;
+			ListCell	*lc;
+
+			if(list_member_ptr(range_outerkeys, range_outer_pathkey))
+			{
+				if(!equal(llast(range_outerkeys), range_outer_pathkey))
+					continue;
+			}
+			else
+				range_outerkeys = lappend(range_outerkeys, range_outer_pathkey);
+
+			/* Sort the mergeclauses into the corresponding ordering */
+			cur_mergeclauses =
+				find_mergeclauses_for_outer_pathkeys(root,
+													 range_outerkeys,
+													 extra->mergeclause_list);
+
+			/* Should have used them all... */
+			Assert(list_length(cur_mergeclauses) == list_length(extra->mergeclause_list));
+
+			/* Build sort pathkeys for the inner side */
+			range_innerkeys = make_inner_pathkeys_for_merge(root,
+													  	  	cur_mergeclauses,
+															range_outerkeys);
+
+			rangeclauses = find_rangeclauses_for_outer_pathkeys(root,
+												range_outerkeys,
+												cur_mergeclauses,
+												extra->rangeclause_list);
+
+			foreach(lc, rangeclauses)
+			{
+				List	*cur_rangeclause = (List *) lfirst(lc);
+				PathKey *range_inner_pathkey;
+
+				range_inner_pathkey = make_inner_pathkey_for_range(root, cur_rangeclause);
+
+				if(list_member_ptr(range_innerkeys, range_inner_pathkey))
+				{
+					if(!equal(llast(range_innerkeys), range_inner_pathkey))
+						continue;
+				}
+				else
+					range_innerkeys = lappend(range_innerkeys, range_inner_pathkey);
+
+
+				mergejoin_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+															 range_outerkeys);
+
+				/*
+				 * And now we can make the path.
+				 *
+				 * Note: it's possible that the cheapest paths will already be sorted
+				 * properly.  try_mergejoin_path will detect that case and suppress an
+				 * explicit sort step, so we needn't do so here.
+				 */
+				try_mergejoin_path(root,
+								   joinrel,
+								   outer_path,
+								   inner_path,
+								   mergejoin_pathkeys,
+								   cur_mergeclauses,
+								   cur_rangeclause,
+								   range_outerkeys,
+								   range_innerkeys,
+								   jointype,
+								   extra,
+								   false);
+			}
+		}
 	}
 }
 
@@ -1364,6 +1513,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 					   merge_pathkeys,
 					   mergeclauses,
 					   NIL,
+					   NIL,
 					   innersortkeys,
 					   jointype,
 					   extra,
@@ -1462,6 +1612,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 							   newclauses,
 							   NIL,
 							   NIL,
+							   NIL,
 							   jointype,
 							   extra,
 							   is_partial);
@@ -1506,6 +1657,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 								   newclauses,
 								   NIL,
 								   NIL,
+								   NIL,
 								   jointype,
 								   extra,
 								   is_partial);
@@ -2272,3 +2424,138 @@ select_mergejoin_clauses(PlannerInfo *root,
 
 	return result_list;
 }
+
+/*
+ * range_clause_order
+ */
+static int
+range_clause_order(RestrictInfo *first,
+				   RestrictInfo *second)
+{
+	/*
+	 * Extract details from first restrictinfo
+	 */
+	Node   *first_left = get_leftop(first->clause),
+		   *first_right = get_rightop(first->clause);
+	bool	first_outer_is_left = first->outer_is_left;
+	int		first_strategy = get_op_opfamily_strategy(((OpExpr *) first->clause)->opno,
+														(linitial_oid(first->rangeleftopfamilies)));
+	bool    first_less = (first_strategy == BTLessStrategyNumber ||
+							first_strategy == BTLessEqualStrategyNumber);
+
+	/*
+	 * Extract details from second restrictinfo
+	 */
+	Node   *second_left = get_leftop(second->clause),
+		   *second_right = get_rightop(second->clause);
+	bool	second_outer_is_left = second->outer_is_left;
+	int		second_strategy = get_op_opfamily_strategy(((OpExpr *) second->clause)->opno,
+														(linitial_oid(second->rangeleftopfamilies)));
+	bool    second_less = (second_strategy == BTLessStrategyNumber ||
+							second_strategy == BTLessEqualStrategyNumber);
+
+	/*
+	 * Check for rangeclause
+	 */
+	if (first_less && second_less)
+	{
+		if (!first_outer_is_left && second_outer_is_left &&
+				equal(first_left, second_right))
+			return 2;
+		else if (first_outer_is_left && !second_outer_is_left &&
+				equal(first_right, second_left))
+			return 1;
+	}
+	else if (!first_less && !second_less)
+	{
+		if (!first_outer_is_left && second_outer_is_left &&
+				equal(first_left, second_right))
+			return 1;
+		else if (first_outer_is_left && !second_outer_is_left &&
+				equal(first_right, second_left))
+			return 2;
+	}
+	else if (first_less && !second_less)
+	{
+		if (!first_outer_is_left && !second_outer_is_left &&
+				equal(first_left, second_left))
+			return 2;
+		else if (first_outer_is_left && second_outer_is_left &&
+				equal(first_right, second_right))
+			return 1;
+	}
+	else if (!first_less && second_less)
+	{
+		if (!first_outer_is_left && !second_outer_is_left &&
+				equal(first_left, second_left))
+			return 1;
+		else if (first_outer_is_left && second_outer_is_left &&
+				equal(first_right, second_right))
+			return 2;
+	}
+
+	return 0;
+}
+
+/*
+ * select_rangejoin_clauses
+ */
+static List *
+select_rangejoin_clauses(RelOptInfo *outerrel,
+						 RelOptInfo *innerrel,
+						 List *restrictlist)
+{
+	List	   *result_list = NIL;
+	ListCell   *l;
+	List	   *range_candidates = NIL;
+
+	foreach(l, restrictlist)
+	{
+		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
+		OpExpr		 *clause = (OpExpr *) restrictinfo->clause;
+
+		/* Check that clause is a rangejoinable operator clause */
+		if (restrictinfo->rangeleftopfamilies == NIL)
+			continue;			/* not rangejoinable */
+
+		/*
+		 * Check if clause has the form "outer op inner" or "inner op outer".
+		 */
+		if (!clause_sides_match_join(restrictinfo, outerrel, innerrel))
+			continue;			/* no good for these input relations */
+
+		if (restrictinfo->rangeleftopfamilies == restrictinfo->rangerightopfamilies)
+		{
+			ListCell *lc;
+			List	 *range_clause;
+
+			foreach(lc, range_candidates)
+			{
+				RestrictInfo *candidate = (RestrictInfo *) lfirst(lc);
+
+				switch (range_clause_order(restrictinfo, candidate))
+				{
+				case 1:
+					range_clause = list_make2(restrictinfo, candidate);
+					result_list = lappend(result_list, range_clause);
+					break;
+
+				case 2:
+					range_clause = list_make2(candidate, restrictinfo);
+					result_list = lappend(result_list, range_clause);
+					break;
+				default:
+					break;
+				}
+			}
+			range_candidates = lappend(range_candidates, restrictinfo);
+		}
+		else if ((clause->opno == OID_RANGE_CONTAINS_ELEM_OP && restrictinfo->outer_is_left) ||
+				(clause->opno == OID_RANGE_ELEM_CONTAINED_OP && !restrictinfo->outer_is_left))
+		{
+			result_list = lappend(result_list, list_make1(restrictinfo));
+		}
+	}
+	list_free(range_candidates);
+	return result_list;
+}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..eb5b54fbbd 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -19,6 +19,7 @@
 
 #include "access/stratnum.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_operator.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/plannodes.h"
@@ -1214,6 +1215,60 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo)
 								 true);
 }
 
+/*
+ * initialize_rangeclause_eclasses
+ */
+void
+initialize_rangeclause_eclasses(PlannerInfo *root,
+								RestrictInfo *restrictinfo)
+{
+	Expr   *clause = restrictinfo->clause;
+	Oid		lefttype,
+			righttype,
+			opno;
+
+	/* Should be a rangeclause ... */
+	Assert(restrictinfo->rangeleftopfamilies != NIL &&
+			restrictinfo->rangerightopfamilies != NIL);
+	/* ... with links not yet set */
+	Assert(restrictinfo->left_ec == NULL);
+	Assert(restrictinfo->right_ec == NULL);
+
+	opno = ((OpExpr *) clause)->opno;
+
+	/* Need the declared input types of the operator */
+	op_input_types(opno, &lefttype, &righttype);
+
+	if(opno == OID_RANGE_CONTAINS_ELEM_OP)
+		righttype = exprType(get_rightop(clause));
+
+	else if(opno == OID_RANGE_ELEM_CONTAINED_OP)
+		lefttype = exprType(get_leftop(clause));
+
+
+	/* Find or create a matching EquivalenceClass for each side */
+	restrictinfo->left_ec =
+		get_eclass_for_sort_expr(root,
+				 	 	 	 	 (Expr *) get_leftop(clause),
+								 restrictinfo->nullable_relids,
+								 restrictinfo->rangeleftopfamilies,
+								 lefttype,
+								 ((OpExpr *) clause)->inputcollid,
+								 0,
+								 NULL,
+								 true);
+	restrictinfo->right_ec =
+		get_eclass_for_sort_expr(root,
+								 (Expr *) get_rightop(clause),
+								 restrictinfo->nullable_relids,
+								 restrictinfo->rangerightopfamilies,
+								 righttype,
+								 ((OpExpr *) clause)->inputcollid,
+								 0,
+								 NULL,
+								 true);
+}
+
 /*
  * update_mergeclause_eclasses
  *		Make the cached EquivalenceClass links valid in a mergeclause
@@ -1347,6 +1402,64 @@ find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 	return mergeclauses;
 }
 
+/*
+ * find_rangeclauses_for_outer_pathkeys
+ */
+List *
+find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
+									List *pathkeys,
+									List *mergeclauses,
+									List *rangeclauses)
+{
+	ListCell   *i;
+	RestrictInfo *mergeclause = NULL;
+	List *result_list = NIL;
+
+	if(mergeclauses)
+		mergeclause = llast(mergeclauses);
+
+	foreach(i, pathkeys)
+	{
+		PathKey    *pathkey = (PathKey *) lfirst(i);
+		EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
+		ListCell   *j;
+
+		if(mergeclause != NULL)
+		{
+			if(mergeclause->outer_is_left)
+			{
+				if(mergeclause->left_ec != pathkey_ec)
+					continue;
+			}
+			else if(mergeclause->right_ec != pathkey_ec)
+				continue;
+		}
+
+		foreach(j, rangeclauses)
+		{
+			List				*rangeclause = (List *) lfirst(j);
+			RestrictInfo 		*rinfo = (RestrictInfo *) linitial(rangeclause);
+			EquivalenceClass 	*clause_ec;
+
+			clause_ec = rinfo->outer_is_left ?
+				rinfo->left_ec : rinfo->right_ec;
+
+			if (clause_ec == pathkey_ec)
+				result_list = lappend(result_list, rangeclause);
+		}
+
+		/*
+		 * Was it the last possible pathkey?
+		 */
+		if(mergeclause == NULL)
+			break;
+		else
+			mergeclause = NULL;
+
+  }
+  return result_list;
+}
+
 /*
  * select_outer_pathkeys_for_merge
  *	  Builds a pathkey list representing a possible sort ordering
@@ -1522,6 +1635,37 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 	return pathkeys;
 }
 
+/*
+ * select_outer_pathkeys_for_range
+ */
+List *
+select_outer_pathkeys_for_range(PlannerInfo *root,
+								List *rangeclauses)
+{
+	EquivalenceClass	*ec;
+	PathKey 			*pathkey;
+	List 				*pathkeys = NIL;
+	ListCell			*lc;
+
+	foreach(lc, rangeclauses){
+		List *rangeclause = lfirst(lc);
+		RestrictInfo *rinfo = linitial(rangeclause);
+
+		if (rinfo->outer_is_left)
+			ec = rinfo->left_ec;
+		else
+			ec = rinfo->right_ec;
+
+		pathkey = make_canonical_pathkey(root,
+										 ec,
+										 linitial_oid(ec->ec_opfamilies),
+										 BTLessStrategyNumber,
+										 false);
+		pathkeys = lappend(pathkeys, pathkey);
+	}
+	return pathkeys;
+}
+
 /*
  * make_inner_pathkeys_for_merge
  *	  Builds a pathkey list representing the explicit sort order that
@@ -1621,6 +1765,35 @@ make_inner_pathkeys_for_merge(PlannerInfo *root,
 	return pathkeys;
 }
 
+/*
+ * make_inner_pathkey_for_range
+ */
+PathKey *
+make_inner_pathkey_for_range(PlannerInfo *root,
+							 List *rangeclause)
+{
+	EquivalenceClass *ec;
+	PathKey 	 *pathkey;
+	RestrictInfo *rinfo;
+
+	if(rangeclause == NIL)
+		return NULL;
+
+	rinfo = linitial(rangeclause);
+
+	if (rinfo->outer_is_left)
+		ec = rinfo->right_ec;
+	else
+		ec = rinfo->left_ec;
+
+	pathkey = make_canonical_pathkey(root,
+									 ec,
+									 linitial_oid(ec->ec_opfamilies),
+									 BTLessStrategyNumber,
+									 false);
+	return pathkey;
+}
+
 /*
  * trim_mergeclauses_for_inner_pathkeys
  *	  This routine trims a list of mergeclauses to include just those that
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 439e6b6426..feb1270a62 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -247,6 +247,7 @@ static Hash *make_hash(Plan *lefttree,
 static MergeJoin *make_mergejoin(List *tlist,
 								 List *joinclauses, List *otherclauses,
 								 List *mergeclauses,
+								 List *rangeclause,
 								 Oid *mergefamilies,
 								 Oid *mergecollations,
 								 int *mergestrategies,
@@ -4304,6 +4305,7 @@ create_mergejoin_plan(PlannerInfo *root,
 	List	   *joinclauses;
 	List	   *otherclauses;
 	List	   *mergeclauses;
+	List	   *rangeclause;
 	List	   *outerpathkeys;
 	List	   *innerpathkeys;
 	int			nClauses;
@@ -4358,6 +4360,9 @@ create_mergejoin_plan(PlannerInfo *root,
 	mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
 	joinclauses = list_difference(joinclauses, mergeclauses);
 
+	rangeclause = get_actual_clauses(best_path->path_rangeclause);
+	joinclauses = list_difference(joinclauses, rangeclause);
+
 	/*
 	 * Replace any outer-relation variables with nestloop params.  There
 	 * should not be any in the mergeclauses.
@@ -4368,6 +4373,8 @@ create_mergejoin_plan(PlannerInfo *root,
 			replace_nestloop_params(root, (Node *) joinclauses);
 		otherclauses = (List *)
 			replace_nestloop_params(root, (Node *) otherclauses);
+		rangeclause = (List *)
+			replace_nestloop_params(root, (Node *) rangeclause);
 	}
 
 	/*
@@ -4584,6 +4591,7 @@ create_mergejoin_plan(PlannerInfo *root,
 							   joinclauses,
 							   otherclauses,
 							   mergeclauses,
+							   rangeclause,
 							   mergefamilies,
 							   mergecollations,
 							   mergestrategies,
@@ -5885,6 +5893,7 @@ make_mergejoin(List *tlist,
 			   List *joinclauses,
 			   List *otherclauses,
 			   List *mergeclauses,
+			   List *rangeclause,
 			   Oid *mergefamilies,
 			   Oid *mergecollations,
 			   int *mergestrategies,
@@ -5911,6 +5920,7 @@ make_mergejoin(List *tlist,
 	node->join.jointype = jointype;
 	node->join.inner_unique = inner_unique;
 	node->join.joinqual = joinclauses;
+	node->rangeclause = rangeclause;
 
 	return node;
 }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 3ac853d9ef..ad86978cfa 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -16,6 +16,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_operator.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -77,6 +78,7 @@ static bool check_equivalence_delay(PlannerInfo *root,
 									RestrictInfo *restrictinfo);
 static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
 static void check_mergejoinable(RestrictInfo *restrictinfo);
+static void check_rangejoinable(RestrictInfo *restrictinfo);
 static void check_hashjoinable(RestrictInfo *restrictinfo);
 static void check_resultcacheable(RestrictInfo *restrictinfo);
 
@@ -1877,6 +1879,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	 */
 	check_mergejoinable(restrictinfo);
 
+	check_rangejoinable(restrictinfo);
+
 	/*
 	 * If it is a true equivalence clause, send it to the EquivalenceClass
 	 * machinery.  We do *not* attach it directly to any restriction or join
@@ -1962,6 +1966,13 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 		}
 	}
 
+	if(restrictinfo->rangeleftopfamilies)
+	{
+		Assert(restrictinfo->rangerightopfamilies);
+
+		initialize_rangeclause_eclasses(root, restrictinfo);
+	}
+
 	/* No EC special case applies, so push it into the clause lists */
 	distribute_restrictinfo_to_rels(root, restrictinfo);
 }
@@ -2677,6 +2688,50 @@ check_mergejoinable(RestrictInfo *restrictinfo)
 	 */
 }
 
+/*
+ * check_rangejoinable
+ */
+static void
+check_rangejoinable(RestrictInfo *restrictinfo)
+{
+	OpExpr	   *clause = (OpExpr *) restrictinfo->clause;
+	Oid			opno = clause->opno;
+
+	if (!restrictinfo->can_join)
+		return;
+	if (contain_volatile_functions((Node *) clause))
+		return;
+
+	if (opno == OID_RANGE_CONTAINS_ELEM_OP ||
+			opno == OID_RANGE_ELEM_CONTAINED_OP)
+	{
+		Node		   *leftarg = get_leftop(clause),
+					   *rightarg = get_rightop(clause);
+
+		Oid				lefttype = exprType(leftarg),
+						righttype = exprType(rightarg);
+
+		TypeCacheEntry *left_typecache = lookup_type_cache(lefttype, TYPECACHE_CMP_PROC),
+					   *right_typecache = lookup_type_cache(righttype, TYPECACHE_CMP_PROC);
+
+		restrictinfo->rangeleftopfamilies =
+				lappend_oid(restrictinfo->rangeleftopfamilies,
+							left_typecache->btree_opf);
+
+		restrictinfo->rangerightopfamilies =
+				lappend_oid(restrictinfo->rangerightopfamilies,
+							right_typecache->btree_opf);
+	}
+	else
+	{
+		List *opfamilies = get_rangejoin_opfamilies(opno);
+
+		restrictinfo->rangeleftopfamilies = opfamilies;
+		restrictinfo->rangerightopfamilies = opfamilies;
+
+	}
+}
+
 /*
  * check_hashjoinable
  *	  If the restrictinfo's clause is hashjoinable, set the hashjoin
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 61ccfd300b..75d35a1a8d 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2024,6 +2024,14 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
 										 (Index) 0,
 										 rtoffset,
 										 NUM_EXEC_QUAL((Plan *) join));
+
+		mj->rangeclause = fix_join_expr(root,
+										 mj->rangeclause,
+										 outer_itlist,
+										 inner_itlist,
+										 (Index) 0,
+										 rtoffset,
+										 NUM_EXEC_QUAL((Plan *) join));
 	}
 	else if (IsA(join, HashJoin))
 	{
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 9ce5f95e3b..96c57f6d6f 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -2505,6 +2505,7 @@ create_mergejoin_path(PlannerInfo *root,
 					  List *pathkeys,
 					  Relids required_outer,
 					  List *mergeclauses,
+					  List *rangeclause,
 					  List *outersortkeys,
 					  List *innersortkeys)
 {
@@ -2533,6 +2534,7 @@ create_mergejoin_path(PlannerInfo *root,
 	pathnode->jpath.innerjoinpath = inner_path;
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
 	pathnode->path_mergeclauses = mergeclauses;
+	pathnode->path_rangeclause = rangeclause;
 	pathnode->outersortkeys = outersortkeys;
 	pathnode->innersortkeys = innersortkeys;
 	/* pathnode->skip_mark_restore will be set by final_cost_mergejoin */
@@ -4135,6 +4137,7 @@ do { \
 				REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
 				ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
 				ADJUST_CHILD_ATTRS(mpath->path_mergeclauses);
+				ADJUST_CHILD_ATTRS(mpath->path_rangeclause);
 				new_path = (Path *) mpath;
 			}
 			break;
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index aa9fb3a9fa..8887341d74 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -201,6 +201,8 @@ make_restrictinfo_internal(PlannerInfo *root,
 	restrictinfo->outer_selec = -1;
 
 	restrictinfo->mergeopfamilies = NIL;
+	restrictinfo->rangeleftopfamilies = NIL;
+	restrictinfo->rangerightopfamilies = NIL;
 
 	restrictinfo->left_ec = NULL;
 	restrictinfo->right_ec = NULL;
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 815175a654..3972480519 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -2507,6 +2507,66 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
 	return true;
 }
 
+/*
+ * Test whether range r is right of a specific element value.
+ */
+bool
+elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
+{
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	int32		cmp;
+
+	range_deserialize(typcache, r, &lower, &upper, &empty);
+
+	if (empty)
+		return true;
+
+	if (!lower.infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  lower.val, val));
+		if (cmp > 0)
+			return true;
+		if (cmp == 0 && !lower.inclusive)
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Test whether range r is left of a specific element value.
+ */
+bool
+elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
+{
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	int32		cmp;
+
+	range_deserialize(typcache, r, &lower, &upper, &empty);
+
+	if (empty)
+		return true;
+
+	if (!upper.infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  upper.val, val));
+		if (cmp < 0)
+			return true;
+		if (cmp == 0 && !upper.inclusive)
+			return true;
+	}
+
+	return false;
+}
+
 
 /*
  * datum_compute_size() and datum_write() are used to insert the bound
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 6bba5f8ec4..76385c99d6 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -389,6 +389,40 @@ get_mergejoin_opfamilies(Oid opno)
 	return result;
 }
 
+/*
+ * get_rangejoin_opfamilies
+ */
+List *
+get_rangejoin_opfamilies(Oid opno)
+{
+	List	   *result = NIL;
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * Search pg_amop to see if the target operator is registered as the "<",
+	 * "<=", ">" or ">=" operator of any btree opfamily.
+	 */
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == BTREE_AM_OID &&
+			(aform->amopstrategy == BTLessStrategyNumber ||
+				aform->amopstrategy == BTLessEqualStrategyNumber ||
+				aform->amopstrategy == BTGreaterStrategyNumber ||
+				aform->amopstrategy == BTGreaterEqualStrategyNumber))
+			result = lappend_oid(result, aform->amopfamily);
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_compatible_hash_operators
  *		Get the OID(s) of hash equality operator(s) compatible with the given
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 7795a69490..a7e6505ef7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1947,6 +1947,7 @@ typedef struct NestLoopState
  */
 /* private in nodeMergejoin.c: */
 typedef struct MergeJoinClauseData *MergeJoinClause;
+typedef struct RangeJoinData *RangeData;
 
 typedef struct MergeJoinState
 {
@@ -1968,6 +1969,8 @@ typedef struct MergeJoinState
 	TupleTableSlot *mj_NullInnerTupleSlot;
 	ExprContext *mj_OuterEContext;
 	ExprContext *mj_InnerEContext;
+	RangeData	 mj_RangeData;
+	bool		 mj_RangeJoin;
 } MergeJoinState;
 
 /* ----------------
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index b7b2817a5d..b132bc3037 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1636,6 +1636,7 @@ typedef struct MergePath
 {
 	JoinPath	jpath;
 	List	   *path_mergeclauses;	/* join clauses to be used for merge */
+	List	   *path_rangeclause;	/* join clause to be used for range merge */
 	List	   *outersortkeys;	/* keys for explicit sort, if any */
 	List	   *innersortkeys;	/* keys for explicit sort, if any */
 	bool		skip_mark_restore;	/* can executor skip mark/restore? */
@@ -2091,6 +2092,8 @@ typedef struct RestrictInfo
 
 	/* valid if clause is mergejoinable, else NIL */
 	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List	   *rangeleftopfamilies;
+	List	   *rangerightopfamilies;
 
 	/* cache space for mergeclause processing; NULL if not yet set */
 	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
@@ -2517,6 +2520,7 @@ typedef struct JoinPathExtraData
 {
 	List	   *restrictlist;
 	List	   *mergeclause_list;
+	List	   *rangeclause_list;
 	bool		inner_unique;
 	SpecialJoinInfo *sjinfo;
 	SemiAntiJoinFactors semifactors;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aaa3b65d04..789888fe25 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -751,6 +751,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 */
+	List	   *rangeclause;	/* rangeclause as expression tree */
 } MergeJoin;
 
 /* ----------------
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 53261ee91f..01cba9d360 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -167,6 +167,7 @@ extern MergePath *create_mergejoin_path(PlannerInfo *root,
 										List *pathkeys,
 										Relids required_outer,
 										List *mergeclauses,
+										List *rangeclause,
 										List *outersortkeys,
 										List *innersortkeys);
 
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index f1d111063c..0acfd297eb 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -231,17 +231,27 @@ extern List *make_pathkeys_for_sortclauses(PlannerInfo *root,
 										   List *tlist);
 extern void initialize_mergeclause_eclasses(PlannerInfo *root,
 											RestrictInfo *restrictinfo);
+extern void initialize_rangeclause_eclasses(PlannerInfo *root,
+											RestrictInfo *restrictinfo);
 extern void update_mergeclause_eclasses(PlannerInfo *root,
 										RestrictInfo *restrictinfo);
 extern List *find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 												  List *pathkeys,
 												  List *restrictinfos);
+extern List *find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
+												  List *pathkeys,
+												  List *mergeclauses,
+												  List *rangeclauses);
 extern List *select_outer_pathkeys_for_merge(PlannerInfo *root,
 											 List *mergeclauses,
 											 RelOptInfo *joinrel);
+extern List *select_outer_pathkeys_for_range(PlannerInfo *root,
+											 List *rangeclauses);
 extern List *make_inner_pathkeys_for_merge(PlannerInfo *root,
 										   List *mergeclauses,
 										   List *outer_pathkeys);
+extern PathKey *make_inner_pathkey_for_range(PlannerInfo *root,
+											 List *rangeclause);
 extern List *trim_mergeclauses_for_inner_pathkeys(PlannerInfo *root,
 												  List *mergeclauses,
 												  List *pathkeys);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 77871aaefc..a47beb8be5 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -79,6 +79,7 @@ extern bool get_ordering_op_properties(Oid opno,
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
 extern List *get_mergejoin_opfamilies(Oid opno);
+extern List *get_rangejoin_opfamilies(Oid opno);
 extern bool get_compatible_hash_operators(Oid opno,
 										  Oid *lhs_opno, Oid *rhs_opno);
 extern bool get_op_hash_functions(Oid opno,
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 04c302c619..51bdc5308b 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -95,6 +95,8 @@ typedef struct
  */
 
 extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
+extern bool elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r);
+extern bool elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r);
 
 /* internal versions of the above */
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
#4Jeff Davis
pgsql@j-davis.com
In reply to: David Rowley (#2)
Re: Patch: Range Merge Join

On Thu, 2021-06-10 at 15:09 +1200, David Rowley wrote:

It shouldn't be a blocker for you, but just so you're aware, there
was
a previous proposal for this in [1] and a patch in [2]. I've include
Jeff here just so he's aware of this. Jeff may wish to state his
intentions with his own patch. It's been a few years now.

Great, thank you for working on this!

I'll start with the reason I set the work down before: it did not work
well with multiple join keys. That might be fine, but I also started
thinking it was specialized enough that I wanted to look into doing it
as an extension using the CustomScan mechanism.

Do you have any solution to working better with multiple join keys? And
do you have thoughts on whether it would be a good candidate for the
CustomScan extension mechanism, which would make it easier to
experiment with?

Regards,
Jeff Davis

#5Jaime Casanova
jcasanov@systemguards.com.ec
In reply to: Jeff Davis (#4)
Re: Patch: Range Merge Join

On Thu, Jun 10, 2021 at 07:14:32PM -0700, Jeff Davis wrote:

On Thu, 2021-06-10 at 15:09 +1200, David Rowley wrote:

It shouldn't be a blocker for you, but just so you're aware, there
was
a previous proposal for this in [1] and a patch in [2]. I've include
Jeff here just so he's aware of this. Jeff may wish to state his
intentions with his own patch. It's been a few years now.

Great, thank you for working on this!

I'll start with the reason I set the work down before: it did not work
well with multiple join keys. That might be fine, but I also started
thinking it was specialized enough that I wanted to look into doing it
as an extension using the CustomScan mechanism.

Do you have any solution to working better with multiple join keys? And
do you have thoughts on whether it would be a good candidate for the
CustomScan extension mechanism, which would make it easier to
experiment with?

Hi,

It seems this has been stalled since jun-2021. I intend mark this as
RwF unless someone speaks in the next hour or so.

--
Jaime Casanova
Director de Servicios Profesionales
SystemGuards - Consultores de PostgreSQL

#6Jaime Casanova
jcasanov@systemguards.com.ec
In reply to: Jaime Casanova (#5)
2 attachment(s)
Re: Patch: Range Merge Join

On Mon, Oct 04, 2021 at 04:27:54PM -0500, Jaime Casanova wrote:

On Thu, Jun 10, 2021 at 07:14:32PM -0700, Jeff Davis wrote:

I'll start with the reason I set the work down before: it did not work
well with multiple join keys. That might be fine, but I also started
thinking it was specialized enough that I wanted to look into doing it
as an extension using the CustomScan mechanism.

Do you have any solution to working better with multiple join keys? And
do you have thoughts on whether it would be a good candidate for the
CustomScan extension mechanism, which would make it easier to
experiment with?

Hi,

It seems this has been stalled since jun-2021. I intend mark this as
RwF unless someone speaks in the next hour or so.

Thomas <thomasmannhart97@gmail.com> wrote me:

Hi,

I registered this patch for the commitfest in july. It had not been reviewed and moved to the next CF. I still like to submit it.

Regards,
Thomas

Just for clarification RwF doesn't imply reject of the patch.
Nevertheless, given that there has been no real review I will mark this
patch as "Waiting on Author" and move it to the next CF.

Meanwhile, cfbot (aka http://commitfest.cputube.org) says this doesn't
compile. Here is a little patch to fix the compilation errors, after
that it passes all tests in make check-world.

Also attached a rebased version of your patch with the fixes so we turn
cfbot entry green again

--
Jaime Casanova
Director de Servicios Profesionales
SystemGuards - Consultores de PostgreSQL

Attachments:

rmj_fixes_to_compile.txttext/plain; charset=us-asciiDownload
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 2dccba24b3..d18b4a4605 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -294,7 +294,7 @@ MJCreateRangeData(List *rangeclause,
 {
 	RangeData data;
 
-	Assert(list_legth(node->rangeclause) < 3);
+	Assert(list_length(rangeclause) < 3);
 
 	data = (RangeData) palloc0(sizeof(RangeJoinData));
 
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 8a2778485c..24ee41b9ed 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3440,8 +3440,8 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 
 
     /* Protect some assumptions below that rowcounts aren't zero */
-    if (inner_path_rows <= 0)
-        inner_path_rows = 1;
+	if (inner_path_rows <= 0)
+		inner_path_rows = 1;
 
 	/* Mark the path with the correct row estimate */
 	if (path->jpath.path.param_info)
postgres-rmj-20211004.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 10644dfac4..9b7503630c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1206,8 +1206,16 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			pname = sname = "Nested Loop";
 			break;
 		case T_MergeJoin:
-			pname = "Merge";	/* "Join" gets added by jointype switch */
-			sname = "Merge Join";
+			if(((MergeJoin *) plan)->rangeclause)
+			{
+				pname = "Range Merge";	/* "Join" gets added by jointype switch */
+				sname = "Range Merge Join";
+			}
+			else
+			{
+				pname = "Merge";	/* "Join" gets added by jointype switch */
+				sname = "Merge Join";
+			}
 			break;
 		case T_HashJoin:
 			pname = "Hash";		/* "Join" gets added by jointype switch */
@@ -1948,6 +1956,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_MergeJoin:
 			show_upper_qual(((MergeJoin *) plan)->mergeclauses,
 							"Merge Cond", planstate, ancestors, es);
+			show_upper_qual(((MergeJoin *) plan)->rangeclause,
+							"Range Cond", planstate, ancestors, es);
 			show_upper_qual(((MergeJoin *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
 			if (((MergeJoin *) plan)->join.joinqual)
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index b41454ab6d..d18b4a4605 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -93,11 +93,15 @@
 #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/rangetypes.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/typcache.h"
 
 
 /*
@@ -140,6 +144,18 @@ typedef struct MergeJoinClauseData
 	SortSupportData ssup;
 }			MergeJoinClauseData;
 
+/*
+ * Runtime data for the range clause
+ */
+typedef struct RangeJoinData
+{
+	ExprState *startClause;
+	ExprState *endClause;
+	ExprState *rangeExpr;
+	ExprState *elemExpr;
+
+}			RangeJoinData;
+
 /* Result type for MJEvalOuterValues and MJEvalInnerValues */
 typedef enum
 {
@@ -269,6 +285,57 @@ MJExamineQuals(List *mergeclauses,
 	return clauses;
 }
 
+/*
+ * MJCreateRangeData
+ */
+static RangeData
+MJCreateRangeData(List *rangeclause,
+				   PlanState *parent)
+{
+	RangeData data;
+
+	Assert(list_length(rangeclause) < 3);
+
+	data = (RangeData) palloc0(sizeof(RangeJoinData));
+
+	data->startClause = NULL;
+	data->endClause = NULL;
+	data->rangeExpr = NULL;
+	data->elemExpr = NULL;
+
+	if(list_length(rangeclause) == 2)
+	{
+		data->startClause = ExecInitExpr(linitial(rangeclause), parent);
+		data->endClause = ExecInitExpr(lsecond(rangeclause), parent);
+	}
+	else
+	{
+		OpExpr		*qual = (OpExpr *) linitial(rangeclause);
+		ExprState	*lexpr;
+		ExprState	*rexpr;
+
+		/*
+		 * Prepare the input expressions for execution.
+		 */
+		lexpr = ExecInitExpr((Expr *) get_leftop(qual), parent);
+		rexpr = ExecInitExpr((Expr *) get_rightop(qual), parent);
+
+		if(qual->opno == OID_RANGE_CONTAINS_ELEM_OP)
+		{
+			data->rangeExpr = lexpr;
+			data->elemExpr = rexpr;
+		}
+		else
+		{
+			Assert(qual->opno == OID_RANGE_ELEM_CONTAINED_OP);
+			data->rangeExpr = rexpr;
+			data->elemExpr = lexpr;
+		}
+	}
+
+	return data;
+}
+
 /*
  * MJEvalOuterValues
  *
@@ -445,6 +512,73 @@ MJCompare(MergeJoinState *mergestate)
 }
 
 
+/*
+ * MJCompareRange
+ *
+ * Compare the rangejoinable values of the current two input tuples
+ * and return 0 if they are equal (ie, the outer interval contains the inner),
+ * >0 if outer > inner, <0 if outer < inner.
+ */
+static int
+MJCompareRange(MergeJoinState *mergestate)
+{
+	int 		  result = 0;
+	bool 		  isNull;
+	MemoryContext oldContext;
+	RangeData	  rangeData = mergestate->mj_RangeData;
+	ExprContext	 *econtext = mergestate->js.ps.ps_ExprContext;
+	ExprState	 *endClause = rangeData->endClause,
+				 *startClause = rangeData->startClause,
+				 *rangeExpr = rangeData->rangeExpr,
+				 *elemExpr = rangeData->elemExpr;
+
+	/*
+	 * Call the comparison functions in short-lived context, in case they leak
+	 * memory.
+	 */
+	ResetExprContext(econtext);
+
+	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+	econtext->ecxt_outertuple = mergestate->mj_OuterTupleSlot;
+	econtext->ecxt_innertuple = mergestate->mj_InnerTupleSlot;
+
+	if (endClause != NULL)
+	{
+		Assert(startClause != NULL);
+
+		if (!ExecEvalExprSwitchContext(endClause, econtext, &isNull))
+			result = -1;
+		else if (!ExecEvalExprSwitchContext(startClause, econtext, &isNull))
+			result = 1;
+	}
+	else
+	{
+		Datum			rangeDatum,
+    					elemDatum;
+		Oid				rangeType;
+		TypeCacheEntry *typecache;
+
+		Assert(rangeExpr != NULL && elemExpr != NULL);
+
+		rangeDatum = ExecEvalExprSwitchContext(rangeExpr, econtext, &isNull);
+		elemDatum = ExecEvalExprSwitchContext(elemExpr, econtext, &isNull);
+
+		rangeType = exprType((Node *) rangeExpr->expr);
+		typecache = lookup_type_cache(rangeType, TYPECACHE_RANGE_INFO);
+
+		if (elem_after_range_internal(typecache, elemDatum, DatumGetRangeTypeP(rangeDatum)))
+			result = -1;
+		else if (elem_before_range_internal(typecache, elemDatum, DatumGetRangeTypeP(rangeDatum)))
+			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.
@@ -604,6 +738,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprState  *otherqual;
 	bool		qualResult;
 	int			compareResult;
+	int			compareRangeResult;
 	PlanState  *innerPlan;
 	TupleTableSlot *innerTupleSlot;
 	PlanState  *outerPlan;
@@ -611,6 +746,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprContext *econtext;
 	bool		doFillOuter;
 	bool		doFillInner;
+	bool		isRangeJoin;
 
 	CHECK_FOR_INTERRUPTS();
 
@@ -624,6 +760,7 @@ ExecMergeJoin(PlanState *pstate)
 	otherqual = node->js.ps.qual;
 	doFillOuter = node->mj_FillOuter;
 	doFillInner = node->mj_FillInner;
+	isRangeJoin = node->mj_RangeJoin;
 
 	/*
 	 * Reset per-tuple memory context to free any expression evaluation
@@ -891,8 +1028,23 @@ ExecMergeJoin(PlanState *pstate)
 						compareResult = MJCompare(node);
 						MJ_DEBUG_COMPARE(compareResult);
 
-						if (compareResult == 0)
-							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						if(compareResult == 0)
+						{
+							if(isRangeJoin)
+							{
+								compareRangeResult = MJCompareRange(node);
+
+								if (compareRangeResult == 0)
+									node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+								else
+								{
+									Assert(compareRangeResult < 0);
+									node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+								}
+							}
+							else
+								node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						}
 						else
 						{
 							Assert(compareResult < 0);
@@ -1085,7 +1237,19 @@ ExecMergeJoin(PlanState *pstate)
 						/* we need not do MJEvalInnerValues again */
 					}
 
-					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					if(isRangeJoin)
+					{
+						compareRangeResult = MJCompareRange(node);
+
+						if(compareRangeResult == 0)
+							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						else if(compareRangeResult < 0)
+							node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+						else /* compareRangeResult > 0 */
+							node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+					}
+					else
+						node->mj_JoinState = EXEC_MJ_JOINTUPLES;
 				}
 				else
 				{
@@ -1184,12 +1348,28 @@ ExecMergeJoin(PlanState *pstate)
 
 				if (compareResult == 0)
 				{
-					if (!node->mj_SkipMarkRestore)
-						ExecMarkPos(innerPlan);
-
-					MarkInnerTuple(node->mj_InnerTupleSlot, node);
-
-					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					if(isRangeJoin)
+					{
+						compareRangeResult = MJCompareRange(node);
+						if(compareRangeResult == 0)
+						{
+							if (!node->mj_SkipMarkRestore)
+								ExecMarkPos(innerPlan);
+							MarkInnerTuple(node->mj_InnerTupleSlot, node);
+							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						}
+						else if(compareRangeResult < 0)
+							node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+						else /* compareRangeResult > 0 */
+							node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+					}
+					else
+					{
+						if (!node->mj_SkipMarkRestore)
+							ExecMarkPos(innerPlan);
+						MarkInnerTuple(node->mj_InnerTupleSlot, node);
+						node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					}
 				}
 				else if (compareResult < 0)
 					node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
@@ -1532,6 +1712,17 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 		ExecInitQual(node->join.joinqual, (PlanState *) mergestate);
 	/* mergeclauses are handled below */
 
+	/*
+	 * initialize range join
+	 */
+	if(node->rangeclause)
+	{
+		mergestate->mj_RangeData = MJCreateRangeData(node->rangeclause, (PlanState *) mergestate);
+		mergestate->mj_RangeJoin = true;
+	}
+	else
+		mergestate->mj_RangeJoin = false;
+
 	/*
 	 * detect whether we need only consider the first matching inner tuple
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 228387eaee..bc76c69bda 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2349,6 +2349,8 @@ _copyRestrictInfo(const RestrictInfo *from)
 	COPY_SCALAR_FIELD(norm_selec);
 	COPY_SCALAR_FIELD(outer_selec);
 	COPY_NODE_FIELD(mergeopfamilies);
+	COPY_NODE_FIELD(rangeleftopfamilies);
+	COPY_NODE_FIELD(rangerightopfamilies);
 	/* EquivalenceClasses are never copied, so shallow-copy the pointers */
 	COPY_SCALAR_FIELD(left_ec);
 	COPY_SCALAR_FIELD(right_ec);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2e5ed77e18..6b478b31d6 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -765,6 +765,7 @@ _outMergeJoin(StringInfo str, const MergeJoin *node)
 
 	WRITE_BOOL_FIELD(skip_mark_restore);
 	WRITE_NODE_FIELD(mergeclauses);
+    WRITE_NODE_FIELD(rangeclause);
 
 	numCols = list_length(node->mergeclauses);
 
@@ -2243,6 +2244,7 @@ _outMergePath(StringInfo str, const MergePath *node)
 	_outJoinPathInfo(str, (const JoinPath *) node);
 
 	WRITE_NODE_FIELD(path_mergeclauses);
+	WRITE_NODE_FIELD(path_rangeclause);
 	WRITE_NODE_FIELD(outersortkeys);
 	WRITE_NODE_FIELD(innersortkeys);
 	WRITE_BOOL_FIELD(skip_mark_restore);
@@ -2559,6 +2561,8 @@ _outRestrictInfo(StringInfo str, const RestrictInfo *node)
 	WRITE_FLOAT_FIELD(norm_selec, "%.4f");
 	WRITE_FLOAT_FIELD(outer_selec, "%.4f");
 	WRITE_NODE_FIELD(mergeopfamilies);
+	WRITE_NODE_FIELD(rangeleftopfamilies);
+	WRITE_NODE_FIELD(rangerightopfamilies);
 	/* don't write left_ec, leads to infinite recursion in plan tree dump */
 	/* don't write right_ec, leads to infinite recursion in plan tree dump */
 	WRITE_NODE_FIELD(left_em);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index abf08b7a2f..1e71f26eb7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2173,6 +2173,7 @@ _readMergeJoin(void)
 
 	READ_BOOL_FIELD(skip_mark_restore);
 	READ_NODE_FIELD(mergeclauses);
+    READ_NODE_FIELD(rangeclause);
 
 	numCols = list_length(local_node->mergeclauses);
 
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1e4d404f02..3a97b9951b 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3419,6 +3419,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	double		inner_path_rows = inner_path->rows;
 	List	   *mergeclauses = path->path_mergeclauses;
 	List	   *innersortkeys = path->innersortkeys;
+	List	   *allclauses;
 	Cost		startup_cost = workspace->startup_cost;
 	Cost		run_cost = workspace->run_cost;
 	Cost		inner_run_cost = workspace->inner_run_cost;
@@ -3435,7 +3436,10 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 				rescannedtuples;
 	double		rescanratio;
 
-	/* Protect some assumptions below that rowcounts aren't zero */
+	allclauses = list_concat(list_copy(mergeclauses), path->path_rangeclause);
+
+
+    /* Protect some assumptions below that rowcounts aren't zero */
 	if (inner_path_rows <= 0)
 		inner_path_rows = 1;
 
@@ -3466,7 +3470,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * Compute cost of the mergequals and qpquals (other restriction clauses)
 	 * separately.
 	 */
-	cost_qual_eval(&merge_qual_cost, mergeclauses, root);
+	cost_qual_eval(&merge_qual_cost, allclauses, root);
 	cost_qual_eval(&qp_qual_cost, path->jpath.joinrestrictinfo, root);
 	qp_qual_cost.startup -= merge_qual_cost.startup;
 	qp_qual_cost.per_tuple -= merge_qual_cost.per_tuple;
@@ -3490,7 +3494,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * Get approx # tuples passing the mergequals.  We use approx_tuple_count
 	 * here because we need an estimate done with JOIN_INNER semantics.
 	 */
-	mergejointuples = approx_tuple_count(root, &path->jpath, mergeclauses);
+	mergejointuples = approx_tuple_count(root, &path->jpath, allclauses);
 
 	/*
 	 * When there are equal merge keys in the outer relation, the mergejoin
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 6407ede12a..19c697402f 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -16,6 +16,7 @@
 
 #include <math.h>
 
+#include "catalog/pg_operator.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
 #include "nodes/nodeFuncs.h"
@@ -24,6 +25,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
 /* Hook for plugins to get control in add_paths_to_joinrel() */
@@ -84,6 +86,9 @@ static List *select_mergejoin_clauses(PlannerInfo *root,
 									  List *restrictlist,
 									  JoinType jointype,
 									  bool *mergejoin_allowed);
+static List *select_rangejoin_clauses(RelOptInfo *outerrel,
+									  RelOptInfo *innerrel,
+									  List *restrictlist);
 static void generate_mergejoin_paths(PlannerInfo *root,
 									 RelOptInfo *joinrel,
 									 RelOptInfo *innerrel,
@@ -147,6 +152,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 
 	extra.restrictlist = restrictlist;
 	extra.mergeclause_list = NIL;
+	extra.rangeclause_list = NIL;
 	extra.sjinfo = sjinfo;
 	extra.param_source_rels = NULL;
 
@@ -207,6 +213,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 * it's a full join.
 	 */
 	if (enable_mergejoin || jointype == JOIN_FULL)
+	{
 		extra.mergeclause_list = select_mergejoin_clauses(root,
 														  joinrel,
 														  outerrel,
@@ -215,6 +222,12 @@ add_paths_to_joinrel(PlannerInfo *root,
 														  jointype,
 														  &mergejoin_allowed);
 
+		if (jointype == JOIN_INNER || jointype == JOIN_LEFT)
+			extra.rangeclause_list = select_rangejoin_clauses(outerrel,
+															  innerrel,
+															  restrictlist);
+	}
+
 	/*
 	 * If it's SEMI, ANTI, or inner_unique join, compute correction factors
 	 * for cost estimation.  These will be the same for all paths.
@@ -777,6 +790,7 @@ try_mergejoin_path(PlannerInfo *root,
 				   Path *inner_path,
 				   List *pathkeys,
 				   List *mergeclauses,
+				   List *rangeclause,
 				   List *outersortkeys,
 				   List *innersortkeys,
 				   JoinType jointype,
@@ -850,6 +864,7 @@ try_mergejoin_path(PlannerInfo *root,
 									   pathkeys,
 									   required_outer,
 									   mergeclauses,
+									   rangeclause,
 									   outersortkeys,
 									   innersortkeys));
 	}
@@ -926,6 +941,7 @@ try_partial_mergejoin_path(PlannerInfo *root,
 										   pathkeys,
 										   NULL,
 										   mergeclauses,
+										   NIL,
 										   outersortkeys,
 										   innersortkeys));
 }
@@ -1107,7 +1123,8 @@ sort_inner_and_outer(PlannerInfo *root,
 	Path	   *inner_path;
 	Path	   *cheapest_partial_outer = NULL;
 	Path	   *cheapest_safe_inner = NULL;
-	List	   *all_pathkeys;
+	List	   *merge_pathkeys;
+	List	   *range_pathkeys;
 	ListCell   *l;
 
 	/*
@@ -1207,25 +1224,80 @@ sort_inner_and_outer(PlannerInfo *root,
 	 * some heuristics behind it (see that function), so be sure to try it
 	 * exactly as-is as well as making variants.
 	 */
-	all_pathkeys = select_outer_pathkeys_for_merge(root,
-												   extra->mergeclause_list,
-												   joinrel);
 
-	foreach(l, all_pathkeys)
+	range_pathkeys = select_outer_pathkeys_for_range(root,
+													 extra->rangeclause_list);
+
+	merge_pathkeys = select_outer_pathkeys_for_merge(root,
+													 extra->mergeclause_list,
+													 joinrel);
+
+	if(merge_pathkeys == NIL && enable_mergejoin)
+	{
+		foreach(l, range_pathkeys)
+		{
+			PathKey		*range_pathkey = (PathKey *) lfirst(l);
+			List		*outerkeys = list_make1(range_pathkey);
+			List		*innerkeys = NIL;
+			List		*rangeclauses;
+			ListCell	*lc;
+
+			rangeclauses = find_rangeclauses_for_outer_pathkeys(root,
+												outerkeys,
+												NIL,
+												extra->rangeclause_list);
+
+			foreach(lc, rangeclauses)
+			{
+				List	*rangeclause = (List *) lfirst(lc);
+				PathKey *range_inner_pathkey =
+						make_inner_pathkey_for_range(root,
+													 rangeclause);
+
+				innerkeys = lappend(innerkeys, range_inner_pathkey);
+
+				merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+															 outerkeys);
+
+				/*
+				 * And now we can make the path.
+				 *
+				 * Note: it's possible that the cheapest paths will already be sorted
+				 * properly.  try_mergejoin_path will detect that case and suppress an
+				 * explicit sort step, so we needn't do so here.
+				 */
+				try_mergejoin_path(root,
+								   joinrel,
+								   outer_path,
+								   inner_path,
+								   merge_pathkeys,
+								   NIL,
+								   rangeclause,
+								   outerkeys,
+								   innerkeys,
+								   jointype,
+								   extra,
+								   false);
+			}
+		}
+	}
+
+
+	foreach(l, merge_pathkeys)
 	{
 		List	   *front_pathkey = (List *) lfirst(l);
 		List	   *cur_mergeclauses;
 		List	   *outerkeys;
 		List	   *innerkeys;
-		List	   *merge_pathkeys;
+		List	   *mergejoin_pathkeys;
 
 		/* Make a pathkey list with this guy first */
-		if (l != list_head(all_pathkeys))
+		if (l != list_head(merge_pathkeys))
 			outerkeys = lcons(front_pathkey,
-							  list_delete_nth_cell(list_copy(all_pathkeys),
-												   foreach_current_index(l)));
+							  list_delete_ptr(list_copy(merge_pathkeys),
+											  front_pathkey));
 		else
-			outerkeys = all_pathkeys;	/* no work at first one... */
+			outerkeys = merge_pathkeys;	/* no work at first one... */
 
 		/* Sort the mergeclauses into the corresponding ordering */
 		cur_mergeclauses =
@@ -1242,7 +1314,7 @@ sort_inner_and_outer(PlannerInfo *root,
 												  outerkeys);
 
 		/* Build pathkeys representing output sort order */
-		merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+		mergejoin_pathkeys = build_join_pathkeys(root, joinrel, jointype,
 											 outerkeys);
 
 		/*
@@ -1256,8 +1328,9 @@ sort_inner_and_outer(PlannerInfo *root,
 						   joinrel,
 						   outer_path,
 						   inner_path,
-						   merge_pathkeys,
+						   mergejoin_pathkeys,
 						   cur_mergeclauses,
+						   NIL,
 						   outerkeys,
 						   innerkeys,
 						   jointype,
@@ -1273,12 +1346,88 @@ sort_inner_and_outer(PlannerInfo *root,
 									   joinrel,
 									   cheapest_partial_outer,
 									   cheapest_safe_inner,
-									   merge_pathkeys,
+									   mergejoin_pathkeys,
 									   cur_mergeclauses,
 									   outerkeys,
 									   innerkeys,
 									   jointype,
 									   extra);
+
+		foreach(l, range_pathkeys)
+		{
+			PathKey		*range_outer_pathkey = (PathKey *) lfirst(l);
+			List		*range_outerkeys = list_copy(outerkeys);
+			List		*range_innerkeys;
+			List		*rangeclauses;
+			ListCell	*lc;
+
+			if(list_member_ptr(range_outerkeys, range_outer_pathkey))
+			{
+				if(!equal(llast(range_outerkeys), range_outer_pathkey))
+					continue;
+			}
+			else
+				range_outerkeys = lappend(range_outerkeys, range_outer_pathkey);
+
+			/* Sort the mergeclauses into the corresponding ordering */
+			cur_mergeclauses =
+				find_mergeclauses_for_outer_pathkeys(root,
+													 range_outerkeys,
+													 extra->mergeclause_list);
+
+			/* Should have used them all... */
+			Assert(list_length(cur_mergeclauses) == list_length(extra->mergeclause_list));
+
+			/* Build sort pathkeys for the inner side */
+			range_innerkeys = make_inner_pathkeys_for_merge(root,
+													  	  	cur_mergeclauses,
+															range_outerkeys);
+
+			rangeclauses = find_rangeclauses_for_outer_pathkeys(root,
+												range_outerkeys,
+												cur_mergeclauses,
+												extra->rangeclause_list);
+
+			foreach(lc, rangeclauses)
+			{
+				List	*cur_rangeclause = (List *) lfirst(lc);
+				PathKey *range_inner_pathkey;
+
+				range_inner_pathkey = make_inner_pathkey_for_range(root, cur_rangeclause);
+
+				if(list_member_ptr(range_innerkeys, range_inner_pathkey))
+				{
+					if(!equal(llast(range_innerkeys), range_inner_pathkey))
+						continue;
+				}
+				else
+					range_innerkeys = lappend(range_innerkeys, range_inner_pathkey);
+
+
+				mergejoin_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+															 range_outerkeys);
+
+				/*
+				 * And now we can make the path.
+				 *
+				 * Note: it's possible that the cheapest paths will already be sorted
+				 * properly.  try_mergejoin_path will detect that case and suppress an
+				 * explicit sort step, so we needn't do so here.
+				 */
+				try_mergejoin_path(root,
+								   joinrel,
+								   outer_path,
+								   inner_path,
+								   mergejoin_pathkeys,
+								   cur_mergeclauses,
+								   cur_rangeclause,
+								   range_outerkeys,
+								   range_innerkeys,
+								   jointype,
+								   extra,
+								   false);
+			}
+		}
 	}
 }
 
@@ -1364,6 +1513,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 					   merge_pathkeys,
 					   mergeclauses,
 					   NIL,
+					   NIL,
 					   innersortkeys,
 					   jointype,
 					   extra,
@@ -1462,6 +1612,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 							   newclauses,
 							   NIL,
 							   NIL,
+							   NIL,
 							   jointype,
 							   extra,
 							   is_partial);
@@ -1506,6 +1657,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 								   newclauses,
 								   NIL,
 								   NIL,
+								   NIL,
 								   jointype,
 								   extra,
 								   is_partial);
@@ -2272,3 +2424,138 @@ select_mergejoin_clauses(PlannerInfo *root,
 
 	return result_list;
 }
+
+/*
+ * range_clause_order
+ */
+static int
+range_clause_order(RestrictInfo *first,
+				   RestrictInfo *second)
+{
+	/*
+	 * Extract details from first restrictinfo
+	 */
+	Node   *first_left = get_leftop(first->clause),
+		   *first_right = get_rightop(first->clause);
+	bool	first_outer_is_left = first->outer_is_left;
+	int		first_strategy = get_op_opfamily_strategy(((OpExpr *) first->clause)->opno,
+														(linitial_oid(first->rangeleftopfamilies)));
+	bool    first_less = (first_strategy == BTLessStrategyNumber ||
+							first_strategy == BTLessEqualStrategyNumber);
+
+	/*
+	 * Extract details from second restrictinfo
+	 */
+	Node   *second_left = get_leftop(second->clause),
+		   *second_right = get_rightop(second->clause);
+	bool	second_outer_is_left = second->outer_is_left;
+	int		second_strategy = get_op_opfamily_strategy(((OpExpr *) second->clause)->opno,
+														(linitial_oid(second->rangeleftopfamilies)));
+	bool    second_less = (second_strategy == BTLessStrategyNumber ||
+							second_strategy == BTLessEqualStrategyNumber);
+
+	/*
+	 * Check for rangeclause
+	 */
+	if (first_less && second_less)
+	{
+		if (!first_outer_is_left && second_outer_is_left &&
+				equal(first_left, second_right))
+			return 2;
+		else if (first_outer_is_left && !second_outer_is_left &&
+				equal(first_right, second_left))
+			return 1;
+	}
+	else if (!first_less && !second_less)
+	{
+		if (!first_outer_is_left && second_outer_is_left &&
+				equal(first_left, second_right))
+			return 1;
+		else if (first_outer_is_left && !second_outer_is_left &&
+				equal(first_right, second_left))
+			return 2;
+	}
+	else if (first_less && !second_less)
+	{
+		if (!first_outer_is_left && !second_outer_is_left &&
+				equal(first_left, second_left))
+			return 2;
+		else if (first_outer_is_left && second_outer_is_left &&
+				equal(first_right, second_right))
+			return 1;
+	}
+	else if (!first_less && second_less)
+	{
+		if (!first_outer_is_left && !second_outer_is_left &&
+				equal(first_left, second_left))
+			return 1;
+		else if (first_outer_is_left && second_outer_is_left &&
+				equal(first_right, second_right))
+			return 2;
+	}
+
+	return 0;
+}
+
+/*
+ * select_rangejoin_clauses
+ */
+static List *
+select_rangejoin_clauses(RelOptInfo *outerrel,
+						 RelOptInfo *innerrel,
+						 List *restrictlist)
+{
+	List	   *result_list = NIL;
+	ListCell   *l;
+	List	   *range_candidates = NIL;
+
+	foreach(l, restrictlist)
+	{
+		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
+		OpExpr		 *clause = (OpExpr *) restrictinfo->clause;
+
+		/* Check that clause is a rangejoinable operator clause */
+		if (restrictinfo->rangeleftopfamilies == NIL)
+			continue;			/* not rangejoinable */
+
+		/*
+		 * Check if clause has the form "outer op inner" or "inner op outer".
+		 */
+		if (!clause_sides_match_join(restrictinfo, outerrel, innerrel))
+			continue;			/* no good for these input relations */
+
+		if (restrictinfo->rangeleftopfamilies == restrictinfo->rangerightopfamilies)
+		{
+			ListCell *lc;
+			List	 *range_clause;
+
+			foreach(lc, range_candidates)
+			{
+				RestrictInfo *candidate = (RestrictInfo *) lfirst(lc);
+
+				switch (range_clause_order(restrictinfo, candidate))
+				{
+				case 1:
+					range_clause = list_make2(restrictinfo, candidate);
+					result_list = lappend(result_list, range_clause);
+					break;
+
+				case 2:
+					range_clause = list_make2(candidate, restrictinfo);
+					result_list = lappend(result_list, range_clause);
+					break;
+				default:
+					break;
+				}
+			}
+			range_candidates = lappend(range_candidates, restrictinfo);
+		}
+		else if ((clause->opno == OID_RANGE_CONTAINS_ELEM_OP && restrictinfo->outer_is_left) ||
+				(clause->opno == OID_RANGE_ELEM_CONTAINED_OP && !restrictinfo->outer_is_left))
+		{
+			result_list = lappend(result_list, list_make1(restrictinfo));
+		}
+	}
+	list_free(range_candidates);
+	return result_list;
+}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 216dd26385..f82e760658 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -19,6 +19,7 @@
 
 #include "access/stratnum.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_operator.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/plannodes.h"
@@ -1214,6 +1215,60 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo)
 								 true);
 }
 
+/*
+ * initialize_rangeclause_eclasses
+ */
+void
+initialize_rangeclause_eclasses(PlannerInfo *root,
+								RestrictInfo *restrictinfo)
+{
+	Expr   *clause = restrictinfo->clause;
+	Oid		lefttype,
+			righttype,
+			opno;
+
+	/* Should be a rangeclause ... */
+	Assert(restrictinfo->rangeleftopfamilies != NIL &&
+			restrictinfo->rangerightopfamilies != NIL);
+	/* ... with links not yet set */
+	Assert(restrictinfo->left_ec == NULL);
+	Assert(restrictinfo->right_ec == NULL);
+
+	opno = ((OpExpr *) clause)->opno;
+
+	/* Need the declared input types of the operator */
+	op_input_types(opno, &lefttype, &righttype);
+
+	if(opno == OID_RANGE_CONTAINS_ELEM_OP)
+		righttype = exprType(get_rightop(clause));
+
+	else if(opno == OID_RANGE_ELEM_CONTAINED_OP)
+		lefttype = exprType(get_leftop(clause));
+
+
+	/* Find or create a matching EquivalenceClass for each side */
+	restrictinfo->left_ec =
+		get_eclass_for_sort_expr(root,
+				 	 	 	 	 (Expr *) get_leftop(clause),
+								 restrictinfo->nullable_relids,
+								 restrictinfo->rangeleftopfamilies,
+								 lefttype,
+								 ((OpExpr *) clause)->inputcollid,
+								 0,
+								 NULL,
+								 true);
+	restrictinfo->right_ec =
+		get_eclass_for_sort_expr(root,
+								 (Expr *) get_rightop(clause),
+								 restrictinfo->nullable_relids,
+								 restrictinfo->rangerightopfamilies,
+								 righttype,
+								 ((OpExpr *) clause)->inputcollid,
+								 0,
+								 NULL,
+								 true);
+}
+
 /*
  * update_mergeclause_eclasses
  *		Make the cached EquivalenceClass links valid in a mergeclause
@@ -1347,6 +1402,64 @@ find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 	return mergeclauses;
 }
 
+/*
+ * find_rangeclauses_for_outer_pathkeys
+ */
+List *
+find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
+									List *pathkeys,
+									List *mergeclauses,
+									List *rangeclauses)
+{
+	ListCell   *i;
+	RestrictInfo *mergeclause = NULL;
+	List *result_list = NIL;
+
+	if(mergeclauses)
+		mergeclause = llast(mergeclauses);
+
+	foreach(i, pathkeys)
+	{
+		PathKey    *pathkey = (PathKey *) lfirst(i);
+		EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
+		ListCell   *j;
+
+		if(mergeclause != NULL)
+		{
+			if(mergeclause->outer_is_left)
+			{
+				if(mergeclause->left_ec != pathkey_ec)
+					continue;
+			}
+			else if(mergeclause->right_ec != pathkey_ec)
+				continue;
+		}
+
+		foreach(j, rangeclauses)
+		{
+			List				*rangeclause = (List *) lfirst(j);
+			RestrictInfo 		*rinfo = (RestrictInfo *) linitial(rangeclause);
+			EquivalenceClass 	*clause_ec;
+
+			clause_ec = rinfo->outer_is_left ?
+				rinfo->left_ec : rinfo->right_ec;
+
+			if (clause_ec == pathkey_ec)
+				result_list = lappend(result_list, rangeclause);
+		}
+
+		/*
+		 * Was it the last possible pathkey?
+		 */
+		if(mergeclause == NULL)
+			break;
+		else
+			mergeclause = NULL;
+
+  }
+  return result_list;
+}
+
 /*
  * select_outer_pathkeys_for_merge
  *	  Builds a pathkey list representing a possible sort ordering
@@ -1522,6 +1635,37 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 	return pathkeys;
 }
 
+/*
+ * select_outer_pathkeys_for_range
+ */
+List *
+select_outer_pathkeys_for_range(PlannerInfo *root,
+								List *rangeclauses)
+{
+	EquivalenceClass	*ec;
+	PathKey 			*pathkey;
+	List 				*pathkeys = NIL;
+	ListCell			*lc;
+
+	foreach(lc, rangeclauses){
+		List *rangeclause = lfirst(lc);
+		RestrictInfo *rinfo = linitial(rangeclause);
+
+		if (rinfo->outer_is_left)
+			ec = rinfo->left_ec;
+		else
+			ec = rinfo->right_ec;
+
+		pathkey = make_canonical_pathkey(root,
+										 ec,
+										 linitial_oid(ec->ec_opfamilies),
+										 BTLessStrategyNumber,
+										 false);
+		pathkeys = lappend(pathkeys, pathkey);
+	}
+	return pathkeys;
+}
+
 /*
  * make_inner_pathkeys_for_merge
  *	  Builds a pathkey list representing the explicit sort order that
@@ -1621,6 +1765,35 @@ make_inner_pathkeys_for_merge(PlannerInfo *root,
 	return pathkeys;
 }
 
+/*
+ * make_inner_pathkey_for_range
+ */
+PathKey *
+make_inner_pathkey_for_range(PlannerInfo *root,
+							 List *rangeclause)
+{
+	EquivalenceClass *ec;
+	PathKey 	 *pathkey;
+	RestrictInfo *rinfo;
+
+	if(rangeclause == NIL)
+		return NULL;
+
+	rinfo = linitial(rangeclause);
+
+	if (rinfo->outer_is_left)
+		ec = rinfo->right_ec;
+	else
+		ec = rinfo->left_ec;
+
+	pathkey = make_canonical_pathkey(root,
+									 ec,
+									 linitial_oid(ec->ec_opfamilies),
+									 BTLessStrategyNumber,
+									 false);
+	return pathkey;
+}
+
 /*
  * trim_mergeclauses_for_inner_pathkeys
  *	  This routine trims a list of mergeclauses to include just those that
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 3dc0176a51..49cc8d16d6 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -246,6 +246,7 @@ static Hash *make_hash(Plan *lefttree,
 static MergeJoin *make_mergejoin(List *tlist,
 								 List *joinclauses, List *otherclauses,
 								 List *mergeclauses,
+								 List *rangeclause,
 								 Oid *mergefamilies,
 								 Oid *mergecollations,
 								 int *mergestrategies,
@@ -4301,6 +4302,7 @@ create_mergejoin_plan(PlannerInfo *root,
 	List	   *joinclauses;
 	List	   *otherclauses;
 	List	   *mergeclauses;
+	List	   *rangeclause;
 	List	   *outerpathkeys;
 	List	   *innerpathkeys;
 	int			nClauses;
@@ -4355,6 +4357,9 @@ create_mergejoin_plan(PlannerInfo *root,
 	mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
 	joinclauses = list_difference(joinclauses, mergeclauses);
 
+	rangeclause = get_actual_clauses(best_path->path_rangeclause);
+	joinclauses = list_difference(joinclauses, rangeclause);
+
 	/*
 	 * Replace any outer-relation variables with nestloop params.  There
 	 * should not be any in the mergeclauses.
@@ -4365,6 +4370,8 @@ create_mergejoin_plan(PlannerInfo *root,
 			replace_nestloop_params(root, (Node *) joinclauses);
 		otherclauses = (List *)
 			replace_nestloop_params(root, (Node *) otherclauses);
+		rangeclause = (List *)
+			replace_nestloop_params(root, (Node *) rangeclause);
 	}
 
 	/*
@@ -4581,6 +4588,7 @@ create_mergejoin_plan(PlannerInfo *root,
 							   joinclauses,
 							   otherclauses,
 							   mergeclauses,
+							   rangeclause,
 							   mergefamilies,
 							   mergecollations,
 							   mergestrategies,
@@ -5883,6 +5891,7 @@ make_mergejoin(List *tlist,
 			   List *joinclauses,
 			   List *otherclauses,
 			   List *mergeclauses,
+			   List *rangeclause,
 			   Oid *mergefamilies,
 			   Oid *mergecollations,
 			   int *mergestrategies,
@@ -5909,6 +5918,7 @@ make_mergejoin(List *tlist,
 	node->join.jointype = jointype;
 	node->join.inner_unique = inner_unique;
 	node->join.joinqual = joinclauses;
+	node->rangeclause = rangeclause;
 
 	return node;
 }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index e25dc9a7ca..847b9dc124 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -16,6 +16,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_operator.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -77,6 +78,7 @@ static bool check_equivalence_delay(PlannerInfo *root,
 									RestrictInfo *restrictinfo);
 static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
 static void check_mergejoinable(RestrictInfo *restrictinfo);
+static void check_rangejoinable(RestrictInfo *restrictinfo);
 static void check_hashjoinable(RestrictInfo *restrictinfo);
 static void check_memoizable(RestrictInfo *restrictinfo);
 
@@ -1877,6 +1879,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	 */
 	check_mergejoinable(restrictinfo);
 
+	check_rangejoinable(restrictinfo);
+
 	/*
 	 * If it is a true equivalence clause, send it to the EquivalenceClass
 	 * machinery.  We do *not* attach it directly to any restriction or join
@@ -1962,6 +1966,13 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 		}
 	}
 
+	if(restrictinfo->rangeleftopfamilies)
+	{
+		Assert(restrictinfo->rangerightopfamilies);
+
+		initialize_rangeclause_eclasses(root, restrictinfo);
+	}
+
 	/* No EC special case applies, so push it into the clause lists */
 	distribute_restrictinfo_to_rels(root, restrictinfo);
 }
@@ -2677,6 +2688,50 @@ check_mergejoinable(RestrictInfo *restrictinfo)
 	 */
 }
 
+/*
+ * check_rangejoinable
+ */
+static void
+check_rangejoinable(RestrictInfo *restrictinfo)
+{
+	OpExpr	   *clause = (OpExpr *) restrictinfo->clause;
+	Oid			opno = clause->opno;
+
+	if (!restrictinfo->can_join)
+		return;
+	if (contain_volatile_functions((Node *) clause))
+		return;
+
+	if (opno == OID_RANGE_CONTAINS_ELEM_OP ||
+			opno == OID_RANGE_ELEM_CONTAINED_OP)
+	{
+		Node		   *leftarg = get_leftop(clause),
+					   *rightarg = get_rightop(clause);
+
+		Oid				lefttype = exprType(leftarg),
+						righttype = exprType(rightarg);
+
+		TypeCacheEntry *left_typecache = lookup_type_cache(lefttype, TYPECACHE_CMP_PROC),
+					   *right_typecache = lookup_type_cache(righttype, TYPECACHE_CMP_PROC);
+
+		restrictinfo->rangeleftopfamilies =
+				lappend_oid(restrictinfo->rangeleftopfamilies,
+							left_typecache->btree_opf);
+
+		restrictinfo->rangerightopfamilies =
+				lappend_oid(restrictinfo->rangerightopfamilies,
+							right_typecache->btree_opf);
+	}
+	else
+	{
+		List *opfamilies = get_rangejoin_opfamilies(opno);
+
+		restrictinfo->rangeleftopfamilies = opfamilies;
+		restrictinfo->rangerightopfamilies = opfamilies;
+
+	}
+}
+
 /*
  * check_hashjoinable
  *	  If the restrictinfo's clause is hashjoinable, set the hashjoin
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 6ccec759bd..c309a3e114 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2038,6 +2038,14 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
 										 (Index) 0,
 										 rtoffset,
 										 NUM_EXEC_QUAL((Plan *) join));
+
+		mj->rangeclause = fix_join_expr(root,
+										 mj->rangeclause,
+										 outer_itlist,
+										 inner_itlist,
+										 (Index) 0,
+										 rtoffset,
+										 NUM_EXEC_QUAL((Plan *) join));
 	}
 	else if (IsA(join, HashJoin))
 	{
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index e53d381e19..f8490fa4ef 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -2502,6 +2502,7 @@ create_mergejoin_path(PlannerInfo *root,
 					  List *pathkeys,
 					  Relids required_outer,
 					  List *mergeclauses,
+					  List *rangeclause,
 					  List *outersortkeys,
 					  List *innersortkeys)
 {
@@ -2530,6 +2531,7 @@ create_mergejoin_path(PlannerInfo *root,
 	pathnode->jpath.innerjoinpath = inner_path;
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
 	pathnode->path_mergeclauses = mergeclauses;
+	pathnode->path_rangeclause = rangeclause;
 	pathnode->outersortkeys = outersortkeys;
 	pathnode->innersortkeys = innersortkeys;
 	/* pathnode->skip_mark_restore will be set by final_cost_mergejoin */
@@ -4134,6 +4136,7 @@ do { \
 				REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
 				ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
 				ADJUST_CHILD_ATTRS(mpath->path_mergeclauses);
+				ADJUST_CHILD_ATTRS(mpath->path_rangeclause);
 				new_path = (Path *) mpath;
 			}
 			break;
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index aa9fb3a9fa..8887341d74 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -201,6 +201,8 @@ make_restrictinfo_internal(PlannerInfo *root,
 	restrictinfo->outer_selec = -1;
 
 	restrictinfo->mergeopfamilies = NIL;
+	restrictinfo->rangeleftopfamilies = NIL;
+	restrictinfo->rangerightopfamilies = NIL;
 
 	restrictinfo->left_ec = NULL;
 	restrictinfo->right_ec = NULL;
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 815175a654..3972480519 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -2507,6 +2507,66 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
 	return true;
 }
 
+/*
+ * Test whether range r is right of a specific element value.
+ */
+bool
+elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
+{
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	int32		cmp;
+
+	range_deserialize(typcache, r, &lower, &upper, &empty);
+
+	if (empty)
+		return true;
+
+	if (!lower.infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  lower.val, val));
+		if (cmp > 0)
+			return true;
+		if (cmp == 0 && !lower.inclusive)
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Test whether range r is left of a specific element value.
+ */
+bool
+elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
+{
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	int32		cmp;
+
+	range_deserialize(typcache, r, &lower, &upper, &empty);
+
+	if (empty)
+		return true;
+
+	if (!upper.infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  upper.val, val));
+		if (cmp < 0)
+			return true;
+		if (cmp == 0 && !upper.inclusive)
+			return true;
+	}
+
+	return false;
+}
+
 
 /*
  * datum_compute_size() and datum_write() are used to insert the bound
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a2..f6cc0cdb8a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -389,6 +389,40 @@ get_mergejoin_opfamilies(Oid opno)
 	return result;
 }
 
+/*
+ * get_rangejoin_opfamilies
+ */
+List *
+get_rangejoin_opfamilies(Oid opno)
+{
+	List	   *result = NIL;
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * Search pg_amop to see if the target operator is registered as the "<",
+	 * "<=", ">" or ">=" operator of any btree opfamily.
+	 */
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == BTREE_AM_OID &&
+			(aform->amopstrategy == BTLessStrategyNumber ||
+				aform->amopstrategy == BTLessEqualStrategyNumber ||
+				aform->amopstrategy == BTGreaterStrategyNumber ||
+				aform->amopstrategy == BTGreaterEqualStrategyNumber))
+			result = lappend_oid(result, aform->amopfamily);
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_compatible_hash_operators
  *		Get the OID(s) of hash equality operator(s) compatible with the given
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 37cb4f3d59..c96d59b48f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1948,6 +1948,7 @@ typedef struct NestLoopState
  */
 /* private in nodeMergejoin.c: */
 typedef struct MergeJoinClauseData *MergeJoinClause;
+typedef struct RangeJoinData *RangeData;
 
 typedef struct MergeJoinState
 {
@@ -1969,6 +1970,8 @@ typedef struct MergeJoinState
 	TupleTableSlot *mj_NullInnerTupleSlot;
 	ExprContext *mj_OuterEContext;
 	ExprContext *mj_InnerEContext;
+	RangeData	 mj_RangeData;
+	bool		 mj_RangeJoin;
 } MergeJoinState;
 
 /* ----------------
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 2a53a6e344..9aa3b5fe77 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1647,6 +1647,7 @@ typedef struct MergePath
 {
 	JoinPath	jpath;
 	List	   *path_mergeclauses;	/* join clauses to be used for merge */
+	List	   *path_rangeclause;	/* join clause to be used for range merge */
 	List	   *outersortkeys;	/* keys for explicit sort, if any */
 	List	   *innersortkeys;	/* keys for explicit sort, if any */
 	bool		skip_mark_restore;	/* can executor skip mark/restore? */
@@ -2102,6 +2103,8 @@ typedef struct RestrictInfo
 
 	/* valid if clause is mergejoinable, else NIL */
 	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List	   *rangeleftopfamilies;
+	List	   *rangerightopfamilies;
 
 	/* cache space for mergeclause processing; NULL if not yet set */
 	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
@@ -2528,6 +2531,7 @@ typedef struct JoinPathExtraData
 {
 	List	   *restrictlist;
 	List	   *mergeclause_list;
+	List	   *rangeclause_list;
 	bool		inner_unique;
 	SpecialJoinInfo *sjinfo;
 	SemiAntiJoinFactors semifactors;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 01a246d50e..5fdc45faf9 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -754,6 +754,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 */
+	List	   *rangeclause;	/* rangeclause as expression tree */
 } MergeJoin;
 
 /* ----------------
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index f704d39980..365d8b809e 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -167,6 +167,7 @@ extern MergePath *create_mergejoin_path(PlannerInfo *root,
 										List *pathkeys,
 										Relids required_outer,
 										List *mergeclauses,
+										List *rangeclause,
 										List *outersortkeys,
 										List *innersortkeys);
 
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index f1d111063c..0acfd297eb 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -231,17 +231,27 @@ extern List *make_pathkeys_for_sortclauses(PlannerInfo *root,
 										   List *tlist);
 extern void initialize_mergeclause_eclasses(PlannerInfo *root,
 											RestrictInfo *restrictinfo);
+extern void initialize_rangeclause_eclasses(PlannerInfo *root,
+											RestrictInfo *restrictinfo);
 extern void update_mergeclause_eclasses(PlannerInfo *root,
 										RestrictInfo *restrictinfo);
 extern List *find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 												  List *pathkeys,
 												  List *restrictinfos);
+extern List *find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
+												  List *pathkeys,
+												  List *mergeclauses,
+												  List *rangeclauses);
 extern List *select_outer_pathkeys_for_merge(PlannerInfo *root,
 											 List *mergeclauses,
 											 RelOptInfo *joinrel);
+extern List *select_outer_pathkeys_for_range(PlannerInfo *root,
+											 List *rangeclauses);
 extern List *make_inner_pathkeys_for_merge(PlannerInfo *root,
 										   List *mergeclauses,
 										   List *outer_pathkeys);
+extern PathKey *make_inner_pathkey_for_range(PlannerInfo *root,
+											 List *rangeclause);
 extern List *trim_mergeclauses_for_inner_pathkeys(PlannerInfo *root,
 												  List *mergeclauses,
 												  List *pathkeys);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 77871aaefc..a47beb8be5 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -79,6 +79,7 @@ extern bool get_ordering_op_properties(Oid opno,
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
 extern List *get_mergejoin_opfamilies(Oid opno);
+extern List *get_rangejoin_opfamilies(Oid opno);
 extern bool get_compatible_hash_operators(Oid opno,
 										  Oid *lhs_opno, Oid *rhs_opno);
 extern bool get_op_hash_functions(Oid opno,
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 04c302c619..51bdc5308b 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -95,6 +95,8 @@ typedef struct
  */
 
 extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
+extern bool elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r);
+extern bool elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r);
 
 /* internal versions of the above */
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
#7Thomas
thomasmannhart97@gmail.com
In reply to: Jaime Casanova (#6)
1 attachment(s)
Re: Patch: Range Merge Join

Dear all,
thanks for the feedback!

We had a closer look at the previous patches and the CustomScan
infrastructure.

Compared to the previous patch, we do not (directly) focus on joins
with the overlap (&&) condition in this patch. Instead we consider
joins with containment (@>) between a range and an element, and joins
with conditions over scalars of the form "right.element BETWEEN
left.start AND left.end", and more generally left.start >(=)
right.element AND right.element <(=) left.end. We call such conditions
range conditions and these conditions can be combined with equality
conditions in the Range Merge Join.

The Range Merge Join can use (optional) equality conditions and one
range condition of the form shown above. In this case the inputs are
sorted first by the attributes used for equality and then one input by
the range (or start in the case of scalars) and the other input by the
element. The Range Merge Join is then a simple extension of the Merge
Join that in addition to the (optional) equality attributes also uses
the range condition in the merge join states. This is similar to an
index-nested loop with scalars for cases when the relation containing
the element has an index on the equality attributes followed by the
element. The Range Merge Join uses sorting and thus does not require
the index for this purpose and performs better.

The patch uses the optimizer estimates to evaluate if the Range Merge
Join is beneficial as compared to other execution strategies, but when
no equality attributes are present, it becomes the only efficient
option for the above range conditions. If a join contains multiple
range conditions, then based on the estimates the most effective
strategy is chosen for the Range Merge Join.

Although we do not directly focus on joins with the overlap (&&)
condition between two ranges, we show in [1]https://doi.org/10.1007/s00778-021-00692-3 (open access) that these joins can be
evaluated using the union (UNION ALL) of two joins with a range
condition, where intuitively, one tests that the start of one input
falls within the range of the other and vice versa. We evaluated this
using regular (B-tree) indices and compare it to joins with the
overlap (&&) condition using GiST, SP-GiST and others, and found that
it performs better. The Range Merge Join would improve this further
and would not require the creation of an index.

We did not consider an implementation as a CustomScan, as we feel the
join is rather general, can be implemented using a small extension of
the existing Merge Join, and would require a substantial duplication
of the Merge Join code.

Kind regards,
Thomas, Anton, Johann, Michael, Peter

[1]: https://doi.org/10.1007/s00778-021-00692-3 (open access)

Am Di., 5. Okt. 2021 um 02:30 Uhr schrieb Jaime Casanova <
jcasanov@systemguards.com.ec>:

Show quoted text

On Mon, Oct 04, 2021 at 04:27:54PM -0500, Jaime Casanova wrote:

On Thu, Jun 10, 2021 at 07:14:32PM -0700, Jeff Davis wrote:

I'll start with the reason I set the work down before: it did not work
well with multiple join keys. That might be fine, but I also started
thinking it was specialized enough that I wanted to look into doing it
as an extension using the CustomScan mechanism.

Do you have any solution to working better with multiple join keys?

And

do you have thoughts on whether it would be a good candidate for the
CustomScan extension mechanism, which would make it easier to
experiment with?

Hi,

It seems this has been stalled since jun-2021. I intend mark this as
RwF unless someone speaks in the next hour or so.

Thomas <thomasmannhart97@gmail.com> wrote me:

Hi,

I registered this patch for the commitfest in july. It had not been

reviewed and moved to the next CF. I still like to submit it.

Regards,
Thomas

Just for clarification RwF doesn't imply reject of the patch.
Nevertheless, given that there has been no real review I will mark this
patch as "Waiting on Author" and move it to the next CF.

Meanwhile, cfbot (aka http://commitfest.cputube.org) says this doesn't
compile. Here is a little patch to fix the compilation errors, after
that it passes all tests in make check-world.

Also attached a rebased version of your patch with the fixes so we turn
cfbot entry green again

--
Jaime Casanova
Director de Servicios Profesionales
SystemGuards - Consultores de PostgreSQL

Attachments:

postgres.patchapplication/octet-stream; name=postgres.patchDownload
diff --git a/.gitignore b/.gitignore
index 794e35b73c..5dca1a39ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,14 @@ lib*.pc
 /Debug/
 /Release/
 /tmp_install/
+
+# Thomas
+/data/
+/server/
+.idea/
+.settings/
+.cproject
+.project
+logfile
+src/backend/catalog/postgres.*
+*.patch
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 10644dfac4..9b7503630c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1206,8 +1206,16 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			pname = sname = "Nested Loop";
 			break;
 		case T_MergeJoin:
-			pname = "Merge";	/* "Join" gets added by jointype switch */
-			sname = "Merge Join";
+			if(((MergeJoin *) plan)->rangeclause)
+			{
+				pname = "Range Merge";	/* "Join" gets added by jointype switch */
+				sname = "Range Merge Join";
+			}
+			else
+			{
+				pname = "Merge";	/* "Join" gets added by jointype switch */
+				sname = "Merge Join";
+			}
 			break;
 		case T_HashJoin:
 			pname = "Hash";		/* "Join" gets added by jointype switch */
@@ -1948,6 +1956,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_MergeJoin:
 			show_upper_qual(((MergeJoin *) plan)->mergeclauses,
 							"Merge Cond", planstate, ancestors, es);
+			show_upper_qual(((MergeJoin *) plan)->rangeclause,
+							"Range Cond", planstate, ancestors, es);
 			show_upper_qual(((MergeJoin *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
 			if (((MergeJoin *) plan)->join.joinqual)
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index b41454ab6d..2dccba24b3 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -93,11 +93,15 @@
 #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/rangetypes.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/typcache.h"
 
 
 /*
@@ -140,6 +144,18 @@ typedef struct MergeJoinClauseData
 	SortSupportData ssup;
 }			MergeJoinClauseData;
 
+/*
+ * Runtime data for the range clause
+ */
+typedef struct RangeJoinData
+{
+	ExprState *startClause;
+	ExprState *endClause;
+	ExprState *rangeExpr;
+	ExprState *elemExpr;
+
+}			RangeJoinData;
+
 /* Result type for MJEvalOuterValues and MJEvalInnerValues */
 typedef enum
 {
@@ -269,6 +285,57 @@ MJExamineQuals(List *mergeclauses,
 	return clauses;
 }
 
+/*
+ * MJCreateRangeData
+ */
+static RangeData
+MJCreateRangeData(List *rangeclause,
+				   PlanState *parent)
+{
+	RangeData data;
+
+	Assert(list_legth(node->rangeclause) < 3);
+
+	data = (RangeData) palloc0(sizeof(RangeJoinData));
+
+	data->startClause = NULL;
+	data->endClause = NULL;
+	data->rangeExpr = NULL;
+	data->elemExpr = NULL;
+
+	if(list_length(rangeclause) == 2)
+	{
+		data->startClause = ExecInitExpr(linitial(rangeclause), parent);
+		data->endClause = ExecInitExpr(lsecond(rangeclause), parent);
+	}
+	else
+	{
+		OpExpr		*qual = (OpExpr *) linitial(rangeclause);
+		ExprState	*lexpr;
+		ExprState	*rexpr;
+
+		/*
+		 * Prepare the input expressions for execution.
+		 */
+		lexpr = ExecInitExpr((Expr *) get_leftop(qual), parent);
+		rexpr = ExecInitExpr((Expr *) get_rightop(qual), parent);
+
+		if(qual->opno == OID_RANGE_CONTAINS_ELEM_OP)
+		{
+			data->rangeExpr = lexpr;
+			data->elemExpr = rexpr;
+		}
+		else
+		{
+			Assert(qual->opno == OID_RANGE_ELEM_CONTAINED_OP);
+			data->rangeExpr = rexpr;
+			data->elemExpr = lexpr;
+		}
+	}
+
+	return data;
+}
+
 /*
  * MJEvalOuterValues
  *
@@ -445,6 +512,73 @@ MJCompare(MergeJoinState *mergestate)
 }
 
 
+/*
+ * MJCompareRange
+ *
+ * Compare the rangejoinable values of the current two input tuples
+ * and return 0 if they are equal (ie, the outer interval contains the inner),
+ * >0 if outer > inner, <0 if outer < inner.
+ */
+static int
+MJCompareRange(MergeJoinState *mergestate)
+{
+	int 		  result = 0;
+	bool 		  isNull;
+	MemoryContext oldContext;
+	RangeData	  rangeData = mergestate->mj_RangeData;
+	ExprContext	 *econtext = mergestate->js.ps.ps_ExprContext;
+	ExprState	 *endClause = rangeData->endClause,
+				 *startClause = rangeData->startClause,
+				 *rangeExpr = rangeData->rangeExpr,
+				 *elemExpr = rangeData->elemExpr;
+
+	/*
+	 * Call the comparison functions in short-lived context, in case they leak
+	 * memory.
+	 */
+	ResetExprContext(econtext);
+
+	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+	econtext->ecxt_outertuple = mergestate->mj_OuterTupleSlot;
+	econtext->ecxt_innertuple = mergestate->mj_InnerTupleSlot;
+
+	if (endClause != NULL)
+	{
+		Assert(startClause != NULL);
+
+		if (!ExecEvalExprSwitchContext(endClause, econtext, &isNull))
+			result = -1;
+		else if (!ExecEvalExprSwitchContext(startClause, econtext, &isNull))
+			result = 1;
+	}
+	else
+	{
+		Datum			rangeDatum,
+    					elemDatum;
+		Oid				rangeType;
+		TypeCacheEntry *typecache;
+
+		Assert(rangeExpr != NULL && elemExpr != NULL);
+
+		rangeDatum = ExecEvalExprSwitchContext(rangeExpr, econtext, &isNull);
+		elemDatum = ExecEvalExprSwitchContext(elemExpr, econtext, &isNull);
+
+		rangeType = exprType((Node *) rangeExpr->expr);
+		typecache = lookup_type_cache(rangeType, TYPECACHE_RANGE_INFO);
+
+		if (elem_after_range_internal(typecache, elemDatum, DatumGetRangeTypeP(rangeDatum)))
+			result = -1;
+		else if (elem_before_range_internal(typecache, elemDatum, DatumGetRangeTypeP(rangeDatum)))
+			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.
@@ -604,6 +738,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprState  *otherqual;
 	bool		qualResult;
 	int			compareResult;
+	int			compareRangeResult;
 	PlanState  *innerPlan;
 	TupleTableSlot *innerTupleSlot;
 	PlanState  *outerPlan;
@@ -611,6 +746,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprContext *econtext;
 	bool		doFillOuter;
 	bool		doFillInner;
+	bool		isRangeJoin;
 
 	CHECK_FOR_INTERRUPTS();
 
@@ -624,6 +760,7 @@ ExecMergeJoin(PlanState *pstate)
 	otherqual = node->js.ps.qual;
 	doFillOuter = node->mj_FillOuter;
 	doFillInner = node->mj_FillInner;
+	isRangeJoin = node->mj_RangeJoin;
 
 	/*
 	 * Reset per-tuple memory context to free any expression evaluation
@@ -891,8 +1028,23 @@ ExecMergeJoin(PlanState *pstate)
 						compareResult = MJCompare(node);
 						MJ_DEBUG_COMPARE(compareResult);
 
-						if (compareResult == 0)
-							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						if(compareResult == 0)
+						{
+							if(isRangeJoin)
+							{
+								compareRangeResult = MJCompareRange(node);
+
+								if (compareRangeResult == 0)
+									node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+								else
+								{
+									Assert(compareRangeResult < 0);
+									node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+								}
+							}
+							else
+								node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						}
 						else
 						{
 							Assert(compareResult < 0);
@@ -1085,7 +1237,19 @@ ExecMergeJoin(PlanState *pstate)
 						/* we need not do MJEvalInnerValues again */
 					}
 
-					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					if(isRangeJoin)
+					{
+						compareRangeResult = MJCompareRange(node);
+
+						if(compareRangeResult == 0)
+							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						else if(compareRangeResult < 0)
+							node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+						else /* compareRangeResult > 0 */
+							node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+					}
+					else
+						node->mj_JoinState = EXEC_MJ_JOINTUPLES;
 				}
 				else
 				{
@@ -1184,12 +1348,28 @@ ExecMergeJoin(PlanState *pstate)
 
 				if (compareResult == 0)
 				{
-					if (!node->mj_SkipMarkRestore)
-						ExecMarkPos(innerPlan);
-
-					MarkInnerTuple(node->mj_InnerTupleSlot, node);
-
-					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					if(isRangeJoin)
+					{
+						compareRangeResult = MJCompareRange(node);
+						if(compareRangeResult == 0)
+						{
+							if (!node->mj_SkipMarkRestore)
+								ExecMarkPos(innerPlan);
+							MarkInnerTuple(node->mj_InnerTupleSlot, node);
+							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						}
+						else if(compareRangeResult < 0)
+							node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+						else /* compareRangeResult > 0 */
+							node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+					}
+					else
+					{
+						if (!node->mj_SkipMarkRestore)
+							ExecMarkPos(innerPlan);
+						MarkInnerTuple(node->mj_InnerTupleSlot, node);
+						node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					}
 				}
 				else if (compareResult < 0)
 					node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
@@ -1532,6 +1712,17 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 		ExecInitQual(node->join.joinqual, (PlanState *) mergestate);
 	/* mergeclauses are handled below */
 
+	/*
+	 * initialize range join
+	 */
+	if(node->rangeclause)
+	{
+		mergestate->mj_RangeData = MJCreateRangeData(node->rangeclause, (PlanState *) mergestate);
+		mergestate->mj_RangeJoin = true;
+	}
+	else
+		mergestate->mj_RangeJoin = false;
+
 	/*
 	 * detect whether we need only consider the first matching inner tuple
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ad1ea2ff2f..262676b1fd 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2349,6 +2349,8 @@ _copyRestrictInfo(const RestrictInfo *from)
 	COPY_SCALAR_FIELD(norm_selec);
 	COPY_SCALAR_FIELD(outer_selec);
 	COPY_NODE_FIELD(mergeopfamilies);
+	COPY_NODE_FIELD(rangeleftopfamilies);
+	COPY_NODE_FIELD(rangerightopfamilies);
 	/* EquivalenceClasses are never copied, so shallow-copy the pointers */
 	COPY_SCALAR_FIELD(left_ec);
 	COPY_SCALAR_FIELD(right_ec);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 23f23f11dc..1361c86d08 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -765,6 +765,7 @@ _outMergeJoin(StringInfo str, const MergeJoin *node)
 
 	WRITE_BOOL_FIELD(skip_mark_restore);
 	WRITE_NODE_FIELD(mergeclauses);
+    WRITE_NODE_FIELD(rangeclause);
 
 	numCols = list_length(node->mergeclauses);
 
@@ -2243,6 +2244,7 @@ _outMergePath(StringInfo str, const MergePath *node)
 	_outJoinPathInfo(str, (const JoinPath *) node);
 
 	WRITE_NODE_FIELD(path_mergeclauses);
+	WRITE_NODE_FIELD(path_rangeclause);
 	WRITE_NODE_FIELD(outersortkeys);
 	WRITE_NODE_FIELD(innersortkeys);
 	WRITE_BOOL_FIELD(skip_mark_restore);
@@ -2559,6 +2561,8 @@ _outRestrictInfo(StringInfo str, const RestrictInfo *node)
 	WRITE_FLOAT_FIELD(norm_selec, "%.4f");
 	WRITE_FLOAT_FIELD(outer_selec, "%.4f");
 	WRITE_NODE_FIELD(mergeopfamilies);
+	WRITE_NODE_FIELD(rangeleftopfamilies);
+	WRITE_NODE_FIELD(rangerightopfamilies);
 	/* don't write left_ec, leads to infinite recursion in plan tree dump */
 	/* don't write right_ec, leads to infinite recursion in plan tree dump */
 	WRITE_NODE_FIELD(left_em);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index abf08b7a2f..1e71f26eb7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2173,6 +2173,7 @@ _readMergeJoin(void)
 
 	READ_BOOL_FIELD(skip_mark_restore);
 	READ_NODE_FIELD(mergeclauses);
+    READ_NODE_FIELD(rangeclause);
 
 	numCols = list_length(local_node->mergeclauses);
 
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1e4d404f02..041331d6bf 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3419,6 +3419,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	double		inner_path_rows = inner_path->rows;
 	List	   *mergeclauses = path->path_mergeclauses;
 	List	   *innersortkeys = path->innersortkeys;
+	List	   *allclauses;
 	Cost		startup_cost = workspace->startup_cost;
 	Cost		run_cost = workspace->run_cost;
 	Cost		inner_run_cost = workspace->inner_run_cost;
@@ -3435,9 +3436,12 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 				rescannedtuples;
 	double		rescanratio;
 
-	/* Protect some assumptions below that rowcounts aren't zero */
-	if (inner_path_rows <= 0)
-		inner_path_rows = 1;
+	allclauses = list_concat(list_copy(mergeclauses), path->path_rangeclause);
+
+
+    /* Protect some assumptions below that rowcounts aren't zero */
+    if (inner_path_rows <= 0)
+        inner_path_rows = 1;
 
 	/* Mark the path with the correct row estimate */
 	if (path->jpath.path.param_info)
@@ -3466,7 +3470,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * Compute cost of the mergequals and qpquals (other restriction clauses)
 	 * separately.
 	 */
-	cost_qual_eval(&merge_qual_cost, mergeclauses, root);
+	cost_qual_eval(&merge_qual_cost, allclauses, root);
 	cost_qual_eval(&qp_qual_cost, path->jpath.joinrestrictinfo, root);
 	qp_qual_cost.startup -= merge_qual_cost.startup;
 	qp_qual_cost.per_tuple -= merge_qual_cost.per_tuple;
@@ -3490,7 +3494,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * Get approx # tuples passing the mergequals.  We use approx_tuple_count
 	 * here because we need an estimate done with JOIN_INNER semantics.
 	 */
-	mergejointuples = approx_tuple_count(root, &path->jpath, mergeclauses);
+	mergejointuples = approx_tuple_count(root, &path->jpath, allclauses);
 
 	/*
 	 * When there are equal merge keys in the outer relation, the mergejoin
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 0f3ad8aa65..4777e0a850 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -16,6 +16,7 @@
 
 #include <math.h>
 
+#include "catalog/pg_operator.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
 #include "nodes/nodeFuncs.h"
@@ -24,6 +25,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
 /* Hook for plugins to get control in add_paths_to_joinrel() */
@@ -84,6 +86,9 @@ static List *select_mergejoin_clauses(PlannerInfo *root,
 									  List *restrictlist,
 									  JoinType jointype,
 									  bool *mergejoin_allowed);
+static List *select_rangejoin_clauses(RelOptInfo *outerrel,
+									  RelOptInfo *innerrel,
+									  List *restrictlist);
 static void generate_mergejoin_paths(PlannerInfo *root,
 									 RelOptInfo *joinrel,
 									 RelOptInfo *innerrel,
@@ -147,6 +152,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 
 	extra.restrictlist = restrictlist;
 	extra.mergeclause_list = NIL;
+	extra.rangeclause_list = NIL;
 	extra.sjinfo = sjinfo;
 	extra.param_source_rels = NULL;
 
@@ -207,6 +213,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 * it's a full join.
 	 */
 	if (enable_mergejoin || jointype == JOIN_FULL)
+	{
 		extra.mergeclause_list = select_mergejoin_clauses(root,
 														  joinrel,
 														  outerrel,
@@ -215,6 +222,12 @@ add_paths_to_joinrel(PlannerInfo *root,
 														  jointype,
 														  &mergejoin_allowed);
 
+		if (jointype == JOIN_INNER || jointype == JOIN_LEFT)
+			extra.rangeclause_list = select_rangejoin_clauses(outerrel,
+															  innerrel,
+															  restrictlist);
+	}
+
 	/*
 	 * If it's SEMI, ANTI, or inner_unique join, compute correction factors
 	 * for cost estimation.  These will be the same for all paths.
@@ -792,6 +805,7 @@ try_mergejoin_path(PlannerInfo *root,
 				   Path *inner_path,
 				   List *pathkeys,
 				   List *mergeclauses,
+				   List *rangeclause,
 				   List *outersortkeys,
 				   List *innersortkeys,
 				   JoinType jointype,
@@ -865,6 +879,7 @@ try_mergejoin_path(PlannerInfo *root,
 									   pathkeys,
 									   required_outer,
 									   mergeclauses,
+									   rangeclause,
 									   outersortkeys,
 									   innersortkeys));
 	}
@@ -941,6 +956,7 @@ try_partial_mergejoin_path(PlannerInfo *root,
 										   pathkeys,
 										   NULL,
 										   mergeclauses,
+										   NIL,
 										   outersortkeys,
 										   innersortkeys));
 }
@@ -1122,7 +1138,8 @@ sort_inner_and_outer(PlannerInfo *root,
 	Path	   *inner_path;
 	Path	   *cheapest_partial_outer = NULL;
 	Path	   *cheapest_safe_inner = NULL;
-	List	   *all_pathkeys;
+	List	   *merge_pathkeys;
+	List	   *range_pathkeys;
 	ListCell   *l;
 
 	/*
@@ -1222,25 +1239,80 @@ sort_inner_and_outer(PlannerInfo *root,
 	 * some heuristics behind it (see that function), so be sure to try it
 	 * exactly as-is as well as making variants.
 	 */
-	all_pathkeys = select_outer_pathkeys_for_merge(root,
-												   extra->mergeclause_list,
-												   joinrel);
 
-	foreach(l, all_pathkeys)
+	range_pathkeys = select_outer_pathkeys_for_range(root,
+													 extra->rangeclause_list);
+
+	merge_pathkeys = select_outer_pathkeys_for_merge(root,
+													 extra->mergeclause_list,
+													 joinrel);
+
+	if(merge_pathkeys == NIL && enable_mergejoin)
+	{
+		foreach(l, range_pathkeys)
+		{
+			PathKey		*range_pathkey = (PathKey *) lfirst(l);
+			List		*outerkeys = list_make1(range_pathkey);
+			List		*innerkeys = NIL;
+			List		*rangeclauses;
+			ListCell	*lc;
+
+			rangeclauses = find_rangeclauses_for_outer_pathkeys(root,
+												outerkeys,
+												NIL,
+												extra->rangeclause_list);
+
+			foreach(lc, rangeclauses)
+			{
+				List	*rangeclause = (List *) lfirst(lc);
+				PathKey *range_inner_pathkey =
+						make_inner_pathkey_for_range(root,
+													 rangeclause);
+
+				innerkeys = lappend(innerkeys, range_inner_pathkey);
+
+				merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+															 outerkeys);
+
+				/*
+				 * And now we can make the path.
+				 *
+				 * Note: it's possible that the cheapest paths will already be sorted
+				 * properly.  try_mergejoin_path will detect that case and suppress an
+				 * explicit sort step, so we needn't do so here.
+				 */
+				try_mergejoin_path(root,
+								   joinrel,
+								   outer_path,
+								   inner_path,
+								   merge_pathkeys,
+								   NIL,
+								   rangeclause,
+								   outerkeys,
+								   innerkeys,
+								   jointype,
+								   extra,
+								   false);
+			}
+		}
+	}
+
+
+	foreach(l, merge_pathkeys)
 	{
 		List	   *front_pathkey = (List *) lfirst(l);
 		List	   *cur_mergeclauses;
 		List	   *outerkeys;
 		List	   *innerkeys;
-		List	   *merge_pathkeys;
+		List	   *mergejoin_pathkeys;
 
 		/* Make a pathkey list with this guy first */
-		if (l != list_head(all_pathkeys))
+		if (l != list_head(merge_pathkeys))
 			outerkeys = lcons(front_pathkey,
-							  list_delete_nth_cell(list_copy(all_pathkeys),
-												   foreach_current_index(l)));
+							  list_delete_ptr(list_copy(merge_pathkeys),
+											  front_pathkey));
 		else
-			outerkeys = all_pathkeys;	/* no work at first one... */
+			outerkeys = merge_pathkeys;	/* no work at first one... */
 
 		/* Sort the mergeclauses into the corresponding ordering */
 		cur_mergeclauses =
@@ -1257,7 +1329,7 @@ sort_inner_and_outer(PlannerInfo *root,
 												  outerkeys);
 
 		/* Build pathkeys representing output sort order */
-		merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+		mergejoin_pathkeys = build_join_pathkeys(root, joinrel, jointype,
 											 outerkeys);
 
 		/*
@@ -1271,8 +1343,9 @@ sort_inner_and_outer(PlannerInfo *root,
 						   joinrel,
 						   outer_path,
 						   inner_path,
-						   merge_pathkeys,
+						   mergejoin_pathkeys,
 						   cur_mergeclauses,
+						   NIL,
 						   outerkeys,
 						   innerkeys,
 						   jointype,
@@ -1288,12 +1361,88 @@ sort_inner_and_outer(PlannerInfo *root,
 									   joinrel,
 									   cheapest_partial_outer,
 									   cheapest_safe_inner,
-									   merge_pathkeys,
+									   mergejoin_pathkeys,
 									   cur_mergeclauses,
 									   outerkeys,
 									   innerkeys,
 									   jointype,
 									   extra);
+
+		foreach(l, range_pathkeys)
+		{
+			PathKey		*range_outer_pathkey = (PathKey *) lfirst(l);
+			List		*range_outerkeys = list_copy(outerkeys);
+			List		*range_innerkeys;
+			List		*rangeclauses;
+			ListCell	*lc;
+
+			if(list_member_ptr(range_outerkeys, range_outer_pathkey))
+			{
+				if(!equal(llast(range_outerkeys), range_outer_pathkey))
+					continue;
+			}
+			else
+				range_outerkeys = lappend(range_outerkeys, range_outer_pathkey);
+
+			/* Sort the mergeclauses into the corresponding ordering */
+			cur_mergeclauses =
+				find_mergeclauses_for_outer_pathkeys(root,
+													 range_outerkeys,
+													 extra->mergeclause_list);
+
+			/* Should have used them all... */
+			Assert(list_length(cur_mergeclauses) == list_length(extra->mergeclause_list));
+
+			/* Build sort pathkeys for the inner side */
+			range_innerkeys = make_inner_pathkeys_for_merge(root,
+													  	  	cur_mergeclauses,
+															range_outerkeys);
+
+			rangeclauses = find_rangeclauses_for_outer_pathkeys(root,
+												range_outerkeys,
+												cur_mergeclauses,
+												extra->rangeclause_list);
+
+			foreach(lc, rangeclauses)
+			{
+				List	*cur_rangeclause = (List *) lfirst(lc);
+				PathKey *range_inner_pathkey;
+
+				range_inner_pathkey = make_inner_pathkey_for_range(root, cur_rangeclause);
+
+				if(list_member_ptr(range_innerkeys, range_inner_pathkey))
+				{
+					if(!equal(llast(range_innerkeys), range_inner_pathkey))
+						continue;
+				}
+				else
+					range_innerkeys = lappend(range_innerkeys, range_inner_pathkey);
+
+
+				mergejoin_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+															 range_outerkeys);
+
+				/*
+				 * And now we can make the path.
+				 *
+				 * Note: it's possible that the cheapest paths will already be sorted
+				 * properly.  try_mergejoin_path will detect that case and suppress an
+				 * explicit sort step, so we needn't do so here.
+				 */
+				try_mergejoin_path(root,
+								   joinrel,
+								   outer_path,
+								   inner_path,
+								   mergejoin_pathkeys,
+								   cur_mergeclauses,
+								   cur_rangeclause,
+								   range_outerkeys,
+								   range_innerkeys,
+								   jointype,
+								   extra,
+								   false);
+			}
+		}
 	}
 }
 
@@ -1379,6 +1528,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 					   merge_pathkeys,
 					   mergeclauses,
 					   NIL,
+					   NIL,
 					   innersortkeys,
 					   jointype,
 					   extra,
@@ -1477,6 +1627,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 							   newclauses,
 							   NIL,
 							   NIL,
+							   NIL,
 							   jointype,
 							   extra,
 							   is_partial);
@@ -1521,6 +1672,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 								   newclauses,
 								   NIL,
 								   NIL,
+								   NIL,
 								   jointype,
 								   extra,
 								   is_partial);
@@ -2287,3 +2439,138 @@ select_mergejoin_clauses(PlannerInfo *root,
 
 	return result_list;
 }
+
+/*
+ * range_clause_order
+ */
+static int
+range_clause_order(RestrictInfo *first,
+				   RestrictInfo *second)
+{
+	/*
+	 * Extract details from first restrictinfo
+	 */
+	Node   *first_left = get_leftop(first->clause),
+		   *first_right = get_rightop(first->clause);
+	bool	first_outer_is_left = first->outer_is_left;
+	int		first_strategy = get_op_opfamily_strategy(((OpExpr *) first->clause)->opno,
+														(linitial_oid(first->rangeleftopfamilies)));
+	bool    first_less = (first_strategy == BTLessStrategyNumber ||
+							first_strategy == BTLessEqualStrategyNumber);
+
+	/*
+	 * Extract details from second restrictinfo
+	 */
+	Node   *second_left = get_leftop(second->clause),
+		   *second_right = get_rightop(second->clause);
+	bool	second_outer_is_left = second->outer_is_left;
+	int		second_strategy = get_op_opfamily_strategy(((OpExpr *) second->clause)->opno,
+														(linitial_oid(second->rangeleftopfamilies)));
+	bool    second_less = (second_strategy == BTLessStrategyNumber ||
+							second_strategy == BTLessEqualStrategyNumber);
+
+	/*
+	 * Check for rangeclause
+	 */
+	if (first_less && second_less)
+	{
+		if (!first_outer_is_left && second_outer_is_left &&
+				equal(first_left, second_right))
+			return 2;
+		else if (first_outer_is_left && !second_outer_is_left &&
+				equal(first_right, second_left))
+			return 1;
+	}
+	else if (!first_less && !second_less)
+	{
+		if (!first_outer_is_left && second_outer_is_left &&
+				equal(first_left, second_right))
+			return 1;
+		else if (first_outer_is_left && !second_outer_is_left &&
+				equal(first_right, second_left))
+			return 2;
+	}
+	else if (first_less && !second_less)
+	{
+		if (!first_outer_is_left && !second_outer_is_left &&
+				equal(first_left, second_left))
+			return 2;
+		else if (first_outer_is_left && second_outer_is_left &&
+				equal(first_right, second_right))
+			return 1;
+	}
+	else if (!first_less && second_less)
+	{
+		if (!first_outer_is_left && !second_outer_is_left &&
+				equal(first_left, second_left))
+			return 1;
+		else if (first_outer_is_left && second_outer_is_left &&
+				equal(first_right, second_right))
+			return 2;
+	}
+
+	return 0;
+}
+
+/*
+ * select_rangejoin_clauses
+ */
+static List *
+select_rangejoin_clauses(RelOptInfo *outerrel,
+						 RelOptInfo *innerrel,
+						 List *restrictlist)
+{
+	List	   *result_list = NIL;
+	ListCell   *l;
+	List	   *range_candidates = NIL;
+
+	foreach(l, restrictlist)
+	{
+		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
+		OpExpr		 *clause = (OpExpr *) restrictinfo->clause;
+
+		/* Check that clause is a rangejoinable operator clause */
+		if (restrictinfo->rangeleftopfamilies == NIL)
+			continue;			/* not rangejoinable */
+
+		/*
+		 * Check if clause has the form "outer op inner" or "inner op outer".
+		 */
+		if (!clause_sides_match_join(restrictinfo, outerrel, innerrel))
+			continue;			/* no good for these input relations */
+
+		if (restrictinfo->rangeleftopfamilies == restrictinfo->rangerightopfamilies)
+		{
+			ListCell *lc;
+			List	 *range_clause;
+
+			foreach(lc, range_candidates)
+			{
+				RestrictInfo *candidate = (RestrictInfo *) lfirst(lc);
+
+				switch (range_clause_order(restrictinfo, candidate))
+				{
+				case 1:
+					range_clause = list_make2(restrictinfo, candidate);
+					result_list = lappend(result_list, range_clause);
+					break;
+
+				case 2:
+					range_clause = list_make2(candidate, restrictinfo);
+					result_list = lappend(result_list, range_clause);
+					break;
+				default:
+					break;
+				}
+			}
+			range_candidates = lappend(range_candidates, restrictinfo);
+		}
+		else if ((clause->opno == OID_RANGE_CONTAINS_ELEM_OP && restrictinfo->outer_is_left) ||
+				(clause->opno == OID_RANGE_ELEM_CONTAINED_OP && !restrictinfo->outer_is_left))
+		{
+			result_list = lappend(result_list, list_make1(restrictinfo));
+		}
+	}
+	list_free(range_candidates);
+	return result_list;
+}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 216dd26385..f82e760658 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -19,6 +19,7 @@
 
 #include "access/stratnum.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_operator.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/plannodes.h"
@@ -1214,6 +1215,60 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo)
 								 true);
 }
 
+/*
+ * initialize_rangeclause_eclasses
+ */
+void
+initialize_rangeclause_eclasses(PlannerInfo *root,
+								RestrictInfo *restrictinfo)
+{
+	Expr   *clause = restrictinfo->clause;
+	Oid		lefttype,
+			righttype,
+			opno;
+
+	/* Should be a rangeclause ... */
+	Assert(restrictinfo->rangeleftopfamilies != NIL &&
+			restrictinfo->rangerightopfamilies != NIL);
+	/* ... with links not yet set */
+	Assert(restrictinfo->left_ec == NULL);
+	Assert(restrictinfo->right_ec == NULL);
+
+	opno = ((OpExpr *) clause)->opno;
+
+	/* Need the declared input types of the operator */
+	op_input_types(opno, &lefttype, &righttype);
+
+	if(opno == OID_RANGE_CONTAINS_ELEM_OP)
+		righttype = exprType(get_rightop(clause));
+
+	else if(opno == OID_RANGE_ELEM_CONTAINED_OP)
+		lefttype = exprType(get_leftop(clause));
+
+
+	/* Find or create a matching EquivalenceClass for each side */
+	restrictinfo->left_ec =
+		get_eclass_for_sort_expr(root,
+				 	 	 	 	 (Expr *) get_leftop(clause),
+								 restrictinfo->nullable_relids,
+								 restrictinfo->rangeleftopfamilies,
+								 lefttype,
+								 ((OpExpr *) clause)->inputcollid,
+								 0,
+								 NULL,
+								 true);
+	restrictinfo->right_ec =
+		get_eclass_for_sort_expr(root,
+								 (Expr *) get_rightop(clause),
+								 restrictinfo->nullable_relids,
+								 restrictinfo->rangerightopfamilies,
+								 righttype,
+								 ((OpExpr *) clause)->inputcollid,
+								 0,
+								 NULL,
+								 true);
+}
+
 /*
  * update_mergeclause_eclasses
  *		Make the cached EquivalenceClass links valid in a mergeclause
@@ -1347,6 +1402,64 @@ find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 	return mergeclauses;
 }
 
+/*
+ * find_rangeclauses_for_outer_pathkeys
+ */
+List *
+find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
+									List *pathkeys,
+									List *mergeclauses,
+									List *rangeclauses)
+{
+	ListCell   *i;
+	RestrictInfo *mergeclause = NULL;
+	List *result_list = NIL;
+
+	if(mergeclauses)
+		mergeclause = llast(mergeclauses);
+
+	foreach(i, pathkeys)
+	{
+		PathKey    *pathkey = (PathKey *) lfirst(i);
+		EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
+		ListCell   *j;
+
+		if(mergeclause != NULL)
+		{
+			if(mergeclause->outer_is_left)
+			{
+				if(mergeclause->left_ec != pathkey_ec)
+					continue;
+			}
+			else if(mergeclause->right_ec != pathkey_ec)
+				continue;
+		}
+
+		foreach(j, rangeclauses)
+		{
+			List				*rangeclause = (List *) lfirst(j);
+			RestrictInfo 		*rinfo = (RestrictInfo *) linitial(rangeclause);
+			EquivalenceClass 	*clause_ec;
+
+			clause_ec = rinfo->outer_is_left ?
+				rinfo->left_ec : rinfo->right_ec;
+
+			if (clause_ec == pathkey_ec)
+				result_list = lappend(result_list, rangeclause);
+		}
+
+		/*
+		 * Was it the last possible pathkey?
+		 */
+		if(mergeclause == NULL)
+			break;
+		else
+			mergeclause = NULL;
+
+  }
+  return result_list;
+}
+
 /*
  * select_outer_pathkeys_for_merge
  *	  Builds a pathkey list representing a possible sort ordering
@@ -1522,6 +1635,37 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 	return pathkeys;
 }
 
+/*
+ * select_outer_pathkeys_for_range
+ */
+List *
+select_outer_pathkeys_for_range(PlannerInfo *root,
+								List *rangeclauses)
+{
+	EquivalenceClass	*ec;
+	PathKey 			*pathkey;
+	List 				*pathkeys = NIL;
+	ListCell			*lc;
+
+	foreach(lc, rangeclauses){
+		List *rangeclause = lfirst(lc);
+		RestrictInfo *rinfo = linitial(rangeclause);
+
+		if (rinfo->outer_is_left)
+			ec = rinfo->left_ec;
+		else
+			ec = rinfo->right_ec;
+
+		pathkey = make_canonical_pathkey(root,
+										 ec,
+										 linitial_oid(ec->ec_opfamilies),
+										 BTLessStrategyNumber,
+										 false);
+		pathkeys = lappend(pathkeys, pathkey);
+	}
+	return pathkeys;
+}
+
 /*
  * make_inner_pathkeys_for_merge
  *	  Builds a pathkey list representing the explicit sort order that
@@ -1621,6 +1765,35 @@ make_inner_pathkeys_for_merge(PlannerInfo *root,
 	return pathkeys;
 }
 
+/*
+ * make_inner_pathkey_for_range
+ */
+PathKey *
+make_inner_pathkey_for_range(PlannerInfo *root,
+							 List *rangeclause)
+{
+	EquivalenceClass *ec;
+	PathKey 	 *pathkey;
+	RestrictInfo *rinfo;
+
+	if(rangeclause == NIL)
+		return NULL;
+
+	rinfo = linitial(rangeclause);
+
+	if (rinfo->outer_is_left)
+		ec = rinfo->right_ec;
+	else
+		ec = rinfo->left_ec;
+
+	pathkey = make_canonical_pathkey(root,
+									 ec,
+									 linitial_oid(ec->ec_opfamilies),
+									 BTLessStrategyNumber,
+									 false);
+	return pathkey;
+}
+
 /*
  * trim_mergeclauses_for_inner_pathkeys
  *	  This routine trims a list of mergeclauses to include just those that
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 3dc0176a51..49cc8d16d6 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -246,6 +246,7 @@ static Hash *make_hash(Plan *lefttree,
 static MergeJoin *make_mergejoin(List *tlist,
 								 List *joinclauses, List *otherclauses,
 								 List *mergeclauses,
+								 List *rangeclause,
 								 Oid *mergefamilies,
 								 Oid *mergecollations,
 								 int *mergestrategies,
@@ -4301,6 +4302,7 @@ create_mergejoin_plan(PlannerInfo *root,
 	List	   *joinclauses;
 	List	   *otherclauses;
 	List	   *mergeclauses;
+	List	   *rangeclause;
 	List	   *outerpathkeys;
 	List	   *innerpathkeys;
 	int			nClauses;
@@ -4355,6 +4357,9 @@ create_mergejoin_plan(PlannerInfo *root,
 	mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
 	joinclauses = list_difference(joinclauses, mergeclauses);
 
+	rangeclause = get_actual_clauses(best_path->path_rangeclause);
+	joinclauses = list_difference(joinclauses, rangeclause);
+
 	/*
 	 * Replace any outer-relation variables with nestloop params.  There
 	 * should not be any in the mergeclauses.
@@ -4365,6 +4370,8 @@ create_mergejoin_plan(PlannerInfo *root,
 			replace_nestloop_params(root, (Node *) joinclauses);
 		otherclauses = (List *)
 			replace_nestloop_params(root, (Node *) otherclauses);
+		rangeclause = (List *)
+			replace_nestloop_params(root, (Node *) rangeclause);
 	}
 
 	/*
@@ -4581,6 +4588,7 @@ create_mergejoin_plan(PlannerInfo *root,
 							   joinclauses,
 							   otherclauses,
 							   mergeclauses,
+							   rangeclause,
 							   mergefamilies,
 							   mergecollations,
 							   mergestrategies,
@@ -5883,6 +5891,7 @@ make_mergejoin(List *tlist,
 			   List *joinclauses,
 			   List *otherclauses,
 			   List *mergeclauses,
+			   List *rangeclause,
 			   Oid *mergefamilies,
 			   Oid *mergecollations,
 			   int *mergestrategies,
@@ -5909,6 +5918,7 @@ make_mergejoin(List *tlist,
 	node->join.jointype = jointype;
 	node->join.inner_unique = inner_unique;
 	node->join.joinqual = joinclauses;
+	node->rangeclause = rangeclause;
 
 	return node;
 }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index f6a202d900..10990394ec 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -16,6 +16,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_operator.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -77,6 +78,7 @@ static bool check_equivalence_delay(PlannerInfo *root,
 									RestrictInfo *restrictinfo);
 static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
 static void check_mergejoinable(RestrictInfo *restrictinfo);
+static void check_rangejoinable(RestrictInfo *restrictinfo);
 static void check_hashjoinable(RestrictInfo *restrictinfo);
 static void check_memoizable(RestrictInfo *restrictinfo);
 
@@ -1877,6 +1879,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	 */
 	check_mergejoinable(restrictinfo);
 
+	check_rangejoinable(restrictinfo);
+
 	/*
 	 * If it is a true equivalence clause, send it to the EquivalenceClass
 	 * machinery.  We do *not* attach it directly to any restriction or join
@@ -1962,6 +1966,13 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 		}
 	}
 
+	if(restrictinfo->rangeleftopfamilies)
+	{
+		Assert(restrictinfo->rangerightopfamilies);
+
+		initialize_rangeclause_eclasses(root, restrictinfo);
+	}
+
 	/* No EC special case applies, so push it into the clause lists */
 	distribute_restrictinfo_to_rels(root, restrictinfo);
 }
@@ -2677,6 +2688,50 @@ check_mergejoinable(RestrictInfo *restrictinfo)
 	 */
 }
 
+/*
+ * check_rangejoinable
+ */
+static void
+check_rangejoinable(RestrictInfo *restrictinfo)
+{
+	OpExpr	   *clause = (OpExpr *) restrictinfo->clause;
+	Oid			opno = clause->opno;
+
+	if (!restrictinfo->can_join)
+		return;
+	if (contain_volatile_functions((Node *) clause))
+		return;
+
+	if (opno == OID_RANGE_CONTAINS_ELEM_OP ||
+			opno == OID_RANGE_ELEM_CONTAINED_OP)
+	{
+		Node		   *leftarg = get_leftop(clause),
+					   *rightarg = get_rightop(clause);
+
+		Oid				lefttype = exprType(leftarg),
+						righttype = exprType(rightarg);
+
+		TypeCacheEntry *left_typecache = lookup_type_cache(lefttype, TYPECACHE_CMP_PROC),
+					   *right_typecache = lookup_type_cache(righttype, TYPECACHE_CMP_PROC);
+
+		restrictinfo->rangeleftopfamilies =
+				lappend_oid(restrictinfo->rangeleftopfamilies,
+							left_typecache->btree_opf);
+
+		restrictinfo->rangerightopfamilies =
+				lappend_oid(restrictinfo->rangerightopfamilies,
+							right_typecache->btree_opf);
+	}
+	else
+	{
+		List *opfamilies = get_rangejoin_opfamilies(opno);
+
+		restrictinfo->rangeleftopfamilies = opfamilies;
+		restrictinfo->rangerightopfamilies = opfamilies;
+
+	}
+}
+
 /*
  * check_hashjoinable
  *	  If the restrictinfo's clause is hashjoinable, set the hashjoin
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 6ccec759bd..c309a3e114 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2038,6 +2038,14 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
 										 (Index) 0,
 										 rtoffset,
 										 NUM_EXEC_QUAL((Plan *) join));
+
+		mj->rangeclause = fix_join_expr(root,
+										 mj->rangeclause,
+										 outer_itlist,
+										 inner_itlist,
+										 (Index) 0,
+										 rtoffset,
+										 NUM_EXEC_QUAL((Plan *) join));
 	}
 	else if (IsA(join, HashJoin))
 	{
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index e53d381e19..f8490fa4ef 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -2502,6 +2502,7 @@ create_mergejoin_path(PlannerInfo *root,
 					  List *pathkeys,
 					  Relids required_outer,
 					  List *mergeclauses,
+					  List *rangeclause,
 					  List *outersortkeys,
 					  List *innersortkeys)
 {
@@ -2530,6 +2531,7 @@ create_mergejoin_path(PlannerInfo *root,
 	pathnode->jpath.innerjoinpath = inner_path;
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
 	pathnode->path_mergeclauses = mergeclauses;
+	pathnode->path_rangeclause = rangeclause;
 	pathnode->outersortkeys = outersortkeys;
 	pathnode->innersortkeys = innersortkeys;
 	/* pathnode->skip_mark_restore will be set by final_cost_mergejoin */
@@ -4134,6 +4136,7 @@ do { \
 				REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
 				ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
 				ADJUST_CHILD_ATTRS(mpath->path_mergeclauses);
+				ADJUST_CHILD_ATTRS(mpath->path_rangeclause);
 				new_path = (Path *) mpath;
 			}
 			break;
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index ebfcf91826..3051ad540b 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -201,6 +201,8 @@ make_restrictinfo_internal(PlannerInfo *root,
 	restrictinfo->outer_selec = -1;
 
 	restrictinfo->mergeopfamilies = NIL;
+	restrictinfo->rangeleftopfamilies = NIL;
+	restrictinfo->rangerightopfamilies = NIL;
 
 	restrictinfo->left_ec = NULL;
 	restrictinfo->right_ec = NULL;
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 815175a654..3972480519 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -2507,6 +2507,66 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
 	return true;
 }
 
+/*
+ * Test whether range r is right of a specific element value.
+ */
+bool
+elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
+{
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	int32		cmp;
+
+	range_deserialize(typcache, r, &lower, &upper, &empty);
+
+	if (empty)
+		return true;
+
+	if (!lower.infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  lower.val, val));
+		if (cmp > 0)
+			return true;
+		if (cmp == 0 && !lower.inclusive)
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Test whether range r is left of a specific element value.
+ */
+bool
+elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
+{
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	int32		cmp;
+
+	range_deserialize(typcache, r, &lower, &upper, &empty);
+
+	if (empty)
+		return true;
+
+	if (!upper.infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  upper.val, val));
+		if (cmp < 0)
+			return true;
+		if (cmp == 0 && !upper.inclusive)
+			return true;
+	}
+
+	return false;
+}
+
 
 /*
  * datum_compute_size() and datum_write() are used to insert the bound
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a2..f6cc0cdb8a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -389,6 +389,40 @@ get_mergejoin_opfamilies(Oid opno)
 	return result;
 }
 
+/*
+ * get_rangejoin_opfamilies
+ */
+List *
+get_rangejoin_opfamilies(Oid opno)
+{
+	List	   *result = NIL;
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * Search pg_amop to see if the target operator is registered as the "<",
+	 * "<=", ">" or ">=" operator of any btree opfamily.
+	 */
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == BTREE_AM_OID &&
+			(aform->amopstrategy == BTLessStrategyNumber ||
+				aform->amopstrategy == BTLessEqualStrategyNumber ||
+				aform->amopstrategy == BTGreaterStrategyNumber ||
+				aform->amopstrategy == BTGreaterEqualStrategyNumber))
+			result = lappend_oid(result, aform->amopfamily);
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_compatible_hash_operators
  *		Get the OID(s) of hash equality operator(s) compatible with the given
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 2e8cbee69f..a682694a21 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1949,6 +1949,7 @@ typedef struct NestLoopState
  */
 /* private in nodeMergejoin.c: */
 typedef struct MergeJoinClauseData *MergeJoinClause;
+typedef struct RangeJoinData *RangeData;
 
 typedef struct MergeJoinState
 {
@@ -1970,6 +1971,8 @@ typedef struct MergeJoinState
 	TupleTableSlot *mj_NullInnerTupleSlot;
 	ExprContext *mj_OuterEContext;
 	ExprContext *mj_InnerEContext;
+	RangeData	 mj_RangeData;
+	bool		 mj_RangeJoin;
 } MergeJoinState;
 
 /* ----------------
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 186e89905b..c7d4431f06 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1647,6 +1647,7 @@ typedef struct MergePath
 {
 	JoinPath	jpath;
 	List	   *path_mergeclauses;	/* join clauses to be used for merge */
+	List	   *path_rangeclause;	/* join clause to be used for range merge */
 	List	   *outersortkeys;	/* keys for explicit sort, if any */
 	List	   *innersortkeys;	/* keys for explicit sort, if any */
 	bool		skip_mark_restore;	/* can executor skip mark/restore? */
@@ -2102,6 +2103,8 @@ typedef struct RestrictInfo
 
 	/* valid if clause is mergejoinable, else NIL */
 	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List	   *rangeleftopfamilies;
+	List	   *rangerightopfamilies;
 
 	/* cache space for mergeclause processing; NULL if not yet set */
 	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
@@ -2529,6 +2532,7 @@ typedef struct JoinPathExtraData
 {
 	List	   *restrictlist;
 	List	   *mergeclause_list;
+	List	   *rangeclause_list;
 	bool		inner_unique;
 	SpecialJoinInfo *sjinfo;
 	SemiAntiJoinFactors semifactors;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 01a246d50e..5fdc45faf9 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -754,6 +754,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 */
+	List	   *rangeclause;	/* rangeclause as expression tree */
 } MergeJoin;
 
 /* ----------------
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index f704d39980..365d8b809e 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -167,6 +167,7 @@ extern MergePath *create_mergejoin_path(PlannerInfo *root,
 										List *pathkeys,
 										Relids required_outer,
 										List *mergeclauses,
+										List *rangeclause,
 										List *outersortkeys,
 										List *innersortkeys);
 
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index f1d111063c..0acfd297eb 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -231,17 +231,27 @@ extern List *make_pathkeys_for_sortclauses(PlannerInfo *root,
 										   List *tlist);
 extern void initialize_mergeclause_eclasses(PlannerInfo *root,
 											RestrictInfo *restrictinfo);
+extern void initialize_rangeclause_eclasses(PlannerInfo *root,
+											RestrictInfo *restrictinfo);
 extern void update_mergeclause_eclasses(PlannerInfo *root,
 										RestrictInfo *restrictinfo);
 extern List *find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 												  List *pathkeys,
 												  List *restrictinfos);
+extern List *find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
+												  List *pathkeys,
+												  List *mergeclauses,
+												  List *rangeclauses);
 extern List *select_outer_pathkeys_for_merge(PlannerInfo *root,
 											 List *mergeclauses,
 											 RelOptInfo *joinrel);
+extern List *select_outer_pathkeys_for_range(PlannerInfo *root,
+											 List *rangeclauses);
 extern List *make_inner_pathkeys_for_merge(PlannerInfo *root,
 										   List *mergeclauses,
 										   List *outer_pathkeys);
+extern PathKey *make_inner_pathkey_for_range(PlannerInfo *root,
+											 List *rangeclause);
 extern List *trim_mergeclauses_for_inner_pathkeys(PlannerInfo *root,
 												  List *mergeclauses,
 												  List *pathkeys);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 77871aaefc..a47beb8be5 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -79,6 +79,7 @@ extern bool get_ordering_op_properties(Oid opno,
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
 extern List *get_mergejoin_opfamilies(Oid opno);
+extern List *get_rangejoin_opfamilies(Oid opno);
 extern bool get_compatible_hash_operators(Oid opno,
 										  Oid *lhs_opno, Oid *rhs_opno);
 extern bool get_op_hash_functions(Oid opno,
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 04c302c619..51bdc5308b 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -95,6 +95,8 @@ typedef struct
  */
 
 extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
+extern bool elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r);
+extern bool elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r);
 
 /* internal versions of the above */
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
#8Daniel Gustafsson
daniel@yesql.se
In reply to: Thomas (#7)
Re: Patch: Range Merge Join

This patch fails to compile due to an incorrect function name in an assertion:

nodeMergejoin.c:297:9: warning: implicit declaration of function 'list_legth' is invalid in C99 [-Wimplicit-function-declaration]
Assert(list_legth(node->rangeclause) < 3);
^

--
Daniel Gustafsson https://vmware.com/

#9Thomas
thomasmannhart97@gmail.com
In reply to: Daniel Gustafsson (#8)
1 attachment(s)
Re: Patch: Range Merge Join

Thank you for the feedback and sorry for the oversight. I fixed the bug and
attached a new version of the patch.

Kind Regards, Thomas

Am Mi., 17. Nov. 2021 um 15:03 Uhr schrieb Daniel Gustafsson <
daniel@yesql.se>:

Show quoted text

This patch fails to compile due to an incorrect function name in an
assertion:

nodeMergejoin.c:297:9: warning: implicit declaration of function
'list_legth' is invalid in C99 [-Wimplicit-function-declaration]
Assert(list_legth(node->rangeclause) < 3);
^

--
Daniel Gustafsson https://vmware.com/

Attachments:

postgres-rmj_11-17-21.patchapplication/octet-stream; name=postgres-rmj_11-17-21.patchDownload
diff --git a/.gitignore b/.gitignore
index 794e35b73c..5dca1a39ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,14 @@ lib*.pc
 /Debug/
 /Release/
 /tmp_install/
+
+# Thomas
+/data/
+/server/
+.idea/
+.settings/
+.cproject
+.project
+logfile
+src/backend/catalog/postgres.*
+*.patch
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 10644dfac4..9b7503630c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1206,8 +1206,16 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			pname = sname = "Nested Loop";
 			break;
 		case T_MergeJoin:
-			pname = "Merge";	/* "Join" gets added by jointype switch */
-			sname = "Merge Join";
+			if(((MergeJoin *) plan)->rangeclause)
+			{
+				pname = "Range Merge";	/* "Join" gets added by jointype switch */
+				sname = "Range Merge Join";
+			}
+			else
+			{
+				pname = "Merge";	/* "Join" gets added by jointype switch */
+				sname = "Merge Join";
+			}
 			break;
 		case T_HashJoin:
 			pname = "Hash";		/* "Join" gets added by jointype switch */
@@ -1948,6 +1956,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_MergeJoin:
 			show_upper_qual(((MergeJoin *) plan)->mergeclauses,
 							"Merge Cond", planstate, ancestors, es);
+			show_upper_qual(((MergeJoin *) plan)->rangeclause,
+							"Range Cond", planstate, ancestors, es);
 			show_upper_qual(((MergeJoin *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
 			if (((MergeJoin *) plan)->join.joinqual)
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index b41454ab6d..09131479b1 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -93,11 +93,15 @@
 #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/rangetypes.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/typcache.h"
 
 
 /*
@@ -140,6 +144,18 @@ typedef struct MergeJoinClauseData
 	SortSupportData ssup;
 }			MergeJoinClauseData;
 
+/*
+ * Runtime data for the range clause
+ */
+typedef struct RangeJoinData
+{
+	ExprState *startClause;
+	ExprState *endClause;
+	ExprState *rangeExpr;
+	ExprState *elemExpr;
+
+}			RangeJoinData;
+
 /* Result type for MJEvalOuterValues and MJEvalInnerValues */
 typedef enum
 {
@@ -269,6 +285,57 @@ MJExamineQuals(List *mergeclauses,
 	return clauses;
 }
 
+/*
+ * MJCreateRangeData
+ */
+static RangeData
+MJCreateRangeData(List *rangeclause,
+				   PlanState *parent)
+{
+	RangeData data;
+
+	Assert(list_length(node->rangeclause) < 3);
+
+	data = (RangeData) palloc0(sizeof(RangeJoinData));
+
+	data->startClause = NULL;
+	data->endClause = NULL;
+	data->rangeExpr = NULL;
+	data->elemExpr = NULL;
+
+	if(list_length(rangeclause) == 2)
+	{
+		data->startClause = ExecInitExpr(linitial(rangeclause), parent);
+		data->endClause = ExecInitExpr(lsecond(rangeclause), parent);
+	}
+	else
+	{
+		OpExpr		*qual = (OpExpr *) linitial(rangeclause);
+		ExprState	*lexpr;
+		ExprState	*rexpr;
+
+		/*
+		 * Prepare the input expressions for execution.
+		 */
+		lexpr = ExecInitExpr((Expr *) get_leftop(qual), parent);
+		rexpr = ExecInitExpr((Expr *) get_rightop(qual), parent);
+
+		if(qual->opno == OID_RANGE_CONTAINS_ELEM_OP)
+		{
+			data->rangeExpr = lexpr;
+			data->elemExpr = rexpr;
+		}
+		else
+		{
+			Assert(qual->opno == OID_RANGE_ELEM_CONTAINED_OP);
+			data->rangeExpr = rexpr;
+			data->elemExpr = lexpr;
+		}
+	}
+
+	return data;
+}
+
 /*
  * MJEvalOuterValues
  *
@@ -445,6 +512,73 @@ MJCompare(MergeJoinState *mergestate)
 }
 
 
+/*
+ * MJCompareRange
+ *
+ * Compare the rangejoinable values of the current two input tuples
+ * and return 0 if they are equal (ie, the outer interval contains the inner),
+ * >0 if outer > inner, <0 if outer < inner.
+ */
+static int
+MJCompareRange(MergeJoinState *mergestate)
+{
+	int 		  result = 0;
+	bool 		  isNull;
+	MemoryContext oldContext;
+	RangeData	  rangeData = mergestate->mj_RangeData;
+	ExprContext	 *econtext = mergestate->js.ps.ps_ExprContext;
+	ExprState	 *endClause = rangeData->endClause,
+				 *startClause = rangeData->startClause,
+				 *rangeExpr = rangeData->rangeExpr,
+				 *elemExpr = rangeData->elemExpr;
+
+	/*
+	 * Call the comparison functions in short-lived context, in case they leak
+	 * memory.
+	 */
+	ResetExprContext(econtext);
+
+	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+	econtext->ecxt_outertuple = mergestate->mj_OuterTupleSlot;
+	econtext->ecxt_innertuple = mergestate->mj_InnerTupleSlot;
+
+	if (endClause != NULL)
+	{
+		Assert(startClause != NULL);
+
+		if (!ExecEvalExprSwitchContext(endClause, econtext, &isNull))
+			result = -1;
+		else if (!ExecEvalExprSwitchContext(startClause, econtext, &isNull))
+			result = 1;
+	}
+	else
+	{
+		Datum			rangeDatum,
+    					elemDatum;
+		Oid				rangeType;
+		TypeCacheEntry *typecache;
+
+		Assert(rangeExpr != NULL && elemExpr != NULL);
+
+		rangeDatum = ExecEvalExprSwitchContext(rangeExpr, econtext, &isNull);
+		elemDatum = ExecEvalExprSwitchContext(elemExpr, econtext, &isNull);
+
+		rangeType = exprType((Node *) rangeExpr->expr);
+		typecache = lookup_type_cache(rangeType, TYPECACHE_RANGE_INFO);
+
+		if (elem_after_range_internal(typecache, elemDatum, DatumGetRangeTypeP(rangeDatum)))
+			result = -1;
+		else if (elem_before_range_internal(typecache, elemDatum, DatumGetRangeTypeP(rangeDatum)))
+			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.
@@ -604,6 +738,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprState  *otherqual;
 	bool		qualResult;
 	int			compareResult;
+	int			compareRangeResult;
 	PlanState  *innerPlan;
 	TupleTableSlot *innerTupleSlot;
 	PlanState  *outerPlan;
@@ -611,6 +746,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprContext *econtext;
 	bool		doFillOuter;
 	bool		doFillInner;
+	bool		isRangeJoin;
 
 	CHECK_FOR_INTERRUPTS();
 
@@ -624,6 +760,7 @@ ExecMergeJoin(PlanState *pstate)
 	otherqual = node->js.ps.qual;
 	doFillOuter = node->mj_FillOuter;
 	doFillInner = node->mj_FillInner;
+	isRangeJoin = node->mj_RangeJoin;
 
 	/*
 	 * Reset per-tuple memory context to free any expression evaluation
@@ -891,8 +1028,23 @@ ExecMergeJoin(PlanState *pstate)
 						compareResult = MJCompare(node);
 						MJ_DEBUG_COMPARE(compareResult);
 
-						if (compareResult == 0)
-							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						if(compareResult == 0)
+						{
+							if(isRangeJoin)
+							{
+								compareRangeResult = MJCompareRange(node);
+
+								if (compareRangeResult == 0)
+									node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+								else
+								{
+									Assert(compareRangeResult < 0);
+									node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+								}
+							}
+							else
+								node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						}
 						else
 						{
 							Assert(compareResult < 0);
@@ -1085,7 +1237,19 @@ ExecMergeJoin(PlanState *pstate)
 						/* we need not do MJEvalInnerValues again */
 					}
 
-					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					if(isRangeJoin)
+					{
+						compareRangeResult = MJCompareRange(node);
+
+						if(compareRangeResult == 0)
+							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						else if(compareRangeResult < 0)
+							node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+						else /* compareRangeResult > 0 */
+							node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+					}
+					else
+						node->mj_JoinState = EXEC_MJ_JOINTUPLES;
 				}
 				else
 				{
@@ -1184,12 +1348,28 @@ ExecMergeJoin(PlanState *pstate)
 
 				if (compareResult == 0)
 				{
-					if (!node->mj_SkipMarkRestore)
-						ExecMarkPos(innerPlan);
-
-					MarkInnerTuple(node->mj_InnerTupleSlot, node);
-
-					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					if(isRangeJoin)
+					{
+						compareRangeResult = MJCompareRange(node);
+						if(compareRangeResult == 0)
+						{
+							if (!node->mj_SkipMarkRestore)
+								ExecMarkPos(innerPlan);
+							MarkInnerTuple(node->mj_InnerTupleSlot, node);
+							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						}
+						else if(compareRangeResult < 0)
+							node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+						else /* compareRangeResult > 0 */
+							node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+					}
+					else
+					{
+						if (!node->mj_SkipMarkRestore)
+							ExecMarkPos(innerPlan);
+						MarkInnerTuple(node->mj_InnerTupleSlot, node);
+						node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					}
 				}
 				else if (compareResult < 0)
 					node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
@@ -1532,6 +1712,17 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 		ExecInitQual(node->join.joinqual, (PlanState *) mergestate);
 	/* mergeclauses are handled below */
 
+	/*
+	 * initialize range join
+	 */
+	if(node->rangeclause)
+	{
+		mergestate->mj_RangeData = MJCreateRangeData(node->rangeclause, (PlanState *) mergestate);
+		mergestate->mj_RangeJoin = true;
+	}
+	else
+		mergestate->mj_RangeJoin = false;
+
 	/*
 	 * detect whether we need only consider the first matching inner tuple
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ad1ea2ff2f..262676b1fd 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2349,6 +2349,8 @@ _copyRestrictInfo(const RestrictInfo *from)
 	COPY_SCALAR_FIELD(norm_selec);
 	COPY_SCALAR_FIELD(outer_selec);
 	COPY_NODE_FIELD(mergeopfamilies);
+	COPY_NODE_FIELD(rangeleftopfamilies);
+	COPY_NODE_FIELD(rangerightopfamilies);
 	/* EquivalenceClasses are never copied, so shallow-copy the pointers */
 	COPY_SCALAR_FIELD(left_ec);
 	COPY_SCALAR_FIELD(right_ec);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 23f23f11dc..1361c86d08 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -765,6 +765,7 @@ _outMergeJoin(StringInfo str, const MergeJoin *node)
 
 	WRITE_BOOL_FIELD(skip_mark_restore);
 	WRITE_NODE_FIELD(mergeclauses);
+    WRITE_NODE_FIELD(rangeclause);
 
 	numCols = list_length(node->mergeclauses);
 
@@ -2243,6 +2244,7 @@ _outMergePath(StringInfo str, const MergePath *node)
 	_outJoinPathInfo(str, (const JoinPath *) node);
 
 	WRITE_NODE_FIELD(path_mergeclauses);
+	WRITE_NODE_FIELD(path_rangeclause);
 	WRITE_NODE_FIELD(outersortkeys);
 	WRITE_NODE_FIELD(innersortkeys);
 	WRITE_BOOL_FIELD(skip_mark_restore);
@@ -2559,6 +2561,8 @@ _outRestrictInfo(StringInfo str, const RestrictInfo *node)
 	WRITE_FLOAT_FIELD(norm_selec, "%.4f");
 	WRITE_FLOAT_FIELD(outer_selec, "%.4f");
 	WRITE_NODE_FIELD(mergeopfamilies);
+	WRITE_NODE_FIELD(rangeleftopfamilies);
+	WRITE_NODE_FIELD(rangerightopfamilies);
 	/* don't write left_ec, leads to infinite recursion in plan tree dump */
 	/* don't write right_ec, leads to infinite recursion in plan tree dump */
 	WRITE_NODE_FIELD(left_em);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index abf08b7a2f..1e71f26eb7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2173,6 +2173,7 @@ _readMergeJoin(void)
 
 	READ_BOOL_FIELD(skip_mark_restore);
 	READ_NODE_FIELD(mergeclauses);
+    READ_NODE_FIELD(rangeclause);
 
 	numCols = list_length(local_node->mergeclauses);
 
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1e4d404f02..041331d6bf 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3419,6 +3419,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	double		inner_path_rows = inner_path->rows;
 	List	   *mergeclauses = path->path_mergeclauses;
 	List	   *innersortkeys = path->innersortkeys;
+	List	   *allclauses;
 	Cost		startup_cost = workspace->startup_cost;
 	Cost		run_cost = workspace->run_cost;
 	Cost		inner_run_cost = workspace->inner_run_cost;
@@ -3435,9 +3436,12 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 				rescannedtuples;
 	double		rescanratio;
 
-	/* Protect some assumptions below that rowcounts aren't zero */
-	if (inner_path_rows <= 0)
-		inner_path_rows = 1;
+	allclauses = list_concat(list_copy(mergeclauses), path->path_rangeclause);
+
+
+    /* Protect some assumptions below that rowcounts aren't zero */
+    if (inner_path_rows <= 0)
+        inner_path_rows = 1;
 
 	/* Mark the path with the correct row estimate */
 	if (path->jpath.path.param_info)
@@ -3466,7 +3470,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * Compute cost of the mergequals and qpquals (other restriction clauses)
 	 * separately.
 	 */
-	cost_qual_eval(&merge_qual_cost, mergeclauses, root);
+	cost_qual_eval(&merge_qual_cost, allclauses, root);
 	cost_qual_eval(&qp_qual_cost, path->jpath.joinrestrictinfo, root);
 	qp_qual_cost.startup -= merge_qual_cost.startup;
 	qp_qual_cost.per_tuple -= merge_qual_cost.per_tuple;
@@ -3490,7 +3494,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * Get approx # tuples passing the mergequals.  We use approx_tuple_count
 	 * here because we need an estimate done with JOIN_INNER semantics.
 	 */
-	mergejointuples = approx_tuple_count(root, &path->jpath, mergeclauses);
+	mergejointuples = approx_tuple_count(root, &path->jpath, allclauses);
 
 	/*
 	 * When there are equal merge keys in the outer relation, the mergejoin
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 0f3ad8aa65..4777e0a850 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -16,6 +16,7 @@
 
 #include <math.h>
 
+#include "catalog/pg_operator.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
 #include "nodes/nodeFuncs.h"
@@ -24,6 +25,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
 /* Hook for plugins to get control in add_paths_to_joinrel() */
@@ -84,6 +86,9 @@ static List *select_mergejoin_clauses(PlannerInfo *root,
 									  List *restrictlist,
 									  JoinType jointype,
 									  bool *mergejoin_allowed);
+static List *select_rangejoin_clauses(RelOptInfo *outerrel,
+									  RelOptInfo *innerrel,
+									  List *restrictlist);
 static void generate_mergejoin_paths(PlannerInfo *root,
 									 RelOptInfo *joinrel,
 									 RelOptInfo *innerrel,
@@ -147,6 +152,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 
 	extra.restrictlist = restrictlist;
 	extra.mergeclause_list = NIL;
+	extra.rangeclause_list = NIL;
 	extra.sjinfo = sjinfo;
 	extra.param_source_rels = NULL;
 
@@ -207,6 +213,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 * it's a full join.
 	 */
 	if (enable_mergejoin || jointype == JOIN_FULL)
+	{
 		extra.mergeclause_list = select_mergejoin_clauses(root,
 														  joinrel,
 														  outerrel,
@@ -215,6 +222,12 @@ add_paths_to_joinrel(PlannerInfo *root,
 														  jointype,
 														  &mergejoin_allowed);
 
+		if (jointype == JOIN_INNER || jointype == JOIN_LEFT)
+			extra.rangeclause_list = select_rangejoin_clauses(outerrel,
+															  innerrel,
+															  restrictlist);
+	}
+
 	/*
 	 * If it's SEMI, ANTI, or inner_unique join, compute correction factors
 	 * for cost estimation.  These will be the same for all paths.
@@ -792,6 +805,7 @@ try_mergejoin_path(PlannerInfo *root,
 				   Path *inner_path,
 				   List *pathkeys,
 				   List *mergeclauses,
+				   List *rangeclause,
 				   List *outersortkeys,
 				   List *innersortkeys,
 				   JoinType jointype,
@@ -865,6 +879,7 @@ try_mergejoin_path(PlannerInfo *root,
 									   pathkeys,
 									   required_outer,
 									   mergeclauses,
+									   rangeclause,
 									   outersortkeys,
 									   innersortkeys));
 	}
@@ -941,6 +956,7 @@ try_partial_mergejoin_path(PlannerInfo *root,
 										   pathkeys,
 										   NULL,
 										   mergeclauses,
+										   NIL,
 										   outersortkeys,
 										   innersortkeys));
 }
@@ -1122,7 +1138,8 @@ sort_inner_and_outer(PlannerInfo *root,
 	Path	   *inner_path;
 	Path	   *cheapest_partial_outer = NULL;
 	Path	   *cheapest_safe_inner = NULL;
-	List	   *all_pathkeys;
+	List	   *merge_pathkeys;
+	List	   *range_pathkeys;
 	ListCell   *l;
 
 	/*
@@ -1222,25 +1239,80 @@ sort_inner_and_outer(PlannerInfo *root,
 	 * some heuristics behind it (see that function), so be sure to try it
 	 * exactly as-is as well as making variants.
 	 */
-	all_pathkeys = select_outer_pathkeys_for_merge(root,
-												   extra->mergeclause_list,
-												   joinrel);
 
-	foreach(l, all_pathkeys)
+	range_pathkeys = select_outer_pathkeys_for_range(root,
+													 extra->rangeclause_list);
+
+	merge_pathkeys = select_outer_pathkeys_for_merge(root,
+													 extra->mergeclause_list,
+													 joinrel);
+
+	if(merge_pathkeys == NIL && enable_mergejoin)
+	{
+		foreach(l, range_pathkeys)
+		{
+			PathKey		*range_pathkey = (PathKey *) lfirst(l);
+			List		*outerkeys = list_make1(range_pathkey);
+			List		*innerkeys = NIL;
+			List		*rangeclauses;
+			ListCell	*lc;
+
+			rangeclauses = find_rangeclauses_for_outer_pathkeys(root,
+												outerkeys,
+												NIL,
+												extra->rangeclause_list);
+
+			foreach(lc, rangeclauses)
+			{
+				List	*rangeclause = (List *) lfirst(lc);
+				PathKey *range_inner_pathkey =
+						make_inner_pathkey_for_range(root,
+													 rangeclause);
+
+				innerkeys = lappend(innerkeys, range_inner_pathkey);
+
+				merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+															 outerkeys);
+
+				/*
+				 * And now we can make the path.
+				 *
+				 * Note: it's possible that the cheapest paths will already be sorted
+				 * properly.  try_mergejoin_path will detect that case and suppress an
+				 * explicit sort step, so we needn't do so here.
+				 */
+				try_mergejoin_path(root,
+								   joinrel,
+								   outer_path,
+								   inner_path,
+								   merge_pathkeys,
+								   NIL,
+								   rangeclause,
+								   outerkeys,
+								   innerkeys,
+								   jointype,
+								   extra,
+								   false);
+			}
+		}
+	}
+
+
+	foreach(l, merge_pathkeys)
 	{
 		List	   *front_pathkey = (List *) lfirst(l);
 		List	   *cur_mergeclauses;
 		List	   *outerkeys;
 		List	   *innerkeys;
-		List	   *merge_pathkeys;
+		List	   *mergejoin_pathkeys;
 
 		/* Make a pathkey list with this guy first */
-		if (l != list_head(all_pathkeys))
+		if (l != list_head(merge_pathkeys))
 			outerkeys = lcons(front_pathkey,
-							  list_delete_nth_cell(list_copy(all_pathkeys),
-												   foreach_current_index(l)));
+							  list_delete_ptr(list_copy(merge_pathkeys),
+											  front_pathkey));
 		else
-			outerkeys = all_pathkeys;	/* no work at first one... */
+			outerkeys = merge_pathkeys;	/* no work at first one... */
 
 		/* Sort the mergeclauses into the corresponding ordering */
 		cur_mergeclauses =
@@ -1257,7 +1329,7 @@ sort_inner_and_outer(PlannerInfo *root,
 												  outerkeys);
 
 		/* Build pathkeys representing output sort order */
-		merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+		mergejoin_pathkeys = build_join_pathkeys(root, joinrel, jointype,
 											 outerkeys);
 
 		/*
@@ -1271,8 +1343,9 @@ sort_inner_and_outer(PlannerInfo *root,
 						   joinrel,
 						   outer_path,
 						   inner_path,
-						   merge_pathkeys,
+						   mergejoin_pathkeys,
 						   cur_mergeclauses,
+						   NIL,
 						   outerkeys,
 						   innerkeys,
 						   jointype,
@@ -1288,12 +1361,88 @@ sort_inner_and_outer(PlannerInfo *root,
 									   joinrel,
 									   cheapest_partial_outer,
 									   cheapest_safe_inner,
-									   merge_pathkeys,
+									   mergejoin_pathkeys,
 									   cur_mergeclauses,
 									   outerkeys,
 									   innerkeys,
 									   jointype,
 									   extra);
+
+		foreach(l, range_pathkeys)
+		{
+			PathKey		*range_outer_pathkey = (PathKey *) lfirst(l);
+			List		*range_outerkeys = list_copy(outerkeys);
+			List		*range_innerkeys;
+			List		*rangeclauses;
+			ListCell	*lc;
+
+			if(list_member_ptr(range_outerkeys, range_outer_pathkey))
+			{
+				if(!equal(llast(range_outerkeys), range_outer_pathkey))
+					continue;
+			}
+			else
+				range_outerkeys = lappend(range_outerkeys, range_outer_pathkey);
+
+			/* Sort the mergeclauses into the corresponding ordering */
+			cur_mergeclauses =
+				find_mergeclauses_for_outer_pathkeys(root,
+													 range_outerkeys,
+													 extra->mergeclause_list);
+
+			/* Should have used them all... */
+			Assert(list_length(cur_mergeclauses) == list_length(extra->mergeclause_list));
+
+			/* Build sort pathkeys for the inner side */
+			range_innerkeys = make_inner_pathkeys_for_merge(root,
+													  	  	cur_mergeclauses,
+															range_outerkeys);
+
+			rangeclauses = find_rangeclauses_for_outer_pathkeys(root,
+												range_outerkeys,
+												cur_mergeclauses,
+												extra->rangeclause_list);
+
+			foreach(lc, rangeclauses)
+			{
+				List	*cur_rangeclause = (List *) lfirst(lc);
+				PathKey *range_inner_pathkey;
+
+				range_inner_pathkey = make_inner_pathkey_for_range(root, cur_rangeclause);
+
+				if(list_member_ptr(range_innerkeys, range_inner_pathkey))
+				{
+					if(!equal(llast(range_innerkeys), range_inner_pathkey))
+						continue;
+				}
+				else
+					range_innerkeys = lappend(range_innerkeys, range_inner_pathkey);
+
+
+				mergejoin_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+															 range_outerkeys);
+
+				/*
+				 * And now we can make the path.
+				 *
+				 * Note: it's possible that the cheapest paths will already be sorted
+				 * properly.  try_mergejoin_path will detect that case and suppress an
+				 * explicit sort step, so we needn't do so here.
+				 */
+				try_mergejoin_path(root,
+								   joinrel,
+								   outer_path,
+								   inner_path,
+								   mergejoin_pathkeys,
+								   cur_mergeclauses,
+								   cur_rangeclause,
+								   range_outerkeys,
+								   range_innerkeys,
+								   jointype,
+								   extra,
+								   false);
+			}
+		}
 	}
 }
 
@@ -1379,6 +1528,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 					   merge_pathkeys,
 					   mergeclauses,
 					   NIL,
+					   NIL,
 					   innersortkeys,
 					   jointype,
 					   extra,
@@ -1477,6 +1627,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 							   newclauses,
 							   NIL,
 							   NIL,
+							   NIL,
 							   jointype,
 							   extra,
 							   is_partial);
@@ -1521,6 +1672,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 								   newclauses,
 								   NIL,
 								   NIL,
+								   NIL,
 								   jointype,
 								   extra,
 								   is_partial);
@@ -2287,3 +2439,138 @@ select_mergejoin_clauses(PlannerInfo *root,
 
 	return result_list;
 }
+
+/*
+ * range_clause_order
+ */
+static int
+range_clause_order(RestrictInfo *first,
+				   RestrictInfo *second)
+{
+	/*
+	 * Extract details from first restrictinfo
+	 */
+	Node   *first_left = get_leftop(first->clause),
+		   *first_right = get_rightop(first->clause);
+	bool	first_outer_is_left = first->outer_is_left;
+	int		first_strategy = get_op_opfamily_strategy(((OpExpr *) first->clause)->opno,
+														(linitial_oid(first->rangeleftopfamilies)));
+	bool    first_less = (first_strategy == BTLessStrategyNumber ||
+							first_strategy == BTLessEqualStrategyNumber);
+
+	/*
+	 * Extract details from second restrictinfo
+	 */
+	Node   *second_left = get_leftop(second->clause),
+		   *second_right = get_rightop(second->clause);
+	bool	second_outer_is_left = second->outer_is_left;
+	int		second_strategy = get_op_opfamily_strategy(((OpExpr *) second->clause)->opno,
+														(linitial_oid(second->rangeleftopfamilies)));
+	bool    second_less = (second_strategy == BTLessStrategyNumber ||
+							second_strategy == BTLessEqualStrategyNumber);
+
+	/*
+	 * Check for rangeclause
+	 */
+	if (first_less && second_less)
+	{
+		if (!first_outer_is_left && second_outer_is_left &&
+				equal(first_left, second_right))
+			return 2;
+		else if (first_outer_is_left && !second_outer_is_left &&
+				equal(first_right, second_left))
+			return 1;
+	}
+	else if (!first_less && !second_less)
+	{
+		if (!first_outer_is_left && second_outer_is_left &&
+				equal(first_left, second_right))
+			return 1;
+		else if (first_outer_is_left && !second_outer_is_left &&
+				equal(first_right, second_left))
+			return 2;
+	}
+	else if (first_less && !second_less)
+	{
+		if (!first_outer_is_left && !second_outer_is_left &&
+				equal(first_left, second_left))
+			return 2;
+		else if (first_outer_is_left && second_outer_is_left &&
+				equal(first_right, second_right))
+			return 1;
+	}
+	else if (!first_less && second_less)
+	{
+		if (!first_outer_is_left && !second_outer_is_left &&
+				equal(first_left, second_left))
+			return 1;
+		else if (first_outer_is_left && second_outer_is_left &&
+				equal(first_right, second_right))
+			return 2;
+	}
+
+	return 0;
+}
+
+/*
+ * select_rangejoin_clauses
+ */
+static List *
+select_rangejoin_clauses(RelOptInfo *outerrel,
+						 RelOptInfo *innerrel,
+						 List *restrictlist)
+{
+	List	   *result_list = NIL;
+	ListCell   *l;
+	List	   *range_candidates = NIL;
+
+	foreach(l, restrictlist)
+	{
+		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
+		OpExpr		 *clause = (OpExpr *) restrictinfo->clause;
+
+		/* Check that clause is a rangejoinable operator clause */
+		if (restrictinfo->rangeleftopfamilies == NIL)
+			continue;			/* not rangejoinable */
+
+		/*
+		 * Check if clause has the form "outer op inner" or "inner op outer".
+		 */
+		if (!clause_sides_match_join(restrictinfo, outerrel, innerrel))
+			continue;			/* no good for these input relations */
+
+		if (restrictinfo->rangeleftopfamilies == restrictinfo->rangerightopfamilies)
+		{
+			ListCell *lc;
+			List	 *range_clause;
+
+			foreach(lc, range_candidates)
+			{
+				RestrictInfo *candidate = (RestrictInfo *) lfirst(lc);
+
+				switch (range_clause_order(restrictinfo, candidate))
+				{
+				case 1:
+					range_clause = list_make2(restrictinfo, candidate);
+					result_list = lappend(result_list, range_clause);
+					break;
+
+				case 2:
+					range_clause = list_make2(candidate, restrictinfo);
+					result_list = lappend(result_list, range_clause);
+					break;
+				default:
+					break;
+				}
+			}
+			range_candidates = lappend(range_candidates, restrictinfo);
+		}
+		else if ((clause->opno == OID_RANGE_CONTAINS_ELEM_OP && restrictinfo->outer_is_left) ||
+				(clause->opno == OID_RANGE_ELEM_CONTAINED_OP && !restrictinfo->outer_is_left))
+		{
+			result_list = lappend(result_list, list_make1(restrictinfo));
+		}
+	}
+	list_free(range_candidates);
+	return result_list;
+}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 216dd26385..f82e760658 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -19,6 +19,7 @@
 
 #include "access/stratnum.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_operator.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/plannodes.h"
@@ -1214,6 +1215,60 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo)
 								 true);
 }
 
+/*
+ * initialize_rangeclause_eclasses
+ */
+void
+initialize_rangeclause_eclasses(PlannerInfo *root,
+								RestrictInfo *restrictinfo)
+{
+	Expr   *clause = restrictinfo->clause;
+	Oid		lefttype,
+			righttype,
+			opno;
+
+	/* Should be a rangeclause ... */
+	Assert(restrictinfo->rangeleftopfamilies != NIL &&
+			restrictinfo->rangerightopfamilies != NIL);
+	/* ... with links not yet set */
+	Assert(restrictinfo->left_ec == NULL);
+	Assert(restrictinfo->right_ec == NULL);
+
+	opno = ((OpExpr *) clause)->opno;
+
+	/* Need the declared input types of the operator */
+	op_input_types(opno, &lefttype, &righttype);
+
+	if(opno == OID_RANGE_CONTAINS_ELEM_OP)
+		righttype = exprType(get_rightop(clause));
+
+	else if(opno == OID_RANGE_ELEM_CONTAINED_OP)
+		lefttype = exprType(get_leftop(clause));
+
+
+	/* Find or create a matching EquivalenceClass for each side */
+	restrictinfo->left_ec =
+		get_eclass_for_sort_expr(root,
+				 	 	 	 	 (Expr *) get_leftop(clause),
+								 restrictinfo->nullable_relids,
+								 restrictinfo->rangeleftopfamilies,
+								 lefttype,
+								 ((OpExpr *) clause)->inputcollid,
+								 0,
+								 NULL,
+								 true);
+	restrictinfo->right_ec =
+		get_eclass_for_sort_expr(root,
+								 (Expr *) get_rightop(clause),
+								 restrictinfo->nullable_relids,
+								 restrictinfo->rangerightopfamilies,
+								 righttype,
+								 ((OpExpr *) clause)->inputcollid,
+								 0,
+								 NULL,
+								 true);
+}
+
 /*
  * update_mergeclause_eclasses
  *		Make the cached EquivalenceClass links valid in a mergeclause
@@ -1347,6 +1402,64 @@ find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 	return mergeclauses;
 }
 
+/*
+ * find_rangeclauses_for_outer_pathkeys
+ */
+List *
+find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
+									List *pathkeys,
+									List *mergeclauses,
+									List *rangeclauses)
+{
+	ListCell   *i;
+	RestrictInfo *mergeclause = NULL;
+	List *result_list = NIL;
+
+	if(mergeclauses)
+		mergeclause = llast(mergeclauses);
+
+	foreach(i, pathkeys)
+	{
+		PathKey    *pathkey = (PathKey *) lfirst(i);
+		EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
+		ListCell   *j;
+
+		if(mergeclause != NULL)
+		{
+			if(mergeclause->outer_is_left)
+			{
+				if(mergeclause->left_ec != pathkey_ec)
+					continue;
+			}
+			else if(mergeclause->right_ec != pathkey_ec)
+				continue;
+		}
+
+		foreach(j, rangeclauses)
+		{
+			List				*rangeclause = (List *) lfirst(j);
+			RestrictInfo 		*rinfo = (RestrictInfo *) linitial(rangeclause);
+			EquivalenceClass 	*clause_ec;
+
+			clause_ec = rinfo->outer_is_left ?
+				rinfo->left_ec : rinfo->right_ec;
+
+			if (clause_ec == pathkey_ec)
+				result_list = lappend(result_list, rangeclause);
+		}
+
+		/*
+		 * Was it the last possible pathkey?
+		 */
+		if(mergeclause == NULL)
+			break;
+		else
+			mergeclause = NULL;
+
+  }
+  return result_list;
+}
+
 /*
  * select_outer_pathkeys_for_merge
  *	  Builds a pathkey list representing a possible sort ordering
@@ -1522,6 +1635,37 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 	return pathkeys;
 }
 
+/*
+ * select_outer_pathkeys_for_range
+ */
+List *
+select_outer_pathkeys_for_range(PlannerInfo *root,
+								List *rangeclauses)
+{
+	EquivalenceClass	*ec;
+	PathKey 			*pathkey;
+	List 				*pathkeys = NIL;
+	ListCell			*lc;
+
+	foreach(lc, rangeclauses){
+		List *rangeclause = lfirst(lc);
+		RestrictInfo *rinfo = linitial(rangeclause);
+
+		if (rinfo->outer_is_left)
+			ec = rinfo->left_ec;
+		else
+			ec = rinfo->right_ec;
+
+		pathkey = make_canonical_pathkey(root,
+										 ec,
+										 linitial_oid(ec->ec_opfamilies),
+										 BTLessStrategyNumber,
+										 false);
+		pathkeys = lappend(pathkeys, pathkey);
+	}
+	return pathkeys;
+}
+
 /*
  * make_inner_pathkeys_for_merge
  *	  Builds a pathkey list representing the explicit sort order that
@@ -1621,6 +1765,35 @@ make_inner_pathkeys_for_merge(PlannerInfo *root,
 	return pathkeys;
 }
 
+/*
+ * make_inner_pathkey_for_range
+ */
+PathKey *
+make_inner_pathkey_for_range(PlannerInfo *root,
+							 List *rangeclause)
+{
+	EquivalenceClass *ec;
+	PathKey 	 *pathkey;
+	RestrictInfo *rinfo;
+
+	if(rangeclause == NIL)
+		return NULL;
+
+	rinfo = linitial(rangeclause);
+
+	if (rinfo->outer_is_left)
+		ec = rinfo->right_ec;
+	else
+		ec = rinfo->left_ec;
+
+	pathkey = make_canonical_pathkey(root,
+									 ec,
+									 linitial_oid(ec->ec_opfamilies),
+									 BTLessStrategyNumber,
+									 false);
+	return pathkey;
+}
+
 /*
  * trim_mergeclauses_for_inner_pathkeys
  *	  This routine trims a list of mergeclauses to include just those that
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 3dc0176a51..49cc8d16d6 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -246,6 +246,7 @@ static Hash *make_hash(Plan *lefttree,
 static MergeJoin *make_mergejoin(List *tlist,
 								 List *joinclauses, List *otherclauses,
 								 List *mergeclauses,
+								 List *rangeclause,
 								 Oid *mergefamilies,
 								 Oid *mergecollations,
 								 int *mergestrategies,
@@ -4301,6 +4302,7 @@ create_mergejoin_plan(PlannerInfo *root,
 	List	   *joinclauses;
 	List	   *otherclauses;
 	List	   *mergeclauses;
+	List	   *rangeclause;
 	List	   *outerpathkeys;
 	List	   *innerpathkeys;
 	int			nClauses;
@@ -4355,6 +4357,9 @@ create_mergejoin_plan(PlannerInfo *root,
 	mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
 	joinclauses = list_difference(joinclauses, mergeclauses);
 
+	rangeclause = get_actual_clauses(best_path->path_rangeclause);
+	joinclauses = list_difference(joinclauses, rangeclause);
+
 	/*
 	 * Replace any outer-relation variables with nestloop params.  There
 	 * should not be any in the mergeclauses.
@@ -4365,6 +4370,8 @@ create_mergejoin_plan(PlannerInfo *root,
 			replace_nestloop_params(root, (Node *) joinclauses);
 		otherclauses = (List *)
 			replace_nestloop_params(root, (Node *) otherclauses);
+		rangeclause = (List *)
+			replace_nestloop_params(root, (Node *) rangeclause);
 	}
 
 	/*
@@ -4581,6 +4588,7 @@ create_mergejoin_plan(PlannerInfo *root,
 							   joinclauses,
 							   otherclauses,
 							   mergeclauses,
+							   rangeclause,
 							   mergefamilies,
 							   mergecollations,
 							   mergestrategies,
@@ -5883,6 +5891,7 @@ make_mergejoin(List *tlist,
 			   List *joinclauses,
 			   List *otherclauses,
 			   List *mergeclauses,
+			   List *rangeclause,
 			   Oid *mergefamilies,
 			   Oid *mergecollations,
 			   int *mergestrategies,
@@ -5909,6 +5918,7 @@ make_mergejoin(List *tlist,
 	node->join.jointype = jointype;
 	node->join.inner_unique = inner_unique;
 	node->join.joinqual = joinclauses;
+	node->rangeclause = rangeclause;
 
 	return node;
 }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index f6a202d900..10990394ec 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -16,6 +16,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_operator.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -77,6 +78,7 @@ static bool check_equivalence_delay(PlannerInfo *root,
 									RestrictInfo *restrictinfo);
 static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
 static void check_mergejoinable(RestrictInfo *restrictinfo);
+static void check_rangejoinable(RestrictInfo *restrictinfo);
 static void check_hashjoinable(RestrictInfo *restrictinfo);
 static void check_memoizable(RestrictInfo *restrictinfo);
 
@@ -1877,6 +1879,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	 */
 	check_mergejoinable(restrictinfo);
 
+	check_rangejoinable(restrictinfo);
+
 	/*
 	 * If it is a true equivalence clause, send it to the EquivalenceClass
 	 * machinery.  We do *not* attach it directly to any restriction or join
@@ -1962,6 +1966,13 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 		}
 	}
 
+	if(restrictinfo->rangeleftopfamilies)
+	{
+		Assert(restrictinfo->rangerightopfamilies);
+
+		initialize_rangeclause_eclasses(root, restrictinfo);
+	}
+
 	/* No EC special case applies, so push it into the clause lists */
 	distribute_restrictinfo_to_rels(root, restrictinfo);
 }
@@ -2677,6 +2688,50 @@ check_mergejoinable(RestrictInfo *restrictinfo)
 	 */
 }
 
+/*
+ * check_rangejoinable
+ */
+static void
+check_rangejoinable(RestrictInfo *restrictinfo)
+{
+	OpExpr	   *clause = (OpExpr *) restrictinfo->clause;
+	Oid			opno = clause->opno;
+
+	if (!restrictinfo->can_join)
+		return;
+	if (contain_volatile_functions((Node *) clause))
+		return;
+
+	if (opno == OID_RANGE_CONTAINS_ELEM_OP ||
+			opno == OID_RANGE_ELEM_CONTAINED_OP)
+	{
+		Node		   *leftarg = get_leftop(clause),
+					   *rightarg = get_rightop(clause);
+
+		Oid				lefttype = exprType(leftarg),
+						righttype = exprType(rightarg);
+
+		TypeCacheEntry *left_typecache = lookup_type_cache(lefttype, TYPECACHE_CMP_PROC),
+					   *right_typecache = lookup_type_cache(righttype, TYPECACHE_CMP_PROC);
+
+		restrictinfo->rangeleftopfamilies =
+				lappend_oid(restrictinfo->rangeleftopfamilies,
+							left_typecache->btree_opf);
+
+		restrictinfo->rangerightopfamilies =
+				lappend_oid(restrictinfo->rangerightopfamilies,
+							right_typecache->btree_opf);
+	}
+	else
+	{
+		List *opfamilies = get_rangejoin_opfamilies(opno);
+
+		restrictinfo->rangeleftopfamilies = opfamilies;
+		restrictinfo->rangerightopfamilies = opfamilies;
+
+	}
+}
+
 /*
  * check_hashjoinable
  *	  If the restrictinfo's clause is hashjoinable, set the hashjoin
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 6ccec759bd..c309a3e114 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2038,6 +2038,14 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
 										 (Index) 0,
 										 rtoffset,
 										 NUM_EXEC_QUAL((Plan *) join));
+
+		mj->rangeclause = fix_join_expr(root,
+										 mj->rangeclause,
+										 outer_itlist,
+										 inner_itlist,
+										 (Index) 0,
+										 rtoffset,
+										 NUM_EXEC_QUAL((Plan *) join));
 	}
 	else if (IsA(join, HashJoin))
 	{
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index e53d381e19..f8490fa4ef 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -2502,6 +2502,7 @@ create_mergejoin_path(PlannerInfo *root,
 					  List *pathkeys,
 					  Relids required_outer,
 					  List *mergeclauses,
+					  List *rangeclause,
 					  List *outersortkeys,
 					  List *innersortkeys)
 {
@@ -2530,6 +2531,7 @@ create_mergejoin_path(PlannerInfo *root,
 	pathnode->jpath.innerjoinpath = inner_path;
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
 	pathnode->path_mergeclauses = mergeclauses;
+	pathnode->path_rangeclause = rangeclause;
 	pathnode->outersortkeys = outersortkeys;
 	pathnode->innersortkeys = innersortkeys;
 	/* pathnode->skip_mark_restore will be set by final_cost_mergejoin */
@@ -4134,6 +4136,7 @@ do { \
 				REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
 				ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
 				ADJUST_CHILD_ATTRS(mpath->path_mergeclauses);
+				ADJUST_CHILD_ATTRS(mpath->path_rangeclause);
 				new_path = (Path *) mpath;
 			}
 			break;
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index ebfcf91826..3051ad540b 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -201,6 +201,8 @@ make_restrictinfo_internal(PlannerInfo *root,
 	restrictinfo->outer_selec = -1;
 
 	restrictinfo->mergeopfamilies = NIL;
+	restrictinfo->rangeleftopfamilies = NIL;
+	restrictinfo->rangerightopfamilies = NIL;
 
 	restrictinfo->left_ec = NULL;
 	restrictinfo->right_ec = NULL;
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 815175a654..3972480519 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -2507,6 +2507,66 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
 	return true;
 }
 
+/*
+ * Test whether range r is right of a specific element value.
+ */
+bool
+elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
+{
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	int32		cmp;
+
+	range_deserialize(typcache, r, &lower, &upper, &empty);
+
+	if (empty)
+		return true;
+
+	if (!lower.infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  lower.val, val));
+		if (cmp > 0)
+			return true;
+		if (cmp == 0 && !lower.inclusive)
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Test whether range r is left of a specific element value.
+ */
+bool
+elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
+{
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	int32		cmp;
+
+	range_deserialize(typcache, r, &lower, &upper, &empty);
+
+	if (empty)
+		return true;
+
+	if (!upper.infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  upper.val, val));
+		if (cmp < 0)
+			return true;
+		if (cmp == 0 && !upper.inclusive)
+			return true;
+	}
+
+	return false;
+}
+
 
 /*
  * datum_compute_size() and datum_write() are used to insert the bound
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a2..f6cc0cdb8a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -389,6 +389,40 @@ get_mergejoin_opfamilies(Oid opno)
 	return result;
 }
 
+/*
+ * get_rangejoin_opfamilies
+ */
+List *
+get_rangejoin_opfamilies(Oid opno)
+{
+	List	   *result = NIL;
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * Search pg_amop to see if the target operator is registered as the "<",
+	 * "<=", ">" or ">=" operator of any btree opfamily.
+	 */
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == BTREE_AM_OID &&
+			(aform->amopstrategy == BTLessStrategyNumber ||
+				aform->amopstrategy == BTLessEqualStrategyNumber ||
+				aform->amopstrategy == BTGreaterStrategyNumber ||
+				aform->amopstrategy == BTGreaterEqualStrategyNumber))
+			result = lappend_oid(result, aform->amopfamily);
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_compatible_hash_operators
  *		Get the OID(s) of hash equality operator(s) compatible with the given
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 2e8cbee69f..a682694a21 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1949,6 +1949,7 @@ typedef struct NestLoopState
  */
 /* private in nodeMergejoin.c: */
 typedef struct MergeJoinClauseData *MergeJoinClause;
+typedef struct RangeJoinData *RangeData;
 
 typedef struct MergeJoinState
 {
@@ -1970,6 +1971,8 @@ typedef struct MergeJoinState
 	TupleTableSlot *mj_NullInnerTupleSlot;
 	ExprContext *mj_OuterEContext;
 	ExprContext *mj_InnerEContext;
+	RangeData	 mj_RangeData;
+	bool		 mj_RangeJoin;
 } MergeJoinState;
 
 /* ----------------
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 186e89905b..c7d4431f06 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1647,6 +1647,7 @@ typedef struct MergePath
 {
 	JoinPath	jpath;
 	List	   *path_mergeclauses;	/* join clauses to be used for merge */
+	List	   *path_rangeclause;	/* join clause to be used for range merge */
 	List	   *outersortkeys;	/* keys for explicit sort, if any */
 	List	   *innersortkeys;	/* keys for explicit sort, if any */
 	bool		skip_mark_restore;	/* can executor skip mark/restore? */
@@ -2102,6 +2103,8 @@ typedef struct RestrictInfo
 
 	/* valid if clause is mergejoinable, else NIL */
 	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List	   *rangeleftopfamilies;
+	List	   *rangerightopfamilies;
 
 	/* cache space for mergeclause processing; NULL if not yet set */
 	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
@@ -2529,6 +2532,7 @@ typedef struct JoinPathExtraData
 {
 	List	   *restrictlist;
 	List	   *mergeclause_list;
+	List	   *rangeclause_list;
 	bool		inner_unique;
 	SpecialJoinInfo *sjinfo;
 	SemiAntiJoinFactors semifactors;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 01a246d50e..5fdc45faf9 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -754,6 +754,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 */
+	List	   *rangeclause;	/* rangeclause as expression tree */
 } MergeJoin;
 
 /* ----------------
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index f704d39980..365d8b809e 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -167,6 +167,7 @@ extern MergePath *create_mergejoin_path(PlannerInfo *root,
 										List *pathkeys,
 										Relids required_outer,
 										List *mergeclauses,
+										List *rangeclause,
 										List *outersortkeys,
 										List *innersortkeys);
 
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index f1d111063c..0acfd297eb 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -231,17 +231,27 @@ extern List *make_pathkeys_for_sortclauses(PlannerInfo *root,
 										   List *tlist);
 extern void initialize_mergeclause_eclasses(PlannerInfo *root,
 											RestrictInfo *restrictinfo);
+extern void initialize_rangeclause_eclasses(PlannerInfo *root,
+											RestrictInfo *restrictinfo);
 extern void update_mergeclause_eclasses(PlannerInfo *root,
 										RestrictInfo *restrictinfo);
 extern List *find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 												  List *pathkeys,
 												  List *restrictinfos);
+extern List *find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
+												  List *pathkeys,
+												  List *mergeclauses,
+												  List *rangeclauses);
 extern List *select_outer_pathkeys_for_merge(PlannerInfo *root,
 											 List *mergeclauses,
 											 RelOptInfo *joinrel);
+extern List *select_outer_pathkeys_for_range(PlannerInfo *root,
+											 List *rangeclauses);
 extern List *make_inner_pathkeys_for_merge(PlannerInfo *root,
 										   List *mergeclauses,
 										   List *outer_pathkeys);
+extern PathKey *make_inner_pathkey_for_range(PlannerInfo *root,
+											 List *rangeclause);
 extern List *trim_mergeclauses_for_inner_pathkeys(PlannerInfo *root,
 												  List *mergeclauses,
 												  List *pathkeys);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 77871aaefc..a47beb8be5 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -79,6 +79,7 @@ extern bool get_ordering_op_properties(Oid opno,
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
 extern List *get_mergejoin_opfamilies(Oid opno);
+extern List *get_rangejoin_opfamilies(Oid opno);
 extern bool get_compatible_hash_operators(Oid opno,
 										  Oid *lhs_opno, Oid *rhs_opno);
 extern bool get_op_hash_functions(Oid opno,
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 04c302c619..51bdc5308b 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -95,6 +95,8 @@ typedef struct
  */
 
 extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
+extern bool elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r);
+extern bool elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r);
 
 /* internal versions of the above */
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
#10Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: Thomas (#9)
2 attachment(s)
Re: Patch: Range Merge Join

On 11/17/21 15:45, Thomas wrote:

Thank you for the feedback and sorry for the oversight. I fixed the bug
and attached a new version of the patch.

Kind Regards, Thomas

Am Mi., 17. Nov. 2021 um 15:03 Uhr schrieb Daniel Gustafsson
<daniel@yesql.se <mailto:daniel@yesql.se>>:

This patch fails to compile due to an incorrect function name in an
assertion:

  nodeMergejoin.c:297:9: warning: implicit declaration of function
'list_legth' is invalid in C99 [-Wimplicit-function-declaration]
  Assert(list_legth(node->rangeclause) < 3);

That still doesn't compile with asserts, because MJCreateRangeData has

Assert(list_length(node->rangeclause) < 3);

but there's no 'node' variable :-/

I took a brief look at the patch, and I think there are two main issues
preventing it from moving forward.

1) no tests

There's not a *single* regression test exercising the new code, so even
after adding Assert(false) to MJCreateRangeData() tests pass just fine.
Clearly, that needs to change.

2) lack of comments

The patch adds a bunch of functions, but it does not really explain what
the functions do (unlike the various surrounding functions). Even if I
can work out what the functions do, it's much harder to determine what
the "contract" is (i.e. what assumptions the function do and what is
guaranteed).

Similarly, the patch modifies/reworks large blocks of executor code,
without updating the comments describing what the block does.

See 0002 for various places that I think are missing comments.

Aside from that, I have a couple minor comments:

3) I'm not quite sure I like "Range Merge Join" to be honest. It's still
a "Merge Join" pretty much. What about ditching the "Range"? There'll
still be "Range Cond" key, which should be good enough I think.

4) Some minor whitespace issues (tabs vs. spaces). See 0002.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

0002-review.patchtext/x-patch; charset=UTF-8; name=0002-review.patchDownload
From 1bc7921b4ad51cf23723c2ad7c7f618cde6fcba3 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas.vondra@postgresql.org>
Date: Wed, 17 Nov 2021 22:52:31 +0100
Subject: [PATCH 2/2] review

---
 .gitignore                            |  1 +
 src/backend/commands/explain.c        |  5 +++++
 src/backend/executor/nodeMergejoin.c  | 24 +++++++++++++++++++++---
 src/backend/optimizer/path/costsize.c |  8 ++++----
 src/backend/optimizer/path/joinpath.c |  9 +++++++++
 src/backend/optimizer/path/pathkeys.c |  8 ++++++++
 src/backend/utils/adt/rangetypes.c    |  4 ++++
 src/backend/utils/cache/lsyscache.c   |  2 ++
 src/include/nodes/pathnodes.h         |  4 ++--
 9 files changed, 56 insertions(+), 9 deletions(-)

diff --git a/.gitignore b/.gitignore
index 5dca1a39ec..23a555185c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,6 +44,7 @@ lib*.pc
 /tmp_install/
 
 # Thomas
+# XXX Shouldn't be in the patch.
 /data/
 /server/
 .idea/
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 9b7503630c..3610048c90 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1206,6 +1206,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			pname = sname = "Nested Loop";
 			break;
 		case T_MergeJoin:
+			/*
+			 * XXX Not sure we want to add another top-level node (Merge vs. Range Merge).
+			 *
+			 * I'd use Merge for both and just differentiate them using Range Cond.
+			 */
 			if(((MergeJoin *) plan)->rangeclause)
 			{
 				pname = "Range Merge";	/* "Join" gets added by jointype switch */
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 09131479b1..d7f9ba809e 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -145,7 +145,9 @@ typedef struct MergeJoinClauseData
 }			MergeJoinClauseData;
 
 /*
- * Runtime data for the range clause
+ * Runtime data for the range clause.
+ *
+ * XXX This really needs some comments, explaining what the fields are for.
  */
 typedef struct RangeJoinData
 {
@@ -153,7 +155,6 @@ typedef struct RangeJoinData
 	ExprState *endClause;
 	ExprState *rangeExpr;
 	ExprState *elemExpr;
-
 }			RangeJoinData;
 
 /* Result type for MJEvalOuterValues and MJEvalInnerValues */
@@ -294,10 +295,14 @@ MJCreateRangeData(List *rangeclause,
 {
 	RangeData data;
 
-	Assert(list_length(node->rangeclause) < 3);
+	/* XXX how come this does not crash anything? */
+	Assert(false);
+	// FIXME, there's no 'node' variable
+	// Assert(list_length(node->rangeclause) < 3);
 
 	data = (RangeData) palloc0(sizeof(RangeJoinData));
 
+	/* XXX useless, thanks to the palloc0 */
 	data->startClause = NULL;
 	data->endClause = NULL;
 	data->rangeExpr = NULL;
@@ -518,6 +523,10 @@ MJCompare(MergeJoinState *mergestate)
  * Compare the rangejoinable values of the current two input tuples
  * and return 0 if they are equal (ie, the outer interval contains the inner),
  * >0 if outer > inner, <0 if outer < inner.
+ *
+ * XXX So this is essentially a simple comparator function, except that it
+ * also deals with ranges. That'd deserve explanation how clauses with ranges
+ * works, I guess.
  */
 static int
 MJCompareRange(MergeJoinState *mergestate)
@@ -1028,6 +1037,9 @@ ExecMergeJoin(PlanState *pstate)
 						compareResult = MJCompare(node);
 						MJ_DEBUG_COMPARE(compareResult);
 
+						/*
+						 * XXX Incomprehensible. Maybe add some comments?
+						 */
 						if(compareResult == 0)
 						{
 							if(isRangeJoin)
@@ -1237,6 +1249,9 @@ ExecMergeJoin(PlanState *pstate)
 						/* we need not do MJEvalInnerValues again */
 					}
 
+					/*
+					 * Comments?
+					 */
 					if(isRangeJoin)
 					{
 						compareRangeResult = MJCompareRange(node);
@@ -1348,6 +1363,9 @@ ExecMergeJoin(PlanState *pstate)
 
 				if (compareResult == 0)
 				{
+					/*
+					 * XXX Comments?
+					 */
 					if(isRangeJoin)
 					{
 						compareRangeResult = MJCompareRange(node);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 041331d6bf..835944661b 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3436,12 +3436,12 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 				rescannedtuples;
 	double		rescanratio;
 
+	/* XXX ??? */
 	allclauses = list_concat(list_copy(mergeclauses), path->path_rangeclause);
 
-
-    /* Protect some assumptions below that rowcounts aren't zero */
-    if (inner_path_rows <= 0)
-        inner_path_rows = 1;
+	/* Protect some assumptions below that rowcounts aren't zero */
+	if (inner_path_rows <= 0)
+		inner_path_rows = 1;
 
 	/* Mark the path with the correct row estimate */
 	if (path->jpath.path.param_info)
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 4777e0a850..3f93f994a2 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -222,6 +222,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 														  jointype,
 														  &mergejoin_allowed);
 
+		/* XXX Why only for inner/left joins? */
 		if (jointype == JOIN_INNER || jointype == JOIN_LEFT)
 			extra.rangeclause_list = select_rangejoin_clauses(outerrel,
 															  innerrel,
@@ -1238,6 +1239,9 @@ sort_inner_and_outer(PlannerInfo *root,
 	 * The pathkey order returned by select_outer_pathkeys_for_merge() has
 	 * some heuristics behind it (see that function), so be sure to try it
 	 * exactly as-is as well as making variants.
+	 *
+	 * XXX This comment probably needs updating? The patch significantly
+	 * reworks the following code ...
 	 */
 
 	range_pathkeys = select_outer_pathkeys_for_range(root,
@@ -1368,6 +1372,7 @@ sort_inner_and_outer(PlannerInfo *root,
 									   jointype,
 									   extra);
 
+		/* XXX comments? */
 		foreach(l, range_pathkeys)
 		{
 			PathKey		*range_outer_pathkey = (PathKey *) lfirst(l);
@@ -2442,6 +2447,8 @@ select_mergejoin_clauses(PlannerInfo *root,
 
 /*
  * range_clause_order
+ *
+ * XXX And what does this do?
  */
 static int
 range_clause_order(RestrictInfo *first,
@@ -2514,6 +2521,8 @@ range_clause_order(RestrictInfo *first,
 
 /*
  * select_rangejoin_clauses
+ *
+ * XXX And what does this do?
  */
 static List *
 select_rangejoin_clauses(RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index f82e760658..4d1c539cd1 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -1217,6 +1217,8 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo)
 
 /*
  * initialize_rangeclause_eclasses
+ *
+ * XXX And what does this do?
  */
 void
 initialize_rangeclause_eclasses(PlannerInfo *root,
@@ -1404,6 +1406,8 @@ find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 
 /*
  * find_rangeclauses_for_outer_pathkeys
+ *
+ * XXX And what does this do?
  */
 List *
 find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
@@ -1637,6 +1641,8 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 
 /*
  * select_outer_pathkeys_for_range
+ *
+ * XXX And what does this do?
  */
 List *
 select_outer_pathkeys_for_range(PlannerInfo *root,
@@ -1767,6 +1773,8 @@ make_inner_pathkeys_for_merge(PlannerInfo *root,
 
 /*
  * make_inner_pathkey_for_range
+ *
+ * XXX And what does this do?
  */
 PathKey *
 make_inner_pathkey_for_range(PlannerInfo *root,
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 3972480519..ad3e2933a7 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -2509,6 +2509,8 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
 
 /*
  * Test whether range r is right of a specific element value.
+ *
+ * XXX naming seems a bit strange, all other functions here start with range_
  */
 bool
 elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
@@ -2539,6 +2541,8 @@ elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType
 
 /*
  * Test whether range r is left of a specific element value.
+ *
+ * XXX naming seems a bit strange, all other functions here start with range_
  */
 bool
 elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index f6cc0cdb8a..136a30ed77 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -391,6 +391,8 @@ get_mergejoin_opfamilies(Oid opno)
 
 /*
  * get_rangejoin_opfamilies
+ *
+ * XXX and what does this do?
  */
 List *
 get_rangejoin_opfamilies(Oid opno)
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index c7d4431f06..66840821fb 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -2103,8 +2103,8 @@ typedef struct RestrictInfo
 
 	/* valid if clause is mergejoinable, else NIL */
 	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
-	List	   *rangeleftopfamilies;
-	List	   *rangerightopfamilies;
+	List	   *rangeleftopfamilies;	/* comment? */
+	List	   *rangerightopfamilies;	/* comment? */
 
 	/* cache space for mergeclause processing; NULL if not yet set */
 	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
-- 
2.31.1

0001-2021-11-17.patchtext/x-patch; charset=UTF-8; name=0001-2021-11-17.patchDownload
From 94f4ea1ffe1551e4d58c2b578e3ae371e28e3883 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas.vondra@postgresql.org>
Date: Wed, 17 Nov 2021 20:24:50 +0100
Subject: [PATCH 1/2] 2021/11/17

---
 .gitignore                                |  11 +
 src/backend/commands/explain.c            |  14 +-
 src/backend/executor/nodeMergejoin.c      | 209 ++++++++++++++-
 src/backend/nodes/copyfuncs.c             |   2 +
 src/backend/nodes/outfuncs.c              |   4 +
 src/backend/nodes/readfuncs.c             |   1 +
 src/backend/optimizer/path/costsize.c     |  14 +-
 src/backend/optimizer/path/joinpath.c     | 313 +++++++++++++++++++++-
 src/backend/optimizer/path/pathkeys.c     | 173 ++++++++++++
 src/backend/optimizer/plan/createplan.c   |  10 +
 src/backend/optimizer/plan/initsplan.c    |  55 ++++
 src/backend/optimizer/plan/setrefs.c      |   8 +
 src/backend/optimizer/util/pathnode.c     |   3 +
 src/backend/optimizer/util/restrictinfo.c |   2 +
 src/backend/utils/adt/rangetypes.c        |  60 +++++
 src/backend/utils/cache/lsyscache.c       |  34 +++
 src/include/nodes/execnodes.h             |   3 +
 src/include/nodes/pathnodes.h             |   4 +
 src/include/nodes/plannodes.h             |   1 +
 src/include/optimizer/pathnode.h          |   1 +
 src/include/optimizer/paths.h             |  10 +
 src/include/utils/lsyscache.h             |   1 +
 src/include/utils/rangetypes.h            |   2 +
 23 files changed, 906 insertions(+), 29 deletions(-)

diff --git a/.gitignore b/.gitignore
index 794e35b73c..5dca1a39ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,14 @@ lib*.pc
 /Debug/
 /Release/
 /tmp_install/
+
+# Thomas
+/data/
+/server/
+.idea/
+.settings/
+.cproject
+.project
+logfile
+src/backend/catalog/postgres.*
+*.patch
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 10644dfac4..9b7503630c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1206,8 +1206,16 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			pname = sname = "Nested Loop";
 			break;
 		case T_MergeJoin:
-			pname = "Merge";	/* "Join" gets added by jointype switch */
-			sname = "Merge Join";
+			if(((MergeJoin *) plan)->rangeclause)
+			{
+				pname = "Range Merge";	/* "Join" gets added by jointype switch */
+				sname = "Range Merge Join";
+			}
+			else
+			{
+				pname = "Merge";	/* "Join" gets added by jointype switch */
+				sname = "Merge Join";
+			}
 			break;
 		case T_HashJoin:
 			pname = "Hash";		/* "Join" gets added by jointype switch */
@@ -1948,6 +1956,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_MergeJoin:
 			show_upper_qual(((MergeJoin *) plan)->mergeclauses,
 							"Merge Cond", planstate, ancestors, es);
+			show_upper_qual(((MergeJoin *) plan)->rangeclause,
+							"Range Cond", planstate, ancestors, es);
 			show_upper_qual(((MergeJoin *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
 			if (((MergeJoin *) plan)->join.joinqual)
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index b41454ab6d..09131479b1 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -93,11 +93,15 @@
 #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/rangetypes.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/typcache.h"
 
 
 /*
@@ -140,6 +144,18 @@ typedef struct MergeJoinClauseData
 	SortSupportData ssup;
 }			MergeJoinClauseData;
 
+/*
+ * Runtime data for the range clause
+ */
+typedef struct RangeJoinData
+{
+	ExprState *startClause;
+	ExprState *endClause;
+	ExprState *rangeExpr;
+	ExprState *elemExpr;
+
+}			RangeJoinData;
+
 /* Result type for MJEvalOuterValues and MJEvalInnerValues */
 typedef enum
 {
@@ -269,6 +285,57 @@ MJExamineQuals(List *mergeclauses,
 	return clauses;
 }
 
+/*
+ * MJCreateRangeData
+ */
+static RangeData
+MJCreateRangeData(List *rangeclause,
+				   PlanState *parent)
+{
+	RangeData data;
+
+	Assert(list_length(node->rangeclause) < 3);
+
+	data = (RangeData) palloc0(sizeof(RangeJoinData));
+
+	data->startClause = NULL;
+	data->endClause = NULL;
+	data->rangeExpr = NULL;
+	data->elemExpr = NULL;
+
+	if(list_length(rangeclause) == 2)
+	{
+		data->startClause = ExecInitExpr(linitial(rangeclause), parent);
+		data->endClause = ExecInitExpr(lsecond(rangeclause), parent);
+	}
+	else
+	{
+		OpExpr		*qual = (OpExpr *) linitial(rangeclause);
+		ExprState	*lexpr;
+		ExprState	*rexpr;
+
+		/*
+		 * Prepare the input expressions for execution.
+		 */
+		lexpr = ExecInitExpr((Expr *) get_leftop(qual), parent);
+		rexpr = ExecInitExpr((Expr *) get_rightop(qual), parent);
+
+		if(qual->opno == OID_RANGE_CONTAINS_ELEM_OP)
+		{
+			data->rangeExpr = lexpr;
+			data->elemExpr = rexpr;
+		}
+		else
+		{
+			Assert(qual->opno == OID_RANGE_ELEM_CONTAINED_OP);
+			data->rangeExpr = rexpr;
+			data->elemExpr = lexpr;
+		}
+	}
+
+	return data;
+}
+
 /*
  * MJEvalOuterValues
  *
@@ -445,6 +512,73 @@ MJCompare(MergeJoinState *mergestate)
 }
 
 
+/*
+ * MJCompareRange
+ *
+ * Compare the rangejoinable values of the current two input tuples
+ * and return 0 if they are equal (ie, the outer interval contains the inner),
+ * >0 if outer > inner, <0 if outer < inner.
+ */
+static int
+MJCompareRange(MergeJoinState *mergestate)
+{
+	int 		  result = 0;
+	bool 		  isNull;
+	MemoryContext oldContext;
+	RangeData	  rangeData = mergestate->mj_RangeData;
+	ExprContext	 *econtext = mergestate->js.ps.ps_ExprContext;
+	ExprState	 *endClause = rangeData->endClause,
+				 *startClause = rangeData->startClause,
+				 *rangeExpr = rangeData->rangeExpr,
+				 *elemExpr = rangeData->elemExpr;
+
+	/*
+	 * Call the comparison functions in short-lived context, in case they leak
+	 * memory.
+	 */
+	ResetExprContext(econtext);
+
+	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+	econtext->ecxt_outertuple = mergestate->mj_OuterTupleSlot;
+	econtext->ecxt_innertuple = mergestate->mj_InnerTupleSlot;
+
+	if (endClause != NULL)
+	{
+		Assert(startClause != NULL);
+
+		if (!ExecEvalExprSwitchContext(endClause, econtext, &isNull))
+			result = -1;
+		else if (!ExecEvalExprSwitchContext(startClause, econtext, &isNull))
+			result = 1;
+	}
+	else
+	{
+		Datum			rangeDatum,
+    					elemDatum;
+		Oid				rangeType;
+		TypeCacheEntry *typecache;
+
+		Assert(rangeExpr != NULL && elemExpr != NULL);
+
+		rangeDatum = ExecEvalExprSwitchContext(rangeExpr, econtext, &isNull);
+		elemDatum = ExecEvalExprSwitchContext(elemExpr, econtext, &isNull);
+
+		rangeType = exprType((Node *) rangeExpr->expr);
+		typecache = lookup_type_cache(rangeType, TYPECACHE_RANGE_INFO);
+
+		if (elem_after_range_internal(typecache, elemDatum, DatumGetRangeTypeP(rangeDatum)))
+			result = -1;
+		else if (elem_before_range_internal(typecache, elemDatum, DatumGetRangeTypeP(rangeDatum)))
+			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.
@@ -604,6 +738,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprState  *otherqual;
 	bool		qualResult;
 	int			compareResult;
+	int			compareRangeResult;
 	PlanState  *innerPlan;
 	TupleTableSlot *innerTupleSlot;
 	PlanState  *outerPlan;
@@ -611,6 +746,7 @@ ExecMergeJoin(PlanState *pstate)
 	ExprContext *econtext;
 	bool		doFillOuter;
 	bool		doFillInner;
+	bool		isRangeJoin;
 
 	CHECK_FOR_INTERRUPTS();
 
@@ -624,6 +760,7 @@ ExecMergeJoin(PlanState *pstate)
 	otherqual = node->js.ps.qual;
 	doFillOuter = node->mj_FillOuter;
 	doFillInner = node->mj_FillInner;
+	isRangeJoin = node->mj_RangeJoin;
 
 	/*
 	 * Reset per-tuple memory context to free any expression evaluation
@@ -891,8 +1028,23 @@ ExecMergeJoin(PlanState *pstate)
 						compareResult = MJCompare(node);
 						MJ_DEBUG_COMPARE(compareResult);
 
-						if (compareResult == 0)
-							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						if(compareResult == 0)
+						{
+							if(isRangeJoin)
+							{
+								compareRangeResult = MJCompareRange(node);
+
+								if (compareRangeResult == 0)
+									node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+								else
+								{
+									Assert(compareRangeResult < 0);
+									node->mj_JoinState = EXEC_MJ_NEXTOUTER;
+								}
+							}
+							else
+								node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						}
 						else
 						{
 							Assert(compareResult < 0);
@@ -1085,7 +1237,19 @@ ExecMergeJoin(PlanState *pstate)
 						/* we need not do MJEvalInnerValues again */
 					}
 
-					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					if(isRangeJoin)
+					{
+						compareRangeResult = MJCompareRange(node);
+
+						if(compareRangeResult == 0)
+							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						else if(compareRangeResult < 0)
+							node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+						else /* compareRangeResult > 0 */
+							node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+					}
+					else
+						node->mj_JoinState = EXEC_MJ_JOINTUPLES;
 				}
 				else
 				{
@@ -1184,12 +1348,28 @@ ExecMergeJoin(PlanState *pstate)
 
 				if (compareResult == 0)
 				{
-					if (!node->mj_SkipMarkRestore)
-						ExecMarkPos(innerPlan);
-
-					MarkInnerTuple(node->mj_InnerTupleSlot, node);
-
-					node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					if(isRangeJoin)
+					{
+						compareRangeResult = MJCompareRange(node);
+						if(compareRangeResult == 0)
+						{
+							if (!node->mj_SkipMarkRestore)
+								ExecMarkPos(innerPlan);
+							MarkInnerTuple(node->mj_InnerTupleSlot, node);
+							node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+						}
+						else if(compareRangeResult < 0)
+							node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
+						else /* compareRangeResult > 0 */
+							node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
+					}
+					else
+					{
+						if (!node->mj_SkipMarkRestore)
+							ExecMarkPos(innerPlan);
+						MarkInnerTuple(node->mj_InnerTupleSlot, node);
+						node->mj_JoinState = EXEC_MJ_JOINTUPLES;
+					}
 				}
 				else if (compareResult < 0)
 					node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
@@ -1532,6 +1712,17 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 		ExecInitQual(node->join.joinqual, (PlanState *) mergestate);
 	/* mergeclauses are handled below */
 
+	/*
+	 * initialize range join
+	 */
+	if(node->rangeclause)
+	{
+		mergestate->mj_RangeData = MJCreateRangeData(node->rangeclause, (PlanState *) mergestate);
+		mergestate->mj_RangeJoin = true;
+	}
+	else
+		mergestate->mj_RangeJoin = false;
+
 	/*
 	 * detect whether we need only consider the first matching inner tuple
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ad1ea2ff2f..262676b1fd 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2349,6 +2349,8 @@ _copyRestrictInfo(const RestrictInfo *from)
 	COPY_SCALAR_FIELD(norm_selec);
 	COPY_SCALAR_FIELD(outer_selec);
 	COPY_NODE_FIELD(mergeopfamilies);
+	COPY_NODE_FIELD(rangeleftopfamilies);
+	COPY_NODE_FIELD(rangerightopfamilies);
 	/* EquivalenceClasses are never copied, so shallow-copy the pointers */
 	COPY_SCALAR_FIELD(left_ec);
 	COPY_SCALAR_FIELD(right_ec);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 23f23f11dc..1361c86d08 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -765,6 +765,7 @@ _outMergeJoin(StringInfo str, const MergeJoin *node)
 
 	WRITE_BOOL_FIELD(skip_mark_restore);
 	WRITE_NODE_FIELD(mergeclauses);
+    WRITE_NODE_FIELD(rangeclause);
 
 	numCols = list_length(node->mergeclauses);
 
@@ -2243,6 +2244,7 @@ _outMergePath(StringInfo str, const MergePath *node)
 	_outJoinPathInfo(str, (const JoinPath *) node);
 
 	WRITE_NODE_FIELD(path_mergeclauses);
+	WRITE_NODE_FIELD(path_rangeclause);
 	WRITE_NODE_FIELD(outersortkeys);
 	WRITE_NODE_FIELD(innersortkeys);
 	WRITE_BOOL_FIELD(skip_mark_restore);
@@ -2559,6 +2561,8 @@ _outRestrictInfo(StringInfo str, const RestrictInfo *node)
 	WRITE_FLOAT_FIELD(norm_selec, "%.4f");
 	WRITE_FLOAT_FIELD(outer_selec, "%.4f");
 	WRITE_NODE_FIELD(mergeopfamilies);
+	WRITE_NODE_FIELD(rangeleftopfamilies);
+	WRITE_NODE_FIELD(rangerightopfamilies);
 	/* don't write left_ec, leads to infinite recursion in plan tree dump */
 	/* don't write right_ec, leads to infinite recursion in plan tree dump */
 	WRITE_NODE_FIELD(left_em);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index abf08b7a2f..1e71f26eb7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2173,6 +2173,7 @@ _readMergeJoin(void)
 
 	READ_BOOL_FIELD(skip_mark_restore);
 	READ_NODE_FIELD(mergeclauses);
+    READ_NODE_FIELD(rangeclause);
 
 	numCols = list_length(local_node->mergeclauses);
 
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1e4d404f02..041331d6bf 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3419,6 +3419,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	double		inner_path_rows = inner_path->rows;
 	List	   *mergeclauses = path->path_mergeclauses;
 	List	   *innersortkeys = path->innersortkeys;
+	List	   *allclauses;
 	Cost		startup_cost = workspace->startup_cost;
 	Cost		run_cost = workspace->run_cost;
 	Cost		inner_run_cost = workspace->inner_run_cost;
@@ -3435,9 +3436,12 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 				rescannedtuples;
 	double		rescanratio;
 
-	/* Protect some assumptions below that rowcounts aren't zero */
-	if (inner_path_rows <= 0)
-		inner_path_rows = 1;
+	allclauses = list_concat(list_copy(mergeclauses), path->path_rangeclause);
+
+
+    /* Protect some assumptions below that rowcounts aren't zero */
+    if (inner_path_rows <= 0)
+        inner_path_rows = 1;
 
 	/* Mark the path with the correct row estimate */
 	if (path->jpath.path.param_info)
@@ -3466,7 +3470,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * Compute cost of the mergequals and qpquals (other restriction clauses)
 	 * separately.
 	 */
-	cost_qual_eval(&merge_qual_cost, mergeclauses, root);
+	cost_qual_eval(&merge_qual_cost, allclauses, root);
 	cost_qual_eval(&qp_qual_cost, path->jpath.joinrestrictinfo, root);
 	qp_qual_cost.startup -= merge_qual_cost.startup;
 	qp_qual_cost.per_tuple -= merge_qual_cost.per_tuple;
@@ -3490,7 +3494,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * Get approx # tuples passing the mergequals.  We use approx_tuple_count
 	 * here because we need an estimate done with JOIN_INNER semantics.
 	 */
-	mergejointuples = approx_tuple_count(root, &path->jpath, mergeclauses);
+	mergejointuples = approx_tuple_count(root, &path->jpath, allclauses);
 
 	/*
 	 * When there are equal merge keys in the outer relation, the mergejoin
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 0f3ad8aa65..4777e0a850 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -16,6 +16,7 @@
 
 #include <math.h>
 
+#include "catalog/pg_operator.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
 #include "nodes/nodeFuncs.h"
@@ -24,6 +25,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
 /* Hook for plugins to get control in add_paths_to_joinrel() */
@@ -84,6 +86,9 @@ static List *select_mergejoin_clauses(PlannerInfo *root,
 									  List *restrictlist,
 									  JoinType jointype,
 									  bool *mergejoin_allowed);
+static List *select_rangejoin_clauses(RelOptInfo *outerrel,
+									  RelOptInfo *innerrel,
+									  List *restrictlist);
 static void generate_mergejoin_paths(PlannerInfo *root,
 									 RelOptInfo *joinrel,
 									 RelOptInfo *innerrel,
@@ -147,6 +152,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 
 	extra.restrictlist = restrictlist;
 	extra.mergeclause_list = NIL;
+	extra.rangeclause_list = NIL;
 	extra.sjinfo = sjinfo;
 	extra.param_source_rels = NULL;
 
@@ -207,6 +213,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 * it's a full join.
 	 */
 	if (enable_mergejoin || jointype == JOIN_FULL)
+	{
 		extra.mergeclause_list = select_mergejoin_clauses(root,
 														  joinrel,
 														  outerrel,
@@ -215,6 +222,12 @@ add_paths_to_joinrel(PlannerInfo *root,
 														  jointype,
 														  &mergejoin_allowed);
 
+		if (jointype == JOIN_INNER || jointype == JOIN_LEFT)
+			extra.rangeclause_list = select_rangejoin_clauses(outerrel,
+															  innerrel,
+															  restrictlist);
+	}
+
 	/*
 	 * If it's SEMI, ANTI, or inner_unique join, compute correction factors
 	 * for cost estimation.  These will be the same for all paths.
@@ -792,6 +805,7 @@ try_mergejoin_path(PlannerInfo *root,
 				   Path *inner_path,
 				   List *pathkeys,
 				   List *mergeclauses,
+				   List *rangeclause,
 				   List *outersortkeys,
 				   List *innersortkeys,
 				   JoinType jointype,
@@ -865,6 +879,7 @@ try_mergejoin_path(PlannerInfo *root,
 									   pathkeys,
 									   required_outer,
 									   mergeclauses,
+									   rangeclause,
 									   outersortkeys,
 									   innersortkeys));
 	}
@@ -941,6 +956,7 @@ try_partial_mergejoin_path(PlannerInfo *root,
 										   pathkeys,
 										   NULL,
 										   mergeclauses,
+										   NIL,
 										   outersortkeys,
 										   innersortkeys));
 }
@@ -1122,7 +1138,8 @@ sort_inner_and_outer(PlannerInfo *root,
 	Path	   *inner_path;
 	Path	   *cheapest_partial_outer = NULL;
 	Path	   *cheapest_safe_inner = NULL;
-	List	   *all_pathkeys;
+	List	   *merge_pathkeys;
+	List	   *range_pathkeys;
 	ListCell   *l;
 
 	/*
@@ -1222,25 +1239,80 @@ sort_inner_and_outer(PlannerInfo *root,
 	 * some heuristics behind it (see that function), so be sure to try it
 	 * exactly as-is as well as making variants.
 	 */
-	all_pathkeys = select_outer_pathkeys_for_merge(root,
-												   extra->mergeclause_list,
-												   joinrel);
 
-	foreach(l, all_pathkeys)
+	range_pathkeys = select_outer_pathkeys_for_range(root,
+													 extra->rangeclause_list);
+
+	merge_pathkeys = select_outer_pathkeys_for_merge(root,
+													 extra->mergeclause_list,
+													 joinrel);
+
+	if(merge_pathkeys == NIL && enable_mergejoin)
+	{
+		foreach(l, range_pathkeys)
+		{
+			PathKey		*range_pathkey = (PathKey *) lfirst(l);
+			List		*outerkeys = list_make1(range_pathkey);
+			List		*innerkeys = NIL;
+			List		*rangeclauses;
+			ListCell	*lc;
+
+			rangeclauses = find_rangeclauses_for_outer_pathkeys(root,
+												outerkeys,
+												NIL,
+												extra->rangeclause_list);
+
+			foreach(lc, rangeclauses)
+			{
+				List	*rangeclause = (List *) lfirst(lc);
+				PathKey *range_inner_pathkey =
+						make_inner_pathkey_for_range(root,
+													 rangeclause);
+
+				innerkeys = lappend(innerkeys, range_inner_pathkey);
+
+				merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+															 outerkeys);
+
+				/*
+				 * And now we can make the path.
+				 *
+				 * Note: it's possible that the cheapest paths will already be sorted
+				 * properly.  try_mergejoin_path will detect that case and suppress an
+				 * explicit sort step, so we needn't do so here.
+				 */
+				try_mergejoin_path(root,
+								   joinrel,
+								   outer_path,
+								   inner_path,
+								   merge_pathkeys,
+								   NIL,
+								   rangeclause,
+								   outerkeys,
+								   innerkeys,
+								   jointype,
+								   extra,
+								   false);
+			}
+		}
+	}
+
+
+	foreach(l, merge_pathkeys)
 	{
 		List	   *front_pathkey = (List *) lfirst(l);
 		List	   *cur_mergeclauses;
 		List	   *outerkeys;
 		List	   *innerkeys;
-		List	   *merge_pathkeys;
+		List	   *mergejoin_pathkeys;
 
 		/* Make a pathkey list with this guy first */
-		if (l != list_head(all_pathkeys))
+		if (l != list_head(merge_pathkeys))
 			outerkeys = lcons(front_pathkey,
-							  list_delete_nth_cell(list_copy(all_pathkeys),
-												   foreach_current_index(l)));
+							  list_delete_ptr(list_copy(merge_pathkeys),
+											  front_pathkey));
 		else
-			outerkeys = all_pathkeys;	/* no work at first one... */
+			outerkeys = merge_pathkeys;	/* no work at first one... */
 
 		/* Sort the mergeclauses into the corresponding ordering */
 		cur_mergeclauses =
@@ -1257,7 +1329,7 @@ sort_inner_and_outer(PlannerInfo *root,
 												  outerkeys);
 
 		/* Build pathkeys representing output sort order */
-		merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+		mergejoin_pathkeys = build_join_pathkeys(root, joinrel, jointype,
 											 outerkeys);
 
 		/*
@@ -1271,8 +1343,9 @@ sort_inner_and_outer(PlannerInfo *root,
 						   joinrel,
 						   outer_path,
 						   inner_path,
-						   merge_pathkeys,
+						   mergejoin_pathkeys,
 						   cur_mergeclauses,
+						   NIL,
 						   outerkeys,
 						   innerkeys,
 						   jointype,
@@ -1288,12 +1361,88 @@ sort_inner_and_outer(PlannerInfo *root,
 									   joinrel,
 									   cheapest_partial_outer,
 									   cheapest_safe_inner,
-									   merge_pathkeys,
+									   mergejoin_pathkeys,
 									   cur_mergeclauses,
 									   outerkeys,
 									   innerkeys,
 									   jointype,
 									   extra);
+
+		foreach(l, range_pathkeys)
+		{
+			PathKey		*range_outer_pathkey = (PathKey *) lfirst(l);
+			List		*range_outerkeys = list_copy(outerkeys);
+			List		*range_innerkeys;
+			List		*rangeclauses;
+			ListCell	*lc;
+
+			if(list_member_ptr(range_outerkeys, range_outer_pathkey))
+			{
+				if(!equal(llast(range_outerkeys), range_outer_pathkey))
+					continue;
+			}
+			else
+				range_outerkeys = lappend(range_outerkeys, range_outer_pathkey);
+
+			/* Sort the mergeclauses into the corresponding ordering */
+			cur_mergeclauses =
+				find_mergeclauses_for_outer_pathkeys(root,
+													 range_outerkeys,
+													 extra->mergeclause_list);
+
+			/* Should have used them all... */
+			Assert(list_length(cur_mergeclauses) == list_length(extra->mergeclause_list));
+
+			/* Build sort pathkeys for the inner side */
+			range_innerkeys = make_inner_pathkeys_for_merge(root,
+													  	  	cur_mergeclauses,
+															range_outerkeys);
+
+			rangeclauses = find_rangeclauses_for_outer_pathkeys(root,
+												range_outerkeys,
+												cur_mergeclauses,
+												extra->rangeclause_list);
+
+			foreach(lc, rangeclauses)
+			{
+				List	*cur_rangeclause = (List *) lfirst(lc);
+				PathKey *range_inner_pathkey;
+
+				range_inner_pathkey = make_inner_pathkey_for_range(root, cur_rangeclause);
+
+				if(list_member_ptr(range_innerkeys, range_inner_pathkey))
+				{
+					if(!equal(llast(range_innerkeys), range_inner_pathkey))
+						continue;
+				}
+				else
+					range_innerkeys = lappend(range_innerkeys, range_inner_pathkey);
+
+
+				mergejoin_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+															 range_outerkeys);
+
+				/*
+				 * And now we can make the path.
+				 *
+				 * Note: it's possible that the cheapest paths will already be sorted
+				 * properly.  try_mergejoin_path will detect that case and suppress an
+				 * explicit sort step, so we needn't do so here.
+				 */
+				try_mergejoin_path(root,
+								   joinrel,
+								   outer_path,
+								   inner_path,
+								   mergejoin_pathkeys,
+								   cur_mergeclauses,
+								   cur_rangeclause,
+								   range_outerkeys,
+								   range_innerkeys,
+								   jointype,
+								   extra,
+								   false);
+			}
+		}
 	}
 }
 
@@ -1379,6 +1528,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 					   merge_pathkeys,
 					   mergeclauses,
 					   NIL,
+					   NIL,
 					   innersortkeys,
 					   jointype,
 					   extra,
@@ -1477,6 +1627,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 							   newclauses,
 							   NIL,
 							   NIL,
+							   NIL,
 							   jointype,
 							   extra,
 							   is_partial);
@@ -1521,6 +1672,7 @@ generate_mergejoin_paths(PlannerInfo *root,
 								   newclauses,
 								   NIL,
 								   NIL,
+								   NIL,
 								   jointype,
 								   extra,
 								   is_partial);
@@ -2287,3 +2439,138 @@ select_mergejoin_clauses(PlannerInfo *root,
 
 	return result_list;
 }
+
+/*
+ * range_clause_order
+ */
+static int
+range_clause_order(RestrictInfo *first,
+				   RestrictInfo *second)
+{
+	/*
+	 * Extract details from first restrictinfo
+	 */
+	Node   *first_left = get_leftop(first->clause),
+		   *first_right = get_rightop(first->clause);
+	bool	first_outer_is_left = first->outer_is_left;
+	int		first_strategy = get_op_opfamily_strategy(((OpExpr *) first->clause)->opno,
+														(linitial_oid(first->rangeleftopfamilies)));
+	bool    first_less = (first_strategy == BTLessStrategyNumber ||
+							first_strategy == BTLessEqualStrategyNumber);
+
+	/*
+	 * Extract details from second restrictinfo
+	 */
+	Node   *second_left = get_leftop(second->clause),
+		   *second_right = get_rightop(second->clause);
+	bool	second_outer_is_left = second->outer_is_left;
+	int		second_strategy = get_op_opfamily_strategy(((OpExpr *) second->clause)->opno,
+														(linitial_oid(second->rangeleftopfamilies)));
+	bool    second_less = (second_strategy == BTLessStrategyNumber ||
+							second_strategy == BTLessEqualStrategyNumber);
+
+	/*
+	 * Check for rangeclause
+	 */
+	if (first_less && second_less)
+	{
+		if (!first_outer_is_left && second_outer_is_left &&
+				equal(first_left, second_right))
+			return 2;
+		else if (first_outer_is_left && !second_outer_is_left &&
+				equal(first_right, second_left))
+			return 1;
+	}
+	else if (!first_less && !second_less)
+	{
+		if (!first_outer_is_left && second_outer_is_left &&
+				equal(first_left, second_right))
+			return 1;
+		else if (first_outer_is_left && !second_outer_is_left &&
+				equal(first_right, second_left))
+			return 2;
+	}
+	else if (first_less && !second_less)
+	{
+		if (!first_outer_is_left && !second_outer_is_left &&
+				equal(first_left, second_left))
+			return 2;
+		else if (first_outer_is_left && second_outer_is_left &&
+				equal(first_right, second_right))
+			return 1;
+	}
+	else if (!first_less && second_less)
+	{
+		if (!first_outer_is_left && !second_outer_is_left &&
+				equal(first_left, second_left))
+			return 1;
+		else if (first_outer_is_left && second_outer_is_left &&
+				equal(first_right, second_right))
+			return 2;
+	}
+
+	return 0;
+}
+
+/*
+ * select_rangejoin_clauses
+ */
+static List *
+select_rangejoin_clauses(RelOptInfo *outerrel,
+						 RelOptInfo *innerrel,
+						 List *restrictlist)
+{
+	List	   *result_list = NIL;
+	ListCell   *l;
+	List	   *range_candidates = NIL;
+
+	foreach(l, restrictlist)
+	{
+		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
+		OpExpr		 *clause = (OpExpr *) restrictinfo->clause;
+
+		/* Check that clause is a rangejoinable operator clause */
+		if (restrictinfo->rangeleftopfamilies == NIL)
+			continue;			/* not rangejoinable */
+
+		/*
+		 * Check if clause has the form "outer op inner" or "inner op outer".
+		 */
+		if (!clause_sides_match_join(restrictinfo, outerrel, innerrel))
+			continue;			/* no good for these input relations */
+
+		if (restrictinfo->rangeleftopfamilies == restrictinfo->rangerightopfamilies)
+		{
+			ListCell *lc;
+			List	 *range_clause;
+
+			foreach(lc, range_candidates)
+			{
+				RestrictInfo *candidate = (RestrictInfo *) lfirst(lc);
+
+				switch (range_clause_order(restrictinfo, candidate))
+				{
+				case 1:
+					range_clause = list_make2(restrictinfo, candidate);
+					result_list = lappend(result_list, range_clause);
+					break;
+
+				case 2:
+					range_clause = list_make2(candidate, restrictinfo);
+					result_list = lappend(result_list, range_clause);
+					break;
+				default:
+					break;
+				}
+			}
+			range_candidates = lappend(range_candidates, restrictinfo);
+		}
+		else if ((clause->opno == OID_RANGE_CONTAINS_ELEM_OP && restrictinfo->outer_is_left) ||
+				(clause->opno == OID_RANGE_ELEM_CONTAINED_OP && !restrictinfo->outer_is_left))
+		{
+			result_list = lappend(result_list, list_make1(restrictinfo));
+		}
+	}
+	list_free(range_candidates);
+	return result_list;
+}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 216dd26385..f82e760658 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -19,6 +19,7 @@
 
 #include "access/stratnum.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_operator.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/plannodes.h"
@@ -1214,6 +1215,60 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo)
 								 true);
 }
 
+/*
+ * initialize_rangeclause_eclasses
+ */
+void
+initialize_rangeclause_eclasses(PlannerInfo *root,
+								RestrictInfo *restrictinfo)
+{
+	Expr   *clause = restrictinfo->clause;
+	Oid		lefttype,
+			righttype,
+			opno;
+
+	/* Should be a rangeclause ... */
+	Assert(restrictinfo->rangeleftopfamilies != NIL &&
+			restrictinfo->rangerightopfamilies != NIL);
+	/* ... with links not yet set */
+	Assert(restrictinfo->left_ec == NULL);
+	Assert(restrictinfo->right_ec == NULL);
+
+	opno = ((OpExpr *) clause)->opno;
+
+	/* Need the declared input types of the operator */
+	op_input_types(opno, &lefttype, &righttype);
+
+	if(opno == OID_RANGE_CONTAINS_ELEM_OP)
+		righttype = exprType(get_rightop(clause));
+
+	else if(opno == OID_RANGE_ELEM_CONTAINED_OP)
+		lefttype = exprType(get_leftop(clause));
+
+
+	/* Find or create a matching EquivalenceClass for each side */
+	restrictinfo->left_ec =
+		get_eclass_for_sort_expr(root,
+				 	 	 	 	 (Expr *) get_leftop(clause),
+								 restrictinfo->nullable_relids,
+								 restrictinfo->rangeleftopfamilies,
+								 lefttype,
+								 ((OpExpr *) clause)->inputcollid,
+								 0,
+								 NULL,
+								 true);
+	restrictinfo->right_ec =
+		get_eclass_for_sort_expr(root,
+								 (Expr *) get_rightop(clause),
+								 restrictinfo->nullable_relids,
+								 restrictinfo->rangerightopfamilies,
+								 righttype,
+								 ((OpExpr *) clause)->inputcollid,
+								 0,
+								 NULL,
+								 true);
+}
+
 /*
  * update_mergeclause_eclasses
  *		Make the cached EquivalenceClass links valid in a mergeclause
@@ -1347,6 +1402,64 @@ find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 	return mergeclauses;
 }
 
+/*
+ * find_rangeclauses_for_outer_pathkeys
+ */
+List *
+find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
+									List *pathkeys,
+									List *mergeclauses,
+									List *rangeclauses)
+{
+	ListCell   *i;
+	RestrictInfo *mergeclause = NULL;
+	List *result_list = NIL;
+
+	if(mergeclauses)
+		mergeclause = llast(mergeclauses);
+
+	foreach(i, pathkeys)
+	{
+		PathKey    *pathkey = (PathKey *) lfirst(i);
+		EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
+		ListCell   *j;
+
+		if(mergeclause != NULL)
+		{
+			if(mergeclause->outer_is_left)
+			{
+				if(mergeclause->left_ec != pathkey_ec)
+					continue;
+			}
+			else if(mergeclause->right_ec != pathkey_ec)
+				continue;
+		}
+
+		foreach(j, rangeclauses)
+		{
+			List				*rangeclause = (List *) lfirst(j);
+			RestrictInfo 		*rinfo = (RestrictInfo *) linitial(rangeclause);
+			EquivalenceClass 	*clause_ec;
+
+			clause_ec = rinfo->outer_is_left ?
+				rinfo->left_ec : rinfo->right_ec;
+
+			if (clause_ec == pathkey_ec)
+				result_list = lappend(result_list, rangeclause);
+		}
+
+		/*
+		 * Was it the last possible pathkey?
+		 */
+		if(mergeclause == NULL)
+			break;
+		else
+			mergeclause = NULL;
+
+  }
+  return result_list;
+}
+
 /*
  * select_outer_pathkeys_for_merge
  *	  Builds a pathkey list representing a possible sort ordering
@@ -1522,6 +1635,37 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 	return pathkeys;
 }
 
+/*
+ * select_outer_pathkeys_for_range
+ */
+List *
+select_outer_pathkeys_for_range(PlannerInfo *root,
+								List *rangeclauses)
+{
+	EquivalenceClass	*ec;
+	PathKey 			*pathkey;
+	List 				*pathkeys = NIL;
+	ListCell			*lc;
+
+	foreach(lc, rangeclauses){
+		List *rangeclause = lfirst(lc);
+		RestrictInfo *rinfo = linitial(rangeclause);
+
+		if (rinfo->outer_is_left)
+			ec = rinfo->left_ec;
+		else
+			ec = rinfo->right_ec;
+
+		pathkey = make_canonical_pathkey(root,
+										 ec,
+										 linitial_oid(ec->ec_opfamilies),
+										 BTLessStrategyNumber,
+										 false);
+		pathkeys = lappend(pathkeys, pathkey);
+	}
+	return pathkeys;
+}
+
 /*
  * make_inner_pathkeys_for_merge
  *	  Builds a pathkey list representing the explicit sort order that
@@ -1621,6 +1765,35 @@ make_inner_pathkeys_for_merge(PlannerInfo *root,
 	return pathkeys;
 }
 
+/*
+ * make_inner_pathkey_for_range
+ */
+PathKey *
+make_inner_pathkey_for_range(PlannerInfo *root,
+							 List *rangeclause)
+{
+	EquivalenceClass *ec;
+	PathKey 	 *pathkey;
+	RestrictInfo *rinfo;
+
+	if(rangeclause == NIL)
+		return NULL;
+
+	rinfo = linitial(rangeclause);
+
+	if (rinfo->outer_is_left)
+		ec = rinfo->right_ec;
+	else
+		ec = rinfo->left_ec;
+
+	pathkey = make_canonical_pathkey(root,
+									 ec,
+									 linitial_oid(ec->ec_opfamilies),
+									 BTLessStrategyNumber,
+									 false);
+	return pathkey;
+}
+
 /*
  * trim_mergeclauses_for_inner_pathkeys
  *	  This routine trims a list of mergeclauses to include just those that
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 3dc0176a51..49cc8d16d6 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -246,6 +246,7 @@ static Hash *make_hash(Plan *lefttree,
 static MergeJoin *make_mergejoin(List *tlist,
 								 List *joinclauses, List *otherclauses,
 								 List *mergeclauses,
+								 List *rangeclause,
 								 Oid *mergefamilies,
 								 Oid *mergecollations,
 								 int *mergestrategies,
@@ -4301,6 +4302,7 @@ create_mergejoin_plan(PlannerInfo *root,
 	List	   *joinclauses;
 	List	   *otherclauses;
 	List	   *mergeclauses;
+	List	   *rangeclause;
 	List	   *outerpathkeys;
 	List	   *innerpathkeys;
 	int			nClauses;
@@ -4355,6 +4357,9 @@ create_mergejoin_plan(PlannerInfo *root,
 	mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
 	joinclauses = list_difference(joinclauses, mergeclauses);
 
+	rangeclause = get_actual_clauses(best_path->path_rangeclause);
+	joinclauses = list_difference(joinclauses, rangeclause);
+
 	/*
 	 * Replace any outer-relation variables with nestloop params.  There
 	 * should not be any in the mergeclauses.
@@ -4365,6 +4370,8 @@ create_mergejoin_plan(PlannerInfo *root,
 			replace_nestloop_params(root, (Node *) joinclauses);
 		otherclauses = (List *)
 			replace_nestloop_params(root, (Node *) otherclauses);
+		rangeclause = (List *)
+			replace_nestloop_params(root, (Node *) rangeclause);
 	}
 
 	/*
@@ -4581,6 +4588,7 @@ create_mergejoin_plan(PlannerInfo *root,
 							   joinclauses,
 							   otherclauses,
 							   mergeclauses,
+							   rangeclause,
 							   mergefamilies,
 							   mergecollations,
 							   mergestrategies,
@@ -5883,6 +5891,7 @@ make_mergejoin(List *tlist,
 			   List *joinclauses,
 			   List *otherclauses,
 			   List *mergeclauses,
+			   List *rangeclause,
 			   Oid *mergefamilies,
 			   Oid *mergecollations,
 			   int *mergestrategies,
@@ -5909,6 +5918,7 @@ make_mergejoin(List *tlist,
 	node->join.jointype = jointype;
 	node->join.inner_unique = inner_unique;
 	node->join.joinqual = joinclauses;
+	node->rangeclause = rangeclause;
 
 	return node;
 }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index f6a202d900..10990394ec 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -16,6 +16,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_operator.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -77,6 +78,7 @@ static bool check_equivalence_delay(PlannerInfo *root,
 									RestrictInfo *restrictinfo);
 static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
 static void check_mergejoinable(RestrictInfo *restrictinfo);
+static void check_rangejoinable(RestrictInfo *restrictinfo);
 static void check_hashjoinable(RestrictInfo *restrictinfo);
 static void check_memoizable(RestrictInfo *restrictinfo);
 
@@ -1877,6 +1879,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	 */
 	check_mergejoinable(restrictinfo);
 
+	check_rangejoinable(restrictinfo);
+
 	/*
 	 * If it is a true equivalence clause, send it to the EquivalenceClass
 	 * machinery.  We do *not* attach it directly to any restriction or join
@@ -1962,6 +1966,13 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 		}
 	}
 
+	if(restrictinfo->rangeleftopfamilies)
+	{
+		Assert(restrictinfo->rangerightopfamilies);
+
+		initialize_rangeclause_eclasses(root, restrictinfo);
+	}
+
 	/* No EC special case applies, so push it into the clause lists */
 	distribute_restrictinfo_to_rels(root, restrictinfo);
 }
@@ -2677,6 +2688,50 @@ check_mergejoinable(RestrictInfo *restrictinfo)
 	 */
 }
 
+/*
+ * check_rangejoinable
+ */
+static void
+check_rangejoinable(RestrictInfo *restrictinfo)
+{
+	OpExpr	   *clause = (OpExpr *) restrictinfo->clause;
+	Oid			opno = clause->opno;
+
+	if (!restrictinfo->can_join)
+		return;
+	if (contain_volatile_functions((Node *) clause))
+		return;
+
+	if (opno == OID_RANGE_CONTAINS_ELEM_OP ||
+			opno == OID_RANGE_ELEM_CONTAINED_OP)
+	{
+		Node		   *leftarg = get_leftop(clause),
+					   *rightarg = get_rightop(clause);
+
+		Oid				lefttype = exprType(leftarg),
+						righttype = exprType(rightarg);
+
+		TypeCacheEntry *left_typecache = lookup_type_cache(lefttype, TYPECACHE_CMP_PROC),
+					   *right_typecache = lookup_type_cache(righttype, TYPECACHE_CMP_PROC);
+
+		restrictinfo->rangeleftopfamilies =
+				lappend_oid(restrictinfo->rangeleftopfamilies,
+							left_typecache->btree_opf);
+
+		restrictinfo->rangerightopfamilies =
+				lappend_oid(restrictinfo->rangerightopfamilies,
+							right_typecache->btree_opf);
+	}
+	else
+	{
+		List *opfamilies = get_rangejoin_opfamilies(opno);
+
+		restrictinfo->rangeleftopfamilies = opfamilies;
+		restrictinfo->rangerightopfamilies = opfamilies;
+
+	}
+}
+
 /*
  * check_hashjoinable
  *	  If the restrictinfo's clause is hashjoinable, set the hashjoin
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 6ccec759bd..c309a3e114 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2038,6 +2038,14 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
 										 (Index) 0,
 										 rtoffset,
 										 NUM_EXEC_QUAL((Plan *) join));
+
+		mj->rangeclause = fix_join_expr(root,
+										 mj->rangeclause,
+										 outer_itlist,
+										 inner_itlist,
+										 (Index) 0,
+										 rtoffset,
+										 NUM_EXEC_QUAL((Plan *) join));
 	}
 	else if (IsA(join, HashJoin))
 	{
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index e53d381e19..f8490fa4ef 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -2502,6 +2502,7 @@ create_mergejoin_path(PlannerInfo *root,
 					  List *pathkeys,
 					  Relids required_outer,
 					  List *mergeclauses,
+					  List *rangeclause,
 					  List *outersortkeys,
 					  List *innersortkeys)
 {
@@ -2530,6 +2531,7 @@ create_mergejoin_path(PlannerInfo *root,
 	pathnode->jpath.innerjoinpath = inner_path;
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
 	pathnode->path_mergeclauses = mergeclauses;
+	pathnode->path_rangeclause = rangeclause;
 	pathnode->outersortkeys = outersortkeys;
 	pathnode->innersortkeys = innersortkeys;
 	/* pathnode->skip_mark_restore will be set by final_cost_mergejoin */
@@ -4134,6 +4136,7 @@ do { \
 				REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath);
 				ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo);
 				ADJUST_CHILD_ATTRS(mpath->path_mergeclauses);
+				ADJUST_CHILD_ATTRS(mpath->path_rangeclause);
 				new_path = (Path *) mpath;
 			}
 			break;
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index ebfcf91826..3051ad540b 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -201,6 +201,8 @@ make_restrictinfo_internal(PlannerInfo *root,
 	restrictinfo->outer_selec = -1;
 
 	restrictinfo->mergeopfamilies = NIL;
+	restrictinfo->rangeleftopfamilies = NIL;
+	restrictinfo->rangerightopfamilies = NIL;
 
 	restrictinfo->left_ec = NULL;
 	restrictinfo->right_ec = NULL;
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 815175a654..3972480519 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -2507,6 +2507,66 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
 	return true;
 }
 
+/*
+ * Test whether range r is right of a specific element value.
+ */
+bool
+elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
+{
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	int32		cmp;
+
+	range_deserialize(typcache, r, &lower, &upper, &empty);
+
+	if (empty)
+		return true;
+
+	if (!lower.infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  lower.val, val));
+		if (cmp > 0)
+			return true;
+		if (cmp == 0 && !lower.inclusive)
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Test whether range r is left of a specific element value.
+ */
+bool
+elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r)
+{
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	int32		cmp;
+
+	range_deserialize(typcache, r, &lower, &upper, &empty);
+
+	if (empty)
+		return true;
+
+	if (!upper.infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  upper.val, val));
+		if (cmp < 0)
+			return true;
+		if (cmp == 0 && !upper.inclusive)
+			return true;
+	}
+
+	return false;
+}
+
 
 /*
  * datum_compute_size() and datum_write() are used to insert the bound
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a2..f6cc0cdb8a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -389,6 +389,40 @@ get_mergejoin_opfamilies(Oid opno)
 	return result;
 }
 
+/*
+ * get_rangejoin_opfamilies
+ */
+List *
+get_rangejoin_opfamilies(Oid opno)
+{
+	List	   *result = NIL;
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * Search pg_amop to see if the target operator is registered as the "<",
+	 * "<=", ">" or ">=" operator of any btree opfamily.
+	 */
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == BTREE_AM_OID &&
+			(aform->amopstrategy == BTLessStrategyNumber ||
+				aform->amopstrategy == BTLessEqualStrategyNumber ||
+				aform->amopstrategy == BTGreaterStrategyNumber ||
+				aform->amopstrategy == BTGreaterEqualStrategyNumber))
+			result = lappend_oid(result, aform->amopfamily);
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_compatible_hash_operators
  *		Get the OID(s) of hash equality operator(s) compatible with the given
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 2e8cbee69f..a682694a21 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1949,6 +1949,7 @@ typedef struct NestLoopState
  */
 /* private in nodeMergejoin.c: */
 typedef struct MergeJoinClauseData *MergeJoinClause;
+typedef struct RangeJoinData *RangeData;
 
 typedef struct MergeJoinState
 {
@@ -1970,6 +1971,8 @@ typedef struct MergeJoinState
 	TupleTableSlot *mj_NullInnerTupleSlot;
 	ExprContext *mj_OuterEContext;
 	ExprContext *mj_InnerEContext;
+	RangeData	 mj_RangeData;
+	bool		 mj_RangeJoin;
 } MergeJoinState;
 
 /* ----------------
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 186e89905b..c7d4431f06 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1647,6 +1647,7 @@ typedef struct MergePath
 {
 	JoinPath	jpath;
 	List	   *path_mergeclauses;	/* join clauses to be used for merge */
+	List	   *path_rangeclause;	/* join clause to be used for range merge */
 	List	   *outersortkeys;	/* keys for explicit sort, if any */
 	List	   *innersortkeys;	/* keys for explicit sort, if any */
 	bool		skip_mark_restore;	/* can executor skip mark/restore? */
@@ -2102,6 +2103,8 @@ typedef struct RestrictInfo
 
 	/* valid if clause is mergejoinable, else NIL */
 	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List	   *rangeleftopfamilies;
+	List	   *rangerightopfamilies;
 
 	/* cache space for mergeclause processing; NULL if not yet set */
 	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
@@ -2529,6 +2532,7 @@ typedef struct JoinPathExtraData
 {
 	List	   *restrictlist;
 	List	   *mergeclause_list;
+	List	   *rangeclause_list;
 	bool		inner_unique;
 	SpecialJoinInfo *sjinfo;
 	SemiAntiJoinFactors semifactors;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 01a246d50e..5fdc45faf9 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -754,6 +754,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 */
+	List	   *rangeclause;	/* rangeclause as expression tree */
 } MergeJoin;
 
 /* ----------------
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index f704d39980..365d8b809e 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -167,6 +167,7 @@ extern MergePath *create_mergejoin_path(PlannerInfo *root,
 										List *pathkeys,
 										Relids required_outer,
 										List *mergeclauses,
+										List *rangeclause,
 										List *outersortkeys,
 										List *innersortkeys);
 
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index f1d111063c..0acfd297eb 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -231,17 +231,27 @@ extern List *make_pathkeys_for_sortclauses(PlannerInfo *root,
 										   List *tlist);
 extern void initialize_mergeclause_eclasses(PlannerInfo *root,
 											RestrictInfo *restrictinfo);
+extern void initialize_rangeclause_eclasses(PlannerInfo *root,
+											RestrictInfo *restrictinfo);
 extern void update_mergeclause_eclasses(PlannerInfo *root,
 										RestrictInfo *restrictinfo);
 extern List *find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
 												  List *pathkeys,
 												  List *restrictinfos);
+extern List *find_rangeclauses_for_outer_pathkeys(PlannerInfo *root,
+												  List *pathkeys,
+												  List *mergeclauses,
+												  List *rangeclauses);
 extern List *select_outer_pathkeys_for_merge(PlannerInfo *root,
 											 List *mergeclauses,
 											 RelOptInfo *joinrel);
+extern List *select_outer_pathkeys_for_range(PlannerInfo *root,
+											 List *rangeclauses);
 extern List *make_inner_pathkeys_for_merge(PlannerInfo *root,
 										   List *mergeclauses,
 										   List *outer_pathkeys);
+extern PathKey *make_inner_pathkey_for_range(PlannerInfo *root,
+											 List *rangeclause);
 extern List *trim_mergeclauses_for_inner_pathkeys(PlannerInfo *root,
 												  List *mergeclauses,
 												  List *pathkeys);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 77871aaefc..a47beb8be5 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -79,6 +79,7 @@ extern bool get_ordering_op_properties(Oid opno,
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
 extern List *get_mergejoin_opfamilies(Oid opno);
+extern List *get_rangejoin_opfamilies(Oid opno);
 extern bool get_compatible_hash_operators(Oid opno,
 										  Oid *lhs_opno, Oid *rhs_opno);
 extern bool get_op_hash_functions(Oid opno,
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 04c302c619..51bdc5308b 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -95,6 +95,8 @@ typedef struct
  */
 
 extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
+extern bool elem_before_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r);
+extern bool elem_after_range_internal(TypeCacheEntry *typcache, Datum val, const RangeType *r);
 
 /* internal versions of the above */
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
-- 
2.31.1

#11Julien Rouhaud
rjuju123@gmail.com
In reply to: Tomas Vondra (#10)
Re: Patch: Range Merge Join

Hi,

On Wed, Nov 17, 2021 at 11:28:43PM +0100, Tomas Vondra wrote:

On 11/17/21 15:45, Thomas wrote:

Thank you for the feedback and sorry for the oversight. I fixed the bug
and attached a new version of the patch.

Kind Regards, Thomas

Am Mi., 17. Nov. 2021 um 15:03�Uhr schrieb Daniel Gustafsson
<daniel@yesql.se <mailto:daniel@yesql.se>>:

This patch fails to compile due to an incorrect function name in an
assertion:

� nodeMergejoin.c:297:9: warning: implicit declaration of function
'list_legth' is invalid in C99 [-Wimplicit-function-declaration]
� Assert(list_legth(node->rangeclause) < 3);

That still doesn't compile with asserts, because MJCreateRangeData has

Assert(list_length(node->rangeclause) < 3);

but there's no 'node' variable :-/

I took a brief look at the patch, and I think there are two main issues
preventing it from moving forward.

1) no tests

2) lack of comments

3) I'm not quite sure I like "Range Merge Join" to be honest. It's still a
"Merge Join" pretty much. What about ditching the "Range"? There'll still be
"Range Cond" key, which should be good enough I think.

4) Some minor whitespace issues (tabs vs. spaces). See 0002.

It's been 2 months since Tomas posted that review.

Thomas, do you plan to work on that patch during this commitfest?