pgsql-v9.4-custom-scan-apis.v1.patch

application/octet-stream

Filename: pgsql-v9.4-custom-scan-apis.v1.patch
Type: application/octet-stream
Part: 1
Message: Custom Scan APIs (Re: Custom Plan node)

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v9
File+
contrib/ctidscan/ctidscan.c 761 0
contrib/ctidscan/Makefile 14 0
contrib/Makefile 1 0
doc/src/sgml/ctidscan.sgml 107 0
src/backend/commands/explain.c 78 0
src/backend/executor/execAmi.c 32 2
src/backend/executor/execProcnode.c 14 0
src/backend/executor/execQual.c 5 5
src/backend/executor/execUtils.c 2 2
src/backend/executor/Makefile 1 1
src/backend/executor/nodeCustom.c 252 0
src/backend/nodes/bitmapset.c 62 0
src/backend/nodes/copyfuncs.c 30 0
src/backend/nodes/outfuncs.c 19 0
src/backend/nodes/print.c 4 0
src/backend/optimizer/path/allpaths.c 23 0
src/backend/optimizer/path/costsize.c 2 5
src/backend/optimizer/path/joinpath.c 18 0
src/backend/optimizer/plan/createplan.c 103 0
src/backend/optimizer/plan/setrefs.c 26 1
src/backend/optimizer/plan/subselect.c 10 0
src/backend/optimizer/util/pathnode.c 40 0
src/backend/utils/adt/ruleutils.c 41 3
src/include/catalog/pg_operator.h 4 0
src/include/executor/executor.h 2 1
src/include/executor/nodeCustom.h 94 0
src/include/nodes/bitmapset.h 4 0
src/include/nodes/execnodes.h 17 0
src/include/nodes/nodes.h 3 0
src/include/nodes/plannodes.h 16 0
src/include/nodes/primnodes.h 1 0
src/include/nodes/relation.h 16 0
src/include/optimizer/cost.h 3 0
src/include/optimizer/pathnode.h 10 0
src/include/optimizer/paths.h 25 0
src/include/optimizer/planmain.h 1 0
src/test/regress/GNUmakefile 12 3
src/test/regress/input/custom_scan.source 49 0
src/test/regress/output/custom_scan.source 290 0
src/test/regress/parallel_schedule 1 1
src/test/regress/serial_schedule 1 0
 contrib/Makefile                           |   1 +
 contrib/ctidscan/Makefile                  |  14 +
 contrib/ctidscan/ctidscan.c                | 761 +++++++++++++++++++++++++++++
 doc/src/sgml/ctidscan.sgml                 | 107 ++++
 src/backend/commands/explain.c             |  78 +++
 src/backend/executor/Makefile              |   2 +-
 src/backend/executor/execAmi.c             |  34 +-
 src/backend/executor/execProcnode.c        |  14 +
 src/backend/executor/execQual.c            |  10 +-
 src/backend/executor/execUtils.c           |   4 +-
 src/backend/executor/nodeCustom.c          | 252 ++++++++++
 src/backend/nodes/bitmapset.c              |  62 +++
 src/backend/nodes/copyfuncs.c              |  30 ++
 src/backend/nodes/outfuncs.c               |  19 +
 src/backend/nodes/print.c                  |   4 +
 src/backend/optimizer/path/allpaths.c      |  23 +
 src/backend/optimizer/path/costsize.c      |   7 +-
 src/backend/optimizer/path/joinpath.c      |  18 +
 src/backend/optimizer/plan/createplan.c    | 103 ++++
 src/backend/optimizer/plan/setrefs.c       |  27 +-
 src/backend/optimizer/plan/subselect.c     |  10 +
 src/backend/optimizer/util/pathnode.c      |  40 ++
 src/backend/utils/adt/ruleutils.c          |  44 +-
 src/include/catalog/pg_operator.h          |   4 +
 src/include/executor/executor.h            |   3 +-
 src/include/executor/nodeCustom.h          |  94 ++++
 src/include/nodes/bitmapset.h              |   4 +
 src/include/nodes/execnodes.h              |  17 +
 src/include/nodes/nodes.h                  |   3 +
 src/include/nodes/plannodes.h              |  16 +
 src/include/nodes/primnodes.h              |   1 +
 src/include/nodes/relation.h               |  16 +
 src/include/optimizer/cost.h               |   3 +
 src/include/optimizer/pathnode.h           |  10 +
 src/include/optimizer/paths.h              |  25 +
 src/include/optimizer/planmain.h           |   1 +
 src/test/regress/GNUmakefile               |  15 +-
 src/test/regress/input/custom_scan.source  |  49 ++
 src/test/regress/output/custom_scan.source | 290 +++++++++++
 src/test/regress/parallel_schedule         |   2 +-
 src/test/regress/serial_schedule           |   1 +
 41 files changed, 2194 insertions(+), 24 deletions(-)

diff --git a/contrib/Makefile b/contrib/Makefile
index 8a2a937..703e5a5 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -12,6 +12,7 @@ SUBDIRS = \
 		btree_gist	\
 		chkpass		\
 		citext		\
+		ctidscan	\
 		cube		\
 		dblink		\
 		dict_int	\
diff --git a/contrib/ctidscan/Makefile b/contrib/ctidscan/Makefile
new file mode 100644
index 0000000..708c5b7
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,14 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/ctidscan
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
new file mode 100644
index 0000000..c392051
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,761 @@
+/*
+ * ctidscan.c
+ *
+ * Definition of Custom TidScan implementation.
+ *
+ * It is designed to demonstrate Custom Scan APIs; that allows to override
+ * a part of executor node. This extension focus on a workload that tries
+ * to fetch records with tid larger or less than a particular value.
+ * In case when inequality operators were given, this module construct
+ * a custom scan path that enables to skip records not to be read. Then,
+ * if it was the chepest one, it shall be used to run the query.
+ * Custom Scan APIs callbacks this extension when executor tries to fetch
+ * underlying records, then it utilizes existing heap_getnext() but seek
+ * the records to be read prior to fetching the first record.
+ *
+ * Portions Copyright (c) 2013, PostgreSQL Global Development Group
+ */
+#include "postgres.h"
+#include "access/relscan.h"
+#include "access/sysattr.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_type.h"
+#include "executor/nodeCustom.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
+#include "optimizer/paths.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/planmain.h"
+#include "optimizer/restrictinfo.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/spccache.h"
+
+extern void		_PG_init(void);
+
+PG_MODULE_MAGIC;
+
+static add_scan_path_hook_type	add_scan_path_next;
+
+#define IsCTIDVar(node,rtindex)											\
+    ((node) != NULL &&													\
+	 IsA((node), Var) &&												\
+	 ((Var *) (node))->varno == (rtindex) &&							\
+	 ((Var *) (node))->varattno == SelfItemPointerAttributeNumber &&	\
+	 ((Var *) (node))->varlevelsup == 0)
+
+/*
+ * CTidQualFromExpr
+ *
+ * It checks whether the given restriction clauses enables to determine
+ * the zone to be scanned, or not. If one or more restriction clauses are
+ * available, it returns a list of them, or NIL elsewhere.
+ * The caller can consider all the conditions are chainned with AND-
+ * boolean operator, so all the operator works for narrowing down the
+ * scope of custom tid scan.
+ */
+static List *
+CTidQualFromExpr(Node *expr, int varno)
+{
+	if (is_opclause(expr))
+	{
+		OpExpr *op = (OpExpr *) expr;
+		Node   *arg1;
+		Node   *arg2;
+		Node   *other = NULL;
+
+		/* only inequality operators are candidate */
+		if (op->opno != TIDLessOperator &&
+			op->opno != TIDLessEqualOperator &&
+			op->opno != TIDGreaterOperator &&
+			op->opno != TIDGreaterEqualOperator)
+			return NULL;
+
+		if (list_length(op->args) != 2)
+			return false;
+
+		arg1 = linitial(op->args);
+		arg2 = lsecond(op->args);
+
+		if (IsCTIDVar(arg1, varno))
+			other = arg2;
+		else if (IsCTIDVar(arg2, varno))
+			other = arg1;
+		else
+			return NULL;
+		if (exprType(other) != TIDOID)
+			return NULL;	/* probably can't happen */
+		/* The other argument must be a pseudoconstant */
+		if (!is_pseudo_constant_clause(other))
+			return NULL;
+
+		return list_make1(copyObject(op));
+	}
+	else if (and_clause(expr))
+	{
+		List	   *rlst = NIL;
+		ListCell   *lc;
+
+		foreach(lc, ((BoolExpr *) expr)->args)
+		{
+			List   *temp = CTidQualFromExpr((Node *) lfirst(lc), varno);
+
+			rlst = list_concat(rlst, temp);
+		}
+		return rlst;
+	}
+	return NIL;
+}
+
+/*
+ * CTidEstimateCosts
+ *
+ * It estimates cost to scan the target relation according to the given
+ * restriction clauses. Its logic to scan relations are almost same as
+ * SeqScan doing, because it uses regular heap_getnext(), except for
+ * the number of tuples to be scanned if restriction clauses work well.
+*/
+static void
+CTidEstimateCosts(PlannerInfo *root,
+				  RelOptInfo *baserel,
+				  CustomPath *cpath)
+{
+	List	   *ctidquals = cpath->custom_private;
+	ListCell   *lc;
+	double		ntuples;
+	ItemPointerData ip_min;
+	ItemPointerData ip_max;
+	bool		has_min_val = false;
+	bool		has_max_val = false;
+	BlockNumber	num_pages;
+	Cost		startup_cost = 0;
+	Cost		run_cost = 0;
+	Cost		cpu_per_tuple;
+	QualCost	qpqual_cost;
+	QualCost	ctid_qual_cost;
+	double		spc_random_page_cost;
+
+	/* Should only be applied to base relations */
+	Assert(baserel->relid > 0);
+	Assert(baserel->rtekind == RTE_RELATION);
+
+	/* Mark the path with the correct row estimate */
+	if (cpath->path.param_info)
+		cpath->path.rows = cpath->path.param_info->ppi_rows;
+	else
+		cpath->path.rows = baserel->rows;
+
+	/* Estimate how many tuples we may retrieve */
+	ItemPointerSet(&ip_min, 0, 0);
+	ItemPointerSet(&ip_max, MaxBlockNumber, MaxOffsetNumber);
+	foreach (lc, ctidquals)
+	{
+		OpExpr	   *op = lfirst(lc);
+		Oid			opno;
+		Node	   *other;
+
+		Assert(is_opclause(op));
+		if (IsCTIDVar(linitial(op->args), baserel->relid))
+		{
+			opno = op->opno;
+			other = lsecond(op->args);
+		}
+		else if (IsCTIDVar(lsecond(op->args), baserel->relid))
+		{
+			/* To simplifies, we assume as if Var node is 1st argument */
+			opno = get_commutator(op->opno);
+			other = linitial(op->args);
+		}
+		else
+			elog(ERROR, "could not identify CTID variable");
+
+		if (IsA(other, Const))
+		{
+			ItemPointer	ip = (ItemPointer)(((Const *) other)->constvalue);
+
+			/*
+			 * Just an rough estimation, we don't distinct inequality and
+			 * inequality-or-equal operator.
+			 */
+			switch (opno)
+			{
+				case TIDLessOperator:
+				case TIDLessEqualOperator:
+					if (ItemPointerCompare(ip, &ip_max) < 0)
+						ItemPointerCopy(ip, &ip_max);
+					has_max_val = true;
+					break;
+				case TIDGreaterOperator:
+				case TIDGreaterEqualOperator:
+					if (ItemPointerCompare(ip, &ip_min) > 0)
+						ItemPointerCopy(ip, &ip_min);
+					has_min_val = true;
+					break;
+				default:
+					elog(ERROR, "unexpected operator code: %u", op->opno);
+					break;
+			}
+		}
+	}
+
+	/* estimated number of tuples in this relation */
+	ntuples = baserel->pages * baserel->tuples;
+
+	if (has_min_val && has_max_val)
+	{
+		/* case of both side being bounded */
+		BlockNumber	bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid);
+		BlockNumber	bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid);
+
+		bnum_max = Min(bnum_max, baserel->pages);
+		bnum_min = Max(bnum_min, 0);
+		num_pages = Min(bnum_max - bnum_min + 1, 1);
+	}
+	else if (has_min_val)
+	{
+		/* case of only lower side being bounded */
+		BlockNumber	bnum_max = baserel->pages;
+		BlockNumber	bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid);
+
+		bnum_min = Max(bnum_min, 0);
+		num_pages = Min(bnum_max - bnum_min + 1, 1);
+	}
+	else if (has_max_val)
+	{
+		/* case of only upper side being bounded */
+		BlockNumber	bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid);
+		BlockNumber	bnum_min = 0;
+
+		bnum_max = Min(bnum_max, baserel->pages);
+		num_pages = Min(bnum_max - bnum_min + 1, 1);
+	}
+	else
+	{
+		/*
+		 * Just a rough estimation. We assume half of records shall be
+		 * read using this restriction clause, but undeterministic untill
+		 * executor run it actually.
+		 */
+		num_pages = Max((baserel->pages + 1) / 2, 1);
+	}
+	ntuples *= ((double) num_pages) / ((double) baserel->pages);
+
+	/*
+	 * The TID qual expressions will be computed once, any other baserestrict
+	 * quals once per retrived tuple.
+	 */
+    cost_qual_eval(&ctid_qual_cost, ctidquals, root);
+
+	/* fetch estimated page cost for tablespace containing table */
+	get_tablespace_page_costs(baserel->reltablespace,
+							  &spc_random_page_cost,
+							  NULL);
+
+	/* disk costs --- assume each tuple on a different page */
+	run_cost += spc_random_page_cost * ntuples;
+
+	/* Add scanning CPU costs */
+	get_restriction_qual_cost(root, baserel,
+							  cpath->path.param_info,
+							  &qpqual_cost);
+
+	/*
+	 * We don't decrease cost for the inequality operators, because 
+	 * it is subset of qpquals and still in.
+	 */
+	startup_cost += qpqual_cost.startup + ctid_qual_cost.per_tuple;
+	cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple -
+		ctid_qual_cost.per_tuple;
+	run_cost = cpu_per_tuple * ntuples;
+
+	cpath->path.startup_cost = startup_cost;
+	cpath->path.total_cost = startup_cost + run_cost;
+}
+
+/*
+ * CTidAddScanPath
+ *
+ * It adds a custom scan path if inequality operators are given on the
+ * relation to be scanned and makes sense to reduce number of tuples.
+ */
+static void
+CTidAddScanPath(PlannerInfo *root,
+				RelOptInfo *baserel,
+				RangeTblEntry *rte)
+{
+	char		relkind;
+	List	   *rlst = NIL;
+	ListCell   *lc;
+
+	/* Gives another extensions chance to add a path */
+	if (add_scan_path_next)
+		(*add_scan_path_next)(root, baserel, rte);
+
+	/* All we support is regular relations */
+	if (rte->rtekind != RTE_RELATION)
+		return;
+	relkind = get_rel_relkind(rte->relid);
+	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_MATVIEW &&
+		relkind != RELKIND_TOASTVALUE)
+		return;
+
+	/* walk on the restrict info */
+	foreach (lc, baserel->baserestrictinfo)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+		List		 *temp;
+
+		if (!IsA(rinfo, RestrictInfo))
+			continue;		/* probably should never happen */
+		temp = CTidQualFromExpr((Node *) rinfo->clause, baserel->relid);
+		rlst = list_concat(rlst, temp);
+	}
+
+	/*
+	 * OK, it is case when a part of restriction clause makes sense to
+	 * reduce number of tuples, so we will add a custom scan path being
+	 * provided by this module.
+	 */
+	if (rlst != NIL)
+	{
+		CustomPath *cpath = makeNode(CustomPath);
+		Relids		required_outer;
+
+		/*
+		 * We don't support pushing join clauses into the quals of a ctidscan,
+		 * but it could still have required parameterization due to LATERAL
+		 * refs in its tlist.
+		 */
+		required_outer = baserel->lateral_relids;
+
+		cpath->path.pathtype = T_CustomScan;
+		cpath->path.parent = baserel;
+		cpath->path.param_info = get_baserel_parampathinfo(root, baserel,
+														   required_outer);
+		cpath->custom_name = pstrdup("ctidscan");
+		cpath->custom_flags = CUSTOM__SUPPORT_BACKWARD_SCAN;
+		cpath->custom_private = rlst;
+
+		CTidEstimateCosts(root, baserel, cpath);
+
+		add_path(baserel, &cpath->path);
+	}
+}
+
+/*
+ * CTidInitCustomScanPlan
+ *
+ * It initializes the given CustomScan plan object according to the CustomPath
+ * being choosen by the optimizer.
+ */
+static void
+CTidInitCustomScanPlan(PlannerInfo *root,
+					   CustomScan *cscan_plan,
+					   CustomPath *cscan_path,
+					   List *tlist,
+					   List *scan_clauses)
+{
+	Index		scan_relid = cscan_path->path.parent->relid;
+	List	   *ctidquals = cscan_path->custom_private;
+
+	/* should be a base relation */
+	Assert(scan_relid > 0);
+	Assert(cscan_path->path.parent->rtekind == RTE_RELATION);
+
+	/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+	scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+	/*
+	 * Most of initialization stuff was done at nodeCustomScan.c. So, all
+	 * we need to do is to put clauses that were little bit adjusted and
+	 * private stuff; list of restriction clauses in this case.
+	 */
+	cscan_plan->scan.plan.targetlist = tlist;
+	cscan_plan->scan.plan.qual = scan_clauses;
+	cscan_plan->custom_private = ctidquals;
+}
+
+/*
+ * CTidScanState
+ *
+ * State of custom-tid scan during its execution.
+ */
+typedef struct {
+	Index			scanrelid;		/* range table index of the relation */
+	ItemPointerData	ip_min;			/* minimum ItemPointer */
+	ItemPointerData	ip_max;			/* maximum ItemPointer */
+	int32			ip_min_comp;	/* comparison policy to ip_min */
+	int32			ip_max_comp;	/* comparison policy to ip_max */
+	bool			ip_needs_eval;	/* true, if needs to seek again */
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CTidScanState;
+
+static bool
+CTidEvalScanZone(CustomScanState *node)
+{
+	CTidScanState  *ctss = node->custom_state;
+	ExprContext	   *econtext = node->ss.ps.ps_ExprContext;
+	ListCell	   *lc;
+
+	/*
+	 * See ItemPointerCompare(), ip_max_comp shall be usually either 1 or
+	 * 0 if tid of fetched records are larger than or equal with ip_min.
+	 * To detect end of scan, we shall check whether the result of
+	 * ItemPointerCompare() is less than ip_max_comp, so it never touch
+	 * the point if ip_max_comp is -1, because all the result is either
+	 * 1, 0 or -1. So, it is same as "open ended" as if no termination
+	 * condition was set.
+	 */
+	ctss->ip_min_comp = -1;
+	ctss->ip_max_comp = 1;
+
+	/* Walks on the inequality operators */
+	foreach (lc, ctss->ctid_quals)
+	{
+		FuncExprState  *fexstate = (FuncExprState *) lfirst(lc);
+		OpExpr		   *op = (OpExpr *)fexstate->xprstate.expr;
+		Node		   *arg1 = linitial(op->args);
+		Node		   *arg2 = lsecond(op->args);
+		Oid				opno;
+		ExprState	   *exstate;
+		ItemPointer		itemptr;
+		bool			isnull;
+
+		if (IsCTIDVar(arg1, ctss->scanrelid))
+		{
+			exstate = (ExprState *) lsecond(fexstate->args);
+			opno = op->opno;
+		}
+		else if (IsCTIDVar(arg2, ctss->scanrelid))
+		{
+			exstate = (ExprState *) linitial(fexstate->args);
+			opno = get_commutator(op->opno);
+		}
+		else
+			elog(ERROR, "could not identify CTID variable");
+
+		itemptr = (ItemPointer)
+			DatumGetPointer(ExecEvalExprSwitchContext(exstate,
+													  econtext,
+													  &isnull,
+													  NULL));
+		if (!isnull)
+		{
+			/*
+			 * OK, we could calculate a particular TID that should be
+			 * larger than, less than or equal with fetched record, thus,
+			 * it allows to determine upper or lower bounds of this scan.
+			 */
+			switch (opno)
+			{
+				case TIDLessOperator:
+					if (ctss->ip_max_comp > 0 ||
+						ItemPointerCompare(itemptr, &ctss->ip_max) <= 0)
+					{
+						ItemPointerCopy(itemptr, &ctss->ip_max);
+						ctss->ip_max_comp = -1;
+					}
+					break;
+				case TIDLessEqualOperator:
+					if (ctss->ip_max_comp > 0 ||
+						ItemPointerCompare(itemptr, &ctss->ip_max) < 0)
+					{
+						ItemPointerCopy(itemptr, &ctss->ip_max);
+						ctss->ip_max_comp = 0;
+					}
+					break;
+				case TIDGreaterOperator:
+					if (ctss->ip_min_comp < 0 ||
+						ItemPointerCompare(itemptr, &ctss->ip_min) >= 0)
+					{
+						ItemPointerCopy(itemptr, &ctss->ip_min);
+						ctss->ip_min_comp = 0;
+					}
+					break;
+				case TIDGreaterEqualOperator:
+					if (ctss->ip_min_comp < 0 ||
+						ItemPointerCompare(itemptr, &ctss->ip_min) > 0)
+					{
+						ItemPointerCopy(itemptr, &ctss->ip_min);
+						ctss->ip_min_comp = 1;
+					}
+					break;
+				default:
+					elog(ERROR, "unsupported operator");
+					break;
+			}
+		}
+		else
+		{
+			/*
+			 * Whole of the restriction clauses chainned with AND- boolean
+			 * operators because false, if one of the clauses has NULL result.
+			 * So, we can immediately break the evaluation to inform caller
+			 * it does not make sense to scan any more.
+			 */
+			return false;
+		}
+	}
+	return true;
+}
+
+/*
+ * CTidBeginCustomScan
+ *
+ * It initializes the given CustomScanState according to the CustomScan plan.
+ */
+static void
+CTidBeginCustomScan(CustomScanState *node, int eflags)
+{
+	CustomScan	   *cscan = (CustomScan *)node->ss.ps.plan;
+	Index			scanrelid = ((Scan *)node->ss.ps.plan)->scanrelid;
+	EState		   *estate = node->ss.ps.state;
+	CTidScanState  *ctss;
+
+	/* Do nothing anymore in EXPLAIN (no ANALYZE) case. */
+	if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
+		return;
+
+	/* Begin sequential scan, but pointer shall be seeked later */
+	node->ss.ss_currentScanDesc
+		= heap_beginscan(node->ss.ss_currentRelation,
+						 estate->es_snapshot, 0, NULL);
+
+	/* init CTidScanState */
+	ctss = palloc0(sizeof(CTidScanState));
+	ctss->scanrelid = scanrelid;
+	ctss->ctid_quals = (List *)
+		ExecInitExpr((Expr *)cscan->custom_private, &node->ss.ps);
+	ctss->ip_needs_eval = true;
+
+	node->custom_state = ctss;
+}
+
+/*
+ * CTidSeekPosition
+ *
+ * It seeks current scan position into a particular point we specified.
+ * Next heap_getnext() will fetch a record from the point we seeked.
+ * It returns false, if specified position was out of range thus does not
+ * make sense to scan any mode. Elsewhere, true shall be return.
+ */
+static bool
+CTidSeekPosition(HeapScanDesc scan, ItemPointer pos, ScanDirection direction)
+{
+	BlockNumber		bnum = BlockIdGetBlockNumber(&pos->ip_blkid);
+	ItemPointerData	save_mctid;
+	int				save_mindex;
+
+	Assert(direction == BackwardScanDirection ||
+		   direction == ForwardScanDirection);
+
+	/*
+	 * In case when block-number is out of the range, it is obvious that
+	 * no tuples shall be fetched if forward scan direction. On the other
+	 * hand, we have nothing special for backward scan direction.
+	 * Note that heap_getnext() shall return NULL tuple just after
+	 * heap_rescan() if NoMovementScanDirection is given. Caller of this
+	 * function override scan direction if 'true' was returned, so it makes
+	 * this scan terminated immediately.
+	 */
+	if (bnum >= scan->rs_nblocks)
+	{
+		heap_rescan(scan, NULL);
+		/* Termination of this scan immediately */
+		if (direction == ForwardScanDirection)
+			return true;
+		/* Elsewhere, backward scan from the beginning */
+		return false;
+	}
+
+	/* save the marked position */
+	ItemPointerCopy(&scan->rs_mctid, &save_mctid);
+	save_mindex = scan->rs_mindex;
+
+	/*
+	 * Ensure the block that includes the position shall be loaded on
+	 * heap_restrpos(). Because heap_restrpos() internally calls
+	 * heapgettup() or heapgettup_pagemode() that kicks heapgetpage()
+	 * when rs_cblock is different from the block number being pointed
+	 * by rs_mctid, it makes sense to put invalid block number not to
+	 * match previous value.
+	 */
+	scan->rs_cblock = InvalidBlockNumber;
+
+	/* Put a pseudo value as if heap_markpos() save a position. */
+	ItemPointerCopy(pos, &scan->rs_mctid);
+	if (scan->rs_pageatatime)
+		scan->rs_mindex = ItemPointerGetOffsetNumber(pos) - 1;
+
+	/* Seek to the point */
+	heap_restrpos(scan);
+
+	/* restore the marked position */
+	ItemPointerCopy(&save_mctid, &scan->rs_mctid);
+	scan->rs_mindex = save_mindex;
+
+	return true;
+}
+
+/*
+ * CTidAccessCustomScan
+ *
+ * Access method of ExecScan(). It fetches a tuple from the underlying heap
+ * scan that was started from the point according to the tid clauses.
+ */
+static TupleTableSlot *
+CTidAccessCustomScan(CustomScanState *node)
+{
+	CTidScanState  *ctss = node->custom_state;
+	HeapScanDesc	scan = node->ss.ss_currentScanDesc;
+	TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	HeapTuple		tuple;
+
+	if (ctss->ip_needs_eval)
+	{
+		/* It terminates this scan, if result set shall be obvious empty. */
+		if (!CTidEvalScanZone(node))
+			return NULL;
+
+		if (direction == ForwardScanDirection)
+		{
+			/* seek to the point if min-tid was obvious */
+			if (ctss->ip_min_comp != -1)
+			{
+				if (CTidSeekPosition(scan, &ctss->ip_min, direction))
+					direction = NoMovementScanDirection;
+			}
+			else if (scan->rs_inited)
+				heap_rescan(scan, NULL);
+		}
+		else if (direction == BackwardScanDirection)
+		{
+			/* seel to the point if max-tid was obvious */
+			if (ctss->ip_max_comp != 1)
+			{
+				if (CTidSeekPosition(scan, &ctss->ip_max, direction))
+					direction = NoMovementScanDirection;
+			}
+			else if (scan->rs_inited)
+				heap_rescan(scan, NULL);
+		}
+		else
+			elog(ERROR, "unexpected scan direction");
+
+		ctss->ip_needs_eval = false;
+	}
+
+	/*
+	 * get the next tuple from the table
+	 */
+	tuple = heap_getnext(scan, direction);
+	if (!HeapTupleIsValid(tuple))
+		return NULL;
+
+	/*
+	 * check whether the fetched tuple reached to the upper bound
+	 * if forward scan, or the lower bound if backward scan.
+	 */
+	if (direction == ForwardScanDirection)
+	{
+		if (ItemPointerCompare(&tuple->t_self,
+							   &ctss->ip_max) > ctss->ip_max_comp)
+			return NULL;
+	}
+	else if (direction == BackwardScanDirection)
+	{
+		if (ItemPointerCompare(&scan->rs_ctup.t_self,
+							   &ctss->ip_min) < ctss->ip_min_comp)
+			return NULL;
+	}
+	ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+	return slot;
+}
+
+/*
+ * CTidRecheckCustomScan
+ *
+ * Recheck method of ExecScan(). We don't need recheck logic.
+ */
+static bool
+CTidRecheckCustomScan(CustomScanState *node, TupleTableSlot *slot)
+{
+	return true;
+}
+
+/*
+ * CTidExecCustomScan
+ *
+ * It fetches a tuple from the underlying heap scan, according to
+ * the Execscan() manner.
+ */
+static TupleTableSlot *
+CTidExecCustomScan(CustomScanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) CTidAccessCustomScan,
+					(ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan
+ *
+ * It terminates custom tid scan.
+ */
+static void
+CTidEndCustomScan(CustomScanState *node)
+{
+	CTidScanState  *ctss = node->custom_state;
+
+	/* if ctss != NULL, we started underlying heap-scan */
+	if (ctss)
+		heap_endscan(node->ss.ss_currentScanDesc);
+}
+
+/*
+ * CTidReScanCustomScan
+ *
+ * It rewinds current position of the scan. Setting ip_needs_eval indicates
+ * to calculate the starting point again and rewinds underlying heap scan
+ * on the next ExecScan timing.
+ */
+static void
+CTidReScanCustomScan(CustomScanState *node)
+{
+	CTidScanState  *ctss = node->custom_state;
+
+	ctss->ip_needs_eval = true;
+
+	ExecScanReScan(&node->ss);
+}
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	CustomProvider		provider;
+
+	/* registration of callback on add scan path */
+	add_scan_path_next = add_scan_path_hook;
+	add_scan_path_hook = CTidAddScanPath;
+
+	/* registration of custom scan provider */
+	memset(&provider, 0, sizeof(provider));
+	snprintf(provider.name, sizeof(provider.name), "ctidscan");
+	provider.InitCustomScanPlan   = CTidInitCustomScanPlan;
+	provider.BeginCustomScan      = CTidBeginCustomScan;
+	provider.ExecCustomScan       = CTidExecCustomScan;
+	provider.EndCustomScan        = CTidEndCustomScan;
+	provider.ReScanCustomScan     = CTidReScanCustomScan;
+
+	register_custom_provider(&provider);
+}
diff --git a/doc/src/sgml/ctidscan.sgml b/doc/src/sgml/ctidscan.sgml
new file mode 100644
index 0000000..60081f7
--- /dev/null
+++ b/doc/src/sgml/ctidscan.sgml
@@ -0,0 +1,107 @@
+<!-- doc/src/sgml/ctidscan.sgml -->
+
+<sect1 id="ctidscan" xreflabel="ctidscan">
+ <title>lo</title>
+
+ <indexterm zone="ctidscan">
+  <primary>ctidscan</primary>
+ </indexterm>
+
+ <para>
+  The <filename>ctidscan</> module provides an additional logic to scan
+  regular relations if <literal>WHERE</> clause contains inequality
+  operators that compares something with <literal>ctid</> system column.
+  It also performs as a proof-of-concept implementation that works on
+  the custom-scan APIs that enables to extend the core executor system.
+ </para>
+
+ <sect2>
+  <title>Overview</title>
+  <para>
+   Once this module is loaded, it registers itself as a custom-scan provider.
+   It allows to provide an additional scan path on regular relations using
+   qualifiers that reference <literal>ctid</> system column.
+  </para>
+  <para>
+   For example, the query below usually falls to sequential scan if this
+   module was not loaded.
+<programlisting>
+SELECT ctid,* FROM my_table WHERE ctid > '(100,0)'::tid;
+</programlisting>
+   On the other hand, <filename>ctidscan</> module can construct an alternative
+   scan plan utilizing inequality operators that involve <literal>ctid</> 
+   system column, to reduce number of rows to be processed.
+   It does not make sense obviously to read tuples within pages being located
+   on 99th page or prior. So, it seeks the internal pointer to scan into
+   <literal>(100,0)</> at beginning of the scan, even though it internally
+   uses same logic with sequential scan.
+  </para>
+  <para>
+   Usually, <productname>PostgreSQL</> runs queries with inequality operators
+   that involves <literal>ctid</> system column using sequential scan, as
+   follows.
+<programlisting>
+postgres=# EXPLAIN SELECT * FROM t1 WHERE ctid > '(100,0)'::tid;
+                       QUERY PLAN
+--------------------------------------------------------
+ Seq Scan on t1  (cost=0.00..209.00 rows=3333 width=37)
+   Filter: (ctid > '(100,0)'::tid)
+(2 rows)
+</programlisting>
+   It works well except for the waste of i/o loads on the pages that contains
+   the records to be skipped.
+  </para>
+  <para>
+   On the other hands, an alternative scan path implemented with
+   <filename>ctidscan</> provides more efficient way; that skips the first
+   100 pages prior to sequencial scan, as follows.
+<programlisting>
+postgres=# load 'ctidscan';
+LOAD
+postgres=# EXPLAIN SELECT * FROM t1 WHERE ctid > '(100,0)'::tid;
+                              QUERY PLAN
+----------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1  (cost=0.00..100.00 rows=3333 width=37)
+   Filter: (ctid > '(100,0)'::tid)
+(2 rows)
+</programlisting>
+   The optimizer internally compares all the candidates of scan paths, then
+   chooses a path with cheapest cost. The custom-scan path provided by
+   <filename>ctidscan</> is usually cheaper than sequential scan because of
+   smaller number of tuples to be processed.
+  </para>
+  <para>
+   Of course, it shall not be choosen if we have more cheaper path than the
+   above custom-scan path. Index-scan based on equality operation is usually
+   cheaper than this custom-scan, so optimizer adopts it instead of sequential
+   scan or custom scan provided by <filename>ctidscan</> for instance.
+<programlisting>
+postgres=# EXPLAIN SELECT * FROM t1 WHERE ctid > '(100,0)'::tid AND a = 100;
+                            QUERY PLAN
+-------------------------------------------------------------------
+ Index Scan using t1_pkey on t1  (cost=0.29..8.30 rows=1 width=37)
+   Index Cond: (a = 100)
+   Filter: (ctid > '(100,0)'::tid)
+(3 rows)
+</programlisting>
+  </para>
+  <para>
+   Its usage is quite simple. All you need to do is, loading
+   the <filename>ctidscan</> into <productname>PostgreSQL</> using
+   <xref linkend="sql-load"> command,
+   <xref linkend="guc-shared-preload-libraries"> or
+   <xref linkend="guc-local-preload-libraries"> parameter, according to
+   your convenience.
+  </para>
+  <para>
+   We have no configurable parameter in this module, right now.
+  </para>
+ </sect2>
+ <sect2>
+  <title>Author</title>
+  <para>
+   KaiGai Kohei <email>kaigai@kaigai.gr.jp</email>
+  </para>
+ </sect2>
+
+</sect1>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 4e93df2..39d2c12 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -19,6 +19,7 @@
 #include "commands/defrem.h"
 #include "commands/prepare.h"
 #include "executor/hashjoin.h"
+#include "executor/nodeCustom.h"
 #include "foreign/fdwapi.h"
 #include "optimizer/clauses.h"
 #include "parser/parsetree.h"
@@ -84,6 +85,7 @@ static void show_hash_info(HashState *hashstate, ExplainState *es);
 static void show_instrumentation_count(const char *qlabel, int which,
 						   PlanState *planstate, ExplainState *es);
 static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_customscan_info(CustomScanState *cstate, ExplainState *es);
 static const char *explain_get_index_name(Oid indexId);
 static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
 						ExplainState *es);
@@ -683,6 +685,11 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
+		case T_CustomScan:
+			if (((Scan *) plan)->scanrelid > 0)
+				*rels_used = bms_add_member(*rels_used,
+											((Scan *) plan)->scanrelid);
+			break;
 		case T_ModifyTable:
 			/* cf ExplainModifyTarget */
 			*rels_used = bms_add_member(*rels_used,
@@ -809,6 +816,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 	const char *sname;			/* node type name for non-text output */
 	const char *strategy = NULL;
 	const char *operation = NULL;
+	char		namebuf[NAMEDATALEN + 32];
 	int			save_indent = es->indent;
 	bool		haschildren;
 
@@ -897,6 +905,13 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomScan:
+			snprintf(namebuf, sizeof(namebuf), "Custom Scan (%s)",
+					 ((CustomScan *) plan)->custom_name);
+			pname = pstrdup(namebuf);
+			sname = "Custom Scan";
+			operation = ((CustomScan *) plan)->custom_name;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1013,6 +1028,10 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			ExplainScanTarget((Scan *) plan, es);
 			break;
+		case T_CustomScan:
+			if (((Scan *) plan)->scanrelid > 0)
+				ExplainScanTarget((Scan *) plan, es);
+			break;
 		case T_IndexScan:
 			{
 				IndexScan  *indexscan = (IndexScan *) plan;
@@ -1291,6 +1310,17 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
 			break;
+		case T_CustomScan:
+			if (((CustomScan *)plan)->funcexpr != NULL && es->verbose)
+				show_expression(((CustomScan *)plan)->funcexpr,
+								"Function Call", planstate, ancestors,
+								es->verbose, es);
+			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+			if (plan->qual)
+				show_instrumentation_count("Rows Removed by Filter", 1,
+										   planstate, es);
+			show_customscan_info((CustomScanState *) planstate, es);
+			break;
 		case T_NestLoop:
 			show_upper_qual(((NestLoop *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
@@ -1858,6 +1888,19 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
 }
 
 /*
+ * Show extra information for a CustomScan node.
+ */
+static void
+show_customscan_info(CustomScanState *cstate, ExplainState *es)
+{
+	CustomProvider *provider = cstate->custom_provider;
+
+	/* Let custom scan provider emit whatever fields it wants */
+	if (provider->ExplainCustomScan != NULL)
+		provider->ExplainCustomScan(cstate, es);
+}
+
+/*
  * Fetch the name of an index in an EXPLAIN
  *
  * We allow plugins to get control here so that plans involving hypothetical
@@ -2025,6 +2068,41 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 			objectname = rte->ctename;
 			objecttag = "CTE Name";
 			break;
+		case T_CustomScan:
+			if (rte->rtekind == RTE_RELATION)
+			{
+				objectname = get_rel_name(rte->relid);
+				if (es->verbose)
+					namespace =
+						get_namespace_name(get_rel_namespace(rte->relid));
+				objecttag = "Relation Name";
+			}
+			else if (rte->rtekind == RTE_JOIN)
+			{
+				objectname = rte->eref->aliasname;
+				objecttag = "Join Alias";
+			}
+			else if (rte->rtekind == RTE_FUNCTION)
+			{
+				Node	   *funcexpr = ((CustomScan *) plan)->funcexpr;
+
+				if (funcexpr && IsA(funcexpr, FuncExpr))
+				{
+					Oid		funcid = ((FuncExpr *) funcexpr)->funcid;
+
+					objectname = get_func_name(funcid);
+					if (es->verbose)
+						namespace =
+							get_namespace_name(get_func_namespace(funcid));
+                }
+				objecttag = "Function Name";
+			}
+			else if (rte->rtekind == RTE_CTE)
+			{
+				objectname = rte->ctename;
+				objecttag = "CTE Name";
+			}
+			break;
 		default:
 			break;
 	}
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 6081b56..4dece5a 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -15,7 +15,7 @@ include $(top_builddir)/src/Makefile.global
 OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
        execProcnode.o execQual.o execScan.o execTuples.o \
        execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
-       nodeBitmapAnd.o nodeBitmapOr.o \
+       nodeBitmapAnd.o nodeBitmapOr.o nodeCustom.o \
        nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
        nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \
        nodeLimit.o nodeLockRows.o \
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index a078104..f80e6c4 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -21,6 +21,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
 #include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
@@ -197,6 +198,10 @@ ExecReScan(PlanState *node)
 			ExecReScanForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecReScanCustomScan((CustomScanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
@@ -291,6 +296,10 @@ ExecMarkPos(PlanState *node)
 			ExecValuesMarkPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecCustomMarkPos((CustomScanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialMarkPos((MaterialState *) node);
 			break;
@@ -348,6 +357,10 @@ ExecRestrPos(PlanState *node)
 			ExecValuesRestrPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecCustomRestrPos((CustomScanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialRestrPos((MaterialState *) node);
 			break;
@@ -379,9 +392,9 @@ ExecRestrPos(PlanState *node)
  * and valuesscan support is actually useless code at present.)
  */
 bool
-ExecSupportsMarkRestore(NodeTag plantype)
+ExecSupportsMarkRestore(Path *path)
 {
-	switch (plantype)
+	switch (path->pathtype)
 	{
 		case T_SeqScan:
 		case T_IndexScan:
@@ -392,6 +405,14 @@ ExecSupportsMarkRestore(NodeTag plantype)
 		case T_Sort:
 			return true;
 
+		case T_CustomPath:
+			{
+				int	flags = ((CustomPath *) path)->custom_flags;
+				if (flags & CUSTOM__SUPPORT_MARK_RESTORE)
+					return true;
+				return false;
+			}
+
 		case T_Result:
 
 			/*
@@ -465,6 +486,15 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomScan:
+			{
+				int		flags = ((CustomScan *) node)->custom_flags;
+
+				if (flags & CUSTOM__SUPPORT_BACKWARD_SCAN)
+					return TargetListSupportsBackwardScan(node->targetlist);
+			}
+			return false;
+
 		case T_Material:
 		case T_Sort:
 			/* these don't evaluate tlist */
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 76dd62f..b1110b9 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -85,6 +85,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
 #include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
@@ -244,6 +245,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													   estate, eflags);
 			break;
 
+		case T_CustomScan:
+			result = (PlanState *) ExecInitCustomScan((CustomScan *) node,
+													  estate, eflags);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -442,6 +448,10 @@ ExecProcNode(PlanState *node)
 			result = ExecForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			result = ExecCustomScan((CustomScanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -678,6 +688,10 @@ ExecEndNode(PlanState *node)
 			ExecEndForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecEndCustomScan((CustomScanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 90c2753..e60ac67 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -592,7 +592,7 @@ ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
 			slot = econtext->ecxt_outertuple;
 			break;
 
-			/* INDEX_VAR is handled by default case */
+			/* INDEX_VAR and CUSTOM_VAR are handled by default case */
 
 		default:				/* get the tuple from the relation being
 								 * scanned */
@@ -680,7 +680,7 @@ ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
 			slot = econtext->ecxt_outertuple;
 			break;
 
-			/* INDEX_VAR is handled by default case */
+			/* INDEX_VAR and CUSTOM_VAR are handled by default case */
 
 		default:				/* get the tuple from the relation being
 								 * scanned */
@@ -732,7 +732,7 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
 			slot = econtext->ecxt_outertuple;
 			break;
 
-			/* INDEX_VAR is handled by default case */
+			/* INDEX_VAR and CUSTOM_VAR are handled by default case */
 
 		default:				/* get the tuple from the relation being
 								 * scanned */
@@ -915,7 +915,7 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
 			slot = econtext->ecxt_outertuple;
 			break;
 
-			/* INDEX_VAR is handled by default case */
+			/* INDEX_VAR and CUSTOM_VAR are handled by default case */
 
 		default:				/* get the tuple from the relation being
 								 * scanned */
@@ -991,7 +991,7 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
 			slot = econtext->ecxt_outertuple;
 			break;
 
-			/* INDEX_VAR is handled by default case */
+			/* INDEX_VAR and CUSTOM_VAR are handled by default case */
 
 		default:				/* get the tuple from the relation being
 								 * scanned */
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 39e3b2e..df0d295 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -578,7 +578,7 @@ ExecBuildProjectionInfo(List *targetList,
 						projInfo->pi_lastOuterVar = attnum;
 					break;
 
-					/* INDEX_VAR is handled by default case */
+					/* INDEX_VAR and CUSTOM_VAR are handled by default case */
 
 				default:
 					varSlotOffsets[numSimpleVars] = offsetof(ExprContext,
@@ -638,7 +638,7 @@ get_last_attnums(Node *node, ProjectionInfo *projInfo)
 					projInfo->pi_lastOuterVar = attnum;
 				break;
 
-				/* INDEX_VAR is handled by default case */
+				/* INDEX_VAR and CUSTOM_VAR are handled by default case */
 
 			default:
 				if (projInfo->pi_lastScanVar < attnum)
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..61bca22
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,252 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ *    Routines to handle execution of custom plan, scan and join node
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "executor/nodeCustom.h"
+#include "parser/parsetree.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+/* static variables */
+static HTAB *custom_provider_hash = NULL;
+
+/*
+ * register_custom_provider
+ *
+ * It registers a custom execution provider; that consists of a set of
+ * callbacks and is identified with a unique name.
+ */
+void
+register_custom_provider(const CustomProvider *provider)
+{
+	CustomProvider *entry;
+	bool			found;
+
+	if (!custom_provider_hash)
+	{
+		HASHCTL		ctl;
+
+		memset(&ctl, 0, sizeof(ctl));
+		ctl.hcxt = CacheMemoryContext;
+		ctl.keysize = NAMEDATALEN;
+		ctl.entrysize = sizeof(CustomProvider);
+
+		custom_provider_hash = hash_create("custom execution providers",
+										   32,
+										   &ctl,
+										   HASH_ELEM | HASH_CONTEXT);
+	}
+
+	entry = hash_search(custom_provider_hash,
+						provider->name,
+						HASH_ENTER, &found);
+	if (found)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("duplicate custom execution provider \"%s\"",
+						provider->name)));
+
+	Assert(strcmp(provider->name, entry->name) == 0);
+	memcpy(entry, provider, sizeof(CustomProvider));
+}
+
+/*
+ * get_custom_provider
+ *
+ * It finds a registered custom execution provide by its name
+ */
+CustomProvider *
+get_custom_provider(const char *custom_name)
+{
+	CustomProvider *entry;
+
+	/* lookup custom execution provider */
+	if (!custom_provider_hash)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("no custom execution provider was registered")));
+
+	entry = (CustomProvider *) hash_search(custom_provider_hash,
+										   custom_name, HASH_FIND, NULL);
+	if (!entry)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("custom execution provider \"%s\" was not registered",
+						custom_name)));
+
+	return entry;
+}
+
+/*
+ * ExecInitCustomScan
+ *
+ * Allocation of CustomScanState and various initialization stuff.
+ * Note that some of initialization jobs are skipped if scanrelid is zero
+ * (that means this custom scan plan is not associated with a particular
+ * relation in range-table list.)
+ */
+CustomScanState *
+ExecInitCustomScan(CustomScan *node, EState *estate, int eflags)
+{
+	CustomProvider	   *provider = get_custom_provider(node->custom_name);
+	CustomScanState	   *csstate;
+	Plan			   *plan = &node->scan.plan;
+	Index				scanrelid = node->scan.scanrelid;
+
+	/*
+	 * Create state structure
+	 */
+	csstate = makeNode(CustomScanState);
+	csstate->ss.ps.plan = plan;
+	csstate->ss.ps.state = estate;
+	csstate->custom_provider = provider;
+	csstate->custom_flags = node->custom_flags;
+	csstate->custom_state = NULL;
+
+	/*
+	 * Miscellaneous initialization
+	 */
+	ExecAssignExprContext(estate, &csstate->ss.ps);
+
+	/*
+	 * Initialization of child expressions
+	 */
+	csstate->ss.ps.targetlist =
+		(List *) ExecInitExpr((Expr *) plan->targetlist, &csstate->ss.ps);
+	csstate->ss.ps.qual =
+		(List *) ExecInitExpr((Expr *) plan->qual, &csstate->ss.ps);
+
+	/*
+	 * tuple table initialization
+	 *
+	 * Note that ss_ScanTupleSlot is set only when scanrelid is associated
+	 * with a particular relation. Elsewhere, it needs to be initialized by
+	 * custom-scan provider itself if it internally uses ss_ScanTupleSlot.
+	 * If it replaces varno of Var node by CUSTOM_VAR, it has to be set to
+	 * reference underlying attribute name to generate EXPLAIN output.
+	 */
+	ExecInitResultTupleSlot(estate, &csstate->ss.ps);
+	if (scanrelid > 0)
+		ExecInitScanTupleSlot(estate, &csstate->ss);
+
+	/*
+	 * open the base relation and acquire appropriate lock on it,
+	 * if this custom scan is connected with a particular relaion.
+	 * Also, assign its scan type according to the table definition.
+	 */
+	if (scanrelid > 0)
+	{
+		Relation	rel = ExecOpenScanRelation(estate, scanrelid, eflags);
+
+		csstate->ss.ss_currentRelation = rel;
+		ExecAssignScanType(&csstate->ss, RelationGetDescr(rel));
+
+		csstate->ss.ps.ps_TupFromTlist = false;
+	}
+
+	/*
+	 * Initialize result tuple type and projection info.
+	 */
+	ExecAssignResultTypeFromTL(&csstate->ss.ps);
+
+	if (scanrelid > 0)
+		ExecAssignScanProjectionInfo(&csstate->ss);
+	else
+		ExecAssignProjectionInfo(&csstate->ss.ps, NULL);
+
+	/*
+	 * Final initialization based on callback of BeginCustomScan method.
+	 * Extension may be able to override initialization stuff above, if
+	 * needed.
+	 */
+	csstate->custom_provider->BeginCustomScan(csstate, eflags);
+
+	return csstate;
+}
+
+/*
+ * ExecCustomScan
+ *
+ * Just an entrypoint of ExecCustomScan method. All the stuff to fetch
+ * a tuple is a job of custom-scan provider.
+ */
+TupleTableSlot *
+ExecCustomScan(CustomScanState *csstate)
+{
+	return csstate->custom_provider->ExecCustomScan(csstate);
+}
+
+/*
+ * MultiExecCustomScan
+ *
+ * Aldo, just an entrypoint of MultiExecCustomScan method. All the stuff
+ * to fetch multiple tuples (according to expectation of upper node) is
+ * a job of custom-scan provider.
+ */
+Node *
+MultiExecCustomScan(CustomScanState *csstate)
+{
+	return csstate->custom_provider->MultiExecCustomScan(csstate);
+}
+
+/*
+ * ExecEndCustomScan
+ *
+ * It releases all the resources allocated on this scan.
+ */
+void
+ExecEndCustomScan(CustomScanState *csstate)
+{
+	/* Let the custom-exec shut down */
+	csstate->custom_provider->EndCustomScan(csstate);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&csstate->ss.ps);
+
+	/* Clean out the tuple table, if exists */
+	ExecClearTuple(csstate->ss.ps.ps_ResultTupleSlot);
+	if (csstate->ss.ss_ScanTupleSlot)
+		ExecClearTuple(csstate->ss.ss_ScanTupleSlot);
+
+	/* close the relation, if opened */
+	if (csstate->ss.ss_currentRelation)
+		ExecCloseScanRelation(csstate->ss.ss_currentRelation);
+}
+
+/*
+ * ExecReScanCustomScan
+ */
+void
+ExecReScanCustomScan(CustomScanState *csstate)
+{
+	csstate->custom_provider->ReScanCustomScan(csstate);
+}
+
+/*
+ * ExecCustomMarkPos
+ */
+void
+ExecCustomMarkPos(CustomScanState *csstate)
+{
+	Assert((csstate->custom_flags & CUSTOM__SUPPORT_MARK_RESTORE) != 0);
+	csstate->custom_provider->ExecMarkPosCustomScan(csstate);
+}
+
+/*
+ * ExecCustomRestrPos
+ */
+void
+ExecCustomRestrPos(CustomScanState *csstate)
+{
+	Assert((csstate->custom_flags & CUSTOM__SUPPORT_MARK_RESTORE) != 0);
+	csstate->custom_provider->ExecRestorePosCustom(csstate);
+}
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index 540db16..44f2236 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -865,3 +865,65 @@ bms_hash_value(const Bitmapset *a)
 	return DatumGetUInt32(hash_any((const unsigned char *) a->words,
 								   (lastword + 1) * sizeof(bitmapword)));
 }
+
+/*
+ * bms_to_string / bms_from_string - transform bitmapset to/from text
+ * representation for portability purpose.
+ */
+char *
+bms_to_string(Bitmapset *a)
+{
+	char   *result;
+	char   *pos;
+	int		i;
+
+	if (bms_is_empty(a))
+		return NULL;
+
+	result = palloc(a->nwords * (BITS_PER_BITMAPWORD / 4) + 1);
+	for (i = a->nwords, pos = result; i > 0; i--)
+		pos += sprintf(pos, "%08x", a->words[i - 1]);
+
+	return result;
+}
+
+Bitmapset *
+bms_from_string(const char *a)
+{
+	Bitmapset  *result;
+	Size		len;
+	int			nwords;
+	int			i, offset = 0;
+
+	if (a == NULL)
+		return NULL;
+
+	len = strlen(a);
+	if (len % (BITS_PER_BITMAPWORD / 4) != 0)
+		elog(WARNING, "strange bitmapset text representation: %s", a);
+
+	nwords = (len + BITS_PER_BITMAPWORD / 4 - 1) / (BITS_PER_BITMAPWORD / 4);
+	result = palloc(BITMAPSET_SIZE(nwords));
+	result->nwords = nwords;
+
+	for (i=result->nwords; i > 0; i--)
+	{
+		bitmapword	word = 0;
+
+		do {
+			int		c = a[offset++];
+			if (c >= '0' && c <= '9')
+				word = (word << 4) | (c - '0');
+			else if (c >= 'a' && c <= 'f')
+				word = (word << 4) | (c - 'a');
+			else if (c >= 'A' && c <= 'F')
+				word = (word << 4) | (c - 'A');
+			else
+				elog(ERROR, "invalid hexadecimal digit");
+		} while ((len - offset) % (BITS_PER_BITMAPWORD / 4) != 0);
+
+		result->words[i - 1] = word;
+	}
+
+	return result;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 65f3b98..b56af14 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -602,6 +602,33 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomScan
+ */
+static CustomScan *
+_copyCustomScan(const CustomScan *from)
+{
+	CustomScan *newnode = makeNode(CustomScan);
+
+	/*
+	 * copy node superclass fields
+	 */
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+	/*
+	 * copy remainder of node
+	 */
+	COPY_STRING_FIELD(custom_name);
+	COPY_SCALAR_FIELD(custom_flags);
+	COPY_NODE_FIELD(custom_private);
+	COPY_NODE_FIELD(custom_exprs);
+
+	COPY_NODE_FIELD(subqry_plan);
+	COPY_NODE_FIELD(funcexpr);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -3929,6 +3956,9 @@ copyObject(const void *from)
 		case T_ForeignScan:
 			retval = _copyForeignScan(from);
 			break;
+		case T_CustomScan:
+			retval = _copyCustomScan(from);
+			break;
 		case T_Join:
 			retval = _copyJoin(from);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 817b149..031112b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -568,6 +568,22 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomScan(StringInfo str, const CustomScan *node)
+{
+	WRITE_NODE_TYPE("CUSTOMSCAN");
+
+	_outScanInfo(str, (const Scan *) node);
+
+	WRITE_STRING_FIELD(custom_name);
+	WRITE_INT_FIELD(custom_flags);
+	WRITE_NODE_FIELD(custom_private);
+	WRITE_NODE_FIELD(custom_exprs);
+
+	WRITE_NODE_FIELD(subqry_plan);
+	WRITE_NODE_FIELD(funcexpr);
+}
+
+static void
 _outJoin(StringInfo str, const Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -2814,6 +2830,9 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignScan:
 				_outForeignScan(str, obj);
 				break;
+			case T_CustomScan:
+				_outCustomScan(str, obj);
+				break;
 			case T_Join:
 				_outJoin(str, obj);
 				break;
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index 48ef325..29fcba9 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -333,6 +333,10 @@ print_expr(const Node *expr, const List *rtable)
 				relname = "INDEX";
 				attname = "?";
 				break;
+			case CUSTOM_VAR:
+				relname = "CUSTOM";
+				attname = "?";
+				break;
 			default:
 				{
 					RangeTblEntry *rte;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index bfd3809..9d0cbf5 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -46,6 +46,8 @@ int			geqo_threshold;
 /* Hook for plugins to replace standard_join_search() */
 join_search_hook_type join_search_hook = NULL;
 
+/* Hook for plugins to add custom scan paths */
+add_scan_path_hook_type add_scan_path_hook = NULL;
 
 static void set_base_rel_sizes(PlannerInfo *root);
 static void set_base_rel_pathlists(PlannerInfo *root);
@@ -399,6 +401,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
 
+	/* Consider Custom scans */
+	add_custom_scan_paths(root,rel,rte);
+
 	/* Now find the cheapest of the paths for this rel */
 	set_cheapest(rel);
 }
@@ -427,6 +432,9 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Call the FDW's GetForeignPaths function to generate path(s) */
 	rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
 
+	/* Consider Custom scans */
+	add_custom_scan_paths(root,rel,rte);
+
 	/* Select cheapest path */
 	set_cheapest(rel);
 }
@@ -1246,6 +1254,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	/* Generate appropriate path */
 	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
 
+	/* Consider Custom scans */
+	add_custom_scan_paths(root,rel,rte);
+
 	/* Select cheapest path (pretty easy in this case...) */
 	set_cheapest(rel);
 }
@@ -1269,6 +1280,9 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Generate appropriate path */
 	add_path(rel, create_functionscan_path(root, rel, required_outer));
 
+	/* Consider Custom scans */
+	add_custom_scan_paths(root,rel,rte);
+
 	/* Select cheapest path (pretty easy in this case...) */
 	set_cheapest(rel);
 }
@@ -1292,6 +1306,9 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Generate appropriate path */
 	add_path(rel, create_valuesscan_path(root, rel, required_outer));
 
+	/* Consider Custom scans */
+	add_custom_scan_paths(root,rel,rte);
+
 	/* Select cheapest path (pretty easy in this case...) */
 	set_cheapest(rel);
 }
@@ -1361,6 +1378,9 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Generate appropriate path */
 	add_path(rel, create_ctescan_path(root, rel, required_outer));
 
+	/* Consider Custom scans */
+	add_custom_scan_paths(root,rel,rte);
+
 	/* Select cheapest path (pretty easy in this case...) */
 	set_cheapest(rel);
 }
@@ -1414,6 +1434,9 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Generate appropriate path */
 	add_path(rel, create_worktablescan_path(root, rel, required_outer));
 
+	/* Consider Custom scans */
+	add_custom_scan_paths(root,rel,rte);
+
 	/* Select cheapest path (pretty easy in this case...) */
 	set_cheapest(rel);
 }
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index e7f8cec..c6e1634 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -130,9 +130,6 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root,
 static void cost_rescan(PlannerInfo *root, Path *path,
 			Cost *rescan_startup_cost, Cost *rescan_total_cost);
 static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
-static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
-						  ParamPathInfo *param_info,
-						  QualCost *qpqual_cost);
 static bool has_indexed_join_quals(NestPath *joinpath);
 static double approx_tuple_count(PlannerInfo *root, JoinPath *path,
 				   List *quals);
@@ -2312,7 +2309,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
 	 * it off does not entitle us to deliver an invalid plan.
 	 */
 	else if (innersortkeys == NIL &&
-			 !ExecSupportsMarkRestore(inner_path->pathtype))
+			 !ExecSupportsMarkRestore(inner_path))
 		path->materialize_inner = true;
 
 	/*
@@ -3201,7 +3198,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
  * some of the quals.  We assume baserestrictcost was previously set by
  * set_baserel_size_estimates().
  */
-static void
+void
 get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
 						  ParamPathInfo *param_info,
 						  QualCost *qpqual_cost)
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 5b477e5..9483614 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -21,6 +21,8 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 
+/* Hook for plugins to add custom join paths */
+add_join_path_hook_type add_join_path_hook = NULL;
 
 #define PATH_PARAM_BY_REL(path, rel)  \
 	((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids))
@@ -259,6 +261,22 @@ add_paths_to_joinrel(PlannerInfo *root,
 							 restrictlist, jointype,
 							 sjinfo, &semifactors,
 							 param_source_rels, extra_lateral_rels);
+
+	/*
+	 * 5. Also consider paths being provided with custom execution provider.
+	 */
+	if (add_join_path_hook)
+		(*add_join_path_hook)(root,
+							  joinrel,
+							  outerrel,
+							  innerrel,
+							  jointype,
+							  sjinfo,
+							  restrictlist,
+							  mergeclause_list,
+							  &semifactors,
+							  param_source_rels,
+							  extra_lateral_rels);
 }
 
 /*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 9b9eb2f..9626d08 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -21,6 +21,7 @@
 
 #include "access/skey.h"
 #include "catalog/pg_class.h"
+#include "executor/nodeCustom.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -77,6 +78,9 @@ static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_pa
 						  List *tlist, List *scan_clauses);
 static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 						List *tlist, List *scan_clauses);
+static CustomScan *create_customscan_plan(PlannerInfo *root,
+										  CustomPath *best_path,
+										  List *tlist, List *scan_clauses);
 static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
 					 Plan *outer_plan, Plan *inner_plan);
 static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
@@ -235,6 +239,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 		case T_CteScan:
 		case T_WorkTableScan:
 		case T_ForeignScan:
+		case T_CustomScan:
 			plan = create_scan_plan(root, best_path);
 			break;
 		case T_HashJoin:
@@ -411,6 +416,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
 													scan_clauses);
 			break;
 
+		case T_CustomScan:
+			plan = (Plan *) create_customscan_plan(root,
+												   (CustomPath *) best_path,
+												   tlist,
+												   scan_clauses);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -2016,6 +2028,97 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	return scan_plan;
 }
 
+/*
+ * create_customscan_plan
+ *   Returns a custom-scan plan for the base relation scanned by 'best_path'
+ *   with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+ */
+static CustomScan *
+create_customscan_plan(PlannerInfo *root,
+					   CustomPath *best_path,
+					   List *tlist,
+					   List *scan_clauses)
+{
+	CustomProvider *provider = get_custom_provider(best_path->custom_name);
+	CustomScan	   *scan_plan = makeNode(CustomScan);
+	RelOptKind		reloptkind = best_path->path.parent->reloptkind;
+	RangeTblEntry  *rte;
+	Index			scan_relid;
+
+	if (reloptkind == RELOPT_BASEREL ||
+		reloptkind == RELOPT_OTHER_MEMBER_REL)
+	{
+		scan_relid = best_path->path.parent->relid;
+
+		rte = planner_rt_fetch(scan_relid, root);
+		/*
+		 * For EXPLAIN output, we save various information in CustomScan plan
+		 * structure. Custom-scan provider can utilize them, but it is not
+		 * recommendablt to adjust.
+		 */
+		if (rte->rtekind == RTE_SUBQUERY)
+		{
+			if (best_path->path.param_info)
+			{
+				List   *subplan_params
+					= best_path->path.parent->subplan_params;
+				process_subquery_nestloop_params(root, subplan_params);
+			}
+			scan_plan->subqry_plan = best_path->path.parent->subplan;
+		}
+		else if (rte->rtekind == RTE_FUNCTION)
+		{
+			Node   *funcexpr = rte->funcexpr;
+
+			if (best_path->path.param_info)
+				funcexpr = replace_nestloop_params(root, funcexpr);
+			scan_plan->funcexpr = funcexpr;
+		}
+	}
+	else if (reloptkind == RELOPT_JOINREL)
+		scan_relid = 0;
+	else
+		elog(ERROR, "unexpected reloptkind: %d", (int)reloptkind);
+
+	scan_clauses = order_qual_clauses(root, scan_clauses);
+	scan_plan->scan.plan.targetlist = NULL;	/* to be set by callback */
+	scan_plan->scan.plan.qual = NULL;		/* to be set by callback */
+	scan_plan->scan.plan.lefttree = NULL;
+	scan_plan->scan.plan.righttree = NULL;
+	scan_plan->scan.scanrelid = scan_relid;
+
+	scan_plan->custom_name = pstrdup(best_path->custom_name);
+	scan_plan->custom_flags = best_path->custom_flags;
+	scan_plan->custom_private = NIL;
+	scan_plan->custom_exprs = NULL;
+
+	/*
+	 * Let custom scan provider perform to set up this custom-scan plan
+	 * according to the given path information. 
+	 */
+	provider->InitCustomScanPlan(root, scan_plan,
+								 best_path, tlist, scan_clauses);
+
+	/* Copy cost data from Path to Plan; no need to make callback do this */
+	copy_path_costsize(&scan_plan->scan.plan, &best_path->path);
+
+	/*
+	 * Replace any outer-relation variables with nestloop params in the qual
+	 * and custom_exprs expressions.  We do this last so that the FDW doesn't
+	 * have to be involved.  (Note that parts of custom_exprs could have come
+	 * from join clauses, so doing this beforehand on the scan_clauses
+	 * wouldn't work.)
+	 */
+	if (best_path->path.param_info)
+	{
+		scan_plan->scan.plan.qual = (List *)
+			replace_nestloop_params(root, (Node *) scan_plan->scan.plan.qual);
+		scan_plan->custom_exprs = (List *)
+			replace_nestloop_params(root, (Node *) scan_plan->custom_exprs);
+	}
+
+	return scan_plan;
+}
 
 /*****************************************************************************
  *
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b78d727..30cf7e5 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -17,6 +17,7 @@
 
 #include "access/transam.h"
 #include "catalog/pg_type.h"
+#include "executor/nodeCustom.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/pathnode.h"
@@ -578,6 +579,30 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomScan:
+			{
+				CustomScan	   *splan = (CustomScan *) plan;
+				CustomProvider *provider
+					= get_custom_provider(splan->custom_name);
+
+				if (provider->SetPlanRefCustomScan)
+					provider->SetPlanRefCustomScan(root, splan, rtoffset);
+				else if (splan->scan.scanrelid > 0)
+				{
+					splan->scan.scanrelid += rtoffset;
+					splan->scan.plan.targetlist =
+						fix_scan_list(root, splan->scan.plan.targetlist,
+									  rtoffset);
+					splan->scan.plan.qual =
+						fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+					splan->custom_exprs =
+						fix_scan_list(root, splan->custom_exprs, rtoffset);
+				}
+				else
+					elog(ERROR, "No implementation to set plan reference");
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1059,7 +1084,7 @@ copyVar(Var *var)
  * We assume it's okay to update opcode info in-place.  So this could possibly
  * scribble on the planner's input data structures, but it's OK.
  */
-static void
+void
 fix_expr_common(PlannerInfo *root, Node *node)
 {
 	/* We assume callers won't call us on a NULL pointer */
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 0df70c4..644a532 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2194,6 +2194,16 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomScan:
+			finalize_primnode((Node *) ((CustomScan *) plan)->custom_exprs,
+							  &context);
+			context.paramids = bms_add_members(context.paramids, scan_params);
+			/*
+			 * XXX - Is it sufficient to do? Don't we need something special
+			 * if CustomScan override FunctionScan or SubqueryScan.
+			 */
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 64b17051..46e814d 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1738,6 +1738,46 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
 }
 
 /*
+ * create_customscan_path
+ *    Creates a path corresponding to a scan of a relation based on logic
+ *    logic being provided by extensions.
+ *
+ * This function is never called from core PostgreSQL. An usual usage is
+ * invocation from callbacks on add_scan_path_hook. We don't have any
+ * assumption on the custom scan logic, thus, caller is responsible to
+ * set adequate cost estimation here.
+ */
+CustomPath *
+create_customscan_path(PlannerInfo *root,
+					   RelOptInfo *baserel,
+					   double rows,
+					   Cost startup_cost,
+					   Cost total_cost,
+					   List *pathkeys,
+					   Relids required_outer,
+					   const char *custom_name,
+					   uint32 custom_flags,
+					   List *custom_private)
+{
+	CustomPath *pathnode = makeNode(CustomPath);
+
+	pathnode->path.pathtype = T_CustomScan;
+	pathnode->path.parent = baserel;
+	pathnode->path.param_info = get_baserel_parampathinfo(root, baserel,
+														  required_outer);
+	pathnode->path.rows = rows;
+	pathnode->path.startup_cost = startup_cost;
+	pathnode->path.total_cost = total_cost;
+	pathnode->path.pathkeys = pathkeys;
+
+	pathnode->custom_name = pstrdup(custom_name);
+	pathnode->custom_flags = custom_flags;
+	pathnode->custom_private = custom_private;
+
+	return pathnode;
+}
+
+/*
  * calc_nestloop_required_outer
  *	  Compute the required_outer set for a nestloop join path
  *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 04b1c4f..eda53d6 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -143,6 +143,7 @@ typedef struct
 	List	   *outer_tlist;	/* referent for OUTER_VAR Vars */
 	List	   *inner_tlist;	/* referent for INNER_VAR Vars */
 	List	   *index_tlist;	/* referent for INDEX_VAR Vars */
+	TupleDesc	custom_tupdesc;	/* referent for CUSTOM_VAR Vars */
 } deparse_namespace;
 
 /*
@@ -2362,14 +2363,19 @@ deparse_context_for(const char *aliasname, Oid relid)
  * deparse_context_for_planstate	- Build deparse context for a plan
  *
  * When deparsing an expression in a Plan tree, we might have to resolve
- * OUTER_VAR, INNER_VAR, or INDEX_VAR references.  To do this, the caller must
- * provide the parent PlanState node.  Then OUTER_VAR and INNER_VAR references
- * can be resolved by drilling down into the left and right child plans.
+ * special varno (OUTER_VAR, INNER_VAR, INDEX_VAR or CUSTOM_VAR) references.
+ * To do this, the caller must provide the parent PlanState node.  Then
+ * OUTER_VAR and INNER_VAR references can be resolved by drilling down into
+ * the left and right child plans.
  * Similarly, INDEX_VAR references can be resolved by reference to the
  * indextlist given in the parent IndexOnlyScan node.  (Note that we don't
  * currently support deparsing of indexquals in regular IndexScan or
  * BitmapIndexScan nodes; for those, we can only deparse the indexqualorig
  * fields, which won't contain INDEX_VAR Vars.)
+ * Also, CUSTOM_VAR references can be resolved by reference to the TupleDesc
+ * of ss_ScanTupleSlot in CustomScanState node. (Note that custom scan
+ * provider must be responsible to initialize the ss_ScanTupleSlot with
+ * appropriate TupleDesc; being likely constructed by ExecTypeFromTL).
  *
  * Note: planstate really ought to be declared as "PlanState *", but we use
  * "Node *" to avoid having to include execnodes.h in builtins.h.
@@ -3627,6 +3633,14 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps)
 		dpns->index_tlist = ((IndexOnlyScan *) ps->plan)->indextlist;
 	else
 		dpns->index_tlist = NIL;
+
+	/* custom_tupdesc is set only if it's an CustomScan */
+	if (IsA(ps, CustomScanState) &&
+		((CustomScanState *)ps)->ss.ss_ScanTupleSlot)
+		dpns->custom_tupdesc =
+			((CustomScanState *)ps)->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+	else
+		dpns->custom_tupdesc = NULL;
 }
 
 /*
@@ -5294,6 +5308,18 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 
 		return NULL;
 	}
+	else if (var->varno == CUSTOM_VAR && dpns->custom_tupdesc)
+	{
+		TupleDesc	tupdesc = dpns->custom_tupdesc;
+
+		Assert(netlevelsup == 0);
+		Assert(var->varattno > 0 && var->varattno <= tupdesc->natts);
+
+		attname = NameStr(tupdesc->attrs[var->varattno - 1]->attname);
+		appendStringInfoString(buf, quote_identifier(attname));
+
+		return attname;
+	}
 	else
 	{
 		elog(ERROR, "bogus varno: %d", var->varno);
@@ -5564,6 +5590,18 @@ get_name_for_var_field(Var *var, int fieldno,
 
 		return result;
 	}
+	else if (var->varno == CUSTOM_VAR && dpns->custom_tupdesc)
+	{
+		TupleDesc	tupdesc = dpns->custom_tupdesc;
+		const char *result;
+
+		Assert(netlevelsup == 0);
+		Assert(var->varattno > 0 && var->varattno <= tupdesc->natts);
+
+		result = NameStr(tupdesc->attrs[var->varattno - 1]->attname);
+
+		return result;
+	}
 	else
 	{
 		elog(ERROR, "bogus varno: %d", var->varno);
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 0350ef6..0c7a233 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -159,15 +159,19 @@ DESCR("equal");
 #define TIDEqualOperator   387
 DATA(insert OID = 402 (  "<>"	   PGNSP PGUID b f f	27	27	16 402 387 tidne neqsel neqjoinsel ));
 DESCR("not equal");
+#define TIDNotEqualOperator	402
 DATA(insert OID = 2799 (  "<"	   PGNSP PGUID b f f	27	27	16 2800 2802 tidlt scalarltsel scalarltjoinsel ));
 DESCR("less than");
 #define TIDLessOperator    2799
 DATA(insert OID = 2800 (  ">"	   PGNSP PGUID b f f	27	27	16 2799 2801 tidgt scalargtsel scalargtjoinsel ));
 DESCR("greater than");
+#define TIDGreaterOperator		2800
 DATA(insert OID = 2801 (  "<="	   PGNSP PGUID b f f	27	27	16 2802 2800 tidle scalarltsel scalarltjoinsel ));
 DESCR("less than or equal");
+#define TIDLessEqualOperator	2801
 DATA(insert OID = 2802 (  ">="	   PGNSP PGUID b f f	27	27	16 2801 2799 tidge scalargtsel scalargtjoinsel ));
 DESCR("greater than or equal");
+#define TIDGreaterEqualOperator	2802
 
 DATA(insert OID = 410 ( "="		   PGNSP PGUID b t t	20	20	16 410 411 int8eq eqsel eqjoinsel ));
 DESCR("equal");
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 75841c8..51537d2 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -16,6 +16,7 @@
 
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
+#include "nodes/relation.h"
 
 
 /*
@@ -102,7 +103,7 @@ extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
 extern void ExecReScan(PlanState *node);
 extern void ExecMarkPos(PlanState *node);
 extern void ExecRestrPos(PlanState *node);
-extern bool ExecSupportsMarkRestore(NodeTag plantype);
+extern bool ExecSupportsMarkRestore(Path *path);
 extern bool ExecSupportsBackwardScan(Plan *node);
 extern bool ExecMaterializesOutput(NodeTag plantype);
 
diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h
new file mode 100644
index 0000000..8ea5693
--- /dev/null
+++ b/src/include/executor/nodeCustom.h
@@ -0,0 +1,94 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.h
+ *
+ * prototypes for CustomScan nodes
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ------------------------------------------------------------------------
+ */
+#ifndef NODECUSTOM_H
+#define NODECUSTOM_H
+#include "commands/explain.h"
+#include "nodes/plannodes.h"
+#include "nodes/relation.h"
+
+/*
+ * Definition of the custom execution provider callbacks
+ */
+typedef void (*InitCustomScanPlan_function)(PlannerInfo *root,
+											CustomScan *cscan_plan,
+											CustomPath *cscan_path,
+											List *tlist,
+											List *scan_clauses);
+typedef void (*SetPlanRefCustomScan_function)(PlannerInfo *root,
+											  CustomScan *cscan_plan,
+											  int rtoffset);
+typedef void (*BeginCustomScan_function)(CustomScanState *csstate, int eflags);
+typedef TupleTableSlot *(*ExecCustomScan_function)(CustomScanState *csstate);
+typedef Node *(*MultiExecCustomScan_function)(CustomScanState *csstate);
+typedef void (*EndCustomScan_function)(CustomScanState *csstate);
+
+typedef void (*ReScanCustomScan_function)(CustomScanState *csstate);
+typedef void (*ExecMarkPosCustomScan_function)(CustomScanState *csstate);
+typedef void (*ExecRestorePosCustom_function)(CustomScanState *csstate);
+
+typedef void (*ExplainCustomScan_function)(CustomScanState *csstate,
+										   ExplainState *es);
+
+typedef struct CustomProvider
+{
+	char							name[NAMEDATALEN];
+
+	InitCustomScanPlan_function		InitCustomScanPlan;
+	SetPlanRefCustomScan_function	SetPlanRefCustomScan;
+
+	BeginCustomScan_function		BeginCustomScan;
+	ExecCustomScan_function			ExecCustomScan;
+	MultiExecCustomScan_function	MultiExecCustomScan;
+	EndCustomScan_function			EndCustomScan;
+
+	ReScanCustomScan_function		ReScanCustomScan;
+	ExecMarkPosCustomScan_function	ExecMarkPosCustomScan;
+	ExecRestorePosCustom_function	ExecRestorePosCustom;
+
+	ExplainCustomScan_function		ExplainCustomScan;
+} CustomProvider;
+
+/* Flags of CustomScan */
+
+/*
+ * CUSTOM__SUPPORT_MARK_RESTORE informs optimizer this custom scan provider
+ * support ExecCustomMarkPos and ExecCustomRestrPos callbacks.
+ */
+#define CUSTOM__SUPPORT_MARK_RESTORE			0x0001
+
+/*
+ * CUSTOM__SUPPORT_BACKWARD_SCAN informs optimizer this custom scan provider
+ * is designed to support backward scan.
+ */
+#define CUSTOM__SUPPORT_BACKWARD_SCAN			0x0002
+
+/*
+ * Registration and lookup custom execution provider
+ */
+extern void register_custom_provider(const CustomProvider *provider);
+
+extern CustomProvider *get_custom_provider(const char *custom_name);
+
+/*
+ * General executor code
+ */
+extern CustomScanState *ExecInitCustomScan(CustomScan *csstate,
+										   EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomScan(CustomScanState *csstate);
+extern Node *MultiExecCustomScan(CustomScanState *csstate);
+extern void ExecEndCustomScan(CustomScanState *csstate);
+
+extern void ExecReScanCustomScan(CustomScanState *csstate);
+extern void ExecCustomMarkPos(CustomScanState *csstate);
+extern void ExecCustomRestrPos(CustomScanState *csstate);
+
+#endif	/* NODECUSTOM_H */
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 2a4b41d..73424f5 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -93,4 +93,8 @@ extern int	bms_first_member(Bitmapset *a);
 /* support for hashtables using Bitmapsets as keys: */
 extern uint32 bms_hash_value(const Bitmapset *a);
 
+/* support for string representation */
+extern char *bms_to_string(Bitmapset *a);
+extern Bitmapset *bms_from_string(const char *a);
+
 #endif   /* BITMAPSET_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 3b430e0..db4176c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1494,6 +1494,23 @@ typedef struct ForeignScanState
 	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
 } ForeignScanState;
 
+/* ----------------
+ *	 CustomScanState information
+ *
+ *		CustomScan nodes are used to scan various relations using custom
+ *		logic.
+ * ----------------
+ */
+typedef struct CustomScanState
+{
+	ScanState	ss;
+
+	/* use struct pointer to avoid including nodeCustom.h here */
+	struct CustomProvider *custom_provider;
+	int			custom_flags;
+	void	   *custom_state;
+} CustomScanState;
+
 /* ----------------------------------------------------------------
  *				 Join State Information
  * ----------------------------------------------------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 78368c6..8f00a6b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -62,6 +62,7 @@ typedef enum NodeTag
 	T_CteScan,
 	T_WorkTableScan,
 	T_ForeignScan,
+	T_CustomScan,
 	T_Join,
 	T_NestLoop,
 	T_MergeJoin,
@@ -107,6 +108,7 @@ typedef enum NodeTag
 	T_CteScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
+	T_CustomScanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
@@ -224,6 +226,7 @@ typedef enum NodeTag
 	T_HashPath,
 	T_TidPath,
 	T_ForeignPath,
+	T_CustomPath,
 	T_AppendPath,
 	T_MergeAppendPath,
 	T_ResultPath,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 44ea0b7..936591b 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -483,6 +483,22 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *		CustomScan node
+ * ----------------
+ */
+typedef struct CustomScan
+{
+	Scan		scan;
+
+	const char *custom_name;		/* name of custom scan provider */
+	int			custom_flags;		/* a set of CUSTOM__* flags */
+	List	   *custom_private;		/* private data for CSP  */
+	List	   *custom_exprs;		/* expressions that CSP may execute */
+
+	Plan	   *subqry_plan;		/* valid, if RTE_SUBQUERY */
+	Node	   *funcexpr;			/* valid, if RTE_FUNCTION */
+} CustomScan;
 
 /*
  * ==========
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7918537..b71c7ca 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -134,6 +134,7 @@ typedef struct Expr
 #define    INNER_VAR		65000		/* reference to inner subplan */
 #define    OUTER_VAR		65001		/* reference to outer subplan */
 #define    INDEX_VAR		65002		/* reference to index column */
+#define    CUSTOM_VAR		65003		/* reference to custom column */
 
 #define IS_SPECIAL_VARNO(varno)		((varno) >= INNER_VAR)
 
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index a2853fb..55fa8aa 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -867,6 +867,22 @@ typedef struct ForeignPath
 } ForeignPath;
 
 /*
+ * CustomPath represents a scan using custom logic
+ *
+ * custom_name is the identifier of custom scan provider when it was
+ * registered. custom_flags is a set of CUSTOM__* bits to control its
+ * behavior. custom_private allows extension to store its private data
+ * but has to be safe for copyObject().
+ */
+typedef struct CustomPath
+{
+	Path		path;
+	const char *custom_name;		/* name of custom scan provider */
+	int			custom_flags;		/* CUSTOM__* flags in nodeCustom.h */
+	List	   *custom_private;		/* can be used for private data */
+} CustomPath;
+
+/*
  * AppendPath represents an Append plan, ie, successive execution of
  * several member plans.
  *
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 444ab740..a2873ec 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -145,6 +145,9 @@ extern void final_cost_hashjoin(PlannerInfo *root, HashPath *path,
 extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan);
 extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root);
 extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root);
+extern void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
+						  ParamPathInfo *param_info,
+						  QualCost *qpqual_cost);
 extern void compute_semi_anti_join_factors(PlannerInfo *root,
 							   RelOptInfo *outerrel,
 							   RelOptInfo *innerrel,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 9686229..1225970 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -82,6 +82,16 @@ extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
 						List *pathkeys,
 						Relids required_outer,
 						List *fdw_private);
+extern CustomPath *create_customscan_path(PlannerInfo *root,
+										  RelOptInfo *baserel,
+										  double rows,
+										  Cost startup_cost,
+										  Cost total_cost,
+										  List *pathkeys,
+										  Relids required_outer,
+										  const char *custom_name,
+										  uint32 custom_flags,
+										  List *custom_private);
 
 extern Relids calc_nestloop_required_outer(Path *outer_path, Path *inner_path);
 extern Relids calc_non_nestloop_required_outer(Path *outer_path, Path *inner_path);
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 9ef93c7..882baf6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -29,6 +29,31 @@ typedef RelOptInfo *(*join_search_hook_type) (PlannerInfo *root,
 														  List *initial_rels);
 extern PGDLLIMPORT join_search_hook_type join_search_hook;
 
+/* Hook for plugins to add custom scan path, in addition to default ones */
+typedef void (*add_scan_path_hook_type)(PlannerInfo *root,
+										RelOptInfo *baserel,
+										RangeTblEntry *rte);
+extern PGDLLIMPORT add_scan_path_hook_type add_scan_path_hook;
+
+#define add_custom_scan_paths(root,baserel,rte)				\
+	do {														\
+		if (add_scan_path_hook)										\
+			(*add_scan_path_hook)((root),(baserel),(rte));			\
+	} while(0)
+
+/* Hook for plugins to add custom join path, in addition to default ones */
+typedef void (*add_join_path_hook_type)(PlannerInfo *root,
+										RelOptInfo *joinrel,
+										RelOptInfo *outerrel,
+										RelOptInfo *innerrel,
+										JoinType jointype,
+										SpecialJoinInfo *sjinfo,
+										List *restrictlist,
+										List *mergeclause_list,
+										SemiAntiJoinFactors *semifactors,
+										Relids param_source_rels,
+										Relids extra_lateral_rels);
+extern PGDLLIMPORT add_join_path_hook_type add_join_path_hook;
 
 extern RelOptInfo *make_one_rel(PlannerInfo *root, List *joinlist);
 extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index ba7ae7c..13cfba8 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -127,6 +127,7 @@ extern List *remove_useless_joins(PlannerInfo *root, List *joinlist);
  * prototypes for plan/setrefs.c
  */
 extern Plan *set_plan_references(PlannerInfo *root, Plan *plan);
+extern void fix_expr_common(PlannerInfo *root, Node *node);
 extern void fix_opfuncids(Node *node);
 extern void set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile
index d5935b6..9645025 100644
--- a/src/test/regress/GNUmakefile
+++ b/src/test/regress/GNUmakefile
@@ -90,6 +90,7 @@ regress_data_files = \
 
 install-tests: all install install-lib installdirs-tests
 	$(MAKE) -C $(top_builddir)/contrib/spi install
+	$(MAKE) -C $(top_builddir)/contrib/ctidscan install
 	for file in $(regress_data_files); do \
 	  $(INSTALL_DATA) $$file '$(DESTDIR)$(pkglibdir)/regress/'$$file || exit; \
 	done
@@ -98,9 +99,9 @@ installdirs-tests: installdirs
 	$(MKDIR_P)  $(patsubst $(srcdir)/%/,'$(DESTDIR)$(pkglibdir)/regress/%',$(sort $(dir $(regress_data_files))))
 
 
-# Get some extra C modules from contrib/spi and contrib/dummy_seclabel...
+# Get some extra C modules from contrib/spi, dummy_seclabel and ctidscan
 
-all: refint$(DLSUFFIX) autoinc$(DLSUFFIX) dummy_seclabel$(DLSUFFIX)
+all: refint$(DLSUFFIX) autoinc$(DLSUFFIX) dummy_seclabel$(DLSUFFIX) ctidscan$(DLSUFFIX)
 
 refint$(DLSUFFIX): $(top_builddir)/contrib/spi/refint$(DLSUFFIX)
 	cp $< $@
@@ -111,19 +112,27 @@ autoinc$(DLSUFFIX): $(top_builddir)/contrib/spi/autoinc$(DLSUFFIX)
 dummy_seclabel$(DLSUFFIX): $(top_builddir)/contrib/dummy_seclabel/dummy_seclabel$(DLSUFFIX)
 	cp $< $@
 
+ctidscan$(DLSUFFIX): $(top_builddir)/contrib/ctidscan/ctidscan$(DLSUFFIX)
+	cp $< $@
+
 $(top_builddir)/contrib/spi/refint$(DLSUFFIX): | submake-contrib-spi ;
 
 $(top_builddir)/contrib/spi/autoinc$(DLSUFFIX): | submake-contrib-spi ;
 
 $(top_builddir)/contrib/dummy_seclabel/dummy_seclabel$(DLSUFFIX): | submake-contrib-dummy_seclabel ;
 
+$(top_builddir)/contrib/ctidscan/ctidscan$(DLSUFFIX): | submake-contrib-ctidscan
+
 submake-contrib-spi:
 	$(MAKE) -C $(top_builddir)/contrib/spi
 
 submake-contrib-dummy_seclabel:
 	$(MAKE) -C $(top_builddir)/contrib/dummy_seclabel
 
-.PHONY: submake-contrib-spi submake-contrib-dummy_seclabel
+submake-contrib-ctidscan:
+	$(MAKE) -C $(top_builddir)/contrib/ctidscan
+
+.PHONY: submake-contrib-spi submake-contrib-dummy_seclabel submake-contrib-ctidscan
 
 # Tablespace setup
 
diff --git a/src/test/regress/input/custom_scan.source b/src/test/regress/input/custom_scan.source
new file mode 100644
index 0000000..1ad0e7a
--- /dev/null
+++ b/src/test/regress/input/custom_scan.source
@@ -0,0 +1,49 @@
+--
+-- Regression Tests for Custom Scan APIs
+--
+
+-- construction of test data
+SET client_min_messages TO 'warning';
+
+CREATE SCHEMA regtest_custom_scan;
+
+SET search_path TO regtest_custom_scan, public;
+
+CREATE TABLE t1 (
+    a   int primary key,
+    b   text
+);
+INSERT INTO t1 (SELECT s, md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t1;
+
+CREATE TABLE t2 (
+    x   int primary key,
+    y   text
+);
+INSERT INTO t2 (SELECT s, md5(s::text)||md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t2;
+
+RESET client_min_messages;
+
+--
+-- Check Plans if no special extension is loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+
+LOAD '@libdir@/ctidscan@DLSUFFIX@';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+
+SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+
+-- Test creanup
+DROP SCHEMA regtest_custom_scan CASCADE;
\ No newline at end of file
diff --git a/src/test/regress/output/custom_scan.source b/src/test/regress/output/custom_scan.source
new file mode 100644
index 0000000..09c1bda
--- /dev/null
+++ b/src/test/regress/output/custom_scan.source
@@ -0,0 +1,290 @@
+--
+-- Regression Tests for Custom Scan APIs
+--
+-- construction of test data
+SET client_min_messages TO 'warning';
+CREATE SCHEMA regtest_custom_scan;
+SET search_path TO regtest_custom_scan, public;
+CREATE TABLE t1 (
+    a   int primary key,
+    b   text
+);
+INSERT INTO t1 (SELECT s, md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t1;
+CREATE TABLE t2 (
+    x   int primary key,
+    y   text
+);
+INSERT INTO t2 (SELECT s, md5(s::text)||md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t2;
+RESET client_min_messages;
+--
+-- Check Plans if no special extension is loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+           QUERY PLAN           
+--------------------------------
+ Index Scan using t1_pkey on t1
+   Index Cond: (a = 40)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+           QUERY PLAN           
+--------------------------------
+ Seq Scan on t1
+   Filter: (b ~~ '%789%'::text)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+             QUERY PLAN             
+------------------------------------
+ Tid Scan on t1
+   TID Cond: (ctid = '(2,10)'::tid)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Seq Scan on t1
+   Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(2 rows)
+
+LOAD '@libdir@/ctidscan@DLSUFFIX@';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+           QUERY PLAN           
+--------------------------------
+ Index Scan using t1_pkey on t1
+   Index Cond: (a = 40)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+           QUERY PLAN           
+--------------------------------
+ Seq Scan on t1
+   Filter: (b ~~ '%789%'::text)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+             QUERY PLAN             
+------------------------------------
+ Tid Scan on t1
+   TID Cond: (ctid = '(2,10)'::tid)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+   Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+                  QUERY PLAN                  
+----------------------------------------------
+ Merge Join
+   Merge Cond: (t1.ctid = t2.ctid)
+   ->  Sort
+         Sort Key: t1.ctid
+         ->  Custom Scan (ctidscan) on t1
+               Filter: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom Scan (ctidscan) on t2
+               Filter: (ctid > '(1,75)'::tid)
+(10 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid;
+  ctid   |  a  |                b                 
+---------+-----+----------------------------------
+ (0,1)   |   1 | c4ca4238a0b923820dcc509a6f75849b
+ (0,2)   |   2 | c81e728d9d4c2f636f067f89cc14862c
+ (0,3)   |   3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+ (0,4)   |   4 | a87ff679a2f3e71d9181a67b7542122c
+ (0,5)   |   5 | e4da3b7fbbce2345d7772b0674a318d5
+ (0,6)   |   6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ (0,7)   |   7 | 8f14e45fceea167a5a36dedd4bea2543
+ (0,8)   |   8 | c9f0f895fb98ab9159f51fd0297e236d
+ (0,9)   |   9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ (0,10)  |  10 | d3d9446802a44259755d38e6d163e820
+ (0,11)  |  11 | 6512bd43d9caa6e02c990b0a82652dca
+ (0,12)  |  12 | c20ad4d76fe97759aa27a0c99bff6710
+ (0,13)  |  13 | c51ce410c124a10e0db5e4b97fc2af39
+ (0,14)  |  14 | aab3238922bcc25a6f606eb525ffdc56
+ (0,15)  |  15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
+ (0,16)  |  16 | c74d97b01eae257e44aa9d5bade97baf
+ (0,17)  |  17 | 70efdf2ec9b086079795c442636b55fb
+ (0,18)  |  18 | 6f4922f45568161a8cdf4ad2299f6d23
+ (0,19)  |  19 | 1f0e3dad99908345f7439f8ffabdffc4
+ (0,20)  |  20 | 98f13708210194c475687be6106a3b84
+ (0,21)  |  21 | 3c59dc048e8850243be8079a5c74d079
+ (0,22)  |  22 | b6d767d2f8ed5d21a44b0e5886680cb9
+ (0,23)  |  23 | 37693cfc748049e45d87b8c7d8b9aacd
+ (0,24)  |  24 | 1ff1de774005f8da13f42943881c655f
+ (0,25)  |  25 | 8e296a067a37563370ded05f5a3bf3ec
+ (0,26)  |  26 | 4e732ced3463d06de0ca9a15b6153677
+ (0,27)  |  27 | 02e74f10e0327ad868d138f2b4fdd6f0
+ (0,28)  |  28 | 33e75ff09dd601bbe69f351039152189
+ (0,29)  |  29 | 6ea9ab1baa0efb9e19094440c317e21b
+ (0,30)  |  30 | 34173cb38f07f89ddbebc2ac9128303f
+ (0,31)  |  31 | c16a5320fa475530d9583c34fd356ef5
+ (0,32)  |  32 | 6364d3f0f495b6ab9dcf8d3b5c6e0b01
+ (0,33)  |  33 | 182be0c5cdcd5072bb1864cdee4d3d6e
+ (0,34)  |  34 | e369853df766fa44e1ed0ff613f563bd
+ (0,35)  |  35 | 1c383cd30b7c298ab50293adfecb7b18
+ (0,36)  |  36 | 19ca14e7ea6328a42e0eb13d585e4c22
+ (0,37)  |  37 | a5bfc9e07964f8dddeb95fc584cd965d
+ (0,38)  |  38 | a5771bce93e200c36f7cd9dfd0e5deaa
+ (0,39)  |  39 | d67d8ab4f4c10bf22aa353e27879133c
+ (0,40)  |  40 | d645920e395fedad7bbbed0eca3fe2e0
+ (0,41)  |  41 | 3416a75f4cea9109507cacd8e2f2aefc
+ (0,42)  |  42 | a1d0c6e83f027327d8461063f4ac58a6
+ (0,43)  |  43 | 17e62166fc8586dfa4d1bc0e1742c08b
+ (0,44)  |  44 | f7177163c833dff4b38fc8d2872f1ec6
+ (0,45)  |  45 | 6c8349cc7260ae62e3b1396831a8398f
+ (0,46)  |  46 | d9d4f495e875a2e075a1a4a6e1b9770f
+ (0,47)  |  47 | 67c6a1e7ce56d3d6fa748ab6d9af3fd7
+ (0,48)  |  48 | 642e92efb79421734881b53e1e1b18b6
+ (0,49)  |  49 | f457c545a9ded88f18ecee47145a72c0
+ (0,50)  |  50 | c0c7c76d30bd3dcaefc96f40275bdc0a
+ (0,51)  |  51 | 2838023a778dfaecdc212708f721b788
+ (0,52)  |  52 | 9a1158154dfa42caddbd0694a4e9bdc8
+ (0,53)  |  53 | d82c8d1619ad8176d665453cfb2e55f0
+ (0,54)  |  54 | a684eceee76fc522773286a895bc8436
+ (0,55)  |  55 | b53b3a3d6ab90ce0268229151c9bde11
+ (0,56)  |  56 | 9f61408e3afb633e50cdf1b20de6f466
+ (0,57)  |  57 | 72b32a1f754ba1c09b3695e0cb6cde7f
+ (0,58)  |  58 | 66f041e16a60928b05a7e228a89c3799
+ (0,59)  |  59 | 093f65e080a295f8076b1c5722a46aa2
+ (0,60)  |  60 | 072b030ba126b2f4b2374f342be9ed44
+ (0,61)  |  61 | 7f39f8317fbdb1988ef4c628eba02591
+ (0,62)  |  62 | 44f683a84163b3523afe57c2e008bc8c
+ (0,63)  |  63 | 03afdbd66e7929b125f8597834fa83a4
+ (0,64)  |  64 | ea5d2f1c4608232e07d3aa3d998e5135
+ (0,65)  |  65 | fc490ca45c00b1249bbe3554a4fdf6fb
+ (0,66)  |  66 | 3295c76acbf4caaed33c36b1b5fc2cb1
+ (0,67)  |  67 | 735b90b4568125ed6c3f678819b6e058
+ (0,68)  |  68 | a3f390d88e4c41f2747bfa2f1b5f87db
+ (0,69)  |  69 | 14bfa6bb14875e45bba028a21ed38046
+ (0,70)  |  70 | 7cbbc409ec990f19c78c75bd1e06f215
+ (0,71)  |  71 | e2c420d928d4bf8ce0ff2ec19b371514
+ (0,72)  |  72 | 32bb90e8976aab5298d5da10fe66f21d
+ (0,73)  |  73 | d2ddea18f00665ce8623e36bd4e3c7c5
+ (0,74)  |  74 | ad61ab143223efbc24c7d2583be69251
+ (0,75)  |  75 | d09bf41544a3365a46c9077ebb5e35c3
+ (0,76)  |  76 | fbd7939d674997cdb4692d34de8633c4
+ (0,77)  |  77 | 28dd2c7955ce926456240b2ff0100bde
+ (0,78)  |  78 | 35f4a8d465e6e1edc05f3d8ab658c551
+ (0,79)  |  79 | d1fe173d08e959397adf34b1d77e88d7
+ (0,80)  |  80 | f033ab37c30201f73f142449d037028d
+ (0,81)  |  81 | 43ec517d68b6edd3015b3edc9a11367b
+ (0,82)  |  82 | 9778d5d219c5080b9a6a17bef029331c
+ (0,83)  |  83 | fe9fc289c3ff0af142b6d3bead98a923
+ (0,84)  |  84 | 68d30a9594728bc39aa24be94b319d21
+ (0,85)  |  85 | 3ef815416f775098fe977004015c6193
+ (0,86)  |  86 | 93db85ed909c13838ff95ccfa94cebd9
+ (0,87)  |  87 | c7e1249ffc03eb9ded908c236bd1996d
+ (0,88)  |  88 | 2a38a4a9316c49e5a833517c45d31070
+ (0,89)  |  89 | 7647966b7343c29048673252e490f736
+ (0,90)  |  90 | 8613985ec49eb8f757ae6439e879bb2a
+ (0,91)  |  91 | 54229abfcfa5649e7003b83dd4755294
+ (0,92)  |  92 | 92cc227532d17e56e07902b254dfad10
+ (0,93)  |  93 | 98dce83da57b0395e163467c9dae521b
+ (0,94)  |  94 | f4b9ec30ad9f68f89b29639786cb62ef
+ (0,95)  |  95 | 812b4ba287f5ee0bc9d43bbf5bbe87fb
+ (0,96)  |  96 | 26657d5ff9020d2abefe558796b99584
+ (0,97)  |  97 | e2ef524fbf3d9fe611d5a8e90fefdc9c
+ (0,98)  |  98 | ed3d2c21991e3bef5e069713af9fa6ca
+ (0,99)  |  99 | ac627ab1ccbdb62ec96e702f07f6425b
+ (0,100) | 100 | f899139df5e1059396431415e770c6dd
+ (0,101) | 101 | 38b3eff8baf56627478ec76a704e9b52
+ (0,102) | 102 | ec8956637a99787bd197eacd77acce5e
+ (0,103) | 103 | 6974ce5ac660610b44d9b9fed0ff9548
+ (0,104) | 104 | c9e1074f5b3f9fc8ea15d152add07294
+ (0,105) | 105 | 65b9eea6e1cc6bb9f0cd2a47751a186f
+ (0,106) | 106 | f0935e4cd5920aa6c7c996a5ee53a70f
+ (0,107) | 107 | a97da629b098b75c294dffdc3e463904
+ (0,108) | 108 | a3c65c2974270fd093ee8a9bf8ae7d0b
+ (0,109) | 109 | 2723d092b63885e0d7c260cc007e8b9d
+ (0,110) | 110 | 5f93f983524def3dca464469d2cf9f3e
+ (0,111) | 111 | 698d51a19d8a121ce581499d7b701668
+ (0,112) | 112 | 7f6ffaa6bb0b408017b62254211691b5
+ (0,113) | 113 | 73278a4a86960eeb576a8fd4c9ec6997
+ (0,114) | 114 | 5fd0b37cd7dbbb00f97ba6ce92bf5add
+ (0,115) | 115 | 2b44928ae11fb9384c4cf38708677c48
+ (0,116) | 116 | c45147dee729311ef5b5c3003946c48f
+ (0,117) | 117 | eb160de1de89d9058fcb0b968dbbbd68
+ (0,118) | 118 | 5ef059938ba799aaa845e1c2e8a762bd
+ (0,119) | 119 | 07e1cd7dca89a1678042477183b7ac3f
+ (0,120) | 120 | da4fb5c6e93e74d3df8527599fa62642
+ (1,1)   | 121 | 4c56ff4ce4aaf9573aa5dff913df997a
+ (1,2)   | 122 | a0a080f42e6f13b3a2df133f073095dd
+ (1,3)   | 123 | 202cb962ac59075b964b07152d234b70
+ (1,4)   | 124 | c8ffe9a587b126f152ed3d89a146b445
+ (1,5)   | 125 | 3def184ad8f4755ff269862ea77393dd
+ (1,6)   | 126 | 069059b7ef840f0c74a814ec9237b6ec
+ (1,7)   | 127 | ec5decca5ed3d6b8079e2e7e7bacc9f2
+ (1,8)   | 128 | 76dc611d6ebaafc66cc0879c71b5db5c
+ (1,9)   | 129 | d1f491a404d6854880943e5c3cd9ca25
+ (1,10)  | 130 | 9b8619251a19057cff70779273e95aa6
+ (1,11)  | 131 | 1afa34a7f984eeabdbb0a7d494132ee5
+ (1,12)  | 132 | 65ded5353c5ee48d0b7d48c591b8f430
+ (1,13)  | 133 | 9fc3d7152ba9336a670e36d0ed79bc43
+ (1,14)  | 134 | 02522a2b2726fb0a03bb19f2d8d9524d
+ (1,15)  | 135 | 7f1de29e6da19d22b51c68001e7e0e54
+ (1,16)  | 136 | 42a0e188f5033bc65bf8d78622277c4e
+ (1,17)  | 137 | 3988c7f88ebcb58c6ce932b957b6f332
+ (1,18)  | 138 | 013d407166ec4fa56eb1e1f8cbe183b9
+ (1,19)  | 139 | e00da03b685a0dd18fb6a08af0923de0
+(139 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid;
+ ctid | a | b 
+------+---+---
+(0 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+  ctid   |  a  |                b                 
+---------+-----+----------------------------------
+ (2,115) | 355 | 82cec96096d4281b7c95cd7e74623496
+ (2,116) | 356 | 6c524f9d5d7027454a783c841250ba71
+ (2,117) | 357 | fb7b9ffa5462084c5f4e7e85a093e6d7
+ (2,118) | 358 | aa942ab2bfa6ebda4840e7360ce6e7ef
+ (2,119) | 359 | c058f544c737782deacefa532d9add4c
+ (2,120) | 360 | e7b24b112a44fdd9ee93bdf998c6ca0e
+ (3,1)   | 361 | 52720e003547c70561bf5e03b95aa99f
+ (3,2)   | 362 | c3e878e27f52e2a57ace4d9a76fd9acf
+ (3,3)   | 363 | 00411460f7c92d2124a67ea0f4cb5f85
+ (3,4)   | 364 | bac9162b47c56fc8a4d2a519803d51b3
+ (3,5)   | 365 | 9be40cee5b0eee1462c82c6964087ff9
+ (3,6)   | 366 | 5ef698cd9fe650923ea331c15af3b160
+ (3,7)   | 367 | 05049e90fa4f5039a8cadc6acbb4b2cc
+ (3,8)   | 368 | cf004fdc76fa1a4f25f62e0eb5261ca3
+ (3,9)   | 369 | 0c74b7f78409a4022a2c4c5a5ca3ee19
+ (3,10)  | 370 | d709f38ef758b5066ef31b18039b8ce5
+(16 rows)
+
+SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+  ctid  |  a  |                b                 |  x  |                                y                                 
+--------+-----+----------------------------------+-----+------------------------------------------------------------------
+ (1,76) | 196 | 084b6fbb10729ed4da8c3d3f5a3ae7c9 | 157 | 6c4b761a28b734fe93831e3fb400ce876c4b761a28b734fe93831e3fb400ce87
+ (1,77) | 197 | 85d8ce590ad8981ca2c8286f79f59954 | 158 | 06409663226af2f3114485aa4e0a23b406409663226af2f3114485aa4e0a23b4
+ (1,78) | 198 | 0e65972dce68dad4d52d063967f0a705 | 159 | 140f6969d5213fd0ece03148e62e461e140f6969d5213fd0ece03148e62e461e
+ (1,79) | 199 | 84d9ee44e457ddef7f2c4f25dc8fa865 | 160 | b73ce398c39f506af761d2277d853a92b73ce398c39f506af761d2277d853a92
+ (1,80) | 200 | 3644a684f98ea8fe223c713b77189a77 | 161 | bd4c9ab730f5513206b999ec0d90d1fbbd4c9ab730f5513206b999ec0d90d1fb
+ (1,81) | 201 | 757b505cfd34c64c85ca5b5690ee5293 | 162 | 82aa4b0af34c2313a562076992e50aa382aa4b0af34c2313a562076992e50aa3
+ (2,1)  | 241 | f340f1b1f65b6df5b5e3f94d95b11daf | 163 | 0777d5c17d4066b82ab86dff8a46af6f0777d5c17d4066b82ab86dff8a46af6f
+ (2,2)  | 242 | e4a6222cdb5b34375400904f03d8e6a5 | 164 | fa7cdfad1a5aaf8370ebeda47a1ff1c3fa7cdfad1a5aaf8370ebeda47a1ff1c3
+ (2,3)  | 243 | cb70ab375662576bd1ac5aaf16b3fca4 | 165 | 9766527f2b5d3e95d4a733fcfb77bd7e9766527f2b5d3e95d4a733fcfb77bd7e
+ (2,4)  | 244 | 9188905e74c28e489b44e954ec0b9bca | 166 | 7e7757b1e12abcb736ab9a754ffb617a7e7757b1e12abcb736ab9a754ffb617a
+ (2,5)  | 245 | 0266e33d3f546cb5436a10798e657d97 | 167 | 5878a7ab84fb43402106c575658472fa5878a7ab84fb43402106c575658472fa
+ (2,6)  | 246 | 38db3aed920cf82ab059bfccbd02be6a | 168 | 006f52e9102a8d3be2fe5614f42ba989006f52e9102a8d3be2fe5614f42ba989
+ (2,7)  | 247 | 3cec07e9ba5f5bb252d13f5f431e4bbb | 169 | 3636638817772e42b59d74cff571fbb33636638817772e42b59d74cff571fbb3
+ (2,8)  | 248 | 621bf66ddb7c962aa0d22ac97d69b793 | 170 | 149e9677a5989fd342ae44213df68868149e9677a5989fd342ae44213df68868
+ (2,9)  | 249 | 077e29b11be80ab57e1a2ecabb7da330 | 171 | a4a042cf4fd6bfb47701cbc8a1653adaa4a042cf4fd6bfb47701cbc8a1653ada
+(15 rows)
+
+-- Test creanup
+DROP SCHEMA regtest_custom_scan CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table t1
+drop cascades to table t2
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 1c1491c..fe81929 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ ignore: random
 # ----------
 # Another group of parallel tests
 # ----------
-test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete
+test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete custom_scan
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index c4d451a..a2287d8 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -91,6 +91,7 @@ test: btree_index
 test: hash_index
 test: update
 test: delete
+test: custom_scan
 test: namespace
 test: prepared_xacts
 test: privileges