Re: [v9.5] Custom Plan API

Started by Kouhei Kaigaiover 11 years ago39 messages
#1Kouhei Kaigai
kaigai@ak.jp.nec.com
3 attachment(s)

On Wed, Sep 17, 2014 at 7:40 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

At this moment, I revised the above portion of the patches.
create_custom_plan() was modified to call "PlanCustomPath" callback
next to the initialization of tlist and clauses.

It's probably same as what you suggested.

create_custom_plan() is mis-named. It's actually only applicable to the
custom-scan case, because it's triggered by create_plan_recurse() getting
a path node with a T_CustomScan pathtype. Now, we could change that;
although in general create_plan_recurse() dispatches on pathtype, we could
make CustomPath an exception; the top of that function could say if
(IsA(best_path, CustomPath)) { /* do custom stuff */ }. But the problem
with that idea is that, when the custom path is specifically a custom scan,
rather than a join or some other thing, you want to do all of the same
processing that's in create_scan_plan().

So I think what should happen is that create_plan_recurse() should handle
T_CustomScan the same way it handles T_SeqScan, T_IndexScan, et
al: by calling create_scan_plan(). The switch inside that function can
then call a function create_customscan_plan() if it sees T_CustomScan. And
that function will be simpler than the
create_custom_plan() that you have now, and it will be named correctly,
too.

Fixed, according to what you suggested. It seems to me create_customscan_plan()
became more simplified than before.
Probably, it will minimize the portion of special case handling if CustomScan
with scanrelid==0 replaces built-in join plan in the future version.

In ExplainNode(), I think sname should be set to "Custom Scan", not "Custom".
And further down, the custom_name should be printed as "Custom Plan
Provider" not just "Custom".

Fixed. I added an additional regression test to check EXPLAIN output
if not a text format.

setrefs.c has remaining handling for the scanrelid = 0 case; please remove
that.

Sorry, I removed it, and checked the patch again to ensure here is no similar
portions.

Thanks for your reviewing.
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-v9.5-custom-scan.part-3.v11.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-3.v11.patchDownload
 contrib/Makefile                       |   1 +
 contrib/ctidscan/Makefile              |  18 +
 contrib/ctidscan/ctidscan.c            | 885 +++++++++++++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control      |   5 +
 contrib/ctidscan/expected/ctidscan.out | 331 ++++++++++++
 contrib/ctidscan/sql/ctidscan.sql      |  59 +++
 src/backend/commands/explain.c         |   4 +-
 src/include/catalog/pg_operator.h      |   3 +
 8 files changed, 1304 insertions(+), 2 deletions(-)

diff --git a/contrib/Makefile b/contrib/Makefile
index b37d0dd..9b4b6ad 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..fbea380
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,18 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+EXTENSION = ctidscan
+
+REGRESS = 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..a8af99e
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,885 @@
+/*
+ * 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 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/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/spccache.h"
+
+PG_MODULE_MAGIC;
+
+typedef struct {
+	CustomPath		cpath;
+	List		   *ctid_quals;
+} CtidScanPath;
+
+typedef struct {
+	CustomScan		cscan;
+	List		   *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+	CustomScanState	css;
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CtidScanState;
+
+/* function declarations */
+void	_PG_init(void);
+
+static void CreateCtidScanPath(PlannerInfo *root,
+							   RelOptInfo *baserel,
+							   RangeTblEntry *rte);
+static Plan *PlanCtidScanPath(PlannerInfo *root,
+							  RelOptInfo *rel,
+							  CustomPath *best_path,
+							  List *tlist,
+							  List *clauses);
+static void TextOutCtidScanPath(StringInfo str, const CustomPath *cpath);
+
+static void SetCtidScanPlanRef(PlannerInfo *root,
+							   CustomScan *custom_plan,
+							   int rtoffset);
+static void FinalizeCtidScanPlan(PlannerInfo *root,
+								 CustomScan *custom_plan,
+								 bool (*finalize_primnode)(),
+								 void *finalize_context);
+static Node *CreateCtidScanState(CustomScan *custom_plan);
+static void TextOutCtidScanPlan(StringInfo str, const CustomScan *node);
+static CustomScan *CopyCtidScanPlan(const CustomScan *from);
+
+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 */
+	CreateCtidScanPath,		/* CreateCustomScanPath */
+	PlanCtidScanPath,		/* PlanCustomPath */
+	TextOutCtidScanPath,	/* TextOutCustomPath */
+};
+
+static CustomScanMethods	ctidscan_scan_methods = {
+	"ctidscan",				/* CustomName */
+	SetCtidScanPlanRef,		/* SetCustomScanRef */
+	FinalizeCtidScanPlan,	/* FinalizeCustomScan */
+	CreateCtidScanState,	/* CreateCustomScanState */
+	TextOutCtidScanPlan,	/* TextOutCustomScan */
+	CopyCtidScanPlan,		/* CopyCustomScan */
+};
+
+static CustomExecMethods	ctidscan_exec_methods = {
+	"ctidscan",				/* CustomName */
+	BeginCtidScan,			/* BeginCustomScan */
+	ExecCtidScan,			/* ExecCustomScan */
+	EndCtidScan,			/* EndCustomScan */
+	ReScanCtidScan,			/* ReScanCustomScan */
+	NULL,					/* MarkPosCustomScan */
+	NULL,					/* RestrPosCustomScan */
+	ExplainCtidScan,		/* ExplainCustomScan */
+	NULL,					/* GetSpecialCustomVar */
+};
+
+#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 chained 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,
+				  CtidScanPath *ctid_path)
+{
+	Path	   *path = &ctid_path->cpath.path;
+	List	   *ctid_quals = ctid_path->ctid_quals;
+	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 (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)
+	{
+		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 indeterministic until
+		 * 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 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,
+							  &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
+	 * (logic copied from 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 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;
+
+	path->startup_cost = startup_cost;
+	path->total_cost = startup_cost + run_cost;
+}
+
+/*
+ * CreateCtidScanPath - entrypoint of the series of custom-scan execution.
+ * It adds CustomPath if referenced relation has inequality expressions on
+ * the ctid system column.
+ */
+static void
+CreateCtidScanPath(PlannerInfo *root, RelOptInfo *baserel, 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;
+
+	/* 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);
+		ctid_quals = list_concat(ctid_quals, 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 (ctid_quals != NIL)
+	{
+		CtidScanPath *ctid_path;
+		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;
+
+		ctid_path = palloc0(sizeof(CtidScanPath));
+		ctid_path->cpath.path.type = T_CustomPath;
+		ctid_path->cpath.path.pathtype = T_CustomScan;
+		ctid_path->cpath.path.parent = baserel;
+		ctid_path->cpath.path.param_info
+			= get_baserel_parampathinfo(root, baserel, required_outer);
+		ctid_path->cpath.flags = CUSTOMPATH_SUPPORT_BACKWARD_SCAN;
+		ctid_path->cpath.methods = &ctidscan_path_methods;
+		ctid_path->ctid_quals = ctid_quals;
+
+		CTidEstimateCosts(root, baserel, ctid_path);
+
+		add_path(baserel, &ctid_path->cpath.path);
+	}
+}
+
+/*
+ * CreateCtidScanPlan - 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)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_scan;
+	List		   *ctid_quals = ctid_path->ctid_quals;
+
+	ctid_scan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_scan, T_CustomScan);
+	ctid_scan->cscan.flags = best_path->flags;
+	ctid_scan->cscan.methods = &ctidscan_scan_methods;
+
+	/* set scanrelid */
+	ctid_scan->cscan.scan.scanrelid = rel->relid;
+	/* set targetlist as is  */
+	ctid_scan->cscan.scan.plan.targetlist = tlist;
+	/* reduce RestrictInfo list to bare expressions */
+	ctid_scan->cscan.scan.plan.qual
+		= extract_actual_clauses(clauses, false);
+	/* set ctid related quals */
+	if (best_path->path.param_info)
+		ctid_quals = (List *)
+			replace_nestloop_params(root, (Node *)ctid_quals);
+	ctid_scan->ctid_quals = ctid_quals;
+
+	/*
+	 * If there are any pseudoconstant clauses attached to this node, insert a
+	 * gating Result node that evaluates the pseudoconstants as one-time
+	 * quals.
+	 */
+	if (root->hasPseudoConstantQuals)
+	{
+		List   *pseudoconstants = extract_actual_clauses(clauses, true);
+
+		if (pseudoconstants != NIL)
+			return (Plan *) make_result(root,
+										tlist,
+										(Node *) pseudoconstants,
+										(Plan *) ctid_scan);
+	}
+	return (Plan *) ctid_scan;
+}
+
+/*
+ * TextOutCtidScanPath - A method of CustomPath; that shows a text
+ * representation of the supplied CustomPath object.
+ */
+static void
+TextOutCtidScanPath(StringInfo str, const CustomPath *cpath)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *)cpath;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_path->ctid_quals));
+}
+
+/*
+ * SetCtidScanPlanRef - A method of CustomScan; that fixes up rtindex
+ * of Var nodes
+ */
+#define fix_scan_list(root, lst, rtoffset)	\
+	((List *) fix_scan_expr(root, (Node *) (lst), rtoffset))
+
+static void
+SetCtidScanPlanRef(PlannerInfo *root,
+				   CustomScan *custom_plan,
+				   int rtoffset)
+{
+	CtidScanPlan   *ctidscan = (CtidScanPlan *) custom_plan;
+	Scan		   *scan = &ctidscan->cscan.scan;
+
+	scan->scanrelid += rtoffset;
+	scan->plan.targetlist =
+		fix_scan_list(root, scan->plan.targetlist, rtoffset);
+	scan->plan.qual =
+		fix_scan_list(root, scan->plan.qual, rtoffset);
+	ctidscan->ctid_quals =
+		fix_scan_list(root, ctidscan->ctid_quals, rtoffset);
+}
+
+/*
+ * FinalizeCtidScanPlan - A method of CustomScan; that handles callbacks
+ * by finalize_plan().
+ */
+static void
+FinalizeCtidScanPlan(PlannerInfo *root,
+					 CustomScan *custom_plan,
+					 bool (*finalize_primnode)(),
+					 void *finalize_context)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) custom_plan;
+
+	/* applies finalize_primnode() on ctid_quals also */
+	finalize_primnode((Node *)ctid_plan->ctid_quals, finalize_context);
+}
+
+/*
+ * 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;
+}
+
+/*
+ * TextOutCtidScanPlan - A method of CustomScan; that generates text
+ * representation of the given object.
+ */
+static void
+TextOutCtidScanPlan(StringInfo str, const CustomScan *node)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_plan->ctid_quals));
+}
+
+/*
+ * CopyCtidScanPlan - A method of CustomScan; that create a copied object.
+ */
+static CustomScan *
+CopyCtidScanPlan(const CustomScan *from)
+{
+	CtidScanPlan   *oldnode = (CtidScanPlan *) from;
+	CtidScanPlan   *newnode = palloc0(sizeof(CtidScanPlan));
+
+	NodeSetTag(newnode, T_CustomScan);
+	newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+	return &newnode->cscan;
+}
+
+/*
+ * 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;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node->ss.ps.plan;
+
+	/*
+	 * In case of custom-plan 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 *)ctid_plan->ctid_quals, &node->ss.ps);
+
+	/* Do nothing anymore in EXPLAIN (no ANALYZE) case. */
+	if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
+		return;
+
+	/* scandesc shall be set later */
+	ctss->css.ss.ss_currentScanDesc = NULL;
+}
+
+/*
+ * 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;
+		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);
+		}
+		else
+			elog(ERROR, "could not identify CTID variable");
+
+		itemptr = (ItemPointer)
+			DatumGetPointer(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;
+		}
+
+		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),
+								 scan->rs_nblocks - 1);
+		scan->rs_startblock = blknum;
+	}
+	else if (direction == BackwardScanDirection && has_ubound)
+	{
+		BlockNumber	blknum = Min(BlockIdGetBlockNumber(&ip_max.ip_blkid),
+								 scan->rs_nblocks - 1);
+		scan->rs_startblock = 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;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->css.ss.ps.plan;
+
+	/* logic copied from show_qual and show_expression */
+	if (ctid_plan->ctid_quals)
+	{
+		bool	useprefix = es->verbose;
+		Node   *qual;
+		List   *context;
+		char   *exprstr;
+
+		/* Convert AND list to explicit AND */
+		qual = (Node *) make_ands_explicit(ctid_plan->ctid_quals);
+
+		/* 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("ctid quals", exprstr, es);
+	}
+}
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	register_custom_path_provider(&ctidscan_path_methods);
+}
diff --git a/contrib/ctidscan/ctidscan.control b/contrib/ctidscan/ctidscan.control
new file mode 100644
index 0000000..ad63432
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.control
@@ -0,0 +1,5 @@
+# ctidscan extension
+comment = 'example implementation for custom-plan interface'
+default_version = '1.0'
+module_pathname = '$libdir/ctidscan'
+relocatable = true
diff --git a/contrib/ctidscan/expected/ctidscan.out b/contrib/ctidscan/expected/ctidscan.out
new file mode 100644
index 0000000..9963a43
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,331 @@
+--
+-- Regression Tests for Custom Plan 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)
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+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))
+   ctid quals: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(3 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)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom Scan (ctidscan) on t2
+               Filter: (ctid > '(1,75)'::tid)
+               ctid quals: (ctid > '(1,75)'::tid)
+(12 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)
+
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+                        WHERE b 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: ((b ~~ '%abc%'::text) AND (ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+   ctid quals: ((ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+(3 rows)
+
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+   Filter: ((b ~~ '%abc%'::text) AND (ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+   ctid quals: ((ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+(3 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>                                              +
+       <Alias>t1</Alias>                                                                                  +
+       <Filter>((b ~~ '%abc%'::text) AND (ctid &gt;= '(0,0)'::tid) AND (ctid &lt;= '(5,0)'::tid))</Filter>+
+       <ctid-quals>((ctid &gt;= '(0,0)'::tid) AND (ctid &lt;= '(5,0)'::tid))</ctid-quals>                 +
+     </Plan>                                                                                              +
+   </Query>                                                                                               +
+ </explain>
+(1 row)
+
+-- Test cleanup
+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/contrib/ctidscan/sql/ctidscan.sql b/contrib/ctidscan/sql/ctidscan.sql
new file mode 100644
index 0000000..26c22c2
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,59 @@
+--
+-- Regression Tests for Custom Plan 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;
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+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;
+
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+                        WHERE b 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);
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 62b5e2a..3f26a50 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -942,7 +942,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			sname = "Custom Scan";
 			custom_name = ((CustomScan *) plan)->methods->CustomName;
 			if (custom_name)
-				pname = psprintf("Custom Scan (%s)", custom_name);
+				pname = psprintf("Custom Plan Provider (%s)", custom_name);
 			else
 				pname = sname;
 			break;
@@ -1048,7 +1048,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		if (plan_name)
 			ExplainPropertyText("Subplan Name", plan_name, es);
 		if (custom_name)
-			ExplainPropertyText("Custom Plan Provider", custom_name, es);
+			ExplainPropertyText("Custom", custom_name, es);
 	}
 
 	switch (nodeTag(plan))
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index d7dcd1c..0dd131c 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -166,10 +166,13 @@ 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");
pgsql-v9.5-custom-scan.part-2.v11.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-2.v11.patchDownload
 doc/src/sgml/custom-plan.sgml           | 359 ++++++++++++++++++++++++++++++++
 doc/src/sgml/filelist.sgml              |   1 +
 doc/src/sgml/postgres.sgml              |   1 +
 src/backend/commands/explain.c          |  25 +++
 src/backend/executor/Makefile           |   2 +-
 src/backend/executor/execAmi.c          |  38 +++-
 src/backend/executor/execProcnode.c     |  14 ++
 src/backend/executor/nodeCustom.c       | 127 +++++++++++
 src/backend/nodes/copyfuncs.c           |  26 +++
 src/backend/nodes/outfuncs.c            |  15 ++
 src/backend/optimizer/path/costsize.c   |   2 +-
 src/backend/optimizer/plan/createplan.c |   3 +-
 src/backend/optimizer/plan/setrefs.c    |  26 ++-
 src/backend/optimizer/plan/subselect.c  |  21 ++
 src/backend/utils/adt/ruleutils.c       |  79 +++++++
 src/include/executor/executor.h         |   3 +-
 src/include/executor/nodeCustom.h       |  30 +++
 src/include/nodes/execnodes.h           |  40 ++++
 src/include/nodes/nodes.h               |   1 +
 src/include/nodes/plannodes.h           |  20 ++
 src/include/optimizer/planmain.h        |   3 +
 21 files changed, 826 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/custom-plan.sgml b/doc/src/sgml/custom-plan.sgml
new file mode 100644
index 0000000..fc4b187
--- /dev/null
+++ b/doc/src/sgml/custom-plan.sgml
@@ -0,0 +1,359 @@
+<!-- doc/src/sgml/custom_plan.sgml -->
+
+<chapter id="custom-plan">
+ <title>Writing A Custom Plan Provider</title>
+ <indexterm zone="custom-plan">
+  <primary>custom plan provider</primary>
+  <secondary>handler for</secondary>
+ </indexterm>
+
+ <para>
+  PostgreSQL has various kind of built-in plan nodes that implement
+  a particular portion to process the supplied SQL queries.
+  For example, SeqScan node implements full table scan, HashJoin node
+  implements tables join using a hash table and so on.
+ </para>
+ <para>
+  The custom-plan interface allows extensions to provide alternative
+  query execution paths, in addition to the built-in ones. Query planner
+  will choose the cheapest path towards a particular relation(s) scan or
+  join in all the proposed ones by built-in and extensions.
+  Once a proposed custom plan got picked up, callback functions associated
+  with the node shall be called and extension will get control to process
+  the task. We call a set of callback functions associated with a certain
+  custom node the custom-path, custom-plan or custom-scan depending on
+  the things it provides; just "provider" in short.
+ </para>
+
+ <sect1 id="custom-plan-overall-steps">
+  <title>Custom Plan Overall Steps</title>
+  <para>
+   A custom-path provider shall be registered by
+   <literal>register_custom_path_provider</> that takes a pointer of
+   <literal>CustomPathMethods</> table which also holds some function
+   pointers.
+   It is usually installed on <literal>_PG_init()</> of extension
+   when either of preload configuration or <xref linkend="sql-load">
+   command loads the extension of the provider.
+  </para>
+  <para>
+   Onec a provider gets registered, the built-in query planner calls
+   back <literal>CreateCustomScanPath</> to ask whether the provider
+   can offer an alternative path to scan the referenced relation, or
+   not.
+   If it is available to provide, this callback will construct
+   <literal>CustomPath</> with cost estimation.
+   Then, query planner compares the alternative paths with built-in
+   paths based on the estimated cost, then the cheapest one shall be
+   chosen.
+  </para>
+  <para>
+   Usually, a provider may need some private fields to store something
+   valuable properties for the path. In this case, extension can extends
+   the <literal>CustomPath</> structure using a new data structure
+   definition that takes <literal>CustomPath</> on the head then followed
+   by private fields.
+<programlisting>
+typedef struct MySpecialPath
+{
+    CustomPath  cpath;
+    List       *some_list;
+    Bitmapset  *some_bitmap;
+       :
+} MySpecialPath;
+</programlisting>
+   The above example shows a structure delivered from <literal>CustomPath</>
+   with some private fields.
+   We assume such kind of manner, like as object oriented language doing,
+   to inject private fields of the provider on other nodes like
+   <literal>CustomScan</>.
+  </para>
+  <para>
+   Once a <literal>CustomPath</> got chosen, its callback shall be kicked
+   to populate <literal>CustomScan</> (or its inheritance) node; that is
+   the only available node type, right now. It consists of a part of query
+   plan tree, instead of the built-in nodes.
+   In the same manner, the <literal>CustomScan</> node populates
+   a <literal>CustomScanState</> (or its inheritance) node; that manages   
+   execution-time status of the custom-scan node and a set of callbacks.
+  </para>
+ </sect1>
+
+ <sect1 id="custom-path-callbacks">
+  <title>Custom Path Callbacks</title>
+  <para>
+   This section introduces callback functions of <structname>CustomPath</>
+   structure; defined in the <structname>CustomPathMethods</>, and
+   related flags.
+  </para>
+  <para>
+<programlisting>
+void
+CreateCustomScanPath(PlannerInfo *root,
+                     RelOptInfo *baserel,
+                     RangeTblEntry *rte);
+</programlisting>
+   As mentioned above, it construct <structname>CustomPath</> (or inherited
+   data type) node if it is available to provide an alternative scan path
+   on the supplied relation and qualifiers; that shall be informed using
+   <literal>RelOptInfo</> argument.
+   The constructed <structname>CustomPath</> node shall be added to
+   candidate path of the relation using <literal>add_path</>.
+  </para>
+  <para>
+<programlisting>
+Plan *
+PlanCustomPath(PlannerInfo *root,
+               RelOptInfo *rel,
+               CustomPath *best_path,
+               List *tlist,
+               List *clauses);
+</programlisting>
+   It populates a <struct>CustomScan</> or its inheritance node
+   according to the supplied <structname>CustomPath</> node which was
+   constructed on the custom path handler function then chosen by the
+   query planner.
+   This callback has to allocate a <struct>CustomScan</> node because
+   only provider can know exact size to be allocated if it is extended
+   to have private fields.
+   This callback is assumed to allocate the <struct>CustomScan</> node
+   with <structname>CustomPlanMethods</> callbacks table, then initialize
+   common plan-node fields and arbitrary private fields.
+  </para>
+
+  <para>
+<programlisting>
+void
+TextOutCustomPath(StringInfo str, const CustomPath *node);
+</programlisting>
+   It makes a text representation of custom path node. If provider extends
+   <structname>CustomPath</> data type, it shall to put private fields on
+   the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <structname>CustomPath</> are handled by
+   backend, so extension needs to do nothing special.
+  </para>
+
+  <para>
+   <literal>CustomPath</> structure can have flags that informs capability
+   of this custom-path to the query planner. Every flags can be combined
+   with OR-operation, then set to <literal>flags</> field.
+   The value of <literal>flags</> should be unchanged across node
+   population, because the plan tree is constructed based on the properties
+   being preliminary configured.
+  </para>
+  <para>
+   <literal>CUSTOMPATH_SUPPORT_BACKWARD_SCAN</> informs the planner
+   this custom-path supports backward scan.
+   <literal>CUSTOMPATH_SUPPORT_MARK_RESTORE</> informs the planner
+   this custom-path supports mark and restore position.
+  </para>
+ </sect1>
+
+ <sect1 id="custom-scan-callbacks">
+  <title>Custom Scan Callbacks</title>
+  <para>
+   This section introduces callback functions of <structname>CustomScan</>
+   structure; defined in the <structname>CustomScanMethods</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+SetCustomScanRef(PlannerInfo *root,
+                 CustomScan *cscan,
+                 int rtoffset);
+</programlisting>
+   It adjusts <literal>varno</> and <literal>varattno</> of var-nodes in
+   the expression tree chained from <structname>CustomScan</> node
+   (including private fields in the inherited data type).
+   In case of usual relation scan, <literal>rtoffset</> shall be added to
+   <literal>varno</> of var-nodes and <literal>scanrelid</> of plan node,
+   then <function>fix_scan_expr</> shall be called on expression nodes to
+   track plan dependency.
+  </para>
+
+  <para>
+<programlisting>
+void
+FinalizeCustomScan(PlannerInfo *root,
+                   CustomScan *cscan,
+                   bool (*finalize_primnode)(),
+                   void *finalize_context);
+</programlisting>
+   It is an optional callback, which applies <literal>finalize_primenode</>
+   on the expression nodes of private fields because only provider knows
+   what private fields are expression node to be finalized.
+   Note that backend applies <literal>finalize_primenode</> on
+   the <literal>tlist</> and <literal>qual</> of the base plan node,
+   so provider shall do nothing special, if it has no private expression
+   node. Also, it handles recursive stuff on the <literal>lefttree</> and
+   <literal>righttree</>, so it does not need to handle recursive walks.
+  </para>
+
+  <para>
+<programlisting>
+Node *
+CreateCustomScanState(CustomScan *cscan);
+</programlisting>
+   It populates a <structname>CustomScanState</> (or its inheritance)
+   node according to the supplied <structname>CustomScan</> node that was
+   preliminary constructed on the beginning of query executor.
+   Only provider can know exact size of the node to be allocated, this
+   callback allocate a <structname>CustomScanState</> node
+   with <structfield>CustomExecMethods</> callbacks table and arbitrary
+   private fields.
+  </para>
+  <para>
+   Note that main purpose of this callback is allocation of
+   <literal>CustomScanState</> node, not initialization of individual
+   fields because it shall be handled on the <structfield>BeginCustomPlan</>
+   callback to be invoked next to the common usual initialization.
+  </para>
+
+  <para>
+<programlisting>
+void
+TextOutCustomScan(StringInfo str,
+                  const CustomScan *node);
+</programlisting>
+   It makes a text representation of custom-scan node. If provider extends
+   <structfield>CustomScan</> data type, it shall put private fields on
+   the supplied <literal>StringInfo</> with text form.
+   Note that common fields within <structname>CustomPlan</> are handled
+   by the backend, so extension needs to do nothing special.
+  </para>
+
+  <para>
+<programlisting>
+CustomScan *
+CopyCustomScan(const CustomScan *from);
+</programlisting>
+   It duplicate every private fields of the inheritance of
+   <literal>CustomScan</> onto a newly allocated node.
+   In case when provider extends <literal>CustomScan</> node, only provider
+   can know exact size to be allocated and existence of the private fields.
+   So, it shall be responsible for node allocation and copy of private fields,
+   although the backend copies the fields of
+   <literal>CustomScan</>.
+  </para>
+ </sect1>
+
+ <sect1 id="custom-exec-callbacks">
+  <title>Custom Executor Callbacks</title>
+  <para>
+   This section introduces callback functions of <structname>CustomPlanState</>
+   structure; defined in the <structname>CustomExecMethods</>.
+  </para>
+  <para>
+<programlisting>
+void
+BeginCustomScan(CustomScanState *node,
+                EState *estate,
+                int eflags);
+</programlisting>
+   It begins execution of custom-scan. This callback is invoked during
+   executor startup to initialize the supplied <literal>CustomScanState</>
+   that was constructed on the <literal>CreateCustomScanState</> above.
+   The provider shall have initialization of its private fields and common
+   fields within <literal>CustomScanState</> if needed, because the backend
+   code already applies some fundamental initializations.
+  </para>
+
+  <para>
+<programlisting>
+TupleTableSlot *
+ExecCustomScan(CustomScanState *node);
+</programlisting>
+   It fetches one row from the custom-plan node, returning it in a tuple
+   table slot (<literal>ps_ResultTupleSlot</> of <literal>PlanState</>
+   shall be used) or <literal>NULL</> if no more rows are available.
+   The tuple table slot infrastructure allows either a physical or virtual
+   tuple to be returned; in most cases the latter choice is preferable from
+   a performance standpoint.
+   The rows being returned have to match the tuple-descriptor of the
+   <structname>PlanState</>
+  </para>
+  <para>
+   Note that this call is under a short-lived memory context that will be
+   reset for each invocation. So, it may be a good choice to switch
+   <literal>es_query_cxt</> of the <structname>EState</>, to acquire
+   memory in per-scan duration.
+  </para>
+
+  <para>
+<programlisting>
+void
+EndCustomScan(CustomScanState *node);
+</programlisting>
+   It ends the execution of custom-scan and release any resources held by
+   this node. If provider acquired resources that is not released
+   automatically at end of executor, it is responsibility of the provider.
+  </para>
+
+  <para>
+<programlisting>
+void
+ReScanCustomScan(CustomScanState *node);
+</programlisting>
+   It restarts the scan from the beginning.  Note that any parameters
+   the scan depends on may have changed value, so the new scan does not
+   necessarily return exactly the same rows.
+  </para>
+
+  <para>
+<programlisting>
+void *
+MarkPosCustomScan(CustomScanState *node);
+</programlisting>
+   It is an optional callback if <literal>CUSTOMPATH_SUPPORT_MARK_RESTORE</>
+   is set on the <literal>flags</>. Elsewhere, it should put <literal>NULL</>
+   on the callback table because never called.
+   It saves current scan position on somewhere in private fields of
+   <structname>CustomScanState</> (or its inheritance), to restore
+   the position later.
+  </para>
+
+  <para>
+<programlisting>
+void *
+RestrPosCustomScan(CustomScanState *node);
+</programlisting>
+   It is an optional callback if <literal>CUSTOMPATH_SUPPORT_MARK_RESTORE</>
+   is set on the <literal>flags</>. Elsewhere, it should put <literal>NULL</>
+   on the callback table because never called.
+   It restores the previous scan position saved by
+   the <structname>MarkPosCustomScan</> above.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomScan(CustomScanState *node,
+                  List *ancestors,
+                  ExplainState *es);
+</programlisting>
+   It is an optional callback, to show custom-scan specific explain output.
+  </para>
+
+  <para>
+<programlisting>
+Node   *
+GetSpecialCustomVar(CustomPlanState *node,
+                    Var *varnode,
+                    PlanState **child_ps);
+</programlisting>
+   It is an optional callback, to solve references to special varno on
+   the <literal>CustomScanState</> when <command>EXPLAIN</> needs the
+   text form of the column actually referenced.
+   In case when provider adjusted <literal>varno</> of varnodes on
+   the expression tree to use special varnos (<literal>INNER_VAR</>,
+   <literal>OUTER_VAR</> or <literal>INDEX_VAR</>), provider has
+   to inform the backend which column is mapped on the underlying plan-state.
+  </para>
+  <para>
+   This callback is expected to return <literal>Var</> node to reference
+   an actual variable on the underlying <structname>PlanState</> that shall
+   be set on the <literal>child_ps</> argument for recursive walking down.
+  </para>
+ </sect1>
+</chapter>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 5902f97..8d20594 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -92,6 +92,7 @@
 <!ENTITY nls        SYSTEM "nls.sgml">
 <!ENTITY plhandler  SYSTEM "plhandler.sgml">
 <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml">
+<!ENTITY custom-plan SYSTEM "custom-plan.sgml">
 <!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
 <!ENTITY protocol   SYSTEM "protocol.sgml">
 <!ENTITY sources    SYSTEM "sources.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 9bde108..5f415c6 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -242,6 +242,7 @@
   &nls;
   &plhandler;
   &fdwhandler;
+  &custom-plan;
   &geqo;
   &indexam;
   &gist;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 781a736..62b5e2a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -719,6 +719,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 		case T_CteScan:
 		case T_WorkTableScan:
 		case T_ForeignScan:
+		case T_CustomScan:
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
@@ -848,6 +849,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 	const char *sname;			/* node type name for non-text output */
 	const char *strategy = NULL;
 	const char *operation = NULL;
+	const char *custom_name = NULL;
 	int			save_indent = es->indent;
 	bool		haschildren;
 
@@ -936,6 +938,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomScan:
+			sname = "Custom Scan";
+			custom_name = ((CustomScan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom Scan (%s)", custom_name);
+			else
+				pname = sname;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1037,6 +1047,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			ExplainPropertyText("Parent Relationship", relationship, es);
 		if (plan_name)
 			ExplainPropertyText("Subplan Name", plan_name, es);
+		if (custom_name)
+			ExplainPropertyText("Custom Plan Provider", custom_name, es);
 	}
 
 	switch (nodeTag(plan))
@@ -1050,6 +1062,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_CteScan:
 		case T_WorkTableScan:
 		case T_ForeignScan:
+		case T_CustomScan:
 			ExplainScanTarget((Scan *) plan, es);
 			break;
 		case T_IndexScan:
@@ -1353,6 +1366,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
 			break;
+		case T_CustomScan:
+			{
+				CustomScanState *css = (CustomScanState *) planstate;
+
+				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+				if (plan->qual)
+					show_instrumentation_count("Rows Removed by Filter", 1,
+											   planstate, es);
+				if (css->methods->ExplainCustomScan)
+					css->methods->ExplainCustomScan(css, ancestors, es);
+			}
+			break;
 		case T_NestLoop:
 			show_upper_qual(((NestLoop *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 6081b56..af707b0 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -16,7 +16,7 @@ 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 \
-       nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
+       nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeCustom.o nodeHash.o \
        nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \
        nodeLimit.o nodeLockRows.o \
        nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 640964c..b14e08c 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"
@@ -49,6 +50,7 @@
 #include "executor/nodeWindowAgg.h"
 #include "executor/nodeWorktablescan.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/relation.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
@@ -197,6 +199,10 @@ ExecReScan(PlanState *node)
 			ExecReScanForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecReScanCustomScan((CustomScanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
@@ -291,6 +297,10 @@ ExecMarkPos(PlanState *node)
 			ExecValuesMarkPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecCustomMarkPos((CustomScanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialMarkPos((MaterialState *) node);
 			break;
@@ -348,6 +358,10 @@ ExecRestrPos(PlanState *node)
 			ExecValuesRestrPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecCustomRestrPos((CustomScanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialRestrPos((MaterialState *) node);
 			break;
@@ -379,9 +393,9 @@ ExecRestrPos(PlanState *node)
  * and valuesscan support is actually useless code at present.)
  */
 bool
-ExecSupportsMarkRestore(NodeTag plantype)
+ExecSupportsMarkRestore(Path *pathnode)
 {
-	switch (plantype)
+	switch (pathnode->pathtype)
 	{
 		case T_SeqScan:
 		case T_IndexScan:
@@ -403,6 +417,16 @@ ExecSupportsMarkRestore(NodeTag plantype)
 			 */
 			return false;
 
+		case T_CustomScan:
+			{
+				CustomPath *cpath = (CustomPath *) pathnode;
+
+				Assert(IsA(cpath, CustomPath));
+				if (cpath->flags & CUSTOMPATH_SUPPORT_MARK_RESTORE)
+					return true;
+			}
+			break;
+
 		default:
 			break;
 	}
@@ -465,6 +489,16 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomScan:
+			{
+				uint32	flags = ((CustomScan *) node)->flags;
+
+				if (TargetListSupportsBackwardScan(node->targetlist) &&
+					(flags & CUSTOMPATH_SUPPORT_BACKWARD_SCAN) != 0)
+					return true;
+			}
+			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 c0189eb..e27c062 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/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..a8155d7
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,127 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ *		Routines to handle execution of custom scan node
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "executor/executor.h"
+#include "executor/nodeCustom.h"
+#include "nodes/execnodes.h"
+#include "nodes/plannodes.h"
+#include "parser/parsetree.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+CustomScanState *
+ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
+{
+	CustomScanState    *css;
+	Relation			scan_rel;
+
+	/* populate a CustomScanState according to the CustomScan */
+	css = (CustomScanState *)cscan->methods->CreateCustomScanState(cscan);
+	Assert(IsA(css, CustomScanState));
+
+	/* fill up fields of ScanState */
+	css->ss.ps.plan = &cscan->scan.plan;
+	css->ss.ps.state = estate;
+
+	/* create expression context for node */
+	ExecAssignExprContext(estate, &css->ss.ps);
+	css->ss.ps.ps_TupFromTlist = false;
+
+	/* initialize child expressions */
+	css->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) cscan->scan.plan.targetlist,
+					 (PlanState *) css);
+	css->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) cscan->scan.plan.qual,
+					 (PlanState *) css);
+
+	/* initialization of result tuple slot  */
+	ExecInitResultTupleSlot(estate, &css->ss.ps);
+	ExecAssignResultTypeFromTL(&css->ss.ps);
+
+	/*
+	 * Also, initialization of relation scan stuff if custom-scan
+	 * node intends to run on a particular plain relation.
+	 * Elsewhere, custom-scan provider should be responsible to put
+	 * proper initialization of scan tuple-slot and projection info
+	 * by itself.
+	 */
+	scan_rel = ExecOpenScanRelation(estate, cscan->scan.scanrelid, eflags);
+	css->ss.ss_currentRelation = scan_rel;
+	css->ss.ss_currentScanDesc = NULL;	/* set by provider on demand */
+	ExecInitScanTupleSlot(estate, &css->ss);
+	ExecAssignScanType(&css->ss, RelationGetDescr(scan_rel));
+	ExecAssignScanProjectionInfo(&css->ss);
+
+	/*
+	 * The callback of custom-scan provider applies the final initialization
+	 * of the custom-scan-state node according to its logic.
+	 */
+	css->methods->BeginCustomScan(css, estate, eflags);
+
+	return css;
+}
+
+TupleTableSlot *
+ExecCustomScan(CustomScanState *node)
+{
+	Assert(node->methods->ExecCustomScan != NULL);
+	return node->methods->ExecCustomScan(node);
+}
+
+void
+ExecEndCustomScan(CustomScanState *node)
+{
+	Assert(node->methods->EndCustomScan != NULL);
+	node->methods->EndCustomScan(node);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&node->ss.ps);
+
+	/* Clean out the tuple table */
+	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+	if (node->ss.ss_ScanTupleSlot)
+		ExecClearTuple(node->ss.ss_ScanTupleSlot);
+
+	/* Close the heap relation */
+	ExecCloseScanRelation(node->ss.ss_currentRelation);
+}
+
+void
+ExecReScanCustomScan(CustomScanState *node)
+{
+	Assert(node->methods->ReScanCustomScan != NULL);
+	node->methods->ReScanCustomScan(node);
+}
+
+void
+ExecCustomMarkPos(CustomScanState *node)
+{
+	if (!node->methods->MarkPosCustomScan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("custom-scan \"%s\" does not support MarkPos",
+						node->methods->CustomName)));
+	node->methods->MarkPosCustomScan(node);
+}
+
+void
+ExecCustomRestrPos(CustomScanState *node)
+{
+	if (!node->methods->RestrPosCustomScan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("custom-scan \"%s\" does not support MarkPos",
+						node->methods->CustomName)));
+	node->methods->RestrPosCustomScan(node);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 225756c..a471d43 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,29 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomScan
+ */
+static CustomScan *
+_copyCustomScan(const CustomScan *from)
+{
+	CustomScan		   *newnode;
+
+	newnode = from->methods->CopyCustomScan(from);
+	Assert(nodeTag(newnode) == nodeTag(from));
+
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+	COPY_SCALAR_FIELD(flags);
+	/*
+	 * NOTE: The method field of CustomScan is required to be a pointer
+	 * to a static table of callback functions. So, we don't copy the
+	 * table itself, just reference the original one.
+	 */
+	COPY_SCALAR_FIELD(methods);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -4042,6 +4065,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 8ad9c8d..aed53c7 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,18 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomScan(StringInfo str, const CustomScan *node)
+{
+	WRITE_NODE_TYPE("CUSTOMSCAN");
+
+	_outScanInfo(str, (const Scan *) node);
+	WRITE_UINT_FIELD(flags);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomScan(str, node);
+}
+
+static void
 _outJoin(StringInfo str, const Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -2863,6 +2875,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/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 0cdb790..659daa2 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -2266,7 +2266,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;
 
 	/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 189f7ef..23a26d2 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -86,7 +86,6 @@ static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
 					  Plan *outer_plan, Plan *inner_plan);
 static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path,
 					 Plan *outer_plan, Plan *inner_plan);
-static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
 static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
 static void process_subquery_nestloop_params(PlannerInfo *root,
 								 List *subplan_params);
@@ -2613,7 +2612,7 @@ create_hashjoin_plan(PlannerInfo *root,
  * root->curOuterRels are replaced by Params, and entries are added to
  * root->curOuterParams if not already present.
  */
-static Node *
+Node *
 replace_nestloop_params(PlannerInfo *root, Node *expr)
 {
 	/* No setup needed for tree walk, so away we go */
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9ddc8ad..5589de8 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -94,7 +94,6 @@ static Plan *set_subqueryscan_references(PlannerInfo *root,
 							SubqueryScan *plan,
 							int rtoffset);
 static bool trivial_subqueryscan(SubqueryScan *plan);
-static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
 static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
 static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
 static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
@@ -579,6 +578,27 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomScan:
+			{
+				CustomScan *cscan = (CustomScan *) plan;
+
+				cscan->scan.scanrelid += rtoffset;
+				cscan->scan.plan.targetlist =
+					fix_scan_list(root, cscan->scan.plan.targetlist, rtoffset);
+				cscan->scan.plan.qual =
+					fix_scan_list(root, cscan->scan.plan.qual, rtoffset);
+				/*
+				 * The core implementation applies the routine to fixup
+				 * varno on the target-list and scan qualifier.
+				 * If custom-scan has additional expression nodes on its
+				 * private fields, it has to apply same fixup on them.
+				 * Elsewhere, custom-plan provider skip this callback.
+				 */
+				if (cscan->methods->SetCustomScanRef)
+					cscan->methods->SetCustomScanRef(root, cscan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1063,7 +1083,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 */
@@ -1161,7 +1181,7 @@ fix_param_node(PlannerInfo *root, Param *p)
  * looking up operator opcode info for OpExpr and related nodes,
  * and adding OIDs from regclass Const nodes into root->glob->relationOids.
  */
-static Node *
+Node *
 fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
 {
 	fix_scan_expr_context context;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 3e7dc85..4200ec0 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2283,6 +2283,27 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomScan:
+			{
+				CustomScan *custom_scan = (CustomScan *) plan;
+
+				context.paramids = bms_add_members(context.paramids,
+												   scan_params);
+				/*
+				 * custom-scan provider is responsible to apply
+				 * finalize_primnode() on the expression node of
+				 * its private fields, but no need to apply it
+				 * on the tlist and qual of Plan node because it
+				 * is already done above.
+				 */
+				if (custom_scan->methods->FinalizeCustomScan)
+					custom_scan->methods->FinalizeCustomScan(root,
+															 custom_scan,
+															 finalize_primnode,
+															 (void *)&context);
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7237e5d..f0812af 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5490,6 +5490,37 @@ get_utility_query_def(Query *query, deparse_context *context)
 	}
 }
 
+/*
+ * GetSpecialCustomVar
+ *
+ * This routine provides a way to resolve where the supplied varnode
+ * actually references, using GetSpecialCustomVar method, in case when
+ * custom-scan provider replaced a varno in expression tree by special
+ * varno.
+ */
+static Node *
+GetSpecialCustomVar(PlanState *ps, Var *varnode, PlanState **child_ps)
+{
+	CustomScanState *css = (CustomScanState *) ps;
+
+	/* In case of none custom-scan node, inform the caller it is not
+	 * a case of this routine, because it is only for lookup underlyin
+	 * expression node being referenced by the supplied special var-
+	 * node.
+	 */
+	if (!IsA(ps, CustomScanState))
+		return NULL;
+
+	Assert(IsA(ps, CustomScanState));
+	Assert(IS_SPECIAL_VARNO(varnode->varno));
+
+	if (!css->methods->GetSpecialCustomVar)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s does not support special varno reference",
+						css->methods->CustomName)));
+	return (Node *)css->methods->GetSpecialCustomVar(css, varnode, child_ps);
+}
 
 /*
  * Display a Var appropriately.
@@ -5519,6 +5550,8 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 	int			netlevelsup;
 	deparse_namespace *dpns;
 	deparse_columns *colinfo;
+	PlanState  *child_ps = NULL;
+	Node	   *expr;
 	char	   *refname;
 	char	   *attname;
 
@@ -5543,6 +5576,28 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 		colinfo = deparse_columns_fetch(var->varno, dpns);
 		attnum = var->varattno;
 	}
+	else if (IS_SPECIAL_VARNO(var->varno) &&
+			 (expr = GetSpecialCustomVar(dpns->planstate, var,
+										 &child_ps)) != NULL)
+	{
+		deparse_namespace	save_dpns;
+
+		if (child_ps)
+			push_child_plan(dpns, child_ps, &save_dpns);
+		/*
+		 * Force parentheses because our caller probably assumed a Var is a
+		 * simple expression.
+		 */
+		if (!IsA(expr, Var))
+			appendStringInfoChar(buf, '(');
+		get_rule_expr((Node *) expr, context, true);
+		if (!IsA(expr, Var))
+			appendStringInfoChar(buf, ')');
+
+		if (child_ps)
+			pop_child_plan(dpns, &save_dpns);
+		return NULL;
+	}
 	else if (var->varno == OUTER_VAR && dpns->outer_tlist)
 	{
 		TargetEntry *tle;
@@ -5757,6 +5812,7 @@ get_name_for_var_field(Var *var, int fieldno,
 	AttrNumber	attnum;
 	int			netlevelsup;
 	deparse_namespace *dpns;
+	PlanState  *child_ps = NULL;
 	TupleDesc	tupleDesc;
 	Node	   *expr;
 
@@ -5831,6 +5887,29 @@ get_name_for_var_field(Var *var, int fieldno,
 		rte = rt_fetch(var->varno, dpns->rtable);
 		attnum = var->varattno;
 	}
+	else if (IS_SPECIAL_VARNO(var->varno) &&
+			 (expr = GetSpecialCustomVar(dpns->planstate, var,
+										 &child_ps)) != NULL)
+	{
+		StringInfo		saved = context->buf;
+		StringInfoData	temp;
+		deparse_namespace save_dpns;
+
+		initStringInfo(&temp);
+		context->buf = &temp;
+
+		if (child_ps)
+			push_child_plan(dpns, child_ps, &save_dpns);
+		if (!IsA(expr, Var))
+			appendStringInfoChar(context->buf, '(');
+		get_rule_expr((Node *) expr, context, true);
+		if (!IsA(expr, Var))
+			appendStringInfoChar(context->buf, ')');
+		if (child_ps)
+			pop_child_plan(dpns, &save_dpns);
+		context->buf = saved;
+		return temp.data;
+	}
 	else if (var->varno == OUTER_VAR && dpns->outer_tlist)
 	{
 		TargetEntry *tle;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 0266135..6239a3f 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 *pathnode);
 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..1736d48
--- /dev/null
+++ b/src/include/executor/nodeCustom.h
@@ -0,0 +1,30 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.h
+ *
+ * prototypes for CustomScan nodes
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ------------------------------------------------------------------------
+ */
+#ifndef NODECUSTOM_H
+#define NODECUSTOM_H
+#include "nodes/plannodes.h"
+#include "nodes/execnodes.h"
+
+/*
+ * General executor code
+ */
+extern CustomScanState *ExecInitCustomScan(CustomScan *custom_scan,
+										   EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomScan(CustomScanState *node);
+extern Node *MultiExecCustomScan(CustomScanState *node);
+extern void ExecEndCustomScan(CustomScanState *node);
+
+extern void ExecReScanCustomScan(CustomScanState *node);
+extern void ExecCustomMarkPos(CustomScanState *node);
+extern void ExecCustomRestrPos(CustomScanState *node);
+
+#endif	/* NODECUSTOM_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b271f21..4a94f4a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -19,6 +19,7 @@
 #include "executor/instrument.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
+#include "nodes/relation.h"
 #include "utils/reltrigger.h"
 #include "utils/sortsupport.h"
 #include "utils/tuplestore.h"
@@ -1504,6 +1505,45 @@ typedef struct ForeignScanState
 	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
 } ForeignScanState;
 
+/* ----------------
+ * CustomScanState information
+ *
+ *		CustomScan nodes are used to execute custom code within executor.
+ * ----------------
+ */
+struct CustomExecMethods;
+struct ExplainState;	/* to avoid to include explain.h here */
+
+typedef struct CustomScanState
+{
+	ScanState	ss;
+	uint32		flags;	/* mask of CUSTOMPATH_* flags defined in relation.h*/
+	const struct CustomExecMethods *methods;
+} CustomScanState;
+
+typedef struct CustomExecMethods
+{
+	const char     *CustomName;
+
+	/* EXECUTOR methods */
+	void    (*BeginCustomScan)(CustomScanState *node,
+							   EState *estate,
+							   int eflags);
+	TupleTableSlot *(*ExecCustomScan)(CustomScanState *node);
+	void	(*EndCustomScan)(CustomScanState *node);
+	void	(*ReScanCustomScan)(CustomScanState *node);
+	void	(*MarkPosCustomScan)(CustomScanState *node);
+	void	(*RestrPosCustomScan)(CustomScanState *node);
+
+	/* EXPLAIN support */
+	void    (*ExplainCustomScan)(CustomScanState *node,
+								 List *ancestors,
+								 struct ExplainState *es);
+	Node   *(*GetSpecialCustomVar)(CustomScanState *node,
+								   Var *varnode,
+								   PlanState **child_ps);
+} CustomExecMethods;
+
 /* ----------------------------------------------------------------
  *				 Join State Information
  * ----------------------------------------------------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6584a2c..bc71fea 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -108,6 +108,7 @@ typedef enum NodeTag
 	T_CteScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
+	T_CustomScanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e08fb1d..e059d0d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -15,8 +15,10 @@
 #define PLANNODES_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
+#include "nodes/relation.h"
 
 
 /* ----------------------------------------------------------------
@@ -486,12 +488,30 @@ typedef struct ForeignScan
  *     CustomScan node
  * ----------------
  */
+struct CustomScanMethods;
+
 typedef struct CustomScan
 {
 	Scan		scan;
 	uint32		flags;	/* mask of CUSTOMPATH_* flags defined in relation.h */
+	struct CustomScanMethods *methods;
 } CustomScan;
 
+typedef struct CustomScanMethods
+{
+	const char *CustomName;
+	void	   (*SetCustomScanRef)(struct PlannerInfo *root,
+								   CustomScan *cscan,
+								   int rtoffset);
+	void	   (*FinalizeCustomScan)(struct PlannerInfo *root,
+									 CustomScan *cscan,
+									 bool (*finalize_primnode)(),
+									 void *finalize_context);
+	Node	  *(*CreateCustomScanState)(CustomScan *cscan);
+	void	   (*TextOutCustomScan)(StringInfo str, const CustomScan *node);
+	CustomScan *(*CopyCustomScan)(const CustomScan *from);
+} CustomScanMethods;
+
 /*
  * ==========
  * Join nodes
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 3fdc2cb..c97c577 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -86,6 +86,7 @@ extern ModifyTable *make_modifytable(PlannerInfo *root,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, int epqParam);
 extern bool is_projection_capable_plan(Plan *plan);
+extern Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
 
 /*
  * prototypes for plan/initsplan.c
@@ -130,6 +131,8 @@ extern bool query_is_distinct_for(Query *query, List *colnos, List *opids);
  */
 extern Plan *set_plan_references(PlannerInfo *root, Plan *plan);
 extern void fix_opfuncids(Node *node);
+extern Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
+extern void fix_expr_common(PlannerInfo *root, Node *node);
 extern void set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
pgsql-v9.5-custom-scan.part-1.v11.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-1.v11.patchDownload
 src/backend/nodes/outfuncs.c            | 14 +++++++
 src/backend/optimizer/path/allpaths.c   |  3 ++
 src/backend/optimizer/plan/createplan.c | 73 +++++++++++++++++++++++++++++++++
 src/backend/optimizer/util/pathnode.c   | 50 ++++++++++++++++++++++
 src/include/nodes/nodes.h               |  2 +
 src/include/nodes/plannodes.h           |  9 ++++
 src/include/nodes/relation.h            | 37 +++++++++++++++++
 src/include/optimizer/pathnode.h        |  9 ++++
 8 files changed, 197 insertions(+)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1ff78eb..8ad9c8d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1582,6 +1582,17 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
 }
 
 static void
+_outCustomPath(StringInfo str, const CustomPath *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPATH");
+	_outPathInfo(str, (const Path *) node);
+	WRITE_UINT_FIELD(flags);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomPath(str, node);
+}
+
+static void
 _outAppendPath(StringInfo str, const AppendPath *node)
 {
 	WRITE_NODE_TYPE("APPENDPATH");
@@ -3060,6 +3071,9 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignPath:
 				_outForeignPath(str, obj);
 				break;
+			case T_CustomPath:
+				_outCustomPath(str, obj);
+				break;
 			case T_AppendPath:
 				_outAppendPath(str, obj);
 				break;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index c81efe9..8b42e36 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -402,6 +402,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
 
+	/* Consider custom scans, if any */
+	create_customscan_paths(root, rel, rte);
+
 	/* Now find the cheapest of the paths for this rel */
 	set_cheapest(rel);
 }
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4b641a2..189f7ef 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,6 +77,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,
@@ -233,6 +236,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:
@@ -409,6 +413,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);
@@ -1072,6 +1083,68 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 	return plan;
 }
 
+/*
+ * create_custom_plan
+ *
+ * Returns a custom-scan plan for the base relation scanned by 'best_path'
+ * with restriction clauses 'clauses' and targetlist 'tlist'.
+ */
+static CustomScan *
+create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
+					   List *tlist, List *scan_clauses)
+{
+	CustomScan	   *custom_scan;
+	RelOptInfo	   *rel = best_path->path.parent;
+
+	/*
+	 * Right now, all we can support is CustomScan node which is associated
+	 * with a particular base relation to be scanned.
+	 */
+	Assert(rel && rel->reloptkind == RELOPT_BASEREL);
+
+	/*
+	 * Sort clauses into the best execution order, although custom-scan
+	 * provider can reorder them again.
+	 */
+	scan_clauses = order_qual_clauses(root, scan_clauses);
+
+	/*
+	 * Create a CustomScan (or its inheritance) node according to
+	 * the supplied CustomPath.
+	 */
+	custom_scan = (CustomScan *)
+		best_path->methods->PlanCustomPath(root,
+										   rel,
+										   best_path,
+										   tlist,
+										   scan_clauses);
+	/* additional sanity checks */
+	Assert(nodeTag(custom_scan) == best_path->path.pathtype);
+	if (IsA(custom_scan, CustomScan))
+	{
+		Index	scanrelid = ((Scan *)custom_scan)->scanrelid;
+
+		if (scanrelid != rel->relid)
+			elog(ERROR, "Bug? CustomScan tries to scan incorrect relation");
+	}
+	else
+		elog(ERROR, "unexpected node: %d", (int)nodeTag(custom_scan));
+
+	/*
+	 * NOTE: unlike create_foreignscan_plan(), it is responsibility of
+	 * the custom plan provider to replace outer-relation variables
+	 * with nestloop params, because we cannot know how many expression
+	 * trees are held in the private fields.
+	 */
+
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize((Plan *)custom_scan, &best_path->path);
+
+	return custom_scan;
+}
 
 /*****************************************************************************
  *
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 319e8b2..2ca0a18 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -27,6 +27,7 @@
 #include "optimizer/var.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/selfuncs.h"
 
 
@@ -1926,3 +1927,52 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*****************************************************************************
+ *     creation of custom-plan paths
+ *****************************************************************************/
+
+static List	   *custom_path_providers = NIL;
+
+/*
+ * register_custom_path_provider
+ *
+ * It registers a table of callback functions that implements a custom-path
+ * provider. The callback functions are expected to construct CustomPath node
+ * that provides an alternative logic to scan a relation (and so on in the
+ * future version), if extension can do.
+ * Note that the supplied CustomPathMethods is expected to locate on static
+ * memory area, so we don't copy individual fields here.
+ */
+void
+register_custom_path_provider(CustomPathMethods *cpp_methods)
+{
+	MemoryContext	oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+	custom_path_providers = lappend(custom_path_providers, cpp_methods);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * create_customscan_paths
+ *
+ * It calls back extension's entrypoint whether it can add alternative
+ * scan paths being extended from the CustomPath type.
+ * If any, the callback will add one or more paths using add_path().
+ */
+void
+create_customscan_paths(PlannerInfo *root,
+						RelOptInfo *baserel,
+						RangeTblEntry *rte)
+{
+	ListCell	   *cell;
+
+	foreach (cell, custom_path_providers)
+	{
+		const CustomPathMethods *cpp_methods = lfirst(cell);
+
+		if (cpp_methods->CreateCustomScanPath)
+			cpp_methods->CreateCustomScanPath(root, baserel, rte);
+	}
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 154d943..6584a2c 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,
@@ -224,6 +225,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 1839494..e08fb1d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -482,6 +482,15 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *     CustomScan node
+ * ----------------
+ */
+typedef struct CustomScan
+{
+	Scan		scan;
+	uint32		flags;	/* mask of CUSTOMPATH_* flags defined in relation.h */
+} CustomScan;
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index f1a0504..2bbb7cc 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -15,6 +15,7 @@
 #define RELATION_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "storage/block.h"
@@ -884,6 +885,42 @@ typedef struct ForeignPath
 } ForeignPath;
 
 /*
+ * CustomPath represents a scan using custom logic
+ *
+ * Extension (that performs as custom-plan provider) can adds an alternative
+ * path using its custom type being delivered from CustomPath.
+ * They can store their private data on the extra fields of their custom
+ * object. A set of common methods are represented as function pointers in
+ * CustomPathMethods structure; extension has to set up then correctly.
+ */
+struct CustomPathMethods;
+struct Plan;		/* not to include plannodes.h here */
+
+#define CUSTOMPATH_SUPPORT_BACKWARD_SCAN	0x0001
+#define CUSTOMPATH_SUPPORT_MARK_RESTORE		0x0002
+
+typedef struct CustomPath
+{
+	Path        path;
+	uint32		flags;
+	const struct CustomPathMethods *methods;
+} CustomPath;
+
+typedef struct CustomPathMethods
+{
+	const char *CustomName;
+	void	(*CreateCustomScanPath)(PlannerInfo *root,
+									RelOptInfo *baserel,
+									RangeTblEntry *rte);
+	struct Plan	*(*PlanCustomPath)(PlannerInfo *root,
+								   RelOptInfo *rel,
+								   CustomPath *best_path,
+								   List *tlist,
+								   List *clauses);
+	void    (*TextOutCustomPath)(StringInfo str, const CustomPath *node);
+} CustomPathMethods;
+
+/*
  * AppendPath represents an Append plan, ie, successive execution of
  * several member plans.
  *
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index a0bcc82..8d75020 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,15 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					double loop_count);
 
 /*
+ * Interface definition of custom-scan providers
+ */
+extern void register_custom_path_provider(CustomPathMethods *cpp_methods);
+
+extern void create_customscan_paths(PlannerInfo *root,
+									RelOptInfo *baserel,
+									RangeTblEntry *rte);
+
+/*
  * prototypes for relnode.c
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
#2Thom Brown
thom@linux.com
In reply to: Kouhei Kaigai (#1)

On 29 September 2014 09:48, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

On Wed, Sep 17, 2014 at 7:40 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

At this moment, I revised the above portion of the patches.
create_custom_plan() was modified to call "PlanCustomPath" callback
next to the initialization of tlist and clauses.

It's probably same as what you suggested.

create_custom_plan() is mis-named. It's actually only applicable to the
custom-scan case, because it's triggered by create_plan_recurse() getting
a path node with a T_CustomScan pathtype. Now, we could change that;
although in general create_plan_recurse() dispatches on pathtype, we could
make CustomPath an exception; the top of that function could say if
(IsA(best_path, CustomPath)) { /* do custom stuff */ }. But the problem
with that idea is that, when the custom path is specifically a custom scan,
rather than a join or some other thing, you want to do all of the same
processing that's in create_scan_plan().

So I think what should happen is that create_plan_recurse() should handle
T_CustomScan the same way it handles T_SeqScan, T_IndexScan, et
al: by calling create_scan_plan(). The switch inside that function can
then call a function create_customscan_plan() if it sees T_CustomScan. And
that function will be simpler than the
create_custom_plan() that you have now, and it will be named correctly,
too.

Fixed, according to what you suggested. It seems to me create_customscan_plan()
became more simplified than before.
Probably, it will minimize the portion of special case handling if CustomScan
with scanrelid==0 replaces built-in join plan in the future version.

In ExplainNode(), I think sname should be set to "Custom Scan", not "Custom".
And further down, the custom_name should be printed as "Custom Plan
Provider" not just "Custom".

Fixed. I added an additional regression test to check EXPLAIN output
if not a text format.

setrefs.c has remaining handling for the scanrelid = 0 case; please remove
that.

Sorry, I removed it, and checked the patch again to ensure here is no similar
portions.

Thanks for your reviewing.

pgsql-v9.5-custom-scan.part-2.v11.patch

+GetSpecialCustomVar(CustomPlanState *node,
+                    Var *varnode,
+                    PlanState **child_ps);

This doesn't seem to strictly match the actual function:

+GetSpecialCustomVar(PlanState *ps, Var *varnode, PlanState **child_ps)

--
Thom

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Thom Brown (#2)

2014-09-29 20:26 GMT+09:00 Thom Brown <thom@linux.com>:

On 29 September 2014 09:48, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

On Wed, Sep 17, 2014 at 7:40 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

At this moment, I revised the above portion of the patches.
create_custom_plan() was modified to call "PlanCustomPath" callback
next to the initialization of tlist and clauses.

It's probably same as what you suggested.

create_custom_plan() is mis-named. It's actually only applicable to the
custom-scan case, because it's triggered by create_plan_recurse() getting
a path node with a T_CustomScan pathtype. Now, we could change that;
although in general create_plan_recurse() dispatches on pathtype, we could
make CustomPath an exception; the top of that function could say if
(IsA(best_path, CustomPath)) { /* do custom stuff */ }. But the problem
with that idea is that, when the custom path is specifically a custom scan,
rather than a join or some other thing, you want to do all of the same
processing that's in create_scan_plan().

So I think what should happen is that create_plan_recurse() should handle
T_CustomScan the same way it handles T_SeqScan, T_IndexScan, et
al: by calling create_scan_plan(). The switch inside that function can
then call a function create_customscan_plan() if it sees T_CustomScan. And
that function will be simpler than the
create_custom_plan() that you have now, and it will be named correctly,
too.

Fixed, according to what you suggested. It seems to me create_customscan_plan()
became more simplified than before.
Probably, it will minimize the portion of special case handling if CustomScan
with scanrelid==0 replaces built-in join plan in the future version.

In ExplainNode(), I think sname should be set to "Custom Scan", not "Custom".
And further down, the custom_name should be printed as "Custom Plan
Provider" not just "Custom".

Fixed. I added an additional regression test to check EXPLAIN output
if not a text format.

setrefs.c has remaining handling for the scanrelid = 0 case; please remove
that.

Sorry, I removed it, and checked the patch again to ensure here is no similar
portions.

Thanks for your reviewing.

pgsql-v9.5-custom-scan.part-2.v11.patch

+GetSpecialCustomVar(CustomPlanState *node,
+                    Var *varnode,
+                    PlanState **child_ps);

This doesn't seem to strictly match the actual function:

+GetSpecialCustomVar(PlanState *ps, Var *varnode, PlanState **child_ps)

It's more convenient if the first argument is PlanState, because
GetSpecialCustomVar() is called towards all the suspicious special
var-node that might be managed by custom-plan provider.
If we have to ensure its first argument is CustomPlanState on the
caller side, it makes function's invocation more complicated.
Also, the callback portion is called only when PlanState is
CustomPlanState, so it is natural to take CustomPlanState for
argument of the callback interface.
Do we need to match the prototype of wrapper function with callback?

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#3)

On Mon, Sep 29, 2014 at 9:04 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

Do we need to match the prototype of wrapper function with callback?

Yes.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#4)
3 attachment(s)

On Mon, Sep 29, 2014 at 9:04 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

Do we need to match the prototype of wrapper function with callback?

Yes.

OK, I fixed up the patch part-2, to fit declaration of GetSpecialCustomVar()
with corresponding callback.

Also, a noise in the part-3 patch, by git-pull, was removed.

Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-v9.5-custom-scan.part-3.v12.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-3.v12.patchDownload
 contrib/Makefile                       |   1 +
 contrib/ctidscan/Makefile              |  18 +
 contrib/ctidscan/ctidscan.c            | 885 +++++++++++++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control      |   5 +
 contrib/ctidscan/expected/ctidscan.out | 331 ++++++++++++
 contrib/ctidscan/sql/ctidscan.sql      |  59 +++
 src/include/catalog/pg_operator.h      |   3 +
 7 files changed, 1302 insertions(+)

diff --git a/contrib/Makefile b/contrib/Makefile
index b37d0dd..9b4b6ad 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..fbea380
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,18 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+EXTENSION = ctidscan
+
+REGRESS = 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..a8af99e
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,885 @@
+/*
+ * 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 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/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/spccache.h"
+
+PG_MODULE_MAGIC;
+
+typedef struct {
+	CustomPath		cpath;
+	List		   *ctid_quals;
+} CtidScanPath;
+
+typedef struct {
+	CustomScan		cscan;
+	List		   *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+	CustomScanState	css;
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CtidScanState;
+
+/* function declarations */
+void	_PG_init(void);
+
+static void CreateCtidScanPath(PlannerInfo *root,
+							   RelOptInfo *baserel,
+							   RangeTblEntry *rte);
+static Plan *PlanCtidScanPath(PlannerInfo *root,
+							  RelOptInfo *rel,
+							  CustomPath *best_path,
+							  List *tlist,
+							  List *clauses);
+static void TextOutCtidScanPath(StringInfo str, const CustomPath *cpath);
+
+static void SetCtidScanPlanRef(PlannerInfo *root,
+							   CustomScan *custom_plan,
+							   int rtoffset);
+static void FinalizeCtidScanPlan(PlannerInfo *root,
+								 CustomScan *custom_plan,
+								 bool (*finalize_primnode)(),
+								 void *finalize_context);
+static Node *CreateCtidScanState(CustomScan *custom_plan);
+static void TextOutCtidScanPlan(StringInfo str, const CustomScan *node);
+static CustomScan *CopyCtidScanPlan(const CustomScan *from);
+
+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 */
+	CreateCtidScanPath,		/* CreateCustomScanPath */
+	PlanCtidScanPath,		/* PlanCustomPath */
+	TextOutCtidScanPath,	/* TextOutCustomPath */
+};
+
+static CustomScanMethods	ctidscan_scan_methods = {
+	"ctidscan",				/* CustomName */
+	SetCtidScanPlanRef,		/* SetCustomScanRef */
+	FinalizeCtidScanPlan,	/* FinalizeCustomScan */
+	CreateCtidScanState,	/* CreateCustomScanState */
+	TextOutCtidScanPlan,	/* TextOutCustomScan */
+	CopyCtidScanPlan,		/* CopyCustomScan */
+};
+
+static CustomExecMethods	ctidscan_exec_methods = {
+	"ctidscan",				/* CustomName */
+	BeginCtidScan,			/* BeginCustomScan */
+	ExecCtidScan,			/* ExecCustomScan */
+	EndCtidScan,			/* EndCustomScan */
+	ReScanCtidScan,			/* ReScanCustomScan */
+	NULL,					/* MarkPosCustomScan */
+	NULL,					/* RestrPosCustomScan */
+	ExplainCtidScan,		/* ExplainCustomScan */
+	NULL,					/* GetSpecialCustomVar */
+};
+
+#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 chained 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,
+				  CtidScanPath *ctid_path)
+{
+	Path	   *path = &ctid_path->cpath.path;
+	List	   *ctid_quals = ctid_path->ctid_quals;
+	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 (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)
+	{
+		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 indeterministic until
+		 * 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 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,
+							  &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
+	 * (logic copied from 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 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;
+
+	path->startup_cost = startup_cost;
+	path->total_cost = startup_cost + run_cost;
+}
+
+/*
+ * CreateCtidScanPath - entrypoint of the series of custom-scan execution.
+ * It adds CustomPath if referenced relation has inequality expressions on
+ * the ctid system column.
+ */
+static void
+CreateCtidScanPath(PlannerInfo *root, RelOptInfo *baserel, 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;
+
+	/* 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);
+		ctid_quals = list_concat(ctid_quals, 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 (ctid_quals != NIL)
+	{
+		CtidScanPath *ctid_path;
+		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;
+
+		ctid_path = palloc0(sizeof(CtidScanPath));
+		ctid_path->cpath.path.type = T_CustomPath;
+		ctid_path->cpath.path.pathtype = T_CustomScan;
+		ctid_path->cpath.path.parent = baserel;
+		ctid_path->cpath.path.param_info
+			= get_baserel_parampathinfo(root, baserel, required_outer);
+		ctid_path->cpath.flags = CUSTOMPATH_SUPPORT_BACKWARD_SCAN;
+		ctid_path->cpath.methods = &ctidscan_path_methods;
+		ctid_path->ctid_quals = ctid_quals;
+
+		CTidEstimateCosts(root, baserel, ctid_path);
+
+		add_path(baserel, &ctid_path->cpath.path);
+	}
+}
+
+/*
+ * CreateCtidScanPlan - 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)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_scan;
+	List		   *ctid_quals = ctid_path->ctid_quals;
+
+	ctid_scan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_scan, T_CustomScan);
+	ctid_scan->cscan.flags = best_path->flags;
+	ctid_scan->cscan.methods = &ctidscan_scan_methods;
+
+	/* set scanrelid */
+	ctid_scan->cscan.scan.scanrelid = rel->relid;
+	/* set targetlist as is  */
+	ctid_scan->cscan.scan.plan.targetlist = tlist;
+	/* reduce RestrictInfo list to bare expressions */
+	ctid_scan->cscan.scan.plan.qual
+		= extract_actual_clauses(clauses, false);
+	/* set ctid related quals */
+	if (best_path->path.param_info)
+		ctid_quals = (List *)
+			replace_nestloop_params(root, (Node *)ctid_quals);
+	ctid_scan->ctid_quals = ctid_quals;
+
+	/*
+	 * If there are any pseudoconstant clauses attached to this node, insert a
+	 * gating Result node that evaluates the pseudoconstants as one-time
+	 * quals.
+	 */
+	if (root->hasPseudoConstantQuals)
+	{
+		List   *pseudoconstants = extract_actual_clauses(clauses, true);
+
+		if (pseudoconstants != NIL)
+			return (Plan *) make_result(root,
+										tlist,
+										(Node *) pseudoconstants,
+										(Plan *) ctid_scan);
+	}
+	return (Plan *) ctid_scan;
+}
+
+/*
+ * TextOutCtidScanPath - A method of CustomPath; that shows a text
+ * representation of the supplied CustomPath object.
+ */
+static void
+TextOutCtidScanPath(StringInfo str, const CustomPath *cpath)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *)cpath;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_path->ctid_quals));
+}
+
+/*
+ * SetCtidScanPlanRef - A method of CustomScan; that fixes up rtindex
+ * of Var nodes
+ */
+#define fix_scan_list(root, lst, rtoffset)	\
+	((List *) fix_scan_expr(root, (Node *) (lst), rtoffset))
+
+static void
+SetCtidScanPlanRef(PlannerInfo *root,
+				   CustomScan *custom_plan,
+				   int rtoffset)
+{
+	CtidScanPlan   *ctidscan = (CtidScanPlan *) custom_plan;
+	Scan		   *scan = &ctidscan->cscan.scan;
+
+	scan->scanrelid += rtoffset;
+	scan->plan.targetlist =
+		fix_scan_list(root, scan->plan.targetlist, rtoffset);
+	scan->plan.qual =
+		fix_scan_list(root, scan->plan.qual, rtoffset);
+	ctidscan->ctid_quals =
+		fix_scan_list(root, ctidscan->ctid_quals, rtoffset);
+}
+
+/*
+ * FinalizeCtidScanPlan - A method of CustomScan; that handles callbacks
+ * by finalize_plan().
+ */
+static void
+FinalizeCtidScanPlan(PlannerInfo *root,
+					 CustomScan *custom_plan,
+					 bool (*finalize_primnode)(),
+					 void *finalize_context)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) custom_plan;
+
+	/* applies finalize_primnode() on ctid_quals also */
+	finalize_primnode((Node *)ctid_plan->ctid_quals, finalize_context);
+}
+
+/*
+ * 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;
+}
+
+/*
+ * TextOutCtidScanPlan - A method of CustomScan; that generates text
+ * representation of the given object.
+ */
+static void
+TextOutCtidScanPlan(StringInfo str, const CustomScan *node)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_plan->ctid_quals));
+}
+
+/*
+ * CopyCtidScanPlan - A method of CustomScan; that create a copied object.
+ */
+static CustomScan *
+CopyCtidScanPlan(const CustomScan *from)
+{
+	CtidScanPlan   *oldnode = (CtidScanPlan *) from;
+	CtidScanPlan   *newnode = palloc0(sizeof(CtidScanPlan));
+
+	NodeSetTag(newnode, T_CustomScan);
+	newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+	return &newnode->cscan;
+}
+
+/*
+ * 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;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node->ss.ps.plan;
+
+	/*
+	 * In case of custom-plan 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 *)ctid_plan->ctid_quals, &node->ss.ps);
+
+	/* Do nothing anymore in EXPLAIN (no ANALYZE) case. */
+	if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
+		return;
+
+	/* scandesc shall be set later */
+	ctss->css.ss.ss_currentScanDesc = NULL;
+}
+
+/*
+ * 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;
+		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);
+		}
+		else
+			elog(ERROR, "could not identify CTID variable");
+
+		itemptr = (ItemPointer)
+			DatumGetPointer(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;
+		}
+
+		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),
+								 scan->rs_nblocks - 1);
+		scan->rs_startblock = blknum;
+	}
+	else if (direction == BackwardScanDirection && has_ubound)
+	{
+		BlockNumber	blknum = Min(BlockIdGetBlockNumber(&ip_max.ip_blkid),
+								 scan->rs_nblocks - 1);
+		scan->rs_startblock = 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;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->css.ss.ps.plan;
+
+	/* logic copied from show_qual and show_expression */
+	if (ctid_plan->ctid_quals)
+	{
+		bool	useprefix = es->verbose;
+		Node   *qual;
+		List   *context;
+		char   *exprstr;
+
+		/* Convert AND list to explicit AND */
+		qual = (Node *) make_ands_explicit(ctid_plan->ctid_quals);
+
+		/* 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("ctid quals", exprstr, es);
+	}
+}
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	register_custom_path_provider(&ctidscan_path_methods);
+}
diff --git a/contrib/ctidscan/ctidscan.control b/contrib/ctidscan/ctidscan.control
new file mode 100644
index 0000000..ad63432
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.control
@@ -0,0 +1,5 @@
+# ctidscan extension
+comment = 'example implementation for custom-plan interface'
+default_version = '1.0'
+module_pathname = '$libdir/ctidscan'
+relocatable = true
diff --git a/contrib/ctidscan/expected/ctidscan.out b/contrib/ctidscan/expected/ctidscan.out
new file mode 100644
index 0000000..9963a43
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,331 @@
+--
+-- Regression Tests for Custom Plan 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)
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+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))
+   ctid quals: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(3 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)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom Scan (ctidscan) on t2
+               Filter: (ctid > '(1,75)'::tid)
+               ctid quals: (ctid > '(1,75)'::tid)
+(12 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)
+
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+                        WHERE b 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: ((b ~~ '%abc%'::text) AND (ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+   ctid quals: ((ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+(3 rows)
+
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+   Filter: ((b ~~ '%abc%'::text) AND (ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+   ctid quals: ((ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+(3 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>                                              +
+       <Alias>t1</Alias>                                                                                  +
+       <Filter>((b ~~ '%abc%'::text) AND (ctid &gt;= '(0,0)'::tid) AND (ctid &lt;= '(5,0)'::tid))</Filter>+
+       <ctid-quals>((ctid &gt;= '(0,0)'::tid) AND (ctid &lt;= '(5,0)'::tid))</ctid-quals>                 +
+     </Plan>                                                                                              +
+   </Query>                                                                                               +
+ </explain>
+(1 row)
+
+-- Test cleanup
+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/contrib/ctidscan/sql/ctidscan.sql b/contrib/ctidscan/sql/ctidscan.sql
new file mode 100644
index 0000000..26c22c2
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,59 @@
+--
+-- Regression Tests for Custom Plan 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;
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+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;
+
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+                        WHERE b 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);
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index d7dcd1c..0dd131c 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -166,10 +166,13 @@ 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");
pgsql-v9.5-custom-scan.part-2.v12.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-2.v12.patchDownload
 doc/src/sgml/custom-plan.sgml           | 359 ++++++++++++++++++++++++++++++++
 doc/src/sgml/filelist.sgml              |   1 +
 doc/src/sgml/postgres.sgml              |   1 +
 src/backend/commands/explain.c          |  25 +++
 src/backend/executor/Makefile           |   2 +-
 src/backend/executor/execAmi.c          |  38 +++-
 src/backend/executor/execProcnode.c     |  14 ++
 src/backend/executor/nodeCustom.c       | 127 +++++++++++
 src/backend/nodes/copyfuncs.c           |  26 +++
 src/backend/nodes/outfuncs.c            |  15 ++
 src/backend/optimizer/path/costsize.c   |   2 +-
 src/backend/optimizer/plan/createplan.c |   3 +-
 src/backend/optimizer/plan/setrefs.c    |  26 ++-
 src/backend/optimizer/plan/subselect.c  |  21 ++
 src/backend/utils/adt/ruleutils.c       |  70 +++++++
 src/include/executor/executor.h         |   3 +-
 src/include/executor/nodeCustom.h       |  30 +++
 src/include/nodes/execnodes.h           |  40 ++++
 src/include/nodes/nodes.h               |   1 +
 src/include/nodes/plannodes.h           |  20 ++
 src/include/optimizer/planmain.h        |   3 +
 21 files changed, 817 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/custom-plan.sgml b/doc/src/sgml/custom-plan.sgml
new file mode 100644
index 0000000..fc4b187
--- /dev/null
+++ b/doc/src/sgml/custom-plan.sgml
@@ -0,0 +1,359 @@
+<!-- doc/src/sgml/custom_plan.sgml -->
+
+<chapter id="custom-plan">
+ <title>Writing A Custom Plan Provider</title>
+ <indexterm zone="custom-plan">
+  <primary>custom plan provider</primary>
+  <secondary>handler for</secondary>
+ </indexterm>
+
+ <para>
+  PostgreSQL has various kind of built-in plan nodes that implement
+  a particular portion to process the supplied SQL queries.
+  For example, SeqScan node implements full table scan, HashJoin node
+  implements tables join using a hash table and so on.
+ </para>
+ <para>
+  The custom-plan interface allows extensions to provide alternative
+  query execution paths, in addition to the built-in ones. Query planner
+  will choose the cheapest path towards a particular relation(s) scan or
+  join in all the proposed ones by built-in and extensions.
+  Once a proposed custom plan got picked up, callback functions associated
+  with the node shall be called and extension will get control to process
+  the task. We call a set of callback functions associated with a certain
+  custom node the custom-path, custom-plan or custom-scan depending on
+  the things it provides; just "provider" in short.
+ </para>
+
+ <sect1 id="custom-plan-overall-steps">
+  <title>Custom Plan Overall Steps</title>
+  <para>
+   A custom-path provider shall be registered by
+   <literal>register_custom_path_provider</> that takes a pointer of
+   <literal>CustomPathMethods</> table which also holds some function
+   pointers.
+   It is usually installed on <literal>_PG_init()</> of extension
+   when either of preload configuration or <xref linkend="sql-load">
+   command loads the extension of the provider.
+  </para>
+  <para>
+   Onec a provider gets registered, the built-in query planner calls
+   back <literal>CreateCustomScanPath</> to ask whether the provider
+   can offer an alternative path to scan the referenced relation, or
+   not.
+   If it is available to provide, this callback will construct
+   <literal>CustomPath</> with cost estimation.
+   Then, query planner compares the alternative paths with built-in
+   paths based on the estimated cost, then the cheapest one shall be
+   chosen.
+  </para>
+  <para>
+   Usually, a provider may need some private fields to store something
+   valuable properties for the path. In this case, extension can extends
+   the <literal>CustomPath</> structure using a new data structure
+   definition that takes <literal>CustomPath</> on the head then followed
+   by private fields.
+<programlisting>
+typedef struct MySpecialPath
+{
+    CustomPath  cpath;
+    List       *some_list;
+    Bitmapset  *some_bitmap;
+       :
+} MySpecialPath;
+</programlisting>
+   The above example shows a structure delivered from <literal>CustomPath</>
+   with some private fields.
+   We assume such kind of manner, like as object oriented language doing,
+   to inject private fields of the provider on other nodes like
+   <literal>CustomScan</>.
+  </para>
+  <para>
+   Once a <literal>CustomPath</> got chosen, its callback shall be kicked
+   to populate <literal>CustomScan</> (or its inheritance) node; that is
+   the only available node type, right now. It consists of a part of query
+   plan tree, instead of the built-in nodes.
+   In the same manner, the <literal>CustomScan</> node populates
+   a <literal>CustomScanState</> (or its inheritance) node; that manages   
+   execution-time status of the custom-scan node and a set of callbacks.
+  </para>
+ </sect1>
+
+ <sect1 id="custom-path-callbacks">
+  <title>Custom Path Callbacks</title>
+  <para>
+   This section introduces callback functions of <structname>CustomPath</>
+   structure; defined in the <structname>CustomPathMethods</>, and
+   related flags.
+  </para>
+  <para>
+<programlisting>
+void
+CreateCustomScanPath(PlannerInfo *root,
+                     RelOptInfo *baserel,
+                     RangeTblEntry *rte);
+</programlisting>
+   As mentioned above, it construct <structname>CustomPath</> (or inherited
+   data type) node if it is available to provide an alternative scan path
+   on the supplied relation and qualifiers; that shall be informed using
+   <literal>RelOptInfo</> argument.
+   The constructed <structname>CustomPath</> node shall be added to
+   candidate path of the relation using <literal>add_path</>.
+  </para>
+  <para>
+<programlisting>
+Plan *
+PlanCustomPath(PlannerInfo *root,
+               RelOptInfo *rel,
+               CustomPath *best_path,
+               List *tlist,
+               List *clauses);
+</programlisting>
+   It populates a <struct>CustomScan</> or its inheritance node
+   according to the supplied <structname>CustomPath</> node which was
+   constructed on the custom path handler function then chosen by the
+   query planner.
+   This callback has to allocate a <struct>CustomScan</> node because
+   only provider can know exact size to be allocated if it is extended
+   to have private fields.
+   This callback is assumed to allocate the <struct>CustomScan</> node
+   with <structname>CustomPlanMethods</> callbacks table, then initialize
+   common plan-node fields and arbitrary private fields.
+  </para>
+
+  <para>
+<programlisting>
+void
+TextOutCustomPath(StringInfo str, const CustomPath *node);
+</programlisting>
+   It makes a text representation of custom path node. If provider extends
+   <structname>CustomPath</> data type, it shall to put private fields on
+   the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <structname>CustomPath</> are handled by
+   backend, so extension needs to do nothing special.
+  </para>
+
+  <para>
+   <literal>CustomPath</> structure can have flags that informs capability
+   of this custom-path to the query planner. Every flags can be combined
+   with OR-operation, then set to <literal>flags</> field.
+   The value of <literal>flags</> should be unchanged across node
+   population, because the plan tree is constructed based on the properties
+   being preliminary configured.
+  </para>
+  <para>
+   <literal>CUSTOMPATH_SUPPORT_BACKWARD_SCAN</> informs the planner
+   this custom-path supports backward scan.
+   <literal>CUSTOMPATH_SUPPORT_MARK_RESTORE</> informs the planner
+   this custom-path supports mark and restore position.
+  </para>
+ </sect1>
+
+ <sect1 id="custom-scan-callbacks">
+  <title>Custom Scan Callbacks</title>
+  <para>
+   This section introduces callback functions of <structname>CustomScan</>
+   structure; defined in the <structname>CustomScanMethods</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+SetCustomScanRef(PlannerInfo *root,
+                 CustomScan *cscan,
+                 int rtoffset);
+</programlisting>
+   It adjusts <literal>varno</> and <literal>varattno</> of var-nodes in
+   the expression tree chained from <structname>CustomScan</> node
+   (including private fields in the inherited data type).
+   In case of usual relation scan, <literal>rtoffset</> shall be added to
+   <literal>varno</> of var-nodes and <literal>scanrelid</> of plan node,
+   then <function>fix_scan_expr</> shall be called on expression nodes to
+   track plan dependency.
+  </para>
+
+  <para>
+<programlisting>
+void
+FinalizeCustomScan(PlannerInfo *root,
+                   CustomScan *cscan,
+                   bool (*finalize_primnode)(),
+                   void *finalize_context);
+</programlisting>
+   It is an optional callback, which applies <literal>finalize_primenode</>
+   on the expression nodes of private fields because only provider knows
+   what private fields are expression node to be finalized.
+   Note that backend applies <literal>finalize_primenode</> on
+   the <literal>tlist</> and <literal>qual</> of the base plan node,
+   so provider shall do nothing special, if it has no private expression
+   node. Also, it handles recursive stuff on the <literal>lefttree</> and
+   <literal>righttree</>, so it does not need to handle recursive walks.
+  </para>
+
+  <para>
+<programlisting>
+Node *
+CreateCustomScanState(CustomScan *cscan);
+</programlisting>
+   It populates a <structname>CustomScanState</> (or its inheritance)
+   node according to the supplied <structname>CustomScan</> node that was
+   preliminary constructed on the beginning of query executor.
+   Only provider can know exact size of the node to be allocated, this
+   callback allocate a <structname>CustomScanState</> node
+   with <structfield>CustomExecMethods</> callbacks table and arbitrary
+   private fields.
+  </para>
+  <para>
+   Note that main purpose of this callback is allocation of
+   <literal>CustomScanState</> node, not initialization of individual
+   fields because it shall be handled on the <structfield>BeginCustomPlan</>
+   callback to be invoked next to the common usual initialization.
+  </para>
+
+  <para>
+<programlisting>
+void
+TextOutCustomScan(StringInfo str,
+                  const CustomScan *node);
+</programlisting>
+   It makes a text representation of custom-scan node. If provider extends
+   <structfield>CustomScan</> data type, it shall put private fields on
+   the supplied <literal>StringInfo</> with text form.
+   Note that common fields within <structname>CustomPlan</> are handled
+   by the backend, so extension needs to do nothing special.
+  </para>
+
+  <para>
+<programlisting>
+CustomScan *
+CopyCustomScan(const CustomScan *from);
+</programlisting>
+   It duplicate every private fields of the inheritance of
+   <literal>CustomScan</> onto a newly allocated node.
+   In case when provider extends <literal>CustomScan</> node, only provider
+   can know exact size to be allocated and existence of the private fields.
+   So, it shall be responsible for node allocation and copy of private fields,
+   although the backend copies the fields of
+   <literal>CustomScan</>.
+  </para>
+ </sect1>
+
+ <sect1 id="custom-exec-callbacks">
+  <title>Custom Executor Callbacks</title>
+  <para>
+   This section introduces callback functions of <structname>CustomPlanState</>
+   structure; defined in the <structname>CustomExecMethods</>.
+  </para>
+  <para>
+<programlisting>
+void
+BeginCustomScan(CustomScanState *node,
+                EState *estate,
+                int eflags);
+</programlisting>
+   It begins execution of custom-scan. This callback is invoked during
+   executor startup to initialize the supplied <literal>CustomScanState</>
+   that was constructed on the <literal>CreateCustomScanState</> above.
+   The provider shall have initialization of its private fields and common
+   fields within <literal>CustomScanState</> if needed, because the backend
+   code already applies some fundamental initializations.
+  </para>
+
+  <para>
+<programlisting>
+TupleTableSlot *
+ExecCustomScan(CustomScanState *node);
+</programlisting>
+   It fetches one row from the custom-plan node, returning it in a tuple
+   table slot (<literal>ps_ResultTupleSlot</> of <literal>PlanState</>
+   shall be used) or <literal>NULL</> if no more rows are available.
+   The tuple table slot infrastructure allows either a physical or virtual
+   tuple to be returned; in most cases the latter choice is preferable from
+   a performance standpoint.
+   The rows being returned have to match the tuple-descriptor of the
+   <structname>PlanState</>
+  </para>
+  <para>
+   Note that this call is under a short-lived memory context that will be
+   reset for each invocation. So, it may be a good choice to switch
+   <literal>es_query_cxt</> of the <structname>EState</>, to acquire
+   memory in per-scan duration.
+  </para>
+
+  <para>
+<programlisting>
+void
+EndCustomScan(CustomScanState *node);
+</programlisting>
+   It ends the execution of custom-scan and release any resources held by
+   this node. If provider acquired resources that is not released
+   automatically at end of executor, it is responsibility of the provider.
+  </para>
+
+  <para>
+<programlisting>
+void
+ReScanCustomScan(CustomScanState *node);
+</programlisting>
+   It restarts the scan from the beginning.  Note that any parameters
+   the scan depends on may have changed value, so the new scan does not
+   necessarily return exactly the same rows.
+  </para>
+
+  <para>
+<programlisting>
+void *
+MarkPosCustomScan(CustomScanState *node);
+</programlisting>
+   It is an optional callback if <literal>CUSTOMPATH_SUPPORT_MARK_RESTORE</>
+   is set on the <literal>flags</>. Elsewhere, it should put <literal>NULL</>
+   on the callback table because never called.
+   It saves current scan position on somewhere in private fields of
+   <structname>CustomScanState</> (or its inheritance), to restore
+   the position later.
+  </para>
+
+  <para>
+<programlisting>
+void *
+RestrPosCustomScan(CustomScanState *node);
+</programlisting>
+   It is an optional callback if <literal>CUSTOMPATH_SUPPORT_MARK_RESTORE</>
+   is set on the <literal>flags</>. Elsewhere, it should put <literal>NULL</>
+   on the callback table because never called.
+   It restores the previous scan position saved by
+   the <structname>MarkPosCustomScan</> above.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomScan(CustomScanState *node,
+                  List *ancestors,
+                  ExplainState *es);
+</programlisting>
+   It is an optional callback, to show custom-scan specific explain output.
+  </para>
+
+  <para>
+<programlisting>
+Node   *
+GetSpecialCustomVar(CustomPlanState *node,
+                    Var *varnode,
+                    PlanState **child_ps);
+</programlisting>
+   It is an optional callback, to solve references to special varno on
+   the <literal>CustomScanState</> when <command>EXPLAIN</> needs the
+   text form of the column actually referenced.
+   In case when provider adjusted <literal>varno</> of varnodes on
+   the expression tree to use special varnos (<literal>INNER_VAR</>,
+   <literal>OUTER_VAR</> or <literal>INDEX_VAR</>), provider has
+   to inform the backend which column is mapped on the underlying plan-state.
+  </para>
+  <para>
+   This callback is expected to return <literal>Var</> node to reference
+   an actual variable on the underlying <structname>PlanState</> that shall
+   be set on the <literal>child_ps</> argument for recursive walking down.
+  </para>
+ </sect1>
+</chapter>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 5902f97..8d20594 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -92,6 +92,7 @@
 <!ENTITY nls        SYSTEM "nls.sgml">
 <!ENTITY plhandler  SYSTEM "plhandler.sgml">
 <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml">
+<!ENTITY custom-plan SYSTEM "custom-plan.sgml">
 <!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
 <!ENTITY protocol   SYSTEM "protocol.sgml">
 <!ENTITY sources    SYSTEM "sources.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 9bde108..5f415c6 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -242,6 +242,7 @@
   &nls;
   &plhandler;
   &fdwhandler;
+  &custom-plan;
   &geqo;
   &indexam;
   &gist;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 781a736..62b5e2a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -719,6 +719,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 		case T_CteScan:
 		case T_WorkTableScan:
 		case T_ForeignScan:
+		case T_CustomScan:
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
@@ -848,6 +849,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 	const char *sname;			/* node type name for non-text output */
 	const char *strategy = NULL;
 	const char *operation = NULL;
+	const char *custom_name = NULL;
 	int			save_indent = es->indent;
 	bool		haschildren;
 
@@ -936,6 +938,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomScan:
+			sname = "Custom Scan";
+			custom_name = ((CustomScan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom Scan (%s)", custom_name);
+			else
+				pname = sname;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1037,6 +1047,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			ExplainPropertyText("Parent Relationship", relationship, es);
 		if (plan_name)
 			ExplainPropertyText("Subplan Name", plan_name, es);
+		if (custom_name)
+			ExplainPropertyText("Custom Plan Provider", custom_name, es);
 	}
 
 	switch (nodeTag(plan))
@@ -1050,6 +1062,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_CteScan:
 		case T_WorkTableScan:
 		case T_ForeignScan:
+		case T_CustomScan:
 			ExplainScanTarget((Scan *) plan, es);
 			break;
 		case T_IndexScan:
@@ -1353,6 +1366,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
 			break;
+		case T_CustomScan:
+			{
+				CustomScanState *css = (CustomScanState *) planstate;
+
+				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+				if (plan->qual)
+					show_instrumentation_count("Rows Removed by Filter", 1,
+											   planstate, es);
+				if (css->methods->ExplainCustomScan)
+					css->methods->ExplainCustomScan(css, ancestors, es);
+			}
+			break;
 		case T_NestLoop:
 			show_upper_qual(((NestLoop *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 6081b56..af707b0 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -16,7 +16,7 @@ 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 \
-       nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
+       nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeCustom.o nodeHash.o \
        nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \
        nodeLimit.o nodeLockRows.o \
        nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 640964c..b14e08c 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"
@@ -49,6 +50,7 @@
 #include "executor/nodeWindowAgg.h"
 #include "executor/nodeWorktablescan.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/relation.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
@@ -197,6 +199,10 @@ ExecReScan(PlanState *node)
 			ExecReScanForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecReScanCustomScan((CustomScanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
@@ -291,6 +297,10 @@ ExecMarkPos(PlanState *node)
 			ExecValuesMarkPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecCustomMarkPos((CustomScanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialMarkPos((MaterialState *) node);
 			break;
@@ -348,6 +358,10 @@ ExecRestrPos(PlanState *node)
 			ExecValuesRestrPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecCustomRestrPos((CustomScanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialRestrPos((MaterialState *) node);
 			break;
@@ -379,9 +393,9 @@ ExecRestrPos(PlanState *node)
  * and valuesscan support is actually useless code at present.)
  */
 bool
-ExecSupportsMarkRestore(NodeTag plantype)
+ExecSupportsMarkRestore(Path *pathnode)
 {
-	switch (plantype)
+	switch (pathnode->pathtype)
 	{
 		case T_SeqScan:
 		case T_IndexScan:
@@ -403,6 +417,16 @@ ExecSupportsMarkRestore(NodeTag plantype)
 			 */
 			return false;
 
+		case T_CustomScan:
+			{
+				CustomPath *cpath = (CustomPath *) pathnode;
+
+				Assert(IsA(cpath, CustomPath));
+				if (cpath->flags & CUSTOMPATH_SUPPORT_MARK_RESTORE)
+					return true;
+			}
+			break;
+
 		default:
 			break;
 	}
@@ -465,6 +489,16 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomScan:
+			{
+				uint32	flags = ((CustomScan *) node)->flags;
+
+				if (TargetListSupportsBackwardScan(node->targetlist) &&
+					(flags & CUSTOMPATH_SUPPORT_BACKWARD_SCAN) != 0)
+					return true;
+			}
+			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 c0189eb..e27c062 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/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..a8155d7
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,127 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ *		Routines to handle execution of custom scan node
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "executor/executor.h"
+#include "executor/nodeCustom.h"
+#include "nodes/execnodes.h"
+#include "nodes/plannodes.h"
+#include "parser/parsetree.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+CustomScanState *
+ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
+{
+	CustomScanState    *css;
+	Relation			scan_rel;
+
+	/* populate a CustomScanState according to the CustomScan */
+	css = (CustomScanState *)cscan->methods->CreateCustomScanState(cscan);
+	Assert(IsA(css, CustomScanState));
+
+	/* fill up fields of ScanState */
+	css->ss.ps.plan = &cscan->scan.plan;
+	css->ss.ps.state = estate;
+
+	/* create expression context for node */
+	ExecAssignExprContext(estate, &css->ss.ps);
+	css->ss.ps.ps_TupFromTlist = false;
+
+	/* initialize child expressions */
+	css->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) cscan->scan.plan.targetlist,
+					 (PlanState *) css);
+	css->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) cscan->scan.plan.qual,
+					 (PlanState *) css);
+
+	/* initialization of result tuple slot  */
+	ExecInitResultTupleSlot(estate, &css->ss.ps);
+	ExecAssignResultTypeFromTL(&css->ss.ps);
+
+	/*
+	 * Also, initialization of relation scan stuff if custom-scan
+	 * node intends to run on a particular plain relation.
+	 * Elsewhere, custom-scan provider should be responsible to put
+	 * proper initialization of scan tuple-slot and projection info
+	 * by itself.
+	 */
+	scan_rel = ExecOpenScanRelation(estate, cscan->scan.scanrelid, eflags);
+	css->ss.ss_currentRelation = scan_rel;
+	css->ss.ss_currentScanDesc = NULL;	/* set by provider on demand */
+	ExecInitScanTupleSlot(estate, &css->ss);
+	ExecAssignScanType(&css->ss, RelationGetDescr(scan_rel));
+	ExecAssignScanProjectionInfo(&css->ss);
+
+	/*
+	 * The callback of custom-scan provider applies the final initialization
+	 * of the custom-scan-state node according to its logic.
+	 */
+	css->methods->BeginCustomScan(css, estate, eflags);
+
+	return css;
+}
+
+TupleTableSlot *
+ExecCustomScan(CustomScanState *node)
+{
+	Assert(node->methods->ExecCustomScan != NULL);
+	return node->methods->ExecCustomScan(node);
+}
+
+void
+ExecEndCustomScan(CustomScanState *node)
+{
+	Assert(node->methods->EndCustomScan != NULL);
+	node->methods->EndCustomScan(node);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&node->ss.ps);
+
+	/* Clean out the tuple table */
+	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+	if (node->ss.ss_ScanTupleSlot)
+		ExecClearTuple(node->ss.ss_ScanTupleSlot);
+
+	/* Close the heap relation */
+	ExecCloseScanRelation(node->ss.ss_currentRelation);
+}
+
+void
+ExecReScanCustomScan(CustomScanState *node)
+{
+	Assert(node->methods->ReScanCustomScan != NULL);
+	node->methods->ReScanCustomScan(node);
+}
+
+void
+ExecCustomMarkPos(CustomScanState *node)
+{
+	if (!node->methods->MarkPosCustomScan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("custom-scan \"%s\" does not support MarkPos",
+						node->methods->CustomName)));
+	node->methods->MarkPosCustomScan(node);
+}
+
+void
+ExecCustomRestrPos(CustomScanState *node)
+{
+	if (!node->methods->RestrPosCustomScan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("custom-scan \"%s\" does not support MarkPos",
+						node->methods->CustomName)));
+	node->methods->RestrPosCustomScan(node);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 225756c..a471d43 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,29 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomScan
+ */
+static CustomScan *
+_copyCustomScan(const CustomScan *from)
+{
+	CustomScan		   *newnode;
+
+	newnode = from->methods->CopyCustomScan(from);
+	Assert(nodeTag(newnode) == nodeTag(from));
+
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+	COPY_SCALAR_FIELD(flags);
+	/*
+	 * NOTE: The method field of CustomScan is required to be a pointer
+	 * to a static table of callback functions. So, we don't copy the
+	 * table itself, just reference the original one.
+	 */
+	COPY_SCALAR_FIELD(methods);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -4042,6 +4065,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 8ad9c8d..aed53c7 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,18 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomScan(StringInfo str, const CustomScan *node)
+{
+	WRITE_NODE_TYPE("CUSTOMSCAN");
+
+	_outScanInfo(str, (const Scan *) node);
+	WRITE_UINT_FIELD(flags);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomScan(str, node);
+}
+
+static void
 _outJoin(StringInfo str, const Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -2863,6 +2875,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/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 0cdb790..659daa2 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -2266,7 +2266,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;
 
 	/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 189f7ef..23a26d2 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -86,7 +86,6 @@ static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
 					  Plan *outer_plan, Plan *inner_plan);
 static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path,
 					 Plan *outer_plan, Plan *inner_plan);
-static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
 static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
 static void process_subquery_nestloop_params(PlannerInfo *root,
 								 List *subplan_params);
@@ -2613,7 +2612,7 @@ create_hashjoin_plan(PlannerInfo *root,
  * root->curOuterRels are replaced by Params, and entries are added to
  * root->curOuterParams if not already present.
  */
-static Node *
+Node *
 replace_nestloop_params(PlannerInfo *root, Node *expr)
 {
 	/* No setup needed for tree walk, so away we go */
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9ddc8ad..5589de8 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -94,7 +94,6 @@ static Plan *set_subqueryscan_references(PlannerInfo *root,
 							SubqueryScan *plan,
 							int rtoffset);
 static bool trivial_subqueryscan(SubqueryScan *plan);
-static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
 static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
 static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
 static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
@@ -579,6 +578,27 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomScan:
+			{
+				CustomScan *cscan = (CustomScan *) plan;
+
+				cscan->scan.scanrelid += rtoffset;
+				cscan->scan.plan.targetlist =
+					fix_scan_list(root, cscan->scan.plan.targetlist, rtoffset);
+				cscan->scan.plan.qual =
+					fix_scan_list(root, cscan->scan.plan.qual, rtoffset);
+				/*
+				 * The core implementation applies the routine to fixup
+				 * varno on the target-list and scan qualifier.
+				 * If custom-scan has additional expression nodes on its
+				 * private fields, it has to apply same fixup on them.
+				 * Elsewhere, custom-plan provider skip this callback.
+				 */
+				if (cscan->methods->SetCustomScanRef)
+					cscan->methods->SetCustomScanRef(root, cscan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1063,7 +1083,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 */
@@ -1161,7 +1181,7 @@ fix_param_node(PlannerInfo *root, Param *p)
  * looking up operator opcode info for OpExpr and related nodes,
  * and adding OIDs from regclass Const nodes into root->glob->relationOids.
  */
-static Node *
+Node *
 fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
 {
 	fix_scan_expr_context context;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 3e7dc85..4200ec0 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2283,6 +2283,27 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomScan:
+			{
+				CustomScan *custom_scan = (CustomScan *) plan;
+
+				context.paramids = bms_add_members(context.paramids,
+												   scan_params);
+				/*
+				 * custom-scan provider is responsible to apply
+				 * finalize_primnode() on the expression node of
+				 * its private fields, but no need to apply it
+				 * on the tlist and qual of Plan node because it
+				 * is already done above.
+				 */
+				if (custom_scan->methods->FinalizeCustomScan)
+					custom_scan->methods->FinalizeCustomScan(root,
+															 custom_scan,
+															 finalize_primnode,
+															 (void *)&context);
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7237e5d..a0fba9b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5490,6 +5490,26 @@ get_utility_query_def(Query *query, deparse_context *context)
 	}
 }
 
+/*
+ * GetSpecialCustomVar
+ *
+ * It provides a way to solve a special varnode being managed by custom-
+ * scan provider. The callback informs the backend an expression tree
+ * which was replaced by a special varnode.
+ */
+static Node *
+GetSpecialCustomVar(CustomScanState *css, Var *varnode, PlanState **child_ps)
+{
+	Assert(IsA(css, CustomScanState));
+	Assert(IS_SPECIAL_VARNO(varnode->varno));
+
+	if (!css->methods->GetSpecialCustomVar)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s does not support special varno reference",
+						css->methods->CustomName)));
+	return (Node *)css->methods->GetSpecialCustomVar(css, varnode, child_ps);
+}
 
 /*
  * Display a Var appropriately.
@@ -5519,6 +5539,8 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 	int			netlevelsup;
 	deparse_namespace *dpns;
 	deparse_columns *colinfo;
+	PlanState  *child_ps = NULL;
+	Node	   *expr;
 	char	   *refname;
 	char	   *attname;
 
@@ -5543,6 +5565,29 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 		colinfo = deparse_columns_fetch(var->varno, dpns);
 		attnum = var->varattno;
 	}
+	else if (IS_SPECIAL_VARNO(var->varno) &&
+			 IsA(dpns->planstate, CustomScanState) &&
+			 (expr = GetSpecialCustomVar((CustomScanState *) dpns->planstate,
+										 var, &child_ps)) != NULL)
+	{
+		deparse_namespace	save_dpns;
+
+		if (child_ps)
+			push_child_plan(dpns, child_ps, &save_dpns);
+		/*
+		 * Force parentheses because our caller probably assumed a Var is a
+		 * simple expression.
+		 */
+		if (!IsA(expr, Var))
+			appendStringInfoChar(buf, '(');
+		get_rule_expr((Node *) expr, context, true);
+		if (!IsA(expr, Var))
+			appendStringInfoChar(buf, ')');
+
+		if (child_ps)
+			pop_child_plan(dpns, &save_dpns);
+		return NULL;
+	}
 	else if (var->varno == OUTER_VAR && dpns->outer_tlist)
 	{
 		TargetEntry *tle;
@@ -5757,6 +5802,7 @@ get_name_for_var_field(Var *var, int fieldno,
 	AttrNumber	attnum;
 	int			netlevelsup;
 	deparse_namespace *dpns;
+	PlanState  *child_ps = NULL;
 	TupleDesc	tupleDesc;
 	Node	   *expr;
 
@@ -5831,6 +5877,30 @@ get_name_for_var_field(Var *var, int fieldno,
 		rte = rt_fetch(var->varno, dpns->rtable);
 		attnum = var->varattno;
 	}
+	else if (IS_SPECIAL_VARNO(var->varno) &&
+			 IsA(dpns->planstate, CustomScanState) &&
+			 (expr = GetSpecialCustomVar((CustomScanState *) dpns->planstate,
+										 var, &child_ps)) != NULL)
+	{
+		StringInfo		saved = context->buf;
+		StringInfoData	temp;
+		deparse_namespace save_dpns;
+
+		initStringInfo(&temp);
+		context->buf = &temp;
+
+		if (child_ps)
+			push_child_plan(dpns, child_ps, &save_dpns);
+		if (!IsA(expr, Var))
+			appendStringInfoChar(context->buf, '(');
+		get_rule_expr((Node *) expr, context, true);
+		if (!IsA(expr, Var))
+			appendStringInfoChar(context->buf, ')');
+		if (child_ps)
+			pop_child_plan(dpns, &save_dpns);
+		context->buf = saved;
+		return temp.data;
+	}
 	else if (var->varno == OUTER_VAR && dpns->outer_tlist)
 	{
 		TargetEntry *tle;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 0266135..6239a3f 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 *pathnode);
 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..1736d48
--- /dev/null
+++ b/src/include/executor/nodeCustom.h
@@ -0,0 +1,30 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.h
+ *
+ * prototypes for CustomScan nodes
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ------------------------------------------------------------------------
+ */
+#ifndef NODECUSTOM_H
+#define NODECUSTOM_H
+#include "nodes/plannodes.h"
+#include "nodes/execnodes.h"
+
+/*
+ * General executor code
+ */
+extern CustomScanState *ExecInitCustomScan(CustomScan *custom_scan,
+										   EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomScan(CustomScanState *node);
+extern Node *MultiExecCustomScan(CustomScanState *node);
+extern void ExecEndCustomScan(CustomScanState *node);
+
+extern void ExecReScanCustomScan(CustomScanState *node);
+extern void ExecCustomMarkPos(CustomScanState *node);
+extern void ExecCustomRestrPos(CustomScanState *node);
+
+#endif	/* NODECUSTOM_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b271f21..4a94f4a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -19,6 +19,7 @@
 #include "executor/instrument.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
+#include "nodes/relation.h"
 #include "utils/reltrigger.h"
 #include "utils/sortsupport.h"
 #include "utils/tuplestore.h"
@@ -1504,6 +1505,45 @@ typedef struct ForeignScanState
 	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
 } ForeignScanState;
 
+/* ----------------
+ * CustomScanState information
+ *
+ *		CustomScan nodes are used to execute custom code within executor.
+ * ----------------
+ */
+struct CustomExecMethods;
+struct ExplainState;	/* to avoid to include explain.h here */
+
+typedef struct CustomScanState
+{
+	ScanState	ss;
+	uint32		flags;	/* mask of CUSTOMPATH_* flags defined in relation.h*/
+	const struct CustomExecMethods *methods;
+} CustomScanState;
+
+typedef struct CustomExecMethods
+{
+	const char     *CustomName;
+
+	/* EXECUTOR methods */
+	void    (*BeginCustomScan)(CustomScanState *node,
+							   EState *estate,
+							   int eflags);
+	TupleTableSlot *(*ExecCustomScan)(CustomScanState *node);
+	void	(*EndCustomScan)(CustomScanState *node);
+	void	(*ReScanCustomScan)(CustomScanState *node);
+	void	(*MarkPosCustomScan)(CustomScanState *node);
+	void	(*RestrPosCustomScan)(CustomScanState *node);
+
+	/* EXPLAIN support */
+	void    (*ExplainCustomScan)(CustomScanState *node,
+								 List *ancestors,
+								 struct ExplainState *es);
+	Node   *(*GetSpecialCustomVar)(CustomScanState *node,
+								   Var *varnode,
+								   PlanState **child_ps);
+} CustomExecMethods;
+
 /* ----------------------------------------------------------------
  *				 Join State Information
  * ----------------------------------------------------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6584a2c..bc71fea 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -108,6 +108,7 @@ typedef enum NodeTag
 	T_CteScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
+	T_CustomScanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e08fb1d..e059d0d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -15,8 +15,10 @@
 #define PLANNODES_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
+#include "nodes/relation.h"
 
 
 /* ----------------------------------------------------------------
@@ -486,12 +488,30 @@ typedef struct ForeignScan
  *     CustomScan node
  * ----------------
  */
+struct CustomScanMethods;
+
 typedef struct CustomScan
 {
 	Scan		scan;
 	uint32		flags;	/* mask of CUSTOMPATH_* flags defined in relation.h */
+	struct CustomScanMethods *methods;
 } CustomScan;
 
+typedef struct CustomScanMethods
+{
+	const char *CustomName;
+	void	   (*SetCustomScanRef)(struct PlannerInfo *root,
+								   CustomScan *cscan,
+								   int rtoffset);
+	void	   (*FinalizeCustomScan)(struct PlannerInfo *root,
+									 CustomScan *cscan,
+									 bool (*finalize_primnode)(),
+									 void *finalize_context);
+	Node	  *(*CreateCustomScanState)(CustomScan *cscan);
+	void	   (*TextOutCustomScan)(StringInfo str, const CustomScan *node);
+	CustomScan *(*CopyCustomScan)(const CustomScan *from);
+} CustomScanMethods;
+
 /*
  * ==========
  * Join nodes
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 3fdc2cb..c97c577 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -86,6 +86,7 @@ extern ModifyTable *make_modifytable(PlannerInfo *root,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, int epqParam);
 extern bool is_projection_capable_plan(Plan *plan);
+extern Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
 
 /*
  * prototypes for plan/initsplan.c
@@ -130,6 +131,8 @@ extern bool query_is_distinct_for(Query *query, List *colnos, List *opids);
  */
 extern Plan *set_plan_references(PlannerInfo *root, Plan *plan);
 extern void fix_opfuncids(Node *node);
+extern Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
+extern void fix_expr_common(PlannerInfo *root, Node *node);
 extern void set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
pgsql-v9.5-custom-scan.part-1.v12.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-1.v12.patchDownload
 src/backend/nodes/outfuncs.c            | 14 +++++++
 src/backend/optimizer/path/allpaths.c   |  3 ++
 src/backend/optimizer/plan/createplan.c | 73 +++++++++++++++++++++++++++++++++
 src/backend/optimizer/util/pathnode.c   | 50 ++++++++++++++++++++++
 src/include/nodes/nodes.h               |  2 +
 src/include/nodes/plannodes.h           |  9 ++++
 src/include/nodes/relation.h            | 37 +++++++++++++++++
 src/include/optimizer/pathnode.h        |  9 ++++
 8 files changed, 197 insertions(+)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1ff78eb..8ad9c8d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1582,6 +1582,17 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
 }
 
 static void
+_outCustomPath(StringInfo str, const CustomPath *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPATH");
+	_outPathInfo(str, (const Path *) node);
+	WRITE_UINT_FIELD(flags);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomPath(str, node);
+}
+
+static void
 _outAppendPath(StringInfo str, const AppendPath *node)
 {
 	WRITE_NODE_TYPE("APPENDPATH");
@@ -3060,6 +3071,9 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignPath:
 				_outForeignPath(str, obj);
 				break;
+			case T_CustomPath:
+				_outCustomPath(str, obj);
+				break;
 			case T_AppendPath:
 				_outAppendPath(str, obj);
 				break;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index c81efe9..8b42e36 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -402,6 +402,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
 
+	/* Consider custom scans, if any */
+	create_customscan_paths(root, rel, rte);
+
 	/* Now find the cheapest of the paths for this rel */
 	set_cheapest(rel);
 }
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4b641a2..189f7ef 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,6 +77,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,
@@ -233,6 +236,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:
@@ -409,6 +413,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);
@@ -1072,6 +1083,68 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 	return plan;
 }
 
+/*
+ * create_custom_plan
+ *
+ * Returns a custom-scan plan for the base relation scanned by 'best_path'
+ * with restriction clauses 'clauses' and targetlist 'tlist'.
+ */
+static CustomScan *
+create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
+					   List *tlist, List *scan_clauses)
+{
+	CustomScan	   *custom_scan;
+	RelOptInfo	   *rel = best_path->path.parent;
+
+	/*
+	 * Right now, all we can support is CustomScan node which is associated
+	 * with a particular base relation to be scanned.
+	 */
+	Assert(rel && rel->reloptkind == RELOPT_BASEREL);
+
+	/*
+	 * Sort clauses into the best execution order, although custom-scan
+	 * provider can reorder them again.
+	 */
+	scan_clauses = order_qual_clauses(root, scan_clauses);
+
+	/*
+	 * Create a CustomScan (or its inheritance) node according to
+	 * the supplied CustomPath.
+	 */
+	custom_scan = (CustomScan *)
+		best_path->methods->PlanCustomPath(root,
+										   rel,
+										   best_path,
+										   tlist,
+										   scan_clauses);
+	/* additional sanity checks */
+	Assert(nodeTag(custom_scan) == best_path->path.pathtype);
+	if (IsA(custom_scan, CustomScan))
+	{
+		Index	scanrelid = ((Scan *)custom_scan)->scanrelid;
+
+		if (scanrelid != rel->relid)
+			elog(ERROR, "Bug? CustomScan tries to scan incorrect relation");
+	}
+	else
+		elog(ERROR, "unexpected node: %d", (int)nodeTag(custom_scan));
+
+	/*
+	 * NOTE: unlike create_foreignscan_plan(), it is responsibility of
+	 * the custom plan provider to replace outer-relation variables
+	 * with nestloop params, because we cannot know how many expression
+	 * trees are held in the private fields.
+	 */
+
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize((Plan *)custom_scan, &best_path->path);
+
+	return custom_scan;
+}
 
 /*****************************************************************************
  *
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 319e8b2..2ca0a18 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -27,6 +27,7 @@
 #include "optimizer/var.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/selfuncs.h"
 
 
@@ -1926,3 +1927,52 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*****************************************************************************
+ *     creation of custom-plan paths
+ *****************************************************************************/
+
+static List	   *custom_path_providers = NIL;
+
+/*
+ * register_custom_path_provider
+ *
+ * It registers a table of callback functions that implements a custom-path
+ * provider. The callback functions are expected to construct CustomPath node
+ * that provides an alternative logic to scan a relation (and so on in the
+ * future version), if extension can do.
+ * Note that the supplied CustomPathMethods is expected to locate on static
+ * memory area, so we don't copy individual fields here.
+ */
+void
+register_custom_path_provider(CustomPathMethods *cpp_methods)
+{
+	MemoryContext	oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+	custom_path_providers = lappend(custom_path_providers, cpp_methods);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * create_customscan_paths
+ *
+ * It calls back extension's entrypoint whether it can add alternative
+ * scan paths being extended from the CustomPath type.
+ * If any, the callback will add one or more paths using add_path().
+ */
+void
+create_customscan_paths(PlannerInfo *root,
+						RelOptInfo *baserel,
+						RangeTblEntry *rte)
+{
+	ListCell	   *cell;
+
+	foreach (cell, custom_path_providers)
+	{
+		const CustomPathMethods *cpp_methods = lfirst(cell);
+
+		if (cpp_methods->CreateCustomScanPath)
+			cpp_methods->CreateCustomScanPath(root, baserel, rte);
+	}
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 154d943..6584a2c 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,
@@ -224,6 +225,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 1839494..e08fb1d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -482,6 +482,15 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *     CustomScan node
+ * ----------------
+ */
+typedef struct CustomScan
+{
+	Scan		scan;
+	uint32		flags;	/* mask of CUSTOMPATH_* flags defined in relation.h */
+} CustomScan;
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index f1a0504..2bbb7cc 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -15,6 +15,7 @@
 #define RELATION_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "storage/block.h"
@@ -884,6 +885,42 @@ typedef struct ForeignPath
 } ForeignPath;
 
 /*
+ * CustomPath represents a scan using custom logic
+ *
+ * Extension (that performs as custom-plan provider) can adds an alternative
+ * path using its custom type being delivered from CustomPath.
+ * They can store their private data on the extra fields of their custom
+ * object. A set of common methods are represented as function pointers in
+ * CustomPathMethods structure; extension has to set up then correctly.
+ */
+struct CustomPathMethods;
+struct Plan;		/* not to include plannodes.h here */
+
+#define CUSTOMPATH_SUPPORT_BACKWARD_SCAN	0x0001
+#define CUSTOMPATH_SUPPORT_MARK_RESTORE		0x0002
+
+typedef struct CustomPath
+{
+	Path        path;
+	uint32		flags;
+	const struct CustomPathMethods *methods;
+} CustomPath;
+
+typedef struct CustomPathMethods
+{
+	const char *CustomName;
+	void	(*CreateCustomScanPath)(PlannerInfo *root,
+									RelOptInfo *baserel,
+									RangeTblEntry *rte);
+	struct Plan	*(*PlanCustomPath)(PlannerInfo *root,
+								   RelOptInfo *rel,
+								   CustomPath *best_path,
+								   List *tlist,
+								   List *clauses);
+	void    (*TextOutCustomPath)(StringInfo str, const CustomPath *node);
+} CustomPathMethods;
+
+/*
  * AppendPath represents an Append plan, ie, successive execution of
  * several member plans.
  *
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index a0bcc82..8d75020 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,15 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					double loop_count);
 
 /*
+ * Interface definition of custom-scan providers
+ */
+extern void register_custom_path_provider(CustomPathMethods *cpp_methods);
+
+extern void create_customscan_paths(PlannerInfo *root,
+									RelOptInfo *baserel,
+									RangeTblEntry *rte);
+
+/*
  * prototypes for relnode.c
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
#6Thom Brown
thom@linux.com
In reply to: Kouhei Kaigai (#5)

On 30 September 2014 07:27, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

On Mon, Sep 29, 2014 at 9:04 AM, Kohei KaiGai <kaigai@kaigai.gr.jp>

wrote:

Do we need to match the prototype of wrapper function with callback?

Yes.

OK, I fixed up the patch part-2, to fit declaration of
GetSpecialCustomVar()
with corresponding callback.

Also, a noise in the part-3 patch, by git-pull, was removed.

FYI, patch v12 part 2 no longer applies cleanly.

--
Thom

#7Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Thom Brown (#6)
3 attachment(s)

FYI, patch v12 part 2 no longer applies cleanly.

Thanks. I rebased the patch set according to the latest master branch.
The attached v13 can be applied to the master.
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

Show quoted text

-----Original Message-----
From: thombrown@gmail.com [mailto:thombrown@gmail.com] On Behalf Of Thom
Brown
Sent: Sunday, October 26, 2014 9:22 PM
To: Kaigai Kouhei(海外 浩平)
Cc: Robert Haas; Kohei KaiGai; Tom Lane; Alvaro Herrera; Shigeru Hanada;
Simon Riggs; Stephen Frost; Andres Freund; PgHacker; Jim Mlodgenski; Peter
Eisentraut
Subject: Re: [HACKERS] [v9.5] Custom Plan API

On 30 September 2014 07:27, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

On Mon, Sep 29, 2014 at 9:04 AM, Kohei KaiGai

<kaigai@kaigai.gr.jp> wrote:

Do we need to match the prototype of wrapper function with

callback?

Yes.

OK, I fixed up the patch part-2, to fit declaration of
GetSpecialCustomVar()
with corresponding callback.

Also, a noise in the part-3 patch, by git-pull, was removed.

FYI, patch v12 part 2 no longer applies cleanly.

--
Thom

Attachments:

pgsql-v9.5-custom-scan.part-3.v13.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-3.v13.patchDownload
 contrib/Makefile                       |   1 +
 contrib/ctidscan/Makefile              |  18 +
 contrib/ctidscan/ctidscan.c            | 886 +++++++++++++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control      |   5 +
 contrib/ctidscan/expected/ctidscan.out | 331 ++++++++++++
 contrib/ctidscan/sql/ctidscan.sql      |  59 +++
 src/include/catalog/pg_operator.h      |   3 +
 7 files changed, 1303 insertions(+)

diff --git a/contrib/Makefile b/contrib/Makefile
index b37d0dd..9b4b6ad 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..fbea380
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,18 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+EXTENSION = ctidscan
+
+REGRESS = 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..3a48588
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,886 @@
+/*
+ * 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 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/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/spccache.h"
+
+PG_MODULE_MAGIC;
+
+typedef struct {
+	CustomPath		cpath;
+	List		   *ctid_quals;
+} CtidScanPath;
+
+typedef struct {
+	CustomScan		cscan;
+	List		   *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+	CustomScanState	css;
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CtidScanState;
+
+/* function declarations */
+void	_PG_init(void);
+
+static void CreateCtidScanPath(PlannerInfo *root,
+							   RelOptInfo *baserel,
+							   RangeTblEntry *rte);
+static Plan *PlanCtidScanPath(PlannerInfo *root,
+							  RelOptInfo *rel,
+							  CustomPath *best_path,
+							  List *tlist,
+							  List *clauses);
+static void TextOutCtidScanPath(StringInfo str, const CustomPath *cpath);
+
+static void SetCtidScanPlanRef(PlannerInfo *root,
+							   CustomScan *custom_plan,
+							   int rtoffset);
+static void FinalizeCtidScanPlan(PlannerInfo *root,
+								 CustomScan *custom_plan,
+								 bool (*finalize_primnode)(),
+								 void *finalize_context);
+static Node *CreateCtidScanState(CustomScan *custom_plan);
+static void TextOutCtidScanPlan(StringInfo str, const CustomScan *node);
+static CustomScan *CopyCtidScanPlan(const CustomScan *from);
+
+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 */
+	CreateCtidScanPath,		/* CreateCustomScanPath */
+	PlanCtidScanPath,		/* PlanCustomPath */
+	TextOutCtidScanPath,	/* TextOutCustomPath */
+};
+
+static CustomScanMethods	ctidscan_scan_methods = {
+	"ctidscan",				/* CustomName */
+	SetCtidScanPlanRef,		/* SetCustomScanRef */
+	FinalizeCtidScanPlan,	/* FinalizeCustomScan */
+	CreateCtidScanState,	/* CreateCustomScanState */
+	TextOutCtidScanPlan,	/* TextOutCustomScan */
+	CopyCtidScanPlan,		/* CopyCustomScan */
+};
+
+static CustomExecMethods	ctidscan_exec_methods = {
+	"ctidscan",				/* CustomName */
+	BeginCtidScan,			/* BeginCustomScan */
+	ExecCtidScan,			/* ExecCustomScan */
+	EndCtidScan,			/* EndCustomScan */
+	ReScanCtidScan,			/* ReScanCustomScan */
+	NULL,					/* MarkPosCustomScan */
+	NULL,					/* RestrPosCustomScan */
+	ExplainCtidScan,		/* ExplainCustomScan */
+	NULL,					/* GetSpecialCustomVar */
+};
+
+#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 chained 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,
+				  CtidScanPath *ctid_path)
+{
+	Path	   *path = &ctid_path->cpath.path;
+	List	   *ctid_quals = ctid_path->ctid_quals;
+	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 (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)
+	{
+		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 indeterministic until
+		 * 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 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,
+							  &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
+	 * (logic copied from 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 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;
+
+	path->startup_cost = startup_cost;
+	path->total_cost = startup_cost + run_cost;
+}
+
+/*
+ * CreateCtidScanPath - entrypoint of the series of custom-scan execution.
+ * It adds CustomPath if referenced relation has inequality expressions on
+ * the ctid system column.
+ */
+static void
+CreateCtidScanPath(PlannerInfo *root, RelOptInfo *baserel, 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;
+
+	/* 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);
+		ctid_quals = list_concat(ctid_quals, 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 (ctid_quals != NIL)
+	{
+		CtidScanPath *ctid_path;
+		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;
+
+		ctid_path = palloc0(sizeof(CtidScanPath));
+		ctid_path->cpath.path.type = T_CustomPath;
+		ctid_path->cpath.path.pathtype = T_CustomScan;
+		ctid_path->cpath.path.parent = baserel;
+		ctid_path->cpath.path.param_info
+			= get_baserel_parampathinfo(root, baserel, required_outer);
+		ctid_path->cpath.flags = CUSTOMPATH_SUPPORT_BACKWARD_SCAN;
+		ctid_path->cpath.methods = &ctidscan_path_methods;
+		ctid_path->ctid_quals = ctid_quals;
+
+		CTidEstimateCosts(root, baserel, ctid_path);
+
+		add_path(baserel, &ctid_path->cpath.path);
+	}
+}
+
+/*
+ * CreateCtidScanPlan - 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)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_scan;
+	List		   *ctid_quals = ctid_path->ctid_quals;
+
+	ctid_scan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_scan, T_CustomScan);
+	ctid_scan->cscan.flags = best_path->flags;
+	ctid_scan->cscan.methods = &ctidscan_scan_methods;
+
+	/* set scanrelid */
+	ctid_scan->cscan.scan.scanrelid = rel->relid;
+	/* set targetlist as is  */
+	ctid_scan->cscan.scan.plan.targetlist = tlist;
+	/* reduce RestrictInfo list to bare expressions */
+	ctid_scan->cscan.scan.plan.qual
+		= extract_actual_clauses(clauses, false);
+	/* set ctid related quals */
+	if (best_path->path.param_info)
+		ctid_quals = (List *)
+			replace_nestloop_params(root, (Node *)ctid_quals);
+	ctid_scan->ctid_quals = ctid_quals;
+
+	/*
+	 * If there are any pseudoconstant clauses attached to this node, insert a
+	 * gating Result node that evaluates the pseudoconstants as one-time
+	 * quals.
+	 */
+	if (root->hasPseudoConstantQuals)
+	{
+		List   *pseudoconstants = extract_actual_clauses(clauses, true);
+
+		if (pseudoconstants != NIL)
+			return (Plan *) make_result(root,
+										tlist,
+										(Node *) pseudoconstants,
+										(Plan *) ctid_scan);
+	}
+	return (Plan *) ctid_scan;
+}
+
+/*
+ * TextOutCtidScanPath - A method of CustomPath; that shows a text
+ * representation of the supplied CustomPath object.
+ */
+static void
+TextOutCtidScanPath(StringInfo str, const CustomPath *cpath)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *)cpath;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_path->ctid_quals));
+}
+
+/*
+ * SetCtidScanPlanRef - A method of CustomScan; that fixes up rtindex
+ * of Var nodes
+ */
+#define fix_scan_list(root, lst, rtoffset)	\
+	((List *) fix_scan_expr(root, (Node *) (lst), rtoffset))
+
+static void
+SetCtidScanPlanRef(PlannerInfo *root,
+				   CustomScan *custom_plan,
+				   int rtoffset)
+{
+	CtidScanPlan   *ctidscan = (CtidScanPlan *) custom_plan;
+	Scan		   *scan = &ctidscan->cscan.scan;
+
+	scan->scanrelid += rtoffset;
+	scan->plan.targetlist =
+		fix_scan_list(root, scan->plan.targetlist, rtoffset);
+	scan->plan.qual =
+		fix_scan_list(root, scan->plan.qual, rtoffset);
+	ctidscan->ctid_quals =
+		fix_scan_list(root, ctidscan->ctid_quals, rtoffset);
+}
+
+/*
+ * FinalizeCtidScanPlan - A method of CustomScan; that handles callbacks
+ * by finalize_plan().
+ */
+static void
+FinalizeCtidScanPlan(PlannerInfo *root,
+					 CustomScan *custom_plan,
+					 bool (*finalize_primnode)(),
+					 void *finalize_context)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) custom_plan;
+
+	/* applies finalize_primnode() on ctid_quals also */
+	finalize_primnode((Node *)ctid_plan->ctid_quals, finalize_context);
+}
+
+/*
+ * 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;
+}
+
+/*
+ * TextOutCtidScanPlan - A method of CustomScan; that generates text
+ * representation of the given object.
+ */
+static void
+TextOutCtidScanPlan(StringInfo str, const CustomScan *node)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_plan->ctid_quals));
+}
+
+/*
+ * CopyCtidScanPlan - A method of CustomScan; that create a copied object.
+ */
+static CustomScan *
+CopyCtidScanPlan(const CustomScan *from)
+{
+	CtidScanPlan   *oldnode = (CtidScanPlan *) from;
+	CtidScanPlan   *newnode = palloc0(sizeof(CtidScanPlan));
+
+	NodeSetTag(newnode, T_CustomScan);
+	newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+	return &newnode->cscan;
+}
+
+/*
+ * 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;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node->ss.ps.plan;
+
+	/*
+	 * In case of custom-plan 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 *)ctid_plan->ctid_quals, &node->ss.ps);
+
+	/* Do nothing anymore in EXPLAIN (no ANALYZE) case. */
+	if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
+		return;
+
+	/* scandesc shall be set later */
+	ctss->css.ss.ss_currentScanDesc = NULL;
+}
+
+/*
+ * 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;
+		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);
+		}
+		else
+			elog(ERROR, "could not identify CTID variable");
+
+		itemptr = (ItemPointer)
+			DatumGetPointer(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;
+		}
+
+		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),
+								 scan->rs_nblocks - 1);
+		scan->rs_startblock = blknum;
+	}
+	else if (direction == BackwardScanDirection && has_ubound)
+	{
+		BlockNumber	blknum = Min(BlockIdGetBlockNumber(&ip_max.ip_blkid),
+								 scan->rs_nblocks - 1);
+		scan->rs_startblock = 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;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->css.ss.ps.plan;
+
+	/* logic copied from show_qual and show_expression */
+	if (ctid_plan->ctid_quals)
+	{
+		bool	useprefix = es->verbose;
+		Node   *qual;
+		List   *context;
+		char   *exprstr;
+
+		/* Convert AND list to explicit AND */
+		qual = (Node *) make_ands_explicit(ctid_plan->ctid_quals);
+
+		/* 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("ctid quals", exprstr, es);
+	}
+}
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	register_custom_path_provider(&ctidscan_path_methods);
+}
diff --git a/contrib/ctidscan/ctidscan.control b/contrib/ctidscan/ctidscan.control
new file mode 100644
index 0000000..ad63432
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.control
@@ -0,0 +1,5 @@
+# ctidscan extension
+comment = 'example implementation for custom-plan interface'
+default_version = '1.0'
+module_pathname = '$libdir/ctidscan'
+relocatable = true
diff --git a/contrib/ctidscan/expected/ctidscan.out b/contrib/ctidscan/expected/ctidscan.out
new file mode 100644
index 0000000..9963a43
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,331 @@
+--
+-- Regression Tests for Custom Plan 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)
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+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))
+   ctid quals: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(3 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)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom Scan (ctidscan) on t2
+               Filter: (ctid > '(1,75)'::tid)
+               ctid quals: (ctid > '(1,75)'::tid)
+(12 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)
+
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+                        WHERE b 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: ((b ~~ '%abc%'::text) AND (ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+   ctid quals: ((ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+(3 rows)
+
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+   Filter: ((b ~~ '%abc%'::text) AND (ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+   ctid quals: ((ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+(3 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>                                              +
+       <Alias>t1</Alias>                                                                                  +
+       <Filter>((b ~~ '%abc%'::text) AND (ctid &gt;= '(0,0)'::tid) AND (ctid &lt;= '(5,0)'::tid))</Filter>+
+       <ctid-quals>((ctid &gt;= '(0,0)'::tid) AND (ctid &lt;= '(5,0)'::tid))</ctid-quals>                 +
+     </Plan>                                                                                              +
+   </Query>                                                                                               +
+ </explain>
+(1 row)
+
+-- Test cleanup
+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/contrib/ctidscan/sql/ctidscan.sql b/contrib/ctidscan/sql/ctidscan.sql
new file mode 100644
index 0000000..26c22c2
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,59 @@
+--
+-- Regression Tests for Custom Plan 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;
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+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;
+
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+                        WHERE b 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);
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index d7dcd1c..0dd131c 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -166,10 +166,13 @@ 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");
pgsql-v9.5-custom-scan.part-2.v13.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-2.v13.patchDownload
 doc/src/sgml/custom-plan.sgml           | 359 ++++++++++++++++++++++++++++++++
 doc/src/sgml/filelist.sgml              |   1 +
 doc/src/sgml/postgres.sgml              |   1 +
 src/backend/commands/explain.c          |  25 +++
 src/backend/executor/Makefile           |   2 +-
 src/backend/executor/execAmi.c          |  38 +++-
 src/backend/executor/execProcnode.c     |  14 ++
 src/backend/executor/nodeCustom.c       | 127 +++++++++++
 src/backend/nodes/copyfuncs.c           |  26 +++
 src/backend/nodes/outfuncs.c            |  15 ++
 src/backend/optimizer/path/costsize.c   |   2 +-
 src/backend/optimizer/plan/createplan.c |   3 +-
 src/backend/optimizer/plan/setrefs.c    |  26 ++-
 src/backend/optimizer/plan/subselect.c  |  21 ++
 src/backend/utils/adt/ruleutils.c       |  70 +++++++
 src/include/executor/executor.h         |   3 +-
 src/include/executor/nodeCustom.h       |  30 +++
 src/include/nodes/execnodes.h           |  40 ++++
 src/include/nodes/nodes.h               |   1 +
 src/include/nodes/plannodes.h           |  20 ++
 src/include/optimizer/planmain.h        |   3 +
 21 files changed, 817 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/custom-plan.sgml b/doc/src/sgml/custom-plan.sgml
new file mode 100644
index 0000000..fc4b187
--- /dev/null
+++ b/doc/src/sgml/custom-plan.sgml
@@ -0,0 +1,359 @@
+<!-- doc/src/sgml/custom_plan.sgml -->
+
+<chapter id="custom-plan">
+ <title>Writing A Custom Plan Provider</title>
+ <indexterm zone="custom-plan">
+  <primary>custom plan provider</primary>
+  <secondary>handler for</secondary>
+ </indexterm>
+
+ <para>
+  PostgreSQL has various kind of built-in plan nodes that implement
+  a particular portion to process the supplied SQL queries.
+  For example, SeqScan node implements full table scan, HashJoin node
+  implements tables join using a hash table and so on.
+ </para>
+ <para>
+  The custom-plan interface allows extensions to provide alternative
+  query execution paths, in addition to the built-in ones. Query planner
+  will choose the cheapest path towards a particular relation(s) scan or
+  join in all the proposed ones by built-in and extensions.
+  Once a proposed custom plan got picked up, callback functions associated
+  with the node shall be called and extension will get control to process
+  the task. We call a set of callback functions associated with a certain
+  custom node the custom-path, custom-plan or custom-scan depending on
+  the things it provides; just "provider" in short.
+ </para>
+
+ <sect1 id="custom-plan-overall-steps">
+  <title>Custom Plan Overall Steps</title>
+  <para>
+   A custom-path provider shall be registered by
+   <literal>register_custom_path_provider</> that takes a pointer of
+   <literal>CustomPathMethods</> table which also holds some function
+   pointers.
+   It is usually installed on <literal>_PG_init()</> of extension
+   when either of preload configuration or <xref linkend="sql-load">
+   command loads the extension of the provider.
+  </para>
+  <para>
+   Onec a provider gets registered, the built-in query planner calls
+   back <literal>CreateCustomScanPath</> to ask whether the provider
+   can offer an alternative path to scan the referenced relation, or
+   not.
+   If it is available to provide, this callback will construct
+   <literal>CustomPath</> with cost estimation.
+   Then, query planner compares the alternative paths with built-in
+   paths based on the estimated cost, then the cheapest one shall be
+   chosen.
+  </para>
+  <para>
+   Usually, a provider may need some private fields to store something
+   valuable properties for the path. In this case, extension can extends
+   the <literal>CustomPath</> structure using a new data structure
+   definition that takes <literal>CustomPath</> on the head then followed
+   by private fields.
+<programlisting>
+typedef struct MySpecialPath
+{
+    CustomPath  cpath;
+    List       *some_list;
+    Bitmapset  *some_bitmap;
+       :
+} MySpecialPath;
+</programlisting>
+   The above example shows a structure delivered from <literal>CustomPath</>
+   with some private fields.
+   We assume such kind of manner, like as object oriented language doing,
+   to inject private fields of the provider on other nodes like
+   <literal>CustomScan</>.
+  </para>
+  <para>
+   Once a <literal>CustomPath</> got chosen, its callback shall be kicked
+   to populate <literal>CustomScan</> (or its inheritance) node; that is
+   the only available node type, right now. It consists of a part of query
+   plan tree, instead of the built-in nodes.
+   In the same manner, the <literal>CustomScan</> node populates
+   a <literal>CustomScanState</> (or its inheritance) node; that manages   
+   execution-time status of the custom-scan node and a set of callbacks.
+  </para>
+ </sect1>
+
+ <sect1 id="custom-path-callbacks">
+  <title>Custom Path Callbacks</title>
+  <para>
+   This section introduces callback functions of <structname>CustomPath</>
+   structure; defined in the <structname>CustomPathMethods</>, and
+   related flags.
+  </para>
+  <para>
+<programlisting>
+void
+CreateCustomScanPath(PlannerInfo *root,
+                     RelOptInfo *baserel,
+                     RangeTblEntry *rte);
+</programlisting>
+   As mentioned above, it construct <structname>CustomPath</> (or inherited
+   data type) node if it is available to provide an alternative scan path
+   on the supplied relation and qualifiers; that shall be informed using
+   <literal>RelOptInfo</> argument.
+   The constructed <structname>CustomPath</> node shall be added to
+   candidate path of the relation using <literal>add_path</>.
+  </para>
+  <para>
+<programlisting>
+Plan *
+PlanCustomPath(PlannerInfo *root,
+               RelOptInfo *rel,
+               CustomPath *best_path,
+               List *tlist,
+               List *clauses);
+</programlisting>
+   It populates a <struct>CustomScan</> or its inheritance node
+   according to the supplied <structname>CustomPath</> node which was
+   constructed on the custom path handler function then chosen by the
+   query planner.
+   This callback has to allocate a <struct>CustomScan</> node because
+   only provider can know exact size to be allocated if it is extended
+   to have private fields.
+   This callback is assumed to allocate the <struct>CustomScan</> node
+   with <structname>CustomPlanMethods</> callbacks table, then initialize
+   common plan-node fields and arbitrary private fields.
+  </para>
+
+  <para>
+<programlisting>
+void
+TextOutCustomPath(StringInfo str, const CustomPath *node);
+</programlisting>
+   It makes a text representation of custom path node. If provider extends
+   <structname>CustomPath</> data type, it shall to put private fields on
+   the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <structname>CustomPath</> are handled by
+   backend, so extension needs to do nothing special.
+  </para>
+
+  <para>
+   <literal>CustomPath</> structure can have flags that informs capability
+   of this custom-path to the query planner. Every flags can be combined
+   with OR-operation, then set to <literal>flags</> field.
+   The value of <literal>flags</> should be unchanged across node
+   population, because the plan tree is constructed based on the properties
+   being preliminary configured.
+  </para>
+  <para>
+   <literal>CUSTOMPATH_SUPPORT_BACKWARD_SCAN</> informs the planner
+   this custom-path supports backward scan.
+   <literal>CUSTOMPATH_SUPPORT_MARK_RESTORE</> informs the planner
+   this custom-path supports mark and restore position.
+  </para>
+ </sect1>
+
+ <sect1 id="custom-scan-callbacks">
+  <title>Custom Scan Callbacks</title>
+  <para>
+   This section introduces callback functions of <structname>CustomScan</>
+   structure; defined in the <structname>CustomScanMethods</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+SetCustomScanRef(PlannerInfo *root,
+                 CustomScan *cscan,
+                 int rtoffset);
+</programlisting>
+   It adjusts <literal>varno</> and <literal>varattno</> of var-nodes in
+   the expression tree chained from <structname>CustomScan</> node
+   (including private fields in the inherited data type).
+   In case of usual relation scan, <literal>rtoffset</> shall be added to
+   <literal>varno</> of var-nodes and <literal>scanrelid</> of plan node,
+   then <function>fix_scan_expr</> shall be called on expression nodes to
+   track plan dependency.
+  </para>
+
+  <para>
+<programlisting>
+void
+FinalizeCustomScan(PlannerInfo *root,
+                   CustomScan *cscan,
+                   bool (*finalize_primnode)(),
+                   void *finalize_context);
+</programlisting>
+   It is an optional callback, which applies <literal>finalize_primenode</>
+   on the expression nodes of private fields because only provider knows
+   what private fields are expression node to be finalized.
+   Note that backend applies <literal>finalize_primenode</> on
+   the <literal>tlist</> and <literal>qual</> of the base plan node,
+   so provider shall do nothing special, if it has no private expression
+   node. Also, it handles recursive stuff on the <literal>lefttree</> and
+   <literal>righttree</>, so it does not need to handle recursive walks.
+  </para>
+
+  <para>
+<programlisting>
+Node *
+CreateCustomScanState(CustomScan *cscan);
+</programlisting>
+   It populates a <structname>CustomScanState</> (or its inheritance)
+   node according to the supplied <structname>CustomScan</> node that was
+   preliminary constructed on the beginning of query executor.
+   Only provider can know exact size of the node to be allocated, this
+   callback allocate a <structname>CustomScanState</> node
+   with <structfield>CustomExecMethods</> callbacks table and arbitrary
+   private fields.
+  </para>
+  <para>
+   Note that main purpose of this callback is allocation of
+   <literal>CustomScanState</> node, not initialization of individual
+   fields because it shall be handled on the <structfield>BeginCustomPlan</>
+   callback to be invoked next to the common usual initialization.
+  </para>
+
+  <para>
+<programlisting>
+void
+TextOutCustomScan(StringInfo str,
+                  const CustomScan *node);
+</programlisting>
+   It makes a text representation of custom-scan node. If provider extends
+   <structfield>CustomScan</> data type, it shall put private fields on
+   the supplied <literal>StringInfo</> with text form.
+   Note that common fields within <structname>CustomPlan</> are handled
+   by the backend, so extension needs to do nothing special.
+  </para>
+
+  <para>
+<programlisting>
+CustomScan *
+CopyCustomScan(const CustomScan *from);
+</programlisting>
+   It duplicate every private fields of the inheritance of
+   <literal>CustomScan</> onto a newly allocated node.
+   In case when provider extends <literal>CustomScan</> node, only provider
+   can know exact size to be allocated and existence of the private fields.
+   So, it shall be responsible for node allocation and copy of private fields,
+   although the backend copies the fields of
+   <literal>CustomScan</>.
+  </para>
+ </sect1>
+
+ <sect1 id="custom-exec-callbacks">
+  <title>Custom Executor Callbacks</title>
+  <para>
+   This section introduces callback functions of <structname>CustomPlanState</>
+   structure; defined in the <structname>CustomExecMethods</>.
+  </para>
+  <para>
+<programlisting>
+void
+BeginCustomScan(CustomScanState *node,
+                EState *estate,
+                int eflags);
+</programlisting>
+   It begins execution of custom-scan. This callback is invoked during
+   executor startup to initialize the supplied <literal>CustomScanState</>
+   that was constructed on the <literal>CreateCustomScanState</> above.
+   The provider shall have initialization of its private fields and common
+   fields within <literal>CustomScanState</> if needed, because the backend
+   code already applies some fundamental initializations.
+  </para>
+
+  <para>
+<programlisting>
+TupleTableSlot *
+ExecCustomScan(CustomScanState *node);
+</programlisting>
+   It fetches one row from the custom-plan node, returning it in a tuple
+   table slot (<literal>ps_ResultTupleSlot</> of <literal>PlanState</>
+   shall be used) or <literal>NULL</> if no more rows are available.
+   The tuple table slot infrastructure allows either a physical or virtual
+   tuple to be returned; in most cases the latter choice is preferable from
+   a performance standpoint.
+   The rows being returned have to match the tuple-descriptor of the
+   <structname>PlanState</>
+  </para>
+  <para>
+   Note that this call is under a short-lived memory context that will be
+   reset for each invocation. So, it may be a good choice to switch
+   <literal>es_query_cxt</> of the <structname>EState</>, to acquire
+   memory in per-scan duration.
+  </para>
+
+  <para>
+<programlisting>
+void
+EndCustomScan(CustomScanState *node);
+</programlisting>
+   It ends the execution of custom-scan and release any resources held by
+   this node. If provider acquired resources that is not released
+   automatically at end of executor, it is responsibility of the provider.
+  </para>
+
+  <para>
+<programlisting>
+void
+ReScanCustomScan(CustomScanState *node);
+</programlisting>
+   It restarts the scan from the beginning.  Note that any parameters
+   the scan depends on may have changed value, so the new scan does not
+   necessarily return exactly the same rows.
+  </para>
+
+  <para>
+<programlisting>
+void *
+MarkPosCustomScan(CustomScanState *node);
+</programlisting>
+   It is an optional callback if <literal>CUSTOMPATH_SUPPORT_MARK_RESTORE</>
+   is set on the <literal>flags</>. Elsewhere, it should put <literal>NULL</>
+   on the callback table because never called.
+   It saves current scan position on somewhere in private fields of
+   <structname>CustomScanState</> (or its inheritance), to restore
+   the position later.
+  </para>
+
+  <para>
+<programlisting>
+void *
+RestrPosCustomScan(CustomScanState *node);
+</programlisting>
+   It is an optional callback if <literal>CUSTOMPATH_SUPPORT_MARK_RESTORE</>
+   is set on the <literal>flags</>. Elsewhere, it should put <literal>NULL</>
+   on the callback table because never called.
+   It restores the previous scan position saved by
+   the <structname>MarkPosCustomScan</> above.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomScan(CustomScanState *node,
+                  List *ancestors,
+                  ExplainState *es);
+</programlisting>
+   It is an optional callback, to show custom-scan specific explain output.
+  </para>
+
+  <para>
+<programlisting>
+Node   *
+GetSpecialCustomVar(CustomPlanState *node,
+                    Var *varnode,
+                    PlanState **child_ps);
+</programlisting>
+   It is an optional callback, to solve references to special varno on
+   the <literal>CustomScanState</> when <command>EXPLAIN</> needs the
+   text form of the column actually referenced.
+   In case when provider adjusted <literal>varno</> of varnodes on
+   the expression tree to use special varnos (<literal>INNER_VAR</>,
+   <literal>OUTER_VAR</> or <literal>INDEX_VAR</>), provider has
+   to inform the backend which column is mapped on the underlying plan-state.
+  </para>
+  <para>
+   This callback is expected to return <literal>Var</> node to reference
+   an actual variable on the underlying <structname>PlanState</> that shall
+   be set on the <literal>child_ps</> argument for recursive walking down.
+  </para>
+ </sect1>
+</chapter>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 5902f97..8d20594 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -92,6 +92,7 @@
 <!ENTITY nls        SYSTEM "nls.sgml">
 <!ENTITY plhandler  SYSTEM "plhandler.sgml">
 <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml">
+<!ENTITY custom-plan SYSTEM "custom-plan.sgml">
 <!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
 <!ENTITY protocol   SYSTEM "protocol.sgml">
 <!ENTITY sources    SYSTEM "sources.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 9bde108..5f415c6 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -242,6 +242,7 @@
   &nls;
   &plhandler;
   &fdwhandler;
+  &custom-plan;
   &geqo;
   &indexam;
   &gist;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 3fcc1dd..99aa0f0 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -724,6 +724,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 		case T_CteScan:
 		case T_WorkTableScan:
 		case T_ForeignScan:
+		case T_CustomScan:
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
@@ -853,6 +854,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 	const char *sname;			/* node type name for non-text output */
 	const char *strategy = NULL;
 	const char *operation = NULL;
+	const char *custom_name = NULL;
 	int			save_indent = es->indent;
 	bool		haschildren;
 
@@ -941,6 +943,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomScan:
+			sname = "Custom Scan";
+			custom_name = ((CustomScan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom Scan (%s)", custom_name);
+			else
+				pname = sname;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1042,6 +1052,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			ExplainPropertyText("Parent Relationship", relationship, es);
 		if (plan_name)
 			ExplainPropertyText("Subplan Name", plan_name, es);
+		if (custom_name)
+			ExplainPropertyText("Custom Plan Provider", custom_name, es);
 	}
 
 	switch (nodeTag(plan))
@@ -1055,6 +1067,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_CteScan:
 		case T_WorkTableScan:
 		case T_ForeignScan:
+		case T_CustomScan:
 			ExplainScanTarget((Scan *) plan, es);
 			break;
 		case T_IndexScan:
@@ -1358,6 +1371,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
 			break;
+		case T_CustomScan:
+			{
+				CustomScanState *css = (CustomScanState *) planstate;
+
+				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+				if (plan->qual)
+					show_instrumentation_count("Rows Removed by Filter", 1,
+											   planstate, es);
+				if (css->methods->ExplainCustomScan)
+					css->methods->ExplainCustomScan(css, ancestors, es);
+			}
+			break;
 		case T_NestLoop:
 			show_upper_qual(((NestLoop *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 6081b56..af707b0 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -16,7 +16,7 @@ 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 \
-       nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
+       nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeCustom.o nodeHash.o \
        nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \
        nodeLimit.o nodeLockRows.o \
        nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 640964c..b14e08c 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"
@@ -49,6 +50,7 @@
 #include "executor/nodeWindowAgg.h"
 #include "executor/nodeWorktablescan.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/relation.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
@@ -197,6 +199,10 @@ ExecReScan(PlanState *node)
 			ExecReScanForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecReScanCustomScan((CustomScanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
@@ -291,6 +297,10 @@ ExecMarkPos(PlanState *node)
 			ExecValuesMarkPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecCustomMarkPos((CustomScanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialMarkPos((MaterialState *) node);
 			break;
@@ -348,6 +358,10 @@ ExecRestrPos(PlanState *node)
 			ExecValuesRestrPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomScanState:
+			ExecCustomRestrPos((CustomScanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialRestrPos((MaterialState *) node);
 			break;
@@ -379,9 +393,9 @@ ExecRestrPos(PlanState *node)
  * and valuesscan support is actually useless code at present.)
  */
 bool
-ExecSupportsMarkRestore(NodeTag plantype)
+ExecSupportsMarkRestore(Path *pathnode)
 {
-	switch (plantype)
+	switch (pathnode->pathtype)
 	{
 		case T_SeqScan:
 		case T_IndexScan:
@@ -403,6 +417,16 @@ ExecSupportsMarkRestore(NodeTag plantype)
 			 */
 			return false;
 
+		case T_CustomScan:
+			{
+				CustomPath *cpath = (CustomPath *) pathnode;
+
+				Assert(IsA(cpath, CustomPath));
+				if (cpath->flags & CUSTOMPATH_SUPPORT_MARK_RESTORE)
+					return true;
+			}
+			break;
+
 		default:
 			break;
 	}
@@ -465,6 +489,16 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomScan:
+			{
+				uint32	flags = ((CustomScan *) node)->flags;
+
+				if (TargetListSupportsBackwardScan(node->targetlist) &&
+					(flags & CUSTOMPATH_SUPPORT_BACKWARD_SCAN) != 0)
+					return true;
+			}
+			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 c0189eb..e27c062 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/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..a8155d7
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,127 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ *		Routines to handle execution of custom scan node
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "executor/executor.h"
+#include "executor/nodeCustom.h"
+#include "nodes/execnodes.h"
+#include "nodes/plannodes.h"
+#include "parser/parsetree.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+CustomScanState *
+ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
+{
+	CustomScanState    *css;
+	Relation			scan_rel;
+
+	/* populate a CustomScanState according to the CustomScan */
+	css = (CustomScanState *)cscan->methods->CreateCustomScanState(cscan);
+	Assert(IsA(css, CustomScanState));
+
+	/* fill up fields of ScanState */
+	css->ss.ps.plan = &cscan->scan.plan;
+	css->ss.ps.state = estate;
+
+	/* create expression context for node */
+	ExecAssignExprContext(estate, &css->ss.ps);
+	css->ss.ps.ps_TupFromTlist = false;
+
+	/* initialize child expressions */
+	css->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) cscan->scan.plan.targetlist,
+					 (PlanState *) css);
+	css->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) cscan->scan.plan.qual,
+					 (PlanState *) css);
+
+	/* initialization of result tuple slot  */
+	ExecInitResultTupleSlot(estate, &css->ss.ps);
+	ExecAssignResultTypeFromTL(&css->ss.ps);
+
+	/*
+	 * Also, initialization of relation scan stuff if custom-scan
+	 * node intends to run on a particular plain relation.
+	 * Elsewhere, custom-scan provider should be responsible to put
+	 * proper initialization of scan tuple-slot and projection info
+	 * by itself.
+	 */
+	scan_rel = ExecOpenScanRelation(estate, cscan->scan.scanrelid, eflags);
+	css->ss.ss_currentRelation = scan_rel;
+	css->ss.ss_currentScanDesc = NULL;	/* set by provider on demand */
+	ExecInitScanTupleSlot(estate, &css->ss);
+	ExecAssignScanType(&css->ss, RelationGetDescr(scan_rel));
+	ExecAssignScanProjectionInfo(&css->ss);
+
+	/*
+	 * The callback of custom-scan provider applies the final initialization
+	 * of the custom-scan-state node according to its logic.
+	 */
+	css->methods->BeginCustomScan(css, estate, eflags);
+
+	return css;
+}
+
+TupleTableSlot *
+ExecCustomScan(CustomScanState *node)
+{
+	Assert(node->methods->ExecCustomScan != NULL);
+	return node->methods->ExecCustomScan(node);
+}
+
+void
+ExecEndCustomScan(CustomScanState *node)
+{
+	Assert(node->methods->EndCustomScan != NULL);
+	node->methods->EndCustomScan(node);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&node->ss.ps);
+
+	/* Clean out the tuple table */
+	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+	if (node->ss.ss_ScanTupleSlot)
+		ExecClearTuple(node->ss.ss_ScanTupleSlot);
+
+	/* Close the heap relation */
+	ExecCloseScanRelation(node->ss.ss_currentRelation);
+}
+
+void
+ExecReScanCustomScan(CustomScanState *node)
+{
+	Assert(node->methods->ReScanCustomScan != NULL);
+	node->methods->ReScanCustomScan(node);
+}
+
+void
+ExecCustomMarkPos(CustomScanState *node)
+{
+	if (!node->methods->MarkPosCustomScan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("custom-scan \"%s\" does not support MarkPos",
+						node->methods->CustomName)));
+	node->methods->MarkPosCustomScan(node);
+}
+
+void
+ExecCustomRestrPos(CustomScanState *node)
+{
+	if (!node->methods->RestrPosCustomScan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("custom-scan \"%s\" does not support MarkPos",
+						node->methods->CustomName)));
+	node->methods->RestrPosCustomScan(node);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 21b070a..3e03cd8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,29 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomScan
+ */
+static CustomScan *
+_copyCustomScan(const CustomScan *from)
+{
+	CustomScan		   *newnode;
+
+	newnode = from->methods->CopyCustomScan(from);
+	Assert(nodeTag(newnode) == nodeTag(from));
+
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+	COPY_SCALAR_FIELD(flags);
+	/*
+	 * NOTE: The method field of CustomScan is required to be a pointer
+	 * to a static table of callback functions. So, we don't copy the
+	 * table itself, just reference the original one.
+	 */
+	COPY_SCALAR_FIELD(methods);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -4042,6 +4065,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 d6da32c..cdf1e7e 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,18 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomScan(StringInfo str, const CustomScan *node)
+{
+	WRITE_NODE_TYPE("CUSTOMSCAN");
+
+	_outScanInfo(str, (const Scan *) node);
+	WRITE_UINT_FIELD(flags);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomScan(str, node);
+}
+
+static void
 _outJoin(StringInfo str, const Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -2866,6 +2878,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/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 0cdb790..659daa2 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -2266,7 +2266,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;
 
 	/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 189f7ef..23a26d2 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -86,7 +86,6 @@ static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
 					  Plan *outer_plan, Plan *inner_plan);
 static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path,
 					 Plan *outer_plan, Plan *inner_plan);
-static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
 static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
 static void process_subquery_nestloop_params(PlannerInfo *root,
 								 List *subplan_params);
@@ -2613,7 +2612,7 @@ create_hashjoin_plan(PlannerInfo *root,
  * root->curOuterRels are replaced by Params, and entries are added to
  * root->curOuterParams if not already present.
  */
-static Node *
+Node *
 replace_nestloop_params(PlannerInfo *root, Node *expr)
 {
 	/* No setup needed for tree walk, so away we go */
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9ddc8ad..5589de8 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -94,7 +94,6 @@ static Plan *set_subqueryscan_references(PlannerInfo *root,
 							SubqueryScan *plan,
 							int rtoffset);
 static bool trivial_subqueryscan(SubqueryScan *plan);
-static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
 static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
 static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
 static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
@@ -579,6 +578,27 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomScan:
+			{
+				CustomScan *cscan = (CustomScan *) plan;
+
+				cscan->scan.scanrelid += rtoffset;
+				cscan->scan.plan.targetlist =
+					fix_scan_list(root, cscan->scan.plan.targetlist, rtoffset);
+				cscan->scan.plan.qual =
+					fix_scan_list(root, cscan->scan.plan.qual, rtoffset);
+				/*
+				 * The core implementation applies the routine to fixup
+				 * varno on the target-list and scan qualifier.
+				 * If custom-scan has additional expression nodes on its
+				 * private fields, it has to apply same fixup on them.
+				 * Elsewhere, custom-plan provider skip this callback.
+				 */
+				if (cscan->methods->SetCustomScanRef)
+					cscan->methods->SetCustomScanRef(root, cscan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1063,7 +1083,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 */
@@ -1161,7 +1181,7 @@ fix_param_node(PlannerInfo *root, Param *p)
  * looking up operator opcode info for OpExpr and related nodes,
  * and adding OIDs from regclass Const nodes into root->glob->relationOids.
  */
-static Node *
+Node *
 fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
 {
 	fix_scan_expr_context context;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 3e7dc85..4200ec0 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2283,6 +2283,27 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomScan:
+			{
+				CustomScan *custom_scan = (CustomScan *) plan;
+
+				context.paramids = bms_add_members(context.paramids,
+												   scan_params);
+				/*
+				 * custom-scan provider is responsible to apply
+				 * finalize_primnode() on the expression node of
+				 * its private fields, but no need to apply it
+				 * on the tlist and qual of Plan node because it
+				 * is already done above.
+				 */
+				if (custom_scan->methods->FinalizeCustomScan)
+					custom_scan->methods->FinalizeCustomScan(root,
+															 custom_scan,
+															 finalize_primnode,
+															 (void *)&context);
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 24ade6c..5aa334c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5493,6 +5493,26 @@ get_utility_query_def(Query *query, deparse_context *context)
 	}
 }
 
+/*
+ * GetSpecialCustomVar
+ *
+ * It provides a way to solve a special varnode being managed by custom-
+ * scan provider. The callback informs the backend an expression tree
+ * which was replaced by a special varnode.
+ */
+static Node *
+GetSpecialCustomVar(CustomScanState *css, Var *varnode, PlanState **child_ps)
+{
+	Assert(IsA(css, CustomScanState));
+	Assert(IS_SPECIAL_VARNO(varnode->varno));
+
+	if (!css->methods->GetSpecialCustomVar)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s does not support special varno reference",
+						css->methods->CustomName)));
+	return (Node *)css->methods->GetSpecialCustomVar(css, varnode, child_ps);
+}
 
 /*
  * Display a Var appropriately.
@@ -5522,6 +5542,8 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 	int			netlevelsup;
 	deparse_namespace *dpns;
 	deparse_columns *colinfo;
+	PlanState  *child_ps = NULL;
+	Node	   *expr;
 	char	   *refname;
 	char	   *attname;
 
@@ -5546,6 +5568,29 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 		colinfo = deparse_columns_fetch(var->varno, dpns);
 		attnum = var->varattno;
 	}
+	else if (IS_SPECIAL_VARNO(var->varno) &&
+			 IsA(dpns->planstate, CustomScanState) &&
+			 (expr = GetSpecialCustomVar((CustomScanState *) dpns->planstate,
+										 var, &child_ps)) != NULL)
+	{
+		deparse_namespace	save_dpns;
+
+		if (child_ps)
+			push_child_plan(dpns, child_ps, &save_dpns);
+		/*
+		 * Force parentheses because our caller probably assumed a Var is a
+		 * simple expression.
+		 */
+		if (!IsA(expr, Var))
+			appendStringInfoChar(buf, '(');
+		get_rule_expr((Node *) expr, context, true);
+		if (!IsA(expr, Var))
+			appendStringInfoChar(buf, ')');
+
+		if (child_ps)
+			pop_child_plan(dpns, &save_dpns);
+		return NULL;
+	}
 	else if (var->varno == OUTER_VAR && dpns->outer_tlist)
 	{
 		TargetEntry *tle;
@@ -5760,6 +5805,7 @@ get_name_for_var_field(Var *var, int fieldno,
 	AttrNumber	attnum;
 	int			netlevelsup;
 	deparse_namespace *dpns;
+	PlanState  *child_ps = NULL;
 	TupleDesc	tupleDesc;
 	Node	   *expr;
 
@@ -5834,6 +5880,30 @@ get_name_for_var_field(Var *var, int fieldno,
 		rte = rt_fetch(var->varno, dpns->rtable);
 		attnum = var->varattno;
 	}
+	else if (IS_SPECIAL_VARNO(var->varno) &&
+			 IsA(dpns->planstate, CustomScanState) &&
+			 (expr = GetSpecialCustomVar((CustomScanState *) dpns->planstate,
+										 var, &child_ps)) != NULL)
+	{
+		StringInfo		saved = context->buf;
+		StringInfoData	temp;
+		deparse_namespace save_dpns;
+
+		initStringInfo(&temp);
+		context->buf = &temp;
+
+		if (child_ps)
+			push_child_plan(dpns, child_ps, &save_dpns);
+		if (!IsA(expr, Var))
+			appendStringInfoChar(context->buf, '(');
+		get_rule_expr((Node *) expr, context, true);
+		if (!IsA(expr, Var))
+			appendStringInfoChar(context->buf, ')');
+		if (child_ps)
+			pop_child_plan(dpns, &save_dpns);
+		context->buf = saved;
+		return temp.data;
+	}
 	else if (var->varno == OUTER_VAR && dpns->outer_tlist)
 	{
 		TargetEntry *tle;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d167b49..a44b4cd 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"
 #include "utils/lockwaitpolicy.h"
 
 
@@ -103,7 +104,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 *pathnode);
 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..1736d48
--- /dev/null
+++ b/src/include/executor/nodeCustom.h
@@ -0,0 +1,30 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.h
+ *
+ * prototypes for CustomScan nodes
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ------------------------------------------------------------------------
+ */
+#ifndef NODECUSTOM_H
+#define NODECUSTOM_H
+#include "nodes/plannodes.h"
+#include "nodes/execnodes.h"
+
+/*
+ * General executor code
+ */
+extern CustomScanState *ExecInitCustomScan(CustomScan *custom_scan,
+										   EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomScan(CustomScanState *node);
+extern Node *MultiExecCustomScan(CustomScanState *node);
+extern void ExecEndCustomScan(CustomScanState *node);
+
+extern void ExecReScanCustomScan(CustomScanState *node);
+extern void ExecCustomMarkPos(CustomScanState *node);
+extern void ExecCustomRestrPos(CustomScanState *node);
+
+#endif	/* NODECUSTOM_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 39d2c10..b72e605 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -19,6 +19,7 @@
 #include "executor/instrument.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
+#include "nodes/relation.h"
 #include "utils/reltrigger.h"
 #include "utils/sortsupport.h"
 #include "utils/tuplestore.h"
@@ -1504,6 +1505,45 @@ typedef struct ForeignScanState
 	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
 } ForeignScanState;
 
+/* ----------------
+ * CustomScanState information
+ *
+ *		CustomScan nodes are used to execute custom code within executor.
+ * ----------------
+ */
+struct CustomExecMethods;
+struct ExplainState;	/* to avoid to include explain.h here */
+
+typedef struct CustomScanState
+{
+	ScanState	ss;
+	uint32		flags;	/* mask of CUSTOMPATH_* flags defined in relation.h*/
+	const struct CustomExecMethods *methods;
+} CustomScanState;
+
+typedef struct CustomExecMethods
+{
+	const char     *CustomName;
+
+	/* EXECUTOR methods */
+	void    (*BeginCustomScan)(CustomScanState *node,
+							   EState *estate,
+							   int eflags);
+	TupleTableSlot *(*ExecCustomScan)(CustomScanState *node);
+	void	(*EndCustomScan)(CustomScanState *node);
+	void	(*ReScanCustomScan)(CustomScanState *node);
+	void	(*MarkPosCustomScan)(CustomScanState *node);
+	void	(*RestrPosCustomScan)(CustomScanState *node);
+
+	/* EXPLAIN support */
+	void    (*ExplainCustomScan)(CustomScanState *node,
+								 List *ancestors,
+								 struct ExplainState *es);
+	Node   *(*GetSpecialCustomVar)(CustomScanState *node,
+								   Var *varnode,
+								   PlanState **child_ps);
+} CustomExecMethods;
+
 /* ----------------------------------------------------------------
  *				 Join State Information
  * ----------------------------------------------------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6584a2c..bc71fea 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -108,6 +108,7 @@ typedef enum NodeTag
 	T_CteScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
+	T_CustomScanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index db02323..9dbb91c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -15,8 +15,10 @@
 #define PLANNODES_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
+#include "nodes/relation.h"
 #include "utils/lockwaitpolicy.h"
 
 
@@ -487,12 +489,30 @@ typedef struct ForeignScan
  *     CustomScan node
  * ----------------
  */
+struct CustomScanMethods;
+
 typedef struct CustomScan
 {
 	Scan		scan;
 	uint32		flags;	/* mask of CUSTOMPATH_* flags defined in relation.h */
+	struct CustomScanMethods *methods;
 } CustomScan;
 
+typedef struct CustomScanMethods
+{
+	const char *CustomName;
+	void	   (*SetCustomScanRef)(struct PlannerInfo *root,
+								   CustomScan *cscan,
+								   int rtoffset);
+	void	   (*FinalizeCustomScan)(struct PlannerInfo *root,
+									 CustomScan *cscan,
+									 bool (*finalize_primnode)(),
+									 void *finalize_context);
+	Node	  *(*CreateCustomScanState)(CustomScan *cscan);
+	void	   (*TextOutCustomScan)(StringInfo str, const CustomScan *node);
+	CustomScan *(*CopyCustomScan)(const CustomScan *from);
+} CustomScanMethods;
+
 /*
  * ==========
  * Join nodes
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 3fdc2cb..c97c577 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -86,6 +86,7 @@ extern ModifyTable *make_modifytable(PlannerInfo *root,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *rowMarks, int epqParam);
 extern bool is_projection_capable_plan(Plan *plan);
+extern Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
 
 /*
  * prototypes for plan/initsplan.c
@@ -130,6 +131,8 @@ extern bool query_is_distinct_for(Query *query, List *colnos, List *opids);
  */
 extern Plan *set_plan_references(PlannerInfo *root, Plan *plan);
 extern void fix_opfuncids(Node *node);
+extern Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
+extern void fix_expr_common(PlannerInfo *root, Node *node);
 extern void set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
pgsql-v9.5-custom-scan.part-1.v13.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-1.v13.patchDownload
 src/backend/nodes/outfuncs.c            | 14 +++++++
 src/backend/optimizer/path/allpaths.c   |  3 ++
 src/backend/optimizer/plan/createplan.c | 73 +++++++++++++++++++++++++++++++++
 src/backend/optimizer/util/pathnode.c   | 50 ++++++++++++++++++++++
 src/include/nodes/nodes.h               |  2 +
 src/include/nodes/plannodes.h           |  9 ++++
 src/include/nodes/relation.h            | 37 +++++++++++++++++
 src/include/optimizer/pathnode.h        |  9 ++++
 8 files changed, 197 insertions(+)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4bbfa62..d6da32c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1585,6 +1585,17 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
 }
 
 static void
+_outCustomPath(StringInfo str, const CustomPath *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPATH");
+	_outPathInfo(str, (const Path *) node);
+	WRITE_UINT_FIELD(flags);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomPath(str, node);
+}
+
+static void
 _outAppendPath(StringInfo str, const AppendPath *node)
 {
 	WRITE_NODE_TYPE("APPENDPATH");
@@ -3063,6 +3074,9 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignPath:
 				_outForeignPath(str, obj);
 				break;
+			case T_CustomPath:
+				_outCustomPath(str, obj);
+				break;
 			case T_AppendPath:
 				_outAppendPath(str, obj);
 				break;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index c81efe9..8b42e36 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -402,6 +402,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
 
+	/* Consider custom scans, if any */
+	create_customscan_paths(root, rel, rte);
+
 	/* Now find the cheapest of the paths for this rel */
 	set_cheapest(rel);
 }
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4b641a2..189f7ef 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,6 +77,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,
@@ -233,6 +236,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:
@@ -409,6 +413,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);
@@ -1072,6 +1083,68 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 	return plan;
 }
 
+/*
+ * create_custom_plan
+ *
+ * Returns a custom-scan plan for the base relation scanned by 'best_path'
+ * with restriction clauses 'clauses' and targetlist 'tlist'.
+ */
+static CustomScan *
+create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
+					   List *tlist, List *scan_clauses)
+{
+	CustomScan	   *custom_scan;
+	RelOptInfo	   *rel = best_path->path.parent;
+
+	/*
+	 * Right now, all we can support is CustomScan node which is associated
+	 * with a particular base relation to be scanned.
+	 */
+	Assert(rel && rel->reloptkind == RELOPT_BASEREL);
+
+	/*
+	 * Sort clauses into the best execution order, although custom-scan
+	 * provider can reorder them again.
+	 */
+	scan_clauses = order_qual_clauses(root, scan_clauses);
+
+	/*
+	 * Create a CustomScan (or its inheritance) node according to
+	 * the supplied CustomPath.
+	 */
+	custom_scan = (CustomScan *)
+		best_path->methods->PlanCustomPath(root,
+										   rel,
+										   best_path,
+										   tlist,
+										   scan_clauses);
+	/* additional sanity checks */
+	Assert(nodeTag(custom_scan) == best_path->path.pathtype);
+	if (IsA(custom_scan, CustomScan))
+	{
+		Index	scanrelid = ((Scan *)custom_scan)->scanrelid;
+
+		if (scanrelid != rel->relid)
+			elog(ERROR, "Bug? CustomScan tries to scan incorrect relation");
+	}
+	else
+		elog(ERROR, "unexpected node: %d", (int)nodeTag(custom_scan));
+
+	/*
+	 * NOTE: unlike create_foreignscan_plan(), it is responsibility of
+	 * the custom plan provider to replace outer-relation variables
+	 * with nestloop params, because we cannot know how many expression
+	 * trees are held in the private fields.
+	 */
+
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize((Plan *)custom_scan, &best_path->path);
+
+	return custom_scan;
+}
 
 /*****************************************************************************
  *
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 319e8b2..2ca0a18 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -27,6 +27,7 @@
 #include "optimizer/var.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/selfuncs.h"
 
 
@@ -1926,3 +1927,52 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*****************************************************************************
+ *     creation of custom-plan paths
+ *****************************************************************************/
+
+static List	   *custom_path_providers = NIL;
+
+/*
+ * register_custom_path_provider
+ *
+ * It registers a table of callback functions that implements a custom-path
+ * provider. The callback functions are expected to construct CustomPath node
+ * that provides an alternative logic to scan a relation (and so on in the
+ * future version), if extension can do.
+ * Note that the supplied CustomPathMethods is expected to locate on static
+ * memory area, so we don't copy individual fields here.
+ */
+void
+register_custom_path_provider(CustomPathMethods *cpp_methods)
+{
+	MemoryContext	oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+	custom_path_providers = lappend(custom_path_providers, cpp_methods);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * create_customscan_paths
+ *
+ * It calls back extension's entrypoint whether it can add alternative
+ * scan paths being extended from the CustomPath type.
+ * If any, the callback will add one or more paths using add_path().
+ */
+void
+create_customscan_paths(PlannerInfo *root,
+						RelOptInfo *baserel,
+						RangeTblEntry *rte)
+{
+	ListCell	   *cell;
+
+	foreach (cell, custom_path_providers)
+	{
+		const CustomPathMethods *cpp_methods = lfirst(cell);
+
+		if (cpp_methods->CreateCustomScanPath)
+			cpp_methods->CreateCustomScanPath(root, baserel, rte);
+	}
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 154d943..6584a2c 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,
@@ -224,6 +225,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 fb02390..db02323 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -483,6 +483,15 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *     CustomScan node
+ * ----------------
+ */
+typedef struct CustomScan
+{
+	Scan		scan;
+	uint32		flags;	/* mask of CUSTOMPATH_* flags defined in relation.h */
+} CustomScan;
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index f1a0504..2bbb7cc 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -15,6 +15,7 @@
 #define RELATION_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "storage/block.h"
@@ -884,6 +885,42 @@ typedef struct ForeignPath
 } ForeignPath;
 
 /*
+ * CustomPath represents a scan using custom logic
+ *
+ * Extension (that performs as custom-plan provider) can adds an alternative
+ * path using its custom type being delivered from CustomPath.
+ * They can store their private data on the extra fields of their custom
+ * object. A set of common methods are represented as function pointers in
+ * CustomPathMethods structure; extension has to set up then correctly.
+ */
+struct CustomPathMethods;
+struct Plan;		/* not to include plannodes.h here */
+
+#define CUSTOMPATH_SUPPORT_BACKWARD_SCAN	0x0001
+#define CUSTOMPATH_SUPPORT_MARK_RESTORE		0x0002
+
+typedef struct CustomPath
+{
+	Path        path;
+	uint32		flags;
+	const struct CustomPathMethods *methods;
+} CustomPath;
+
+typedef struct CustomPathMethods
+{
+	const char *CustomName;
+	void	(*CreateCustomScanPath)(PlannerInfo *root,
+									RelOptInfo *baserel,
+									RangeTblEntry *rte);
+	struct Plan	*(*PlanCustomPath)(PlannerInfo *root,
+								   RelOptInfo *rel,
+								   CustomPath *best_path,
+								   List *tlist,
+								   List *clauses);
+	void    (*TextOutCustomPath)(StringInfo str, const CustomPath *node);
+} CustomPathMethods;
+
+/*
  * AppendPath represents an Append plan, ie, successive execution of
  * several member plans.
  *
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 26b17f5..2b67ae6 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,15 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					double loop_count);
 
 /*
+ * Interface definition of custom-scan providers
+ */
+extern void register_custom_path_provider(CustomPathMethods *cpp_methods);
+
+extern void create_customscan_paths(PlannerInfo *root,
+									RelOptInfo *baserel,
+									RangeTblEntry *rte);
+
+/*
  * prototypes for relnode.c
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
#8Robert Haas
robertmhaas@gmail.com
In reply to: Kouhei Kaigai (#7)

On Mon, Oct 27, 2014 at 2:35 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

FYI, patch v12 part 2 no longer applies cleanly.

Thanks. I rebased the patch set according to the latest master branch.
The attached v13 can be applied to the master.

I've committed parts 1 and 2 of this, without the documentation, and
with some additional cleanup. I am not sure that this feature is
sufficiently non-experimental that it deserves to be documented, but
if we're thinking of doing that then the documentation needs a lot
more work. I think part 3 of the patch is mostly useful as a
demonstration of how this API can be used, and is not something we
probably want to commit. So I'm not planning, at this point, to spend
any more time on this patch series, and will mark it Committed in the
CF app.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#8)

On Mon, Oct 27, 2014 at 2:35 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

FYI, patch v12 part 2 no longer applies cleanly.

Thanks. I rebased the patch set according to the latest master branch.
The attached v13 can be applied to the master.

I've committed parts 1 and 2 of this, without the documentation, and with
some additional cleanup. I am not sure that this feature is sufficiently
non-experimental that it deserves to be documented, but if we're thinking
of doing that then the documentation needs a lot more work. I think part
3 of the patch is mostly useful as a demonstration of how this API can be
used, and is not something we probably want to commit. So I'm not planning,
at this point, to spend any more time on this patch series, and will mark
it Committed in the CF app.

Thanks for your great help.

I and Hanada-san have discussed a further enhancement of that interface
that allows to replace a join by custom-scan; probably, can be utilized
with an extension that runs materialized-view instead of join on the fly.
We will submit a design proposal of this enhancement later.

Best regards,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#8)

On Sat, Nov 8, 2014 at 4:16 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Oct 27, 2014 at 2:35 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com>

wrote:

FYI, patch v12 part 2 no longer applies cleanly.

Thanks. I rebased the patch set according to the latest master branch.
The attached v13 can be applied to the master.

I've committed parts 1 and 2 of this, without the documentation, and
with some additional cleanup.

Few observations/questions related to this commit:

1.
@@ -5546,6 +5568,29 @@ get_variable(Var *var, int levelsup, bool
istoplevel, deparse_context *context)
  colinfo = deparse_columns_fetch(var->varno, dpns);
  attnum = var->varattno;
  }
+ else if (IS_SPECIAL_VARNO(var->varno) &&
+ IsA(dpns->planstate, CustomScanState) &&
+ (expr = GetSpecialCustomVar((CustomScanState *) dpns->planstate,
+ var, &child_ps)) != NULL)
+ {
+ deparse_namespace save_dpns;
+
+ if (child_ps)
+ push_child_plan(dpns, child_ps, &save_dpns);
+ /*
+ * Force parentheses because our caller probably assumed a Var is a
+ * simple expression.
+ */
+ if (!IsA(expr, Var))
+ appendStringInfoChar(buf, '(');
+ get_rule_expr((Node *) expr, context, true);
+ if (!IsA(expr, Var))
+ appendStringInfoChar(buf, ')');
+
+ if (child_ps)
+ pop_child_plan(dpns, &save_dpns);
+ return NULL;
+ }

a. It seems Assert for netlelvelsup is missing in this loop.
b. Below comment in function get_variable can be improved
w.r.t handling for CustomScanState. The comment indicates
that if varno is OUTER_VAR or INNER_VAR or INDEX_VAR, it handles
all of them similarly which seems to be slightly changed for
CustomScanState.

/*
* Try to find the relevant RTE in this rtable. In a plan tree, it's
* likely that varno is
OUTER_VAR or INNER_VAR, in which case we must dig
* down into the subplans, or INDEX_VAR, which is
resolved similarly. Also
* find the aliases previously assigned for this RTE.
*/

2.
+void
+register_custom_path_provider(CustomPathMethods *cpp_methods)
{
..
}

Shouldn't there be unregister function corresponding to above
register function?

With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

#11Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Amit Kapila (#10)
1 attachment(s)

On Sat, Nov 8, 2014 at 4:16 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Oct 27, 2014 at 2:35 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com>

wrote:

FYI, patch v12 part 2 no longer applies cleanly.

Thanks. I rebased the patch set according to the latest master branch.
The attached v13 can be applied to the master.

I've committed parts 1 and 2 of this, without the documentation, and
with some additional cleanup.

Few observations/questions related to this commit:

1.
@@ -5546,6 +5568,29 @@ get_variable(Var *var, int levelsup, bool istoplevel,
deparse_context *context)
colinfo = deparse_columns_fetch(var->varno, dpns);
attnum = var->varattno;
}
+ else if (IS_SPECIAL_VARNO(var->varno) && IsA(dpns->planstate,
+ CustomScanState) && (expr = GetSpecialCustomVar((CustomScanState *)
+ dpns->planstate, var, &child_ps)) != NULL) { deparse_namespace
+ save_dpns;
+
+ if (child_ps)
+ push_child_plan(dpns, child_ps, &save_dpns);
+ /*
+ * Force parentheses because our caller probably assumed a Var is a
+ * simple expression.
+ */
+ if (!IsA(expr, Var))
+ appendStringInfoChar(buf, '(');
+ get_rule_expr((Node *) expr, context, true); if (!IsA(expr, Var))
+ appendStringInfoChar(buf, ')');
+
+ if (child_ps)
+ pop_child_plan(dpns, &save_dpns);
+ return NULL;
+ }

a. It seems Assert for netlelvelsup is missing in this loop.

Indeed, this if-block does not have assertion unlike other special-varno.

b. Below comment in function get_variable can be improved w.r.t handling
for CustomScanState. The comment indicates that if varno is OUTER_VAR or
INNER_VAR or INDEX_VAR, it handles all of them similarly which seems to
be slightly changed for CustomScanState.

/*
* Try to find the relevant RTE in this rtable. In a plan tree, it's
* likely that varno is
OUTER_VAR or INNER_VAR, in which case we must dig
* down into the subplans, or INDEX_VAR, which is resolved similarly. Also
* find the aliases previously assigned for this RTE.
*/

I made a small comment that introduces only extension knows the mapping
between these special varno and underlying expression, thus, it queries
providers the expression being tied with this special varnode.
Does it make sense?

2.
+void
+register_custom_path_provider(CustomPathMethods *cpp_methods)
{
..
}

Shouldn't there be unregister function corresponding to above register
function?

Even though it is not difficult to implement, what situation will make
sense to unregister rather than enable_xxxx_scan GUC parameter added by
extension itself?
I initially thought prepared statement with custom-scan node is problematic
if provider got unregistered / unloaded, however, internal_unload_library()
actually does nothing. So, it is at least harmless even if we implemented.

Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-v9.5-get_variable-smallfix.patchapplication/octet-stream; name=pgsql-v9.5-get_variable-smallfix.patchDownload
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index bf4e81f..b5616ba 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5560,6 +5560,9 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 	 * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
 	 * down into the subplans, or INDEX_VAR, which is resolved similarly. Also
 	 * find the aliases previously assigned for this RTE.
+	 * In case when these special verno is assigned by custom-scan provider,
+	 * it has to be resolved by querying the provider because only extension
+	 * knows the mapping of the special var-node.
 	 */
 	if (var->varno >= 1 && var->varno <= list_length(dpns->rtable))
 	{
@@ -5575,6 +5578,8 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 	{
 		deparse_namespace	save_dpns;
 
+		Assert(netlevelsup == 0);
+
 		if (child_ps)
 			push_child_plan(dpns, child_ps, &save_dpns);
 		/*
#12Amit Kapila
amit.kapila16@gmail.com
In reply to: Kouhei Kaigai (#11)

On Mon, Nov 10, 2014 at 4:18 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

Few observations/questions related to this commit:

1.
@@ -5546,6 +5568,29 @@ get_variable(Var *var, int levelsup, bool

istoplevel,

deparse_context *context)
colinfo = deparse_columns_fetch(var->varno, dpns);
attnum = var->varattno;
}
+ else if (IS_SPECIAL_VARNO(var->varno) && IsA(dpns->planstate,
+ CustomScanState) && (expr = GetSpecialCustomVar((CustomScanState *)
+ dpns->planstate, var, &child_ps)) != NULL) { deparse_namespace
+ save_dpns;
+
+ if (child_ps)
+ push_child_plan(dpns, child_ps, &save_dpns);
+ /*
+ * Force parentheses because our caller probably assumed a Var is a
+ * simple expression.
+ */
+ if (!IsA(expr, Var))
+ appendStringInfoChar(buf, '(');
+ get_rule_expr((Node *) expr, context, true); if (!IsA(expr, Var))
+ appendStringInfoChar(buf, ')');
+
+ if (child_ps)
+ pop_child_plan(dpns, &save_dpns);
+ return NULL;
+ }

a. It seems Assert for netlelvelsup is missing in this loop.

Indeed, this if-block does not have assertion unlike other special-varno.

Similar handling is required in function get_name_for_var_field().
Another point which I wanted to clarify is that in function
get_name_for_var_field(), for all other cases except the new
case added for CustomScanState, it calls get_name_for_var_field()
recursively to get the name of field whereas for CustomScanState,
it calls get_rule_expr() which doesn't look to be problematic in general,
but still it is better to get the name as other cases does unless there
is a special need for CustomScanState?

2.
+void
+register_custom_path_provider(CustomPathMethods *cpp_methods)
{
..
}

Shouldn't there be unregister function corresponding to above register
function?

Even though it is not difficult to implement, what situation will make
sense to unregister rather than enable_xxxx_scan GUC parameter added by
extension itself?

I thought that in general if user has the API to register the custom path
methods, it should have some way to unregister them and also user might
need to register some different custom path methods after unregistering
the previous one's. I think we should see what Robert or others have to
say about this point before trying to provide such an API.

With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

#13Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#12)

On Mon, Nov 10, 2014 at 6:55 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:

I thought that in general if user has the API to register the custom path
methods, it should have some way to unregister them and also user might
need to register some different custom path methods after unregistering
the previous one's. I think we should see what Robert or others have to
say about this point before trying to provide such an API.

I wouldn't bother. As KaiGai says, if you want to shut the
functionality off, the provider itself can provide a GUC. Also, we
really have made no effort to ensure that loadable modules can be
safely unloaded, or hooked functions safely-unhooked.
ExecutorRun_hook is a good example. Typical of hook installation is
this:

prev_ExecutorRun = ExecutorRun_hook;
ExecutorRun_hook = pgss_ExecutorRun;

Well, if multiple extensions use this hook, then there's no hope of
unloading them exception in reverse order of installation. We
essentially end up creating a singly-linked list of hook users, but
with the next-pointers stored in arbitrarily-named, likely-static
variables owned by the individual extensions, so that nobody can
actually traverse it. This might be worth fixing as part of a
concerted campaign to make UNLOAD work, but unless somebody's really
going to do that I see little reason to hold this to a higher standard
than we apply elsewhere. The ability to remove extensions from this
hook won't be valuable by itself.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#13)

On Mon, Nov 10, 2014 at 6:33 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Nov 10, 2014 at 6:55 AM, Amit Kapila <amit.kapila16@gmail.com>

wrote:

I thought that in general if user has the API to register the custom

path

methods, it should have some way to unregister them and also user might
need to register some different custom path methods after unregistering
the previous one's. I think we should see what Robert or others have to
say about this point before trying to provide such an API.

I wouldn't bother. As KaiGai says, if you want to shut the
functionality off, the provider itself can provide a GUC. Also, we
really have made no effort to ensure that loadable modules can be
safely unloaded, or hooked functions safely-unhooked.
ExecutorRun_hook is a good example. Typical of hook installation is
this:

prev_ExecutorRun = ExecutorRun_hook;
ExecutorRun_hook = pgss_ExecutorRun;

In this case, Extension takes care of register and unregister for
hook. In _PG_init(), it register the hook and _PG_fini() it
unregisters the same. So if for custom scan core pg is
providing API to register the methods, shouldn't it provide an
API to unregister the same?

With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

#15Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#14)

On Tue, Nov 11, 2014 at 12:33 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:

On Mon, Nov 10, 2014 at 6:33 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Nov 10, 2014 at 6:55 AM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

I thought that in general if user has the API to register the custom
path
methods, it should have some way to unregister them and also user might
need to register some different custom path methods after unregistering
the previous one's. I think we should see what Robert or others have to
say about this point before trying to provide such an API.

I wouldn't bother. As KaiGai says, if you want to shut the
functionality off, the provider itself can provide a GUC. Also, we
really have made no effort to ensure that loadable modules can be
safely unloaded, or hooked functions safely-unhooked.
ExecutorRun_hook is a good example. Typical of hook installation is
this:

prev_ExecutorRun = ExecutorRun_hook;
ExecutorRun_hook = pgss_ExecutorRun;

In this case, Extension takes care of register and unregister for
hook. In _PG_init(), it register the hook and _PG_fini() it
unregisters the same.

The point is that there's nothing that you can do _PG_fini() that will
work correctly. If it does ExecutorRun_hook = prev_ExecutorRun, that's
fine if it's the most-recently-installed hook. But if it isn't, then
doing so corrupts the list.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#8)

Robert Haas <robertmhaas@gmail.com> writes:

I've committed parts 1 and 2 of this, without the documentation, and
with some additional cleanup. I am not sure that this feature is
sufficiently non-experimental that it deserves to be documented, but
if we're thinking of doing that then the documentation needs a lot
more work. I think part 3 of the patch is mostly useful as a
demonstration of how this API can be used, and is not something we
probably want to commit. So I'm not planning, at this point, to spend
any more time on this patch series, and will mark it Committed in the
CF app.

I've done some preliminary cleanup on this patch, but I'm still pretty
desperately unhappy about some aspects of it, in particular the way that
it gets custom scan providers directly involved in setrefs.c,
finalize_primnode, and replace_nestloop_params processing. I don't
want any of that stuff exported outside the core, as freezing those
APIs would be a very nasty restriction on future planner development.
What's more, it doesn't seem like doing that creates any value for
custom-scan providers, only a requirement for extra boilerplate code
for them to provide.

ISTM that we could avoid that by borrowing the design used for FDW
plans, namely that any expressions you would like planner post-processing
services for should be stuck into a predefined List field (fdw_exprs
for the ForeignScan case, perhaps custom_exprs for the CustomScan case?).
This would let us get rid of the SetCustomScanRef and FinalizeCustomScan
callbacks as well as simplify the API contract for PlanCustomPath.

I'm also wondering why this patch didn't follow the FDW lead in terms of
expecting private data to be linked from specialized "private" fields.
The design as it stands (with an expectation that CustomPaths, CustomPlans
etc would be larger than the core code knows about) is not awful, but it
seems just randomly different from the FDW precedent, and I don't see a
good argument why it should be. If we undid that we could get rid of
CopyCustomScan callbacks, and perhaps also TextOutCustomPath and
TextOutCustomScan (though I concede there might be some argument to keep
the latter two anyway for debugging reasons).

Lastly, I'm pretty unconvinced that the GetSpecialCustomVar mechanism
added to ruleutils.c is anything but dead weight that we'll have to
maintain forever. It seems somewhat unlikely that anyone will figure
out how to use it, or indeed that it can be used for anything very
interesting. I suppose the argument for it is that you could stick
"custom vars" into the tlist of a CustomScan plan node, but you cannot,
at least not without a bunch of infrastructure that isn't there now;
in particular how would such an expression ever get matched by setrefs.c
to higher-level plan tlists? I think we should rip that out and wait
to see a complete use-case before considering putting it back.

Comments?

regards, tom lane

PS: with no documentation it's arguable that the entire patch is just
dead weight. I'm not very happy that it went in without any.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#16)

On Thu, Nov 20, 2014 at 7:10 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I've done some preliminary cleanup on this patch, but I'm still pretty
desperately unhappy about some aspects of it, in particular the way that
it gets custom scan providers directly involved in setrefs.c,
finalize_primnode, and replace_nestloop_params processing. I don't
want any of that stuff exported outside the core, as freezing those
APIs would be a very nasty restriction on future planner development.
What's more, it doesn't seem like doing that creates any value for
custom-scan providers, only a requirement for extra boilerplate code
for them to provide.

ISTM that we could avoid that by borrowing the design used for FDW
plans, namely that any expressions you would like planner post-processing
services for should be stuck into a predefined List field (fdw_exprs
for the ForeignScan case, perhaps custom_exprs for the CustomScan case?).
This would let us get rid of the SetCustomScanRef and FinalizeCustomScan
callbacks as well as simplify the API contract for PlanCustomPath.

Ah, that makes sense. I'm not sure I really understand what's so bad
about the current system, but I have no issue with revising it for
consistency.

I'm also wondering why this patch didn't follow the FDW lead in terms of
expecting private data to be linked from specialized "private" fields.
The design as it stands (with an expectation that CustomPaths, CustomPlans
etc would be larger than the core code knows about) is not awful, but it
seems just randomly different from the FDW precedent, and I don't see a
good argument why it should be. If we undid that we could get rid of
CopyCustomScan callbacks, and perhaps also TextOutCustomPath and
TextOutCustomScan (though I concede there might be some argument to keep
the latter two anyway for debugging reasons).

OK.

Lastly, I'm pretty unconvinced that the GetSpecialCustomVar mechanism
added to ruleutils.c is anything but dead weight that we'll have to
maintain forever. It seems somewhat unlikely that anyone will figure
out how to use it, or indeed that it can be used for anything very
interesting. I suppose the argument for it is that you could stick
"custom vars" into the tlist of a CustomScan plan node, but you cannot,
at least not without a bunch of infrastructure that isn't there now;
in particular how would such an expression ever get matched by setrefs.c
to higher-level plan tlists? I think we should rip that out and wait
to see a complete use-case before considering putting it back.

I thought this was driven by a suggestion from you, but maybe KaiGai
can comment.

PS: with no documentation it's arguable that the entire patch is just
dead weight. I'm not very happy that it went in without any.

As I said, I wasn't sure we wanted to commit to the API enough to
document it, and by the time you get done whacking the stuff above
around, the documentation KaiGai wrote for it (which was also badly in
need of editing by a native English speaker) would have been mostly
obsolete anyway. But I'm willing to put some effort into it once you
get done rearranging the furniture, if that's helpful.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Tom Lane (#16)

Robert Haas <robertmhaas@gmail.com> writes:

I've committed parts 1 and 2 of this, without the documentation, and
with some additional cleanup. I am not sure that this feature is
sufficiently non-experimental that it deserves to be documented, but
if we're thinking of doing that then the documentation needs a lot
more work. I think part 3 of the patch is mostly useful as a
demonstration of how this API can be used, and is not something we
probably want to commit. So I'm not planning, at this point, to spend
any more time on this patch series, and will mark it Committed in the
CF app.

I've done some preliminary cleanup on this patch, but I'm still pretty
desperately unhappy about some aspects of it, in particular the way that
it gets custom scan providers directly involved in setrefs.c,
finalize_primnode, and replace_nestloop_params processing. I don't want
any of that stuff exported outside the core, as freezing those APIs would
be a very nasty restriction on future planner development.
What's more, it doesn't seem like doing that creates any value for
custom-scan providers, only a requirement for extra boilerplate code for
them to provide.

ISTM that we could avoid that by borrowing the design used for FDW plans,
namely that any expressions you would like planner post-processing services
for should be stuck into a predefined List field (fdw_exprs for the
ForeignScan case, perhaps custom_exprs for the CustomScan case?).
This would let us get rid of the SetCustomScanRef and FinalizeCustomScan
callbacks as well as simplify the API contract for PlanCustomPath.

If core backend can know which field contains expression nodes but
processed by custom-scan provider, FinalizedCustomScan might be able
to rid. However, rid of SetCustomScanRef makes unavailable a significant
use case I intend.
In case when tlist contains complicated expression node (thus it takes
many cpu cycles) and custom-scan provider has a capability to compute
this expression node externally, SetCustomScanRef hook allows to replace
this complicate expression node by a simple Var node that references
a value being externally computed.

Because only custom-scan provider can know how this "pseudo" varnode
is mapped to the original expression, it needs to call the hook to
assign correct varno/varattno. We expect it assigns a special vano,
like OUTER_VAR, and it is solved with GetSpecialCustomVar.

One other idea is, core backend has a facility to translate relationship
between the original expression and the pseudo varnode according to the
map information given by custom-scan provider.

I'm also wondering why this patch didn't follow the FDW lead in terms of
expecting private data to be linked from specialized "private" fields.
The design as it stands (with an expectation that CustomPaths, CustomPlans
etc would be larger than the core code knows about) is not awful, but it
seems just randomly different from the FDW precedent, and I don't see a
good argument why it should be. If we undid that we could get rid of
CopyCustomScan callbacks, and perhaps also TextOutCustomPath and
TextOutCustomScan (though I concede there might be some argument to keep
the latter two anyway for debugging reasons).

Yep, its original proposition last year followed the FDW manner. It has
custom_private field to store the private data of custom-scan provider,
however, I was suggested to change the current form because it added
a couple of routines to encode / decode Bitmapset that may lead other
encode / decode routines for other data types.

I'm neutral for this design choice. Either of them people accept is
better for me.

Lastly, I'm pretty unconvinced that the GetSpecialCustomVar mechanism added
to ruleutils.c is anything but dead weight that we'll have to maintain
forever. It seems somewhat unlikely that anyone will figure out how to
use it, or indeed that it can be used for anything very interesting. I
suppose the argument for it is that you could stick "custom vars" into the
tlist of a CustomScan plan node, but you cannot, at least not without a
bunch of infrastructure that isn't there now; in particular how would such
an expression ever get matched by setrefs.c to higher-level plan tlists?
I think we should rip that out and wait to see a complete use-case before
considering putting it back.

As I described above, as long as core backend has a facility to manage the
relationship between a pseudo varnode and complicated expression node, I
think we can rid this callback.

PS: with no documentation it's arguable that the entire patch is just dead
weight. I'm not very happy that it went in without any.

I agree with this. Is it a good to write up a wikipage to brush up the
documentation draft?

Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Kouhei Kaigai (#18)

On Thu, Nov 20, 2014 at 7:10 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I've done some preliminary cleanup on this patch, but I'm still pretty
desperately unhappy about some aspects of it, in particular the way
that it gets custom scan providers directly involved in setrefs.c,
finalize_primnode, and replace_nestloop_params processing. I don't
want any of that stuff exported outside the core, as freezing those
APIs would be a very nasty restriction on future planner development.
What's more, it doesn't seem like doing that creates any value for
custom-scan providers, only a requirement for extra boilerplate code
for them to provide.

ISTM that we could avoid that by borrowing the design used for FDW
plans, namely that any expressions you would like planner
post-processing services for should be stuck into a predefined List
field (fdw_exprs for the ForeignScan case, perhaps custom_exprs for the

CustomScan case?).

This would let us get rid of the SetCustomScanRef and
FinalizeCustomScan callbacks as well as simplify the API contract for

PlanCustomPath.

Ah, that makes sense. I'm not sure I really understand what's so bad about
the current system, but I have no issue with revising it for consistency.

I'm also wondering why this patch didn't follow the FDW lead in terms
of expecting private data to be linked from specialized "private" fields.
The design as it stands (with an expectation that CustomPaths,
CustomPlans etc would be larger than the core code knows about) is not
awful, but it seems just randomly different from the FDW precedent,
and I don't see a good argument why it should be. If we undid that we
could get rid of CopyCustomScan callbacks, and perhaps also
TextOutCustomPath and TextOutCustomScan (though I concede there might
be some argument to keep the latter two anyway for debugging reasons).

OK.

So, the existing form shall be revised as follows?

* CustomScan shall not be a base type of custom data-type managed by
extension, instead of private data field.
* It also eliminates CopyCustomScan and TextOutCustomPath/Scan callback.
* Expression nodes that will not be processed by core backend, but
processed by extension shall be connected to special field, like
fdw_exprs in FDW.
* Translation between a pseudo varnode and original expression node
shall be informed to the core backend, instead of SetCustomScanRef
and GetSpecialCustomVar.

Lastly, I'm pretty unconvinced that the GetSpecialCustomVar mechanism
added to ruleutils.c is anything but dead weight that we'll have to
maintain forever. It seems somewhat unlikely that anyone will figure
out how to use it, or indeed that it can be used for anything very
interesting. I suppose the argument for it is that you could stick
"custom vars" into the tlist of a CustomScan plan node, but you
cannot, at least not without a bunch of infrastructure that isn't
there now; in particular how would such an expression ever get matched
by setrefs.c to higher-level plan tlists? I think we should rip that
out and wait to see a complete use-case before considering putting it

back.

I thought this was driven by a suggestion from you, but maybe KaiGai can
comment.

PS: with no documentation it's arguable that the entire patch is just
dead weight. I'm not very happy that it went in without any.

As I said, I wasn't sure we wanted to commit to the API enough to document
it, and by the time you get done whacking the stuff above around, the
documentation KaiGai wrote for it (which was also badly in need of editing
by a native English speaker) would have been mostly obsolete anyway. But
I'm willing to put some effort into it once you get done rearranging the
furniture, if that's helpful.

For people's convenient, I'd like to set up a wikipage to write up a draft
of SGML documentation for easy updates by native English speakers.

Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kouhei Kaigai (#18)

Kouhei Kaigai <kaigai@ak.jp.nec.com> writes:

I've done some preliminary cleanup on this patch, but I'm still pretty
desperately unhappy about some aspects of it, in particular the way that
it gets custom scan providers directly involved in setrefs.c,
finalize_primnode, and replace_nestloop_params processing. I don't want
any of that stuff exported outside the core, as freezing those APIs would
be a very nasty restriction on future planner development.

If core backend can know which field contains expression nodes but
processed by custom-scan provider, FinalizedCustomScan might be able
to rid. However, rid of SetCustomScanRef makes unavailable a significant
use case I intend.
In case when tlist contains complicated expression node (thus it takes
many cpu cycles) and custom-scan provider has a capability to compute
this expression node externally, SetCustomScanRef hook allows to replace
this complicate expression node by a simple Var node that references
a value being externally computed.

That's a fine goal to have, but this is not a solution that works for
any except trivial cases. The problem is that that complicated expression
isn't going to be in the CustomScan's tlist in the first place unless you
have a one-node plan. As soon as you have a join, for example, the
planner is going to delay calculation of anything more complex than a
plain Var to above the join. Aggregation, GROUP BY, etc would also defeat
such an optimization.

This gets back to the remarks I made earlier about it not being possible
to do anything very interesting in a plugin of this nature. You really
need cooperation from other places in the planner if you want to do things
like pushing down calculations into an external provider. And it's not
at all clear how that would even work, let alone how we might make it
implementable as a plugin rather than core code.

Also, even if we could think of a way to do this from a CustomScan plugin,
that would fail to cover some very significant use-cases for pushing
down complex expressions, for example:
* retrieving values of expensive functions from expression indexes;
* pushing down expensive functions into FDWs so they can be done remotely.
And I'm also worried that once we've exported and thereby frozen the APIs
in this area, we'd be operating with one hand tied behind our backs in
solving those use-cases. So I'm not very excited about pursuing the
problem in this form.

So I remain of the opinion that we should get the CustomScan stuff out
of setrefs processing, and also that having EXPLAIN support for such
special variables is premature. It's possible that after the dust
settles we'd wind up with additions to ruleutils that look exactly like
what's in this patch ... but I'd bet against that.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#17)

Robert Haas <robertmhaas@gmail.com> writes:

As I said, I wasn't sure we wanted to commit to the API enough to
document it, and by the time you get done whacking the stuff above
around, the documentation KaiGai wrote for it (which was also badly in
need of editing by a native English speaker) would have been mostly
obsolete anyway. But I'm willing to put some effort into it once you
get done rearranging the furniture, if that's helpful.

I thought of another API change we should consider. It's weird that
CustomPathMethods includes CreateCustomScanPath, because that's not
a method you apply to a CustomPath, it's what creates them in the first
place. I'm inclined to think that we should get rid of that and
register_custom_path_provider() altogether and just provide a function
hook variable equivalent to create_customscan_paths, which providers can
link into in the usual way. The register_custom_path_provider mechanism
might have some use if we were also going to provide deregister-by-name
functionality, but as you pointed out upthread, that's not likely to ever
be worth doing.

The hook function might better be named something like
editorialize_on_relation_paths, since in principle it could screw around
with the Paths already made by the core code, not just add CustomPaths.
There's an analogy to get_relation_info_hook, which is meant to let
plugins editorialize on the relation's index list. So maybe
set_plain_rel_pathlist_hook?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Tom Lane (#20)

Kouhei Kaigai <kaigai@ak.jp.nec.com> writes:

I've done some preliminary cleanup on this patch, but I'm still
pretty desperately unhappy about some aspects of it, in particular
the way that it gets custom scan providers directly involved in
setrefs.c, finalize_primnode, and replace_nestloop_params processing.
I don't want any of that stuff exported outside the core, as freezing
those APIs would be a very nasty restriction on future planner

development.

If core backend can know which field contains expression nodes but
processed by custom-scan provider, FinalizedCustomScan might be able
to rid. However, rid of SetCustomScanRef makes unavailable a
significant use case I intend.
In case when tlist contains complicated expression node (thus it takes
many cpu cycles) and custom-scan provider has a capability to compute
this expression node externally, SetCustomScanRef hook allows to
replace this complicate expression node by a simple Var node that
references a value being externally computed.

That's a fine goal to have, but this is not a solution that works for any
except trivial cases. The problem is that that complicated expression
isn't going to be in the CustomScan's tlist in the first place unless you
have a one-node plan. As soon as you have a join, for example, the planner
is going to delay calculation of anything more complex than a plain Var
to above the join. Aggregation, GROUP BY, etc would also defeat such an
optimization.

This gets back to the remarks I made earlier about it not being possible
to do anything very interesting in a plugin of this nature. You really
need cooperation from other places in the planner if you want to do things
like pushing down calculations into an external provider. And it's not
at all clear how that would even work, let alone how we might make it
implementable as a plugin rather than core code.

Also, even if we could think of a way to do this from a CustomScan plugin,
that would fail to cover some very significant use-cases for pushing down
complex expressions, for example:
* retrieving values of expensive functions from expression indexes;
* pushing down expensive functions into FDWs so they can be done remotely.
And I'm also worried that once we've exported and thereby frozen the APIs
in this area, we'd be operating with one hand tied behind our backs in solving
those use-cases. So I'm not very excited about pursuing the problem in
this form.

I count understand your concern; only available on a one-node plan and
may needs additional interaction between core and extension to push-
down complicated expression.
So, right now, I have to admit to rid of this hook for this purpose.

On the other hand, I thought to use similar functionality, but not
same, to implement join-replacement by custom-scan. I'd like to see
your comment prior to patch submission.

Let assume a custom-scan provider that runs on a materialized-view
(or, something like a query cache in memory) instead of join.
In this case, a reasonable design is to fetch a tuple from the
materialized-view then put it on the ecxt_scantuple of ExprContext
prior to evaluation of qualifier or tlist, unlike usual join takes
two slots - ecxt_innertuple and ecxt_outertuple.
Also, it leads individual varnode has to reference exct_scantuple,
neither ecxt_innertuple nor ecxt_outertuple.
The tuple in exct_scantuple contains attributes come from both
relations, thus, it needs to keep relationship a varattno of the
scanned tuple and its source relation where does it come from.

I intended to use the SetCustomScanRef callback to adjust varno
and varattno of the varnode that references the custom-scan node;
as if set_join_references() doing.
It does not mean a replacement of general expression by varnode,
just re-mapping of varno/varattno.

So I remain of the opinion that we should get the CustomScan stuff out of
setrefs processing, and also that having EXPLAIN support for such special
variables is premature. It's possible that after the dust settles we'd
wind up with additions to ruleutils that look exactly like what's in this
patch ... but I'd bet against that.

So, I can agree with rid of SetCustomScanRef and GetSpecialCustomVar.
However, some alternative functionality to implement the varno/varattno
remapping is needed soon.
How about your thought?

Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kouhei Kaigai (#22)

Kouhei Kaigai <kaigai@ak.jp.nec.com> writes:

Let assume a custom-scan provider that runs on a materialized-view
(or, something like a query cache in memory) instead of join.
In this case, a reasonable design is to fetch a tuple from the
materialized-view then put it on the ecxt_scantuple of ExprContext
prior to evaluation of qualifier or tlist, unlike usual join takes
two slots - ecxt_innertuple and ecxt_outertuple.
Also, it leads individual varnode has to reference exct_scantuple,
neither ecxt_innertuple nor ecxt_outertuple.

OK, that's possibly a reasonable way to do it at runtime. You don't
*have* to do it that way of course. It would be only marginally
less efficient to reconstruct two tuples that match the shapes of the
original join inputs.

I intended to use the SetCustomScanRef callback to adjust varno
and varattno of the varnode that references the custom-scan node;
as if set_join_references() doing.

I think this is really fundamentally misguided. setrefs.c has no
business doing anything "interesting" like making semantically important
substitutions; those decisions need to be made much earlier. An example
in the context of your previous proposal is that getting rid of expensive
functions without any adjustment of cost estimates is just wrong; and
I don't mean that you forgot to have your setrefs.c hook hack up the
Plan's cost fields. The cost estimates need to change at the Path stage,
or the planner might not even select the right path at all.

I'm not sure where would be an appropriate place to deal with the kind of
thing you're thinking about here. But I'm really not happy with the
concept of exposing the guts of setrefs.c in order to enable
single-purpose kluges like this. We have fairly general problems to solve
in this area, and we should be working on solving them, not on freezing
relevant planner APIs to support marginally-useful external plugins.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Tom Lane (#23)

Kouhei Kaigai <kaigai@ak.jp.nec.com> writes:

Let assume a custom-scan provider that runs on a materialized-view
(or, something like a query cache in memory) instead of join.
In this case, a reasonable design is to fetch a tuple from the
materialized-view then put it on the ecxt_scantuple of ExprContext
prior to evaluation of qualifier or tlist, unlike usual join takes two
slots - ecxt_innertuple and ecxt_outertuple.
Also, it leads individual varnode has to reference exct_scantuple,
neither ecxt_innertuple nor ecxt_outertuple.

OK, that's possibly a reasonable way to do it at runtime. You don't
*have* to do it that way of course. It would be only marginally less
efficient to reconstruct two tuples that match the shapes of the original
join inputs.

I intended to use the SetCustomScanRef callback to adjust varno and
varattno of the varnode that references the custom-scan node; as if
set_join_references() doing.

I think this is really fundamentally misguided. setrefs.c has no business
doing anything "interesting" like making semantically important
substitutions; those decisions need to be made much earlier. An example
in the context of your previous proposal is that getting rid of expensive
functions without any adjustment of cost estimates is just wrong; and I
don't mean that you forgot to have your setrefs.c hook hack up the Plan's
cost fields. The cost estimates need to change at the Path stage, or the
planner might not even select the right path at all.

Because we right now have no functionality to register custom-scan path
instead of join, I had to show another use scenario...

I'm not sure where would be an appropriate place to deal with the kind of
thing you're thinking about here. But I'm really not happy with the concept
of exposing the guts of setrefs.c in order to enable single-purpose kluges
like this. We have fairly general problems to solve in this area, and we
should be working on solving them, not on freezing relevant planner APIs
to support marginally-useful external plugins.

From my standpoint, varnode remapping on relations join is higher priority
than complicated expression node. As long as the core backend handles this
job, yes, I think a hook in setrefs.c is not mandatory.
Also, it means the role to solve special vernode on EXPLAIN is moved from
extension to the code, GetSpecialCustomVar can be rid.

Let me explain the current idea of mine.
CustomScan node will have a field that hold varnode mapping information
that is constructed by custom-scan provider on create_customscan_plan,
if they want. It is probably a list of varnode.
If exists, setrefs.c changes its behavior; that updates varno/varattno of
varnode according to this mapping, as if set_join_references() does
based on indexed_tlist.
To reference exct_scantuple, INDEX_VAR will be a best choice for varno
of these varnodes, and index of the above varnode mapping list will
be varattno. It can be utilized to make EXPLAIN output, instead of
GetSpecialCustomVar hook.

So, steps to go may be:
(1) Add custom_private, custom_exprs, ... instead of self defined data
type based on CustomXXX.
(2) Rid of SetCustomScanRef and GetSpecialCustomVar hook for the current
custom-"scan" support.
(3) Integration of above varnode mapping feature within upcoming join
replacement by custom-scan support.

Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kouhei Kaigai (#24)

Kouhei Kaigai <kaigai@ak.jp.nec.com> writes:

Let me explain the current idea of mine.
CustomScan node will have a field that hold varnode mapping information
that is constructed by custom-scan provider on create_customscan_plan,
if they want. It is probably a list of varnode.
If exists, setrefs.c changes its behavior; that updates varno/varattno of
varnode according to this mapping, as if set_join_references() does
based on indexed_tlist.
To reference exct_scantuple, INDEX_VAR will be a best choice for varno
of these varnodes, and index of the above varnode mapping list will
be varattno. It can be utilized to make EXPLAIN output, instead of
GetSpecialCustomVar hook.

So, steps to go may be:
(1) Add custom_private, custom_exprs, ... instead of self defined data
type based on CustomXXX.
(2) Rid of SetCustomScanRef and GetSpecialCustomVar hook for the current
custom-"scan" support.
(3) Integration of above varnode mapping feature within upcoming join
replacement by custom-scan support.

Well ... I still do not find this interesting, because I don't believe
that CustomScan is a solution to anything interesting. It's difficult
enough to solve problems like expensive-function pushdown within the
core code; why would we tie one hand behind our backs by insisting that
they should be solved by extensions? And as I mentioned before, we do
need solutions to these problems in the core, regardless of CustomScan.

I think that a useful way to go at this might be to think first about
how to make use of expensive functions that have been cached in indexes,
and then see how the solution to that might translate to pushing down
expensive functions into FDWs and CustomScans. If you start with the
CustomScan aspect of it then you immediately find yourself trying to
design APIs to divide up the solution, which is premature when you
don't even know what the solution is.

The rough idea I'd had about this is that while canvassing a relation's
indexes (in get_relation_info), we could create a list of precomputed
expressions that are available from indexes, then run through the
query tree and replace any matching subexpressions with some Var-like
nodes (or maybe better PlaceHolderVar-like nodes) that indicate that
"we can get this expression for free if we read the right index".
If we do read the right index, such an expression reduces to a Var in
the finished plan tree; if not, it reverts to the original expression.
(Some thought would need to be given to the semantics when the index's
table is underneath an outer join --- that may just mean that we can't
necessarily replace every textually-matching subexpression, only those
that are not above an outer join.) One question mark here is how to do
the "replace any matching subexpressions" bit without O(lots) processing
cost in big queries. But that's probably just a SMOP. The bigger issue
I fear is that the planner is not currently structured to think that
evaluation cost of expressions in the SELECT list has anything to do
with which Path it should pick. That is tied to the handwaving I've
been doing for awhile now about converting all the upper-level planning
logic into generate-and-compare-Paths style; we certainly cannot ignore
tlist eval costs while making those decisions. So at least for those
upper-level Paths, we'd have to have a notion of what tlist we expect
that plan level to compute, and charge appropriate evaluation costs.

So there's a lot of work there and I don't find that CustomScan looks
like a solution to any of it. CustomScan and FDWs could benefit from
this work, in that we'd now have a way to deal with the concept that
expensive functions (and aggregates, I hope) might be computed at
the bottom scan level. But it's folly to suppose that we can make it
work just by hacking some arms-length extension code without any
fundamental planner changes.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#26Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Tom Lane (#25)

Kouhei Kaigai <kaigai@ak.jp.nec.com> writes:

Let me explain the current idea of mine.
CustomScan node will have a field that hold varnode mapping
information that is constructed by custom-scan provider on
create_customscan_plan, if they want. It is probably a list of varnode.
If exists, setrefs.c changes its behavior; that updates varno/varattno
of varnode according to this mapping, as if set_join_references() does
based on indexed_tlist.
To reference exct_scantuple, INDEX_VAR will be a best choice for varno
of these varnodes, and index of the above varnode mapping list will be
varattno. It can be utilized to make EXPLAIN output, instead of
GetSpecialCustomVar hook.

So, steps to go may be:
(1) Add custom_private, custom_exprs, ... instead of self defined data
type based on CustomXXX.
(2) Rid of SetCustomScanRef and GetSpecialCustomVar hook for the current
custom-"scan" support.
(3) Integration of above varnode mapping feature within upcoming join
replacement by custom-scan support.

Well ... I still do not find this interesting, because I don't believe that
CustomScan is a solution to anything interesting. It's difficult enough
to solve problems like expensive-function pushdown within the core code;
why would we tie one hand behind our backs by insisting that they should
be solved by extensions? And as I mentioned before, we do need solutions
to these problems in the core, regardless of CustomScan.

I'd like to split the "anything interesting" into two portions.
As you pointed out, the feature to push-down complicated expression
may need a bit large efforts (for remaining two commit-fest at least),
however, what the feature to replace join by custom-scan requires is
similar to job of set_join_references() because it never involves
translation between varnode and general expression.

Also, from my standpoint, a simple join replacement by custom-scan has
higher priority; join acceleration in v9.5 makes sense even if full-
functionality of pushing down general expression is not supported yet.

I think that a useful way to go at this might be to think first about how
to make use of expensive functions that have been cached in indexes, and
then see how the solution to that might translate to pushing down expensive
functions into FDWs and CustomScans. If you start with the CustomScan
aspect of it then you immediately find yourself trying to design APIs to
divide up the solution, which is premature when you don't even know what
the solution is.

Yep, it also seems to me remaining two commit fests are a bit tight
schedule to make consensus of overall design and to implement.
I'd like to focus on the simpler portion first.

The rough idea I'd had about this is that while canvassing a relation's
indexes (in get_relation_info), we could create a list of precomputed
expressions that are available from indexes, then run through the query
tree and replace any matching subexpressions with some Var-like nodes (or
maybe better PlaceHolderVar-like nodes) that indicate that "we can get this
expression for free if we read the right index".
If we do read the right index, such an expression reduces to a Var in the
finished plan tree; if not, it reverts to the original expression.
(Some thought would need to be given to the semantics when the index's table
is underneath an outer join --- that may just mean that we can't necessarily
replace every textually-matching subexpression, only those that are not
above an outer join.) One question mark here is how to do the "replace
any matching subexpressions" bit without O(lots) processing cost in big
queries. But that's probably just a SMOP. The bigger issue I fear is that
the planner is not currently structured to think that evaluation cost of
expressions in the SELECT list has anything to do with which Path it should
pick. That is tied to the handwaving I've been doing for awhile now about
converting all the upper-level planning logic into
generate-and-compare-Paths style; we certainly cannot ignore tlist eval
costs while making those decisions. So at least for those upper-level Paths,
we'd have to have a notion of what tlist we expect that plan level to compute,
and charge appropriate evaluation costs.

Let me investigate the planner code more prior to comment on...

So there's a lot of work there and I don't find that CustomScan looks like
a solution to any of it. CustomScan and FDWs could benefit from this work,
in that we'd now have a way to deal with the concept that expensive functions
(and aggregates, I hope) might be computed at the bottom scan level. But
it's folly to suppose that we can make it work just by hacking some
arms-length extension code without any fundamental planner changes.

Indeed, I don't think it is a good idea to start from this harder portion.
Let's focus on just varno/varattno remapping to replace join relation by
custom-scan, as an immediate target.

Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Robert Haas
robertmhaas@gmail.com
In reply to: Kouhei Kaigai (#26)

On Mon, Nov 24, 2014 at 6:57 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

Indeed, I don't think it is a good idea to start from this harder portion.
Let's focus on just varno/varattno remapping to replace join relation by
custom-scan, as an immediate target.

We still need something like this for FDWs, as well. The potential
gains there are enormous. Anything we do had better fit in nicely
with that, rather than looking like a separate hack.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#27)

On Mon, Nov 24, 2014 at 6:57 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

Indeed, I don't think it is a good idea to start from this harder portion.
Let's focus on just varno/varattno remapping to replace join relation
by custom-scan, as an immediate target.

We still need something like this for FDWs, as well. The potential gains
there are enormous. Anything we do had better fit in nicely with that,
rather than looking like a separate hack.

Today, I had a talk with Hanada-san to clarify which can be a common portion
of them and how to implement it. Then, we concluded both of features can be
shared most of the infrastructure.
Let me put an introduction of join replacement by foreign-/custom-scan below.

Its overall design intends to inject foreign-/custom-scan node instead of
the built-in join logic (based on the estimated cost). From the viewpoint of
core backend, it looks like a sub-query scan that contains relations join
internally.

What we need to do is below:

(1) Add a hook add_paths_to_joinrel()
It gives extensions (including FDW drivers and custom-scan providers) chance
to add alternative paths towards a particular join of relations, using
ForeignScanPath or CustomScanPath, if it can run instead of the built-in ones.

(2) Informs the core backend varno/varattno mapping
One thing we need to pay attention is, foreign-/custom-scan node that performs
instead of the built-in join node must return mixture of values come from both
relations. In case when FDW driver fetch a remote record (also, fetch a record
computed by external computing resource), the most reasonable way is to store
it on ecxt_scantuple of ExprContext, then kicks projection with varnode that
references this slot.
It needs an infrastructure that tracks relationship between original varnode
and the alternative varno/varattno. We thought, it shall be mapped to INDEX_VAR
and a virtual attribute number to reference ecxt_scantuple naturally, and
this infrastructure is quite helpful for both of ForegnScan/CustomScan.
We'd like to add List *fdw_varmap/*custom_varmap variable to both of plan nodes.
It contains list of the original Var node that shall be mapped on the position
according to the list index. (e.g, the first varnode is varno=INDEX_VAR and
varattno=1)

(3) Reverse mapping on EXPLAIN
For EXPLAIN support, above varnode on the pseudo relation scan needed to be
solved. All we need to do is initialization of dpns->inner_tlist on
set_deparse_planstate() according to the above mapping.

(4) case of scanrelid == 0
To skip open/close (foreign) tables, we need to have a mark to introduce the
backend not to initialize the scan node according to table definition, but
according to the pseudo varnodes list.
As earlier custom-scan patch doing, scanrelid == 0 is a straightforward mark
to show the scan node is not combined with a particular real relation.
So, it also need to add special case handling around foreign-/custom-scan code.

We expect above changes are enough small to implement basic join push-down
functionality (that does not involves external computing of complicated
expression node), but valuable to support in v9.5.

Please comment on the proposition above.

Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#29Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#8)

On 7 November 2014 at 22:46, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Oct 27, 2014 at 2:35 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

FYI, patch v12 part 2 no longer applies cleanly.

Thanks. I rebased the patch set according to the latest master branch.
The attached v13 can be applied to the master.

I've committed parts 1 and 2 of this, without the documentation, and
with some additional cleanup. I am not sure that this feature is
sufficiently non-experimental that it deserves to be documented, but
if we're thinking of doing that then the documentation needs a lot
more work. I think part 3 of the patch is mostly useful as a
demonstration of how this API can be used, and is not something we
probably want to commit. So I'm not planning, at this point, to spend
any more time on this patch series, and will mark it Committed in the
CF app.

I'm very concerned about the state of this feature. No docs, no
examples, and therefore, no testing. This standard of code is much
less than I've been taught is the minimum standard on this project.

There are zero docs, even in README. Experimental feature, or not,
there MUST be documentation somewhere, in some form, even if that is
just on the Wiki. Otherwise how it will ever be used sufficiently to
allow it to be declared fully usable?

The example contrib module was not committed and I am advised no longer works.

After much effort in persuading academic contacts to begin using the
feature for open source research it now appears pretty much unusable.

This is supposed to be an open project. Whoever takes responsibility
here, please ensure that those things are resolved, quickly.

We're on a time limit because any flaws in the API need to be ironed
out before its too late and we have to decide to either remove the API
because its flaky, or commit to supporting it in production for 9.5.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Simon Riggs (#29)

On 7 November 2014 at 22:46, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Oct 27, 2014 at 2:35 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com>

wrote:

FYI, patch v12 part 2 no longer applies cleanly.

Thanks. I rebased the patch set according to the latest master branch.
The attached v13 can be applied to the master.

I've committed parts 1 and 2 of this, without the documentation, and
with some additional cleanup. I am not sure that this feature is
sufficiently non-experimental that it deserves to be documented, but
if we're thinking of doing that then the documentation needs a lot
more work. I think part 3 of the patch is mostly useful as a
demonstration of how this API can be used, and is not something we
probably want to commit. So I'm not planning, at this point, to spend
any more time on this patch series, and will mark it Committed in the
CF app.

I'm very concerned about the state of this feature. No docs, no examples,
and therefore, no testing. This standard of code is much less than I've
been taught is the minimum standard on this project.

There are zero docs, even in README. Experimental feature, or not, there
MUST be documentation somewhere, in some form, even if that is just on the
Wiki. Otherwise how it will ever be used sufficiently to allow it to be
declared fully usable?

The reason why documentation portion was not yet committed is, sorry, it
is due to quality of documentation from the standpoint of native English
speaker.
Now, I'm writing up a documentation stuff according to the latest code base,
please wait for several days and help to improve.

The example contrib module was not committed and I am advised no longer
works.

May I submit the contrib/ctidscan module again for an example?

Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#31Simon Riggs
simon@2ndQuadrant.com
In reply to: Kouhei Kaigai (#30)

On 27 November 2014 at 10:33, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

The reason why documentation portion was not yet committed is, sorry, it
is due to quality of documentation from the standpoint of native English
speaker.
Now, I'm writing up a documentation stuff according to the latest code base,
please wait for several days and help to improve.

Happy to help with that.

Please post to the Wiki first so we can edit it communally.

The example contrib module was not committed and I am advised no longer
works.

May I submit the contrib/ctidscan module again for an example?

Yes please. We have other contrib modules that exist as tests, so this
seems reasonable to me.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#32Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Simon Riggs (#31)

-----Original Message-----
From: Simon Riggs [mailto:simon@2ndQuadrant.com]
Sent: Thursday, November 27, 2014 8:48 PM
To: Kaigai Kouhei(海外 浩平)
Cc: Robert Haas; Thom Brown; Kohei KaiGai; Tom Lane; Alvaro Herrera; Shigeru
Hanada; Stephen Frost; Andres Freund; PgHacker; Jim Mlodgenski; Peter
Eisentraut
Subject: Re: [HACKERS] [v9.5] Custom Plan API

On 27 November 2014 at 10:33, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

The reason why documentation portion was not yet committed is, sorry,
it is due to quality of documentation from the standpoint of native
English speaker.
Now, I'm writing up a documentation stuff according to the latest code
base, please wait for several days and help to improve.

Happy to help with that.

Please post to the Wiki first so we can edit it communally.

Simon,

I tried to describe how custom-scan provider interact with the core backend,
and expectations to individual callbacks here.

https://wiki.postgresql.org/wiki/CustomScanInterface

I'd like to see which kind of description should be added, from third person's
viewpoint.

Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33Robert Haas
robertmhaas@gmail.com
In reply to: Kouhei Kaigai (#28)

On Tue, Nov 25, 2014 at 3:44 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

Today, I had a talk with Hanada-san to clarify which can be a common portion
of them and how to implement it. Then, we concluded both of features can be
shared most of the infrastructure.
Let me put an introduction of join replacement by foreign-/custom-scan below.

Its overall design intends to inject foreign-/custom-scan node instead of
the built-in join logic (based on the estimated cost). From the viewpoint of
core backend, it looks like a sub-query scan that contains relations join
internally.

What we need to do is below:

(1) Add a hook add_paths_to_joinrel()
It gives extensions (including FDW drivers and custom-scan providers) chance
to add alternative paths towards a particular join of relations, using
ForeignScanPath or CustomScanPath, if it can run instead of the built-in ones.

(2) Informs the core backend varno/varattno mapping
One thing we need to pay attention is, foreign-/custom-scan node that performs
instead of the built-in join node must return mixture of values come from both
relations. In case when FDW driver fetch a remote record (also, fetch a record
computed by external computing resource), the most reasonable way is to store
it on ecxt_scantuple of ExprContext, then kicks projection with varnode that
references this slot.
It needs an infrastructure that tracks relationship between original varnode
and the alternative varno/varattno. We thought, it shall be mapped to INDEX_VAR
and a virtual attribute number to reference ecxt_scantuple naturally, and
this infrastructure is quite helpful for both of ForegnScan/CustomScan.
We'd like to add List *fdw_varmap/*custom_varmap variable to both of plan nodes.
It contains list of the original Var node that shall be mapped on the position
according to the list index. (e.g, the first varnode is varno=INDEX_VAR and
varattno=1)

(3) Reverse mapping on EXPLAIN
For EXPLAIN support, above varnode on the pseudo relation scan needed to be
solved. All we need to do is initialization of dpns->inner_tlist on
set_deparse_planstate() according to the above mapping.

(4) case of scanrelid == 0
To skip open/close (foreign) tables, we need to have a mark to introduce the
backend not to initialize the scan node according to table definition, but
according to the pseudo varnodes list.
As earlier custom-scan patch doing, scanrelid == 0 is a straightforward mark
to show the scan node is not combined with a particular real relation.
So, it also need to add special case handling around foreign-/custom-scan code.

We expect above changes are enough small to implement basic join push-down
functionality (that does not involves external computing of complicated
expression node), but valuable to support in v9.5.

Please comment on the proposition above.

I don't really have any technical comments on this design right at the
moment, but I think it's an important area where PostgreSQL needs to
make some progress sooner rather than later, so I hope that we can get
something committed in time for 9.5.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#34Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#31)

On 27 November 2014 at 20:48, Simon Riggs <simon@2ndquadrant.com> wrote:

On 27 November 2014 at 10:33, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

The reason why documentation portion was not yet committed is, sorry, it
is due to quality of documentation from the standpoint of native English
speaker.
Now, I'm writing up a documentation stuff according to the latest code base,
please wait for several days and help to improve.

Happy to help with that.

Please post to the Wiki first so we can edit it communally.

I've corrected a spelling mistake, but it reads OK at moment.

The example contrib module was not committed and I am advised no longer
works.

May I submit the contrib/ctidscan module again for an example?

Yes please. We have other contrib modules that exist as tests, so this
seems reasonable to me.

I can't improve the docs without the example code. Is that available now?

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#35Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Simon Riggs (#34)

Simon,

Yes please. We have other contrib modules that exist as tests, so this
seems reasonable to me.

I can't improve the docs without the example code. Is that available now?

Please wait for a few days. The ctidscan module is not adjusted for the
latest interface yet.

--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

-----Original Message-----
From: Simon Riggs [mailto:simon@2ndQuadrant.com]
Sent: Sunday, December 07, 2014 12:37 AM
To: Kaigai Kouhei(海外 浩平)
Cc: Robert Haas; Thom Brown; Kohei KaiGai; Tom Lane; Alvaro Herrera; Shigeru
Hanada; Stephen Frost; Andres Freund; PgHacker; Jim Mlodgenski; Peter
Eisentraut
Subject: Re: [HACKERS] [v9.5] Custom Plan API

On 27 November 2014 at 20:48, Simon Riggs <simon@2ndquadrant.com> wrote:

On 27 November 2014 at 10:33, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

The reason why documentation portion was not yet committed is, sorry,
it is due to quality of documentation from the standpoint of native
English speaker.
Now, I'm writing up a documentation stuff according to the latest
code base, please wait for several days and help to improve.

Happy to help with that.

Please post to the Wiki first so we can edit it communally.

I've corrected a spelling mistake, but it reads OK at moment.

The example contrib module was not committed and I am advised no
longer works.

May I submit the contrib/ctidscan module again for an example?

Yes please. We have other contrib modules that exist as tests, so this
seems reasonable to me.

I can't improve the docs without the example code. Is that available now?

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#36Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Kouhei Kaigai (#35)

On 12/6/14, 5:21 PM, Kouhei Kaigai wrote:

Yes please. We have other contrib modules that exist as tests, so this
seems reasonable to me.

I can't improve the docs without the example code. Is that available now?

Please wait for a few days. The ctidscan module is not adjusted for the
latest interface yet.

I've made some minor edits, with an emphasis on not changing original intent. Each section was saved as a separate edit, so if anyone objects to something just revert the relevant change. Once the code is available more editing can be done.
--
Jim Nasby, Data Architect, Blue Treble Consulting
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Simon Riggs
simon@2ndQuadrant.com
In reply to: Kouhei Kaigai (#35)

On 7 December 2014 at 08:21, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

Please wait for a few days. The ctidscan module is not adjusted for the
latest interface yet.

I am in many ways a patient man. At this point it is 12 days since my
request for a working example.

Feedback I am receiving is that the API is unusable. That could be
because it is impenetrable, or because it is unusable. I'm not sure it
matters which.

We need a working example to ensure that the API meets the needs of a
wide section of users and if it does not, to give other users a chance
to request changes to the API so that it becomes usable. The window
for such feedback is approaching zero very quickly now and we need
action.

Thanks

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Simon Riggs (#37)

Simon,

The sample code is here:
https://github.com/kaigai/ctidscan

The code itself and regression tests shows how does it work and
interact with the core backend.

However, its source code comments are not updated and SGML document
is not ready yet, because of my schedule in earlier half of December.
I try to add the above stuff for a patch of contrib module, but will
take a few more days.

Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>

-----Original Message-----
From: Simon Riggs [mailto:simon@2ndQuadrant.com]
Sent: Tuesday, December 09, 2014 12:24 AM
To: Kaigai Kouhei(海外 浩平)
Cc: Robert Haas; Thom Brown; Kohei KaiGai; Tom Lane; Alvaro Herrera; Shigeru
Hanada; Stephen Frost; Andres Freund; PgHacker; Jim Mlodgenski; Peter
Eisentraut
Subject: Re: [HACKERS] [v9.5] Custom Plan API

On 7 December 2014 at 08:21, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

Please wait for a few days. The ctidscan module is not adjusted for
the latest interface yet.

I am in many ways a patient man. At this point it is 12 days since my request
for a working example.

Feedback I am receiving is that the API is unusable. That could be because
it is impenetrable, or because it is unusable. I'm not sure it matters which.

We need a working example to ensure that the API meets the needs of a wide
section of users and if it does not, to give other users a chance to request
changes to the API so that it becomes usable. The window for such feedback
is approaching zero very quickly now and we need action.

Thanks

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#39Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#37)

On Tue, Dec 9, 2014 at 3:24 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

Feedback I am receiving is that the API is unusable. That could be
because it is impenetrable, or because it is unusable. I'm not sure it
matters which.

It would be nice to here what someone is trying to use it for and what
problems that person is encountering. Without that, it's pretty much
impossible for anyone to fix anything.

As for sample code, KaiGai had a working example, which of course got
broken when Tom changed the API, but it didn't look to me like Tom's
changes would have made anything impossible that was possible before.
I'm frankly kind of astonished by the tenor of this entire
conversation; there is certainly plenty of code in the backend that is
less self-documenting than this is; and KaiGai did already put up a
wiki page with documentation as you requested. From his response, it
sounds like he has updated the ctidscan code, too.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers