 doc/src/sgml/contrib.sgml                       |   1 +
 doc/src/sgml/ctidscan.sgml                      |  52 ++
 doc/src/sgml/filelist.sgml                      |   1 +
 src/include/catalog/pg_operator.h               |   4 +
 src/test/modules/Makefile                       |   1 +
 src/test/modules/ctidscan/Makefile              |  16 +
 src/test/modules/ctidscan/ctidscan.c            | 828 ++++++++++++++++++++++++
 src/test/modules/ctidscan/expected/ctidscan.out | 588 +++++++++++++++++
 src/test/modules/ctidscan/sql/ctidscan.sql      | 132 ++++
 src/test/regress/expected/custom_scan.out       |   0
 src/test/regress/sql/custom_scan.sql            |  53 ++
 11 files changed, 1676 insertions(+)

diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..59eab97 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -109,6 +109,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
  &btree-gist;
  &chkpass;
  &citext;
+ &ctidscan;
  &cube;
  &dblink;
  &dict-int;
diff --git a/doc/src/sgml/ctidscan.sgml b/doc/src/sgml/ctidscan.sgml
new file mode 100644
index 0000000..fa21b0f
--- /dev/null
+++ b/doc/src/sgml/ctidscan.sgml
@@ -0,0 +1,52 @@
+<!-- doc/src/sgml/ctidscan.sgml  -->
+
+<sect1 id="ctidscan" xreflabel="ctidscan">
+ <title>ctidscan</title>
+
+ <indexterm zone="ctidscan">
+  <primary>ctidscan</primary>
+ </indexterm>
+
+ <para>
+  This module implements a custom-scan provider that utilizes inequality
+  operator that involves the <literal>ctid</literal> system column.
+ </para>
+
+ <para>
+  This module provides no SQL accessible interface. For installation,
+  all you need to do is just load the module to the server.
+
+  You can load it an individual session using:
+<programlisting>
+LOAD 'ctidscan';
+</programlisting>
+
+  or, you can also take more typical usage with extension preloading
+  using <xref linkend="guc-session-preload-libraries"> or
+  <xref linkend="guc-shared-preload-libraries"> in
+  <filename>postgresql.conf</>.
+
+  Then, planner may consider more cheap execution path if supplied query
+  involves above operators.
+ </para>
+ <variablelist>
+  <varlistentry>
+   <term>
+    <varname>enable_ctidscan</varname> (<type>bool</type>)
+    <indexterm>
+      <primary><varname>enable_ctidscan</> configuration parameter</primary>
+    </indexterm>
+   </term>
+   <listitem>
+    <para>
+     <varname>enable_ctidscan</varname> turns on/off functionality of
+     ctidscan custom-scan provider.
+     If turned off, it does not offer alternative scan path even if
+     supplied query is sufficient to run by ctidscan plan.
+     Its default is <literal>true</>.
+     Anybody can change using <command>SET</command> command.
+    </para>
+   </listitem>
+  </varlistentry>
+ </variablelist>
+</sect1>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index f03b72a..15d569e 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -107,6 +107,7 @@
 <!ENTITY btree-gist      SYSTEM "btree-gist.sgml">
 <!ENTITY chkpass         SYSTEM "chkpass.sgml">
 <!ENTITY citext          SYSTEM "citext.sgml">
+<!ENTITY ctidscan        SYSTEM "ctidscan.sgml">
 <!ENTITY cube            SYSTEM "cube.sgml">
 <!ENTITY dblink          SYSTEM "dblink.sgml">
 <!ENTITY dict-int        SYSTEM "dict-int.sgml">
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 88c737b..2ab15a5 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -161,15 +161,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/test/modules/Makefile b/src/test/modules/Makefile
index 93d93af..c288276 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -6,6 +6,7 @@ include $(top_builddir)/src/Makefile.global
 
 SUBDIRS = \
 		  commit_ts \
+		  ctidscan \
 		  worker_spi \
 		  dummy_seclabel \
 		  test_shm_mq \
diff --git a/src/test/modules/ctidscan/Makefile b/src/test/modules/ctidscan/Makefile
new file mode 100644
index 0000000..bbe280a
--- /dev/null
+++ b/src/test/modules/ctidscan/Makefile
@@ -0,0 +1,16 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+REGRESS = ctidscan
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/ctidscan
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/ctidscan/ctidscan.c b/src/test/modules/ctidscan/ctidscan.c
new file mode 100644
index 0000000..54f0c85
--- /dev/null
+++ b/src/test/modules/ctidscan/ctidscan.c
@@ -0,0 +1,828 @@
+/*
+ * ctidscan.c
+ *
+ * A custom-scan provide that utilizes ctid system column within
+ * inequality-operators, to skip block reads never referenced.
+ *
+ * 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 cheapest 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) 2014, 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 "commands/defrem.h"
+#include "commands/explain.h"
+#include "executor/executor.h"
+#include "executor/nodeCustom.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
+#include "optimizer/paths.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
+#include "optimizer/planmain.h"
+#include "optimizer/placeholder.h"
+#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
+#include "parser/parsetree.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/spccache.h"
+
+PG_MODULE_MAGIC;
+
+/*
+ * NOTE: We don't use any special data type to save the private data.
+ * All we want to save in private fields is expression-list that shall
+ * be adjusted by setrefs.c/subselect.c, so we put it on the custom_exprs
+ * of CustomScan structure, not custom_private field.
+ * Due to the interface contract, only expression nodes are allowed to put
+ * on the custom_exprs, and we have to pay attention the core backend may
+ * adjust expression items.
+ */
+
+/*
+ * CtidScanState - state object of ctidscan on executor.
+ * It has few additional internal state. The 'ctid_quals' has list of
+ * ExprState for inequality operators that involve ctid system column.
+ */
+typedef struct {
+	CustomScanState	css;
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CtidScanState;
+
+/* static variables */
+static bool		enable_ctidscan;
+static set_rel_pathlist_hook_type set_rel_pathlist_next = NULL;
+
+/* function declarations */
+void	_PG_init(void);
+
+static void SetCtidScanPath(PlannerInfo *root,
+							RelOptInfo *rel,
+							Index rti,
+							RangeTblEntry *rte);
+/* CustomPathMethods */
+static Plan *PlanCtidScanPath(PlannerInfo *root,
+							  RelOptInfo *rel,
+							  CustomPath *best_path,
+							  List *tlist,
+							  List *clauses);
+
+/* CustomScanMethods */
+static Node *CreateCtidScanState(CustomScan *custom_plan);
+
+/* CustomScanExecMethods */
+static void BeginCtidScan(CustomScanState *node, EState *estate, int eflags);
+static void ReScanCtidScan(CustomScanState *node);
+static TupleTableSlot *ExecCtidScan(CustomScanState *node);
+static void EndCtidScan(CustomScanState *node);
+static void ExplainCtidScan(CustomScanState *node, List *ancestors,
+							ExplainState *es);
+
+/* static table of custom-scan callbacks */
+static CustomPathMethods	ctidscan_path_methods = {
+	"ctidscan",				/* CustomName */
+	PlanCtidScanPath,		/* PlanCustomPath */
+	NULL,					/* TextOutCustomPath */
+};
+
+static CustomScanMethods	ctidscan_scan_methods = {
+	"ctidscan",				/* CustomName */
+	CreateCtidScanState,	/* CreateCustomScanState */
+	NULL,					/* TextOutCustomScan */
+};
+
+static CustomExecMethods	ctidscan_exec_methods = {
+	"ctidscan",				/* CustomName */
+	BeginCtidScan,			/* BeginCustomScan */
+	ExecCtidScan,			/* ExecCustomScan */
+	EndCtidScan,			/* EndCustomScan */
+	ReScanCtidScan,			/* ReScanCustomScan */
+	NULL,					/* MarkPosCustomScan */
+	NULL,					/* RestrPosCustomScan */
+	ExplainCtidScan,		/* ExplainCustomScan */
+};
+
+#define IsCTIDVar(node,rtindex)											\
+	((node) != NULL &&													\
+	 IsA((node), Var) &&												\
+	 ((Var *) (node))->varno == (rtindex) &&							\
+	 ((Var *) (node))->varattno == SelfItemPointerAttributeNumber &&	\
+	 ((Var *) (node))->varlevelsup == 0)
+
+/*
+ * IsCTidInequalExpr
+ *
+ * It checks whether the given restriction clauses enables to determine
+ * the zone to be scanned, or not.
+ */
+static bool
+IsCTidInequalExpr(RelOptInfo *rel, RestrictInfo *rinfo)
+{
+	if (is_opclause(rinfo->clause))
+	{
+		OpExpr *op = (OpExpr *) rinfo->clause;
+		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 false;
+
+		if (list_length(op->args) != 2)
+			return false;	/* should not happen */
+
+		arg1 = linitial(op->args);
+		arg2 = lsecond(op->args);
+
+		if (IsCTIDVar(arg1, rel->relid))
+			other = arg2;
+		else if (IsCTIDVar(arg2, rel->relid))
+			other = arg1;
+		else
+			return false;
+
+		if (exprType(other) != TIDOID)
+			return false;	/* should not happen */
+
+		/* The other argument must be a pseudoconstant */
+		if (!is_pseudo_constant_clause(other))
+			return false;
+
+		return true;
+	}
+	return false;
+}
+
+/*
+ * 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)
+{
+	Path	   *path = &cpath->path;
+	List	   *ctid_quals = 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_seq_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 (path->param_info)
+		path->rows = path->param_info->ppi_rows;
+	else
+		path->rows = baserel->rows;
+
+	/* Estimate how many tuples we may retrieve */
+	ItemPointerSet(&ip_min, 0, 0);
+	ItemPointerSet(&ip_max, MaxBlockNumber, MaxOffsetNumber);
+	foreach (lc, ctid_quals)
+	{
+		RestrictInfo *rinfo = lfirst(lc);
+		OpExpr	   *op = (OpExpr *) rinfo->clause;
+		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);
+			if (!OidIsValid(opno))
+				elog(ERROR, "could not find commutator for operator %u",
+					 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 from scan-size estimation
+			 * perspective.
+			 */
+			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", opno);
+					break;
+			}
+		}
+		else if (IsA(other, Param))
+		{
+			ItemPointerData	temp;
+
+			/*
+			 * Just a "very" rough estimation. We cannot know exact value
+			 * of parameter until execution stage, so assume roughly
+			 * 33% blocks shall be read by an operator.
+			 */
+			switch (opno)
+			{
+				case TIDLessOperator:
+				case TIDLessEqualOperator:
+					ItemPointerSetBlockNumber(&temp, baserel->pages * 2 / 3);
+					ItemPointerSetOffsetNumber(&temp, 0);
+					if (ItemPointerCompare(&temp, &ip_max) < 0)
+						ItemPointerCopy(&temp, &ip_max);
+					has_max_val = true;
+					break;
+				case TIDGreaterOperator:
+				case TIDGreaterEqualOperator:
+					ItemPointerSetBlockNumber(&temp, baserel->pages / 3);
+					ItemPointerSetOffsetNumber(&temp, 0);
+					if (ItemPointerCompare(&temp, &ip_max) > 0)
+						ItemPointerCopy(&temp, &ip_max);
+					has_min_val = true;
+					break;
+				default:
+					elog(ERROR, "unexpected operator code: %u", op->opno);
+					break;
+			}
+		}
+	}
+
+	/* estimate min/max block numbers */
+	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 = Max(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 = Max(bnum_max - bnum_min, 0) + 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 = Max(bnum_max - bnum_min, 0) + 1;
+	}
+	else
+	{
+		/*
+		 * Just a rough estimation. We assume half of records shall be
+		 * read using this restriction clause, but indeterministic until
+		 * executor run it actually.
+		 */
+		num_pages = Max((baserel->pages + 1) / 2, 1);
+	}
+	ntuples = baserel->tuples * (double) num_pages / (double) baserel->pages;
+
+	/*
+	 * The TID qual expressions will be computed once, any other baserestrict
+	 * quals once per retrieved tuple.
+	 */
+	cost_qual_eval(&ctid_qual_cost, ctid_quals, root);
+
+	/* fetch estimated page cost for tablespace containing table */
+	get_tablespace_page_costs(baserel->reltablespace,
+							  NULL,
+							  &spc_seq_page_cost);
+
+	/* Disk costs */
+	run_cost += spc_seq_page_cost * num_pages;
+
+	/* CPU costs; logic originates get_restriction_qual_cost() */
+	if (path->param_info)
+	{
+		/* Include costs of pushed-down clauses */
+		cost_qual_eval(&qpqual_cost, path->param_info->ppi_clauses, root);
+
+		qpqual_cost.startup += baserel->baserestrictcost.startup;
+		qpqual_cost.per_tuple += baserel->baserestrictcost.per_tuple;
+	}
+	else
+		qpqual_cost = baserel->baserestrictcost;
+
+	/*
+	 * We don't eliminate the cost for the ctid inequality operators,
+	 * because it is still subset of qpquals and needs to be evaluated
+	 * on run time to check offset-number (even we can skip blocks that
+	 * are never referenced obviously).
+	 */
+	startup_cost += qpqual_cost.startup;
+	cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
+	run_cost += cpu_per_tuple * ntuples;
+
+	/* Just a spice to prefer SeqScan, if inequality operators tooks
+	 * obvious out-of-range thus its cost is identical with SeqScan.
+	 */
+	startup_cost += 1.0;
+
+	path->startup_cost = startup_cost;
+	path->total_cost = startup_cost + run_cost;
+}
+
+/*
+ * SetCtidScanPath - entrypoint of the series of custom-scan execution.
+ * It adds CustomPath if referenced relation has inequality expressions on
+ * the ctid system column.
+ */
+static void
+SetCtidScanPath(PlannerInfo *root, RelOptInfo *baserel,
+				Index rtindex, RangeTblEntry *rte)
+{
+	char			relkind;
+	ListCell	   *lc;
+	List		   *ctid_quals = NIL;
+
+	/* only plain relations are supported */
+	if (rte->rtekind != RTE_RELATION)
+		return;
+	relkind = get_rel_relkind(rte->relid);
+	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_MATVIEW &&
+		relkind != RELKIND_TOASTVALUE)
+		return;
+
+	/*
+	 * NOTE: Unlike built-in execution path, always we can have core path
+	 * even though ctid scan is not available. So, simply, we don't add
+	 * any paths, instead of adding disable_cost.
+	 */
+	if (!enable_ctidscan)
+		return;
+
+	/* walk on the restrict info */
+	foreach (lc, baserel->baserestrictinfo)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+		Assert(IsA(rinfo, RestrictInfo));
+		if (IsCTidInequalExpr(baserel, rinfo))
+			ctid_quals = lappend(ctid_quals, rinfo);
+	}
+
+	/*
+	 * 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 (ctid_quals != NIL)
+	{
+		CustomPath *cpath;
+		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 = palloc0(sizeof(CustomPath));
+		cpath->path.type = T_CustomPath;
+		cpath->path.pathtype = T_CustomScan;
+		cpath->path.parent = baserel;
+		cpath->path.param_info
+			= get_baserel_parampathinfo(root, baserel, required_outer);
+		cpath->flags = CUSTOMPATH_SUPPORT_BACKWARD_SCAN;
+		cpath->custom_private = ctid_quals;
+		cpath->methods = &ctidscan_path_methods;
+
+		CTidEstimateCosts(root, baserel, cpath);
+
+		add_path(baserel, &cpath->path);
+	}
+}
+
+/*
+ * PlanCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomScan type, according to the supplied
+ * CustomPath object.
+ */
+static Plan *
+PlanCtidScanPath(PlannerInfo *root,
+				 RelOptInfo *rel,
+				 CustomPath *best_path,
+				 List *tlist,
+				 List *clauses)
+{
+	CustomScan *cscan = makeNode(CustomScan);
+	List	   *scan_quals = extract_actual_clauses(clauses, false);
+	List	   *ctid_quals
+		= extract_actual_clauses(best_path->custom_private, false);
+
+	cscan->flags = best_path->flags;
+	cscan->methods = &ctidscan_scan_methods;
+
+	/* set scanrelid */
+	cscan->scan.scanrelid = rel->relid;
+	/* set targetlist as is  */
+	cscan->scan.plan.targetlist = tlist;
+	/* reduce RestrictInfo list to bare expressions */
+	cscan->scan.plan.qual = list_difference(scan_quals, ctid_quals);
+	/* set ctid related quals */
+	cscan->custom_exprs = ctid_quals;
+
+	return &cscan->scan.plan;
+}
+
+/*
+ * CreateCtidScanState - A method of CustomScan; that populate a custom
+ * object being delivered from CustomScanState type, according to the
+ * supplied CustomPath object.
+ */
+static Node *
+CreateCtidScanState(CustomScan *custom_plan)
+{
+	CtidScanState  *ctss = palloc0(sizeof(CtidScanState));
+
+	NodeSetTag(ctss, T_CustomScanState);
+	ctss->css.flags = custom_plan->flags;
+	ctss->css.methods = &ctidscan_exec_methods;
+
+	return (Node *)&ctss->css;
+}
+
+/*
+ * BeginCtidScan - A method of CustomScanState; that initializes
+ * the supplied CtidScanState object, at beginning of the executor.
+ */
+static void
+BeginCtidScan(CustomScanState *node, EState *estate, int eflags)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CustomScan	   *cscan = (CustomScan *) node->ss.ps.plan;
+
+	/*
+	 * In case of custom-scan provider that offers an alternative way
+	 * to scan a particular relation, most of the needed initialization,
+	 * like relation open or assignment of scan tuple-slot or projection
+	 * info, shall be done by the core implementation. So, all we need
+	 * to have is initialization of own local properties.
+	 */
+	ctss->ctid_quals = (List *)
+		ExecInitExpr((Expr *)cscan->custom_exprs, &node->ss.ps);
+}
+
+/*
+ * ReScanCtidScan - A method of CustomScanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomScanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+	HeapScanDesc	scan = ctss->css.ss.ss_currentScanDesc;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	Relation		relation = ctss->css.ss.ss_currentRelation;
+	ExprContext	   *econtext = ctss->css.ss.ps.ps_ExprContext;
+	ScanKeyData		keys[2];
+	bool			has_ubound = false;
+	bool			has_lbound = false;
+	ItemPointerData	ip_max;
+	ItemPointerData	ip_min;
+	ListCell	   *lc;
+
+	/* once close the existing scandesc, if any */
+	if (scan)
+	{
+		heap_endscan(scan);
+		scan = ctss->css.ss.ss_currentScanDesc = NULL;
+	}
+
+	/* 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);
+		Index			scanrelid;
+		Oid				opno;
+		ExprState	   *exstate;
+		ItemPointer		itemptr;
+		Datum			value;
+		bool			isnull;
+
+		scanrelid = ((Scan *)ctss->css.ss.ps.plan)->scanrelid;
+		if (IsCTIDVar(arg1, scanrelid))
+		{
+			exstate = (ExprState *) lsecond(fexstate->args);
+			opno = op->opno;
+		}
+		else if (IsCTIDVar(arg2, scanrelid))
+		{
+			exstate = (ExprState *) linitial(fexstate->args);
+			opno = get_commutator(op->opno);
+			if (!OidIsValid(opno))
+				elog(ERROR, "could not find commutator for operator %u",
+					 op->opno);
+		}
+		else
+			elog(ERROR, "could not identify CTID variable");
+
+		/* evaluate the CTID value */
+		value = ExecEvalExprSwitchContext(exstate,
+										  econtext,
+										  &isnull,
+										  NULL);
+		if (isnull)
+		{
+			/*
+			 * Whole of the restriction clauses chained 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.
+			 * In this case, scandesc is kept to NULL.
+			 */
+			return;
+		}
+
+		/*
+		 * NOTE: ScanKeyInit saves only pointers, so we have to keep the
+		 * ItemPointerData variable on somewhere available during the
+		 * query execution.
+		 */
+		itemptr = MemoryContextAlloc(estate->es_query_cxt,
+									 sizeof(ItemPointerData));
+		ItemPointerCopy((ItemPointer) DatumGetPointer(value), itemptr);
+
+		switch (opno)
+		{
+			case TIDLessOperator:
+				if (!has_ubound || ItemPointerCompare(itemptr, &ip_max) <= 0)
+				{
+					ScanKeyInit(&keys[0],
+								SelfItemPointerAttributeNumber,
+								BTLessStrategyNumber,
+								F_TIDLT,
+								PointerGetDatum(itemptr));
+					ItemPointerCopy(itemptr, &ip_max);
+					has_ubound = true;
+				}
+				break;
+
+			case TIDLessEqualOperator:
+				if (!has_ubound || ItemPointerCompare(itemptr, &ip_max) < 0)
+				{
+					ScanKeyInit(&keys[0],
+								SelfItemPointerAttributeNumber,
+								BTLessEqualStrategyNumber,
+								F_TIDLE,
+								PointerGetDatum(itemptr));
+					ItemPointerCopy(itemptr, &ip_max);
+					has_ubound = true;
+				}
+				break;
+
+
+			case TIDGreaterOperator:
+				if (!has_lbound || ItemPointerCompare(itemptr, &ip_min) >= 0)
+				{
+					ScanKeyInit(&keys[1],
+								SelfItemPointerAttributeNumber,
+								BTGreaterStrategyNumber,
+								F_TIDGT,
+								PointerGetDatum(itemptr));
+					ItemPointerCopy(itemptr, &ip_min);
+					has_lbound = true;
+				}
+				break;
+
+			case TIDGreaterEqualOperator:
+				if (!has_lbound || ItemPointerCompare(itemptr, &ip_min) > 0)
+				{
+					ScanKeyInit(&keys[1],
+								SelfItemPointerAttributeNumber,
+								BTGreaterEqualStrategyNumber,
+								F_TIDGE,
+								PointerGetDatum(itemptr));
+					ItemPointerCopy(itemptr, &ip_min);
+					has_lbound = true;
+				}
+				break;
+
+			default:
+				elog(ERROR, "unsupported operator");
+				break;
+		}
+	}
+	/* begin heapscan with the key above */
+	if (has_ubound && has_lbound)
+		scan = heap_beginscan(relation, estate->es_snapshot, 2, &keys[0]);
+	else if (has_ubound)
+		scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[0]);
+	else if (has_lbound)
+		scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[1]);
+	else
+		scan = heap_beginscan(relation, estate->es_snapshot, 0, NULL);
+
+	/* Seek the starting position, if possible */
+	if (direction == ForwardScanDirection && has_lbound)
+	{
+		BlockNumber	blknum = Min(BlockIdGetBlockNumber(&ip_min.ip_blkid) - 1,
+								 scan->rs_nblocks - 1);
+		heap_setscanlimits(scan, blknum, scan->rs_nblocks - blknum);
+	}
+	else if (direction == BackwardScanDirection && has_ubound)
+	{
+		BlockNumber	blknum = Min(BlockIdGetBlockNumber(&ip_max.ip_blkid),
+								 scan->rs_nblocks - 1);
+		heap_setscanlimits(scan, 0, blknum);
+	}
+	ctss->css.ss.ss_currentScanDesc = scan;
+}
+
+/*
+ * CTidAccessCustomScan
+ *
+ * Access method of ExecCtidScan below. 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 = (CtidScanState *) node;
+	HeapScanDesc	scan;
+	TupleTableSlot *slot;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	HeapTuple		tuple;
+
+	if (!ctss->css.ss.ss_currentScanDesc)
+		ReScanCtidScan(node);
+	scan = ctss->css.ss.ss_currentScanDesc;
+	Assert(scan != NULL);
+
+	/*
+	 * get the next tuple from the table
+	 */
+	tuple = heap_getnext(scan, direction);
+	if (!HeapTupleIsValid(tuple))
+		return NULL;
+
+	slot = ctss->css.ss.ss_ScanTupleSlot;
+	ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+	return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomScanState *node, TupleTableSlot *slot)
+{
+	return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomScanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomScanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) CTidAccessCustomScan,
+					(ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomScanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomScanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+
+	if (ctss->css.ss.ss_currentScanDesc)
+		heap_endscan(ctss->css.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplainCtidScan - A method of CustomScanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomScanState *node, List *ancestors, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CustomScan	   *cscan = (CustomScan *) ctss->css.ss.ps.plan;
+
+	/* logic copied from show_qual and show_expression */
+	if (cscan->custom_exprs)
+	{
+		bool	useprefix = es->verbose;
+		Node   *qual;
+		List   *context;
+		char   *exprstr;
+
+		/* Convert AND list to explicit AND */
+		qual = (Node *) make_ands_explicit(cscan->custom_exprs);
+
+		/* Set up deparsing context */
+		context = deparse_context_for_planstate((Node *)&node->ss.ps,
+												ancestors,
+												es->rtable,
+												es->rtable_names);
+
+		/* Deparse the expression */
+		exprstr = deparse_expression(qual, context, useprefix, false);
+
+		/* And add to es->str */
+		ExplainPropertyText("TID Range", exprstr, es);
+	}
+}
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	DefineCustomBoolVariable("enable_ctidscan",
+							 "Enables the planner's use of ctid-scan plans.",
+							 NULL,
+							 &enable_ctidscan,
+							 true,
+							 PGC_USERSET,
+							 GUC_NOT_IN_SAMPLE,
+							 NULL, NULL, NULL);
+
+	/* registration of the hook to add alternative path */
+	set_rel_pathlist_next = set_rel_pathlist_hook;
+	set_rel_pathlist_hook = SetCtidScanPath;
+}
diff --git a/src/test/modules/ctidscan/expected/ctidscan.out b/src/test/modules/ctidscan/expected/ctidscan.out
new file mode 100644
index 0000000..b16f088
--- /dev/null
+++ b/src/test/modules/ctidscan/expected/ctidscan.out
@@ -0,0 +1,588 @@
+--
+-- Regression Tests for CustomScan Interface with CtidScan Provider
+--
+-- construction of test data
+SET client_min_messages TO 'warning';
+SET SEED TO 0.20140702;
+CREATE SCHEMA regtest_custom_scan;
+SET search_path TO regtest_custom_scan, public;
+CREATE TABLE t1 (
+    a   int primary key,
+    b   float,
+    c   text
+);
+INSERT INTO t1 (SELECT i, ceil(random()*10000.0) / 1000.0, md5(i::text) FROM generate_series(1,40000) i);
+VACUUM ANALYZE t1;
+CREATE TABLE t2 (
+    x   int primary key,
+    y   float,
+    z   text
+);
+INSERT INTO t2 (SELECT i, ceil(random()*10000.0) / 1000.0, md5((-i)::text) FROM generate_series(201,40200) i);
+VACUUM ANALYZE t2;
+CREATE TABLE t3 (
+    a   int references t1(a),
+    x   int references t2(x)
+);
+INSERT INTO t3 (SELECT ceil(random() * 1000), ceil(random() * 1000) + 200 FROM generate_series(1,8000) i);
+RESET client_min_messages;
+set log_error_verbosity = verbose;
+--
+-- Check Plans if no special extensions are loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid;
+           QUERY PLAN            
+---------------------------------
+ Index Scan using t1_pkey on t1
+   Index Cond: (a = 40)
+   Filter: (ctid < '(6,0)'::tid)
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Seq Scan on t1
+   Filter: ((c ~~ '%789%'::text) AND (ctid < '(5,0)'::tid))
+(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)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE '(8,0)'::tid < ctid AND c like '%ab%';
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Seq Scan on t1
+   Filter: (('(8,0)'::tid < ctid) AND (c ~~ '%ab%'::text))
+(2 rows)
+
+EXPLAIN (costs on) SELECT * FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%';
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Seq Scan on t1  (cost=0.00..1013.00 rows=135 width=45)
+   Filter: (('(9999,99)'::tid > ctid) AND (c ~~ '%def%'::text))
+(2 rows)
+
+EXPLAIN (costs off)
+    SELECT * FROM t1, t2, t3
+    WHERE t1.a = t3.a AND t2.x = t3.x
+          AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+          AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid
+          AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+                                   QUERY PLAN                                   
+--------------------------------------------------------------------------------
+ Nested Loop
+   ->  Nested Loop
+         ->  Seq Scan on t3
+               Filter: ((ctid >= '(2,0)'::tid) AND (ctid <= '(5,0)'::tid))
+         ->  Index Scan using t1_pkey on t1
+               Index Cond: (a = t3.a)
+               Filter: ((ctid >= '(3,10)'::tid) AND (ctid <= '(10,9999)'::tid))
+   ->  Index Scan using t2_pkey on t2
+         Index Cond: (x = t3.x)
+         Filter: ((ctid >= '(4,9999)'::tid) AND (ctid <= '(8,0)'::tid))
+(10 rows)
+
+EXPLAIN (costs off, verbose)
+    SELECT count(*), ceil(b)
+    FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid
+    GROUP BY ceil(b);
+                                 QUERY PLAN                                 
+----------------------------------------------------------------------------
+ HashAggregate
+   Output: count(*), (ceil(b))
+   Group Key: ceil(t1.b)
+   ->  Seq Scan on regtest_custom_scan.t1
+         Output: ceil(b)
+         Filter: ((t1.ctid >= '(3,0)'::tid) AND (t1.ctid <= '(10,0)'::tid))
+(6 rows)
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid;
+           QUERY PLAN            
+---------------------------------
+ Index Scan using t1_pkey on t1
+   Index Cond: (a = 40)
+   Filter: (ctid < '(6,0)'::tid)
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid;
+             QUERY PLAN             
+------------------------------------
+ Custom Scan (ctidscan) on t1
+   Filter: (c ~~ '%789%'::text)
+   TID Range: (ctid < '(5,0)'::tid)
+(3 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
+   TID Range: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE '(8,0)'::tid < ctid AND c like '%ab%';
+             QUERY PLAN             
+------------------------------------
+ Custom Scan (ctidscan) on t1
+   Filter: (c ~~ '%ab%'::text)
+   TID Range: ('(8,0)'::tid < ctid)
+(3 rows)
+
+EXPLAIN (costs on) SELECT * FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%';	-- SeqScan is cheaper
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Seq Scan on t1  (cost=0.00..1013.00 rows=135 width=45)
+   Filter: (('(9999,99)'::tid > ctid) AND (c ~~ '%def%'::text))
+(2 rows)
+
+EXPLAIN (costs off)
+    SELECT * FROM t1, t2, t3
+    WHERE t1.a = t3.a AND t2.x = t3.x
+          AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+          AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid
+          AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+                                     QUERY PLAN                                     
+------------------------------------------------------------------------------------
+ Nested Loop
+   ->  Hash Join
+         Hash Cond: (t2.x = t3.x)
+         ->  Custom Scan (ctidscan) on t2
+               TID Range: ((ctid >= '(4,9999)'::tid) AND (ctid <= '(8,0)'::tid))
+         ->  Hash
+               ->  Custom Scan (ctidscan) on t3
+                     TID Range: ((ctid >= '(2,0)'::tid) AND (ctid <= '(5,0)'::tid))
+   ->  Index Scan using t1_pkey on t1
+         Index Cond: (a = t3.a)
+         Filter: ((ctid >= '(3,10)'::tid) AND (ctid <= '(10,9999)'::tid))
+(11 rows)
+
+EXPLAIN (costs off, verbose)
+    SELECT count(*), ceil(b)
+    FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid
+    GROUP BY ceil(b);
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ HashAggregate
+   Output: count(*), (ceil(b))
+   Group Key: ceil(t1.b)
+   ->  Custom Scan (ctidscan) on regtest_custom_scan.t1
+         Output: ceil(b)
+         TID Range: ((t1.ctid >= '(3,0)'::tid) AND (t1.ctid <= '(10,0)'::tid))
+(6 rows)
+
+--
+-- Run the query without EXPLAIN
+--
+SELECT ctid,* FROM t1 WHERE ctid <= '(1,20)'::tid ORDER BY ctid DESC LIMIT 25;
+  ctid  |  a  |   b   |                c                 
+--------+-----+-------+----------------------------------
+ (1,20) | 117 | 2.893 | eb160de1de89d9058fcb0b968dbbbd68
+ (1,19) | 116 |  4.68 | c45147dee729311ef5b5c3003946c48f
+ (1,18) | 115 | 3.107 | 2b44928ae11fb9384c4cf38708677c48
+ (1,17) | 114 | 0.675 | 5fd0b37cd7dbbb00f97ba6ce92bf5add
+ (1,16) | 113 | 8.544 | 73278a4a86960eeb576a8fd4c9ec6997
+ (1,15) | 112 | 4.443 | 7f6ffaa6bb0b408017b62254211691b5
+ (1,14) | 111 | 2.845 | 698d51a19d8a121ce581499d7b701668
+ (1,13) | 110 | 1.052 | 5f93f983524def3dca464469d2cf9f3e
+ (1,12) | 109 | 1.784 | 2723d092b63885e0d7c260cc007e8b9d
+ (1,11) | 108 | 2.646 | a3c65c2974270fd093ee8a9bf8ae7d0b
+ (1,10) | 107 | 3.065 | a97da629b098b75c294dffdc3e463904
+ (1,9)  | 106 | 3.412 | f0935e4cd5920aa6c7c996a5ee53a70f
+ (1,8)  | 105 | 8.583 | 65b9eea6e1cc6bb9f0cd2a47751a186f
+ (1,7)  | 104 | 6.715 | c9e1074f5b3f9fc8ea15d152add07294
+ (1,6)  | 103 | 4.109 | 6974ce5ac660610b44d9b9fed0ff9548
+ (1,5)  | 102 | 9.121 | ec8956637a99787bd197eacd77acce5e
+ (1,4)  | 101 | 4.645 | 38b3eff8baf56627478ec76a704e9b52
+ (1,3)  | 100 | 1.237 | f899139df5e1059396431415e770c6dd
+ (1,2)  |  99 |  5.83 | ac627ab1ccbdb62ec96e702f07f6425b
+ (1,1)  |  98 | 4.243 | ed3d2c21991e3bef5e069713af9fa6ca
+ (0,97) |  97 | 3.799 | e2ef524fbf3d9fe611d5a8e90fefdc9c
+ (0,96) |  96 | 2.132 | 26657d5ff9020d2abefe558796b99584
+ (0,95) |  95 | 6.077 | 812b4ba287f5ee0bc9d43bbf5bbe87fb
+ (0,94) |  94 | 0.365 | f4b9ec30ad9f68f89b29639786cb62ef
+ (0,93) |  93 | 2.718 | 98dce83da57b0395e163467c9dae521b
+(25 rows)
+
+SELECT count(*) FROM t1 WHERE ctid > '(200,0)'::tid;
+ count 
+-------
+ 20600
+(1 row)
+
+SELECT ctid,* FROM t1 WHERE c like '%678%' AND ctid >= '(3,50)'::tid LIMIT 25;
+  ctid   |  a   |   b   |                c                 
+---------+------+-------+----------------------------------
+ (6,78)  |  660 | 2.946 | 68264bdb65b97eeae6788aa3348e553c
+ (7,43)  |  722 | 1.612 | c8ed21db4f678f3b13b9d5ee16489088
+ (8,36)  |  812 | 3.673 | 81e74d678581a3bb7a720b019f4f1a93
+ (9,97)  |  970 | 7.977 | 89fcd07f20b6785b92134bd6c1d0fa42
+ (11,60) | 1127 | 8.636 | 678a1491514b7f1006d605e9161946b1
+ (14,10) | 1368 |   9.3 | 6c1da886822c67822bcf3679d04369fa
+ (14,41) | 1399 | 5.773 | 602d1305678a8d5fdb372271e980da6a
+ (14,94) | 1452 | 2.564 | 6786f3c62fbf9021694f6e51cc07fe3c
+ (16,74) | 1626 | 8.511 | bc573864331a9e42e4511de6f678aa83
+ (17,25) | 1674 | 4.681 | 757f843a169cc678064d9530d12a1881
+ (17,56) | 1705 | 8.138 | 2a27b8144ac02f67687f76782a3b5d8f
+ (18,90) | 1836 | 4.489 | f26dab9bf6a137c3b6782e562794c2f2
+ (19,41) | 1884 | 0.274 | f0fcf351df4eb6786e9bb6fc4e2dee02
+ (22,48) | 2182 | 2.666 | d51b416788b6ee70eb0c381c06efc9f1
+ (23,63) | 2294 | 3.001 | 229754d7799160502a143a72f6789927
+ (23,83) | 2314 | 8.831 | db9eeb7e678863649bce209842e0d164
+ (30,18) | 2928 | 0.852 | 9087b0efc7c7acd1ef7e153678809c77
+ (30,19) | 2929 | 5.443 | a36b0dcd1e6384abc0e1867860ad3ee3
+ (30,64) | 2974 |  9.84 | 6788076842014c83cedadbe6b0ba0314
+ (30,85) | 2995 | 9.828 | cb12d7f933e7d102c52231bf62b8a678
+ (32,21) | 3125 | 5.251 | 019f8b946a256d9357eadc5ace2c8678
+ (32,32) | 3136 |  5.17 | 2ecd2bd94734e5dd392d8678bc64cdab
+ (32,58) | 3162 | 8.215 | d60678e8f2ba9c540798ebbde31177e8
+ (32,64) | 3168 | 2.302 | ce393994e8cf430867408678d1a5a9c8
+ (33,14) | 3215 | 5.748 | e2eacaff46787bfeefcaa24cf35264c7
+(25 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+  ctid  |  a  |   b   |                c                 
+--------+-----+-------+----------------------------------
+ (3,1)  | 292 |  3.11 | 1700002963a49da13542e0726b7bb758
+ (3,2)  | 293 | 4.932 | 53c3bce66e43be4f209556518c2fcb54
+ (3,3)  | 294 | 3.851 | 6883966fd8f918a4aa29be29d2c386fb
+ (3,4)  | 295 | 9.821 | 49182f81e6a13cf5eaa496d51fea6406
+ (3,5)  | 296 | 1.623 | d296c101daa88a51f6ca8cfc1ac79b50
+ (3,6)  | 297 | 9.248 | 9fd81843ad7f202f26c1a174c7357585
+ (3,7)  | 298 | 8.073 | 26e359e83860db1d11b6acca57d8ea88
+ (3,8)  | 299 | 5.224 | ef0d3930a7b6c95bd2b32ed45989c61f
+ (3,9)  | 300 | 4.252 | 94f6d7e04a4d452035300f18b984988c
+ (3,10) | 301 | 4.599 | 34ed066df378efacc9b924ec161e7639
+(10 rows)
+
+SELECT ctid,* FROM t1 WHERE '(80,0)'::tid < ctid AND c like '%ab%' LIMIT 25;
+  ctid   |  a   |   b   |                c                 
+---------+------+-------+----------------------------------
+ (80,4)  | 7764 | 7.307 | fdff71fcab656abfbefaabecab1a7f6d
+ (80,10) | 7770 | 6.521 | c802ceaa43e6ad9ddc511cab5f34789c
+ (80,27) | 7787 | 7.681 | 72a8ab4748d4707fda159db0088d85de
+ (80,51) | 7811 | 5.957 | ddd1df443471e3abe89933f20d08116a
+ (80,52) | 7812 | 0.676 | eefc7bfe8fd6e2c8c01aa6ca7b1aab1a
+ (80,85) | 7845 | 0.486 | 0118a063b4aae95277f0bc1752c75abf
+ (80,89) | 7849 | 6.941 | ae3f4c649fb55c2ee3ef4d1abdb79ce5
+ (81,19) | 7876 | 9.923 | 42dab56861d81108ee356d037190c315
+ (81,36) | 7893 | 1.108 | 76fabdc82dd649afd7efa2d6894e568d
+ (81,43) | 7900 | 8.274 | 400c3241004b5db7ca7f5abfef2794f2
+ (81,47) | 7904 | 4.554 | b5d62aa6024ab6a65a12c78c4c2d4efc
+ (81,50) | 7907 | 4.477 | 10b4945abe2e627db646b3c5226a4e50
+ (81,56) | 7913 | 8.261 | 5527eaab87a00dbe1614481ef174f285
+ (81,86) | 7943 | 6.993 | 0163cceb20f5ca7b313419c068abd9dc
+ (81,92) | 7949 | 0.418 | 5fedcaffc4aba6e57a3563b1c7d60c1d
+ (81,95) | 7952 | 1.682 | f316e3fe33f1f754851712c760ab9d48
+ (82,2)  | 7956 |  2.71 | 7fd4db88d31ab524e0afe153c4f9465a
+ (82,3)  | 7957 | 2.215 | fa636c3d216834a2e0db24cc157ab5f0
+ (82,9)  | 7963 |  2.67 | a1d4c20b182ad7137ab3606f0e3fc8a4
+ (82,12) | 7966 |  0.01 | ab00b14a2da2e3cdcc44f06265db6574
+ (82,13) | 7967 | 0.551 | dab1263d1e6a88c9ba5e7e294def5e8b
+ (82,18) | 7972 | 4.943 | b5507f51b88a3ae4a99ba87e4877ab57
+ (82,21) | 7975 | 6.321 | 25f09e44e51b17fb527fba402bfba5ab
+ (82,37) | 7991 | 0.868 | ef7be8c57773f2ab48d013434d3ad4f7
+ (82,47) | 8001 | 0.091 | bc3c4a6331a8a9950945a1aa8c95ab8a
+(25 rows)
+
+SELECT ctid,* FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%' LIMIT 25;
+  ctid   |  a   |   b   |                c                 
+---------+------+-------+----------------------------------
+ (1,13)  |  110 | 1.052 | 5f93f983524def3dca464469d2cf9f3e
+ (1,28)  |  125 |  8.49 | 3def184ad8f4755ff269862ea77393dd
+ (1,77)  |  174 |  4.08 | bf8229696f7a3bb4700cfddef19fa23f
+ (2,5)   |  199 | 1.387 | 84d9ee44e457ddef7f2c4f25dc8fa865
+ (6,14)  |  596 | 7.644 | b2eeb7362ef83deff5c7813a67e14f0a
+ (8,3)   |  779 | 1.881 | 67d96d458abdef21792e6d8e590244e7
+ (8,68)  |  844 | 3.318 | e97ee2054defb209c35fe4dc94599061
+ (8,91)  |  867 | 0.073 | ede7e2b6d13a41ddf9f4bdef84fdc737
+ (10,31) | 1001 | 0.921 | b8c37e33defde51cf91e1e03e51657da
+ (11,52) | 1119 |  7.95 | 8597a6cfa74defcbde3047c891d78f90
+ (11,57) | 1124 |  9.97 | c7635bfd99248a2cdef8249ef7bfbef4
+ (12,4)  | 1168 | 5.197 | 2f29b6e3abc6ebdefb55456ea6ca5dc8
+ (12,14) | 1178 | 1.548 | 7d771e0e8f3633ab54856925ecdefc5d
+ (13,91) | 1352 | 4.859 | 8b0dc65f996f98fd178a9defd0efa077
+ (14,83) | 1441 | 5.126 | b197ffdef2ddc3308584dce7afa3661b
+ (16,2)  | 1554 | 8.012 | 98986c005e5def2da341b4e0627d4712
+ (16,90) | 1642 |  9.65 | 81c650caac28cdefce4de5ddc18befa0
+ (17,5)  | 1654 | 0.452 | 9d2682367c3935defcb1f9e247a97c0d
+ (17,77) | 1726 | 8.232 | 74563ba21a90da13dacf2a73e3ddefa7
+ (17,97) | 1746 | 7.861 | 442cde81694ca09a626eeddefd1b74ca
+ (23,62) | 2293 | 8.685 | 5f6371c9126149517d9ba475def53139
+ (25,36) | 2461 |  9.39 | cf05968255451bdefe3c5bc64d550517
+ (26,87) | 2609 | 4.644 | 339a18def9898dd60a634b2ad8fbbd58
+ (26,90) | 2612 | 6.852 | 1175defd049d3301e047ce50d93e9c7a
+ (27,46) | 2665 | 2.777 | e727fa59ddefcefb5d39501167623132
+(25 rows)
+
+SELECT t1.ctid,* FROM t1 NATURAL JOIN t3 WHERE t3.ctid IN (
+  SELECT t3.ctid FROM t2 NATURAL JOIN t3
+    WHERE t2.ctid BETWEEN '(4,0)'::tid AND '(5,0)'::tid)
+    LIMIT 25 OFFSET 50;
+  ctid   |  a  |   b   |                c                 |  x  
+---------+-----+-------+----------------------------------+-----
+ (8,44)  | 820 | 8.478 | e2a2dcc36a08a345332c751b2f2e476c | 640
+ (1,21)  | 118 | 5.258 | 5ef059938ba799aaa845e1c2e8a762bd | 636
+ (8,92)  | 868 | 9.159 | dd45045f8c68db9f54e70c67048d32e8 | 660
+ (9,65)  | 938 | 9.768 | 74bba22728b6185eec06286af6bec36d | 629
+ (1,87)  | 184 | 7.903 | 6cdd60ea0045eb7a6ec44c54d29ed402 | 672
+ (9,88)  | 961 | 0.201 | d707329bece455a462b58ce00d1194c9 | 612
+ (0,94)  |  94 | 0.365 | f4b9ec30ad9f68f89b29639786cb62ef | 664
+ (7,51)  | 730 | 7.128 | d5cfead94f5350c12c322b5b664544c1 | 669
+ (4,52)  | 440 | 6.344 | a8abb4bb284b5b27aa7cb790dc20f80b | 683
+ (10,3)  | 973 | 2.017 | ca75910166da03ff9d4655a0338e6b09 | 633
+ (4,65)  | 453 | 2.254 | 49ae49a23f67c759bf4fc791ba842aa2 | 674
+ (9,48)  | 921 | 5.248 | 430c3626b879b4005d41b8a46172e0c0 | 599
+ (5,5)   | 490 | 5.695 | c410003ef13d451727aeff9082c29a5c | 655
+ (0,35)  |  35 | 8.377 | 1c383cd30b7c298ab50293adfecb7b18 | 676
+ (4,37)  | 425 | 5.531 | 25b2822c2f5a3230abfadd476e8b04c9 | 611
+ (10,12) | 982 |  1.32 | fec8d47d412bcbeece3d9128ae855a7a | 682
+ (9,83)  | 956 | 3.577 | 168908dd3227b8358eababa07fcaf091 | 612
+ (9,66)  | 939 |  7.47 | 3df1d4b96d8976ff5986393e8767f5b2 | 617
+ (8,53)  | 829 | 0.186 | ce78d1da254c0843eb23951ae077ff5f | 658
+ (7,27)  | 706 |  0.11 | 9c82c7143c102b71c593d98d96093fde | 609
+ (8,63)  | 839 | 3.211 | 8f7d807e1f53eff5f9efbe5cb81090fb | 678
+ (8,33)  | 809 | 6.587 | 32b30a250abd6331e03a2a1f16466346 | 622
+ (8,1)   | 777 | 2.071 | f1c1592588411002af340cbaedd6fc33 | 616
+ (1,92)  | 189 | 3.371 | a2557a7b2e94197ff767970b67041697 | 660
+ (4,45)  | 433 | 2.954 | 019d385eb67632a7e958e23f24bd07d7 | 625
+(25 rows)
+
+-- Misc Queries
+SELECT ctid,* FROM t1 ORDER BY ctid DESC LIMIT 50;
+   ctid   |   a   |   b   |                c                 
+----------+-------+-------+----------------------------------
+ (412,36) | 40000 | 5.642 | 7c77f048a2d02e784926184a82686fa0
+ (412,35) | 39999 | 5.855 | 6d7364731230122df155fddea6878dd3
+ (412,34) | 39998 | 6.955 | 1969f0d80e750feb485671caaa4c59d1
+ (412,33) | 39997 | 5.958 | 26e31ab36807914055cf505c63c05bd1
+ (412,32) | 39996 | 2.004 | 16ecd261ac5088aee91078bf5225abd9
+ (412,31) | 39995 | 9.428 | 3724f1b309d02b7f475f69eba8107ae0
+ (412,30) | 39994 | 8.388 | 95831099d5d2171aea50c24de5332f73
+ (412,29) | 39993 | 7.867 | a463cac2327534f6f02563ffbdf92918
+ (412,28) | 39992 | 6.644 | ffacbb7db90628bfcc8be667616dfcc7
+ (412,27) | 39991 | 4.335 | 62e81b7815b24e46b69fcfa197aea837
+ (412,26) | 39990 | 3.085 | df7c6cbfde52a0ccf19c3a82487c3ca5
+ (412,25) | 39989 | 4.267 | 4325d1772bbb08248572e96f643a8de1
+ (412,24) | 39988 | 0.814 | 7b3f65a67546eca7a9249e1310a6be4f
+ (412,23) | 39987 | 4.573 | 4a2ad15a73d498efa82cc2893a52d08e
+ (412,22) | 39986 | 7.006 | 4144092976e61a9c32e1c7b205d85452
+ (412,21) | 39985 | 7.093 | 797ed5077436dc8abaec64750e2c3d3d
+ (412,20) | 39984 | 1.172 | e42a68e0a57044cc230ac7c901756c1d
+ (412,19) | 39983 | 1.165 | c8f2e54fe7b8ab5c291ea8d5831669df
+ (412,18) | 39982 | 0.123 | 54996ced8ec545754b9c7404027969d8
+ (412,17) | 39981 | 2.212 | 5e26badb3867ac7f26d3624ca39a9df4
+ (412,16) | 39980 | 5.832 | 906fec3cca7ccc130fa2b1844aa10126
+ (412,15) | 39979 | 4.755 | 34c5b339ea5917d60a26a3d1ef3a8fb2
+ (412,14) | 39978 | 9.307 | 497290bed604efe673ff973099876689
+ (412,13) | 39977 | 8.515 | 2a8b437cac4fd7012194170f76b385fe
+ (412,12) | 39976 | 7.702 | 589e19fafa037ef3e798363d7f9bd6d3
+ (412,11) | 39975 | 1.391 | b4803e027e986edc9bb95df3e2a0b525
+ (412,10) | 39974 | 3.145 | 1dc43216fffa0191d44329797ac898c0
+ (412,9)  | 39973 | 3.922 | 7a1fd501b45f517e975995e1ef9b956c
+ (412,8)  | 39972 | 9.996 | ae0752582ef51ba1b182f3d7f7cd9751
+ (412,7)  | 39971 | 3.518 | b0c2187f8453302e766a91b72f65a6cf
+ (412,6)  | 39970 | 5.561 | 12b25b878c7297605a57d9a104de9976
+ (412,5)  | 39969 | 9.684 | e0b6c164920a5a463aa98867f13038d4
+ (412,4)  | 39968 | 3.852 | f36a0f70291c903ae5664927b016c488
+ (412,3)  | 39967 | 7.527 | 385d960968e481fe04be1a04f429110d
+ (412,2)  | 39966 |  7.57 | f339e1789582cc3bbe44b27ef01b3617
+ (412,1)  | 39965 | 4.137 | b404579a33d6c50199eefb174df7b02a
+ (411,97) | 39964 | 2.785 | 6e4fd1f8d7082b92ba9842ab88aab57e
+ (411,96) | 39963 | 4.053 | 21ee021ac65ce078bfd68b48368dc6a8
+ (411,95) | 39962 | 4.783 | 76e91437c7f841334f01db0c85a8ec00
+ (411,94) | 39961 | 2.377 | f80ca2ec372a23b59dc6a5bfee18bfac
+ (411,93) | 39960 | 3.522 | 744a37185fa8c4449a60bd11976f9045
+ (411,92) | 39959 | 8.512 | 2d968cb99817ea5e49f4bcb70f7f154a
+ (411,91) | 39958 | 7.262 | b3f7bb40292f61fa966cb2a8a4cd339d
+ (411,90) | 39957 | 3.722 | eef43b55de33c2d530ea4a9669b81062
+ (411,89) | 39956 | 3.402 | f5fee8f7da74f4887f5bcae2bafb6dd6
+ (411,88) | 39955 | 5.841 | bc4d9b0e9bdbd3186592452785c479cc
+ (411,87) | 39954 | 6.971 | 23a28e6d57e4c5d8eb0bff70ae01ed09
+ (411,86) | 39953 |  8.96 | e954fd23871b5505e296fe8fddf78623
+ (411,85) | 39952 | 5.334 | 7ea9f068a460ee6c285e7ca7af850c51
+ (411,84) | 39951 | 5.368 | a08758e937069b10802ab9331cd70273
+(50 rows)
+
+EXPLAIN (verbose) UPDATE t1 SET c = c || '_may_last_page'
+    WHERE ctid >= (
+        SELECT '(' || relpages - 1 || ',20)' FROM pg_class
+        WHERE relname = 't1' AND
+              relnamespace = (
+            SELECT oid FROM pg_namespace
+                       WHERE nspname = 'regtest_custom_scan'))::tid
+    RETURNING ctid,*;
+                                                  QUERY PLAN                                                  
+--------------------------------------------------------------------------------------------------------------
+ Update on regtest_custom_scan.t1  (cost=10.38..601.56 rows=13333 width=51)
+   Output: t1.ctid, t1.a, t1.b, t1.c
+   InitPlan 2 (returns $1)
+     ->  Index Scan using pg_class_relname_nsp_index on pg_catalog.pg_class  (cost=1.35..9.38 rows=1 width=4)
+           Output: (('('::text || ((pg_class.relpages - 1))::text) || ',20)'::text)
+           Index Cond: ((pg_class.relname = 't1'::name) AND (pg_class.relnamespace = $0))
+           InitPlan 1 (returns $0)
+             ->  Seq Scan on pg_catalog.pg_namespace  (cost=0.00..1.07 rows=1 width=4)
+                   Output: pg_namespace.oid
+                   Filter: (pg_namespace.nspname = 'regtest_custom_scan'::name)
+   ->  Custom Scan (ctidscan) on regtest_custom_scan.t1  (cost=1.00..592.18 rows=13333 width=51)
+         Output: t1.a, t1.b, (t1.c || '_may_last_page'::text), t1.ctid
+         TID Range: (t1.ctid >= ($1)::tid)
+(13 rows)
+
+--set enable_ctidscan = off;
+UPDATE t1 SET c = c || '_may_last_page'
+    WHERE ctid >= (
+        SELECT '(' || relpages - 1 || ',20)' FROM pg_class
+        WHERE relname = 't1' AND
+              relnamespace = (
+            SELECT oid FROM pg_namespace
+                       WHERE nspname = 'regtest_custom_scan'))::tid
+    RETURNING ctid,*;
+   ctid   |   a   |   b   |                       c                        
+----------+-------+-------+------------------------------------------------
+ (412,37) | 39984 | 1.172 | e42a68e0a57044cc230ac7c901756c1d_may_last_page
+ (412,38) | 39985 | 7.093 | 797ed5077436dc8abaec64750e2c3d3d_may_last_page
+ (412,39) | 39986 | 7.006 | 4144092976e61a9c32e1c7b205d85452_may_last_page
+ (412,40) | 39987 | 4.573 | 4a2ad15a73d498efa82cc2893a52d08e_may_last_page
+ (412,41) | 39988 | 0.814 | 7b3f65a67546eca7a9249e1310a6be4f_may_last_page
+ (412,42) | 39989 | 4.267 | 4325d1772bbb08248572e96f643a8de1_may_last_page
+ (412,43) | 39990 | 3.085 | df7c6cbfde52a0ccf19c3a82487c3ca5_may_last_page
+ (412,44) | 39991 | 4.335 | 62e81b7815b24e46b69fcfa197aea837_may_last_page
+ (412,45) | 39992 | 6.644 | ffacbb7db90628bfcc8be667616dfcc7_may_last_page
+ (412,46) | 39993 | 7.867 | a463cac2327534f6f02563ffbdf92918_may_last_page
+ (412,47) | 39994 | 8.388 | 95831099d5d2171aea50c24de5332f73_may_last_page
+ (412,48) | 39995 | 9.428 | 3724f1b309d02b7f475f69eba8107ae0_may_last_page
+ (412,49) | 39996 | 2.004 | 16ecd261ac5088aee91078bf5225abd9_may_last_page
+ (412,50) | 39997 | 5.958 | 26e31ab36807914055cf505c63c05bd1_may_last_page
+ (412,51) | 39998 | 6.955 | 1969f0d80e750feb485671caaa4c59d1_may_last_page
+ (412,52) | 39999 | 5.855 | 6d7364731230122df155fddea6878dd3_may_last_page
+ (412,53) | 40000 | 5.642 | 7c77f048a2d02e784926184a82686fa0_may_last_page
+(17 rows)
+
+SELECT ctid,* FROM t1 ORDER BY ctid DESC LIMIT 50;
+   ctid   |   a   |   b   |                       c                        
+----------+-------+-------+------------------------------------------------
+ (412,53) | 40000 | 5.642 | 7c77f048a2d02e784926184a82686fa0_may_last_page
+ (412,52) | 39999 | 5.855 | 6d7364731230122df155fddea6878dd3_may_last_page
+ (412,51) | 39998 | 6.955 | 1969f0d80e750feb485671caaa4c59d1_may_last_page
+ (412,50) | 39997 | 5.958 | 26e31ab36807914055cf505c63c05bd1_may_last_page
+ (412,49) | 39996 | 2.004 | 16ecd261ac5088aee91078bf5225abd9_may_last_page
+ (412,48) | 39995 | 9.428 | 3724f1b309d02b7f475f69eba8107ae0_may_last_page
+ (412,47) | 39994 | 8.388 | 95831099d5d2171aea50c24de5332f73_may_last_page
+ (412,46) | 39993 | 7.867 | a463cac2327534f6f02563ffbdf92918_may_last_page
+ (412,45) | 39992 | 6.644 | ffacbb7db90628bfcc8be667616dfcc7_may_last_page
+ (412,44) | 39991 | 4.335 | 62e81b7815b24e46b69fcfa197aea837_may_last_page
+ (412,43) | 39990 | 3.085 | df7c6cbfde52a0ccf19c3a82487c3ca5_may_last_page
+ (412,42) | 39989 | 4.267 | 4325d1772bbb08248572e96f643a8de1_may_last_page
+ (412,41) | 39988 | 0.814 | 7b3f65a67546eca7a9249e1310a6be4f_may_last_page
+ (412,40) | 39987 | 4.573 | 4a2ad15a73d498efa82cc2893a52d08e_may_last_page
+ (412,39) | 39986 | 7.006 | 4144092976e61a9c32e1c7b205d85452_may_last_page
+ (412,38) | 39985 | 7.093 | 797ed5077436dc8abaec64750e2c3d3d_may_last_page
+ (412,37) | 39984 | 1.172 | e42a68e0a57044cc230ac7c901756c1d_may_last_page
+ (412,19) | 39983 | 1.165 | c8f2e54fe7b8ab5c291ea8d5831669df
+ (412,18) | 39982 | 0.123 | 54996ced8ec545754b9c7404027969d8
+ (412,17) | 39981 | 2.212 | 5e26badb3867ac7f26d3624ca39a9df4
+ (412,16) | 39980 | 5.832 | 906fec3cca7ccc130fa2b1844aa10126
+ (412,15) | 39979 | 4.755 | 34c5b339ea5917d60a26a3d1ef3a8fb2
+ (412,14) | 39978 | 9.307 | 497290bed604efe673ff973099876689
+ (412,13) | 39977 | 8.515 | 2a8b437cac4fd7012194170f76b385fe
+ (412,12) | 39976 | 7.702 | 589e19fafa037ef3e798363d7f9bd6d3
+ (412,11) | 39975 | 1.391 | b4803e027e986edc9bb95df3e2a0b525
+ (412,10) | 39974 | 3.145 | 1dc43216fffa0191d44329797ac898c0
+ (412,9)  | 39973 | 3.922 | 7a1fd501b45f517e975995e1ef9b956c
+ (412,8)  | 39972 | 9.996 | ae0752582ef51ba1b182f3d7f7cd9751
+ (412,7)  | 39971 | 3.518 | b0c2187f8453302e766a91b72f65a6cf
+ (412,6)  | 39970 | 5.561 | 12b25b878c7297605a57d9a104de9976
+ (412,5)  | 39969 | 9.684 | e0b6c164920a5a463aa98867f13038d4
+ (412,4)  | 39968 | 3.852 | f36a0f70291c903ae5664927b016c488
+ (412,3)  | 39967 | 7.527 | 385d960968e481fe04be1a04f429110d
+ (412,2)  | 39966 |  7.57 | f339e1789582cc3bbe44b27ef01b3617
+ (412,1)  | 39965 | 4.137 | b404579a33d6c50199eefb174df7b02a
+ (411,97) | 39964 | 2.785 | 6e4fd1f8d7082b92ba9842ab88aab57e
+ (411,96) | 39963 | 4.053 | 21ee021ac65ce078bfd68b48368dc6a8
+ (411,95) | 39962 | 4.783 | 76e91437c7f841334f01db0c85a8ec00
+ (411,94) | 39961 | 2.377 | f80ca2ec372a23b59dc6a5bfee18bfac
+ (411,93) | 39960 | 3.522 | 744a37185fa8c4449a60bd11976f9045
+ (411,92) | 39959 | 8.512 | 2d968cb99817ea5e49f4bcb70f7f154a
+ (411,91) | 39958 | 7.262 | b3f7bb40292f61fa966cb2a8a4cd339d
+ (411,90) | 39957 | 3.722 | eef43b55de33c2d530ea4a9669b81062
+ (411,89) | 39956 | 3.402 | f5fee8f7da74f4887f5bcae2bafb6dd6
+ (411,88) | 39955 | 5.841 | bc4d9b0e9bdbd3186592452785c479cc
+ (411,87) | 39954 | 6.971 | 23a28e6d57e4c5d8eb0bff70ae01ed09
+ (411,86) | 39953 |  8.96 | e954fd23871b5505e296fe8fddf78623
+ (411,85) | 39952 | 5.334 | 7ea9f068a460ee6c285e7ca7af850c51
+ (411,84) | 39951 | 5.368 | a08758e937069b10802ab9331cd70273
+(50 rows)
+
+-- PREPARE'd statement
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+                        WHERE c like '%abc%' AND ctid BETWEEN $1 AND $2;
+EXPLAIN (costs off) EXECUTE p1('(5,0)'::tid, '(10,0)'::tid);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+   Filter: (c ~~ '%abc%'::text)
+   TID Range: ((ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+(3 rows)
+
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Seq Scan on t1
+   Filter: ((c ~~ '%abc%'::text) AND (ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+(2 rows)
+
+-- Also, EXPLAIN with none-text format
+EXPLAIN (costs off, format xml) EXECUTE p1('(0,0)'::tid, '(5,0)'::tid);
+                                       QUERY PLAN                                       
+----------------------------------------------------------------------------------------
+ <explain xmlns="http://www.postgresql.org/2009/explain">                              +
+   <Query>                                                                             +
+     <Plan>                                                                            +
+       <Node-Type>Custom Scan</Node-Type>                                              +
+       <Custom-Plan-Provider>ctidscan</Custom-Plan-Provider>                           +
+       <Relation-Name>t1</Relation-Name>                                               +
+       <Alias>t1</Alias>                                                               +
+       <Filter>(c ~~ '%abc%'::text)</Filter>                                           +
+       <TID-Range>((ctid &gt;= '(0,0)'::tid) AND (ctid &lt;= '(5,0)'::tid))</TID-Range>+
+     </Plan>                                                                           +
+   </Query>                                                                            +
+ </explain>
+(1 row)
+
+-- Turn off the feature
+SET enable_ctidscan = off;
+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)
+
+-- Test Cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table t1
+drop cascades to table t2
+drop cascades to table t3
diff --git a/src/test/modules/ctidscan/sql/ctidscan.sql b/src/test/modules/ctidscan/sql/ctidscan.sql
new file mode 100644
index 0000000..231768e
--- /dev/null
+++ b/src/test/modules/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,132 @@
+--
+-- Regression Tests for CustomScan Interface with CtidScan Provider
+--
+
+-- construction of test data
+SET client_min_messages TO 'warning';
+
+SET SEED TO 0.20140702;
+
+CREATE SCHEMA regtest_custom_scan;
+
+SET search_path TO regtest_custom_scan, public;
+
+CREATE TABLE t1 (
+    a   int primary key,
+    b   float,
+    c   text
+);
+INSERT INTO t1 (SELECT i, ceil(random()*10000.0) / 1000.0, md5(i::text) FROM generate_series(1,40000) i);
+VACUUM ANALYZE t1;
+
+CREATE TABLE t2 (
+    x   int primary key,
+    y   float,
+    z   text
+);
+INSERT INTO t2 (SELECT i, ceil(random()*10000.0) / 1000.0, md5((-i)::text) FROM generate_series(201,40200) i);
+VACUUM ANALYZE t2;
+
+CREATE TABLE t3 (
+    a   int references t1(a),
+    x   int references t2(x)
+);
+INSERT INTO t3 (SELECT ceil(random() * 1000), ceil(random() * 1000) + 200 FROM generate_series(1,8000) i);
+
+RESET client_min_messages;
+
+--
+-- Check Plans if no special extensions are loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid;
+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 WHERE '(8,0)'::tid < ctid AND c like '%ab%';
+EXPLAIN (costs on) SELECT * FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%';
+EXPLAIN (costs off)
+    SELECT * FROM t1, t2, t3
+    WHERE t1.a = t3.a AND t2.x = t3.x
+          AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+          AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid
+          AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+EXPLAIN (costs off, verbose)
+    SELECT count(*), ceil(b)
+    FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid
+    GROUP BY ceil(b);
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid;
+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 WHERE '(8,0)'::tid < ctid AND c like '%ab%';
+EXPLAIN (costs on) SELECT * FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%';	-- SeqScan is cheaper
+EXPLAIN (costs off)
+    SELECT * FROM t1, t2, t3
+    WHERE t1.a = t3.a AND t2.x = t3.x
+          AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+          AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid
+          AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+EXPLAIN (costs off, verbose)
+    SELECT count(*), ceil(b)
+    FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid
+    GROUP BY ceil(b);
+
+--
+-- Run the query without EXPLAIN
+--
+SELECT ctid,* FROM t1 WHERE ctid <= '(1,20)'::tid ORDER BY ctid DESC LIMIT 25;
+SELECT count(*) FROM t1 WHERE ctid > '(200,0)'::tid;
+SELECT ctid,* FROM t1 WHERE c like '%678%' AND ctid >= '(3,50)'::tid LIMIT 25;
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+SELECT ctid,* FROM t1 WHERE '(80,0)'::tid < ctid AND c like '%ab%' LIMIT 25;
+SELECT ctid,* FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%' LIMIT 25;
+SELECT t1.ctid,* FROM t1 NATURAL JOIN t3 WHERE t3.ctid IN (
+  SELECT t3.ctid FROM t2 NATURAL JOIN t3
+    WHERE t2.ctid BETWEEN '(4,0)'::tid AND '(5,0)'::tid)
+    LIMIT 25 OFFSET 50;
+
+-- Misc Queries
+SELECT ctid,* FROM t1 ORDER BY ctid DESC LIMIT 50;
+
+EXPLAIN (verbose) UPDATE t1 SET c = c || '_may_last_page'
+    WHERE ctid >= (
+        SELECT '(' || relpages - 1 || ',20)' FROM pg_class
+        WHERE relname = 't1' AND
+              relnamespace = (
+            SELECT oid FROM pg_namespace
+                       WHERE nspname = 'regtest_custom_scan'))::tid
+    RETURNING ctid,*;
+--set enable_ctidscan = off;
+UPDATE t1 SET c = c || '_may_last_page'
+    WHERE ctid >= (
+        SELECT '(' || relpages - 1 || ',20)' FROM pg_class
+        WHERE relname = 't1' AND
+              relnamespace = (
+            SELECT oid FROM pg_namespace
+                       WHERE nspname = 'regtest_custom_scan'))::tid
+    RETURNING ctid,*;
+
+SELECT ctid,* FROM t1 ORDER BY ctid DESC LIMIT 50;
+
+-- PREPARE'd statement
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+                        WHERE c like '%abc%' AND ctid BETWEEN $1 AND $2;
+EXPLAIN (costs off) EXECUTE p1('(5,0)'::tid, '(10,0)'::tid);
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+
+-- Also, EXPLAIN with none-text format
+EXPLAIN (costs off, format xml) EXECUTE p1('(0,0)'::tid, '(5,0)'::tid);
+
+-- Turn off the feature
+SET enable_ctidscan = off;
+
+EXPLAIN (costs off)
+SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+
+-- Test Cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/src/test/regress/expected/custom_scan.out b/src/test/regress/expected/custom_scan.out
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/regress/sql/custom_scan.sql b/src/test/regress/sql/custom_scan.sql
new file mode 100644
index 0000000..683154b
--- /dev/null
+++ b/src/test/regress/sql/custom_scan.sql
@@ -0,0 +1,53 @@
+--
+-- Regression Tests for CustomScan Interface with CtidScan Provider
+--
+
+-- construction of test data
+SET client_min_messages TO 'warning';
+
+SET SEED 0.20140702;
+
+CREATE SCHEMA regtest_custom_scan;
+
+SET search_path TO regtest_custom_scan, public;
+
+CREATE TABLE t1 (
+    a   int primary key,
+    b   float,
+    c   text
+);
+INSERT INTO t1 (SELECT i, ceil(random()*1000.0) / 1000.0, md5(i::text) FROM generate_series(1,1000) i);
+VACUUM ANALYZE t1;
+
+CREATE TABLE t2 (
+    x   int primary key,
+    y   float,
+    z   text
+);
+INSERT INTO t2 (SELECT i, ceil(random()*1000.0) / 1000.0, md5((-i)::text) FROM generate_series(201,1200) i);
+VACUUM ANALYZE t2;
+
+CREATE TABLE t3 (
+    a   int references t1(a),
+    x   int references t2(x)
+);
+INSERT INTO t3 (SELECT ceil(random() * 1000), ceil(random() * 1000) + 200 FROM generate_series(1,8000) i);
+
+RESET client_min_messages;
+--
+-- Check Plans if no special extensions are 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;
+EXPLAIN (costs off) SELECT * FROM t1, t2, t3
+    WHERE t1.a = t3.a AND t2.x = t3.x
+          AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+          AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8.0)'::tid
+          AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+
+
+
+-- Test Cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
